mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Remove unused code
This commit is contained in:
parent
09902904f3
commit
52030b12e1
7 changed files with 349 additions and 810 deletions
|
|
@ -9,7 +9,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
|||
import { ApiHandler, SingleCompletionHandler } from "../"
|
||||
import { ApiHandlerOptions, BedrockModelId, ModelInfo, bedrockDefaultModelId, bedrockModels } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToBedrockConverseMessages, convertToAnthropicMessage } from "../transform/bedrock-converse-format"
|
||||
import { convertToBedrockConverseMessages } from "../transform/bedrock-converse-format"
|
||||
|
||||
const BEDROCK_DEFAULT_TEMPERATURE = 0.3
|
||||
|
||||
|
|
|
|||
|
|
@ -1,254 +1,167 @@
|
|||
// npx jest src/api/transform/__tests__/bedrock-converse-format.test.ts
|
||||
|
||||
import { convertToBedrockConverseMessages, convertToAnthropicMessage } from "../bedrock-converse-format"
|
||||
import { convertToBedrockConverseMessages } from "../bedrock-converse-format"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ContentBlock, ToolResultContentBlock } from "@aws-sdk/client-bedrock-runtime"
|
||||
import { StreamEvent } from "../../providers/bedrock"
|
||||
|
||||
describe("bedrock-converse-format", () => {
|
||||
describe("convertToBedrockConverseMessages", () => {
|
||||
test("converts simple text messages correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
]
|
||||
describe("convertToBedrockConverseMessages", () => {
|
||||
test("converts simple text messages correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ text: "Hello" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ text: "Hi there" }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("converts messages with images correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Look at this image:",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
data: "SGVsbG8=", // "Hello" in base64
|
||||
media_type: "image/jpeg" as const,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("user")
|
||||
expect(result[0].content).toHaveLength(2)
|
||||
expect(result[0].content[0]).toEqual({ text: "Look at this image:" })
|
||||
|
||||
const imageBlock = result[0].content[1] as ContentBlock
|
||||
if ("image" in imageBlock && imageBlock.image && imageBlock.image.source) {
|
||||
expect(imageBlock.image.format).toBe("jpeg")
|
||||
expect(imageBlock.image.source).toBeDefined()
|
||||
expect(imageBlock.image.source.bytes).toBeDefined()
|
||||
} else {
|
||||
fail("Expected image block not found")
|
||||
}
|
||||
})
|
||||
|
||||
test("converts tool use messages correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "test-id",
|
||||
name: "read_file",
|
||||
input: {
|
||||
path: "test.txt",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("assistant")
|
||||
const toolBlock = result[0].content[0] as ContentBlock
|
||||
if ("toolUse" in toolBlock && toolBlock.toolUse) {
|
||||
expect(toolBlock.toolUse).toEqual({
|
||||
toolUseId: "test-id",
|
||||
name: "read_file",
|
||||
input: "<read_file>\n<path>\ntest.txt\n</path>\n</read_file>",
|
||||
})
|
||||
} else {
|
||||
fail("Expected tool use block not found")
|
||||
}
|
||||
})
|
||||
|
||||
test("converts tool result messages correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "test-id",
|
||||
content: [{ type: "text", text: "File contents here" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("assistant")
|
||||
const resultBlock = result[0].content[0] as ContentBlock
|
||||
if ("toolResult" in resultBlock && resultBlock.toolResult) {
|
||||
const expectedContent: ToolResultContentBlock[] = [{ text: "File contents here" }]
|
||||
expect(resultBlock.toolResult).toEqual({
|
||||
toolUseId: "test-id",
|
||||
content: expectedContent,
|
||||
status: "success",
|
||||
})
|
||||
} else {
|
||||
fail("Expected tool result block not found")
|
||||
}
|
||||
})
|
||||
|
||||
test("handles text content correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Hello world",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("user")
|
||||
expect(result[0].content).toHaveLength(1)
|
||||
const textBlock = result[0].content[0] as ContentBlock
|
||||
expect(textBlock).toEqual({ text: "Hello world" })
|
||||
})
|
||||
expect(result).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ text: "Hello" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ text: "Hi there" }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
describe("convertToAnthropicMessage", () => {
|
||||
test("converts metadata events correctly", () => {
|
||||
const event: StreamEvent = {
|
||||
metadata: {
|
||||
usage: {
|
||||
inputTokens: 10,
|
||||
outputTokens: 20,
|
||||
test("converts messages with images correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Look at this image:",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const result = convertToAnthropicMessage(event, "test-model")
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "",
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
model: "test-model",
|
||||
usage: {
|
||||
input_tokens: 10,
|
||||
output_tokens: 20,
|
||||
cache_creation_input_tokens: null,
|
||||
cache_read_input_tokens: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("converts content block start events correctly", () => {
|
||||
const event: StreamEvent = {
|
||||
contentBlockStart: {
|
||||
start: {
|
||||
text: "Hello",
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
data: "SGVsbG8=", // "Hello" in base64
|
||||
media_type: "image/jpeg" as const,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAnthropicMessage(event, "test-model")
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "message",
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("user")
|
||||
expect(result[0].content).toHaveLength(2)
|
||||
expect(result[0].content[0]).toEqual({ text: "Look at this image:" })
|
||||
|
||||
const imageBlock = result[0].content[1] as ContentBlock
|
||||
if ("image" in imageBlock && imageBlock.image && imageBlock.image.source) {
|
||||
expect(imageBlock.image.format).toBe("jpeg")
|
||||
expect(imageBlock.image.source).toBeDefined()
|
||||
expect(imageBlock.image.source.bytes).toBeDefined()
|
||||
} else {
|
||||
fail("Expected image block not found")
|
||||
}
|
||||
})
|
||||
|
||||
test("converts tool use messages correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Hello", citations: null }],
|
||||
model: "test-model",
|
||||
})
|
||||
})
|
||||
|
||||
test("converts content block delta events correctly", () => {
|
||||
const event: StreamEvent = {
|
||||
contentBlockDelta: {
|
||||
delta: {
|
||||
text: " world",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "test-id",
|
||||
name: "read_file",
|
||||
input: {
|
||||
path: "test.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAnthropicMessage(event, "test-model")
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: " world", citations: null }],
|
||||
model: "test-model",
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("assistant")
|
||||
const toolBlock = result[0].content[0] as ContentBlock
|
||||
if ("toolUse" in toolBlock && toolBlock.toolUse) {
|
||||
expect(toolBlock.toolUse).toEqual({
|
||||
toolUseId: "test-id",
|
||||
name: "read_file",
|
||||
input: "<read_file>\n<path>\ntest.txt\n</path>\n</read_file>",
|
||||
})
|
||||
})
|
||||
} else {
|
||||
fail("Expected tool use block not found")
|
||||
}
|
||||
})
|
||||
|
||||
test("converts message stop events correctly", () => {
|
||||
const event: StreamEvent = {
|
||||
messageStop: {
|
||||
stopReason: "end_turn" as const,
|
||||
},
|
||||
}
|
||||
|
||||
const result = convertToAnthropicMessage(event, "test-model")
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "message",
|
||||
test("converts tool result messages correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
model: "test-model",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "test-id",
|
||||
content: [{ type: "text", text: "File contents here" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("assistant")
|
||||
const resultBlock = result[0].content[0] as ContentBlock
|
||||
if ("toolResult" in resultBlock && resultBlock.toolResult) {
|
||||
const expectedContent: ToolResultContentBlock[] = [{ text: "File contents here" }]
|
||||
expect(resultBlock.toolResult).toEqual({
|
||||
toolUseId: "test-id",
|
||||
content: expectedContent,
|
||||
status: "success",
|
||||
})
|
||||
})
|
||||
} else {
|
||||
fail("Expected tool result block not found")
|
||||
}
|
||||
})
|
||||
|
||||
test("handles text content correctly", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Hello world",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
if (!result[0] || !result[0].content) {
|
||||
fail("Expected result to have content")
|
||||
return
|
||||
}
|
||||
|
||||
expect(result[0].role).toBe("user")
|
||||
expect(result[0].content).toHaveLength(1)
|
||||
const textBlock = result[0].content[0] as ContentBlock
|
||||
expect(textBlock).toEqual({ text: "Hello world" })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,281 +1,131 @@
|
|||
// npx jest src/api/transform/__tests__/openai-format.test.ts
|
||||
|
||||
import { convertToOpenAiMessages, convertToAnthropicMessage } from "../openai-format"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
type PartialChatCompletion = Omit<OpenAI.Chat.Completions.ChatCompletion, "choices"> & {
|
||||
choices: Array<
|
||||
Partial<OpenAI.Chat.Completions.ChatCompletion.Choice> & {
|
||||
message: OpenAI.Chat.Completions.ChatCompletion.Choice["message"]
|
||||
finish_reason: string
|
||||
index: number
|
||||
}
|
||||
>
|
||||
}
|
||||
import { convertToOpenAiMessages } from "../openai-format"
|
||||
|
||||
describe("OpenAI Format Transformations", () => {
|
||||
describe("convertToOpenAiMessages", () => {
|
||||
it("should convert simple text messages", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Hi there!",
|
||||
},
|
||||
]
|
||||
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(2)
|
||||
expect(openAiMessages[0]).toEqual({
|
||||
describe("convertToOpenAiMessages", () => {
|
||||
it("should convert simple text messages", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
})
|
||||
expect(openAiMessages[1]).toEqual({
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Hi there!",
|
||||
})
|
||||
},
|
||||
]
|
||||
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(2)
|
||||
expect(openAiMessages[0]).toEqual({
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
})
|
||||
|
||||
it("should handle messages with image content", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "What is in this image?",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/jpeg",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(1)
|
||||
expect(openAiMessages[0].role).toBe("user")
|
||||
|
||||
const content = openAiMessages[0].content as Array<{
|
||||
type: string
|
||||
text?: string
|
||||
image_url?: { url: string }
|
||||
}>
|
||||
|
||||
expect(Array.isArray(content)).toBe(true)
|
||||
expect(content).toHaveLength(2)
|
||||
expect(content[0]).toEqual({ type: "text", text: "What is in this image?" })
|
||||
expect(content[1]).toEqual({
|
||||
type: "image_url",
|
||||
image_url: { url: "data:image/jpeg;base64,base64data" },
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle assistant messages with tool use", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Let me check the weather.",
|
||||
},
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "weather-123",
|
||||
name: "get_weather",
|
||||
input: { city: "London" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(1)
|
||||
|
||||
const assistantMessage = openAiMessages[0] as OpenAI.Chat.ChatCompletionAssistantMessageParam
|
||||
expect(assistantMessage.role).toBe("assistant")
|
||||
expect(assistantMessage.content).toBe("Let me check the weather.")
|
||||
expect(assistantMessage.tool_calls).toHaveLength(1)
|
||||
expect(assistantMessage.tool_calls![0]).toEqual({
|
||||
id: "weather-123",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: JSON.stringify({ city: "London" }),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle user messages with tool results", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "weather-123",
|
||||
content: "Current temperature in London: 20°C",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(1)
|
||||
|
||||
const toolMessage = openAiMessages[0] as OpenAI.Chat.ChatCompletionToolMessageParam
|
||||
expect(toolMessage.role).toBe("tool")
|
||||
expect(toolMessage.tool_call_id).toBe("weather-123")
|
||||
expect(toolMessage.content).toBe("Current temperature in London: 20°C")
|
||||
expect(openAiMessages[1]).toEqual({
|
||||
role: "assistant",
|
||||
content: "Hi there!",
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertToAnthropicMessage", () => {
|
||||
it("should convert simple completion", () => {
|
||||
const openAiCompletion: PartialChatCompletion = {
|
||||
id: "completion-123",
|
||||
model: "gpt-4",
|
||||
choices: [
|
||||
it("should handle messages with image content", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: "Hello there!",
|
||||
refusal: null,
|
||||
type: "text",
|
||||
text: "What is in this image?",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/jpeg",
|
||||
data: "base64data",
|
||||
},
|
||||
finish_reason: "stop",
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 5,
|
||||
total_tokens: 15,
|
||||
},
|
||||
created: 123456789,
|
||||
object: "chat.completion",
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const anthropicMessage = convertToAnthropicMessage(
|
||||
openAiCompletion as OpenAI.Chat.Completions.ChatCompletion,
|
||||
)
|
||||
expect(anthropicMessage.id).toBe("completion-123")
|
||||
expect(anthropicMessage.role).toBe("assistant")
|
||||
expect(anthropicMessage.content).toHaveLength(1)
|
||||
expect(anthropicMessage.content[0]).toEqual({
|
||||
type: "text",
|
||||
text: "Hello there!",
|
||||
citations: null,
|
||||
})
|
||||
expect(anthropicMessage.stop_reason).toBe("end_turn")
|
||||
expect(anthropicMessage.usage).toEqual({
|
||||
input_tokens: 10,
|
||||
output_tokens: 5,
|
||||
cache_creation_input_tokens: null,
|
||||
cache_read_input_tokens: null,
|
||||
})
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(1)
|
||||
expect(openAiMessages[0].role).toBe("user")
|
||||
|
||||
const content = openAiMessages[0].content as Array<{
|
||||
type: string
|
||||
text?: string
|
||||
image_url?: { url: string }
|
||||
}>
|
||||
|
||||
expect(Array.isArray(content)).toBe(true)
|
||||
expect(content).toHaveLength(2)
|
||||
expect(content[0]).toEqual({ type: "text", text: "What is in this image?" })
|
||||
expect(content[1]).toEqual({
|
||||
type: "image_url",
|
||||
image_url: { url: "data:image/jpeg;base64,base64data" },
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle tool calls in completion", () => {
|
||||
const openAiCompletion: PartialChatCompletion = {
|
||||
id: "completion-123",
|
||||
model: "gpt-4",
|
||||
choices: [
|
||||
it("should handle assistant messages with tool use", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: "Let me check the weather.",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "weather-123",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: '{"city":"London"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
refusal: null,
|
||||
},
|
||||
finish_reason: "tool_calls",
|
||||
index: 0,
|
||||
type: "text",
|
||||
text: "Let me check the weather.",
|
||||
},
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "weather-123",
|
||||
name: "get_weather",
|
||||
input: { city: "London" },
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 15,
|
||||
completion_tokens: 8,
|
||||
total_tokens: 23,
|
||||
},
|
||||
created: 123456789,
|
||||
object: "chat.completion",
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const anthropicMessage = convertToAnthropicMessage(
|
||||
openAiCompletion as OpenAI.Chat.Completions.ChatCompletion,
|
||||
)
|
||||
expect(anthropicMessage.content).toHaveLength(2)
|
||||
expect(anthropicMessage.content[0]).toEqual({
|
||||
type: "text",
|
||||
text: "Let me check the weather.",
|
||||
citations: null,
|
||||
})
|
||||
expect(anthropicMessage.content[1]).toEqual({
|
||||
type: "tool_use",
|
||||
id: "weather-123",
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(1)
|
||||
|
||||
const assistantMessage = openAiMessages[0] as OpenAI.Chat.ChatCompletionAssistantMessageParam
|
||||
expect(assistantMessage.role).toBe("assistant")
|
||||
expect(assistantMessage.content).toBe("Let me check the weather.")
|
||||
expect(assistantMessage.tool_calls).toHaveLength(1)
|
||||
expect(assistantMessage.tool_calls![0]).toEqual({
|
||||
id: "weather-123",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_weather",
|
||||
input: { city: "London" },
|
||||
})
|
||||
expect(anthropicMessage.stop_reason).toBe("tool_use")
|
||||
arguments: JSON.stringify({ city: "London" }),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle invalid tool call arguments", () => {
|
||||
const openAiCompletion: PartialChatCompletion = {
|
||||
id: "completion-123",
|
||||
model: "gpt-4",
|
||||
choices: [
|
||||
it("should handle user messages with tool results", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: "Testing invalid arguments",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "test-123",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "test_function",
|
||||
arguments: "invalid json",
|
||||
},
|
||||
},
|
||||
],
|
||||
refusal: null,
|
||||
},
|
||||
finish_reason: "tool_calls",
|
||||
index: 0,
|
||||
type: "tool_result",
|
||||
tool_use_id: "weather-123",
|
||||
content: "Current temperature in London: 20°C",
|
||||
},
|
||||
],
|
||||
created: 123456789,
|
||||
object: "chat.completion",
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const anthropicMessage = convertToAnthropicMessage(
|
||||
openAiCompletion as OpenAI.Chat.Completions.ChatCompletion,
|
||||
)
|
||||
expect(anthropicMessage.content).toHaveLength(2)
|
||||
expect(anthropicMessage.content[1]).toEqual({
|
||||
type: "tool_use",
|
||||
id: "test-123",
|
||||
name: "test_function",
|
||||
input: {}, // Should default to empty object for invalid JSON
|
||||
})
|
||||
})
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
expect(openAiMessages).toHaveLength(1)
|
||||
|
||||
const toolMessage = openAiMessages[0] as OpenAI.Chat.ChatCompletionToolMessageParam
|
||||
expect(toolMessage.role).toBe("tool")
|
||||
expect(toolMessage.tool_call_id).toBe("weather-123")
|
||||
expect(toolMessage.content).toBe("Current temperature in London: 20°C")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// npx jest src/api/transform/__tests__/vscode-lm-format.test.ts
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
import { convertToVsCodeLmMessages, convertToAnthropicRole, convertToAnthropicMessage } from "../vscode-lm-format"
|
||||
|
||||
import { convertToVsCodeLmMessages, convertToAnthropicRole } from "../vscode-lm-format"
|
||||
|
||||
// Mock crypto
|
||||
const mockCrypto = {
|
||||
|
|
@ -29,14 +29,6 @@ interface MockLanguageModelToolResultPart {
|
|||
parts: MockLanguageModelTextPart[]
|
||||
}
|
||||
|
||||
type MockMessageContent = MockLanguageModelTextPart | MockLanguageModelToolCallPart | MockLanguageModelToolResultPart
|
||||
|
||||
interface MockLanguageModelChatMessage {
|
||||
role: string
|
||||
name?: string
|
||||
content: MockMessageContent[]
|
||||
}
|
||||
|
||||
// Mock vscode namespace
|
||||
jest.mock("vscode", () => {
|
||||
const LanguageModelChatMessageRole = {
|
||||
|
|
@ -86,174 +78,115 @@ jest.mock("vscode", () => {
|
|||
}
|
||||
})
|
||||
|
||||
describe("vscode-lm-format", () => {
|
||||
describe("convertToVsCodeLmMessages", () => {
|
||||
it("should convert simple string messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
]
|
||||
describe("convertToVsCodeLmMessages", () => {
|
||||
it("should convert simple string messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
]
|
||||
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].role).toBe("user")
|
||||
expect((result[0].content[0] as MockLanguageModelTextPart).value).toBe("Hello")
|
||||
expect(result[1].role).toBe("assistant")
|
||||
expect((result[1].content[0] as MockLanguageModelTextPart).value).toBe("Hi there")
|
||||
})
|
||||
|
||||
it("should handle complex user messages with tool results", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Here is the result:" },
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-1",
|
||||
content: "Tool output",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].role).toBe("user")
|
||||
expect(result[0].content).toHaveLength(2)
|
||||
const [toolResult, textContent] = result[0].content as [
|
||||
MockLanguageModelToolResultPart,
|
||||
MockLanguageModelTextPart,
|
||||
]
|
||||
expect(toolResult.type).toBe("tool_result")
|
||||
expect(textContent.type).toBe("text")
|
||||
})
|
||||
|
||||
it("should handle complex assistant messages with tool calls", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me help you with that." },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "tool-1",
|
||||
name: "calculator",
|
||||
input: { operation: "add", numbers: [2, 2] },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].role).toBe("assistant")
|
||||
expect(result[0].content).toHaveLength(2)
|
||||
const [toolCall, textContent] = result[0].content as [
|
||||
MockLanguageModelToolCallPart,
|
||||
MockLanguageModelTextPart,
|
||||
]
|
||||
expect(toolCall.type).toBe("tool_call")
|
||||
expect(textContent.type).toBe("text")
|
||||
})
|
||||
|
||||
it("should handle image blocks with appropriate placeholders", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Look at this:" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
const imagePlaceholder = result[0].content[1] as MockLanguageModelTextPart
|
||||
expect(imagePlaceholder.value).toContain("[Image (base64): image/png not supported by VSCode LM API]")
|
||||
})
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].role).toBe("user")
|
||||
expect((result[0].content[0] as MockLanguageModelTextPart).value).toBe("Hello")
|
||||
expect(result[1].role).toBe("assistant")
|
||||
expect((result[1].content[0] as MockLanguageModelTextPart).value).toBe("Hi there")
|
||||
})
|
||||
|
||||
describe("convertToAnthropicRole", () => {
|
||||
it("should convert assistant role correctly", () => {
|
||||
const result = convertToAnthropicRole("assistant" as any)
|
||||
expect(result).toBe("assistant")
|
||||
})
|
||||
|
||||
it("should convert user role correctly", () => {
|
||||
const result = convertToAnthropicRole("user" as any)
|
||||
expect(result).toBe("user")
|
||||
})
|
||||
|
||||
it("should return null for unknown roles", () => {
|
||||
const result = convertToAnthropicRole("unknown" as any)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertToAnthropicMessage", () => {
|
||||
it("should convert assistant message with text content", async () => {
|
||||
const vsCodeMessage = {
|
||||
role: "assistant",
|
||||
name: "assistant",
|
||||
content: [new vscode.LanguageModelTextPart("Hello")],
|
||||
}
|
||||
|
||||
const result = await convertToAnthropicMessage(vsCodeMessage as any)
|
||||
|
||||
expect(result.role).toBe("assistant")
|
||||
expect(result.content).toHaveLength(1)
|
||||
expect(result.content[0]).toEqual({
|
||||
type: "text",
|
||||
text: "Hello",
|
||||
citations: null,
|
||||
})
|
||||
expect(result.id).toBe("test-uuid")
|
||||
})
|
||||
|
||||
it("should convert assistant message with tool calls", async () => {
|
||||
const vsCodeMessage = {
|
||||
role: "assistant",
|
||||
name: "assistant",
|
||||
content: [
|
||||
new vscode.LanguageModelToolCallPart("call-1", "calculator", { operation: "add", numbers: [2, 2] }),
|
||||
],
|
||||
}
|
||||
|
||||
const result = await convertToAnthropicMessage(vsCodeMessage as any)
|
||||
|
||||
expect(result.content).toHaveLength(1)
|
||||
expect(result.content[0]).toEqual({
|
||||
type: "tool_use",
|
||||
id: "call-1",
|
||||
name: "calculator",
|
||||
input: { operation: "add", numbers: [2, 2] },
|
||||
})
|
||||
expect(result.id).toBe("test-uuid")
|
||||
})
|
||||
|
||||
it("should throw error for non-assistant messages", async () => {
|
||||
const vsCodeMessage = {
|
||||
it("should handle complex user messages with tool results", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
name: "user",
|
||||
content: [new vscode.LanguageModelTextPart("Hello")],
|
||||
}
|
||||
content: [
|
||||
{ type: "text", text: "Here is the result:" },
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-1",
|
||||
content: "Tool output",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
await expect(convertToAnthropicMessage(vsCodeMessage as any)).rejects.toThrow(
|
||||
"Roo Code <Language Model API>: Only assistant messages are supported.",
|
||||
)
|
||||
})
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].role).toBe("user")
|
||||
expect(result[0].content).toHaveLength(2)
|
||||
const [toolResult, textContent] = result[0].content as [
|
||||
MockLanguageModelToolResultPart,
|
||||
MockLanguageModelTextPart,
|
||||
]
|
||||
expect(toolResult.type).toBe("tool_result")
|
||||
expect(textContent.type).toBe("text")
|
||||
})
|
||||
|
||||
it("should handle complex assistant messages with tool calls", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me help you with that." },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "tool-1",
|
||||
name: "calculator",
|
||||
input: { operation: "add", numbers: [2, 2] },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].role).toBe("assistant")
|
||||
expect(result[0].content).toHaveLength(2)
|
||||
const [toolCall, textContent] = result[0].content as [MockLanguageModelToolCallPart, MockLanguageModelTextPart]
|
||||
expect(toolCall.type).toBe("tool_call")
|
||||
expect(textContent.type).toBe("text")
|
||||
})
|
||||
|
||||
it("should handle image blocks with appropriate placeholders", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Look at this:" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToVsCodeLmMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
const imagePlaceholder = result[0].content[1] as MockLanguageModelTextPart
|
||||
expect(imagePlaceholder.value).toContain("[Image (base64): image/png not supported by VSCode LM API]")
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertToAnthropicRole", () => {
|
||||
it("should convert assistant role correctly", () => {
|
||||
const result = convertToAnthropicRole("assistant" as any)
|
||||
expect(result).toBe("assistant")
|
||||
})
|
||||
|
||||
it("should convert user role correctly", () => {
|
||||
const result = convertToAnthropicRole("user" as any)
|
||||
expect(result).toBe("user")
|
||||
})
|
||||
|
||||
it("should return null for unknown roles", () => {
|
||||
const result = convertToAnthropicRole("unknown" as any)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { MessageContent } from "../../shared/api"
|
||||
import { ConversationRole, Message, ContentBlock } from "@aws-sdk/client-bedrock-runtime"
|
||||
|
||||
// Import StreamEvent type from bedrock.ts
|
||||
import { StreamEvent } from "../providers/bedrock"
|
||||
import { MessageContent } from "../../shared/api"
|
||||
|
||||
/**
|
||||
* Convert Anthropic messages to Bedrock Converse format
|
||||
|
|
@ -175,51 +173,3 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Bedrock Converse stream events to Anthropic message format
|
||||
*/
|
||||
export function convertToAnthropicMessage(
|
||||
streamEvent: StreamEvent,
|
||||
modelId: string,
|
||||
): Partial<Anthropic.Messages.Message> {
|
||||
// Handle metadata events
|
||||
if (streamEvent.metadata?.usage) {
|
||||
return {
|
||||
id: "", // Bedrock doesn't provide message IDs
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
model: modelId,
|
||||
usage: {
|
||||
input_tokens: streamEvent.metadata.usage.inputTokens || 0,
|
||||
output_tokens: streamEvent.metadata.usage.outputTokens || 0,
|
||||
cache_creation_input_tokens: null,
|
||||
cache_read_input_tokens: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content blocks
|
||||
const text = streamEvent.contentBlockStart?.start?.text || streamEvent.contentBlockDelta?.delta?.text
|
||||
if (text !== undefined) {
|
||||
return {
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: text, citations: null }],
|
||||
model: modelId,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle message stop
|
||||
if (streamEvent.messageStop) {
|
||||
return {
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
stop_reason: streamEvent.messageStop.stopReason || null,
|
||||
stop_sequence: null,
|
||||
model: modelId,
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,63 +144,3 @@ export function convertToOpenAiMessages(
|
|||
|
||||
return openAiMessages
|
||||
}
|
||||
|
||||
// Convert OpenAI response to Anthropic format
|
||||
export function convertToAnthropicMessage(
|
||||
completion: OpenAI.Chat.Completions.ChatCompletion,
|
||||
): Anthropic.Messages.Message {
|
||||
const openAiMessage = completion.choices[0].message
|
||||
const anthropicMessage: Anthropic.Messages.Message = {
|
||||
id: completion.id,
|
||||
type: "message",
|
||||
role: openAiMessage.role, // always "assistant"
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: openAiMessage.content || "",
|
||||
citations: null,
|
||||
},
|
||||
],
|
||||
model: completion.model,
|
||||
stop_reason: (() => {
|
||||
switch (completion.choices[0].finish_reason) {
|
||||
case "stop":
|
||||
return "end_turn"
|
||||
case "length":
|
||||
return "max_tokens"
|
||||
case "tool_calls":
|
||||
return "tool_use"
|
||||
case "content_filter": // Anthropic doesn't have an exact equivalent
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})(),
|
||||
stop_sequence: null, // which custom stop_sequence was generated, if any (not applicable if you don't use stop_sequence)
|
||||
usage: {
|
||||
input_tokens: completion.usage?.prompt_tokens || 0,
|
||||
output_tokens: completion.usage?.completion_tokens || 0,
|
||||
cache_creation_input_tokens: null,
|
||||
cache_read_input_tokens: null,
|
||||
},
|
||||
}
|
||||
|
||||
if (openAiMessage.tool_calls && openAiMessage.tool_calls.length > 0) {
|
||||
anthropicMessage.content.push(
|
||||
...openAiMessage.tool_calls.map((toolCall): Anthropic.ToolUseBlock => {
|
||||
let parsedInput = {}
|
||||
try {
|
||||
parsedInput = JSON.parse(toolCall.function.arguments || "{}")
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tool arguments:", error)
|
||||
}
|
||||
return {
|
||||
type: "tool_use",
|
||||
id: toolCall.id,
|
||||
name: toolCall.function.name,
|
||||
input: parsedInput,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
return anthropicMessage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,50 +155,3 @@ export function convertToAnthropicRole(vsCodeLmMessageRole: vscode.LanguageModel
|
|||
return null
|
||||
}
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
type: "message",
|
||||
model: "vscode-lm",
|
||||
role: anthropicRole,
|
||||
content: vsCodeLmMessage.content
|
||||
.map((part): Anthropic.ContentBlock | null => {
|
||||
if (part instanceof vscode.LanguageModelTextPart) {
|
||||
return {
|
||||
type: "text",
|
||||
text: part.value,
|
||||
citations: null,
|
||||
}
|
||||
}
|
||||
|
||||
if (part instanceof vscode.LanguageModelToolCallPart) {
|
||||
return {
|
||||
type: "tool_use",
|
||||
id: part.callId || crypto.randomUUID(),
|
||||
name: part.name,
|
||||
input: asObjectSafe(part.input),
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
.filter((part): part is Anthropic.ContentBlock => part !== null),
|
||||
stop_reason: null,
|
||||
stop_sequence: null,
|
||||
usage: {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_creation_input_tokens: null,
|
||||
cache_read_input_tokens: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue