/** * OpenResponses Feature Parity E2E Tests * * Tests for input_image, input_file, and client-side tools (Hosted Tools) * support in the OpenResponses `/v1/responses` endpoint. */ import { describe, it, expect } from "vitest"; describe("OpenResponses Feature Parity", () => { describe("Schema Validation", () => { it("should validate input_image with url source", async () => { const { InputImageContentPartSchema } = await import("./open-responses.schema.js"); const validImage = { type: "input_image" as const, source: { type: "url" as const, url: "https://example.com/image.png", }, }; const result = InputImageContentPartSchema.safeParse(validImage); expect(result.success).toBe(true); }); it("should validate input_image with base64 source", async () => { const { InputImageContentPartSchema } = await import("./open-responses.schema.js"); const validImage = { type: "input_image" as const, source: { type: "base64" as const, media_type: "image/png" as const, data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", }, }; const result = InputImageContentPartSchema.safeParse(validImage); expect(result.success).toBe(true); }); it("should reject input_image with invalid mime type", async () => { const { InputImageContentPartSchema } = await import("./open-responses.schema.js"); const invalidImage = { type: "input_image" as const, source: { type: "base64" as const, media_type: "application/json" as const, // Not an image data: "SGVsbG8gV29ybGQh", }, }; const result = InputImageContentPartSchema.safeParse(invalidImage); expect(result.success).toBe(false); }); it("should validate input_file with url source", async () => { const { InputFileContentPartSchema } = await import("./open-responses.schema.js"); const validFile = { type: "input_file" as const, source: { type: "url" as const, url: "https://example.com/document.txt", }, }; const result = InputFileContentPartSchema.safeParse(validFile); expect(result.success).toBe(true); }); it("should validate input_file with base64 source", async () => { const { InputFileContentPartSchema } = await import("./open-responses.schema.js"); const validFile = { type: "input_file" as const, source: { type: "base64" as const, media_type: "text/plain" as const, data: "SGVsbG8gV29ybGQh", filename: "hello.txt", }, }; const result = InputFileContentPartSchema.safeParse(validFile); expect(result.success).toBe(true); }); it("should validate tool definition", async () => { const { ToolDefinitionSchema } = await import("./open-responses.schema.js"); const validTool = { type: "function" as const, function: { name: "get_weather", description: "Get the current weather", parameters: { type: "object", properties: { location: { type: "string" }, }, required: ["location"], }, }, }; const result = ToolDefinitionSchema.safeParse(validTool); expect(result.success).toBe(true); }); it("should reject tool definition without name", async () => { const { ToolDefinitionSchema } = await import("./open-responses.schema.js"); const invalidTool = { type: "function" as const, function: { name: "", // Empty name description: "Get the current weather", }, }; const result = ToolDefinitionSchema.safeParse(invalidTool); expect(result.success).toBe(false); }); }); describe("CreateResponseBody Schema", () => { it("should validate request with input_image", async () => { const { CreateResponseBodySchema } = await import("./open-responses.schema.js"); const validRequest = { model: "claude-sonnet-4-20250514", input: [ { type: "message" as const, role: "user" as const, content: [ { type: "input_image" as const, source: { type: "url" as const, url: "https://example.com/photo.jpg", }, }, { type: "input_text" as const, text: "What's in this image?", }, ], }, ], }; const result = CreateResponseBodySchema.safeParse(validRequest); expect(result.success).toBe(true); }); it("should validate request with client tools", async () => { const { CreateResponseBodySchema } = await import("./open-responses.schema.js"); const validRequest = { model: "claude-sonnet-4-20250514", input: [ { type: "message" as const, role: "user" as const, content: "What's the weather?", }, ], tools: [ { type: "function" as const, function: { name: "get_weather", description: "Get weather for a location", parameters: { type: "object", properties: { location: { type: "string" }, }, required: ["location"], }, }, }, ], }; const result = CreateResponseBodySchema.safeParse(validRequest); expect(result.success).toBe(true); }); it("should validate request with function_call_output for turn-based tools", async () => { const { CreateResponseBodySchema } = await import("./open-responses.schema.js"); const validRequest = { model: "claude-sonnet-4-20250514", input: [ { type: "function_call_output" as const, call_id: "call_123", output: '{"temperature": "72°F", "condition": "sunny"}', }, ], }; const result = CreateResponseBodySchema.safeParse(validRequest); expect(result.success).toBe(true); }); it("should validate complete turn-based tool flow", async () => { const { CreateResponseBodySchema } = await import("./open-responses.schema.js"); const turn1Request = { model: "claude-sonnet-4-20250514", input: [ { type: "message" as const, role: "user" as const, content: "What's the weather in San Francisco?", }, ], tools: [ { type: "function" as const, function: { name: "get_weather", description: "Get weather for a location", }, }, ], }; const turn1Result = CreateResponseBodySchema.safeParse(turn1Request); expect(turn1Result.success).toBe(true); // Turn 2: Client provides tool output const turn2Request = { model: "claude-sonnet-4-20250514", input: [ { type: "function_call_output" as const, call_id: "call_123", output: '{"temperature": "72°F", "condition": "sunny"}', }, ], }; const turn2Result = CreateResponseBodySchema.safeParse(turn2Request); expect(turn2Result.success).toBe(true); }); }); describe("Response Resource Schema", () => { it("should validate response with function_call output", async () => { const { OutputItemSchema } = await import("./open-responses.schema.js"); const functionCallOutput = { type: "function_call" as const, id: "msg_123", call_id: "call_456", name: "get_weather", arguments: '{"location": "San Francisco"}', }; const result = OutputItemSchema.safeParse(functionCallOutput); expect(result.success).toBe(true); }); }); describe("buildAgentPrompt", () => { it("should convert function_call_output to tool entry", async () => { const { buildAgentPrompt } = await import("./openresponses-http.js"); const result = buildAgentPrompt([ { type: "function_call_output" as const, call_id: "call_123", output: '{"temperature": "72°F"}', }, ]); // When there's only a tool output (no history), returns just the body expect(result.message).toBe('{"temperature": "72°F"}'); }); it("should handle mixed message and function_call_output items", async () => { const { buildAgentPrompt } = await import("./openresponses-http.js"); const result = buildAgentPrompt([ { type: "message" as const, role: "user" as const, content: "What's the weather?", }, { type: "function_call_output" as const, call_id: "call_123", output: '{"temperature": "72°F"}', }, { type: "message" as const, role: "user" as const, content: "Thanks!", }, ]); // Should include both user messages and tool output expect(result.message).toContain("weather"); expect(result.message).toContain("72°F"); expect(result.message).toContain("Thanks"); }); }); });