From 5291b2f5a07f81e64a8312f00c7c7e2f04a1e02a Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 23 Jun 2026 01:42:53 +0000 Subject: [PATCH] fix(ai): skipped context management for injected clients Injected Anthropic clients bypass buildAnthropicClientOptions, so this package cannot add the context-management beta header their SDK instance would need before accepting context_management.clear_thinking_20251015. Omit context_management for options.client requests while preserving thinking itself, and add regression coverage for injected-client payload shaping. Fixes #3288 --- packages/ai/CHANGELOG.md | 2 +- packages/ai/src/providers/anthropic.ts | 13 ++++---- .../ai/test/anthropic-stream-envelope.test.ts | 30 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index f167fe2d3..bd038ba1a 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. GitHub Copilot's Anthropic proxy is excluded because it strips Anthropic betas and demotes thinking blocks to text upstream. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) +- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. Injected SDK clients and GitHub Copilot's Anthropic proxy are excluded because this code path cannot add the beta to caller-owned clients, while Copilot strips Anthropic betas and demotes thinking blocks to text upstream. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) ## [16.1.15] - 2026-06-22 diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 7543d0594..8300316a2 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -2966,12 +2966,15 @@ function buildParams( // on the wire, so the model loses the prior reasoning chain across turns // and the KV cache misses every turn (#3288). Narrowing this guard back // to `isOAuthToken` regresses every API-key thinking provider. Skip - // Copilot — its proxy strips Anthropic betas (so the required - // `context-management-2025-06-27` header never lands) and the compat - // flag demotes thinking blocks to text, so `keep: "all"` is a no-op - // that risks the proxy rejecting an unrecognized field. + // injected clients because this code cannot add the required + // `context-management-2025-06-27` beta to caller-owned SDK clients. Skip + // Copilot because its proxy strips Anthropic betas and demotes thinking + // blocks to text upstream, so `keep: "all"` is a no-op that risks proxy + // rejection of an unrecognized field. const shouldKeepThinkingContext = - model.provider !== "github-copilot" && (thinking?.type === "adaptive" || thinking?.type === "enabled"); + !options?.client && + model.provider !== "github-copilot" && + (thinking?.type === "adaptive" || thinking?.type === "enabled"); const contextManagement = shouldKeepThinkingContext ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] } : undefined; diff --git a/packages/ai/test/anthropic-stream-envelope.test.ts b/packages/ai/test/anthropic-stream-envelope.test.ts index f73748009..27cee8e5c 100644 --- a/packages/ai/test/anthropic-stream-envelope.test.ts +++ b/packages/ai/test/anthropic-stream-envelope.test.ts @@ -576,6 +576,36 @@ describe("anthropic stream envelope handling", () => { expect(capturedParams?.tools?.map(tool => tool.name)).toEqual(["web_search"]); expect(capturedOptions?.headers).toEqual({ "X-Umans-Websearch-Provider": "exa" }); }); + + it("does not send context_management through injected clients", async () => { + type CapturedPayload = { + thinking?: { type?: string }; + context_management?: unknown; + }; + let capturedParams: CapturedPayload | undefined; + const client: AnthropicMessagesClientLike = { + messages: { + create(params) { + capturedParams = params as CapturedPayload; + return createMockRequest(createTextSuccessEvents("done")); + }, + }, + }; + + const stream = streamAnthropic(model, context, { + client, + thinkingEnabled: true, + }); + const events: AssistantMessageEvent[] = []; + for await (const event of stream) { + events.push(event); + } + const result = await stream.result(); + + expect(result.content).toEqual([{ type: "text", text: "done" }]); + expect(capturedParams?.thinking?.type).toBe("enabled"); + expect(capturedParams?.context_management).toBeUndefined(); + }); it("unwraps thinking blocks that Anthropic streams with literal thinking tags", async () => { const wrappedThinking = "\n\nCheck logs before accepting container health.\n";