import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
import {
  DAEMON_CLIENT_TIMEOUT_MS,
  EMBEDDING_REQUEST_TIMEOUT_MS,
  LLM_REQUEST_TIMEOUT_MS
} from "./chunk-WBLTISAK.js";

// src/intelligence/ollama.ts
var ENDPOINT_GENERATE = "/api/generate";
var ENDPOINT_EMBED = "/api/embed";
var ENDPOINT_TAGS = "/api/tags";
var OllamaBackend = class _OllamaBackend {
  static DEFAULT_BASE_URL = "http://localhost:11434";
  name = "ollama";
  baseUrl;
  model;
  defaultMaxTokens;
  contextWindow;
  constructor(config) {
    this.baseUrl = config?.base_url ?? _OllamaBackend.DEFAULT_BASE_URL;
    this.model = config?.model ?? config?.summary_model ?? "llama3.2";
    this.defaultMaxTokens = config?.max_tokens ?? 1024;
    this.contextWindow = config?.context_window;
  }
  async summarize(prompt, opts) {
    const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
    const contextLength = opts?.contextLength ?? this.contextWindow;
    const options = { num_predict: maxTokens };
    if (contextLength) {
      options.num_ctx = contextLength;
    }
    const body = {
      model: this.model,
      prompt,
      stream: true,
      options
    };
    if (opts?.systemPrompt) {
      body.system = opts.systemPrompt;
    }
    if (opts?.reasoning) {
      body.think = opts.reasoning === "off" ? false : opts.reasoning;
    }
    if (opts?.keepAlive) {
      body.keep_alive = opts.keepAlive;
    }
    const response = await fetch(`${this.baseUrl}${ENDPOINT_GENERATE}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(opts?.timeoutMs ?? LLM_REQUEST_TIMEOUT_MS)
    });
    if (!response.ok) {
      const errorBody = await response.text().catch(() => "");
      throw new Error(`Ollama summarize failed: ${response.status} ${errorBody.slice(0, 500)}`);
    }
    return this.readStream(response);
  }
  /** Read an Ollama streaming response (newline-delimited JSON) and accumulate the result. */
  async readStream(response) {
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let text = "";
    let model = this.model;
    let buffer = "";
    try {
      for (; ; ) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() ?? "";
        for (const line of lines) {
          if (!line.trim()) continue;
          const chunk = JSON.parse(line);
          if (chunk.error) throw new Error(`Ollama stream error: ${chunk.error}`);
          text += chunk.response ?? "";
          if (chunk.model) model = chunk.model;
        }
      }
      if (buffer.trim()) {
        const chunk = JSON.parse(buffer);
        if (chunk.error) throw new Error(`Ollama stream error: ${chunk.error}`);
        text += chunk.response ?? "";
        if (chunk.model) model = chunk.model;
      }
    } finally {
      reader.releaseLock();
    }
    return { text, model };
  }
  async embed(text) {
    const response = await fetch(`${this.baseUrl}${ENDPOINT_EMBED}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: this.model,
        input: text
      }),
      signal: AbortSignal.timeout(EMBEDDING_REQUEST_TIMEOUT_MS)
    });
    if (!response.ok) {
      throw new Error(`Ollama embed failed: ${response.status} ${response.statusText}`);
    }
    const data = await response.json();
    const embedding = data.embeddings[0];
    return { embedding, model: data.model, dimensions: embedding.length };
  }
  async isAvailable() {
    try {
      const response = await fetch(`${this.baseUrl}${ENDPOINT_TAGS}`, {
        signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS)
      });
      return response.ok;
    } catch {
      return false;
    }
  }
  /** List available models on this Ollama instance. */
  async listModels(timeoutMs) {
    try {
      const response = await fetch(`${this.baseUrl}${ENDPOINT_TAGS}`, {
        signal: AbortSignal.timeout(timeoutMs ?? DAEMON_CLIENT_TIMEOUT_MS)
      });
      const data = await response.json();
      return data.models.map((m) => m.name);
    } catch {
      return [];
    }
  }
};

