diff --git a/extensions/ollama/src/stream-runtime.test.ts b/extensions/ollama/src/stream-runtime.test.ts index 174e6dbf88e..298a45255fb 100644 --- a/extensions/ollama/src/stream-runtime.test.ts +++ b/extensions/ollama/src/stream-runtime.test.ts @@ -10,6 +10,7 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({ import { buildOllamaChatRequest, + createConfiguredOllamaCompatStreamWrapper, createConfiguredOllamaStreamFn, createOllamaStreamFn, convertToOllamaMessages, @@ -57,6 +58,46 @@ describe("buildOllamaChatRequest", () => { }); }); +describe("createConfiguredOllamaCompatStreamWrapper", () => { + it("adds Moonshot thinking config for Ollama cloud Kimi compat requests", async () => { + let patchedPayload: Record | undefined; + const baseStreamFn = vi.fn((_model, _context, options) => { + options?.onPayload?.({ tool_choice: "auto" }); + return (async function* () {})(); + }); + const model = { + api: "openai-completions", + provider: "ollama", + id: "kimi-k2.5:cloud", + contextWindow: 262144, + }; + + const wrapped = createConfiguredOllamaCompatStreamWrapper({ + provider: "ollama", + modelId: "kimi-k2.5:cloud", + model, + streamFn: baseStreamFn, + thinkingLevel: "high", + extraParams: {}, + } as never); + + await wrapped?.( + model as never, + { messages: [] } as never, + { + onPayload: (payload: unknown) => { + patchedPayload = payload as Record; + }, + } as never, + ); + + expect(patchedPayload).toMatchObject({ + thinking: { type: "enabled" }, + options: { num_ctx: 262144 }, + }); + }); +}); + describe("convertToOllamaMessages", () => { it("converts user text messages", () => { const messages = [{ role: "user", content: "hello" }]; diff --git a/src/agents/openai-completions-compat.ts b/src/agents/openai-completions-compat.ts index 544eb6081d2..ebc9825d27e 100644 --- a/src/agents/openai-completions-compat.ts +++ b/src/agents/openai-completions-compat.ts @@ -67,7 +67,6 @@ export function resolveOpenAICompletionsCompatDefaults( endpointClass === "mistral-public" || knownProviderFamily === "mistral" || (isDefaultRoute && isDefaultRouteProvider(provider, "chutes")); - const isOllamaCompatProvider = provider === "ollama"; return { supportsStore: !isNonStandard && knownProviderFamily !== "mistral" && !usesExplicitProxyLikeEndpoint, @@ -78,8 +77,7 @@ export function resolveOpenAICompletionsCompatDefaults( endpointClass !== "xai-native" && !usesExplicitProxyLikeEndpoint, supportsUsageInStreaming: - isOllamaCompatProvider || - (!isNonStandard && (!usesConfiguredNonOpenAIEndpoint || supportsNativeStreamingUsageCompat)), + !isNonStandard && (!usesConfiguredNonOpenAIEndpoint || supportsNativeStreamingUsageCompat), maxTokensField: usesMaxTokens ? "max_tokens" : "max_completion_tokens", thinkingFormat: isZai ? "zai" : isOpenRouterLike ? "openrouter" : "openai", visibleReasoningDetailTypes: isOpenRouterLike ? ["response.output_text", "response.text"] : [], diff --git a/src/agents/provider-attribution.test.ts b/src/agents/provider-attribution.test.ts index b55afe8fd58..9e1438a423b 100644 --- a/src/agents/provider-attribution.test.ts +++ b/src/agents/provider-attribution.test.ts @@ -712,7 +712,7 @@ describe("provider attribution", () => { expect( resolveProviderRequestCapabilities({ - provider: "ollama", + provider: "custom-local", api: "openai-completions", baseUrl: "http://127.0.0.1:11434/v1", capability: "llm", @@ -720,20 +720,7 @@ describe("provider attribution", () => { }), ).toMatchObject({ endpointClass: "local", - supportsNativeStreamingUsageCompat: true, - }); - - expect( - resolveProviderRequestCapabilities({ - provider: "ollama", - api: "openai-completions", - baseUrl: "http://127.0.0.1:11434/v1", - capability: "llm", - transport: "stream", - modelId: "kimi-k2.5:cloud", - }), - ).toMatchObject({ - compatibilityFamily: "moonshot", + supportsNativeStreamingUsageCompat: false, }); }); @@ -928,28 +915,6 @@ describe("provider attribution", () => { supportsNativeStreamingUsageCompat: true, }, }, - { - name: "Ollama OpenAI-compatible completions", - input: { - provider: "ollama", - api: "openai-completions", - baseUrl: "http://127.0.0.1:11434/v1", - capability: "llm" as const, - transport: "stream" as const, - }, - expected: { - knownProviderFamily: "ollama", - endpointClass: "local", - isKnownNativeEndpoint: false, - allowsOpenAIServiceTier: false, - supportsOpenAIReasoningCompatPayload: false, - allowsResponsesStore: false, - supportsResponsesStoreField: false, - shouldStripResponsesPromptCache: false, - allowsAnthropicServiceTier: false, - supportsNativeStreamingUsageCompat: true, - }, - }, { name: "native Google Gemini api", input: { diff --git a/src/agents/provider-attribution.ts b/src/agents/provider-attribution.ts index ff0b65321e9..46fff7b2753 100644 --- a/src/agents/provider-attribution.ts +++ b/src/agents/provider-attribution.ts @@ -132,10 +132,6 @@ const OPENAI_RESPONSES_APIS = new Set([ const OPENAI_RESPONSES_PROVIDERS = new Set(["openai", "azure-openai", "azure-openai-responses"]); const MOONSHOT_COMPAT_PROVIDERS = new Set(["moonshot", "kimi"]); -function isOllamaMoonshotCompatModel(modelId: string | null | undefined): boolean { - return /^kimi-k2\.5(?::|$)/i.test(modelId?.trim() ?? ""); -} - function formatOpenClawUserAgent(version: string): string { return `${OPENCLAW_ATTRIBUTION_ORIGINATOR}/${version}`; } @@ -575,12 +571,7 @@ export function resolveProviderRequestCapabilities( endpointClass === "google-vertex"; let compatibilityFamily: ProviderRequestCompatibilityFamily | undefined; - const isOllamaOpenAICompletions = provider === "ollama" && api === "openai-completions"; - if ( - provider && - (MOONSHOT_COMPAT_PROVIDERS.has(provider) || - (provider === "ollama" && isOllamaMoonshotCompatModel(input.modelId))) - ) { + if (provider && MOONSHOT_COMPAT_PROVIDERS.has(provider)) { compatibilityFamily = "moonshot"; } @@ -638,9 +629,7 @@ export function resolveProviderRequestCapabilities( // Native endpoint class is the real signal here. Users can point a generic // provider key at Moonshot or DashScope and still need streaming usage. supportsNativeStreamingUsageCompat: - endpointClass === "moonshot-native" || - endpointClass === "modelstudio-native" || - isOllamaOpenAICompletions, + endpointClass === "moonshot-native" || endpointClass === "modelstudio-native", compatibilityFamily, }; }