← back to AbramsOS

lib/ollama.js

60 lines

// Local Ollama client. Mac1 is the bulk default; Mac2 is overflow.
// Per Steve's rules: NEVER call the Anthropic API. Local Ollama only.

const DEFAULT_BASE = process.env.OLLAMA_BASE_URL || 'http://192.168.1.133:11434';
const DEFAULT_MODEL = process.env.OLLAMA_MODEL || 'qwen3:14b';
const DEFAULT_TIMEOUT_MS = 30_000;

async function generate(prompt, { model = DEFAULT_MODEL, base = DEFAULT_BASE, timeoutMs = DEFAULT_TIMEOUT_MS, format = null, system = null } = {}) {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
  try {
    const body = { model, prompt, stream: false };
    if (format) body.format = format;
    if (system) body.system = system;
    const r = await fetch(`${base}/api/generate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
      signal: ctrl.signal,
    });
    if (!r.ok) throw new Error(`ollama ${r.status}: ${await r.text().catch(() => '')}`);
    const j = await r.json();
    return (j.response || '').trim();
  } finally {
    clearTimeout(t);
  }
}

async function generateJson(prompt, opts = {}) {
  // Use Ollama's structured-output mode by passing format: "json".
  const raw = await generate(prompt, { ...opts, format: 'json' });
  // Some models still wrap in code fences; strip if present.
  const cleaned = raw.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/, '').trim();
  try {
    return JSON.parse(cleaned);
  } catch (err) {
    // Last-resort: pull first {...} block
    const m = cleaned.match(/\{[\s\S]*\}/);
    if (m) {
      try { return JSON.parse(m[0]); } catch (_) {}
    }
    throw new Error(`ollama returned non-JSON: ${cleaned.slice(0, 200)}`);
  }
}

async function reachable(base = DEFAULT_BASE, timeoutMs = 3000) {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
  try {
    const r = await fetch(`${base}/api/tags`, { signal: ctrl.signal });
    return r.ok;
  } catch (_) {
    return false;
  } finally {
    clearTimeout(t);
  }
}

module.exports = { generate, generateJson, reachable, DEFAULT_BASE, DEFAULT_MODEL };