import { describe, test, expect } from "bun:test" import { z } from "zod" import type { AnthropicMessagesPayload } from "~/routes/messages/anthropic-types" import { translateToOpenAI } from "../src/routes/messages/non-stream-translation" // Zod schema for a single message in the chat completion request. const messageSchema = z.object({ role: z.enum([ "system", "user", "assistant", "tool", "function", "developer", ]), content: z.union([z.string(), z.object({}), z.array(z.any())]), name: z.string().optional(), tool_calls: z.array(z.any()).optional(), tool_call_id: z.string().optional(), }) // Zod schema for the entire chat completion request payload. // This is derived from the openapi.documented.yml specification. const chatCompletionRequestSchema = z.object({ messages: z.array(messageSchema).min(1, "Messages array cannot be empty."), model: z.string(), frequency_penalty: z.number().min(-2).max(2).optional().nullable(), logit_bias: z.record(z.string(), z.number()).optional().nullable(), logprobs: z.boolean().optional().nullable(), top_logprobs: z.number().int().min(0).max(20).optional().nullable(), max_tokens: z.number().int().optional().nullable(), n: z.number().int().min(1).max(128).optional().nullable(), presence_penalty: z.number().min(-2).max(2).optional().nullable(), response_format: z .object({ type: z.enum(["text", "json_object", "json_schema"]), json_schema: z.object({}).optional(), }) .optional(), seed: z.number().int().optional().nullable(), stop: z .union([z.string(), z.array(z.string())]) .optional() .nullable(), stream: z.boolean().optional().nullable(), temperature: z.number().min(0).max(2).optional().nullable(), top_p: z.number().min(0).max(1).optional().nullable(), tools: z.array(z.any()).optional(), tool_choice: z.union([z.string(), z.object({})]).optional(), user: z.string().optional(), }) /** * Validates if a request payload conforms to the OpenAI Chat Completion v1 shape using Zod. * @param payload The request payload to validate. * @returns True if the payload is valid, false otherwise. */ function isValidChatCompletionRequest(payload: unknown): boolean { const result = chatCompletionRequestSchema.safeParse(payload) return result.success } describe("Anthropic to OpenAI translation logic", () => { test("should translate minimal Anthropic payload to valid OpenAI payload", () => { const anthropicPayload: AnthropicMessagesPayload = { model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], max_tokens: 0, } const openAIPayload = translateToOpenAI(anthropicPayload) expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) }) test("should translate comprehensive Anthropic payload to valid OpenAI payload", () => { const anthropicPayload: AnthropicMessagesPayload = { model: "gpt-4o", system: "You are a helpful assistant.", messages: [ { role: "user", content: "What is the weather like in Boston?" }, { role: "assistant", content: "The weather in Boston is sunny and 75°F.", }, ], temperature: 0.7, max_tokens: 150, top_p: 1, stream: false, metadata: { user_id: "user-123" }, tools: [ { name: "getWeather", description: "Gets weather info", input_schema: { location: { type: "string" } }, }, ], tool_choice: { type: "auto" }, } const openAIPayload = translateToOpenAI(anthropicPayload) expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) }) test("should handle missing fields gracefully", () => { const anthropicPayload: AnthropicMessagesPayload = { model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], max_tokens: 0, } const openAIPayload = translateToOpenAI(anthropicPayload) expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) }) test("should handle invalid types in Anthropic payload", () => { const anthropicPayload = { model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], temperature: "hot", // Should be a number } // @ts-expect-error intended to be invalid const openAIPayload = translateToOpenAI(anthropicPayload) // Should fail validation expect(isValidChatCompletionRequest(openAIPayload)).toBe(false) }) test("should handle thinking blocks in assistant messages", () => { const anthropicPayload: AnthropicMessagesPayload = { model: "claude-3-5-sonnet-20241022", messages: [ { role: "user", content: "What is 2+2?" }, { role: "assistant", content: [ { type: "thinking", thinking: "Let me think about this simple math problem...", }, { type: "text", text: "2+2 equals 4." }, ], }, ], max_tokens: 100, } const openAIPayload = translateToOpenAI(anthropicPayload) expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) // Check that thinking content is combined with text content const assistantMessage = openAIPayload.messages.find( (m) => m.role === "assistant", ) expect(assistantMessage?.content).toContain( "Let me think about this simple math problem...", ) expect(assistantMessage?.content).toContain("2+2 equals 4.") }) test("should handle thinking blocks with tool calls", () => { const anthropicPayload: AnthropicMessagesPayload = { model: "claude-3-5-sonnet-20241022", messages: [ { role: "user", content: "What's the weather?" }, { role: "assistant", content: [ { type: "thinking", thinking: "I need to call the weather API to get current weather information.", }, { type: "text", text: "I'll check the weather for you." }, { type: "tool_use", id: "call_123", name: "get_weather", input: { location: "New York" }, }, ], }, ], max_tokens: 100, } const openAIPayload = translateToOpenAI(anthropicPayload) expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) // Check that thinking content is included in the message content const assistantMessage = openAIPayload.messages.find( (m) => m.role === "assistant", ) expect(assistantMessage?.content).toContain( "I need to call the weather API", ) expect(assistantMessage?.content).toContain( "I'll check the weather for you.", ) expect(assistantMessage?.tool_calls).toHaveLength(1) expect(assistantMessage?.tool_calls?.[0].function.name).toBe("get_weather") }) }) describe("OpenAI Chat Completion v1 Request Payload Validation with Zod", () => { test("should return true for a minimal valid request payload", () => { const validPayload = { model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], } expect(isValidChatCompletionRequest(validPayload)).toBe(true) }) test("should return true for a comprehensive valid request payload", () => { const validPayload = { model: "gpt-4o", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "What is the weather like in Boston?" }, ], temperature: 0.7, max_tokens: 150, top_p: 1, frequency_penalty: 0, presence_penalty: 0, stream: false, n: 1, } expect(isValidChatCompletionRequest(validPayload)).toBe(true) }) test('should return false if the "model" field is missing', () => { const invalidPayload = { messages: [{ role: "user", content: "Hello!" }], } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test('should return false if the "messages" field is missing', () => { const invalidPayload = { model: "gpt-4o", } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test('should return false if the "messages" array is empty', () => { const invalidPayload = { model: "gpt-4o", messages: [], } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test('should return false if "model" is not a string', () => { const invalidPayload = { model: 12345, messages: [{ role: "user", content: "Hello!" }], } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test('should return false if "messages" is not an array', () => { const invalidPayload = { model: "gpt-4o", messages: { role: "user", content: "Hello!" }, } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test('should return false if a message in the "messages" array is missing a "role"', () => { const invalidPayload = { model: "gpt-4o", messages: [{ content: "Hello!" }], } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test('should return false if a message in the "messages" array is missing "content"', () => { const invalidPayload = { model: "gpt-4o", messages: [{ role: "user" }], } // Note: Zod considers 'undefined' as missing, so this will fail as expected. const result = chatCompletionRequestSchema.safeParse(invalidPayload) expect(result.success).toBe(false) }) test('should return false if a message has an invalid "role"', () => { const invalidPayload = { model: "gpt-4o", messages: [{ role: "customer", content: "Hello!" }], } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test("should return false if an optional field has an incorrect type", () => { const invalidPayload = { model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], temperature: "hot", // Should be a number } expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test("should return false for a completely empty object", () => { const invalidPayload = {} expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) }) test("should return false for null or non-object payloads", () => { expect(isValidChatCompletionRequest(null)).toBe(false) expect(isValidChatCompletionRequest(undefined)).toBe(false) expect(isValidChatCompletionRequest("a string")).toBe(false) expect(isValidChatCompletionRequest(123)).toBe(false) }) })