From 9f522ee7dffdb72d2482348c025f5abfd2153702 Mon Sep 17 00:00:00 2001 From: Kailigithub <100wkl@163.com> Date: Sat, 13 Jun 2026 19:52:02 +0800 Subject: [PATCH] fix(copilot): disable eager tool streaming for Claude 4.5 (#75393) Disable Anthropic eager tool streaming only for GitHub Copilot Claude 4.5 proxy routes, preserving newer Claude behavior. Fixes #75348 Co-authored-by: Kailigithub --- extensions/github-copilot/model-metadata.ts | 14 +++++++++++++- extensions/github-copilot/models.test.ts | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/extensions/github-copilot/model-metadata.ts b/extensions/github-copilot/model-metadata.ts index 0816c892eef..277a0c914ff 100644 --- a/extensions/github-copilot/model-metadata.ts +++ b/extensions/github-copilot/model-metadata.ts @@ -56,6 +56,10 @@ function isCopilotGeminiModelId(modelId: string): boolean { return /(?:^|[-_.])gemini(?:$|[-_.])/.test(modelId); } +function isCopilotClaude45ModelId(modelId: string): boolean { + return /^claude-(?:haiku|opus|sonnet)-4[.-]5(?:$|[-.])/.test(modelId); +} + export function resolveCopilotTransportApi(modelId: string): CopilotRuntimeApi { const normalized = normalizeOptionalLowercaseString(modelId) ?? ""; if (normalized.includes("claude")) { @@ -71,7 +75,15 @@ export function resolveCopilotModelCompat( modelId: string, ): ModelDefinitionConfig["compat"] | undefined { const normalized = normalizeOptionalLowercaseString(modelId) ?? ""; - return isCopilotGeminiModelId(normalized) ? { ...COPILOT_CHAT_COMPLETIONS_COMPAT } : undefined; + if (isCopilotGeminiModelId(normalized)) { + return { ...COPILOT_CHAT_COMPLETIONS_COMPAT }; + } + // Copilot's Claude 4.5 endpoints reject Anthropic's eager tool extension, + // while current Claude 4.6+ endpoints accept it. + if (isCopilotClaude45ModelId(normalized)) { + return { supportsEagerToolInputStreaming: false }; + } + return undefined; } function compatSupportsEffort( diff --git a/extensions/github-copilot/models.test.ts b/extensions/github-copilot/models.test.ts index 5189967de93..51e634ae0b5 100644 --- a/extensions/github-copilot/models.test.ts +++ b/extensions/github-copilot/models.test.ts @@ -90,8 +90,18 @@ describe("github-copilot model defaults", () => { const def = buildCopilotModelDefinition("claude-sonnet-4.6"); expect(def.id).toBe("claude-sonnet-4.6"); expect(def.api).toBe("anthropic-messages"); + expect(def.compat).toBeUndefined(); }); + it.each(["claude-haiku-4.5", "claude-sonnet-4-5"])( + "disables eager tool streaming for Copilot Claude 4.5 model %s", + (modelId) => { + expect(buildCopilotModelDefinition(modelId).compat).toEqual({ + supportsEagerToolInputStreaming: false, + }); + }, + ); + it("uses static metadata overrides for gpt-5.5 fallback rows", () => { const def = buildCopilotModelDefinition("gpt-5.5"); expect(def).toEqual({ @@ -243,6 +253,12 @@ describe("resolveCopilotForwardCompatModel", () => { expect((result as unknown as Record).input).toEqual(["text", "image"]); }); + it("disables eager tool streaming for synthetic Copilot Claude 4.5 models", () => { + const result = requireResolvedModel(createMockCtx("claude-haiku-4.5")); + expect(result.api).toBe("anthropic-messages"); + expect(result.compat).toEqual({ supportsEagerToolInputStreaming: false }); + }); + it("creates synthetic Gemini models with Chat Completions compatibility", () => { const result = requireResolvedModel(createMockCtx("gemini-3.1-pro-preview")); expect((result as unknown as Record).api).toBe("openai-completions"); @@ -620,6 +636,7 @@ describe("fetchCopilotModelCatalog", () => { const opus45 = out.find((m) => m.id === "claude-opus-4-5"); expect(opus45?.thinkingLevelMap).toEqual({ xhigh: null, max: null }); expect(opus45?.compat).toEqual({ + supportsEagerToolInputStreaming: false, supportedReasoningEfforts: ["low", "medium", "high", "max"], }); });