





















@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
44import os from "node:os";
55import path from "node:path";
66import { describe, expect, it } from "vitest";
7+import { isLocalOllamaBaseUrl } from "./src/discovery-shared.js";
78import { createOllamaEmbeddingProvider } from "./src/embedding-provider.js";
89import { createOllamaStreamFn } from "./src/stream.js";
910import { createOllamaWebSearchProvider } from "./src/web-search-provider.js";
@@ -16,6 +17,38 @@ const EMBEDDING_MODEL =
1617process.env.OPENCLAW_LIVE_OLLAMA_EMBED_MODEL?.trim() || "embeddinggemma:latest";
1718const PROVIDER_ID = process.env.OPENCLAW_LIVE_OLLAMA_PROVIDER_ID?.trim() || "ollama-live-custom";
1819const RUN_WEB_SEARCH = process.env.OPENCLAW_LIVE_OLLAMA_WEB_SEARCH !== "0";
20+const RUN_EMBEDDINGS =
21+process.env.OPENCLAW_LIVE_OLLAMA_EMBEDDINGS === "1" ||
22+(process.env.OPENCLAW_LIVE_OLLAMA_EMBEDDINGS !== "0" && !isOllamaCloudBaseUrl(OLLAMA_BASE_URL));
23+const OLLAMA_CONFIG_API_KEY = isLocalOllamaBaseUrl(OLLAMA_BASE_URL)
24+ ? "ollama-local"
25+ : "OLLAMA_API_KEY";
26+27+function isOllamaCloudBaseUrl(baseUrl: string): boolean {
28+try {
29+const parsed = new URL(baseUrl);
30+return parsed.protocol === "https:" && parsed.hostname === "ollama.com";
31+} catch {
32+return false;
33+}
34+}
35+36+function requireOllamaRuntimeApiKey(): string | undefined {
37+if (OLLAMA_CONFIG_API_KEY !== "OLLAMA_API_KEY") {
38+return undefined;
39+}
40+const apiKey = process.env.OLLAMA_API_KEY?.trim();
41+if (!apiKey) {
42+throw new Error(
43+"OPENCLAW_LIVE_OLLAMA_BASE_URL points at a remote Ollama host; set OLLAMA_API_KEY.",
44+);
45+}
46+return apiKey;
47+}
48+49+function resolveOllamaDirectApiKey(): string {
50+return requireOllamaRuntimeApiKey() ?? "ollama-local";
51+}
19522053async function collectStreamEvents<T>(stream: AsyncIterable<T>): Promise<T[]> {
2154const events: T[] = [];
@@ -37,7 +70,7 @@ async function withTempOpenClawState<T>(run: (paths: { root: string }) => Promis
3770ollama: {
3871api: "ollama",
3972baseUrl: OLLAMA_BASE_URL,
40-apiKey: "ollama-local",
73+apiKey: OLLAMA_CONFIG_API_KEY,
4174models: [],
4275},
4376},
@@ -54,6 +87,13 @@ async function withTempOpenClawState<T>(run: (paths: { root: string }) => Promis
5487}
55885689async function runOpenClawCli(args: string[], env: NodeJS.ProcessEnv) {
90+const hasBuiltEntry = ["entry.js", "entry.mjs"].some((entry) =>
91+fsSync.existsSync(path.join(process.cwd(), "dist", entry)),
92+);
93+const sourceRunnerAvailable = !hasBuiltEntry;
94+const commandArgs = sourceRunnerAvailable
95+ ? ["scripts/run-node.mjs", ...args]
96+ : ["openclaw.mjs", ...args];
5797const outputRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "openclaw-ollama-cli-output-"));
5898const stdoutPath = path.join(outputRoot, "stdout.txt");
5999const stderrPath = path.join(outputRoot, "stderr.txt");
@@ -62,10 +102,10 @@ async function runOpenClawCli(args: string[], env: NodeJS.ProcessEnv) {
62102let stdoutClosed = false;
63103let stderrClosed = false;
64104try {
65-const result = spawnSync(process.execPath, ["openclaw.mjs", ...args], {
105+const result = spawnSync(process.execPath, commandArgs, {
66106cwd: process.cwd(),
67107 env,
68-timeout: 90_000,
108+timeout: sourceRunnerAvailable ? 180_000 : 90_000,
69109stdio: ["ignore", stdoutFd, stderrFd],
70110});
71111fsSync.closeSync(stdoutFd);
@@ -96,6 +136,7 @@ function parseJsonEnvelope(stdout: string): Record<string, unknown> {
96136}
9713798138function buildCliEnv(root: string): NodeJS.ProcessEnv {
139+const apiKey = requireOllamaRuntimeApiKey();
99140return {
100141PATH: process.env.PATH,
101142HOME: process.env.HOME,
@@ -110,7 +151,9 @@ function buildCliEnv(root: string): NodeJS.ProcessEnv {
110151OPENCLAW_CONFIG_PATH: path.join(root, "openclaw.json"),
111152OPENCLAW_NO_RESPAWN: "1",
112153OPENCLAW_TEST_FAST: "1",
113-OLLAMA_API_KEY: "ollama-local",
154+PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false",
155+pnpm_config_verify_deps_before_run: "false",
156+OLLAMA_API_KEY: apiKey ?? "ollama-local",
114157};
115158}
116159@@ -204,6 +247,7 @@ describe.skipIf(!LIVE)("ollama live", () => {
204247onPayload: (body: unknown) => {
205248payload = body as NonNullable<typeof payload>;
206249},
250+apiKey: requireOllamaRuntimeApiKey(),
207251} as never,
208252);
209253@@ -223,31 +267,35 @@ describe.skipIf(!LIVE)("ollama live", () => {
223267expect(properties?.options?.type).toBe("object");
224268}, 60_000);
225269226-it("embeds a batch through the current Ollama endpoint for custom providers", async () => {
227-const { client } = await createOllamaEmbeddingProvider({
228-config: {
229-models: {
230-providers: {
231-[PROVIDER_ID]: {
232-api: "ollama",
233-baseUrl: OLLAMA_BASE_URL,
234-apiKey: "ollama-local",
270+it.skipIf(!RUN_EMBEDDINGS)(
271+"embeds a batch through the current Ollama endpoint for custom providers",
272+async () => {
273+const { client } = await createOllamaEmbeddingProvider({
274+config: {
275+models: {
276+providers: {
277+[PROVIDER_ID]: {
278+api: "ollama",
279+baseUrl: OLLAMA_BASE_URL,
280+apiKey: resolveOllamaDirectApiKey(),
281+},
235282},
236283},
237284},
238-},
239-provider: PROVIDER_ID,
240-model: `${PROVIDER_ID}/${EMBEDDING_MODEL}`,
241-} as never);
285+provider: PROVIDER_ID,
286+model: `${PROVIDER_ID}/${EMBEDDING_MODEL}`,
287+} as never);
242288243-const embeddings = await client.embedBatch(["hello", "world"]);
289+ const embeddings = await client.embedBatch(["hello", "world"]);
244290245-expect(embeddings).toHaveLength(2);
246-expect(embeddings[0]?.length ?? 0).toBeGreaterThan(0);
247-expect(embeddings[1]?.length).toBe(embeddings[0]?.length);
248-expect(Math.hypot(...embeddings[0])).toBeGreaterThan(0.99);
249-expect(Math.hypot(...embeddings[0])).toBeLessThan(1.01);
250-}, 45_000);
291+expect(embeddings).toHaveLength(2);
292+expect(embeddings[0]?.length ?? 0).toBeGreaterThan(0);
293+expect(embeddings[1]?.length).toBe(embeddings[0]?.length);
294+expect(Math.hypot(...embeddings[0])).toBeGreaterThan(0.99);
295+expect(Math.hypot(...embeddings[0])).toBeLessThan(1.01);
296+},
297+45_000,
298+);
251299252300it.skipIf(!RUN_WEB_SEARCH)(
253301"searches through Ollama web search fallback endpoints",
@@ -260,7 +308,7 @@ describe.skipIf(!LIVE)("ollama live", () => {
260308ollama: {
261309api: "ollama",
262310baseUrl: OLLAMA_BASE_URL,
263-apiKey: "ollama-local",
311+apiKey: resolveOllamaDirectApiKey(),
264312},
265313},
266314},
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。