mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 09:42:15 +03:00
fix(providers): use prefix regex for web search fallback detector to catch versioned tool types (#9279)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2e71558a0f
commit
3be585ef41
1
changelog.d/fixes/9279-fix.plan.md
Normal file
1
changelog.d/fixes/9279-fix.plan.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(providers):** the web search fallback detector in `webSearchFallback.ts` used an exact `Set` (`web_search`, `web_search_preview`) that missed Anthropic's date-suffixed server-tool variant `web_search_20250305` (sent by Claude Code 2.1.220+). Changed to prefix regex `/^web_search/`, matching the two other detectors in the codebase, so the fallback intercepts versioned web search tools for OpenAI-compatible upstreams ([#9279](https://github.com/diegosouzapw/OmniRoute/pull/9279))
|
||||
@@ -1,7 +1,10 @@
|
||||
import { FORMATS } from "../translator/formats.ts";
|
||||
|
||||
export const OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME = "omniroute_web_search";
|
||||
const WEB_SEARCH_TOOL_TYPES = new Set(["web_search", "web_search_preview"]);
|
||||
// Prefix match — Anthropic sends date-suffixed variants (web_search_20250305, …).
|
||||
// The other two detectors (openai-responses/helpers.ts, webSearchRouting.ts) already
|
||||
// use /^web_search/ prefix matching; this aligns the fallback detector with them.
|
||||
const WEB_SEARCH_TOOL_TYPES = /^web_search/;
|
||||
const SEARCH_CONTEXT_DEFAULTS: Record<string, number> = {
|
||||
low: 5,
|
||||
medium: 8,
|
||||
@@ -27,13 +30,13 @@ function toRecord(value: unknown): JsonRecord {
|
||||
function isBuiltInWebSearchTool(tool: unknown): tool is JsonRecord {
|
||||
const toolRecord = toRecord(tool);
|
||||
const toolType = typeof toolRecord.type === "string" ? toolRecord.type : "";
|
||||
return WEB_SEARCH_TOOL_TYPES.has(toolType) && !toolRecord.function;
|
||||
return WEB_SEARCH_TOOL_TYPES.test(toolType) && !toolRecord.function;
|
||||
}
|
||||
|
||||
function isBuiltInWebSearchToolChoice(toolChoice: unknown): boolean {
|
||||
const choice = toRecord(toolChoice);
|
||||
const toolType = typeof choice.type === "string" ? choice.type : "";
|
||||
return WEB_SEARCH_TOOL_TYPES.has(toolType);
|
||||
return WEB_SEARCH_TOOL_TYPES.test(toolType);
|
||||
}
|
||||
|
||||
function buildFallbackDescription(tool: JsonRecord): string {
|
||||
|
||||
81
tests/unit/web-search-9279-repro.test.ts
Normal file
81
tests/unit/web-search-9279-repro.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { prepareWebSearchFallbackBody, OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME } =
|
||||
await import("../../open-sse/services/webSearchFallback.ts");
|
||||
|
||||
// #9279 — Anthropic's date-suffixed server-tool variant web_search_20250305
|
||||
// (sent by Claude Code 2.1.220+) is not intercepted by the web search fallback
|
||||
// detector in webSearchFallback.ts:4, which uses an exact Set.
|
||||
// Clasue -> OpenAI-compatible provider requests carry the raw Claude tool shape
|
||||
// { type: "web_search_20250305", name: "web_search", max_uses: 8 }.
|
||||
// The fallback must detect and intercept these too.
|
||||
|
||||
test("#9279 versioned web_search_20250305 IS intercepted with interceptSearchOverride=true", () => {
|
||||
const { body, fallback } = prepareWebSearchFallbackBody(
|
||||
{
|
||||
tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 8 }],
|
||||
},
|
||||
{
|
||||
provider: "opencode-go",
|
||||
sourceFormat: "claude",
|
||||
targetFormat: "openai",
|
||||
nativeCodexPassthrough: false,
|
||||
interceptSearchOverride: true,
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(fallback.enabled, true);
|
||||
assert.equal(
|
||||
fallback.toolName,
|
||||
OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME
|
||||
);
|
||||
assert.equal(fallback.convertedToolCount, 1);
|
||||
});
|
||||
|
||||
test("#9279 versioned web_search_20250305 intercepted even without per-model override (claude->openai is not a native-bypass path)", () => {
|
||||
// sourceFormat=claude, targetFormat=openai is NOT a native bypass path
|
||||
// (supportsNativeWebSearchFallbackBypass returns false), so the fallback
|
||||
// MUST fire without any interceptSearchOverride.
|
||||
const { body, fallback } = prepareWebSearchFallbackBody(
|
||||
{
|
||||
tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 8 }],
|
||||
},
|
||||
{
|
||||
provider: "opencode-go",
|
||||
sourceFormat: "claude",
|
||||
targetFormat: "openai",
|
||||
nativeCodexPassthrough: false,
|
||||
// no interceptSearchOverride — must still be detected by tool type matching
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(fallback.enabled, true);
|
||||
assert.equal(
|
||||
fallback.toolName,
|
||||
OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME
|
||||
);
|
||||
assert.equal(fallback.convertedToolCount, 1);
|
||||
});
|
||||
|
||||
test("#9279 tool_choice with web_search_20250305 redirects to omniroute_web_search", () => {
|
||||
const { body, fallback } = prepareWebSearchFallbackBody(
|
||||
{
|
||||
tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 8 }],
|
||||
tool_choice: { type: "web_search_20250305" },
|
||||
},
|
||||
{
|
||||
provider: "opencode-go",
|
||||
sourceFormat: "claude",
|
||||
targetFormat: "openai",
|
||||
nativeCodexPassthrough: false,
|
||||
interceptSearchOverride: true,
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(fallback.enabled, true);
|
||||
const choice = body.tool_choice as Record<string, unknown>;
|
||||
const fn = choice.function as Record<string, unknown> | undefined;
|
||||
assert.equal(fn?.name, OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME);
|
||||
assert.equal(choice.type, "function");
|
||||
});
|
||||
Reference in New Issue
Block a user