// src/intelligence/lm-studio.ts
var ENDPOINT_CHAT = "/api/v1/chat";
var ENDPOINT_MODELS_LOAD = "/api/v1/models/load";
var ENDPOINT_MODELS_LIST = "/v1/models";
var ENDPOINT_MODELS_NATIVE = "/api/v1/models";
var ENDPOINT_EMBEDDINGS = "/v1/embeddings";
var LmStudioBackend = class _LmStudioBackend {
  static DEFAULT_BASE_URL = "http://localhost:1234";
  name = "lm-studio";
  baseUrl;
  model;
  instanceId = null;
  contextWindow;
  defaultMaxTokens;
  constructor(config) {
    this.baseUrl = config?.base_url ?? _LmStudioBackend.DEFAULT_BASE_URL;
    this.model = config?.model ?? config?.summary_model ?? "llama3.2";
    this.contextWindow = config?.context_window;
    this.defaultMaxTokens = config?.max_tokens ?? 1024;
  }
  /**
   * Generate text using LM Studio's native REST API (/api/v1/chat).
   * Routes to our specific instance by ID when available, with model name +
   * context_length as fallback. This ensures correct routing when multiple
   * daemons share the same LM Studio, and graceful degradation when our
   * instance is evicted by idle TTL.
   */
  async summarize(prompt, opts) {
    const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
    const contextLength = opts?.contextLength ?? this.contextWindow;
    const body = {
      model: this.instanceId ?? this.model,
      input: prompt,
      max_output_tokens: maxTokens,
      store: false
    };
    if (contextLength) {
      body.context_length = contextLength;
    }
    if (opts?.systemPrompt) {
      body.system_prompt = opts.systemPrompt;
    }
    if (opts?.reasoning) {
      body.reasoning = opts.reasoning;
    }
    const response = await fetch(`${this.baseUrl}${ENDPOINT_CHAT}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(opts?.timeoutMs ?? LLM_REQUEST_TIMEOUT_MS)
    });
    if (!response.ok) {
      const errorBody = await response.text().catch(() => "");
      if (response.status === 404 && this.instanceId) {
        this.instanceId = null;
      }
      throw new Error(`LM Studio summarize failed: ${response.status} ${errorBody.slice(0, 500)}`);
    }
    const data = await response.json();
    const messageOutput = data.output.find((o) => o.type === "message");
    const text = messageOutput?.content ?? "";
    return { text, model: data.model_instance_id };
  }
  /**
   * Generate embeddings using LM Studio's OpenAI-compatible endpoint.
   * (The native API doesn't have an embedding endpoint — OpenAI-compat is fine here.)
   */
  async embed(text) {
    const response = await fetch(`${this.baseUrl}${ENDPOINT_EMBEDDINGS}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: this.model,
        input: text
      }),
      signal: AbortSignal.timeout(EMBEDDING_REQUEST_TIMEOUT_MS)
    });
    if (!response.ok) {
      throw new Error(`LM Studio embed failed: ${response.status}`);
    }
    const data = await response.json();
    const embedding = data.data[0].embedding;
    return { embedding, model: data.model, dimensions: embedding.length };
  }
  /**
   * Ensure a model instance is loaded with the desired settings.
   * Called every digest cycle (not cached) so it recovers from idle TTL eviction.
   *
   * The load API is necessary to control offload_kv_cache_to_gpu — a load-time
   * setting that cannot be set per-request via the chat API.
   *
   * Multi-daemon safe: finds or loads our own compatible instance without
   * touching instances from other daemons/projects. Routes by instance ID.
   */
  async ensureLoaded(contextLength, gpuKvCache) {
    const ctx = contextLength ?? this.contextWindow;
    const kvCache = gpuKvCache ?? false;
    const instances = await this.getLoadedInstances();
    for (const instance of instances) {
      const matchesContext = !ctx || instance.config.context_length === ctx;
      const matchesKvCache = instance.config.offload_kv_cache_to_gpu === kvCache;
      if (matchesContext && matchesKvCache) {
        this.instanceId = instance.id;
        return;
      }
    }
    const body = {
      model: this.model,
      flash_attention: true,
      offload_kv_cache_to_gpu: kvCache
    };
    if (ctx) {
      body.context_length = ctx;
    }
    const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LOAD}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(LLM_REQUEST_TIMEOUT_MS)
    });
    if (!response.ok) {
      const errorBody = await response.text().catch(() => "");
      throw new Error(`LM Studio model load failed: ${response.status} ${errorBody.slice(0, 200)}`);
    }
    const loadResult = await response.json();
    const id = loadResult.instance_id ?? loadResult.id ?? loadResult.model_instance_id;
    if (id) {
      this.instanceId = id;
    }
  }
  /**
   * Query the LM Studio native API for loaded instances of this model.
   * Returns an empty array if the API is unavailable or the model has no loaded instances.
   */
  async getLoadedInstances() {
    try {
      const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_NATIVE}`, {
        signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS)
      });
      if (!response.ok) return [];
      const data = await response.json();
      const entry = data.models.find((m) => m.key === this.model);
      return entry?.loaded_instances ?? [];
    } catch {
      return [];
    }
  }
  async isAvailable() {
    try {
      const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LIST}`, {
        signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS)
      });
      return response.ok;
    } catch {
      return false;
    }
  }
  /** List available models on this LM Studio instance. */
  async listModels(timeoutMs) {
    try {
      const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LIST}`, {
        signal: AbortSignal.timeout(timeoutMs ?? DAEMON_CLIENT_TIMEOUT_MS)
      });
      const data = await response.json();
      return data.data.map((m) => m.id);
    } catch {
      return [];
    }
  }
};

export {
  OllamaBackend,
  LmStudioBackend
};
//# sourceMappingURL=chunk-MWW62YZP.js.map