Skip to content

Commit 353dfeb

Browse files
haoyu-haoyuclaudevincentkocsteipete
authored
fix(anthropic): migrate 1M context to GA handling
* feat(anthropic): migrate 1M context from beta to GA Anthropic has graduated the 1M context window from beta to GA. This commit: - Stops injecting the context-1m-2025-08-07 beta header when context1m: true is configured - Removes the OAuth token skip logic that was needed because Anthropic previously rejected the context-1m beta with OAuth auth (OAuth now supports 1M natively) - Strips the legacy beta header from user-configured anthropicBeta arrays to prevent sending a stale header - Removes the now-unused isAnthropic1MModel helper, ANTHROPIC_1M_MODEL_PREFIXES constant, and logger import from the stream wrappers The context1m config param continues to be respected for context window sizing in context.ts — only the beta header injection is removed. Closes #45550 (Phase 1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(anthropic): migrate 1M context handling to GA * fix(clownfish): address review for ghcrawl-156721-autonomous-smoke (1) * fix(anthropic): restrict ga 1m context models * docs(anthropic): align ga 1m context guidance * fix(anthropic): normalize ga 1m model metadata --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent 5c535df commit 353dfeb

12 files changed

Lines changed: 279 additions & 121 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Docs: https://docs.openclaw.ai
5555

