import { config } from "../config/index.js"; import fs from "node:fs"; async function ollamaFetch(path, body) { let res; try { res = await fetch(`${config.ollama.url}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }); } catch (err) { // #region debug-point C:ollama-fetch-throw (() => { let u = "http://127.0.0.1:7777/event"; let s = "chat-502-gateway"; try { const e = fs.readFileSync(".dbg/chat-502-gateway.env", "utf8"); u = e.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u; s = e.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s; } catch {} fetch(u, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ sessionId: s, runId: "post-fix", hypothesisId: "C", location: "api/services/ollamaClient.js", msg: "[DEBUG] ollama fetch threw", data: { url: config.ollama.url, path, model: body?.model ?? null, promptLen: typeof body?.prompt === "string" ? body.prompt.length : null, messagesCount: Array.isArray(body?.messages) ? body.messages.length : null, name: err?.name ?? null, message: typeof err?.message === "string" ? err.message : null }, ts: Date.now() }) }).catch(() => {}); })(); // #endregion throw err; } if (!res.ok) { const text = await res.text().catch(() => ""); // #region debug-point C:ollama-non-200 (() => { let u = "http://127.0.0.1:7777/event"; let s = "chat-502-gateway"; try { const e = fs.readFileSync(".dbg/chat-502-gateway.env", "utf8"); u = e.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u; s = e.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s; } catch {} fetch(u, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ sessionId: s, runId: "post-fix", hypothesisId: "C", location: "api/services/ollamaClient.js", msg: "[DEBUG] ollama non-200", data: { url: config.ollama.url, path, status: res.status, statusText: res.statusText, model: body?.model ?? null, promptLen: typeof body?.prompt === "string" ? body.prompt.length : null, messagesCount: Array.isArray(body?.messages) ? body.messages.length : null, responseSnippet: typeof text === "string" ? text.slice(0, 300) : "" }, ts: Date.now() }) }).catch(() => {}); })(); // #endregion let msg = `ollama_error:${res.status}:${text || res.statusText}`; if ( res.status === 404 && typeof body?.model === "string" && typeof text === "string" && text.toLowerCase().includes("model") && text.toLowerCase().includes("not found") ) { let installed = []; try { const tagsRes = await fetch(`${config.ollama.url}/api/tags`); const tags = await tagsRes.json().catch(() => ({})); installed = Array.isArray(tags?.models) ? tags.models.map((m) => m?.name).filter(Boolean) : []; } catch {} const installedList = installed.length ? installed.slice(0, 10).join(",") : "none"; msg = `ollama_model_not_found:${body.model}:installed=${installedList}:hint=ollama pull ${body.model}`; } const err = new Error(msg); err.statusCode = 502; throw err; } return res.json(); } export async function embedTexts(texts) { const embeddings = []; for (const text of texts) { const data = await ollamaFetch("/api/embeddings", { model: config.ollama.embeddingsModel, prompt: text }); embeddings.push(data.embedding); } return embeddings; } export async function chatCompletion({ messages }) { const data = await ollamaFetch("/api/chat", { model: config.ollama.chatModel, messages, stream: false }); return { content: data?.message?.content ?? "" }; } export async function visionExtractFromImage({ imageBase64, prompt }) { const data = await ollamaFetch("/api/chat", { model: config.ollama.visionModel, messages: [ { role: "user", content: prompt, images: [imageBase64] } ], stream: false }); return { content: data?.message?.content ?? "" }; }