mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 09:42:15 +03:00
* fix(executors): preserve non-strict Codex tool semantics * docs(changelog): add Codex strict semantics fix
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { normalizeCodexTools } from "../../open-sse/executors/codex/tools.ts";
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
function functionTool(name: string, definition: JsonRecord, extra: JsonRecord = {}): JsonRecord {
|
|
return {
|
|
type: "function",
|
|
name,
|
|
parameters: { type: "object" },
|
|
...extra,
|
|
...definition,
|
|
};
|
|
}
|
|
|
|
function nestedFunctionTool(name: string, definition: JsonRecord): JsonRecord {
|
|
return {
|
|
type: "function",
|
|
function: {
|
|
name,
|
|
parameters: { type: "object" },
|
|
...definition,
|
|
},
|
|
};
|
|
}
|
|
|
|
function strictOf(tool: JsonRecord): unknown {
|
|
return tool.strict;
|
|
}
|
|
|
|
test("default false applies to omitted nested and flat function strict", () => {
|
|
const flat = functionTool("flat_tool", {});
|
|
const nested = nestedFunctionTool("nested_tool", {});
|
|
|
|
normalizeCodexTools({ tools: [flat, nested] }, { defaultFunctionStrict: false });
|
|
|
|
assert.equal(strictOf(flat), false);
|
|
assert.equal(strictOf(nested), false);
|
|
});
|
|
|
|
test("omitted function strict stays omitted without a fallback", () => {
|
|
const flat = functionTool("flat_tool", {});
|
|
const nested = nestedFunctionTool("nested_tool", {});
|
|
|
|
normalizeCodexTools({ tools: [flat, nested] });
|
|
|
|
assert.equal(strictOf(flat), undefined);
|
|
assert.equal(strictOf(nested), undefined);
|
|
});
|
|
|
|
test("explicit top-level true and false are preserved", () => {
|
|
for (const value of [true, false]) {
|
|
const tool = functionTool("top_level_tool", { strict: value });
|
|
|
|
normalizeCodexTools({ tools: [tool] }, { defaultFunctionStrict: !value });
|
|
|
|
assert.equal(strictOf(tool), value);
|
|
}
|
|
});
|
|
|
|
test("explicit nested function true and false are preserved", () => {
|
|
for (const value of [true, false]) {
|
|
const tool = nestedFunctionTool("nested_tool", { strict: value });
|
|
|
|
normalizeCodexTools({ tools: [tool] }, { defaultFunctionStrict: !value });
|
|
|
|
assert.equal(strictOf(tool), value);
|
|
}
|
|
});
|
|
|
|
test("top-level boolean strict takes precedence over nested boolean strict", () => {
|
|
const topLevelTrue = nestedFunctionTool("top_true", { strict: false });
|
|
topLevelTrue.strict = true;
|
|
const topLevelFalse = nestedFunctionTool("top_false", { strict: true });
|
|
topLevelFalse.strict = false;
|
|
|
|
normalizeCodexTools({ tools: [topLevelTrue, topLevelFalse] }, { defaultFunctionStrict: true });
|
|
|
|
assert.equal(strictOf(topLevelTrue), true);
|
|
assert.equal(strictOf(topLevelFalse), false);
|
|
});
|
|
|
|
test("nonboolean explicit strict values fall back through the precedence chain", () => {
|
|
const topNonBoolean = nestedFunctionTool("nested_boolean", { strict: false });
|
|
topNonBoolean.strict = "true";
|
|
const bothNonBoolean = nestedFunctionTool("fallback", { strict: "false" });
|
|
bothNonBoolean.strict = 1;
|
|
|
|
normalizeCodexTools({ tools: [topNonBoolean, bothNonBoolean] }, { defaultFunctionStrict: true });
|
|
|
|
assert.equal(strictOf(topNonBoolean), false);
|
|
assert.equal(strictOf(bothNonBoolean), true);
|
|
});
|
|
|
|
test("hosted, namespace, and custom tools are unchanged by function strict defaults", () => {
|
|
const hosted = { type: "web_search", search_context_size: "high" };
|
|
const namespace = {
|
|
type: "namespace",
|
|
name: "mcp__example__",
|
|
tools: [{ type: "function", name: "search", parameters: { type: "object" } }],
|
|
};
|
|
const custom = {
|
|
type: "custom",
|
|
name: "apply_patch",
|
|
format: { type: "grammar", syntax: "lark", definition: "start: /.+/" },
|
|
};
|
|
const tools = [hosted, namespace, custom];
|
|
const before = structuredClone(tools);
|
|
|
|
normalizeCodexTools({ tools }, { defaultFunctionStrict: false, preserveCustomTools: true });
|
|
|
|
assert.deepEqual(tools, before);
|
|
});
|
|
|
|
test("production-like enum and oneOf schema is preserved exactly", () => {
|
|
const parameters = {
|
|
type: "object",
|
|
description: "Dynamic action parameters",
|
|
properties: {
|
|
action: {
|
|
type: "string",
|
|
enum: ["read_file", "write_file", "list_files"],
|
|
oneOf: [
|
|
{
|
|
const: "write_file",
|
|
description: "Write a file",
|
|
title: "Write file",
|
|
$comment: "branch 1",
|
|
},
|
|
{
|
|
const: "read_file",
|
|
description: "Read a file",
|
|
title: "Read file",
|
|
$comment: "branch 2",
|
|
},
|
|
{
|
|
const: "list_files",
|
|
description: "List files",
|
|
title: "List files",
|
|
$comment: "branch 3",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
required: ["action"],
|
|
};
|
|
const tool = {
|
|
type: "function",
|
|
function: { name: "dynamic_tool", parameters },
|
|
} as JsonRecord;
|
|
const expected = structuredClone(parameters);
|
|
|
|
normalizeCodexTools({ tools: [tool] });
|
|
|
|
assert.deepEqual(tool.parameters, expected);
|
|
});
|