132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { sanitizeKiroTools } from "../../open-sse/utils/kiroSanitizer.ts";
|
|
const { convertKiroToOpenAI } = await import(
|
|
"../../open-sse/translator/response/kiro-to-openai.ts"
|
|
);
|
|
|
|
// #1375 — Kiro returns 400 "Improperly formed request" when a tool schema
|
|
// carries unsupported JSON-Schema keywords or a tool name longer than 64 chars.
|
|
|
|
test("sanitizeKiroTools: strips unsupported JSON-Schema keywords recursively", () => {
|
|
const tools = [
|
|
{
|
|
toolSpecification: {
|
|
name: "search",
|
|
inputSchema: {
|
|
json: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
properties: {
|
|
filter: {
|
|
anyOf: [{ type: "string" }, { type: "null" }],
|
|
$ref: "#/$defs/Filter",
|
|
description: "kept",
|
|
},
|
|
},
|
|
$defs: { Filter: { type: "string" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
const { tools: out } = sanitizeKiroTools(tools);
|
|
const schema = out[0].toolSpecification.inputSchema.json;
|
|
|
|
assert.equal("additionalProperties" in schema, false);
|
|
assert.equal("$schema" in schema, false);
|
|
assert.equal("$defs" in schema, false);
|
|
assert.equal("anyOf" in schema.properties.filter, false);
|
|
assert.equal("$ref" in schema.properties.filter, false);
|
|
// Non-stripped keys survive.
|
|
assert.equal(schema.properties.filter.description, "kept");
|
|
assert.equal(schema.type, "object");
|
|
});
|
|
|
|
test("sanitizeKiroTools: drops empty required array and guarantees required:[]", () => {
|
|
const { tools: out } = sanitizeKiroTools([
|
|
{
|
|
toolSpecification: {
|
|
name: "noop",
|
|
inputSchema: { json: { type: "object", properties: {}, required: [] } },
|
|
},
|
|
},
|
|
]);
|
|
|
|
const schema = out[0].toolSpecification.inputSchema.json;
|
|
assert.deepEqual(schema.required, []);
|
|
});
|
|
|
|
test("sanitizeKiroTools: hash-truncates names >64 chars and records nameMap", () => {
|
|
const longName = "a".repeat(80);
|
|
const { tools: out, nameMap } = sanitizeKiroTools([
|
|
{
|
|
toolSpecification: {
|
|
name: longName,
|
|
inputSchema: { json: { type: "object", properties: {} } },
|
|
},
|
|
},
|
|
]);
|
|
|
|
const truncated = out[0].toolSpecification.name;
|
|
assert.ok(truncated.length <= 64, `truncated name should be <=64, got ${truncated.length}`);
|
|
assert.notEqual(truncated, longName);
|
|
// nameMap maps the truncated name back to the original.
|
|
assert.equal(nameMap.get(truncated), longName);
|
|
});
|
|
|
|
test("sanitizeKiroTools: short names are left untouched with an empty nameMap", () => {
|
|
const { tools: out, nameMap } = sanitizeKiroTools([
|
|
{
|
|
toolSpecification: {
|
|
name: "read_file",
|
|
inputSchema: { json: { type: "object", properties: {}, required: ["path"] } },
|
|
},
|
|
},
|
|
]);
|
|
|
|
assert.equal(out[0].toolSpecification.name, "read_file");
|
|
assert.equal(out[0].toolSpecification.inputSchema.json.required[0], "path");
|
|
assert.equal(nameMap.size, 0);
|
|
});
|
|
|
|
test("sanitizeKiroTools: non-array input is returned untouched", () => {
|
|
const { tools, nameMap } = sanitizeKiroTools(undefined);
|
|
assert.equal(tools, undefined);
|
|
assert.equal(nameMap.size, 0);
|
|
});
|
|
|
|
test("kiro-to-openai: streamed tool name is reverse-mapped via state.toolNameMap (#1375)", () => {
|
|
const longName = "b".repeat(80);
|
|
// Build the truncated name exactly as sanitizeKiroTools would, then verify
|
|
// the response translator maps the streamed (truncated) name back.
|
|
const { tools: out, nameMap } = sanitizeKiroTools([
|
|
{
|
|
toolSpecification: {
|
|
name: longName,
|
|
inputSchema: { json: { type: "object", properties: {} } },
|
|
},
|
|
},
|
|
]);
|
|
const truncated = out[0].toolSpecification.name;
|
|
|
|
const result = convertKiroToOpenAI(
|
|
{ _eventType: "toolUseEvent", toolUseId: "call_9", name: truncated, input: {} },
|
|
{ toolNameMap: nameMap }
|
|
);
|
|
|
|
assert.equal(result.choices[0].delta.tool_calls[0].function.name, longName);
|
|
});
|
|
|
|
test("kiro-to-openai: unmapped tool name passes through unchanged", () => {
|
|
const result = convertKiroToOpenAI(
|
|
{ _eventType: "toolUseEvent", toolUseId: "call_10", name: "read_file", input: {} },
|
|
{ toolNameMap: new Map() }
|
|
);
|
|
|
|
assert.equal(result.choices[0].delta.tool_calls[0].function.name, "read_file");
|
|
});
|