From 9320bd379ef3ce1fc11f29fbbabf82bd1db94b80 Mon Sep 17 00:00:00 2001 From: Vito Cappello Date: Mon, 10 Aug 2026 22:06:54 -0400 Subject: [PATCH] fix(ollama): enable cloud max thinking (#121074) Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com> --- docs/providers/ollama.md | 6 +- docs/tools/thinking.md | 2 +- extensions/ollama/index.test.ts | 44 +++-- extensions/ollama/provider-policy-api.test.ts | 54 +++++- extensions/ollama/provider-policy-api.ts | 18 +- extensions/ollama/src/model-reasoning.ts | 14 ++ extensions/ollama/src/provider-models.ts | 12 +- extensions/ollama/src/stream-compat.ts | 70 ++++--- extensions/ollama/src/stream-runtime.test.ts | 182 +++++++++++++----- extensions/ollama/src/stream.runtime.ts | 3 +- 10 files changed, 297 insertions(+), 108 deletions(-) create mode 100644 extensions/ollama/src/model-reasoning.ts diff --git a/docs/providers/ollama.md b/docs/providers/ollama.md index 139fe17ee8cf..5c9779705ea7 100644 --- a/docs/providers/ollama.md +++ b/docs/providers/ollama.md @@ -1120,8 +1120,10 @@ For full setup and behavior, see [Ollama Web Search](/tools/ollama-search). For native requests, thinking control is forwarded directly: `/think off` and `openclaw agent --thinking off` send top-level `think: false` unless an explicit `params.think`/`params.thinking` is configured; `/think - low|medium|high` send the matching effort string; `/think max` maps to - Ollama's highest effort, `think: "high"`. + low|medium|high` send the matching effort string. Verified full-effort + Ollama Cloud families such as GLM 5.2 and DeepSeek V4 also send native + `think: "max"` for `/think max`; other models and local servers keep the + compatible `think: "high"` mapping. For the OpenAI-compatible endpoint instead, see "Legacy OpenAI-compatible mode" above — streaming and tool calling may not work together there. diff --git a/docs/tools/thinking.md b/docs/tools/thinking.md index 312faab4536c..297a7e380a95 100644 --- a/docs/tools/thinking.md +++ b/docs/tools/thinking.md @@ -29,7 +29,7 @@ title: "Thinking levels" - Anthropic Claude Opus 4.7+ also exposes `/think max`; it maps to the same provider-owned max effort path. - Direct DeepSeek V4 models expose `/think xhigh|max`; both map to DeepSeek `reasoning_effort: "max"` while lower non-off levels map to `high`. - OpenRouter-routed DeepSeek V4 models expose `/think xhigh` and send OpenRouter-supported `reasoning.effort` values instead of DeepSeek-native top-level `reasoning_effort`. Lower non-off levels map to `high`, and stored `max` overrides fall back to `xhigh`. - - Ollama thinking-capable models expose `/think low|medium|high|max`; `max` maps to native `think: "high"` because Ollama's native API accepts `low`, `medium`, and `high` effort strings. + - Ollama thinking-capable models expose `/think low|medium|high|max`. Verified full-effort Ollama Cloud families such as GLM 5.2 and DeepSeek V4 send each matching native `think` effort, including `max`; other models and local Ollama keep the compatible `high` mapping for `/think max`. - OpenAI GPT models map `/think` through model-specific Responses API effort support. `/think off` sends `reasoning.effort: "none"` only when the target model supports it; otherwise OpenClaw omits the disabled reasoning payload instead of sending an unsupported value. - GPT-5.6 Sol and Terra expose native `/think ultra` through the Codex runtime. GPT-5.6 Luna exposes levels through `max` because its Codex catalog does not advertise Ultra. - The embedded OpenClaw runtime exposes logical `/think ultra` for GPT-5.6 Sol, Terra, and Luna. It sends provider max effort and adds run-scoped proactive sub-agent orchestration guidance. diff --git a/extensions/ollama/index.test.ts b/extensions/ollama/index.test.ts index 456a7baae2ae..6da7617261ac 100644 --- a/extensions/ollama/index.test.ts +++ b/extensions/ollama/index.test.ts @@ -238,8 +238,12 @@ async function augmentOllamaCatalog( function captureWrappedOllamaPayload( thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "max" | undefined, + route: { provider?: string; modelId?: string; baseUrl?: string } = {}, ) { const provider = registerProvider(); + const providerId = route.provider ?? "ollama"; + const modelId = route.modelId ?? "qwen3.5:9b"; + const baseUrl = route.baseUrl ?? "http://127.0.0.1:11434"; let payloadSeen: Record | undefined; const baseStreamFn = vi.fn((_model, _context, options) => { const payload: Record = { @@ -256,22 +260,22 @@ function captureWrappedOllamaPayload( config: { models: { providers: { - ollama: { + [providerId]: { api: "ollama", - baseUrl: "http://127.0.0.1:11434", + baseUrl, models: [], }, }, }, }, - provider: "ollama", - modelId: "qwen3.5:9b", + provider: providerId, + modelId, thinkingLevel, model: { api: "ollama", - provider: "ollama", - id: "qwen3.5:9b", - baseUrl: "http://127.0.0.1:11434", + provider: providerId, + id: modelId, + baseUrl, contextWindow: 131_072, }, streamFn: baseStreamFn, @@ -283,8 +287,8 @@ function captureWrappedOllamaPayload( void wrapped( { api: "ollama", - provider: "ollama", - id: "qwen3.5:9b", + provider: providerId, + id: modelId, } as never, {} as never, {}, @@ -2422,13 +2426,13 @@ describe("ollama plugin", () => { thinkingLevel: "off" as const, expectedThink: false, }, + ...(["low", "medium", "high"] as const).map((thinkingLevel) => ({ + name: `preserves native Ollama ${thinkingLevel} thinking on the wire`, + thinkingLevel, + expectedThink: thinkingLevel, + })), { - name: "wraps native Ollama payloads with top-level think effort when thinking is enabled", - thinkingLevel: "low" as const, - expectedThink: "low", - }, - { - name: "maps native Ollama max thinking to the highest supported wire effort", + name: "keeps the compatible local Ollama max mapping", thinkingLevel: "max" as const, expectedThink: "high", }, @@ -2444,6 +2448,16 @@ describe("ollama plugin", () => { expect((payloadSeen?.options as Record | undefined)?.think).toBeUndefined(); }); + it("preserves native Ollama Cloud max thinking on the wire", () => { + const { payloadSeen } = captureWrappedOllamaPayload("max", { + provider: "ollama-cloud", + modelId: "glm-5.2", + baseUrl: "https://ollama.com", + }); + + expect(payloadSeen?.think).toBe("max"); + }); + it("keeps native Ollama thinking off by default while exposing opt-in effort levels", () => { const provider = registerProvider(); diff --git a/extensions/ollama/provider-policy-api.test.ts b/extensions/ollama/provider-policy-api.test.ts index 348a2d5e1338..851023df4b9a 100644 --- a/extensions/ollama/provider-policy-api.test.ts +++ b/extensions/ollama/provider-policy-api.test.ts @@ -106,14 +106,62 @@ describe("ollama provider policy public artifact", () => { ).toBeUndefined(); }); - it("exposes max thinking for reasoning-capable models without full plugin activation", () => { - expect(resolveThinkingProfile({ reasoning: true })).toEqual({ + it("exposes every native effort for reasoning-capable models without full plugin activation", () => { + expect( + resolveThinkingProfile({ provider: "ollama", modelId: "qwen3:32b", reasoning: true }), + ).toEqual({ levels: [{ id: "off" }, { id: "low" }, { id: "medium" }, { id: "high" }, { id: "max" }], defaultLevel: "off", }); - expect(resolveThinkingProfile({ reasoning: false })).toEqual({ + expect( + resolveThinkingProfile({ provider: "ollama", modelId: "llama3.2", reasoning: false }), + ).toEqual({ levels: [{ id: "off" }], defaultLevel: "off", }); }); + + it.each(["glm-5.2", "deepseek-v4-pro:cloud"])( + "exposes full native effort for cloud model %s when lightweight projections omit metadata", + (modelId) => { + expect(resolveThinkingProfile({ provider: "ollama-cloud", modelId }).levels).toEqual([ + { id: "off" }, + { id: "low" }, + { id: "medium" }, + { id: "high" }, + { id: "max" }, + ]); + }, + ); + + it.each(["minimax-m2.7", "glm-5.1", "kimi-k2.5", "custom-thinking-model"])( + "does not invent effort levels for catalog-light cloud model %s", + (modelId) => { + expect(resolveThinkingProfile({ provider: "ollama-cloud", modelId }).levels).toEqual([ + { id: "off" }, + ]); + }, + ); + + it("keeps explicit non-reasoning metadata authoritative for known cloud model ids", () => { + expect( + resolveThinkingProfile({ + provider: "ollama-cloud", + modelId: "glm-5.2", + reasoning: false, + }).levels, + ).toEqual([{ id: "off" }]); + }); + + it("does not infer thinking support for unknown models without catalog metadata", () => { + expect(resolveThinkingProfile({ provider: "ollama", modelId: "llama3.2" }).levels).toEqual([ + { id: "off" }, + ]); + }); + + it("does not apply cloud catalog facts to an unqualified local model", () => { + expect(resolveThinkingProfile({ provider: "ollama", modelId: "glm-5.2" }).levels).toEqual([ + { id: "off" }, + ]); + }); }); diff --git a/extensions/ollama/provider-policy-api.ts b/extensions/ollama/provider-policy-api.ts index 5fb45b6259a8..9c0e0e141457 100644 --- a/extensions/ollama/provider-policy-api.ts +++ b/extensions/ollama/provider-policy-api.ts @@ -1,10 +1,13 @@ // Ollama API module exposes the plugin public contract. import type { + ProviderDefaultThinkingPolicyContext, ProviderNormalizeResolvedModelContext, ProviderThinkingProfile, } from "openclaw/plugin-sdk/plugin-entry"; +import { isCloudModelRef, normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared"; import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types"; import { OLLAMA_CLOUD_PROVIDER_ID, OLLAMA_DEFAULT_BASE_URL } from "./src/defaults.js"; +import { supportsOllamaCloudFullThinkingEffort } from "./src/model-reasoning.js"; type OllamaProviderConfigDraft = Partial; @@ -64,9 +67,16 @@ export function projectConfiguredModelRow(ctx: ProviderNormalizeResolvedModelCon } export function resolveThinkingProfile({ + modelId, + provider, reasoning, -}: { - reasoning?: boolean; -}): ProviderThinkingProfile { - return reasoning ? OLLAMA_REASONING_THINKING_PROFILE : OLLAMA_NON_REASONING_THINKING_PROFILE; +}: ProviderDefaultThinkingPolicyContext): ProviderThinkingProfile { + const isCloudRoute = + normalizeProviderId(provider) === OLLAMA_CLOUD_PROVIDER_ID || isCloudModelRef(modelId); + const supportsThinking = + reasoning === true || + (reasoning === undefined && isCloudRoute && supportsOllamaCloudFullThinkingEffort(modelId)); + return supportsThinking + ? OLLAMA_REASONING_THINKING_PROFILE + : OLLAMA_NON_REASONING_THINKING_PROFILE; } diff --git a/extensions/ollama/src/model-reasoning.ts b/extensions/ollama/src/model-reasoning.ts new file mode 100644 index 000000000000..d3678ed73d99 --- /dev/null +++ b/extensions/ollama/src/model-reasoning.ts @@ -0,0 +1,14 @@ +// Ollama plugin module owns model-specific native thinking contracts. +export function supportsOllamaCloudFullThinkingEffort(modelId: string): boolean { + // These hosted families accept low, medium, high, and max even when + // lightweight catalog projections omit their reasoning metadata. + const normalized = normalizeOllamaCloudModelId(modelId); + return normalized === "glm-5.2" || /^deepseek-v4-(?:flash|pro)$/.test(normalized); +} + +function normalizeOllamaCloudModelId(modelId: string): string { + return modelId + .trim() + .toLowerCase() + .replace(/(?::cloud|-cloud)$/, ""); +} diff --git a/extensions/ollama/src/provider-models.ts b/extensions/ollama/src/provider-models.ts index bee9967b5a54..6bd2c5ab5dbc 100644 --- a/extensions/ollama/src/provider-models.ts +++ b/extensions/ollama/src/provider-models.ts @@ -16,6 +16,7 @@ import { OLLAMA_DEFAULT_MAX_TOKENS, OLLAMA_LOCAL_CONTEXT_TOKENS, } from "./defaults.js"; +import { supportsOllamaCloudFullThinkingEffort } from "./model-reasoning.js"; export type OllamaTagModel = { name: string; @@ -343,15 +344,6 @@ export function isReasoningModelHeuristic(modelId: string): boolean { return /r1|reasoning|think|reason/i.test(modelId); } -function isKnownOllamaCloudReasoningModel(modelId: string): boolean { - // Match both the canonical direct-host id and the local `:cloud` routing alias. - const normalized = modelId - .trim() - .toLowerCase() - .replace(/:cloud$/, ""); - return normalized === "glm-5.2" || /^deepseek-v4-(?:flash|pro)$/.test(normalized); -} - export function buildOllamaModelDefinition( modelId: string, contextWindow?: number, @@ -361,7 +353,7 @@ export function buildOllamaModelDefinition( const hasVision = capabilities?.includes("vision") ?? false; const input: ("text" | "image")[] = hasVision ? ["text", "image"] : ["text"]; const reasoning = - isKnownOllamaCloudReasoningModel(modelId) || + supportsOllamaCloudFullThinkingEffort(modelId) || (capabilities === undefined ? isReasoningModelHeuristic(modelId) : capabilities.includes("thinking")); diff --git a/extensions/ollama/src/stream-compat.ts b/extensions/ollama/src/stream-compat.ts index 4b2493749d4b..04a01b409b04 100644 --- a/extensions/ollama/src/stream-compat.ts +++ b/extensions/ollama/src/stream-compat.ts @@ -15,8 +15,9 @@ import { } from "openclaw/plugin-sdk/provider-stream-shared"; import { isLoopbackHost } from "openclaw/plugin-sdk/ssrf-runtime"; import { shouldWrapOllamaCompatMoonshotThinking } from "./model-behavior.js"; +import { supportsOllamaCloudFullThinkingEffort } from "./model-reasoning.js"; -export type OllamaThinkValue = boolean | "low" | "medium" | "high"; +export type OllamaThinkValue = boolean | "low" | "medium" | "high" | "max"; export function resolveConfiguredOllamaProviderConfig(params: { config?: OpenClawConfig; @@ -114,42 +115,56 @@ function createOllamaThinkingWrapper( }); } -function resolveOllamaThinkValue(thinkingLevel: unknown): OllamaThinkValue | undefined { - if (thinkingLevel === "off") { +function normalizeOllamaThinkValue( + value: unknown, + nativeMax: boolean, +): OllamaThinkValue | undefined { + if (typeof value === "boolean") { + return value; + } + if (value === "off") { return false; } - if (thinkingLevel === "low" || thinkingLevel === "medium" || thinkingLevel === "high") { - return thinkingLevel; + if (value === "low" || value === "medium" || value === "high") { + return value; } - if (thinkingLevel === "minimal") { + if (value === "max") { + // Verified full-effort Cloud families accept native max. Keep the shipped + // high fallback for local and model-specific contracts without that tier. + return nativeMax ? "max" : "high"; + } + if (value === "minimal") { return "low"; } - if (thinkingLevel === "xhigh" || thinkingLevel === "adaptive" || thinkingLevel === "max") { + if (value === "xhigh" || value === "adaptive") { + // These OpenClaw-only tiers are not advertised by Ollama; keep their established high mapping. return "high"; } return undefined; } +function resolveOllamaThinkValue( + thinkingLevel: unknown, + nativeMax: boolean, +): OllamaThinkValue | undefined { + return normalizeOllamaThinkValue(thinkingLevel, nativeMax); +} + export function resolveOllamaThinkParamValue( params: Record | undefined, + nativeMax = false, ): OllamaThinkValue | undefined { - const raw = params?.think ?? params?.thinking; - if (typeof raw === "boolean") { - return raw; - } - if (raw === "off") { - return false; - } - if (raw === "low" || raw === "medium" || raw === "high") { - return raw; - } - if (raw === "minimal") { - return "low"; - } - if (raw === "xhigh" || raw === "adaptive" || raw === "max") { - return "high"; - } - return undefined; + return normalizeOllamaThinkValue(params?.think ?? params?.thinking, nativeMax); +} + +export function supportsNativeOllamaMax( + model: Pick | undefined, + providerId?: string, +): boolean { + const isCloudProvider = + normalizeProviderId(model?.provider ?? "") === "ollama-cloud" || + normalizeProviderId(providerId ?? "") === "ollama-cloud"; + return isCloudProvider && supportsOllamaCloudFullThinkingEffort(model?.id ?? ""); } export function shouldForwardNativeOllamaThink( @@ -209,9 +224,12 @@ export function createConfiguredOllamaCompatStreamWrapper( streamFn = wrapOllamaCompatNumCtx(streamFn, resolveOllamaNumCtx(model)); } - const configuredThinkValue = model ? resolveOllamaThinkParamValue(model.params) : undefined; + const nativeMax = supportsNativeOllamaMax(model, ctx.provider); + const configuredThinkValue = model + ? resolveOllamaThinkParamValue(model.params, nativeMax) + : undefined; const runtimeThinkValue = isNativeOllamaTransport - ? resolveOllamaThinkValue(ctx.thinkingLevel) + ? resolveOllamaThinkValue(ctx.thinkingLevel, nativeMax) : undefined; // "off" is also the implicit agent default. Preserve explicit native Ollama // model config unless the active run requests a non-off thinking level. diff --git a/extensions/ollama/src/stream-runtime.test.ts b/extensions/ollama/src/stream-runtime.test.ts index 750dd1b3e353..a9b38573577e 100644 --- a/extensions/ollama/src/stream-runtime.test.ts +++ b/extensions/ollama/src/stream-runtime.test.ts @@ -320,7 +320,16 @@ describe("createConfiguredOllamaCompatStreamWrapper", () => { expect(payload.options).toEqual({ num_ctx: 131072 }); }); - it.each([ + it.each<{ + name: string; + id: string; + contextWindow: number; + provider?: string; + reasoning?: boolean; + thinkingLevel: string; + params?: Record; + expectedThink: boolean | string | undefined; + }>([ { name: "forwards think=false on native Ollama chat requests when thinking is off", id: "qwen3:32b", @@ -353,57 +362,99 @@ describe("createConfiguredOllamaCompatStreamWrapper", () => { thinkingLevel: "low", expectedThink: undefined, }, - { - name: "forwards the native think effort on native Ollama chat requests when thinking is enabled", - id: "qwen3:32b", + ...(["low", "medium", "high"] as const).map((thinkingLevel) => ({ + name: `preserves native Ollama ${thinkingLevel} thinking on the wire`, + id: "gpt-oss:20b", contextWindow: 131072, - thinkingLevel: "low", - expectedThink: "low", - }, + thinkingLevel, + expectedThink: thinkingLevel, + })), { - name: "maps native Ollama max thinking to think=high on the wire", + name: "keeps the compatible local Ollama max mapping", id: "gpt-oss:20b", contextWindow: 131072, thinkingLevel: "max", expectedThink: "high", }, - ])("$name", async ({ id, contextWindow, reasoning, thinkingLevel, params, expectedThink }) => { - await withSuccessfulOllamaFetch(async (fetchMock) => { - const model = { - api: "ollama", - provider: "ollama", - id, - contextWindow, - ...(reasoning === undefined ? {} : { reasoning }), - ...(params ? { params } : {}), - }; - const wrapped = expectDefined( - createConfiguredOllamaCompatStreamWrapper({ - provider: "ollama", - modelId: id, - model, - streamFn: createOllamaStreamFn("http://ollama-host:11434"), - thinkingLevel, - } as never), - "wrapped Ollama stream function", - ); - const stream = await Promise.resolve( - wrapped( - model as never, - { messages: [{ role: "user", content: "hello" }] } as never, - {} as never, - ), - ); - await collectStreamEvents(stream); + { + name: "does not infer native max support from a local cloud model alias", + id: "glm-5.2:cloud", + contextWindow: 131072, + thinkingLevel: "max", + expectedThink: "high", + }, + { + name: "preserves native Ollama Cloud max thinking on the wire", + id: "glm-5.2", + provider: "ollama-cloud", + contextWindow: 131072, + thinkingLevel: "max", + expectedThink: "max", + }, + { + name: "keeps the high fallback for Ollama Cloud GPT-OSS", + id: "gpt-oss:120b", + provider: "ollama-cloud", + contextWindow: 131072, + thinkingLevel: "max", + expectedThink: "high", + }, + { + name: "keeps the high fallback for Cloud models without a verified max tier", + id: "kimi-k2.5", + provider: "ollama-cloud", + contextWindow: 131072, + thinkingLevel: "max", + expectedThink: "high", + }, + ])( + "$name", + async ({ + id, + provider = "ollama", + contextWindow, + reasoning, + thinkingLevel, + params, + expectedThink, + }) => { + await withSuccessfulOllamaFetch(async (fetchMock) => { + const model = { + api: "ollama", + provider, + id, + contextWindow, + ...(reasoning === undefined ? {} : { reasoning }), + ...(params ? { params } : {}), + }; + const wrapped = expectDefined( + createConfiguredOllamaCompatStreamWrapper({ + provider, + modelId: id, + model, + streamFn: createOllamaStreamFn("http://ollama-host:11434"), + thinkingLevel, + } as never), + "wrapped Ollama stream function", + ); + const stream = await Promise.resolve( + wrapped( + model as never, + { messages: [{ role: "user", content: "hello" }] } as never, + {} as never, + ), + ); + await collectStreamEvents(stream); - const requestBody = getGuardedFetchJsonBody(fetchMock); - expect(requestBody.think).toBe(expectedThink); - expect(requireOptionalRecord(requestBody.options)?.think).toBeUndefined(); - if (reasoning !== false) { - expect(requireOptionalRecord(requestBody.options)?.num_ctx).toBeUndefined(); - } - }); - }); + const requestBody = getGuardedFetchJsonBody(fetchMock); + expect(requestBody.think).toBe(expectedThink); + expect(requireOptionalRecord(requestBody.options)?.think).toBeUndefined(); + if (reasoning !== false) { + expect(requireOptionalRecord(requestBody.options)?.num_ctx).toBeUndefined(); + } + }); + }, + ); it("passes resolved provider request timeouts to native Ollama chat fetches", async () => { await withMockNdjsonFetch( @@ -2645,16 +2696,55 @@ describe("createOllamaStreamFn", () => { ); }); - it("maps configured native Ollama params.thinking=max to the stable top-level think value", async () => { + it.each(["low", "medium", "high"] as const)( + "preserves configured native Ollama params.thinking=%s", + async (thinking) => { + await expectSuccessfulOllamaRequest( + { baseUrl: "http://ollama-host:11434", model: { params: { thinking } } }, + ({ body }) => { + expect(body.think).toBe(thinking); + expect(requireOptionalRecord(body.options)?.think).toBeUndefined(); + }, + ); + }, + ); + + it("keeps configured local Ollama params.thinking=max compatible", async () => { await expectSuccessfulOllamaRequest( { baseUrl: "http://ollama-host:11434", model: { params: { thinking: "max" } } }, ({ body }) => { expect(body.think).toBe("high"); - expect(requireOptionalRecord(body.options)?.think).toBeUndefined(); }, ); }); + it("preserves configured Ollama Cloud params.thinking=max", async () => { + await expectSuccessfulOllamaRequest( + { + baseUrl: "https://ollama.com", + model: { provider: "ollama-cloud", id: "glm-5.2", params: { thinking: "max" } }, + }, + ({ body }) => { + expect(body.think).toBe("max"); + }, + ); + }); + + it.each(["gpt-oss:120b", "kimi-k2.5", "custom-thinking-model"])( + "keeps configured Ollama Cloud %s params.thinking=max compatible", + async (id) => { + await expectSuccessfulOllamaRequest( + { + baseUrl: "https://ollama.com", + model: { provider: "ollama-cloud", id, params: { thinking: "max" } }, + }, + ({ body }) => { + expect(body.think).toBe("high"); + }, + ); + }, + ); + it("uses the default loopback policy when baseUrl is empty", async () => { await expectSuccessfulOllamaRequest({ baseUrl: "" }, ({ request }) => { expect(request.url).toBe("http://127.0.0.1:11434/api/chat"); diff --git a/extensions/ollama/src/stream.runtime.ts b/extensions/ollama/src/stream.runtime.ts index bae228a7efd4..7c9f1e454724 100644 --- a/extensions/ollama/src/stream.runtime.ts +++ b/extensions/ollama/src/stream.runtime.ts @@ -35,6 +35,7 @@ import { type OllamaThinkValue, resolveOllamaConfiguredNumCtx, resolveOllamaThinkParamValue, + supportsNativeOllamaMax, shouldForwardNativeOllamaThink, } from "./stream-compat.js"; import { OLLAMA_INCOMPLETE_STREAM_ERROR } from "./stream-contract.js"; @@ -245,7 +246,7 @@ function resolveOllamaTopLevelParams( } } } - const think = resolveOllamaThinkParamValue(params); + const think = resolveOllamaThinkParamValue(params, supportsNativeOllamaMax(model)); if (think !== undefined && shouldForwardNativeOllamaThink(model, think)) { requestParams.think = think; }