

























11// Ollama tests cover discovery shared plugin behavior.
2+import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
23import { describe, expect, it } from "vitest";
3-import { isLocalOllamaBaseUrl } from "./discovery-shared.js";
4+import {
5+isHostedOllamaCloud,
6+isLocalOllamaBaseUrl,
7+resolveOllamaDiscoveryResult,
8+} from "./discovery-shared.js";
49510describe("isLocalOllamaBaseUrl", () => {
611it.each([
@@ -40,3 +45,192 @@ describe("isLocalOllamaBaseUrl", () => {
4045expect(isLocalOllamaBaseUrl(baseUrl)).toBe(false);
4146});
4247});
48+49+describe("isHostedOllamaCloud", () => {
50+it.each([
51+"https://ollama.com",
52+"https://ollama.com:11434",
53+"https://api.ollama.com",
54+"https://api.ollama.com/v1",
55+"https://sub.ollama.com",
56+])("classifies %s as hosted cloud", (baseUrl) => {
57+expect(isHostedOllamaCloud(baseUrl)).toBe(true);
58+});
59+60+it.each([
61+undefined,
62+"",
63+"http://localhost:11434",
64+"http://127.0.0.1:11434",
65+"https://ollama.mycompany.com",
66+"https://ollama.example.com",
67+"http://10.0.0.5:11434",
68+"not a url",
69+])("classifies %s as not hosted cloud", (baseUrl) => {
70+expect(isHostedOllamaCloud(baseUrl)).toBe(false);
71+});
72+});
73+74+describe("resolveOllamaDiscoveryResult — hosted Ollama Cloud guard", () => {
75+const discoveredModel = {
76+id: "discovered-model",
77+name: "discovered-model",
78+reasoning: false,
79+input: ["text"],
80+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
81+contextWindow: 128000,
82+maxTokens: 8192,
83+compat: { supportsTools: true, supportsUsageInStreaming: true },
84+params: { num_ctx: 128000 },
85+} satisfies ModelProviderConfig["models"][number];
86+87+const cloudModel = {
88+id: "minimax-m3:cloud",
89+name: "minimax-m3:cloud",
90+reasoning: false,
91+input: ["text"],
92+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
93+contextWindow: 128000,
94+maxTokens: 8192,
95+compat: { supportsTools: true, supportsUsageInStreaming: true },
96+params: { num_ctx: 128000 },
97+} satisfies ModelProviderConfig["models"][number];
98+99+const buildMockProvider = async (
100+_configuredBaseUrl?: string,
101+_opts?: { quiet?: boolean },
102+): Promise<ModelProviderConfig> => ({
103+baseUrl: "https://ollama.com",
104+api: "ollama",
105+models: [discoveredModel],
106+});
107+108+it("returns null for remote base URL without explicit models", async () => {
109+const result = await resolveOllamaDiscoveryResult({
110+ctx: {
111+config: {
112+models: {
113+providers: {
114+ollama: {
115+baseUrl: "https://ollama.com",
116+apiKey: "test-key",
117+api: "ollama",
118+},
119+},
120+},
121+},
122+env: {},
123+resolveProviderApiKey: () => ({ apiKey: "test-key" }),
124+},
125+pluginConfig: {},
126+buildProvider: buildMockProvider,
127+});
128+expect(result).toBeNull();
129+});
130+131+it("returns explicit models for remote base URL when models are configured", async () => {
132+const result = await resolveOllamaDiscoveryResult({
133+ctx: {
134+config: {
135+models: {
136+providers: {
137+ollama: {
138+baseUrl: "https://ollama.com",
139+apiKey: "test-key",
140+api: "ollama",
141+models: [cloudModel],
142+},
143+},
144+},
145+},
146+env: {},
147+resolveProviderApiKey: () => ({ apiKey: "test-key" }),
148+},
149+pluginConfig: {},
150+buildProvider: buildMockProvider,
151+});
152+expect(result).not.toBeNull();
153+expect(result!.provider.models).toHaveLength(1);
154+expect(result!.provider.models[0].id).toBe("minimax-m3:cloud");
155+});
156+157+it("does not call buildProvider for remote base URL without explicit models", async () => {
158+let providerCalled = false;
159+const trackingBuildProvider = async (
160+_configuredBaseUrl?: string,
161+_opts?: { quiet?: boolean },
162+): Promise<ModelProviderConfig> => {
163+providerCalled = true;
164+return buildMockProvider();
165+};
166+167+const result = await resolveOllamaDiscoveryResult({
168+ctx: {
169+config: {
170+models: {
171+providers: {
172+ollama: {
173+baseUrl: "https://ollama.com",
174+apiKey: "test-key",
175+api: "ollama",
176+},
177+},
178+},
179+},
180+env: {},
181+resolveProviderApiKey: () => ({ apiKey: "test-key" }),
182+},
183+pluginConfig: {},
184+buildProvider: trackingBuildProvider,
185+});
186+expect(result).toBeNull();
187+expect(providerCalled).toBe(false);
188+});
189+190+it("still auto-discovers for remote self-hosted base URL when no explicit models", async () => {
191+const result = await resolveOllamaDiscoveryResult({
192+ctx: {
193+config: {
194+models: {
195+providers: {
196+ollama: {
197+baseUrl: "https://ollama.mycompany.com",
198+apiKey: "test-key",
199+api: "ollama",
200+},
201+},
202+},
203+},
204+env: {},
205+resolveProviderApiKey: () => ({ apiKey: "test-key" }),
206+},
207+pluginConfig: {},
208+buildProvider: buildMockProvider,
209+});
210+// Remote self-hosted base URL should still reach the discovery path
211+expect(result).not.toBeNull();
212+});
213+214+it("still auto-discovers for local base URL when no explicit models", async () => {
215+const result = await resolveOllamaDiscoveryResult({
216+ctx: {
217+config: {
218+models: {
219+providers: {
220+ollama: {
221+baseUrl: "http://localhost:11434",
222+api: "ollama",
223+},
224+},
225+},
226+},
227+env: { OLLAMA_API_KEY: "ollama-local" },
228+resolveProviderApiKey: () => ({ apiKey: "ollama-local" }),
229+},
230+pluginConfig: {},
231+buildProvider: buildMockProvider,
232+});
233+// Local base URL should still reach the discovery path
234+expect(result).not.toBeNull();
235+});
236+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。