test(gateway): add OpenResponses parity E2E tests

- Add schema validation tests for input_image, input_file, client tools
- Add buildAgentPrompt tests for turn-based tool flow
This commit is contained in:
Ryan Lisse
2026-01-19 12:43:17 +01:00
committed by Peter Steinberger
parent a5afe7bc2b
commit 4f02c74dca
7 changed files with 769 additions and 0 deletions

View File

@@ -0,0 +1,315 @@
/**
* 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");
});
});
});