The before_provider_request hook used model.startsWith("claude") to gate
native web search injection. This matched claude-* models served by any
provider (GitHub Copilot, AWS Bedrock, etc.), incorrectly injecting
Anthropic-only web_search_20250305 tool definitions into non-Anthropic
API requests.
The fix checks the isAnthropicProvider flag (set by model_select via the
provider field) instead of sniffing the model name.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c87f15868a
commit
c344b0af54
2 changed files with 72 additions and 6 deletions
|
|
@ -99,12 +99,11 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|||
const payload = event.payload as Record<string, unknown>;
|
||||
if (!payload) return;
|
||||
|
||||
// Detect Anthropic by model name prefix (works even before model_select fires)
|
||||
const model = payload.model as string | undefined;
|
||||
if (!model || !model.startsWith("claude")) return;
|
||||
|
||||
// Keep provider tracking in sync
|
||||
isAnthropicProvider = true;
|
||||
// Only inject native web search for confirmed Anthropic provider.
|
||||
// model_select sets isAnthropicProvider via the provider field.
|
||||
// Model name prefix is NOT sufficient — other providers (GitHub Copilot,
|
||||
// AWS Bedrock, etc.) serve claude-* models through non-Anthropic APIs.
|
||||
if (!isAnthropicProvider) return;
|
||||
|
||||
// Strip thinking blocks from history to avoid signature validation errors
|
||||
// caused by the SDK dropping server_tool_use/web_search_tool_result blocks.
|
||||
|
|
|
|||
|
|
@ -72,6 +72,14 @@ test("before_provider_request injects web_search for claude models", async () =>
|
|||
const pi = createMockPI();
|
||||
registerNativeSearchHooks(pi);
|
||||
|
||||
// Confirm Anthropic provider via model_select before request
|
||||
await pi.fire("model_select", {
|
||||
type: "model_select",
|
||||
model: { provider: "anthropic", name: "claude-sonnet-4-6" },
|
||||
previousModel: undefined,
|
||||
source: "set",
|
||||
});
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model: "claude-sonnet-4-6-20250514",
|
||||
tools: [{ name: "bash", type: "custom" }],
|
||||
|
|
@ -109,10 +117,48 @@ test("before_provider_request does NOT inject for non-claude models", async () =
|
|||
assert.equal(tools.length, 1, "Should not add tools to non-claude payload");
|
||||
});
|
||||
|
||||
test("before_provider_request does NOT inject for claude model on non-Anthropic provider", async () => {
|
||||
const pi = createMockPI();
|
||||
registerNativeSearchHooks(pi);
|
||||
|
||||
// GitHub Copilot (or Bedrock, etc.) serving a claude model
|
||||
await pi.fire("model_select", {
|
||||
type: "model_select",
|
||||
model: { provider: "copilot", name: "claude-sonnet-4-6" },
|
||||
previousModel: undefined,
|
||||
source: "set",
|
||||
});
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model: "claude-sonnet-4-6-20250514",
|
||||
tools: [{ name: "bash", type: "custom" }],
|
||||
};
|
||||
|
||||
const result = await pi.fire("before_provider_request", {
|
||||
type: "before_provider_request",
|
||||
payload,
|
||||
});
|
||||
|
||||
assert.equal(result, undefined, "Should not modify payload for non-Anthropic provider");
|
||||
const tools = payload.tools as any[];
|
||||
assert.equal(tools.length, 1, "Should not inject web_search for non-Anthropic provider");
|
||||
assert.ok(
|
||||
!tools.some((t: any) => t.type === "web_search_20250305"),
|
||||
"web_search_20250305 must NOT be present for non-Anthropic providers"
|
||||
);
|
||||
});
|
||||
|
||||
test("before_provider_request does not double-inject", async () => {
|
||||
const pi = createMockPI();
|
||||
registerNativeSearchHooks(pi);
|
||||
|
||||
await pi.fire("model_select", {
|
||||
type: "model_select",
|
||||
model: { provider: "anthropic", name: "claude-opus-4-6" },
|
||||
previousModel: undefined,
|
||||
source: "set",
|
||||
});
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model: "claude-opus-4-6-20250514",
|
||||
tools: [{ type: "web_search_20250305", name: "web_search" }],
|
||||
|
|
@ -132,6 +178,13 @@ test("before_provider_request creates tools array if missing", async () => {
|
|||
const pi = createMockPI();
|
||||
registerNativeSearchHooks(pi);
|
||||
|
||||
await pi.fire("model_select", {
|
||||
type: "model_select",
|
||||
model: { provider: "anthropic", name: "claude-haiku-4-5" },
|
||||
previousModel: undefined,
|
||||
source: "set",
|
||||
});
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model: "claude-haiku-4-5-20251001",
|
||||
};
|
||||
|
|
@ -342,6 +395,13 @@ test("before_provider_request removes Brave tools from payload when no BRAVE_API
|
|||
const pi = createMockPI();
|
||||
registerNativeSearchHooks(pi);
|
||||
|
||||
await pi.fire("model_select", {
|
||||
type: "model_select",
|
||||
model: { provider: "anthropic", name: "claude-sonnet-4-6" },
|
||||
previousModel: undefined,
|
||||
source: "set",
|
||||
});
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model: "claude-sonnet-4-6-20250514",
|
||||
tools: [
|
||||
|
|
@ -379,6 +439,13 @@ test("before_provider_request keeps Brave tools in payload when BRAVE_API_KEY se
|
|||
const pi = createMockPI();
|
||||
registerNativeSearchHooks(pi);
|
||||
|
||||
await pi.fire("model_select", {
|
||||
type: "model_select",
|
||||
model: { provider: "anthropic", name: "claude-sonnet-4-6" },
|
||||
previousModel: undefined,
|
||||
source: "set",
|
||||
});
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model: "claude-sonnet-4-6-20250514",
|
||||
tools: [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue