From 7070342fb1d0852be513e9ef4457404033a86a6f Mon Sep 17 00:00:00 2001 From: robbond Date: Sat, 5 Sep 2026 19:41:01 +0100 Subject: [PATCH] fix(confidence-engine): preserve successful chat detection --- docs/current-handoff.md | 6 ++++++ lib/llm/provider.js | 8 ++++---- tests/llm/provider.test.js | 8 ++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 2e7479b..cccea91 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -92,6 +92,12 @@ - Provider execution diagnostics now distinguish chat skipped due capability state, chat attempted and failed before generate fallback, and successful chat without fallback. They are deterministically verified through `/api/cases/start`; endpoint selection and fallback behaviour remain unchanged. - Next restart point: one observation-only fixed-scenario production call to identify why `/api/generate` is reached. +## Chat capability body disposal + +- A fresh-process debugger observation proved capability `/api/chat` returned HTTP 200, then invalid `res.body?.consume()` threw and the catch incorrectly cached `_chatSupported = false`. +- This skipped schema-constrained reconstruction `/api/chat` and used unconstrained `/api/generate`. Response-body disposal now uses Fetch-compatible consumption without changing capability or fallback semantics. +- Next boundary: one fresh-process fixed-scenario production observation. + ## Current product architecture Three distinct routes, not a single page: diff --git a/lib/llm/provider.js b/lib/llm/provider.js index fcca1f3..4bdb74e 100644 --- a/lib/llm/provider.js +++ b/lib/llm/provider.js @@ -84,13 +84,13 @@ async function detectChatSupport(baseUrl, modelName) { }); if (res.ok) { - await res.body?.consume(); + await res.text(); _chatSupported = true; } else if (res.status === 405 || res.status === 501) { - await res.body?.consume(); + await res.text(); _chatSupported = false; } else { - await res.body?.consume(); + await res.text(); _chatSupported = false; } } catch { @@ -160,7 +160,7 @@ class OllamaLlmProvider { : JSON.stringify(fullResponseData.message?.content ?? null); apiUsed = "/api/chat"; } else { - await res.body?.consume(); + await res.text(); } } catch (e) { if (!e.message.includes("abort")) { /* non-fatal */ } diff --git a/tests/llm/provider.test.js b/tests/llm/provider.test.js index e23a198..eb16177 100644 --- a/tests/llm/provider.test.js +++ b/tests/llm/provider.test.js @@ -5,7 +5,7 @@ describe("OllamaLlmProvider chat capability detection", () => { it("uses the configured model for the chat probe and keeps the chat path", async () => { const originalBaseUrl = process.env.OLLAMA_BASE_URL; const fetchSpy = vi.fn() - .mockResolvedValueOnce({ ok: true, body: { consume: vi.fn() } }) + .mockResolvedValueOnce({ ok: true, text: async () => "" }) .mockResolvedValueOnce({ ok: true, json: async () => ({ message: { content: "{}" } }), @@ -55,7 +55,7 @@ describe("OllamaLlmProvider chat capability detection", () => { it("reports chat-skipped generate fallback execution", async () => { const originalBaseUrl = process.env.OLLAMA_BASE_URL; const fetchSpy = vi.fn() - .mockResolvedValueOnce({ ok: false, status: 501, body: { consume: vi.fn() } }) + .mockResolvedValueOnce({ ok: false, status: 501, text: async () => "" }) .mockResolvedValueOnce({ ok: false, status: 500, text: async () => "failure" }); vi.stubGlobal("fetch", fetchSpy); process.env.OLLAMA_BASE_URL = "http://ollama.test"; @@ -84,8 +84,8 @@ describe("OllamaLlmProvider chat capability detection", () => { it("reports chat-attempt-failed generate fallback execution", async () => { const originalBaseUrl = process.env.OLLAMA_BASE_URL; const fetchSpy = vi.fn() - .mockResolvedValueOnce({ ok: true, body: { consume: vi.fn() } }) - .mockResolvedValueOnce({ ok: false, body: { consume: vi.fn() } }) + .mockResolvedValueOnce({ ok: true, text: async () => "" }) + .mockResolvedValueOnce({ ok: false, text: async () => "" }) .mockResolvedValueOnce({ ok: false, status: 500, text: async () => "failure" }); vi.stubGlobal("fetch", fetchSpy); process.env.OLLAMA_BASE_URL = "http://ollama.test";