mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-06 02:47:56 +00:00
Add tests, remove unused code
This commit is contained in:
parent
bf1aa4c7b2
commit
42108193c7
3 changed files with 342 additions and 108 deletions
338
src/api/transform/__tests__/gemini-format.test.ts
Normal file
338
src/api/transform/__tests__/gemini-format.test.ts
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
// npx jest src/api/transform/__tests__/gemini-format.test.ts
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import { convertAnthropicMessageToGemini } from "../gemini-format"
|
||||
|
||||
describe("convertAnthropicMessageToGemini", () => {
|
||||
it("should convert a simple text message", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: "Hello, world!",
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [{ text: "Hello, world!" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert assistant role to model role", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "assistant",
|
||||
content: "I'm an assistant",
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "model",
|
||||
parts: [{ text: "I'm an assistant" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert a message with text blocks", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "First paragraph" },
|
||||
{ type: "text", text: "Second paragraph" },
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [{ text: "First paragraph" }, { text: "Second paragraph" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert a message with an image", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Check out this image:" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/jpeg",
|
||||
data: "base64encodeddata",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [
|
||||
{ text: "Check out this image:" },
|
||||
{
|
||||
inlineData: {
|
||||
data: "base64encodeddata",
|
||||
mimeType: "image/jpeg",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw an error for unsupported image source type", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "url", // Not supported
|
||||
url: "https://example.com/image.jpg",
|
||||
} as any,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
expect(() => convertAnthropicMessageToGemini(anthropicMessage)).toThrow("Unsupported image source type")
|
||||
})
|
||||
|
||||
it("should convert a message with tool use", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me calculate that for you." },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "calc-123",
|
||||
name: "calculator",
|
||||
input: { operation: "add", numbers: [2, 3] },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "model",
|
||||
parts: [
|
||||
{ text: "Let me calculate that for you." },
|
||||
{
|
||||
functionCall: {
|
||||
name: "calculator",
|
||||
args: { operation: "add", numbers: [2, 3] },
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert a message with tool result as string", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Here's the result:" },
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "calculator-123",
|
||||
content: "The result is 5",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [
|
||||
{ text: "Here's the result:" },
|
||||
{
|
||||
functionResponse: {
|
||||
name: "calculator",
|
||||
response: {
|
||||
name: "calculator",
|
||||
content: "The result is 5",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle empty tool result content", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "calculator-123",
|
||||
content: null as any, // Empty content
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
// Should skip the empty tool result
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert a message with tool result as array with text only", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "search-123",
|
||||
content: [
|
||||
{ type: "text", text: "First result" },
|
||||
{ type: "text", text: "Second result" },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
name: "search",
|
||||
response: {
|
||||
name: "search",
|
||||
content: "First result\n\nSecond result",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert a message with tool result as array with text and images", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "search-123",
|
||||
content: [
|
||||
{ type: "text", text: "Search results:" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "image1data",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/jpeg",
|
||||
data: "image2data",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
name: "search",
|
||||
response: {
|
||||
name: "search",
|
||||
content: "Search results:\n\n(See next part for image)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
inlineData: {
|
||||
data: "image1data",
|
||||
mimeType: "image/png",
|
||||
},
|
||||
},
|
||||
{
|
||||
inlineData: {
|
||||
data: "image2data",
|
||||
mimeType: "image/jpeg",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should convert a message with tool result containing only images", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "imagesearch-123",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "onlyimagedata",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = convertAnthropicMessageToGemini(anthropicMessage)
|
||||
|
||||
expect(result).toEqual({
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
name: "imagesearch",
|
||||
response: {
|
||||
name: "imagesearch",
|
||||
content: "\n\n(See next part for image)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
inlineData: {
|
||||
data: "onlyimagedata",
|
||||
mimeType: "image/png",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw an error for unsupported content block type", () => {
|
||||
const anthropicMessage: Anthropic.Messages.MessageParam = {
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "unknown_type", // Unsupported type
|
||||
data: "some data",
|
||||
} as any,
|
||||
],
|
||||
}
|
||||
|
||||
expect(() => convertAnthropicMessageToGemini(anthropicMessage)).toThrow(
|
||||
"Unsupported content block type: unknown_type",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,20 +1,11 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import {
|
||||
Content,
|
||||
EnhancedGenerateContentResponse,
|
||||
FunctionCallPart,
|
||||
FunctionDeclaration,
|
||||
FunctionResponsePart,
|
||||
InlineDataPart,
|
||||
Part,
|
||||
SchemaType,
|
||||
TextPart,
|
||||
} from "@google/generative-ai"
|
||||
import { Content, FunctionCallPart, FunctionResponsePart, InlineDataPart, Part, TextPart } from "@google/generative-ai"
|
||||
|
||||
export function convertAnthropicContentToGemini(content: Anthropic.Messages.MessageParam["content"]): Part[] {
|
||||
function convertAnthropicContentToGemini(content: Anthropic.Messages.MessageParam["content"]): Part[] {
|
||||
if (typeof content === "string") {
|
||||
return [{ text: content } as TextPart]
|
||||
}
|
||||
|
||||
return content.flatMap((block) => {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
|
|
@ -90,99 +81,3 @@ export function convertAnthropicMessageToGemini(message: Anthropic.Messages.Mess
|
|||
parts: convertAnthropicContentToGemini(message.content),
|
||||
}
|
||||
}
|
||||
|
||||
export function convertAnthropicToolToGemini(tool: Anthropic.Messages.Tool): FunctionDeclaration {
|
||||
return {
|
||||
name: tool.name,
|
||||
description: tool.description || "",
|
||||
parameters: {
|
||||
type: SchemaType.OBJECT,
|
||||
properties: Object.fromEntries(
|
||||
Object.entries(tool.input_schema.properties || {}).map(([key, value]) => [
|
||||
key,
|
||||
{
|
||||
type: (value as any).type.toUpperCase(),
|
||||
description: (value as any).description || "",
|
||||
},
|
||||
]),
|
||||
),
|
||||
required: (tool.input_schema.required as string[]) || [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
It looks like gemini likes to double escape certain characters when writing file contents: https://discuss.ai.google.dev/t/function-call-string-property-is-double-escaped/37867
|
||||
*/
|
||||
export function unescapeGeminiContent(content: string) {
|
||||
return content
|
||||
.replace(/\\n/g, "\n")
|
||||
.replace(/\\'/g, "'")
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\r/g, "\r")
|
||||
.replace(/\\t/g, "\t")
|
||||
}
|
||||
|
||||
export function convertGeminiResponseToAnthropic(
|
||||
response: EnhancedGenerateContentResponse,
|
||||
): Anthropic.Messages.Message {
|
||||
const content: Anthropic.Messages.ContentBlock[] = []
|
||||
|
||||
// Add the main text response
|
||||
const text = response.text()
|
||||
if (text) {
|
||||
content.push({ type: "text", text, citations: null })
|
||||
}
|
||||
|
||||
// Add function calls as tool_use blocks
|
||||
const functionCalls = response.functionCalls()
|
||||
if (functionCalls) {
|
||||
functionCalls.forEach((call, index) => {
|
||||
if ("content" in call.args && typeof call.args.content === "string") {
|
||||
call.args.content = unescapeGeminiContent(call.args.content)
|
||||
}
|
||||
content.push({
|
||||
type: "tool_use",
|
||||
id: `${call.name}-${index}-${Date.now()}`,
|
||||
name: call.name,
|
||||
input: call.args,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Determine stop reason
|
||||
let stop_reason: Anthropic.Messages.Message["stop_reason"] = null
|
||||
const finishReason = response.candidates?.[0]?.finishReason
|
||||
if (finishReason) {
|
||||
switch (finishReason) {
|
||||
case "STOP":
|
||||
stop_reason = "end_turn"
|
||||
break
|
||||
case "MAX_TOKENS":
|
||||
stop_reason = "max_tokens"
|
||||
break
|
||||
case "SAFETY":
|
||||
case "RECITATION":
|
||||
case "OTHER":
|
||||
stop_reason = "stop_sequence"
|
||||
break
|
||||
// Add more cases if needed
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: `msg_${Date.now()}`, // Generate a unique ID
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content,
|
||||
model: "",
|
||||
stop_reason,
|
||||
stop_sequence: null, // Gemini doesn't provide this information
|
||||
usage: {
|
||||
input_tokens: response.usageMetadata?.promptTokenCount ?? 0,
|
||||
output_tokens: response.usageMetadata?.candidatesTokenCount ?? 0,
|
||||
cache_creation_input_tokens: null,
|
||||
cache_read_input_tokens: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ export async function convertToAnthropicMessage(
|
|||
vsCodeLmMessage: vscode.LanguageModelChatMessage,
|
||||
): Promise<Anthropic.Messages.Message> {
|
||||
const anthropicRole: string | null = convertToAnthropicRole(vsCodeLmMessage.role)
|
||||
|
||||
if (anthropicRole !== "assistant") {
|
||||
throw new Error("Roo Code <Language Model API>: Only assistant messages are supported.")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue