From 409af331e7caa4e2ff82599890e4430b0c7af750 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Fri, 19 Jun 2026 02:28:19 -0700 Subject: [PATCH] fix(catalog): omit Ollama Cloud output caps --- packages/catalog/CHANGELOG.md | 4 + .../catalog/scripts/generated-policies.ts | 4 + packages/catalog/src/models.json | 93 ++++++++++++++++--- .../catalog/src/provider-models/ollama.ts | 1 + .../catalog/test/generated-policies.test.ts | 24 +++++ .../test/ollama-cloud-output-caps.test.ts | 34 +++++++ packages/coding-agent/CHANGELOG.md | 4 + .../coding-agent/src/config/model-registry.ts | 1 + .../coding-agent/test/model-registry.test.ts | 4 +- 9 files changed, 156 insertions(+), 13 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index d7c4edbad..f5eac3323 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Marked Ollama Cloud catalog models to omit on-the-wire output-token caps, preventing context-window-sized `num_predict` values from causing HTTP 400s for models whose true output cap is not discoverable. ([#2984](https://github.com/can1357/oh-my-pi/issues/2984)) + ## [16.0.9] - 2026-06-18 ### Fixed diff --git a/packages/catalog/scripts/generated-policies.ts b/packages/catalog/scripts/generated-policies.ts index e3402782e..52122b8b6 100644 --- a/packages/catalog/scripts/generated-policies.ts +++ b/packages/catalog/scripts/generated-policies.ts @@ -208,6 +208,10 @@ function applyGeneratedModelPolicy(model: ModelSpec): void { model.maxTokens = copilotLimits.maxTokens; } + if (model.provider === "ollama-cloud") { + model.omitMaxOutputTokens = true; + } + // GLM Coding Plan: GLM-5.2 is the selectable 1M served id; pin it so // endpoint discovery or older bundled fallbacks cannot regress to 200k. if ((model.provider === "zai" || model.provider === "zhipu-coding-plan") && model.id === "glm-5.2") { diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index ec19d4d90..7a2c60e79 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -53268,6 +53268,7 @@ }, "contextWindow": 163840, "maxTokens": 32000, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53296,6 +53297,7 @@ }, "contextWindow": 163840, "maxTokens": 163840, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53324,6 +53326,7 @@ }, "contextWindow": 163840, "maxTokens": 65536, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53352,6 +53355,7 @@ }, "contextWindow": 1048576, "maxTokens": 1048576, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53381,6 +53385,7 @@ }, "contextWindow": 1048576, "maxTokens": 1048576, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53409,7 +53414,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "omitMaxOutputTokens": true }, "devstral-small-2:24b": { "id": "devstral-small-2:24b", @@ -53429,7 +53435,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "omitMaxOutputTokens": true }, "gemini-3-flash-preview": { "id": "gemini-3-flash-preview", @@ -53450,6 +53457,7 @@ }, "contextWindow": 1048576, "maxTokens": 65536, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53480,6 +53488,7 @@ }, "contextWindow": 262144, "maxTokens": 262144, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53508,6 +53517,7 @@ }, "contextWindow": 202752, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53536,6 +53546,7 @@ }, "contextWindow": 202752, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53564,6 +53575,7 @@ }, "contextWindow": 202752, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53592,6 +53604,7 @@ }, "contextWindow": 202752, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53602,6 +53615,36 @@ ] } }, + "glm-5.2": { + "id": "glm-5.2", + "name": "GLM-5.2", + "api": "ollama-chat", + "provider": "ollama-cloud", + "baseUrl": "https://ollama.com", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 976000, + "maxTokens": 131072, + "omitMaxOutputTokens": true, + "thinking": { + "mode": "effort", + "efforts": [ + "high", + "xhigh" + ], + "effortMap": { + "xhigh": "max" + } + } + }, "gpt-oss:120b": { "id": "gpt-oss:120b", "name": "gpt-oss:120b", @@ -53620,6 +53663,7 @@ }, "contextWindow": 131072, "maxTokens": 32768, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53648,6 +53692,7 @@ }, "contextWindow": 131072, "maxTokens": 32768, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53676,6 +53721,7 @@ }, "contextWindow": 262144, "maxTokens": 262144, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53704,7 +53750,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "omitMaxOutputTokens": true }, "kimi-k2.5": { "id": "kimi-k2.5", @@ -53725,6 +53772,7 @@ }, "contextWindow": 262144, "maxTokens": 262144, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53754,6 +53802,7 @@ }, "contextWindow": 262144, "maxTokens": 262144, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53783,6 +53832,7 @@ }, "contextWindow": 262144, "maxTokens": 262144, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53811,6 +53861,7 @@ }, "contextWindow": 204800, "maxTokens": 128000, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53840,6 +53891,7 @@ }, "contextWindow": 204800, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53869,6 +53921,7 @@ }, "contextWindow": 204800, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53898,6 +53951,7 @@ }, "contextWindow": 196608, "maxTokens": 196608, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53928,6 +53982,7 @@ }, "contextWindow": 512000, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -53956,7 +54011,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 128000 + "maxTokens": 128000, + "omitMaxOutputTokens": true }, "ministral-3:3b": { "id": "ministral-3:3b", @@ -53976,7 +54032,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 128000 + "maxTokens": 128000, + "omitMaxOutputTokens": true }, "ministral-3:8b": { "id": "ministral-3:8b", @@ -53996,7 +54053,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 128000 + "maxTokens": 128000, + "omitMaxOutputTokens": true }, "mistral-large-3:675b": { "id": "mistral-large-3:675b", @@ -54016,7 +54074,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "omitMaxOutputTokens": true }, "nemotron-3-nano:30b": { "id": "nemotron-3-nano:30b", @@ -54036,6 +54095,7 @@ }, "contextWindow": 1048576, "maxTokens": 131072, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -54064,6 +54124,7 @@ }, "contextWindow": 262144, "maxTokens": 65536, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -54092,6 +54153,7 @@ }, "contextWindow": 262144, "maxTokens": 128000, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -54119,7 +54181,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "omitMaxOutputTokens": true }, "qwen3-coder:480b": { "id": "qwen3-coder:480b", @@ -54138,7 +54201,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "omitMaxOutputTokens": true }, "qwen3-next:80b": { "id": "qwen3-next:80b", @@ -54158,6 +54222,7 @@ }, "contextWindow": 262144, "maxTokens": 32768, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -54187,6 +54252,7 @@ }, "contextWindow": 262144, "maxTokens": 32768, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -54215,7 +54281,8 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 131072 + "maxTokens": 131072, + "omitMaxOutputTokens": true }, "qwen3.5:397b": { "id": "qwen3.5:397b", @@ -54236,6 +54303,7 @@ }, "contextWindow": 262144, "maxTokens": 65536, + "omitMaxOutputTokens": true, "thinking": { "mode": "effort", "efforts": [ @@ -54263,7 +54331,8 @@ "cacheWrite": 0 }, "contextWindow": 32768, - "maxTokens": 4096 + "maxTokens": 4096, + "omitMaxOutputTokens": true } }, "openai": { @@ -85019,4 +85088,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/catalog/src/provider-models/ollama.ts b/packages/catalog/src/provider-models/ollama.ts index eed33d7d7..4c65788ad 100644 --- a/packages/catalog/src/provider-models/ollama.ts +++ b/packages/catalog/src/provider-models/ollama.ts @@ -159,6 +159,7 @@ export function ollamaCloudModelManagerOptions( discoveredContextWindow !== null && discoveredContextWindow !== undefined ? (providerReference?.maxTokens ?? Math.min(contextWindow, 8192)) : Math.min(contextWindow, 8192), + omitMaxOutputTokens: true, }; }), ); diff --git a/packages/catalog/test/generated-policies.test.ts b/packages/catalog/test/generated-policies.test.ts index 8e9f578c9..3db875175 100644 --- a/packages/catalog/test/generated-policies.test.ts +++ b/packages/catalog/test/generated-policies.test.ts @@ -196,6 +196,30 @@ describe("generated model policies", () => { expect(models[2]?.maxTokens).toBe(64000); }); + it("marks Ollama Cloud generated rows to omit max output tokens", () => { + const models: ModelSpec[] = [ + createSpec({ + id: "deepseek-v4-flash", + api: "ollama-chat", + provider: "ollama-cloud", + contextWindow: 1048576, + maxTokens: 1048576, + }), + createSpec({ + id: "deepseek-v4-flash", + api: "ollama-chat", + provider: "ollama", + contextWindow: 1048576, + maxTokens: 1048576, + }), + ]; + + applyGeneratedModelPolicies(models); + + expect(models[0]?.omitMaxOutputTokens).toBe(true); + expect(models[1]?.omitMaxOutputTokens).toBeUndefined(); + }); + it("marks OpenCode Go MiMo models as not supporting tool_choice", () => { const models: ModelSpec<"openai-completions">[] = [ createSpec({ diff --git a/packages/catalog/test/ollama-cloud-output-caps.test.ts b/packages/catalog/test/ollama-cloud-output-caps.test.ts index 1716be904..2411ac918 100644 --- a/packages/catalog/test/ollama-cloud-output-caps.test.ts +++ b/packages/catalog/test/ollama-cloud-output-caps.test.ts @@ -48,6 +48,40 @@ test("ollama-cloud discovery does not inherit unsafe cross-provider maxTokens", expect(model?.maxTokens).toBe(8192); }); +test("ollama-cloud discovery always omits max output tokens", async () => { + const fetchMock: FetchImpl = vi.fn(async (input, _init) => { + const url = String(input); + if (url === "https://ollama.com/api/tags") { + return new Response(JSON.stringify({ models: [{ name: "deepseek-v4-flash" }] }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + if (url === "https://ollama.com/api/show") { + return new Response( + JSON.stringify({ + capabilities: ["completion", "thinking"], + model_info: { "deepseek4.context_length": 1048576 }, + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + }, + ); + } + throw new Error(`Unexpected URL: ${url}`); + }); + + const options = ollamaCloudModelManagerOptions({ apiKey: "cloud-test-key", fetch: fetchMock }); + const models = await options.fetchDynamicModels?.(); + const model = models?.find(candidate => candidate.id === "deepseek-v4-flash"); + + expect(model?.provider).toBe("ollama-cloud"); + expect(model?.contextWindow).toBe(1048576); + expect(model?.maxTokens).toBe(1048576); + expect(model?.omitMaxOutputTokens).toBe(true); +}); + test("ollama-chat omits num_predict when model opts out of max output tokens", async () => { let requestBody: Record | undefined; const fetchMock: FetchImpl = vi.fn(async (_input, init) => { diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 9bad3a89d..c8384ba70 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Preserved bundled `omitMaxOutputTokens` policy when fresh cached provider discovery rows replace Ollama Cloud catalog models, so stale `models.db` entries cannot re-enable context-window-sized `num_predict` values. ([#2984](https://github.com/can1357/oh-my-pi/issues/2984)) + ## [16.1.1] - 2026-06-19 ### Changed diff --git a/packages/coding-agent/src/config/model-registry.ts b/packages/coding-agent/src/config/model-registry.ts index f12884945..b4378ae8d 100644 --- a/packages/coding-agent/src/config/model-registry.ts +++ b/packages/coding-agent/src/config/model-registry.ts @@ -900,6 +900,7 @@ export class ModelRegistry { ...replacementModel, contextWindow: replacementModel.contextWindow ?? existing.contextWindow, maxTokens: replacementModel.maxTokens ?? existing.maxTokens, + omitMaxOutputTokens: replacementModel.omitMaxOutputTokens ?? existing.omitMaxOutputTokens, ...(supportsTools !== undefined ? { supportsTools } : {}), }; }); diff --git a/packages/coding-agent/test/model-registry.test.ts b/packages/coding-agent/test/model-registry.test.ts index ce0420187..d81ab416a 100644 --- a/packages/coding-agent/test/model-registry.test.ts +++ b/packages/coding-agent/test/model-registry.test.ts @@ -2047,7 +2047,9 @@ describe("ModelRegistry", () => { }); test("loads cached standard provider discovery models on startup", () => { - expect(standardCache.find("ollama-cloud", "deepseek-v4-pro")?.maxTokens).toBe(384_000); + const model = standardCache.find("ollama-cloud", "deepseek-v4-pro"); + expect(model?.maxTokens).toBe(384_000); + expect(model?.omitMaxOutputTokens).toBe(true); }); test("loads cached special provider discovery models on startup", () => {