diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ed22a1c91..7516d18293c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai - Providers/Ollama: move memory embeddings to Ollama's current `/api/embed` endpoint with batched `input` requests while preserving vector normalization and custom provider auth/header overrides. Fixes #39983. Thanks @sskkcc and @LiudengZhang. - Providers/Ollama: route local web search through Ollama's signed `/api/experimental/web_search` daemon proxy, use hosted `/api/web_search` directly for `ollama.com`, and keep `OLLAMA_API_KEY` scoped to cloud fallback auth. Fixes #69132. Thanks @yoon1012 and @hyspacex. - Providers/Ollama: accept OpenAI SDK-style `baseURL` as an alias for `baseUrl` across discovery, streaming, setup pulls, embeddings, and web search so remote Ollama hosts are not silently ignored. Fixes #62533; supersedes #62549. Thanks @Julien-BKK and @Linux2010. +- Providers/PDF/Ollama: add bounded network timeouts for Ollama model pulls and native Anthropic/Gemini PDF analysis requests so unresponsive provider endpoints no longer hang sessions indefinitely. Fixes #54142; supersedes #54144 and #54145. Thanks @jinduwang1001-max and @arkyu2077. - Memory/doctor: treat Ollama memory embeddings as key-optional so `openclaw doctor` no longer warns about a missing API key when the gateway reports embeddings are ready. Fixes #46584. Thanks @fengly78. - Agents/Ollama: apply provider-owned replay turn normalization to native Ollama chat so Cloud models no longer reject non-alternating replay history in agent/Gateway runs. Fixes #71697. Thanks @ismael-81. - Control UI/Ollama: show the resolved configured thinking default in chat and session thinking dropdowns so inherited `adaptive`/per-model thinking config no longer appears as `Default (off)` or a generic inherit value. Fixes #72407. Thanks @NotecAG. diff --git a/extensions/ollama/src/setup.test.ts b/extensions/ollama/src/setup.test.ts index 926f2b690b0..8a7a4d3b4ae 100644 --- a/extensions/ollama/src/setup.test.ts +++ b/extensions/ollama/src/setup.test.ts @@ -417,6 +417,9 @@ describe("ollama setup", () => { expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock.mock.calls[1][0]).toContain("/api/pull"); + const pullInit = fetchMock.mock.calls[1][1]; + expect(pullInit?.signal).toBeInstanceOf(AbortSignal); + expect(pullInit?.signal?.aborted).toBe(false); }); it("skips pull when model is already available", async () => { diff --git a/extensions/ollama/src/setup.ts b/extensions/ollama/src/setup.ts index 4a36e327ecf..2c44fdad742 100644 --- a/extensions/ollama/src/setup.ts +++ b/extensions/ollama/src/setup.ts @@ -42,6 +42,7 @@ const OLLAMA_SUGGESTED_MODELS_LOCAL = [OLLAMA_DEFAULT_MODEL]; const OLLAMA_SUGGESTED_MODELS_CLOUD = ["kimi-k2.5:cloud", "minimax-m2.7:cloud", "glm-5.1:cloud"]; const OLLAMA_CONTEXT_ENRICH_LIMIT = 200; const OLLAMA_CLOUD_MAX_DISCOVERED_MODELS = 500; +const OLLAMA_PULL_REQUEST_TIMEOUT_MS = 30_000; type OllamaSetupOptions = { customBaseUrl?: string; @@ -172,6 +173,7 @@ async function pullOllamaModelCore(params: { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: modelName }), }, + timeoutMs: OLLAMA_PULL_REQUEST_TIMEOUT_MS, policy: buildOllamaBaseUrlSsrFPolicy(baseUrl), auditContext: "ollama-setup.pull", }); diff --git a/src/agents/tools/pdf-native-providers.test.ts b/src/agents/tools/pdf-native-providers.test.ts index 5e7cceb2538..b2aedd833f8 100644 --- a/src/agents/tools/pdf-native-providers.test.ts +++ b/src/agents/tools/pdf-native-providers.test.ts @@ -78,6 +78,8 @@ describe("native PDF provider API calls", () => { expect(fetchMock).toHaveBeenCalledTimes(1); const [url, opts] = fetchMock.mock.calls[0]; expect(url).toContain("/v1/messages"); + expect(opts.signal).toBeInstanceOf(AbortSignal); + expect(opts.signal.aborted).toBe(false); const body = JSON.parse(opts.body); expect(body.model).toBe("claude-opus-4-6"); expect(body.messages[0].content).toHaveLength(2); @@ -132,6 +134,8 @@ describe("native PDF provider API calls", () => { const [url, opts] = fetchMock.mock.calls[0]; expect(url).toContain("generateContent"); expect(url).toContain("gemini-2.5-pro"); + expect(opts.signal).toBeInstanceOf(AbortSignal); + expect(opts.signal.aborted).toBe(false); const body = JSON.parse(opts.body); expect(body.contents[0].parts).toHaveLength(2); expect(body.contents[0].parts[0].inline_data.mime_type).toBe("application/pdf"); diff --git a/src/agents/tools/pdf-native-providers.ts b/src/agents/tools/pdf-native-providers.ts index aa47540e530..fc7622145b2 100644 --- a/src/agents/tools/pdf-native-providers.ts +++ b/src/agents/tools/pdf-native-providers.ts @@ -12,6 +12,8 @@ type PdfInput = { filename?: string; }; +const NATIVE_PDF_PROVIDER_FETCH_TIMEOUT_MS = 120_000; + // --------------------------------------------------------------------------- // Anthropic – native PDF via Messages API // --------------------------------------------------------------------------- @@ -74,6 +76,7 @@ export async function anthropicAnalyzePdf(params: { max_tokens: params.maxTokens ?? 4096, messages: [{ role: "user", content }], }), + signal: AbortSignal.timeout(NATIVE_PDF_PROVIDER_FETCH_TIMEOUT_MS), }); if (!res.ok) { @@ -158,6 +161,7 @@ export async function geminiAnalyzePdf(params: { body: JSON.stringify({ contents: [{ role: "user", parts }], }), + signal: AbortSignal.timeout(NATIVE_PDF_PROVIDER_FETCH_TIMEOUT_MS), }); if (!res.ok) {