Files
openclaw/src/agents/pi-tools.schema.test.ts
yelog dd3796aef3 fix: normalize MCP tool schemas missing properties field for OpenAI Responses API
Tools with no parameters produce { type: "object" } schemas without a
properties field. The OpenAI Responses API rejects these, silently
crashing entire sessions.

Add properties: {} injection in normalizeToolParameters() and
convertTools() to ensure all object-type schemas include a properties
field.

Closes #58246
2026-04-01 02:30:45 +09:00

87 lines
2.9 KiB
TypeScript

import { Type } from "@sinclair/typebox";
import { describe, expect, it, vi } from "vitest";
import { normalizeToolParameters } from "./pi-tools.schema.js";
import type { AnyAgentTool } from "./pi-tools.types.js";
describe("normalizeToolParameters", () => {
it("injects properties:{} for type:object schemas missing properties (MCP no-param tools)", () => {
const tool: AnyAgentTool = {
name: "list_regions",
label: "list_regions",
description: "List all AWS regions",
parameters: { type: "object" },
execute: vi.fn(),
};
const normalized = normalizeToolParameters(tool);
const parameters = normalized.parameters as Record<string, unknown>;
expect(parameters.type).toBe("object");
expect(parameters.properties).toEqual({});
});
it("preserves existing properties on type:object schemas", () => {
const tool: AnyAgentTool = {
name: "query",
label: "query",
description: "Run a query",
parameters: { type: "object", properties: { q: { type: "string" } } },
execute: vi.fn(),
};
const normalized = normalizeToolParameters(tool);
const parameters = normalized.parameters as Record<string, unknown>;
expect(parameters.type).toBe("object");
expect(parameters.properties).toEqual({ q: { type: "string" } });
});
it("injects properties:{} for type:object with only additionalProperties", () => {
const tool: AnyAgentTool = {
name: "passthrough",
label: "passthrough",
description: "Accept any input",
parameters: { type: "object", additionalProperties: true },
execute: vi.fn(),
};
const normalized = normalizeToolParameters(tool);
const parameters = normalized.parameters as Record<string, unknown>;
expect(parameters.type).toBe("object");
expect(parameters.properties).toEqual({});
expect(parameters.additionalProperties).toBe(true);
});
it("strips compat-declared unsupported schema keywords without provider-specific branching", () => {
const tool: AnyAgentTool = {
name: "demo",
label: "demo",
description: "demo",
parameters: Type.Object({
count: Type.Integer({ minimum: 1, maximum: 5 }),
query: Type.Optional(Type.String({ minLength: 2 })),
}),
execute: vi.fn(),
};
const normalized = normalizeToolParameters(tool, {
modelCompat: {
unsupportedToolSchemaKeywords: ["minimum", "maximum", "minLength"],
},
});
const parameters = normalized.parameters as {
required?: string[];
properties?: Record<string, Record<string, unknown>>;
};
expect(parameters.required).toEqual(["count"]);
expect(parameters.properties?.count.minimum).toBeUndefined();
expect(parameters.properties?.count.maximum).toBeUndefined();
expect(parameters.properties?.count.type).toBe("integer");
expect(parameters.properties?.query.minLength).toBeUndefined();
expect(parameters.properties?.query.type).toBe("string");
});
});