mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 02:42:24 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
144 lines
5.6 KiB
TypeScript
144 lines
5.6 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
// #6951 — Codex Responses API strict mode forces every tool property into
|
|
// `required`, so the model always emits *some* value for "optional" params.
|
|
// `stripEmptyOptionalToolArgs` (open-sse/translator/response/openai-responses/pureHelpers.ts)
|
|
// used to be an allowlist of 2 tool names that only stripped empty-string/empty-array
|
|
// values, so a forced non-empty value on a non-allowlisted tool (or a schema-declared
|
|
// default value) was always forwarded verbatim to the client. This test proves the
|
|
// schema-aware normalization (drop-if-default, generalized drop-if-empty) added for #6951,
|
|
// and the end-to-end schema threading from the request's `tools[]` into the streaming
|
|
// call sites in `openai-responses.ts` (response.output_item.done handling).
|
|
|
|
const { stripEmptyOptionalToolArgs } =
|
|
await import("../../open-sse/translator/response/openai-responses/pureHelpers.ts");
|
|
const { extractToolSchemaMap } =
|
|
await import("../../open-sse/translator/response/openai-responses/toolSchemas.ts");
|
|
const { openaiResponsesToOpenAIResponse } =
|
|
await import("../../open-sse/translator/response/openai-responses.ts");
|
|
|
|
const AGENT_SCHEMA = {
|
|
type: "object",
|
|
properties: {
|
|
description: { type: "string" },
|
|
prompt: { type: "string" },
|
|
subagent_type: { type: "string" },
|
|
model: { type: "string" },
|
|
run_in_background: { type: "boolean" },
|
|
isolation: { type: "string", enum: ["local", "remote"], default: "local" },
|
|
cloud_base_branch: { type: "string" },
|
|
},
|
|
// Responses API strict mode: every property is required, even "optional" ones.
|
|
required: [
|
|
"description",
|
|
"prompt",
|
|
"subagent_type",
|
|
"model",
|
|
"run_in_background",
|
|
"isolation",
|
|
"cloud_base_branch",
|
|
],
|
|
};
|
|
|
|
test("6951: drop-if-default — forced value equal to the schema default is stripped", () => {
|
|
const raw = JSON.stringify({
|
|
description: "x",
|
|
prompt: "y",
|
|
subagent_type: "claude",
|
|
model: "sonnet",
|
|
run_in_background: false,
|
|
isolation: "local", // matches schema default -> indistinguishable from omission
|
|
cloud_base_branch: "",
|
|
});
|
|
const cleaned = JSON.parse(stripEmptyOptionalToolArgs(raw, "Agent", AGENT_SCHEMA));
|
|
assert.equal(Object.prototype.hasOwnProperty.call(cleaned, "isolation"), false);
|
|
// cloud_base_branch is required by this schema -> must NOT be stripped just for being empty.
|
|
assert.equal(Object.prototype.hasOwnProperty.call(cleaned, "cloud_base_branch"), true);
|
|
});
|
|
|
|
test("6951: generalized drop-if-empty — empty optional prop stripped for ANY tool when not in schema.required", () => {
|
|
const schema = {
|
|
type: "object",
|
|
properties: { note: { type: "string" }, tags: { type: "array" } },
|
|
required: [], // both optional
|
|
};
|
|
const raw = JSON.stringify({ note: "", tags: [] });
|
|
const cleaned = JSON.parse(stripEmptyOptionalToolArgs(raw, "SomeOtherTool", schema));
|
|
assert.equal(Object.prototype.hasOwnProperty.call(cleaned, "note"), false);
|
|
assert.equal(Object.prototype.hasOwnProperty.call(cleaned, "tags"), false);
|
|
});
|
|
|
|
test("6951: required-by-schema empty prop is preserved (safety — not indistinguishable from omission)", () => {
|
|
const schema = {
|
|
type: "object",
|
|
properties: { note: { type: "string" } },
|
|
required: ["note"],
|
|
};
|
|
const raw = JSON.stringify({ note: "" });
|
|
const cleaned = JSON.parse(stripEmptyOptionalToolArgs(raw, "SomeOtherTool", schema));
|
|
assert.equal(Object.prototype.hasOwnProperty.call(cleaned, "note"), true);
|
|
});
|
|
|
|
test("6951: no schema supplied — behavior unchanged (allowlist + empty-only, backward compatible)", () => {
|
|
const raw = JSON.stringify({ query: "", tags: [] });
|
|
assert.equal(stripEmptyOptionalToolArgs(raw, "SomeOtherTool"), raw);
|
|
});
|
|
|
|
test("6951: extractToolSchemaMap builds a name->schema map from Chat Completions tools[]", () => {
|
|
const body = {
|
|
tools: [
|
|
{ type: "function", function: { name: "Agent", parameters: AGENT_SCHEMA } },
|
|
{ type: "function", function: { name: "no_schema" } },
|
|
],
|
|
};
|
|
const map = extractToolSchemaMap(body);
|
|
assert.equal(map?.get("Agent"), AGENT_SCHEMA);
|
|
assert.equal(map?.has("no_schema"), false);
|
|
assert.equal(extractToolSchemaMap({}), null);
|
|
});
|
|
|
|
test("6951: extractToolSchemaMap accepts Claude input_schema tools", () => {
|
|
const body = { tools: [{ name: "Agent", input_schema: AGENT_SCHEMA }] };
|
|
const map = extractToolSchemaMap(body);
|
|
assert.equal(map?.get("Agent"), AGENT_SCHEMA);
|
|
});
|
|
|
|
test("6951: RED->GREEN — schema threaded end-to-end strips the default-valued isolation arg", () => {
|
|
// Simulates createSSEStream's TranslateState carrying `toolSchemas` extracted from the
|
|
// request body, as wired in open-sse/utils/stream.ts.
|
|
const state = { toolSchemas: new Map([["Agent", AGENT_SCHEMA]]) };
|
|
|
|
openaiResponsesToOpenAIResponse(
|
|
{
|
|
type: "response.output_item.added",
|
|
item: { type: "function_call", call_id: "call_1", name: "Agent" },
|
|
},
|
|
state
|
|
);
|
|
const done = openaiResponsesToOpenAIResponse(
|
|
{
|
|
type: "response.output_item.done",
|
|
item: {
|
|
type: "function_call",
|
|
call_id: "call_1",
|
|
name: "Agent",
|
|
arguments: JSON.stringify({
|
|
description: "d",
|
|
prompt: "p",
|
|
subagent_type: "claude",
|
|
model: "sonnet",
|
|
run_in_background: false,
|
|
isolation: "local",
|
|
cloud_base_branch: "",
|
|
}),
|
|
},
|
|
},
|
|
state
|
|
);
|
|
|
|
const args = JSON.parse(done.choices[0].delta.tool_calls[0].function.arguments);
|
|
assert.equal(Object.prototype.hasOwnProperty.call(args, "isolation"), false);
|
|
assert.equal(args.description, "d");
|
|
});
|