import { describe, expect, it } from "vitest"; import { transformMessages } from "../src/providers/transform-messages.js"; import type { AssistantMessage, Message, Model, ToolCall } from "../src/types.js"; // Normalize function matching what anthropic.ts uses function anthropicNormalizeToolCallId( id: string, _model: Model<"anthropic-messages">, _source: AssistantMessage, ): string { return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64); } function makeCopilotClaudeModel(): Model<"anthropic-messages"> { return { id: "claude-sonnet-4", name: "Claude Sonnet 4", api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 16000, }; } describe("OpenAI to Anthropic session migration for Copilot Claude", () => { it("converts thinking blocks to plain text when source model differs", () => { const model = makeCopilotClaudeModel(); const messages: Message[] = [ { role: "user", content: "hello", timestamp: Date.now() }, { role: "assistant", content: [ { type: "thinking", thinking: "Let me think about this...", thinkingSignature: "reasoning_content", }, { type: "text", text: "Hi there!" }, ], api: "openai-completions", provider: "github-copilot", model: "gpt-4o", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "stop", timestamp: Date.now(), }, ]; const result = transformMessages(messages, model, anthropicNormalizeToolCallId); const assistantMsg = result.find((m) => m.role === "assistant") as AssistantMessage; // Thinking block should be converted to text since models differ const textBlocks = assistantMsg.content.filter((b) => b.type === "text"); const thinkingBlocks = assistantMsg.content.filter((b) => b.type === "thinking"); expect(thinkingBlocks).toHaveLength(0); expect(textBlocks.length).toBeGreaterThanOrEqual(2); }); it("removes thoughtSignature from tool calls when migrating between models", () => { const model = makeCopilotClaudeModel(); const messages: Message[] = [ { role: "user", content: "run a command", timestamp: Date.now() }, { role: "assistant", content: [ { type: "toolCall", id: "call_123", name: "bash", arguments: { command: "ls" }, thoughtSignature: JSON.stringify({ type: "reasoning.encrypted", id: "call_123", data: "encrypted" }), }, ], api: "openai-responses", provider: "github-copilot", model: "gpt-5", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "toolUse", timestamp: Date.now(), }, { role: "toolResult", toolCallId: "call_123", toolName: "bash", content: [{ type: "text", text: "output" }], isError: false, timestamp: Date.now(), }, ]; const result = transformMessages(messages, model, anthropicNormalizeToolCallId); const assistantMsg = result.find((m) => m.role === "assistant") as AssistantMessage; const toolCall = assistantMsg.content.find((b) => b.type === "toolCall") as ToolCall; expect(toolCall.thoughtSignature).toBeUndefined(); }); });