import test from "node:test";
import assert from "node:assert/strict";
import { buildOpenAIRequest } from "../../../../src/model/providers/openai/request.js";
import type {
CanonicalModelRequest,
CanonicalToolSchema,
ModelDefinition,
} from "../../../../src/model/protocol/canonical.js";
import { DEFAULT_MODEL_CAPABILITIES } from "../../../../src/model/protocol/capabilities.js";
import { DEFAULT_MULTIMODAL_CONSTRAINTS } from "../../../../src/model/protocol/multimodal.js";
const TEST_MODEL: ModelDefinition = {
id: "openai/test",
capabilities: {
...DEFAULT_MODEL_CAPABILITIES,
maxOutputTokens: 1024,
},
multimodal: DEFAULT_MULTIMODAL_CONSTRAINTS,
};
function createRequest(tools: CanonicalToolSchema[]): CanonicalModelRequest {
return {
model: "openai/test",
provider: "openai",
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
tools,
};
}
test("buildOpenAIRequest normalizes array-union tool schema nodes missing items", () => {
const schema = {
type: "object",
additionalProperties: false,
properties: {
value: {
type: ["object", "array", "string", "number", "boolean"],
},
status: {
type: ["string", "array"],
},
nested: {
oneOf: [
{ type: ["array", "null"] },
{
type: "object",
properties: {
tags: { type: ["string", "array"] },
},
},
],
},
},
} as Record<string, unknown>;
const request = createRequest([{ name: "task_like_tool", inputSchema: schema }]);
const body = buildOpenAIRequest(request, TEST_MODEL);
const params = body.tools?.[0]?.function.parameters as Record<string, unknown>;
const properties = params.properties as Record<string, unknown>;
assert.deepEqual((properties.value as Record<string, unknown>).items, {});
assert.deepEqual((properties.status as Record<string, unknown>).items, {});
const nested = properties.nested as Record<string, unknown>;
const oneOf = nested.oneOf as Array<Record<string, unknown>>;
assert.deepEqual(oneOf[0].items, {});
const nestedProps = (oneOf[1].properties as Record<string, unknown>);
assert.deepEqual((nestedProps.tags as Record<string, unknown>).items, {});
});
test("buildOpenAIRequest preserves existing items and does not mutate original schema", () => {
const schema = {
type: "object",
properties: {
ids: {
type: ["array", "string"],
},
labels: {
type: "array",
items: { type: "string" },
},
},
} as Record<string, unknown>;
const request = createRequest([{ name: "mixed_tool", inputSchema: schema }]);
const body = buildOpenAIRequest(request, TEST_MODEL);
const params = body.tools?.[0]?.function.parameters as Record<string, unknown>;
const properties = params.properties as Record<string, unknown>;
assert.deepEqual((properties.ids as Record<string, unknown>).items, {});
assert.deepEqual((properties.labels as Record<string, unknown>).items, { type: "string" });
const originalProps = schema.properties as Record<string, unknown>;
assert.equal((originalProps.ids as Record<string, unknown>).items, undefined);
assert.deepEqual((originalProps.labels as Record<string, unknown>).items, { type: "string" });
});
test("buildOpenAIRequest repairs assistant tool calls missing OpenAI-required fields", () => {
const request: CanonicalModelRequest = {
model: "openai/test",
provider: "openai",
messages: [
{
role: "assistant",
content: [
{
type: "tool_call",
id: "",
name: "find_skills",
input: { query: "popular skills" },
},
],
},
{
role: "user",
content: [
{
type: "tool_result",
toolCallId: "",
content: [{ type: "text", text: "[]" }],
},
],
},
],
tools: [{ name: "find_skills", inputSchema: { type: "object", properties: {} } }],
};
const body = buildOpenAIRequest(request, TEST_MODEL);
const assistant = body.messages[0]!;
const toolCall = assistant.tool_calls?.[0] as {
id: string;
type: string;
function: { name: string; arguments: string };
};
assert.equal(assistant.role, "assistant");
assert.equal(toolCall.id, "call_0_0");
assert.equal(toolCall.type, "function");
assert.equal(toolCall.function.name, "find_skills");
assert.equal(toolCall.function.arguments, JSON.stringify({ query: "popular skills" }));
assert.deepEqual(
body.messages.filter((message) => message.role === "tool").map((message) => message.tool_call_id),
["call_0_0"],
);
});
test("buildOpenAIRequest drops orphaned tool messages that are not responses to tool calls", () => {
const request: CanonicalModelRequest = {
model: "openai/test",
provider: "openai",
messages: [
{
role: "user",
content: [
{
type: "tool_result",
toolCallId: "orphaned_call",
content: [{ type: "text", text: "stale result" }],
},
{ type: "text", text: "continue" },
],
},
{
role: "assistant",
content: [
{
type: "tool_call",
id: "call_valid",
name: "find_skills",
input: { query: "popular skills" },
},
],
},
{
role: "user",
content: [
{
type: "tool_result",
toolCallId: "wrong_call",
content: [{ type: "text", text: "wrong result" }],
},
{
type: "tool_result",
toolCallId: "call_valid",
content: [{ type: "text", text: "valid result" }],
},
],
},
],
tools: [{ name: "find_skills", inputSchema: { type: "object", properties: {} } }],
};
const body = buildOpenAIRequest(request, TEST_MODEL);
const toolMessages = body.messages.filter((message) => message.role === "tool");
assert.deepEqual(toolMessages.map((message) => message.tool_call_id), ["call_valid"]);
assert.equal(toolMessages[0]?.content, "valid result");
});