5656
- CLI/tasks: reject partially numeric `openclaw tasks audit --limit` values so audit limits must be real positive integers instead of accepting strings like `5abc`. (#84901) Thanks @jbetala7.
5757
- Status/diagnostics: bound deep Docker audit probes so `openclaw status --deep` reports slow container checks instead of hanging behind unbounded inspection. (#85476) Thanks @giodl73-repo.
58+
- Providers/Anthropic: migrate 1M context handling to GA-capable Claude 4.x models by sizing eligible models at 1M without the retired `context-1m-2025-08-07` beta, ignoring that retired beta in older configs, and preserving OAuth-required Anthropic beta headers. (#45613) Thanks @haoyu-haoyu.
5859
- Twitch: keep stale message-handler cleanup callbacks from removing newer handler registrations for the same account, preserving inbound message delivery after reconnects. Fixes #83888. (#85425) Thanks @alkor2000.
5960
- Memory/LanceDB: expose public memory artifacts through the active memory provider bridge so memory-wiki imports durable memory files, daily notes, dream reports, and event logs without depending on memory-core internals. Fixes #83604. (#85060) Thanks @brokemac79.
6061
- Docker setup: stop printing the Gateway bearer token in setup logs and printed follow-up commands.

docs/gateway/troubleshooting.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,16 @@ openclaw config get agents.defaults.models
171171

172172
Look for:
173173

174-
- Selected Anthropic Opus/Sonnet model has `params.context1m: true`.
174+
- Selected Anthropic model is a GA-capable 1M Claude 4.x model, or the model has legacy `params.context1m: true`.
175175
- Current Anthropic credential is not eligible for long-context usage.
176-
- Requests fail only on long sessions/model runs that need the 1M beta path.
176+
- Requests fail only on long sessions/model runs that need the 1M context path.
177177

178178
Fix options:
179179

180180
<Steps>
181-
<Step title="Disable context1m">
182-
Disable `context1m` for that model to fall back to the normal context window.
181+
<Step title="Use a standard context window">
182+
Switch to a standard-window model, or remove legacy `context1m` from older
183+
model config that is not GA-capable for 1M context.
183184
</Step>
184185
<Step title="Use an eligible credential">
185186
Use an Anthropic credential that is eligible for long-context requests, or switch to an Anthropic API key.

docs/help/faq-first-run.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -578,9 +578,10 @@ and troubleshooting see the main [FAQ](/help/faq).
578578

579579
If the message is specifically:
580580
`Extra usage is required for long context requests`, the request is trying to use
581-
Anthropic's 1M context beta (`context1m: true`). That only works when your
582-
credential is eligible for long-context billing (API key billing or the
583-
OpenClaw Claude-login path with Extra Usage enabled).
581+
Anthropic's 1M context window (a GA-capable 1M Claude 4.x model or legacy
582+
`context1m: true` config). That only works when your credential is eligible
583+
for long-context billing (API key billing or the OpenClaw Claude-login path
584+
with Extra Usage enabled).
584585

585586
Tip: set a **fallback model** so OpenClaw can keep replying while a provider is rate-limited.
586587
See [Models](/cli/models), [OAuth](/concepts/oauth), and

docs/providers/anthropic.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,37 +267,41 @@ OpenClaw supports Anthropic's prompt caching feature for API-key auth.
267267

268268
</Accordion>
269269

270-
<Accordion title="1M context window (beta)">
271-
Anthropic's 1M context window is beta-gated. Enable it per model:
270+
<Accordion title="1M context window">
271+
Anthropic's 1M context window is available on GA-capable Claude 4.x models
272+
such as Opus 4.6, Opus 4.7, and Sonnet 4.6. OpenClaw sizes those models at
273+
1M automatically:
272274

273275
```json5
274276
{
275277
agents: {
276278
defaults: {
277279
models: {
278-
"anthropic/claude-opus-4-6": {
279-
params: { context1m: true },
280-
},
280+
"anthropic/claude-opus-4-6": {},
281281
},
282282
},
283283
},
284284
}
285285
```
286286

287-
OpenClaw maps this to `anthropic-beta: context-1m-2025-08-07` on requests.
287+
Older configs can keep `params.context1m: true`, but OpenClaw no longer sends
288+
the retired `context-1m-2025-08-07` beta header. Older `anthropicBeta` config
289+
entries with that value are ignored during request header resolution and
290+
unsupported older Claude models stay on their normal context window.
288291

289292
`params.context1m: true` also applies to the Claude CLI backend
290-
(`claude-cli/*`) for eligible Opus and Sonnet models, expanding the runtime
291-
context window for those CLI sessions to match the direct-API behavior.
293+
(`claude-cli/*`) for eligible GA-capable Opus and Sonnet models, preserving
294+
the runtime context window for those CLI sessions to match the direct-API
295+
behavior.
292296

293297
<Warning>
294-
Requires long-context access on your Anthropic credential. Legacy token auth (`sk-ant-oat-*`) is rejected for 1M context requests — OpenClaw logs a warning and falls back to the standard context window.
298+
Requires long-context access on your Anthropic credential. OAuth/subscription token auth keeps its required Anthropic beta headers, but OpenClaw strips the retired 1M beta header if it remains in older config.
295299
</Warning>
296300

297301
</Accordion>
298302

299303
<Accordion title="Claude Opus 4.7 1M context">
300-
`anthropic/claude-opus-4.7` and its `claude-cli` variant have a 1M context
304+
`anthropic/claude-opus-4-7` and its `claude-cli` variant have a 1M context
301305
window by default — no `params.context1m: true` needed.
302306
</Accordion>
303307
</AccordionGroup>

docs/reference/token-use.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -194,31 +194,30 @@ agents:
194194
`agents.list[].params` merges on top of the selected model's `params`, so you can
195195
override only `cacheRetention` and inherit other model defaults unchanged.
196196

197-
### Example: enable Anthropic 1M context beta header
197+
### Anthropic 1M context
198198

199-
Anthropic's 1M context window is currently beta-gated. OpenClaw can inject the
200-
required `anthropic-beta` value when you enable `context1m` on supported Opus
201-
or Sonnet models.
199+
OpenClaw sizes GA-capable Claude 4.x models such as Opus 4.6, Opus 4.7, and
200+
Sonnet 4.6 with Anthropic's 1M context window. You do not need
201+
`params.context1m: true` for those models.
202202

203203
```yaml
204204
agents:
205205
defaults:
206206
models:
207207
"anthropic/claude-opus-4-6":
208-
params:
209-
context1m: true
208+
alias: opus
210209
```
211210

212-
This maps to Anthropic's `context-1m-2025-08-07` beta header.
213-
214-
This only applies when `context1m: true` is set on that model entry.
211+
Older configs can keep `context1m: true`, but OpenClaw no longer sends
212+
Anthropic's retired `context-1m-2025-08-07` beta header for this setting and
213+
does not expand unsupported older Claude models to 1M.
215214

216215
Requirement: the credential must be eligible for long-context usage. If not,
217216
Anthropic responds with a provider-side rate limit error for that request.
218217

219218
If you authenticate Anthropic with OAuth/subscription tokens (`sk-ant-oat-*`),
220-
OpenClaw skips the `context-1m-*` beta header because Anthropic currently
221-
rejects that combination with HTTP 401.
219+
OpenClaw preserves the OAuth-required Anthropic beta headers while stripping the
220+
retired `context-1m-*` beta if it remains in older config.
222221

223222
## Tips for reducing token pressure
224223

extensions/anthropic/index.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,12 +546,14 @@ describe("anthropic provider replay hooks", () => {
546546
expect(normalized?.input).toEqual(["text", "image"]);
547547
});
548548

549-
it("normalizes exact claude opus 4.7 variants to 1M context", async () => {
549+
it("normalizes GA 1M Claude variants to 1M context", async () => {
550550
const provider = await registerSingleProviderPlugin(anthropicPlugin);
551551

552552
for (const [runtimeProvider, modelId] of [
553553
["anthropic", "claude-opus-4-7"],
554554
["claude-cli", "claude-opus-4.7-20260219"],
555+
["anthropic", "claude-opus-4-6"],
556+
["anthropic", "claude-sonnet-4-6"],
555557
] as const) {
556558
expectFields(
557559
provider.normalizeResolvedModel?.({
@@ -578,6 +580,29 @@ describe("anthropic provider replay hooks", () => {
578580
}
579581
});
580582

583+
it("does not normalize legacy Claude 4.5 models to 1M context", async () => {
584+
const provider = await registerSingleProviderPlugin(anthropicPlugin);
585+
586+
const normalized = provider.normalizeResolvedModel?.({
587+
provider: "anthropic",
588+
modelId: "claude-sonnet-4-5",
589+
model: {
590+
id: "claude-sonnet-4-5",
591+
name: "Claude Sonnet 4.5",
592+
provider: "anthropic",
593+
api: "anthropic-messages",
594+
reasoning: true,
595+
input: ["text", "image"],
596+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
597+
contextWindow: 200_000,
598+
contextTokens: 200_000,
599+
maxTokens: 32_000,
600+
},
601+
} as never);
602+
603+
expect(normalized).toBeUndefined();
604+
});
605+
581606
it("resolves claude-cli synthetic oauth auth", async () => {
582607
readClaudeCliCredentialsForRuntimeMock.mockReset();
583608
readClaudeCliCredentialsForRuntimeMock.mockReturnValue({

extensions/anthropic/register.runtime.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ type UpsertAuthProfileParams = Parameters<typeof upsertAuthProfileWithLock>[0];
4848
const DEFAULT_ANTHROPIC_MODEL = "anthropic/claude-opus-4-7";
4949
const ANTHROPIC_OPUS_47_MODEL_ID = "claude-opus-4-7";
5050
const ANTHROPIC_OPUS_47_DOT_MODEL_ID = "claude-opus-4.7";
51-
const ANTHROPIC_OPUS_47_CONTEXT_TOKENS = 1_048_576;
51+
const ANTHROPIC_GA_1M_CONTEXT_TOKENS = 1_048_576;
5252
const ANTHROPIC_OPUS_46_MODEL_ID = "claude-opus-4-6";
5353
const ANTHROPIC_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
5454
const ANTHROPIC_OPUS_47_TEMPLATE_MODEL_IDS = [
@@ -61,6 +61,14 @@ const ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-5", "claude-opus-4.5"]
6161
const ANTHROPIC_SONNET_46_MODEL_ID = "claude-sonnet-4-6";
6262
const ANTHROPIC_SONNET_46_DOT_MODEL_ID = "claude-sonnet-4.6";
6363
const ANTHROPIC_SONNET_TEMPLATE_MODEL_IDS = ["claude-sonnet-4-5", "claude-sonnet-4.5"] as const;
64+
const ANTHROPIC_GA_1M_MODEL_PREFIXES = [
65+
ANTHROPIC_OPUS_46_MODEL_ID,
66+
ANTHROPIC_OPUS_46_DOT_MODEL_ID,
67+
ANTHROPIC_OPUS_47_MODEL_ID,
68+
ANTHROPIC_OPUS_47_DOT_MODEL_ID,
69+
ANTHROPIC_SONNET_46_MODEL_ID,
70+
ANTHROPIC_SONNET_46_DOT_MODEL_ID,
71+
] as const;
6472
const ANTHROPIC_MODERN_MODEL_PREFIXES = [
6573
"claude-opus-4-7",
6674
"claude-opus-4.7",
@@ -294,12 +302,9 @@ function resolveAnthropicForwardCompatModel(
294302
);
295303
}
296304

297-
function isAnthropicOpus47Model(modelId: string): boolean {
305+
function isAnthropicGa1MModel(modelId: string): boolean {
298306
const normalized = normalizeLowercaseStringOrEmpty(modelId);
299-
return (
300-
normalized.startsWith(ANTHROPIC_OPUS_47_MODEL_ID) ||
301-
normalized.startsWith(ANTHROPIC_OPUS_47_DOT_MODEL_ID)
302-
);
307+
return ANTHROPIC_GA_1M_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
303308
}
304309

305310
function hasConfiguredModelContextOverride(
@@ -338,26 +343,26 @@ function hasConfiguredModelContextOverride(
338343
return false;
339344
}
340345

341-
function applyAnthropicOpus47ContextWindow(params: {
346+
function applyAnthropicGa1MContextWindow(params: {
342347
config?: ProviderNormalizeResolvedModelContext["config"];
343348
provider: string;
344349
modelId: string;
345350
model: ProviderRuntimeModel;
346351
}): ProviderRuntimeModel | undefined {
347-
if (!isAnthropicOpus47Model(params.modelId)) {
352+
if (!isAnthropicGa1MModel(params.modelId)) {
348353
return undefined;
349354
}
350355
if (hasConfiguredModelContextOverride(params.config, params.provider, params.modelId)) {
351356
return undefined;
352357
}
353358
const nextContextWindow = Math.max(
354359
params.model.contextWindow ?? 0,
355-
ANTHROPIC_OPUS_47_CONTEXT_TOKENS,
360+
ANTHROPIC_GA_1M_CONTEXT_TOKENS,
356361
);
357362
const nextContextTokens =
358363
typeof params.model.contextTokens === "number"
359-
? Math.max(params.model.contextTokens, ANTHROPIC_OPUS_47_CONTEXT_TOKENS)
360-
: ANTHROPIC_OPUS_47_CONTEXT_TOKENS;
364+
? Math.max(params.model.contextTokens, ANTHROPIC_GA_1M_CONTEXT_TOKENS)
365+
: ANTHROPIC_GA_1M_CONTEXT_TOKENS;
361366
if (
362367
nextContextWindow === params.model.contextWindow &&
363368
nextContextTokens === params.model.contextTokens
@@ -407,7 +412,7 @@ function normalizeAnthropicResolvedModel(
407412
): ProviderRuntimeModel | undefined {
408413
const imageCapableModel = applyAnthropicImageInputCapability(ctx) ?? ctx.model;
409414
const contextWindowModel =
410-
applyAnthropicOpus47ContextWindow({
415+
applyAnthropicGa1MContextWindow({
411416
config: ctx.config,
412417
provider: ctx.provider,
413418
modelId: ctx.modelId,
@@ -628,7 +633,7 @@ export function buildAnthropicProvider(): ProviderPlugin {
628633
model,
629634
}) ?? model;
630635
return (
631-
applyAnthropicOpus47ContextWindow({
636+
applyAnthropicGa1MContextWindow({
632637
config: ctx.config,
633638
provider: ctx.provider,
634639
modelId: ctx.modelId,

extensions/anthropic/stream-wrappers.test.ts

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createAnthropicFastModeWrapper,
77
createAnthropicServiceTierWrapper,
88
createAnthropicThinkingPrefillWrapper,
9+
resolveAnthropicBetas,
910
wrapAnthropicProviderStream,
1011
} from "./stream-wrappers.js";
1112

@@ -88,17 +89,20 @@ describe("anthropic stream wrappers", () => {
8889
vi.restoreAllMocks();
8990
});
9091

91-
it("strips context-1m for Claude CLI or legacy token auth and warns", () => {
92+
it("strips legacy context-1m betas for Claude CLI or legacy token auth", () => {
9293
const warn = vi.spyOn(testing.log, "warn").mockImplementation(() => undefined);
9394
const headers = runWrapper("sk-ant-oat01-123");
94-
expect(headers?.["anthropic-beta"]).toBe(OAUTH_BETA_HEADER);
95-
expect(warn).toHaveBeenCalledOnce();
95+
expect(headers?.["anthropic-beta"]).toBeDefined();
96+
expect(headers?.["anthropic-beta"]).toContain(OAUTH_BETA);
97+
expect(headers?.["anthropic-beta"]).not.toContain(CONTEXT_1M_BETA);
98+
expect(warn).not.toHaveBeenCalled();
9699
});
97100

98-
it("keeps context-1m for API key auth", () => {
101+
it("strips legacy context-1m betas for API key auth", () => {
99102
const warn = vi.spyOn(testing.log, "warn").mockImplementation(() => undefined);
100103
const headers = runWrapper("sk-ant-api-123");
101-
expect(headers?.["anthropic-beta"]).toBe(`${DEFAULT_BETA_HEADER},${CONTEXT_1M_BETA}`);
104+
expect(headers?.["anthropic-beta"]).toBeDefined();
105+
expect(headers?.["anthropic-beta"]).not.toContain(CONTEXT_1M_BETA);
102106
expect(warn).not.toHaveBeenCalled();
103107
});
104108

@@ -110,8 +114,79 @@ describe("anthropic stream wrappers", () => {
110114

111115
it("composes the anthropic provider stream chain from extra params", () => {
112116
const captured = runComposedAnthropicProviderStream("sk-ant-api-123");
113-
expect(captured.headers?.["anthropic-beta"]).toBe(`${DEFAULT_BETA_HEADER},${CONTEXT_1M_BETA}`);
114-
expect(captured.payload).toEqual({ service_tier: "auto" });
117+
expect(captured.headers?.["anthropic-beta"]).not.toContain(CONTEXT_1M_BETA);
118+
expect(captured.payload).toMatchObject({ service_tier: "auto" });
119+
});
120+
121+
it("does not emit the legacy context-1m beta from context1m or explicit config", () => {
122+
expect(
123+
resolveAnthropicBetas(
124+
{ context1m: true, anthropicBeta: [CONTEXT_1M_BETA, "files-api-2025-04-14"] },
125+
"claude-sonnet-4-6",
126+
),
127+
).toEqual(["files-api-2025-04-14"]);
128+
});
129+
130+
it("strips legacy context-1m beta from comma-separated string config", () => {
131+
expect(
132+
resolveAnthropicBetas(
133+
{ anthropicBeta: `${CONTEXT_1M_BETA},files-api-2025-04-14` },
134+
"claude-sonnet-4-6",
135+
),
136+
).toEqual(["files-api-2025-04-14"]);
137+
});
138+
139+
it("preserves OAuth-required betas when context1m is the only configured beta trigger", () => {
140+
const captured: { headers?: Record<string, string> } = {};
141+
const wrapped = wrapAnthropicProviderStream({
142+
streamFn: createPayloadCapturingBaseStream(captured),
143+
modelId: "claude-sonnet-4-6",
144+
extraParams: { context1m: true },
145+
} as never);
146+
147+
void wrapped?.(
148+
{ provider: "anthropic", api: "anthropic-messages", id: "claude-sonnet-4-6" } as never,
149+
{} as never,
150+
{ apiKey: "sk-ant-oat01-oauth-token" } as never,
151+
);
152+
153+
expect(captured.headers?.["anthropic-beta"]).toContain(OAUTH_BETA);
154+
expect(captured.headers?.["anthropic-beta"]).not.toContain(CONTEXT_1M_BETA);
155+
});
156+
157+
it("does not add beta headers for context1m-only legacy Sonnet 4.5 configs", () => {
158+
const captured: { headers?: Record<string, string> } = {};
159+
const wrapped = wrapAnthropicProviderStream({
160+
streamFn: createPayloadCapturingBaseStream(captured),
161+
modelId: "claude-sonnet-4-5",
162+
extraParams: { context1m: true },
163+
} as never);
164+
165+
void wrapped?.(
166+
{ provider: "anthropic", api: "anthropic-messages", id: "claude-sonnet-4-5" } as never,
167+
{} as never,
168+
{ apiKey: "sk-ant-api-123" } as never,
169+
);
170+
171+
expect(captured.headers?.["anthropic-beta"]).toBeUndefined();
172+
});
173+
174+
it("preserves OAuth-required betas when legacy context-1m is the only configured beta", () => {
175+
const captured: { headers?: Record<string, string> } = {};
176+
const wrapped = wrapAnthropicProviderStream({
177+
streamFn: createPayloadCapturingBaseStream(captured),
178+
modelId: "claude-sonnet-4-6",
179+
extraParams: { anthropicBeta: [CONTEXT_1M_BETA] },
180+
} as never);
181+
182+
void wrapped?.(
183+
{ provider: "anthropic", api: "anthropic-messages", id: "claude-sonnet-4-6" } as never,
184+
{} as never,
185+
{ apiKey: "sk-ant-oat01-oauth-token" } as never,
186+
);
187+
188+
expect(captured.headers?.["anthropic-beta"]).toContain(OAUTH_BETA);
189+
expect(captured.headers?.["anthropic-beta"]).not.toContain(CONTEXT_1M_BETA);
115190
});
116191
});
117192

0 commit comments

Comments
 (0)