diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 39e50067..4ce1b715 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed OpenRouter Anthropic cache breakpoints to advance through tool results and enabled cache control for `~anthropic/*-latest` aliases ([#6941](https://github.com/earendil-works/pi/pull/6941) by [@mteam88](https://github.com/mteam88)). + ## [0.81.1] - 2026-07-21 ### Added diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 207ec1e0..07ae2ef3 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -572,7 +572,8 @@ function detectOpenAICompletionsCompat(model: Model<"openai-completions">): Open const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com"); const isOpenRouterDeveloperRoleModel = isOpenRouter && (model.id.startsWith("anthropic/") || model.id.startsWith("openai/")); - const cacheControlFormat = provider === "openrouter" && model.id.startsWith("anthropic/") ? "anthropic" : undefined; + const cacheControlFormat = + provider === "openrouter" && /^~?anthropic\//.test(model.id) ? "anthropic" : undefined; return { supportsStore: !isNonStandard, diff --git a/packages/ai/src/providers/opencode.models.ts b/packages/ai/src/providers/opencode.models.ts index e428be67..69a1fda6 100644 --- a/packages/ai/src/providers/opencode.models.ts +++ b/packages/ai/src/providers/opencode.models.ts @@ -193,6 +193,10 @@ export const OPENCODE_MODELS = values as { id: "kimi-k2.7-code"; provider: "opencode"; }; + "laguna-s-2.1-free": Model<"openai-completions"> & { + id: "laguna-s-2.1-free"; + provider: "opencode"; + }; "mimo-v2.5-free": Model<"openai-completions"> & { id: "mimo-v2.5-free"; provider: "opencode"; diff --git a/packages/ai/src/providers/openrouter.models.ts b/packages/ai/src/providers/openrouter.models.ts index 30d51762..3a23c668 100644 --- a/packages/ai/src/providers/openrouter.models.ts +++ b/packages/ai/src/providers/openrouter.models.ts @@ -745,6 +745,14 @@ export const OPENROUTER_MODELS = values as { id: "poolside/laguna-m.1:free"; provider: "openrouter"; }; + "poolside/laguna-s-2.1": Model<"openai-completions"> & { + id: "poolside/laguna-s-2.1"; + provider: "openrouter"; + }; + "poolside/laguna-s-2.1:free": Model<"openai-completions"> & { + id: "poolside/laguna-s-2.1:free"; + provider: "openrouter"; + }; "poolside/laguna-xs-2.1": Model<"openai-completions"> & { id: "poolside/laguna-xs-2.1"; provider: "openrouter"; diff --git a/packages/ai/src/providers/vercel-ai-gateway.models.ts b/packages/ai/src/providers/vercel-ai-gateway.models.ts index 1441b98a..59007046 100644 --- a/packages/ai/src/providers/vercel-ai-gateway.models.ts +++ b/packages/ai/src/providers/vercel-ai-gateway.models.ts @@ -637,6 +637,14 @@ export const VERCEL_AI_GATEWAY_MODELS = values as { id: "openai/o4-mini"; provider: "vercel-ai-gateway"; }; + "poolside/laguna-s-2.1": Model<"anthropic-messages"> & { + id: "poolside/laguna-s-2.1"; + provider: "vercel-ai-gateway"; + }; + "poolside/laguna-s-2.1-free": Model<"anthropic-messages"> & { + id: "poolside/laguna-s-2.1-free"; + provider: "vercel-ai-gateway"; + }; "sakana/fugu-ultra": Model<"anthropic-messages"> & { id: "sakana/fugu-ultra"; provider: "vercel-ai-gateway"; diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index aeaf18c6..02e973f3 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -524,7 +524,7 @@ export interface OpenAICompletionsCompat { zaiToolStream?: boolean; /** Whether the provider supports the `strict` field in tool definitions. Default: true. */ supportsStrictMode?: boolean; - /** Cache control convention for prompt caching. "anthropic" applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user/assistant text content. */ + /** Cache control convention for prompt caching. "anthropic" applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user, assistant, or tool-result text content. */ cacheControlFormat?: "anthropic"; /** Whether to send session-affinity data from `options.sessionId`. Default: false. */ sendSessionAffinityHeaders?: boolean; diff --git a/packages/ai/test/openrouter-cache-control-models.test.ts b/packages/ai/test/openrouter-cache-control-models.test.ts new file mode 100644 index 00000000..2c210444 --- /dev/null +++ b/packages/ai/test/openrouter-cache-control-models.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from "vitest"; +import { getModel } from "../src/compat.ts"; + +const OPENROUTER_ANTHROPIC_LATEST_MODEL_IDS = [ + "~anthropic/claude-fable-latest", + "~anthropic/claude-haiku-latest", + "~anthropic/claude-opus-latest", + "~anthropic/claude-sonnet-latest", +] as const; + +describe("OpenRouter Anthropic cache control metadata", () => { + it.each(OPENROUTER_ANTHROPIC_LATEST_MODEL_IDS)("enables cache control for %s", (modelId) => { + expect(getModel("openrouter", modelId).compat?.cacheControlFormat).toBe("anthropic"); + }); +}); diff --git a/packages/coding-agent/docs/custom-provider.md b/packages/coding-agent/docs/custom-provider.md index 5ddeab0e..82625904 100644 --- a/packages/coding-agent/docs/custom-provider.md +++ b/packages/coding-agent/docs/custom-provider.md @@ -259,7 +259,7 @@ models: [{ ``` Use `openrouter` for OpenRouter-style `reasoning: { effort }` controls. Use `together` for Together-style `reasoning: { enabled }` controls; with `supportsReasoningEffort`, it also sends `reasoning_effort`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. -Use `cacheControlFormat: "anthropic"` for OpenAI-compatible providers that expose Anthropic-style prompt caching via `cache_control` on the system prompt, last tool definition, and last user/assistant text content. +Use `cacheControlFormat: "anthropic"` for OpenAI-compatible providers that expose Anthropic-style prompt caching via `cache_control` on the system prompt, last tool definition, and last user, assistant, or tool-result text content. For Anthropic-compatible providers using `api: "anthropic-messages"`, set `compat.forceAdaptiveThinking: true` on models or providers whose upstream model requires adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`). Built-in adaptive Claude models set this automatically. Set `compat.allowEmptySignature: true` only for providers that emit empty thinking signatures and expect `signature: ""` on replay. @@ -760,4 +760,4 @@ interface ProviderModelConfig { ``` `openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. Use `chat-template` for configurable `chat_template_kwargs`, for example DeepSeek V3.x behind vLLM with `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }`. -`cacheControlFormat: "anthropic"` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user/assistant text content. +`cacheControlFormat: "anthropic"` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user, assistant, or tool-result text content. diff --git a/packages/coding-agent/docs/models.md b/packages/coding-agent/docs/models.md index 50efc369..04066c4b 100644 --- a/packages/coding-agent/docs/models.md +++ b/packages/coding-agent/docs/models.md @@ -445,7 +445,7 @@ For providers with partial OpenAI compatibility, use the `compat` field. | `requiresReasoningContentOnAssistantMessages` | Include empty `reasoning_content` on all replayed assistant messages when reasoning is enabled | | `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters | | `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values | -| `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user/assistant text content. Currently only `anthropic` is supported. | +| `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user, assistant, or tool-result text content. Currently only `anthropic` is supported. | | `sendSessionAffinityHeaders` | For `openai-completions`, send session-affinity headers from the session id when caching is enabled. Default: `false`. | | `sessionAffinityFormat` | For `openai-completions` and `openai-responses`, the session-affinity header format: `openai` sends `session_id`/`x-client-request-id` (completions also `x-session-affinity`), `openai-nosession` omits the underscore-containing `session_id` header, `openrouter` sends `x-session-id`. Does not affect the `prompt_cache_key` body param. Default: auto-detected. | | `supportsStrictMode` | Include the `strict` field in tool definitions |