mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Integrated into release/v3.8.6.
This commit is contained in:
committed by
GitHub
parent
fee1c17d51
commit
c216085e92
@@ -27,6 +27,7 @@
|
||||
- **gemini:** translate signature-less Gemini thinking model tool calls to text parts to prevent `400 "missing thought_signature"` errors (#2801 — thanks @herjarsa)
|
||||
- **translator:** strip `safety_identifier` from `/v1/responses` body before forwarding to Chat Completions upstream; fixes LobeHub-originated `400` errors (#2770)
|
||||
- **warning-cleanup:** relax node engine constraint to `>=22.0.0` and clean dependencies (keeping `marked-terminal` to prevent TUI REPL crash) (#2792 — thanks @oyi77)
|
||||
- **translator:** silently drop `tool_search` built-in tool type instead of returning 400 — newer Codex clients send `tool_search` as a Responses API built-in with no Chat Completions equivalent (#2766)
|
||||
- **usage:** un-invert GitHub Copilot Free / limited plan quota — `limited_user_quotas` is the *remaining* count, not used, so the dashboard now shows 100% when the quota is untouched and 0% when fully exhausted (#2876 — thanks @androw)
|
||||
- **fix(cli):** register openclaw in the CLI tool-detector so it appears in `omniroute status` alongside its existing API and config support ([#2833](https://github.com/diegosouzapw/OmniRoute/issues/2833))
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ const COPILOT_REASONING_SUMMARY_MARKER = "_omnirouteCopilotReasoningSummary";
|
||||
|
||||
// Forward-compatible regex: matches web_search, web_search_20250305, and any future versioned names.
|
||||
const WEB_SEARCH_TOOL_TYPES = /^web_search/;
|
||||
// tool_search is a Responses API built-in sent by newer Codex clients; it has no Chat Completions
|
||||
// equivalent and must be silently dropped (not rejected with 400).
|
||||
const TOOL_SEARCH_TOOL_TYPES = /^tool_search/;
|
||||
|
||||
function toRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
|
||||
@@ -72,6 +75,8 @@ export function openaiResponsesToOpenAIRequest(
|
||||
// Allow: function tools, tools already in Chat format (have .function property), CLI subagent tools,
|
||||
// namespace tools (MCP tool groups used by Codex/OpenAI Responses API), and web_search server tools
|
||||
// (Anthropic versioned: web_search_20250305, web_search_20250101, etc. — or plain web_search).
|
||||
// tool_search is a Responses API built-in sent by newer Codex clients; silently skip it here
|
||||
// (it will be filtered out during tools conversion below).
|
||||
if (
|
||||
toolType &&
|
||||
toolType !== "function" &&
|
||||
@@ -79,6 +84,7 @@ export function openaiResponsesToOpenAIRequest(
|
||||
toolType !== "command" &&
|
||||
toolType !== "namespace" &&
|
||||
!WEB_SEARCH_TOOL_TYPES.test(toolType) &&
|
||||
!TOOL_SEARCH_TOOL_TYPES.test(toolType) &&
|
||||
!tool.function
|
||||
) {
|
||||
throw unsupportedFeature(
|
||||
@@ -257,26 +263,33 @@ export function openaiResponsesToOpenAIRequest(
|
||||
|
||||
// Convert tools format
|
||||
if (Array.isArray(root.tools)) {
|
||||
result.tools = root.tools.map((toolValue) => {
|
||||
const tool = toRecord(toolValue);
|
||||
if (tool.function) return toolValue;
|
||||
const toolType = toString(tool.type);
|
||||
// Pass web_search server tools through with their original type (versioned or plain).
|
||||
// These have no Chat Completions equivalent; preserve as-is so upstreams that understand
|
||||
// Anthropic-style web_search_YYYYMMDD naming receive the exact name they expect.
|
||||
if (WEB_SEARCH_TOOL_TYPES.test(toolType)) {
|
||||
return toolValue;
|
||||
}
|
||||
return {
|
||||
type: "function",
|
||||
function: {
|
||||
name: toString(tool.name),
|
||||
description: toString(tool.description),
|
||||
parameters: tool.parameters,
|
||||
strict: tool.strict,
|
||||
},
|
||||
};
|
||||
});
|
||||
result.tools = root.tools
|
||||
.filter((toolValue) => {
|
||||
const tool = toRecord(toolValue);
|
||||
const toolType = toString(tool.type);
|
||||
// tool_search has no Chat Completions equivalent; drop it silently (issue #2766).
|
||||
return !TOOL_SEARCH_TOOL_TYPES.test(toolType);
|
||||
})
|
||||
.map((toolValue) => {
|
||||
const tool = toRecord(toolValue);
|
||||
if (tool.function) return toolValue;
|
||||
const toolType = toString(tool.type);
|
||||
// Pass web_search server tools through with their original type (versioned or plain).
|
||||
// These have no Chat Completions equivalent; preserve as-is so upstreams that understand
|
||||
// Anthropic-style web_search_YYYYMMDD naming receive the exact name they expect.
|
||||
if (WEB_SEARCH_TOOL_TYPES.test(toolType)) {
|
||||
return toolValue;
|
||||
}
|
||||
return {
|
||||
type: "function",
|
||||
function: {
|
||||
name: toString(tool.name),
|
||||
description: toString(tool.description),
|
||||
parameters: tool.parameters,
|
||||
strict: tool.strict,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Filter orphaned tool results (no matching tool_call in assistant messages)
|
||||
|
||||
@@ -696,3 +696,54 @@ test("Responses -> Chat: unknown tool type still throws unsupported_feature (no
|
||||
(error: any) => error.statusCode === 400 && error.errorType === "unsupported_feature"
|
||||
);
|
||||
});
|
||||
|
||||
// --- Issue #2766: tool_search built-in should be silently dropped ---
|
||||
|
||||
test("Responses -> Chat: tool_search does not throw (issue #2766)", () => {
|
||||
// Codex newer clients send tool_search as a Responses API built-in.
|
||||
// OmniRoute must not return 400 — it should silently drop the tool_search entry.
|
||||
assert.doesNotThrow(() =>
|
||||
openaiResponsesToOpenAIRequest(
|
||||
"gpt-4o",
|
||||
{
|
||||
input: [{ role: "user", content: [{ type: "input_text", text: "search" }] }],
|
||||
tools: [{ type: "tool_search", name: "search" }],
|
||||
},
|
||||
false,
|
||||
null
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("Responses -> Chat: tool_search is stripped from output tools array (issue #2766)", () => {
|
||||
// Codex clients send tool_search alongside function tools. tool_search has no
|
||||
// Chat Completions equivalent and must be dropped; function tools must remain.
|
||||
const result = openaiResponsesToOpenAIRequest(
|
||||
"gpt-4o",
|
||||
{
|
||||
input: [{ role: "user", content: [{ type: "input_text", text: "hello" }] }],
|
||||
tools: [
|
||||
{ type: "tool_search", name: "search" },
|
||||
{
|
||||
type: "function",
|
||||
name: "foo",
|
||||
description: "A function",
|
||||
parameters: { type: "object" },
|
||||
},
|
||||
],
|
||||
},
|
||||
false,
|
||||
null
|
||||
) as Record<string, unknown>;
|
||||
|
||||
const tools = result.tools as any[];
|
||||
assert.ok(Array.isArray(tools), "tools array must be present");
|
||||
assert.equal(
|
||||
tools.some((t) => t.type === "tool_search"),
|
||||
false,
|
||||
"tool_search must be stripped from output"
|
||||
);
|
||||
assert.equal(tools.length, 1, "only the function tool must remain");
|
||||
assert.equal(tools[0].type, "function");
|
||||
assert.equal(tools[0].function.name, "foo");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user