diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 7885666..a79ac3f 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -63,6 +63,11 @@ - Caught exceptions log their actual exception, message, and stack while the client response remains generic; successful responses are not logged. No reasoning, status, or response semantics changed. - Next restart point: one production-default manufacturing call while observing the Next.js dev-server terminal. +## Chat capability detection + +- Direct POST `/api/chat` was proven supported. The false-negative cause was the capability probe using model `dummy-check`, which conflated model availability with endpoint capability. +- The probe now uses the configured model; live behaviour after this fix remains untested. + ## Current product architecture Three distinct routes, not a single page: diff --git a/lib/llm/provider.js b/lib/llm/provider.js index c41a313..ecea82e 100644 --- a/lib/llm/provider.js +++ b/lib/llm/provider.js @@ -60,7 +60,7 @@ function recoverJson(raw) { let _chatSupported = null; -async function detectChatSupport(baseUrl) { +async function detectChatSupport(baseUrl, modelName) { if (_chatSupported !== null) return _chatSupported; try { @@ -68,7 +68,7 @@ async function detectChatSupport(baseUrl) { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - model: "dummy-check", + model: modelName, messages: [{ role: "user", content: "test" }], stream: false, }), @@ -109,7 +109,7 @@ class OllamaLlmProvider { // Step 1: Detect whether /api/chat exists (cache result) // ================================================================ try { - chatSupported = await detectChatSupport(baseUrl); + chatSupported = await detectChatSupport(baseUrl, modelName); } catch { /* failed silently — defaults to false */ } // ================================================================ diff --git a/tests/llm/provider.test.js b/tests/llm/provider.test.js new file mode 100644 index 0000000..289fd29 --- /dev/null +++ b/tests/llm/provider.test.js @@ -0,0 +1,32 @@ +import { describe, expect, it, vi } from "vitest"; + +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, + json: async () => ({ message: { content: "{}" } }), + }); + vi.stubGlobal("fetch", fetchSpy); + process.env.OLLAMA_BASE_URL = "http://ollama.test"; + + try { + const { getProvider } = await import("@/lib/llm/provider.js"); + await getProvider().generateReconstruction("prompt", "configured-model"); + + expect(JSON.parse(fetchSpy.mock.calls[0][1].body)).toMatchObject({ + model: "configured-model", + stream: false, + }); + expect(fetchSpy.mock.calls[0][0]).toBe("http://ollama.test/api/chat"); + expect(fetchSpy.mock.calls[1][0]).toBe("http://ollama.test/api/chat"); + expect(fetchSpy.mock.calls[1][0]).not.toContain("/api/generate"); + } finally { + vi.unstubAllGlobals(); + if (originalBaseUrl === undefined) delete process.env.OLLAMA_BASE_URL; + else process.env.OLLAMA_BASE_URL = originalBaseUrl; + } + }); +}); \ No newline at end of file