Fix OpenRouter Kimi reasoning compat

closes #5309
This commit is contained in:
Mario Zechner
2026-06-02 15:36:52 +02:00
parent 0d38e17b68
commit 2125221bfc
5 changed files with 47 additions and 5 deletions

View File

@@ -10,6 +10,7 @@
- Fixed Amazon Bedrock requests to replace blank required user/tool-result text with a placeholder and skip blank replay text blocks ([#4975](https://github.com/earendil-works/pi/issues/4975)).
- Fixed OpenAI GPT-5.5 generated metadata to omit unsupported minimal thinking ([#5243](https://github.com/earendil-works/pi/issues/5243)).
- Fixed OpenRouter Kimi K2.6 thinking replay and preserved developer-role instructions for OpenRouter OpenAI and Anthropic models ([#5309](https://github.com/earendil-works/pi/issues/5309)).
- Fixed GitHub Copilot and OpenRouter test model references that became stale after model regeneration.
## [0.78.0] - 2026-05-29

View File

@@ -1343,7 +1343,11 @@ async function generateModels() {
candidate.maxTokens = 4096;
}
if (candidate.provider === "openrouter" && candidate.id.startsWith("moonshotai/kimi-k2.6")) {
candidate.compat = { ...candidate.compat, supportsDeveloperRole: false };
candidate.compat = {
...candidate.compat,
supportsDeveloperRole: false,
requiresReasoningContentOnAssistantMessages: true,
};
}
if (candidate.provider === "openrouter" && candidate.id === "z-ai/glm-5") {
candidate.cost.input = 0.6;

View File

@@ -10281,7 +10281,7 @@ export const MODELS = {
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"supportsDeveloperRole":false},
compat: {"supportsDeveloperRole":false,"requiresReasoningContentOnAssistantMessages":true},
reasoning: true,
input: ["text", "image"],
cost: {
@@ -10299,7 +10299,7 @@ export const MODELS = {
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"supportsDeveloperRole":false},
compat: {"supportsDeveloperRole":false,"requiresReasoningContentOnAssistantMessages":true},
reasoning: true,
input: ["text", "image"],
cost: {

View File

@@ -1100,11 +1100,13 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
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;
return {
supportsStore: !isNonStandard,
supportsDeveloperRole: !isNonStandard && !isOpenRouter,
supportsDeveloperRole: isOpenRouterDeveloperRoleModel || (!isNonStandard && !isOpenRouter),
supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia,
supportsUsageInStreaming: true,
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",

View File

@@ -817,7 +817,7 @@ describe("openai-completions tool_choice", () => {
expect(writeCall).not.toHaveProperty("partialArgs");
});
it("uses system messages for OpenRouter reasoning model instructions", async () => {
it("uses system messages for non-OpenAI/Anthropic OpenRouter reasoning model instructions", async () => {
const model = getModel("openrouter", "deepseek/deepseek-v4-pro")!;
let payload: unknown;
@@ -839,6 +839,33 @@ describe("openai-completions tool_choice", () => {
expect(params.messages?.[0]?.role).toBe("system");
});
it("keeps developer messages for OpenAI and Anthropic OpenRouter reasoning model instructions", async () => {
for (const model of [
getModel("openrouter", "openai/gpt-5.2-codex"),
getModel("openrouter", "anthropic/claude-sonnet-4.5"),
]) {
expect(model).toBeDefined();
let payload: unknown;
await streamSimple(
model!,
{
systemPrompt: "Follow instructions.",
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{
apiKey: "test",
onPayload: (params: unknown) => {
payload = params;
},
},
).result();
const params = payload as { messages?: Array<{ role?: string }> };
expect(params.messages?.[0]?.role).toBe("developer");
}
});
it("keeps developer messages for OpenAI reasoning model instructions", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-5.5")!;
const model = { ...baseModel, api: "openai-completions" } as const;
@@ -862,6 +889,14 @@ describe("openai-completions tool_choice", () => {
expect(params.messages?.[0]?.role).toBe("developer");
});
it("stores OpenRouter Kimi K2.6 reasoning replay compat in built-in metadata", () => {
for (const modelId of ["moonshotai/kimi-k2.6", "moonshotai/kimi-k2.6:free"] as const) {
const model = getModel("openrouter", modelId)!;
expect(model.compat?.supportsDeveloperRole).toBe(false);
expect(model.compat?.requiresReasoningContentOnAssistantMessages).toBe(true);
}
});
it("stores Xiaomi MiMo reasoning replay compat in built-in metadata", () => {
const providers = ["xiaomi", "xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"] as const;