fix(ai): use Anthropic Messages for OpenRouter ~anthropic latest aliases

The ~anthropic/claude-*-latest aliases used Chat Completions, where pi
dropped the thinking signature and OpenRouter ignored the unsigned
thinking on replay. Route them through Anthropic Messages with adaptive
thinking, like the versioned Claude models.

refs #9534
This commit is contained in:
David Brailovsky 2026-10-28 09:45:08 +01:00
parent a92283056d
commit 44f6851b7f
4 changed files with 6 additions and 19 deletions

View file

@ -30,6 +30,7 @@
- Fixed OpenCode DeepSeek V4.1 Flash sending `prompt_cache_retention`, which the OpenCode gateway rejects, with long cache retention
- Fixed GPT-5.4 and GPT-5.5 failing with `Missing namespace for function_call` after switching from another model that called tools. On the OpenAI and OpenCode providers they now declare their tools in the top-level tool list instead of loading them through `additional_tools` or tool search ([#11415](https://github.com/earendil-works/pi/issues/11415))
- Fixed Bedrock requests failing when `strict: "prefer"` tools exceed Claude's strict tool limits, such as more than 16 optional parameters across all strict tools; requests now retry once with preferred tools non-strict while keeping `strict: "require"` tools strict, as the Anthropic provider already does ([#11416](https://github.com/earendil-works/pi/issues/11416))
- Fixed OpenRouter's `~anthropic/claude-*-latest` aliases losing Claude's earlier thinking after each tool call. They used the Chat Completions API, where pi dropped the thinking signature, and OpenRouter then ignored the unsigned thinking. They now use the Anthropic Messages API with adaptive thinking, like the versioned Claude models ([#9534](https://github.com/earendil-works/pi/issues/9534))
## [1.4.0] - 2026-10-21

View file

@ -650,6 +650,9 @@ function supportsAnthropicMidConvoSystemMessages(modelId: string): boolean {
function isAnthropicAdaptiveThinkingModel(modelId: string): boolean {
return (
// OpenRouter's `~anthropic/claude-*-latest` aliases follow the newest model of each family,
// all of which use adaptive thinking.
/^~anthropic\/claude-[a-z]+-latest$/.test(modelId) ||
modelId.includes("opus-4-6") ||
modelId.includes("opus-4.6") ||
modelId.includes("opus-4-7") ||

View file

@ -45,7 +45,8 @@ export function buildOpenRouterCatalog(
}
const thinkingLevelMap = getOpenRouterThinkingLevelMap(model.reasoning);
const useAnthropicMessages = /^anthropic\//.test(model.id) && !model.id.endsWith(":batch");
// Includes the `~anthropic/claude-*-latest` aliases, which OpenRouter's Messages endpoint accepts.
const useAnthropicMessages = /^~?anthropic\//.test(model.id) && !model.id.endsWith(":batch");
chat.push({
type: "chat",
id: model.id,

View file

@ -1,18 +0,0 @@
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 latest alias metadata", () => {
it.each(OPENROUTER_ANTHROPIC_LATEST_MODEL_IDS)("keeps completions cache control for %s", (modelId) => {
const model = getModel("openrouter", modelId);
expect(model.api).toBe("openai-completions");
if (model.api !== "openai-completions") throw new Error(`Unexpected API for ${modelId}`);
expect(model.compat?.cacheControlFormat).toBe("anthropic");
});
});