|
| 1 | +// This file contains `declare module "vscode"` so we must import it. |
| 2 | +import "../providers/vscode-lm" |
| 3 | +import { describe, it } from "mocha" |
| 4 | +import "should" |
| 5 | +import * as vscode from "vscode" |
| 6 | +import { Anthropic } from "@anthropic-ai/sdk" |
| 7 | +import { asObjectSafe, convertToAnthropicRole, convertToVsCodeLmMessages, convertToAnthropicMessage } from "./vscode-lm-format" |
| 8 | + |
| 9 | +describe("asObjectSafe", () => { |
| 10 | + it("should handle falsy values", () => { |
| 11 | + asObjectSafe(0).should.deepEqual({}) |
| 12 | + asObjectSafe("").should.deepEqual({}) |
| 13 | + asObjectSafe(null).should.deepEqual({}) |
| 14 | + asObjectSafe(undefined).should.deepEqual({}) |
| 15 | + }) |
| 16 | + |
| 17 | + it("should parse valid JSON strings", () => { |
| 18 | + asObjectSafe('{"key": "value"}').should.deepEqual({ key: "value" }) |
| 19 | + }) |
| 20 | + |
| 21 | + it("should return an empty object for invalid JSON strings", () => { |
| 22 | + asObjectSafe("invalid json").should.deepEqual({}) |
| 23 | + }) |
| 24 | + |
| 25 | + it("should convert objects to plain objects", () => { |
| 26 | + const input = { prop: "value" } |
| 27 | + asObjectSafe(input).should.deepEqual(input) |
| 28 | + asObjectSafe(input).should.not.equal(input) // Should be a new object |
| 29 | + }) |
| 30 | + |
| 31 | + it("should convert arrays to plain objects", () => { |
| 32 | + const input = ["hello world"] |
| 33 | + asObjectSafe(input).should.deepEqual({ 0: "hello world" }) |
| 34 | + }) |
| 35 | +}) |
| 36 | + |
| 37 | +describe("convertToAnthropicRole", () => { |
| 38 | + it("should convert VSCode roles to Anthropic roles", () => { |
| 39 | + // @ts-expect-error(Testing with an invalid role) |
| 40 | + const unknownRole = "unknown" as vscode.LanguageModelChatMessageRole |
| 41 | + ;(convertToAnthropicRole(vscode.LanguageModelChatMessageRole.Assistant) === "assistant").should.be.true() |
| 42 | + ;(convertToAnthropicRole(vscode.LanguageModelChatMessageRole.User) === "user").should.be.true() |
| 43 | + ;(convertToAnthropicRole(unknownRole) === null).should.be.true() |
| 44 | + }) |
| 45 | +}) |
| 46 | + |
| 47 | +describe("convertToVsCodeLmMessages", () => { |
| 48 | + it("should convert simple string messages", () => { |
| 49 | + const anthropicMessages: Anthropic.Messages.MessageParam[] = [ |
| 50 | + { role: "user", content: "Hello" }, |
| 51 | + { role: "assistant", content: "Hi there" }, |
| 52 | + ] |
| 53 | + |
| 54 | + const result = convertToVsCodeLmMessages(anthropicMessages) |
| 55 | + |
| 56 | + result.should.have.length(2) |
| 57 | + result[0].role.should.equal(vscode.LanguageModelChatMessageRole.User) |
| 58 | + result[0].content[0].should.be.instanceof(vscode.LanguageModelTextPart) |
| 59 | + const textPart0 = result[0].content[0] as vscode.LanguageModelTextPart |
| 60 | + textPart0.should.have.property("value", "Hello") |
| 61 | + |
| 62 | + result[1].role.should.equal(vscode.LanguageModelChatMessageRole.Assistant) |
| 63 | + result[1].content[0].should.be.instanceof(vscode.LanguageModelTextPart) |
| 64 | + const textPart1 = result[1].content[0] as vscode.LanguageModelTextPart |
| 65 | + textPart1.should.have.property("value", "Hi there") |
| 66 | + }) |
| 67 | + |
| 68 | + it("should convert complex user messages with tool results", () => { |
| 69 | + const anthropicMessages: Anthropic.Messages.MessageParam[] = [ |
| 70 | + { |
| 71 | + role: "user", |
| 72 | + content: [ |
| 73 | + { type: "text", text: "User text" }, |
| 74 | + { |
| 75 | + type: "tool_result", |
| 76 | + tool_use_id: "tool-123", |
| 77 | + content: [{ type: "text", text: "Tool result" }], |
| 78 | + }, |
| 79 | + ], |
| 80 | + }, |
| 81 | + ] |
| 82 | + |
| 83 | + const result = convertToVsCodeLmMessages(anthropicMessages) |
| 84 | + |
| 85 | + result.should.have.length(1) |
| 86 | + result[0].role.should.equal(vscode.LanguageModelChatMessageRole.User) |
| 87 | + result[0].content.should.have.length(2) |
| 88 | + |
| 89 | + // Check that the first content part is a ToolResultPart |
| 90 | + result[0].content[0].should.be.instanceof(vscode.LanguageModelToolResultPart) |
| 91 | + const toolResultPart = result[0].content[0] as vscode.LanguageModelToolResultPart |
| 92 | + toolResultPart.should.have.property("callId", "tool-123") |
| 93 | + |
| 94 | + // Skip detailed testing of internal structure as it may vary |
| 95 | + // Just verify it's the right type with the right ID |
| 96 | + |
| 97 | + // Check the second content part is a TextPart |
| 98 | + result[0].content[1].should.be.instanceof(vscode.LanguageModelTextPart) |
| 99 | + const textPart = result[0].content[1] as vscode.LanguageModelTextPart |
| 100 | + textPart.should.have.property("value", "User text") |
| 101 | + }) |
| 102 | + |
| 103 | + it("should convert complex assistant messages with tool calls", () => { |
| 104 | + const anthropicMessages: Anthropic.Messages.MessageParam[] = [ |
| 105 | + { |
| 106 | + role: "assistant", |
| 107 | + content: [ |
| 108 | + { type: "text", text: "Assistant text" }, |
| 109 | + { |
| 110 | + type: "tool_use", |
| 111 | + id: "tool-123", |
| 112 | + name: "testTool", |
| 113 | + input: { param: "value" }, |
| 114 | + }, |
| 115 | + ], |
| 116 | + }, |
| 117 | + ] |
| 118 | + |
| 119 | + const result = convertToVsCodeLmMessages(anthropicMessages) |
| 120 | + |
| 121 | + result.should.have.length(1) |
| 122 | + result[0].role.should.equal(vscode.LanguageModelChatMessageRole.Assistant) |
| 123 | + result[0].content.should.have.length(2) |
| 124 | + |
| 125 | + result[0].content[0].should.be.instanceof(vscode.LanguageModelToolCallPart) |
| 126 | + const toolCallPart = result[0].content[0] as vscode.LanguageModelToolCallPart |
| 127 | + toolCallPart.should.have.property("callId", "tool-123") |
| 128 | + toolCallPart.should.have.property("name", "testTool") |
| 129 | + toolCallPart.should.have.property("input") |
| 130 | + toolCallPart.input.should.deepEqual({ param: "value" }) |
| 131 | + |
| 132 | + result[0].content[1].should.be.instanceof(vscode.LanguageModelTextPart) |
| 133 | + const textPart = result[0].content[1] as vscode.LanguageModelTextPart |
| 134 | + textPart.should.have.property("value", "Assistant text") |
| 135 | + }) |
| 136 | + |
| 137 | + it("should handle image blocks with appropriate placeholders", () => { |
| 138 | + const anthropicMessages: Anthropic.Messages.MessageParam[] = [ |
| 139 | + { |
| 140 | + role: "user", |
| 141 | + content: [ |
| 142 | + { |
| 143 | + type: "image", |
| 144 | + source: { |
| 145 | + type: "base64", |
| 146 | + media_type: "image/jpeg", |
| 147 | + data: "base64data", |
| 148 | + }, |
| 149 | + }, |
| 150 | + ], |
| 151 | + }, |
| 152 | + ] |
| 153 | + |
| 154 | + const result = convertToVsCodeLmMessages(anthropicMessages) |
| 155 | + |
| 156 | + result.should.have.length(1) |
| 157 | + result[0].content[0].should.be.instanceof(vscode.LanguageModelTextPart) |
| 158 | + const textPart = result[0].content[0] as vscode.LanguageModelTextPart |
| 159 | + textPart.should.have.property("value") |
| 160 | + textPart.value.should.match(/Image \(base64\): image\/jpeg not supported by VSCode LM API/) |
| 161 | + }) |
| 162 | +}) |
| 163 | + |
| 164 | +describe("convertToAnthropicMessage", () => { |
| 165 | + it("should convert VSCode assistant messages to Anthropic format", () => { |
| 166 | + const vsCodeMsg = vscode.LanguageModelChatMessage.Assistant([ |
| 167 | + new vscode.LanguageModelTextPart("Test message"), |
| 168 | + new vscode.LanguageModelToolCallPart("tool-id", "testTool", { param: "value" }), |
| 169 | + ]) |
| 170 | + |
| 171 | + const result = convertToAnthropicMessage(vsCodeMsg) |
| 172 | + |
| 173 | + result.should.have.property("role", "assistant") |
| 174 | + result.should.have.property("content").which.is.an.Array() |
| 175 | + result.content.should.have.length(2) |
| 176 | + |
| 177 | + // Check properties carefully to avoid null reference errors |
| 178 | + if (result.content && result.content.length >= 1) { |
| 179 | + const textContent = result.content[0] |
| 180 | + if (textContent) { |
| 181 | + textContent.should.have.property("type", "text") |
| 182 | + if (textContent.type === "text") { |
| 183 | + textContent.should.have.property("text", "Test message") |
| 184 | + } |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + if (result.content && result.content.length >= 2) { |
| 189 | + const toolContent = result.content[1] |
| 190 | + if (toolContent) { |
| 191 | + toolContent.should.have.property("type", "tool_use") |
| 192 | + if (toolContent.type === "tool_use") { |
| 193 | + toolContent.should.have.property("id", "tool-id") |
| 194 | + toolContent.should.have.property("name", "testTool") |
| 195 | + toolContent.should.have.property("input").which.deepEqual({ param: "value" }) |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + }) |
| 200 | + |
| 201 | + it("should throw an error for non-assistant messages", () => { |
| 202 | + const vsCodeMsg = vscode.LanguageModelChatMessage.User("User message") |
| 203 | + |
| 204 | + try { |
| 205 | + convertToAnthropicMessage(vsCodeMsg) |
| 206 | + throw new Error("Should have thrown an error") |
| 207 | + } catch (error: any) { |
| 208 | + error.message.should.match(/Only assistant messages are supported/) |
| 209 | + } |
| 210 | + }) |
| 211 | +}) |
0 commit comments