mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 01:01:13 +00:00
fix(providers): scope anthropic-family cache semantics (#60370)
This commit is contained in:
@@ -1,21 +1,23 @@
|
||||
import { resolveAnthropicCacheRetentionFamily } from "./anthropic-family-cache-semantics.js";
|
||||
|
||||
type CacheRetention = "none" | "short" | "long";
|
||||
|
||||
export function resolveCacheRetention(
|
||||
extraParams: Record<string, unknown> | undefined,
|
||||
provider: string,
|
||||
modelApi?: string,
|
||||
modelId?: string,
|
||||
): CacheRetention | undefined {
|
||||
const isAnthropicDirect = provider === "anthropic";
|
||||
const hasExplicitCacheConfig =
|
||||
extraParams?.cacheRetention !== undefined || extraParams?.cacheControlTtl !== undefined;
|
||||
const isAnthropicBedrock = provider === "amazon-bedrock" && hasExplicitCacheConfig;
|
||||
const isCustomAnthropicApi =
|
||||
!isAnthropicDirect &&
|
||||
!isAnthropicBedrock &&
|
||||
modelApi === "anthropic-messages" &&
|
||||
hasExplicitCacheConfig;
|
||||
const family = resolveAnthropicCacheRetentionFamily({
|
||||
provider,
|
||||
modelApi,
|
||||
modelId,
|
||||
hasExplicitCacheConfig,
|
||||
});
|
||||
|
||||
if (!isAnthropicDirect && !isAnthropicBedrock && !isCustomAnthropicApi) {
|
||||
if (!family) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -32,5 +34,5 @@ export function resolveCacheRetention(
|
||||
return "long";
|
||||
}
|
||||
|
||||
return isAnthropicDirect ? "short" : undefined;
|
||||
return family === "anthropic-direct" ? "short" : undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
type AnthropicCacheRetentionFamily =
|
||||
| "anthropic-direct"
|
||||
| "anthropic-bedrock"
|
||||
| "custom-anthropic-api";
|
||||
|
||||
export function isAnthropicModelRef(modelId: string): boolean {
|
||||
return modelId.trim().toLowerCase().startsWith("anthropic/");
|
||||
}
|
||||
|
||||
export function isAnthropicBedrockModel(modelId: string): boolean {
|
||||
const normalized = modelId.trim().toLowerCase();
|
||||
return normalized.includes("anthropic.claude") || normalized.includes("anthropic/claude");
|
||||
}
|
||||
|
||||
export function isOpenRouterAnthropicModelRef(provider: string, modelId: string): boolean {
|
||||
return provider.trim().toLowerCase() === "openrouter" && isAnthropicModelRef(modelId);
|
||||
}
|
||||
|
||||
export function resolveAnthropicCacheRetentionFamily(params: {
|
||||
provider: string;
|
||||
modelApi?: string;
|
||||
modelId?: string;
|
||||
hasExplicitCacheConfig: boolean;
|
||||
}): AnthropicCacheRetentionFamily | undefined {
|
||||
const normalizedProvider = params.provider.trim().toLowerCase();
|
||||
if (normalizedProvider === "anthropic") {
|
||||
return "anthropic-direct";
|
||||
}
|
||||
if (
|
||||
normalizedProvider === "amazon-bedrock" &&
|
||||
params.hasExplicitCacheConfig &&
|
||||
typeof params.modelId === "string" &&
|
||||
isAnthropicBedrockModel(params.modelId)
|
||||
) {
|
||||
return "anthropic-bedrock";
|
||||
}
|
||||
if (
|
||||
normalizedProvider !== "anthropic" &&
|
||||
normalizedProvider !== "amazon-bedrock" &&
|
||||
params.hasExplicitCacheConfig &&
|
||||
params.modelApi === "anthropic-messages"
|
||||
) {
|
||||
return "custom-anthropic-api";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { streamSimple } from "@mariozechner/pi-ai";
|
||||
import { isAnthropicBedrockModel } from "./anthropic-family-cache-semantics.js";
|
||||
|
||||
export function createBedrockNoCacheWrapper(baseStreamFn: StreamFn | undefined): StreamFn {
|
||||
const underlying = baseStreamFn ?? streamSimple;
|
||||
@@ -10,7 +11,4 @@ export function createBedrockNoCacheWrapper(baseStreamFn: StreamFn | undefined):
|
||||
});
|
||||
}
|
||||
|
||||
export function isAnthropicBedrockModel(modelId: string): boolean {
|
||||
const normalized = modelId.toLowerCase();
|
||||
return normalized.includes("anthropic.claude") || normalized.includes("anthropic/claude");
|
||||
}
|
||||
export { isAnthropicBedrockModel };
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { applyExtraParamsToAgent } from "../pi-embedded-runner.js";
|
||||
import { resolveCacheRetention } from "./anthropic-cache-retention.js";
|
||||
import { isOpenRouterAnthropicModelRef } from "./anthropic-family-cache-semantics.js";
|
||||
|
||||
function applyAndExpectWrapped(params: {
|
||||
cfg?: Parameters<typeof applyExtraParamsToAgent>[1];
|
||||
@@ -214,4 +215,34 @@ describe("cacheRetention default behavior", () => {
|
||||
resolveCacheRetention({ cacheRetention: "short" }, "litellm", "anthropic-messages"),
|
||||
).toBe("short");
|
||||
});
|
||||
|
||||
it("does not treat non-Anthropic Bedrock models as cache-retention eligible", () => {
|
||||
expect(
|
||||
resolveCacheRetention(
|
||||
{ cacheRetention: "long" },
|
||||
"amazon-bedrock",
|
||||
"openai-completions",
|
||||
"amazon.nova-micro-v1:0",
|
||||
),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps explicit cacheRetention for Anthropic Bedrock models", () => {
|
||||
expect(
|
||||
resolveCacheRetention(
|
||||
{ cacheRetention: "long" },
|
||||
"amazon-bedrock",
|
||||
"openai-completions",
|
||||
"us.anthropic.claude-sonnet-4-5",
|
||||
),
|
||||
).toBe("long");
|
||||
});
|
||||
});
|
||||
|
||||
describe("anthropic-family cache semantics", () => {
|
||||
it("classifies OpenRouter Anthropic model refs centrally", () => {
|
||||
expect(isOpenRouterAnthropicModelRef("openrouter", "anthropic/claude-opus-4-6")).toBe(true);
|
||||
expect(isOpenRouterAnthropicModelRef("openrouter", "google/gemini-2.5-pro")).toBe(false);
|
||||
expect(isOpenRouterAnthropicModelRef("OpenRouter", "Anthropic/Claude-Sonnet-4")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,6 +210,7 @@ function createStreamFnWithExtraParams(
|
||||
extraParams,
|
||||
provider,
|
||||
typeof model?.api === "string" ? model.api : undefined,
|
||||
typeof model?.id === "string" ? model.id : undefined,
|
||||
);
|
||||
if (cacheRetention) {
|
||||
streamParams.cacheRetention = cacheRetention;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { streamSimple } from "@mariozechner/pi-ai";
|
||||
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import { resolveProviderRequestPolicyConfig } from "../provider-request-config.js";
|
||||
import { isOpenRouterAnthropicModelRef } from "./anthropic-family-cache-semantics.js";
|
||||
import { streamWithPayloadPatch } from "./stream-payload-utils.js";
|
||||
const KILOCODE_FEATURE_HEADER = "X-KILOCODE-FEATURE";
|
||||
const KILOCODE_FEATURE_DEFAULT = "openclaw";
|
||||
@@ -12,10 +13,6 @@ function resolveKilocodeAppHeaders(): Record<string, string> {
|
||||
return { [KILOCODE_FEATURE_HEADER]: feature };
|
||||
}
|
||||
|
||||
function isOpenRouterAnthropicModel(provider: string, modelId: string): boolean {
|
||||
return provider.toLowerCase() === "openrouter" && modelId.toLowerCase().startsWith("anthropic/");
|
||||
}
|
||||
|
||||
function mapThinkingLevelToOpenRouterReasoningEffort(
|
||||
thinkingLevel: ThinkLevel,
|
||||
): "none" | "minimal" | "low" | "medium" | "high" | "xhigh" {
|
||||
@@ -62,7 +59,7 @@ export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | unde
|
||||
if (
|
||||
typeof model.provider !== "string" ||
|
||||
typeof model.id !== "string" ||
|
||||
!isOpenRouterAnthropicModel(model.provider, model.id)
|
||||
!isOpenRouterAnthropicModelRef(model.provider, model.id)
|
||||
) {
|
||||
return underlying(model, context, options);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user