From 2cfaeb61163b7ad6973ae9c3591c4d86a676f4c5 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sat, 1 Aug 2026 19:06:32 +0000 Subject: [PATCH] fix(catalog): honored openrouter deepseek effort metadata - Parsed OpenRouter reasoning effort ladders and defaults during discovery. - Preserved explicit thinking metadata from models.yml patches. - Regenerated the catalog and covered both regression paths. Fixes #7307 --- packages/catalog/CHANGELOG.md | 1 + packages/catalog/src/model-thinking.ts | 17 +- packages/catalog/src/models.json | 428 ++++++++++++------ .../src/provider-models/openai-compat.ts | 20 + packages/catalog/test/build.test.ts | 29 ++ packages/coding-agent/CHANGELOG.md | 4 + .../coding-agent/src/config/model-registry.ts | 8 +- .../coding-agent/test/model-registry.test.ts | 27 ++ 8 files changed, 397 insertions(+), 137 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index d07c2d7e7..e19448bb1 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed `gen:models` Codex discovery to union models across every stored OAuth account and fail closed on partial resolution, matching runtime discovery ([#6265](https://github.com/can1357/oh-my-pi/issues/6265)); restored the bundled `gpt-5.4`, `gpt-5.6-sol`, and `gpt-5.3-codex-spark` entries a single-account regen had dropped. +- Fixed OpenRouter `deepseek/deepseek-v4-flash-0731` exposing only `high` thinking effort by consuming the live `reasoning.supported_efforts` and `default_effort` metadata and bundling its `low`/`high`/`max` ladder. ([#7307](https://github.com/can1357/oh-my-pi/issues/7307)) ## [17.2.3] - 2026-08-01 diff --git a/packages/catalog/src/model-thinking.ts b/packages/catalog/src/model-thinking.ts index 1783d1508..2cde43081 100644 --- a/packages/catalog/src/model-thinking.ts +++ b/packages/catalog/src/model-thinking.ts @@ -62,8 +62,8 @@ const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, E const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]; const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High]; const LOW_MEDIUM_HIGH_REASONING_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High]; -/** Kimi K3's wire-exact mandatory reasoning scale. */ -const KIMI_K3_REASONING_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max]; +/** Wire-exact `low`/`high`/`max` scale used by Kimi K3 and OpenRouter DeepSeek V4 Flash 0731. */ +const LOW_HIGH_MAX_REASONING_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max]; /** Wire-exact two-tier scale (`high`/`max`): GLM-5.2 on Z.ai/Umans/Ollama Cloud/Baseten, Sakana Fugu, DeepSeek. */ const HIGH_MAX_REASONING_EFFORTS: readonly Effort[] = [Effort.High, Effort.Max]; /** OpenRouter's DeepSeek route accepts only `high`. */ @@ -339,7 +339,7 @@ function getModelDefinedEfforts( } } if (isKimiK3ModelId(spec.id)) { - return KIMI_K3_REASONING_EFFORTS; + return LOW_HIGH_MAX_REASONING_EFFORTS; } if (isSakanaFuguReasoningModel(spec)) { return HIGH_MAX_REASONING_EFFORTS; @@ -366,9 +366,14 @@ function getModelDefinedEfforts( return OLLAMA_REASONING_EFFORTS; } if (isOpenAICompatReasoningApi(spec.api) && isDeepseekReasoningModel(spec)) { - // DeepSeek's reasoning_effort accepts only high/max; OpenRouter's - // DeepSeek route tops out at high. - return isOpenRouterThinkingFormat(compat) ? HIGH_ONLY_REASONING_EFFORTS : HIGH_MAX_REASONING_EFFORTS; + // OpenRouter generally exposes only high for DeepSeek, but V4 Flash 0731 + // advertises and accepts the wire-exact low/high/max ladder. + if (isOpenRouterThinkingFormat(compat)) { + return bareModelId(spec.id) === "deepseek-v4-flash-0731" + ? LOW_HIGH_MAX_REASONING_EFFORTS + : HIGH_ONLY_REASONING_EFFORTS; + } + return HIGH_MAX_REASONING_EFFORTS; } if (spec.provider === "baseten" && isOpenAIGptOssModelId(spec.id)) { // Baseten's gpt-oss router mirrors its GLM route: high/max only. diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index a7062f7bc..de29f7d30 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -16,7 +16,7 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 1048576, + "contextWindow": 1000000, "maxTokens": 393216, "thinking": { "mode": "effort", @@ -24,8 +24,7 @@ "high", "max" ] - }, - "supportsComputerUse": false + } }, "deepseek-ai/deepseek-v4-pro": { "id": "deepseek-ai/deepseek-v4-pro", @@ -43,7 +42,7 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 1048576, + "contextWindow": 1000000, "maxTokens": 393216, "thinking": { "mode": "effort", @@ -51,8 +50,7 @@ "high", "max" ] - }, - "supportsComputerUse": false + } }, "google/gemma-4-31b-it": { "id": "google/gemma-4-31b-it", @@ -82,8 +80,37 @@ "high", "xhigh" ] + } + }, + "moonshotai/kimi-k2.6": { + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "api": "openai-completions", + "provider": "aiand", + "baseUrl": "https://api.aiand.com/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0.85, + "output": 3.5, + "cacheRead": 0, + "cacheWrite": 0 }, - "supportsComputerUse": false + "contextWindow": 262144, + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "efforts": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "moonshotai/kimi-k2.7-code": { "id": "moonshotai/kimi-k2.7-code", @@ -113,8 +140,7 @@ "high", "xhigh" ] - }, - "supportsComputerUse": false + } }, "moonshotai/kimi-k3": { "id": "moonshotai/kimi-k3", @@ -175,8 +201,7 @@ "medium", "high" ] - }, - "supportsComputerUse": false + } }, "qwen/qwen3.6-27b": { "id": "qwen/qwen3.6-27b", @@ -190,8 +215,8 @@ "image" ], "cost": { - "input": 0.32, - "output": 3.2, + "input": 0, + "output": 0, "cacheRead": 0, "cacheWrite": 0 }, @@ -205,8 +230,36 @@ "medium", "high" ] + } + }, + "zai-org/glm-5.1": { + "id": "zai-org/glm-5.1", + "name": "GLM 5.1", + "api": "openai-completions", + "provider": "aiand", + "baseUrl": "https://api.aiand.com/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 1.4, + "output": 4.4, + "cacheRead": 0, + "cacheWrite": 0 }, - "supportsComputerUse": false + "contextWindow": 202752, + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "efforts": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "zai-org/glm-5.2": { "id": "zai-org/glm-5.2", @@ -224,7 +277,7 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 1048576, + "contextWindow": 1000000, "maxTokens": 131072, "thinking": { "mode": "effort", @@ -235,8 +288,7 @@ "high", "max" ] - }, - "supportsComputerUse": false + } } }, "aimlapi": { @@ -11767,9 +11819,7 @@ "max" ], "supportsDisplay": true - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false + } }, "claude-opus-4-0": { "id": "claude-opus-4-0", @@ -20750,7 +20800,7 @@ }, "deepseek-v4-flash-0731": { "id": "deepseek-v4-flash-0731", - "name": "DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", "api": "openai-completions", "provider": "fireworks", "baseUrl": "https://api.fireworks.ai/inference/v1", @@ -27972,6 +28022,26 @@ ] } }, + "~deepseek/deepseek-v4-flash-latest": { + "id": "~deepseek/deepseek-v4-flash-latest", + "name": "DeepSeek V4 Flash Latest", + "api": "openai-completions", + "provider": "kilo", + "baseUrl": "https://api.kilo.ai/api/gateway", + "reasoning": false, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1048576, + "maxTokens": 1048576, + "supportsComputerUse": false + }, "~google/gemini-flash-latest": { "id": "~google/gemini-flash-latest", "name": "Gemini Flash Latest", @@ -40004,8 +40074,8 @@ "cost": { "input": 0.3, "output": 1.2, - "cacheRead": 0, - "cacheWrite": 0 + "cacheRead": 0.03, + "cacheWrite": 0.375 }, "contextWindow": 204800, "maxTokens": 131072, @@ -40270,8 +40340,8 @@ "cost": { "input": 0.3, "output": 1.2, - "cacheRead": 0, - "cacheWrite": 0 + "cacheRead": 0.03, + "cacheWrite": 0.375 }, "contextWindow": 204800, "maxTokens": 131072, @@ -74174,6 +74244,32 @@ "supportsComputerUse": false, "supportsComputerUseConfig": false }, + "~deepseek/deepseek-v4-flash-latest": { + "id": "~deepseek/deepseek-v4-flash-latest", + "name": "DeepSeek V4 Flash Latest", + "api": "openrouter", + "provider": "openrouter", + "baseUrl": "https://openrouter.ai/api/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 0.09, + "output": 0.18, + "cacheRead": 0.018, + "cacheWrite": 0 + }, + "contextWindow": 1048576, + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "efforts": [ + "high" + ] + }, + "supportsComputerUse": false + }, "~google/gemini-flash-latest": { "id": "~google/gemini-flash-latest", "name": "Gemini Flash Latest", @@ -76380,7 +76476,9 @@ "thinking": { "mode": "effort", "efforts": [ - "high" + "low", + "high", + "max" ] }, "supportsComputerUse": false, @@ -81508,7 +81606,7 @@ ], "cost": { "input": 0.03, - "output": 0.14, + "output": 0.13, "cacheRead": 0.03, "cacheWrite": 0 }, @@ -85391,13 +85489,13 @@ "text" ], "cost": { - "input": 0.76006, - "output": 2.38876, - "cacheRead": 0.141154, + "input": 0.7168, + "output": 2.2527999999999997, + "cacheRead": 0.13312, "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 128000, + "maxTokens": 131072, "thinking": { "mode": "effort", "efforts": [ @@ -85596,9 +85694,69 @@ } }, "synthetic": { + "hf:MiniMaxAI/MiniMax-M3": { + "id": "hf:MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "api": "openai-completions", + "provider": "synthetic", + "baseUrl": "https://api.synthetic.new/openai/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0.6, + "output": 1.2, + "cacheRead": 0.6, + "cacheWrite": 0 + }, + "contextWindow": 524288, + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "efforts": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "hf:moonshotai/Kimi-K2.7-Code": { + "id": "hf:moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "api": "openai-completions", + "provider": "synthetic", + "baseUrl": "https://api.synthetic.new/openai/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0.95, + "output": 4, + "cacheRead": 0.95, + "cacheWrite": 0 + }, + "contextWindow": 262144, + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "efforts": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, "hf:moonshotai/Kimi-K3": { "id": "hf:moonshotai/Kimi-K3", - "name": "moonshotai/Kimi-K3", + "name": "Kimi K3", "api": "openai-completions", "provider": "synthetic", "baseUrl": "https://api.synthetic.new/openai/v1", @@ -85615,8 +85773,6 @@ }, "contextWindow": 524288, "maxTokens": 65536, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, "thinking": { "mode": "effort", "efforts": [ @@ -85633,7 +85789,7 @@ }, "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4": { "id": "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4", - "name": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4", + "name": "Nemotron 3 Super 120B A12B", "api": "openai-completions", "provider": "synthetic", "baseUrl": "https://api.synthetic.new/openai/v1", @@ -85644,7 +85800,7 @@ "cost": { "input": 0.3, "output": 1, - "cacheRead": 0.06, + "cacheRead": 0.3, "cacheWrite": 0 }, "contextWindow": 262144, @@ -85658,13 +85814,11 @@ "high", "xhigh" ] - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false + } }, "hf:openai/gpt-oss-120b": { "id": "hf:openai/gpt-oss-120b", - "name": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", "api": "openai-completions", "provider": "synthetic", "baseUrl": "https://api.synthetic.new/openai/v1", @@ -85675,11 +85829,11 @@ "cost": { "input": 0.1, "output": 0.1, - "cacheRead": 0.02, + "cacheRead": 0.1, "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536, + "maxTokens": 32768, "thinking": { "mode": "effort", "efforts": [ @@ -85687,13 +85841,11 @@ "medium", "high" ] - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false + } }, "hf:Qwen/Qwen3.6-27B": { "id": "hf:Qwen/Qwen3.6-27B", - "name": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", "api": "openai-completions", "provider": "synthetic", "baseUrl": "https://api.synthetic.new/openai/v1", @@ -85704,8 +85856,8 @@ ], "cost": { "input": 0.45, - "output": 2.2, - "cacheRead": 0.09, + "output": 3.6, + "cacheRead": 0.45, "cacheWrite": 0 }, "contextWindow": 262144, @@ -85718,13 +85870,11 @@ "medium", "high" ] - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false + } }, "hf:zai-org/GLM-4.7-Flash": { "id": "hf:zai-org/GLM-4.7-Flash", - "name": "zai-org/GLM-4.7-Flash", + "name": "GLM-4.7-Flash", "api": "openai-completions", "provider": "synthetic", "baseUrl": "https://api.synthetic.new/openai/v1", @@ -85735,7 +85885,7 @@ "cost": { "input": 0.1, "output": 0.5, - "cacheRead": 0.02, + "cacheRead": 0.1, "cacheWrite": 0 }, "contextWindow": 196608, @@ -85749,13 +85899,11 @@ "high", "xhigh" ] - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false + } }, "hf:zai-org/GLM-5.2": { "id": "hf:zai-org/GLM-5.2", - "name": "zai-org/GLM-5.2", + "name": "GLM-5.2", "api": "openai-completions", "provider": "synthetic", "baseUrl": "https://api.synthetic.new/openai/v1", @@ -85764,9 +85912,9 @@ "text" ], "cost": { - "input": 1, - "output": 3, - "cacheRead": 0.16, + "input": 1.4, + "output": 4.4, + "cacheRead": 1.4, "cacheWrite": 0 }, "contextWindow": 524288, @@ -85780,9 +85928,7 @@ "high", "xhigh" ] - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false + } }, "syn:large:text": { "id": "syn:large:text", @@ -86536,7 +86682,7 @@ "cost": { "input": 0.6, "output": 3.6, - "cacheRead": 0, + "cacheRead": 0.35, "cacheWrite": 0 }, "contextWindow": 262144, @@ -86621,7 +86767,7 @@ "cost": { "input": 1.25, "output": 3.75, - "cacheRead": 0, + "cacheRead": 0.125, "cacheWrite": 0 }, "contextWindow": 1000000, @@ -86728,7 +86874,7 @@ "cost": { "input": 1.4, "output": 4.4, - "cacheRead": 0, + "cacheRead": 0.26, "cacheWrite": 0 }, "contextWindow": 202752, @@ -87675,26 +87821,28 @@ }, "deepseek-v4-flash-0731": { "id": "deepseek-v4-flash-0731", - "name": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", "api": "openai-completions", "provider": "venice", "baseUrl": "https://api.venice.ai/api/v1", - "reasoning": false, + "reasoning": true, "input": [ "text" ], "cost": { - "input": 0, - "output": 0, - "cacheRead": 0, + "input": 0.175, + "output": 0.35, + "cacheRead": 0.035, "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 1048576, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, - "compat": { - "supportsUsageInStreaming": false + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "efforts": [ + "high", + "max" + ] } }, "deepseek-v4-pro": { @@ -90547,9 +90695,9 @@ "text" ], "cost": { - "input": 0.125, - "output": 0.5, - "cacheRead": 0, + "input": 0.06, + "output": 0.4, + "cacheRead": 0.01, "cacheWrite": 0 }, "contextWindow": 128000, @@ -93104,7 +93252,8 @@ "baseUrl": "https://ai-gateway.vercel.sh", "reasoning": true, "input": [ - "text" + "text", + "image" ], "cost": { "input": 0.15, @@ -93174,7 +93323,8 @@ "baseUrl": "https://ai-gateway.vercel.sh", "reasoning": true, "input": [ - "text" + "text", + "image" ], "cost": { "input": 0.74, @@ -93904,7 +94054,8 @@ "provider": "vercel-ai-gateway", "reasoning": false, "input": [ - "text" + "text", + "image" ], "cost": { "input": 0.09999999999999999, @@ -93924,7 +94075,8 @@ "provider": "vercel-ai-gateway", "reasoning": false, "input": [ - "text" + "text", + "image" ], "cost": { "input": 0.15, @@ -94017,7 +94169,8 @@ "baseUrl": "https://ai-gateway.vercel.sh", "reasoning": false, "input": [ - "text" + "text", + "image" ], "cost": { "input": 0.15, @@ -98491,8 +98644,6 @@ }, "contextWindow": 2000000, "maxTokens": 2000000, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { "minimal": "low" @@ -98523,8 +98674,6 @@ }, "contextWindow": 2000000, "maxTokens": 2000000, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { "minimal": "low" @@ -98554,6 +98703,16 @@ }, "contextWindow": 2000000, "maxTokens": 2000000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "includeEncryptedReasoning": false, + "filterReasoningHistory": true, + "supportsImageDetailOriginal": false, + "omitReasoningEffort": false, + "supportsReasoningEffort": true + }, "thinking": { "mode": "effort", "efforts": [ @@ -98566,18 +98725,6 @@ "effortMap": { "minimal": "low" } - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, - "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, - "includeEncryptedReasoning": false, - "filterReasoningHistory": true, - "supportsImageDetailOriginal": false, - "omitReasoningEffort": false, - "supportsReasoningEffort": true } }, "grok-4.3": { @@ -98599,6 +98746,16 @@ }, "contextWindow": 1000000, "maxTokens": 1000000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "includeEncryptedReasoning": false, + "filterReasoningHistory": true, + "supportsImageDetailOriginal": false, + "omitReasoningEffort": false, + "supportsReasoningEffort": true + }, "thinking": { "mode": "effort", "efforts": [ @@ -98611,18 +98768,6 @@ "effortMap": { "minimal": "low" } - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, - "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, - "includeEncryptedReasoning": false, - "filterReasoningHistory": true, - "supportsImageDetailOriginal": false, - "omitReasoningEffort": false, - "supportsReasoningEffort": true } }, "grok-4.5": { @@ -98644,6 +98789,16 @@ }, "contextWindow": 500000, "maxTokens": 500000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "includeEncryptedReasoning": false, + "filterReasoningHistory": true, + "supportsImageDetailOriginal": false, + "omitReasoningEffort": false, + "supportsReasoningEffort": true + }, "thinking": { "mode": "effort", "efforts": [ @@ -98656,18 +98811,6 @@ "effortMap": { "minimal": "low" } - }, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, - "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, - "includeEncryptedReasoning": false, - "filterReasoningHistory": true, - "supportsImageDetailOriginal": false, - "omitReasoningEffort": false, - "supportsReasoningEffort": true } }, "grok-build": { @@ -98689,8 +98832,6 @@ }, "contextWindow": 512000, "maxTokens": 512000, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { "minimal": "low" @@ -98721,8 +98862,6 @@ }, "contextWindow": 256000, "maxTokens": 256000, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { "minimal": "low" @@ -98752,8 +98891,6 @@ }, "contextWindow": 200000, "maxTokens": 200000, - "supportsComputerUse": false, - "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { "minimal": "low" @@ -105626,6 +105763,37 @@ ] } }, + "glm-5.2-highspeed": { + "id": "glm-5.2-highspeed", + "name": "GLM-5.2 Highspeed", + "api": "openai-completions", + "provider": "zhipu-coding-plan", + "baseUrl": "https://open.bigmodel.cn/api/coding/paas/v4", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1000000, + "maxTokens": 131072, + "compat": { + "thinkingFormat": "zai", + "reasoningContentField": "reasoning_content", + "supportsDeveloperRole": false + }, + "thinking": { + "mode": "effort", + "efforts": [ + "high", + "max" + ] + } + }, "glm-5.2-highspeed[1m]": { "id": "glm-5.2-highspeed[1m]", "name": "GLM-5.2 Highspeed", diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 837cd1991..4fa7e1354 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -2465,6 +2465,24 @@ export interface OpenRouterModelManagerConfig { fetch?: FetchImpl; } +function mapOpenRouterThinking(entry: OpenAICompatibleModelRecord): ThinkingConfig | undefined { + const reasoning = entry.reasoning; + if (!isRecord(reasoning)) return undefined; + const supportedEfforts = reasoning.supported_efforts; + if (!Array.isArray(supportedEfforts)) return undefined; + const efforts = THINKING_EFFORTS.filter(effort => supportedEfforts.includes(effort)); + if (efforts.length === 0) return undefined; + const defaultLevel = + typeof reasoning.default_effort === "string" + ? THINKING_EFFORTS.find(effort => effort === reasoning.default_effort) + : undefined; + return { + mode: "effort", + efforts, + ...(defaultLevel !== undefined && efforts.includes(defaultLevel) ? { defaultLevel } : {}), + }; +} + export function openrouterModelManagerOptions( config?: OpenRouterModelManagerConfig, ): ModelManagerOptions<"openrouter"> { @@ -2496,6 +2514,7 @@ export function openrouterModelManagerOptions( const baseModel = mapWithBundledReference(entry, defaults, reference); const pricing = entry.pricing as Record | undefined; const params = Array.isArray(entry.supported_parameters) ? (entry.supported_parameters as string[]) : []; + const thinking = mapOpenRouterThinking(entry); const modality = String((entry.architecture as Record | undefined)?.modality ?? ""); const topProvider = entry.top_provider as Record | undefined; @@ -2504,6 +2523,7 @@ export function openrouterModelManagerOptions( return { ...baseModel, reasoning: params.includes("reasoning"), + ...(thinking !== undefined ? { thinking } : {}), input: modality.includes("image") ? ["text", "image"] : ["text"], cost: { input: parseFloat(String(pricing?.prompt ?? "0")) * 1_000_000, diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index 386bb23b6..4c617cf58 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -6,6 +6,7 @@ import * as path from "node:path"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import { isOfficialAnthropicApiUrl } from "@oh-my-pi/pi-catalog/compat/anthropic"; import { buildOpenAICompat, buildOpenAIResponsesCompat } from "@oh-my-pi/pi-catalog/compat/openai"; +import { Effort } from "@oh-my-pi/pi-catalog/effort"; import { readModelCache, writeModelCache } from "@oh-my-pi/pi-catalog/model-cache"; import { resolveProviderModels } from "@oh-my-pi/pi-catalog/model-manager"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; @@ -610,6 +611,34 @@ describe("OpenRouter model discovery", () => { } }); + it("maps OpenRouter's advertised reasoning effort ladder and default", async () => { + const options = openrouterModelManagerOptions({ + fetch: async () => + Response.json({ + data: [ + { + id: "deepseek/deepseek-v4-flash-0731", + name: "DeepSeek V4 Flash 0731", + supported_parameters: ["tools", "reasoning", "reasoning_effort"], + reasoning: { + supported_efforts: ["max", "high", "low"], + default_effort: "high", + }, + }, + ], + }), + }); + const specs = await options.fetchDynamicModels?.(); + const spec = specs?.find(model => model.id === "deepseek/deepseek-v4-flash-0731"); + if (!spec) throw new Error("Expected discovered DeepSeek V4 Flash 0731 model"); + + expect(buildModel(spec).thinking).toEqual({ + mode: "effort", + efforts: [Effort.Low, Effort.High, Effort.Max], + defaultLevel: Effort.High, + }); + }); + it("ignores legacy OpenRouter chat-completions cache rows", async () => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-catalog-openrouter-legacy-cache-")); const dbPath = path.join(tempDir, "models.db"); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 1b632e9c5..bf3b67ff3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed explicit `thinking` metadata in `models.yml` custom definitions and `modelOverrides` being replaced by canonical catalog policy during model rebuilding. ([#7307](https://github.com/can1357/oh-my-pi/issues/7307)) + ## [17.2.3] - 2026-08-01 ### Changed diff --git a/packages/coding-agent/src/config/model-registry.ts b/packages/coding-agent/src/config/model-registry.ts index 5c5024988..99f65e8de 100644 --- a/packages/coding-agent/src/config/model-registry.ts +++ b/packages/coding-agent/src/config/model-registry.ts @@ -580,7 +580,13 @@ function applyModelPatch(base: Model, patch: ModelPatch, transport: ModelTr result.headers = patch.headers; compat = patch.compat; } - return buildModel({ ...result, compat } as ModelSpec); + const built = buildModel({ ...result, compat } as ModelSpec); + if (patch.thinking !== undefined && built.thinking !== undefined) { + // Config-authored capability metadata owns the explicit surface; build + // first so non-reasoning and wire-disabled models still suppress it. + built.thinking = patch.thinking; + } + return built; } function applyModelOverride(model: Model, override: ModelOverride): Model { diff --git a/packages/coding-agent/test/model-registry.test.ts b/packages/coding-agent/test/model-registry.test.ts index 6e3694f9b..87f1f7247 100644 --- a/packages/coding-agent/test/model-registry.test.ts +++ b/packages/coding-agent/test/model-registry.test.ts @@ -1174,6 +1174,7 @@ describe("ModelRegistry", () => { }; let thinkingCustom: ModelRegistry; let thinkingOverride: ModelRegistry; + let deepseekOverride: ModelRegistry; beforeAll(() => { thinkingCustom = readonlyRegistry({ providers: { @@ -1193,6 +1194,21 @@ describe("ModelRegistry", () => { }, }, }); + deepseekOverride = readonlyRegistry({ + providers: { + openrouter: { + modelOverrides: { + "deepseek/deepseek-v4-flash-0731": { + thinking: { + mode: "effort", + efforts: [Effort.Max, Effort.High, Effort.Low], + defaultLevel: Effort.High, + }, + }, + }, + }, + }, + }); }); test("custom models preserve explicit thinking verbatim", () => { @@ -1211,6 +1227,17 @@ describe("ModelRegistry", () => { efforts: [Effort.Low, Effort.Medium], }); }); + + test("model overrides preserve explicit OpenRouter DeepSeek thinking metadata", () => { + const model = getModelsForProvider(deepseekOverride, "openrouter").find( + m => m.id === "deepseek/deepseek-v4-flash-0731", + ); + expect(model?.thinking).toEqual({ + mode: "effort", + efforts: [Effort.Max, Effort.High, Effort.Low], + defaultLevel: Effort.High, + }); + }); }); describe("modelOverrides (per-model customization)", () => {