Spaces:
Running
Running
| /** | |
| * 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"); | |
| }); | |
| }); | |
| }); | |