fix(ai): advertised context-management beta for api-key thinking
Anthropic rejects context_management.clear_thinking_20251015 without the context-management-2025-06-27 beta header. OAuth requests carried it via claudeCodeAgentBetaDefaults; API-key requests pushed the new context_management field without the beta, so the server rejected them. Push the beta into extraBetas alongside the field for every API-key thinking request, and exclude the GitHub Copilot proxy (which strips Anthropic betas and demotes thinking blocks upstream) from emitting the field. Fixes #3288
This commit is contained in:
@@ -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. ([#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. 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))
|
||||
|
||||
## [16.1.15] - 2026-06-22
|
||||
|
||||
|
||||
@@ -120,10 +120,11 @@ export function buildBetaHeader(baseBetas: readonly string[], extraBetas: readon
|
||||
}
|
||||
|
||||
const midConversationSystemBeta = "mid-conversation-system-2026-04-07";
|
||||
const contextManagementBeta = "context-management-2025-06-27";
|
||||
const claudeCodeUtilityBetaDefaults = [
|
||||
"oauth-2025-04-20",
|
||||
"interleaved-thinking-2025-05-14",
|
||||
"context-management-2025-06-27",
|
||||
contextManagementBeta,
|
||||
"prompt-caching-scope-2026-01-05",
|
||||
"structured-outputs-2025-12-15",
|
||||
] as const;
|
||||
@@ -131,7 +132,7 @@ const claudeCodeAgentBetaDefaults = [
|
||||
"claude-code-20250219",
|
||||
"oauth-2025-04-20",
|
||||
"interleaved-thinking-2025-05-14",
|
||||
"context-management-2025-06-27",
|
||||
contextManagementBeta,
|
||||
"prompt-caching-scope-2026-01-05",
|
||||
midConversationSystemBeta,
|
||||
"advanced-tool-use-2025-11-20",
|
||||
@@ -1680,6 +1681,20 @@ const streamAnthropicOnce = (
|
||||
// carry it in the Claude Code list).
|
||||
extraBetas.push(midConversationSystemBeta);
|
||||
}
|
||||
// `context_management.clear_thinking_20251015` requires this beta. OAuth
|
||||
// requests carry it in `claudeCodeAgentBetaDefaults`; API-key requests
|
||||
// need it added explicitly so the field is honored instead of rejected
|
||||
// (#3288). Skip Copilot — its proxy strips Anthropic betas and the
|
||||
// upstream compat flag demotes thinking blocks to text, so there is
|
||||
// nothing for `keep: "all"` to preserve.
|
||||
if (
|
||||
model.reasoning &&
|
||||
options?.thinkingEnabled &&
|
||||
model.provider !== "github-copilot" &&
|
||||
!extraBetas.includes(contextManagementBeta)
|
||||
) {
|
||||
extraBetas.push(contextManagementBeta);
|
||||
}
|
||||
|
||||
const created = createClient(model, {
|
||||
model,
|
||||
@@ -2950,8 +2965,13 @@ function buildParams(
|
||||
// strip the replayed thinking blocks `replayUnsignedThinking` puts back
|
||||
// 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.
|
||||
const shouldKeepThinkingContext = thinking?.type === "adaptive" || thinking?.type === "enabled";
|
||||
// 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.
|
||||
const shouldKeepThinkingContext =
|
||||
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;
|
||||
|
||||
@@ -406,6 +406,41 @@ describe("Anthropic request fingerprint alignment", () => {
|
||||
expect(capturedBeta).toContain("mid-conversation-system-2026-04-07");
|
||||
});
|
||||
|
||||
it("adds the context-management beta to API-key thinking requests", async () => {
|
||||
let capturedBeta: string | undefined;
|
||||
const fetchMock = (async (_input: string | URL | Request, init?: RequestInit) => {
|
||||
capturedBeta = (init?.headers as Record<string, string> | undefined)?.["anthropic-beta"];
|
||||
return new Response(
|
||||
JSON.stringify({ type: "error", error: { type: "invalid_request_error", message: "captured" } }),
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}) as typeof fetch;
|
||||
|
||||
// `context_management.clear_thinking_20251015` is rejected without
|
||||
// the `context-management-2025-06-27` beta. OAuth requests carry it
|
||||
// via `claudeCodeAgentBetaDefaults`; API-key requests must add it
|
||||
// explicitly whenever thinking is enabled so the field is honored
|
||||
// instead of dropped on the floor (#3288).
|
||||
await streamAnthropic(
|
||||
ANTHROPIC_MODEL,
|
||||
{ systemPrompt: ["Stay concise."], messages: [{ role: "user", content: "Hi", timestamp: Date.now() }] },
|
||||
{ apiKey: "sk-ant-api-test", thinkingEnabled: true, fetch: fetchMock },
|
||||
).result();
|
||||
|
||||
expect(capturedBeta).toContain("context-management-2025-06-27");
|
||||
|
||||
capturedBeta = undefined;
|
||||
await streamAnthropic(
|
||||
ANTHROPIC_MODEL,
|
||||
{ systemPrompt: ["Stay concise."], messages: [{ role: "user", content: "Hi", timestamp: Date.now() }] },
|
||||
{ apiKey: "sk-ant-api-test", thinkingEnabled: false, fetch: fetchMock },
|
||||
).result();
|
||||
|
||||
// No context_management field is sent when thinking is disabled, so the
|
||||
// beta MUST NOT be advertised either.
|
||||
expect(capturedBeta ?? "").not.toContain("context-management-2025-06-27");
|
||||
});
|
||||
|
||||
it("billing-header fingerprint uses first user message, not leading developer message", async () => {
|
||||
const userText = "Hello from user with enough chars padding here";
|
||||
|
||||
|
||||
@@ -56,9 +56,15 @@ describe("GitHub Copilot reasoning request construction", () => {
|
||||
const payload = (await captureAnthropicPayload(model)) as {
|
||||
thinking?: { type?: string };
|
||||
output_config?: { effort?: string };
|
||||
context_management?: unknown;
|
||||
};
|
||||
|
||||
expect(payload.thinking).toEqual({ type: "adaptive" });
|
||||
expect(payload.output_config).toEqual({ effort: "high" });
|
||||
// The Copilot Anthropic proxy strips Anthropic betas and demotes
|
||||
// thinking blocks to text upstream — the `context_management` field
|
||||
// would have no replayed thinking to keep and risks proxy rejection
|
||||
// of an unrecognized field. The field MUST NOT be sent (#3288).
|
||||
expect(payload.context_management).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user