diff --git a/dist/providers/anthropic.js b/dist/providers/anthropic.js index 1cba2f1365812fd2f88993009c9cc06e9c348279..664dd6d8b400ec523fb735480741b9ad64f9a68c 100644 --- a/dist/providers/anthropic.js +++ b/dist/providers/anthropic.js @@ -298,10 +298,11 @@ function createClient(model, apiKey, interleavedThinking) { }); return { client, isOAuthToken: true }; } + const apiBetaFeatures = ["extended-cache-ttl-2025-04-11", ...betaFeatures]; const defaultHeaders = { accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", - "anthropic-beta": betaFeatures.join(","), + "anthropic-beta": apiBetaFeatures.join(","), ...(model.headers || {}), }; const client = new Anthropic({ @@ -313,9 +314,11 @@ function createClient(model, apiKey, interleavedThinking) { return { client, isOAuthToken: false }; } function buildParams(model, context, isOAuthToken, options) { + const cacheControlTtl = !isOAuthToken ? (options?.cacheControlTtl ?? "1h") : undefined; + const cacheControl = cacheControlTtl ? { type: "ephemeral", ttl: cacheControlTtl } : { type: "ephemeral" }; const params = { model: model.id, - messages: convertMessages(context.messages, model, isOAuthToken), + messages: convertMessages(context.messages, model, isOAuthToken, cacheControl), max_tokens: options?.maxTokens || (model.maxTokens / 3) | 0, stream: true, }; @@ -325,18 +328,14 @@ function buildParams(model, context, isOAuthToken, options) { { type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude.", - cache_control: { - type: "ephemeral", - }, + cache_control: cacheControl, }, ]; if (context.systemPrompt) { params.system.push({ type: "text", text: sanitizeSurrogates(context.systemPrompt), - cache_control: { - type: "ephemeral", - }, + cache_control: cacheControl, }); } } @@ -346,9 +345,7 @@ function buildParams(model, context, isOAuthToken, options) { { type: "text", text: sanitizeSurrogates(context.systemPrompt), - cache_control: { - type: "ephemeral", - }, + cache_control: cacheControl, }, ]; } @@ -378,7 +375,7 @@ function buildParams(model, context, isOAuthToken, options) { function normalizeToolCallId(id) { return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64); } -function convertMessages(messages, model, isOAuthToken) { +function convertMessages(messages, model, isOAuthToken, cacheControl) { const params = []; // Transform messages for cross-provider compatibility const transformedMessages = transformMessages(messages, model, normalizeToolCallId); @@ -514,7 +511,7 @@ function convertMessages(messages, model, isOAuthToken) { const lastBlock = lastMessage.content[lastMessage.content.length - 1]; if (lastBlock && (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) { - lastBlock.cache_control = { type: "ephemeral" }; + lastBlock.cache_control = cacheControl; } } } diff --git a/dist/providers/openai-completions.js b/dist/providers/openai-completions.js index ee5c88d8e280ceeff45ed075f2c7357d40005578..89daad7b0e53753e094028291226d32da9446440 100644 --- a/dist/providers/openai-completions.js +++ b/dist/providers/openai-completions.js @@ -305,7 +305,7 @@ function createClient(model, context, apiKey) { function buildParams(model, context, options) { const compat = getCompat(model); const messages = convertMessages(model, context, compat); - maybeAddOpenRouterAnthropicCacheControl(model, messages); + maybeAddOpenRouterAnthropicCacheControl(model, messages, options?.cacheControlTtl); const params = { model: model.id, messages, @@ -349,9 +349,10 @@ function buildParams(model, context, options) { } return params; } -function maybeAddOpenRouterAnthropicCacheControl(model, messages) { +function maybeAddOpenRouterAnthropicCacheControl(model, messages, cacheControlTtl) { if (model.provider !== "openrouter" || !model.id.startsWith("anthropic/")) return; + const cacheControl = cacheControlTtl ? { type: "ephemeral", ttl: cacheControlTtl } : { type: "ephemeral" }; // Anthropic-style caching requires cache_control on a text part. Add a breakpoint // on the last user/assistant message (walking backwards until we find text content). for (let i = messages.length - 1; i >= 0; i--) { @@ -361,7 +362,7 @@ function maybeAddOpenRouterAnthropicCacheControl(model, messages) { const content = msg.content; if (typeof content === "string") { msg.content = [ - Object.assign({ type: "text", text: content }, { cache_control: { type: "ephemeral" } }), + Object.assign({ type: "text", text: content }, { cache_control: cacheControl }), ]; return; } @@ -371,7 +372,7 @@ function maybeAddOpenRouterAnthropicCacheControl(model, messages) { for (let j = content.length - 1; j >= 0; j--) { const part = content[j]; if (part?.type === "text") { - Object.assign(part, { cache_control: { type: "ephemeral" } }); + Object.assign(part, { cache_control: cacheControl }); return; } } diff --git a/dist/stream.js b/dist/stream.js index d23fdd9f226a949fac4f2c7160af76f7f5fe71d1..3500f074bd88b85f4c7dd9bf42279f80fdf264d1 100644 --- a/dist/stream.js +++ b/dist/stream.js @@ -146,6 +146,7 @@ function mapOptionsForApi(model, options, apiKey) { signal: options?.signal, apiKey: apiKey || options?.apiKey, sessionId: options?.sessionId, + cacheControlTtl: options?.cacheControlTtl, }; // Helper to clamp xhigh to high for providers that don't support it const clampReasoning = (effort) => (effort === "xhigh" ? "high" : effort);