diff --git a/CHANGELOG.md b/CHANGELOG.md index d0bad2cd589..286a1482e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai - Slack/Group policy: move Slack account `groupPolicy` defaulting to provider-level schema defaults so multi-account configs inherit top-level `channels.slack.groupPolicy` instead of silently overriding inheritance with per-account `allowlist`. (#17579) Thanks @ZetiMente. - Providers/Anthropic: skip `context-1m-*` beta injection for OAuth/subscription tokens (`sk-ant-oat-*`) while preserving OAuth-required betas, avoiding Anthropic 401 auth failures when `params.context1m` is enabled. (#10647, #20354) Thanks @ClumsyWizardHands and @dcruver. - Providers/Bedrock: disable prompt-cache retention for non-Anthropic Bedrock models so Nova/Mistral requests do not send unsupported cache metadata. (#20866) Thanks @pierreeurope. +- Providers/Bedrock: apply Anthropic-Claude cacheRetention defaults and runtime pass-through for `amazon-bedrock/*anthropic.claude*` model refs, while keeping non-Anthropic Bedrock models excluded. (#22303) Thanks @snese. - Providers/OpenRouter: remove conflicting top-level `reasoning_effort` when injecting nested `reasoning.effort`, preventing OpenRouter 400 payload-validation failures for reasoning models. (#24120) thanks @tenequm. - Providers/Groq: avoid classifying Groq TPM limit errors as context overflow so throttling paths no longer trigger overflow recovery logic. (#16176) Thanks @dddabtc. - Gateway/WS: close repeated post-handshake `unauthorized role:*` request floods per connection and sample duplicate rejection logs, preventing a single misbehaving client from degrading gateway responsiveness. (#20168) Thanks @acy103, @vibecodooor, and @vincentkoc. diff --git a/src/agents/pi-embedded-runner-extraparams.test.ts b/src/agents/pi-embedded-runner-extraparams.test.ts index 68d7327c33e..19269d2dc1a 100644 --- a/src/agents/pi-embedded-runner-extraparams.test.ts +++ b/src/agents/pi-embedded-runner-extraparams.test.ts @@ -187,6 +187,37 @@ describe("applyExtraParamsToAgent", () => { expect(calls[0]?.cacheRetention).toBeUndefined(); }); + it("passes through explicit cacheRetention for Anthropic Bedrock models", () => { + const { calls, agent } = createOptionsCaptureAgent(); + const cfg = { + agents: { + defaults: { + models: { + "amazon-bedrock/us.anthropic.claude-opus-4-6-v1": { + params: { + cacheRetention: "long", + }, + }, + }, + }, + }, + }; + + applyExtraParamsToAgent(agent, cfg, "amazon-bedrock", "us.anthropic.claude-opus-4-6-v1"); + + const model = { + api: "openai-completions", + provider: "amazon-bedrock", + id: "us.anthropic.claude-opus-4-6-v1", + } as Model<"openai-completions">; + const context: Context = { messages: [] }; + + void agent.streamFn?.(model, context, {}); + + expect(calls).toHaveLength(1); + expect(calls[0]?.cacheRetention).toBe("long"); + }); + it("adds Anthropic 1M beta header when context1m is enabled for Opus/Sonnet", () => { const { calls, agent } = createOptionsCaptureAgent(); const cfg = buildAnthropicModelConfig("anthropic/claude-opus-4-6", { context1m: true }); diff --git a/src/agents/pi-embedded-runner/extra-params.ts b/src/agents/pi-embedded-runner/extra-params.ts index 285ae6a5b23..de4dee783f6 100644 --- a/src/agents/pi-embedded-runner/extra-params.ts +++ b/src/agents/pi-embedded-runner/extra-params.ts @@ -43,16 +43,25 @@ type CacheRetentionStreamOptions = Partial & { * * Mapping: "5m" → "short", "1h" → "long" * - * Only applies to Anthropic provider (OpenRouter uses openai-completions API - * with hardcoded cache_control, not the cacheRetention stream option). + * Applies to: + * - direct Anthropic provider + * - Anthropic Claude models on Bedrock when cache retention is explicitly configured * - * Defaults to "short" for Anthropic provider when not explicitly configured. + * OpenRouter uses openai-completions API with hardcoded cache_control instead + * of the cacheRetention stream option. + * + * Defaults to "short" for direct Anthropic when not explicitly configured. */ function resolveCacheRetention( extraParams: Record | undefined, provider: string, ): CacheRetention | undefined { - if (provider !== "anthropic") { + const isAnthropicDirect = provider === "anthropic"; + const hasBedrockOverride = + extraParams?.cacheRetention !== undefined || extraParams?.cacheControlTtl !== undefined; + const isAnthropicBedrock = provider === "amazon-bedrock" && hasBedrockOverride; + + if (!isAnthropicDirect && !isAnthropicBedrock) { return undefined; } @@ -71,7 +80,13 @@ function resolveCacheRetention( return "long"; } - // Default to "short" for Anthropic when not explicitly configured + // Default to "short" only for direct Anthropic when not explicitly configured. + // Bedrock retains upstream provider defaults unless explicitly set. + if (!isAnthropicDirect) { + return undefined; + } + + // Default to "short" for direct Anthropic when not explicitly configured return "short"; } diff --git a/src/config/config.pruning-defaults.test.ts b/src/config/config.pruning-defaults.test.ts index c37b9ba8f45..f2f66ce6bac 100644 --- a/src/config/config.pruning-defaults.test.ts +++ b/src/config/config.pruning-defaults.test.ts @@ -73,6 +73,54 @@ describe("config pruning defaults", () => { }); }); + it("adds default cacheRetention for Anthropic Claude models on Bedrock", async () => { + await withTempHome(async (home) => { + await writeConfigForTest(home, { + auth: { + profiles: { + "anthropic:api": { provider: "anthropic", mode: "api_key" }, + }, + }, + agents: { + defaults: { + model: { primary: "amazon-bedrock/us.anthropic.claude-opus-4-6-v1" }, + }, + }, + }); + + const cfg = loadConfig(); + + expect( + cfg.agents?.defaults?.models?.["amazon-bedrock/us.anthropic.claude-opus-4-6-v1"]?.params + ?.cacheRetention, + ).toBe("short"); + }); + }); + + it("does not add default cacheRetention for non-Anthropic Bedrock models", async () => { + await withTempHome(async (home) => { + await writeConfigForTest(home, { + auth: { + profiles: { + "anthropic:api": { provider: "anthropic", mode: "api_key" }, + }, + }, + agents: { + defaults: { + model: { primary: "amazon-bedrock/amazon.nova-micro-v1:0" }, + }, + }, + }); + + const cfg = loadConfig(); + + expect( + cfg.agents?.defaults?.models?.["amazon-bedrock/amazon.nova-micro-v1:0"]?.params + ?.cacheRetention, + ).toBeUndefined(); + }); + }); + it("does not override explicit contextPruning mode", async () => { await withTempHome(async (home) => { await writeConfigForTest(home, { agents: { defaults: { contextPruning: { mode: "off" } } } }); diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 55d7093dde0..e8a4db163c6 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -410,10 +410,19 @@ export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig if (authMode === "api_key") { const nextModels = defaults.models ? { ...defaults.models } : {}; let modelsMutated = false; + const isAnthropicCacheRetentionTarget = ( + parsed: { provider: string; model: string } | undefined, + ): parsed is { provider: string; model: string } => + Boolean( + parsed && + (parsed.provider === "anthropic" || + (parsed.provider === "amazon-bedrock" && + parsed.model.toLowerCase().includes("anthropic.claude"))), + ); for (const [key, entry] of Object.entries(nextModels)) { const parsed = parseModelRef(key, "anthropic"); - if (!parsed || parsed.provider !== "anthropic") { + if (!isAnthropicCacheRetentionTarget(parsed ?? undefined)) { continue; } const current = entry ?? {}; @@ -433,7 +442,7 @@ export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig ); if (primary) { const parsedPrimary = parseModelRef(primary, "anthropic"); - if (parsedPrimary?.provider === "anthropic") { + if (isAnthropicCacheRetentionTarget(parsedPrimary ?? undefined)) { const key = `${parsedPrimary.provider}/${parsedPrimary.model}`; const entry = nextModels[key]; const current = entry ?? {};