import { TEXT_ADAPTERS } from "./text-adapters.js";
import { selectProvider, selectProviderWithKeyStore } from "./select.js";
import { resolveModel } from "./model-select.js";
import { createLogger } from "../../../logger.js";
import { getConfig } from "../../../config.js";
const log = createLogger("providers");
const DEFAULT_TIMEOUT_MS = 6e4;
function isLlmConfigured(env = process.env) {
  const raw = env.WIGOLO_LLM_PROVIDER;
  if (raw && (raw.startsWith("http://") || raw.startsWith("https://"))) return true;
  return selectProvider(env) !== null;
}
async function isLlmConfiguredWithKeyStore(env = process.env) {
  const raw = env.WIGOLO_LLM_PROVIDER;
  if (raw && (raw.startsWith("http://") || raw.startsWith("https://"))) return true;
  const cfg = getConfig();
  const result = await selectProviderWithKeyStore(env, { dataDir: cfg.dataDir });
  return result !== null;
}
function pickCustomBackend(env) {
  const raw = env.WIGOLO_LLM_PROVIDER;
  if (raw && (raw.startsWith("http://") || raw.startsWith("https://"))) {
    return { type: "custom", url: raw };
  }
  return null;
}
function buildSignal(opts) {
  if (opts.signal) return opts.signal;
  if (opts.timeoutMs) return AbortSignal.timeout(opts.timeoutMs);
  return AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
}
const RETRY_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504, 529]);
const RETRY_MESSAGE_PATTERN = /\b(429|500|502|503|504|529)\b|too many requests|rate.?limit|unavailable|overloaded|high demand|service unavailable|temporar/i;
const MAX_RETRIES = 2;
const BASE_BACKOFF_MS = 500;
function isTransientError(err) {
  if (!err) return false;
  const status = err.status;
  if (typeof status === "number" && RETRY_STATUS.has(status)) return true;
  const code = err.code;
  if (typeof code === "string" && /ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|ECONNREFUSED/.test(code)) {
    return true;
  }
  const msg = err instanceof Error ? err.message : String(err);
  return RETRY_MESSAGE_PATTERN.test(msg);
}
async function withRetry(label, fn) {
  let lastErr;
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastErr = err;
      if (attempt === MAX_RETRIES || !isTransientError(err)) throw err;
      const backoff = BASE_BACKOFF_MS * Math.pow(3, attempt);
      log.warn("LLM call transient failure, retrying", {
        label,
        attempt: attempt + 1,
        backoffMs: backoff,
        error: err instanceof Error ? err.message : String(err)
      });
      await new Promise((r) => setTimeout(r, backoff));
    }
  }
  throw lastErr;
}
async function runLlmText(opts) {
  const cfg = getConfig();
  const ksOpts = { dataDir: cfg.dataDir };
  const signal = buildSignal(opts);
  const custom = pickCustomBackend(process.env);
  if (custom) {
    const endpoint = custom.url.includes("/chat/completions") ? custom.url : custom.url.replace(/\/+$/, "") + "/v1/chat/completions";
    const model2 = opts.modelOverride ?? process.env.WIGOLO_LLM_MODEL ?? "local";
    log.debug("runLlmText custom", { url: endpoint, model: model2 });
    return withRetry(`custom:${model2}`, async () => {
      const start = Date.now();
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          model: model2,
          messages: [{ role: "user", content: opts.prompt }],
          max_tokens: opts.maxTokens
        }),
        signal
      });
      if (!response.ok) {
        const err = new Error(`Local LLM endpoint returned ${response.status}`);
        err.status = response.status;
        throw err;
      }
      const payload = await response.json();
      const text = payload.choices?.[0]?.message?.content;
      if (typeof text !== "string" || text.trim().length === 0) {
        throw new Error("Local LLM response missing message content");
      }
      return { text, provider: "custom", model: model2, latencyMs: Date.now() - start };
    });
  }
  const resolved = await selectProviderWithKeyStore(process.env, ksOpts);
  if (!resolved) {
    throw new Error("No LLM configured \u2014 set WIGOLO_LLM_PROVIDER or a provider API key");
  }
  const { provider, key: apiKey } = resolved;
  const model = resolveModel(provider, opts.modelOverride);
  log.debug("runLlmText cloud", { provider, model });
  return withRetry(`${provider}:${model}`, async () => {
    const r = await TEXT_ADAPTERS[provider](
      { prompt: opts.prompt, model, maxTokens: opts.maxTokens, signal },
      apiKey
    );
    return { text: r.text, provider: r.provider, model: r.model, latencyMs: r.latencyMs };
  });
}
async function runLlmJson(opts) {
  const schemaText = opts.jsonSchema ? `
Return JSON matching this schema:
${JSON.stringify(opts.jsonSchema)}` : "";
  const wrapped = `${opts.prompt}

Return ONLY valid JSON, no prose.${schemaText}`;
  const r = await runLlmText({ ...opts, prompt: wrapped });
  let values;
  try {
    values = JSON.parse(stripJsonFences(r.text));
  } catch (e) {
    throw new Error(`LLM returned invalid JSON: ${e.message}`);
  }
  if (!values || typeof values !== "object" || Array.isArray(values)) {
    throw new Error("LLM response is not a JSON object");
  }
  return {
    values,
    provider: r.provider,
    model: r.model,
    latencyMs: r.latencyMs
  };
}
function stripJsonFences(text) {
  const trimmed = text.trim();
  const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]+?)\s*```$/);
  if (fenced) return fenced[1];
  return trimmed;
}
async function resolveActiveProviderKey() {
  const cfg = getConfig();
  return selectProviderWithKeyStore(process.env, { dataDir: cfg.dataDir });
}
import { resolveProviderKey as resolveProviderKey2 } from "../../../security/key-store.js";
export {
  isLlmConfigured,
  isLlmConfiguredWithKeyStore,
  resolveActiveProviderKey,
  resolveProviderKey2 as resolveProviderKey,
  runLlmJson,
  runLlmText
};
//# sourceMappingURL=run.js.map