diff --git a/extensions/ollama/index.test.ts b/extensions/ollama/index.test.ts index 5e43f2cb00a2..e732b01cf644 100644 --- a/extensions/ollama/index.test.ts +++ b/extensions/ollama/index.test.ts @@ -153,6 +153,12 @@ describe("ollama tool-schema compatibility", () => { }); } }); + + it("keeps configured-row projection aligned with absent runtime model normalizers", () => { + for (const provider of registerProvidersWithPluginConfig({})) { + expect(provider.normalizeResolvedModel).toBeUndefined(); + } + }); }); function createOllamaResetValidationContext( diff --git a/extensions/ollama/provider-policy-api.test.ts b/extensions/ollama/provider-policy-api.test.ts index 2595e0fd34b2..348a2d5e1338 100644 --- a/extensions/ollama/provider-policy-api.test.ts +++ b/extensions/ollama/provider-policy-api.test.ts @@ -1,7 +1,11 @@ // Ollama tests cover provider policy api plugin behavior. import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types"; import { describe, expect, it } from "vitest"; -import { normalizeConfig, resolveThinkingProfile } from "./provider-policy-api.js"; +import { + normalizeConfig, + projectConfiguredModelRow, + resolveThinkingProfile, +} from "./provider-policy-api.js"; import { OLLAMA_DEFAULT_BASE_URL } from "./src/defaults.js"; function createModel(id: string, name: string): ModelDefinitionConfig { @@ -60,6 +64,48 @@ describe("ollama provider policy public artifact", () => { ).toStrictEqual({}); }); + it.each(["ollama", " OLLAMA-CLOUD "])("skips runtime row normalization for %s", (provider) => { + expect( + projectConfiguredModelRow({ + provider, + modelId: "qwen3.5:9b", + model: { + provider: provider.trim().toLowerCase(), + id: "qwen3.5:9b", + api: "ollama", + baseUrl: OLLAMA_DEFAULT_BASE_URL, + input: ["text"], + name: "Qwen 3.5 9B", + reasoning: true, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 8_192, + }, + }), + ).toBeNull(); + }); + + it("keeps unrelated providers on the runtime normalization path", () => { + expect( + projectConfiguredModelRow({ + provider: "openai", + modelId: "gpt-5.5", + model: { + provider: "openai", + id: "gpt-5.5", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + input: ["text"], + name: "GPT-5.5", + reasoning: true, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 8_192, + }, + }), + ).toBeUndefined(); + }); + it("exposes max thinking for reasoning-capable models without full plugin activation", () => { expect(resolveThinkingProfile({ reasoning: true })).toEqual({ levels: [{ id: "off" }, { id: "low" }, { id: "medium" }, { id: "high" }, { id: "max" }], diff --git a/extensions/ollama/provider-policy-api.ts b/extensions/ollama/provider-policy-api.ts index 13f136c1aa10..5fb45b6259a8 100644 --- a/extensions/ollama/provider-policy-api.ts +++ b/extensions/ollama/provider-policy-api.ts @@ -1,7 +1,10 @@ // Ollama API module exposes the plugin public contract. -import type { ProviderThinkingProfile } from "openclaw/plugin-sdk/plugin-entry"; +import type { + ProviderNormalizeResolvedModelContext, + ProviderThinkingProfile, +} from "openclaw/plugin-sdk/plugin-entry"; import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types"; -import { OLLAMA_DEFAULT_BASE_URL } from "./src/defaults.js"; +import { OLLAMA_CLOUD_PROVIDER_ID, OLLAMA_DEFAULT_BASE_URL } from "./src/defaults.js"; type OllamaProviderConfigDraft = Partial; @@ -51,6 +54,15 @@ export function normalizeConfig({ return next; } +/** + * Ollama's local and cloud providers do not normalize resolved models. + * Skip full plugin activation when the model-list path asks for that no-op. + */ +export function projectConfiguredModelRow(ctx: ProviderNormalizeResolvedModelContext) { + const provider = ctx.provider.trim().toLowerCase(); + return provider === "ollama" || provider === OLLAMA_CLOUD_PROVIDER_ID ? null : undefined; +} + export function resolveThinkingProfile({ reasoning, }: {