// src/llmgateway/contract/errors.ts
var GatewayError = class _GatewayError extends Error {
  kind;
  status;
  provider;
  constructor(kind, message, extra) {
    super(message, extra?.cause !== void 0 ? { cause: extra.cause } : void 0);
    this.name = "GatewayError";
    this.kind = kind;
    this.status = extra?.status;
    this.provider = extra?.provider;
    Object.setPrototypeOf(this, _GatewayError.prototype);
  }
};
function gatewayError(kind, message, extra) {
  return new GatewayError(kind, message, extra);
}

// src/llmgateway/catalog/cards.ts
var MODEL_CARDS = [
  // ── Anthropic (Claude) ──────────────────────────────────────────────────
  {
    id: "claude-opus-4",
    provider: "anthropic",
    api: "anthropic-messages",
    displayName: "Claude Opus 4",
    contextWindow: 2e5,
    maxOutputTokens: 32e3,
    modalities: ["text", "image"],
    reasoning: true,
    cost: {
      // approximate — flagship tier
      inputPerMTok: 15,
      outputPerMTok: 75,
      cacheReadPerMTok: 1.5,
      cacheWritePerMTok: 18.75
    }
  },
  {
    id: "claude-sonnet-4",
    provider: "anthropic",
    api: "anthropic-messages",
    displayName: "Claude Sonnet 4",
    contextWindow: 2e5,
    maxOutputTokens: 64e3,
    modalities: ["text", "image"],
    reasoning: true,
    cost: {
      // approximate — mid tier
      inputPerMTok: 3,
      outputPerMTok: 15,
      cacheReadPerMTok: 0.3,
      cacheWritePerMTok: 3.75
    }
  },
  {
    id: "claude-haiku-3-5",
    provider: "anthropic",
    api: "anthropic-messages",
    displayName: "Claude Haiku 3.5",
    contextWindow: 2e5,
    maxOutputTokens: 8192,
    modalities: ["text", "image"],
    reasoning: false,
    cost: {
      // approximate — fast/cheap tier
      inputPerMTok: 0.8,
      outputPerMTok: 4,
      cacheReadPerMTok: 0.08,
      cacheWritePerMTok: 1
    }
  },
  // ── OpenAI (GPT + o-series) ─────────────────────────────────────────────
  {
    id: "gpt-4o",
    provider: "openai",
    api: "openai-completions",
    displayName: "GPT-4o",
    contextWindow: 128e3,
    maxOutputTokens: 16384,
    modalities: ["text", "image", "audio"],
    reasoning: false,
    cost: {
      // approximate — omni flagship
      inputPerMTok: 2.5,
      outputPerMTok: 10,
      cacheReadPerMTok: 1.25
    }
  },
  {
    id: "gpt-4o-mini",
    provider: "openai",
    api: "openai-completions",
    displayName: "GPT-4o mini",
    contextWindow: 128e3,
    maxOutputTokens: 16384,
    modalities: ["text", "image"],
    reasoning: false,
    cost: {
      // approximate — small omni
      inputPerMTok: 0.15,
      outputPerMTok: 0.6,
      cacheReadPerMTok: 0.075
    }
  },
  {
    id: "o3",
    provider: "openai",
    api: "openai-responses",
    displayName: "OpenAI o3",
    contextWindow: 2e5,
    maxOutputTokens: 1e5,
    modalities: ["text", "image"],
    reasoning: true,
    cost: {
      // approximate — reasoning flagship
      inputPerMTok: 10,
      outputPerMTok: 40,
      cacheReadPerMTok: 2.5
    }
  },
  {
    id: "o4-mini",
    provider: "openai",
    api: "openai-responses",
    displayName: "OpenAI o4-mini",
    contextWindow: 2e5,
    maxOutputTokens: 1e5,
    modalities: ["text", "image"],
    reasoning: true,
    cost: {
      // approximate — small reasoning
      inputPerMTok: 1.1,
      outputPerMTok: 4.4,
      cacheReadPerMTok: 0.275
    }
  },
  // ── Google (Gemini) ─────────────────────────────────────────────────────
  {
    id: "gemini-2.5-pro",
    provider: "google",
    api: "google-generative",
    displayName: "Gemini 2.5 Pro",
    contextWindow: 1e6,
    maxOutputTokens: 65536,
    modalities: ["text", "image", "audio"],
    reasoning: true,
    cost: {
      // approximate — long-context pro tier
      inputPerMTok: 1.25,
      outputPerMTok: 10,
      cacheReadPerMTok: 0.31
    }
  },
  {
    id: "gemini-2.5-flash",
    provider: "google",
    api: "google-generative",
    displayName: "Gemini 2.5 Flash",
    contextWindow: 1e6,
    maxOutputTokens: 65536,
    modalities: ["text", "image", "audio"],
    reasoning: true,
    cost: {
      // approximate — fast flash tier
      inputPerMTok: 0.3,
      outputPerMTok: 2.5,
      cacheReadPerMTok: 0.075
    }
  },
  // ── NVIDIA (OpenAI-compatible NIM endpoint) ─────────────────────────────
  {
    id: "nvidia/llama-3.1-nemotron-70b-instruct",
    provider: "nvidia",
    api: "nvidia-openai-compatible",
    displayName: "Nemotron 70B Instruct",
    baseUrl: "https://integrate.api.nvidia.com/v1",
    contextWindow: 128e3,
    maxOutputTokens: 4096,
    modalities: ["text"],
    reasoning: false,
    cost: {
      // approximate — hosted NIM inference
      inputPerMTok: 0.2,
      outputPerMTok: 0.2
    }
  },
  // ── Kimi / Moonshot (OpenAI-compatible) ─────────────────────────────────
  {
    id: "kimi-k2",
    provider: "kimi",
    api: "kimi-openai-compatible",
    displayName: "Kimi K2",
    baseUrl: "https://api.moonshot.ai/v1",
    contextWindow: 256e3,
    maxOutputTokens: 16384,
    modalities: ["text"],
    reasoning: true,
    cost: {
      // approximate — Moonshot hosted
      inputPerMTok: 0.6,
      outputPerMTok: 2.5,
      cacheReadPerMTok: 0.15
    }
  },
  // ── Ollama (local; pricing is zero — your own hardware) ─────────────────
  {
    id: "llama3.1:8b",
    provider: "ollama",
    api: "ollama",
    displayName: "Llama 3.1 8B (local)",
    baseUrl: "http://localhost:11434",
    contextWindow: 128e3,
    maxOutputTokens: 8192,
    modalities: ["text"],
    reasoning: false,
    cost: {
      // local inference — no per-token cost
      inputPerMTok: 0,
      outputPerMTok: 0
    }
  },
  // ── Mock (deterministic test fixture; free) ─────────────────────────────
  {
    id: "mock-1",
    provider: "mock",
    api: "mock",
    displayName: "Mock Model",
    contextWindow: 32e3,
    maxOutputTokens: 4096,
    modalities: ["text"],
    reasoning: false,
    cost: {
      inputPerMTok: 0,
      outputPerMTok: 0
    }
  }
];

// src/llmgateway/catalog/query.ts
var CardSelection = class _CardSelection {
  constructor(pool, tests) {
    this.pool = pool;
    this.tests = tests;
  }
  pool;
  tests;
  /** Begin a query over the given pool (defaults to the full catalog). */
  static over(pool) {
    return new _CardSelection(pool, []);
  }
  withTest(test) {
    return new _CardSelection(this.pool, [...this.tests, test]);
  }
  /** Keep only cards from a specific vendor. */
  byProvider(provider) {
    return this.withTest((card) => card.provider === provider);
  }
  /** Keep only cards served over a specific wire dialect. */
  byApi(api) {
    return this.withTest((card) => card.api === api);
  }
  /** Keep cards whose reasoning capability matches `enabled`. */
  reasoning(enabled) {
    return this.withTest((card) => card.reasoning === enabled);
  }
  /** Materialize every card that satisfies all accumulated predicates. */
  all() {
    return this.pool.filter((card) => this.tests.every((test) => test(card)));
  }
  /**
   * Terminal lookup by exact `id`, also respecting any accumulated predicates.
   * Returns `undefined` when no surviving card matches.
   */
  find(id) {
    return this.all().find((card) => card.id === id);
  }
};
function models() {
  return CardSelection.over(MODEL_CARDS);
}
function getCard(id) {
  return MODEL_CARDS.find((card) => card.id === id);
}

// src/llmgateway/catalog/cost.ts
var TOKENS_PER_MTOK = 1e6;
function estimateCost(card, usage) {
  const { cost } = card;
  let dollars = 0;
  dollars += usage.inputTokens / TOKENS_PER_MTOK * cost.inputPerMTok;
  dollars += usage.outputTokens / TOKENS_PER_MTOK * cost.outputPerMTok;
  if (usage.cacheReadTokens && cost.cacheReadPerMTok !== void 0) {
    dollars += usage.cacheReadTokens / TOKENS_PER_MTOK * cost.cacheReadPerMTok;
  }
  if (usage.cacheWriteTokens && cost.cacheWritePerMTok !== void 0) {
    dollars += usage.cacheWriteTokens / TOKENS_PER_MTOK * cost.cacheWritePerMTok;
  }
  return dollars;
}

// src/llmgateway/credentials/secrets.ts
var SECRET_TABLE = {
  anthropic: {
    provider: "anthropic",
    scheme: "api-key",
    envVars: ["ANTHROPIC_API_KEY"],
    hint: "Anthropic Messages API bearer key."
  },
  openai: {
    provider: "openai",
    scheme: "api-key",
    envVars: ["OPENAI_API_KEY"],
    hint: "OpenAI platform API key."
  },
  google: {
    provider: "google",
    scheme: "api-key",
    // Gemini Developer API: GEMINI_API_KEY is canonical; GOOGLE_API_KEY is the
    // legacy/alias name still honored by the Generative Language API.
    envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
    hint: "Google Generative Language (Gemini) API key."
  },
  "google-vertex": {
    provider: "google-vertex",
    scheme: "cloud-iam",
    // Vertex authenticates via Application Default Credentials; these point at
    // a service-account key file and the target project/region.
    envVars: [
      "GOOGLE_APPLICATION_CREDENTIALS",
      "GOOGLE_VERTEX_PROJECT",
      "GOOGLE_CLOUD_PROJECT"
    ],
    hint: "Vertex AI via Application Default Credentials (service account)."
  },
  amazon: {
    provider: "amazon",
    scheme: "cloud-iam",
    // Bedrock supports a short-lived bearer token, otherwise the standard
    // AWS SigV4 credential chain.
    envVars: [
      "AWS_BEARER_TOKEN_BEDROCK",
      "AWS_ACCESS_KEY_ID",
      "AWS_PROFILE"
    ],
    hint: "Amazon Bedrock via bearer token or AWS credential chain."
  },
  azure: {
    provider: "azure",
    scheme: "api-key",
    envVars: ["AZURE_OPENAI_API_KEY", "AZURE_API_KEY"],
    hint: "Azure OpenAI resource key (paired with endpoint + deployment)."
  },
  nvidia: {
    provider: "nvidia",
    scheme: "api-key",
    envVars: ["NVIDIA_API_KEY"],
    hint: "NVIDIA NIM / build.nvidia.com API key."
  },
  kimi: {
    provider: "kimi",
    scheme: "api-key",
    // Moonshot AI ships the Kimi models; MOONSHOT_API_KEY is the documented
    // name, KIMI_API_KEY is a common community alias.
    envVars: ["MOONSHOT_API_KEY", "KIMI_API_KEY"],
    hint: "Moonshot AI (Kimi) API key."
  },
  ollama: {
    provider: "ollama",
    scheme: "none",
    envVars: [],
    hint: "Local Ollama server \u2014 no credential required."
  },
  mock: {
    provider: "mock",
    scheme: "none",
    envVars: [],
    hint: "In-process mock transport \u2014 no credential required."
  }
};
function readEnv(name) {
  const raw = process.env[name];
  if (raw === void 0) return void 0;
  const trimmed = raw.trim();
  return trimmed.length > 0 ? trimmed : void 0;
}
async function resolveSecret(provider, override) {
  if (override !== void 0) {
    const trimmed = override.trim();
    if (trimmed.length > 0) return trimmed;
  }
  const descriptor = SECRET_TABLE[provider];
  if (descriptor.scheme === "none") return void 0;
  for (const name of descriptor.envVars) {
    const value = readEnv(name);
    if (value !== void 0) return value;
  }
  return void 0;
}

// src/llmgateway/streaming/sse.ts
var LF = "\n";
var CR = "\r";
async function* sseEvents(body, signal) {
  if (signal?.aborted) {
    throw gatewayError("aborted", "request aborted before reading the event stream");
  }
  const reader = body.getReader();
  const decoder = new TextDecoder("utf-8");
  let pending = "";
  let eventType = "message";
  let dataLines = [];
  let sawData = false;
  const onAbort = () => {
    void reader.cancel().catch(() => {
    });
  };
  signal?.addEventListener("abort", onAbort, { once: true });
  const resetEvent = () => {
    eventType = "message";
    dataLines = [];
    sawData = false;
  };
  const dispatch = () => {
    if (!sawData) {
      resetEvent();
      return void 0;
    }
    const out = { event: eventType, data: dataLines.join(LF) };
    resetEvent();
    return out;
  };
  const consumeLine = (line) => {
    if (line === "") {
      return dispatch();
    }
    if (line.startsWith(":")) {
      return void 0;
    }
    const colon = line.indexOf(":");
    let field;
    let value;
    if (colon === -1) {
      field = line;
      value = "";
    } else {
      field = line.slice(0, colon);
      value = line.slice(colon + 1);
      if (value.startsWith(" ")) {
        value = value.slice(1);
      }
    }
    switch (field) {
      case "event":
        eventType = value;
        break;
      case "data":
        dataLines.push(value);
        sawData = true;
        break;
      // "id" and "retry" are valid SSE fields but irrelevant to LLM streams.
      default:
        break;
    }
    return void 0;
  };
  function* drainLines(atEnd) {
    let start = 0;
    let i = 0;
    while (i < pending.length) {
      const ch = pending[i];
      if (ch === LF) {
        yield pending.slice(start, i);
        i += 1;
        start = i;
      } else if (ch === CR) {
        if (i === pending.length - 1 && !atEnd) {
          break;
        }
        yield pending.slice(start, i);
        if (pending[i + 1] === LF) {
          i += 2;
        } else {
          i += 1;
        }
        start = i;
      } else {
        i += 1;
      }
    }
    pending = pending.slice(start);
    if (atEnd && pending.length > 0) {
      const last = pending;
      pending = "";
      yield last;
    }
  }
  try {
    for (; ; ) {
      if (signal?.aborted) {
        throw gatewayError("aborted", "request aborted while reading the event stream");
      }
      const { value, done } = await reader.read();
      if (done) {
        break;
      }
      pending += decoder.decode(value, { stream: true });
      for (const line of drainLines(false)) {
        const ev = consumeLine(line);
        if (ev !== void 0) {
          yield ev;
        }
      }
    }
    pending += decoder.decode();
    for (const line of drainLines(true)) {
      const ev = consumeLine(line);
      if (ev !== void 0) {
        yield ev;
      }
    }
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "request aborted while reading the event stream", { cause });
    }
    if (cause instanceof Error && cause.name === "GatewayError") {
      throw cause;
    }
    throw gatewayError("transport", "failed reading the server-sent event stream", { cause });
  } finally {
    signal?.removeEventListener("abort", onAbort);
    reader.releaseLock();
  }
}

// src/llmgateway/conversion/reduce.ts
var EMPTY_USAGE = { inputTokens: 0, outputTokens: 0 };
function initial() {
  return {
    thinking: "",
    text: "",
    order: [],
    calls: /* @__PURE__ */ new Map(),
    usage: EMPTY_USAGE,
    stop: "complete"
  };
}
function ensureCall(state, id, name) {
  const existing = state.calls.get(id);
  if (existing === void 0) {
    state.order.push(id);
    state.calls.set(id, { name, args: "" });
  } else if (name !== "" && existing.name === "") {
    existing.name = name;
  }
}
function step(state, emission) {
  switch (emission.kind) {
    case "text":
      state.text += emission.delta;
      break;
    case "thinking":
      state.thinking += emission.delta;
      break;
    case "tool_call_start":
      ensureCall(state, emission.id, emission.name);
      break;
    case "tool_call_delta": {
      ensureCall(state, emission.id, "");
      const slot = state.calls.get(emission.id);
      if (slot !== void 0) {
        slot.args += emission.argsDelta;
      }
      break;
    }
    case "usage":
      state.usage = emission.usage;
      break;
    case "stop":
      state.stop = emission.stop;
      break;
    // `done` and `error` are terminal envelopes, not deltas to fold; a caller
    // that hands them to the reducer simply contributes nothing.
    case "done":
    case "error":
      break;
  }
}
function parseArgs(raw) {
  const trimmed = raw.trim();
  if (trimmed === "") {
    return {};
  }
  try {
    return JSON.parse(trimmed);
  } catch {
    return raw;
  }
}
function toBlocks(state) {
  const blocks = [];
  if (state.thinking !== "") {
    const block = { kind: "thinking", text: state.thinking };
    blocks.push(block);
  }
  if (state.text !== "") {
    const block = { kind: "text", text: state.text };
    blocks.push(block);
  }
  for (const id of state.order) {
    const call = state.calls.get(id);
    if (call === void 0) {
      continue;
    }
    const block = {
      kind: "tool_call",
      id,
      name: call.name,
      input: parseArgs(call.args)
    };
    blocks.push(block);
  }
  return blocks;
}
function foldReply(model, emissions) {
  const state = initial();
  for (const emission of emissions) {
    step(state, emission);
  }
  const blocks = toBlocks(state);
  let stop = state.stop;
  if (stop === "complete" && state.order.length > 0) {
    stop = "tool_calls";
  }
  return {
    role: "assistant",
    model,
    blocks,
    usage: state.usage,
    stop
  };
}

// src/llmgateway/streaming/channel.ts
function channelOf(gen) {
  return {
    [Symbol.asyncIterator]() {
      return gen();
    }
  };
}
async function collectReply(channel) {
  const buffer = [];
  let model = "unknown";
  for await (const emission of channel) {
    if (emission.kind === "done") {
      return emission.reply;
    }
    if (emission.kind === "error") {
      throw emission.error;
    }
    buffer.push(emission);
  }
  if (buffer.length === 0) {
    throw gatewayError("transport", "channel closed without emitting any data");
  }
  return foldReply(model, buffer);
}

// src/llmgateway/conversion/mappers.ts
function stringifyToolOutput(output) {
  if (typeof output === "string") {
    return output;
  }
  try {
    return JSON.stringify(output);
  } catch {
    return String(output);
  }
}
function toArgumentString(input) {
  if (typeof input === "string") {
    return input;
  }
  try {
    return JSON.stringify(input ?? {});
  } catch {
    return "{}";
  }
}
function toOpenAIChatMessages(c) {
  const messages = [];
  if (c.system !== void 0 && c.system !== "") {
    messages.push({ role: "system", content: c.system });
  }
  for (const turn of c.turns) {
    switch (turn.role) {
      case "user":
        messages.push(openAiUserMessage(turn.blocks));
        break;
      case "assistant":
        messages.push(openAiAssistantMessage(turn.blocks));
        break;
      case "tool":
        for (const message of openAiToolMessages(turn.blocks)) {
          messages.push(message);
        }
        break;
    }
  }
  return messages;
}
function openAiUserMessage(blocks) {
  const parts = [];
  let hasImage = false;
  for (const block of blocks) {
    if (block.kind === "text") {
      parts.push({ type: "text", text: block.text });
    } else if (block.kind === "image") {
      hasImage = true;
      parts.push({
        type: "image_url",
        image_url: { url: `data:${block.mediaType};base64,${block.dataBase64}` }
      });
    }
  }
  if (!hasImage) {
    const text = parts.map((p) => p.text ?? "").join("");
    return { role: "user", content: text };
  }
  return { role: "user", content: parts };
}
function openAiAssistantMessage(blocks) {
  let text = "";
  const toolCalls = [];
  for (const block of blocks) {
    if (block.kind === "text") {
      text += block.text;
    } else if (block.kind === "tool_call") {
      toolCalls.push({
        id: block.id,
        type: "function",
        function: { name: block.name, arguments: toArgumentString(block.input) }
      });
    }
  }
  const message = { role: "assistant", content: text === "" ? null : text };
  if (toolCalls.length > 0) {
    message.tool_calls = toolCalls;
  }
  return message;
}
function openAiToolMessages(blocks) {
  const out = [];
  for (const block of blocks) {
    if (block.kind === "tool_result") {
      out.push({
        role: "tool",
        tool_call_id: block.callId,
        content: stringifyToolOutput(block.output)
      });
    }
  }
  return out;
}
function toOpenAITools(tools) {
  if (tools === void 0 || tools.length === 0) {
    return void 0;
  }
  return tools.map((tool) => ({
    type: "function",
    function: {
      name: tool.name,
      description: tool.description,
      parameters: tool.parameters
    }
  }));
}
function toOpenAIToolChoice(choice) {
  if (choice === void 0) {
    return void 0;
  }
  return choice;
}
function anthropicThinkingBudget(level, maxOutput) {
  const fraction = {
    low: 0.2,
    medium: 0.4,
    high: 0.6,
    max: 0.8
  };
  if (level === "off") {
    return 0;
  }
  const budget = Math.floor(maxOutput * fraction[level]);
  return Math.max(1024, budget);
}
function toAnthropicRequest(c, model, opts) {
  const body = {
    model: model.id,
    max_tokens: opts.maxOutputTokens ?? model.maxOutputTokens,
    messages: toAnthropicMessages(c)
  };
  if (c.system !== void 0 && c.system !== "") {
    body.system = c.system;
  }
  const tools = toAnthropicTools(c.tools);
  if (tools !== void 0) {
    body.tools = tools;
  }
  const toolChoice = toAnthropicToolChoice(opts.toolChoice);
  if (toolChoice !== void 0) {
    body.tool_choice = toolChoice;
  }
  if (opts.temperature !== void 0) {
    body.temperature = opts.temperature;
  }
  if (opts.topP !== void 0) {
    body.top_p = opts.topP;
  }
  if (model.reasoning && opts.thinking !== void 0 && opts.thinking !== "off") {
    const max = opts.maxOutputTokens ?? model.maxOutputTokens;
    body.thinking = { type: "enabled", budget_tokens: anthropicThinkingBudget(opts.thinking, max) };
  }
  return body;
}
function toAnthropicMessages(c) {
  const messages = [];
  for (const turn of c.turns) {
    if (turn.role === "tool") {
      messages.push({ role: "user", content: anthropicContent(turn) });
    } else {
      messages.push({ role: turn.role, content: anthropicContent(turn) });
    }
  }
  return messages;
}
function anthropicContent(turn) {
  const content = [];
  for (const block of turn.blocks) {
    switch (block.kind) {
      case "text":
        content.push({ type: "text", text: block.text });
        break;
      case "thinking":
        content.push(
          block.signature !== void 0 ? { type: "thinking", thinking: block.text, signature: block.signature } : { type: "thinking", thinking: block.text }
        );
        break;
      case "tool_call":
        content.push({
          type: "tool_use",
          id: block.id,
          name: block.name,
          input: typeof block.input === "string" ? safeParse(block.input) : block.input ?? {}
        });
        break;
      case "tool_result":
        content.push({
          type: "tool_result",
          tool_use_id: block.callId,
          content: stringifyToolOutput(block.output),
          ...block.isError === true ? { is_error: true } : {}
        });
        break;
      case "image":
        content.push({
          type: "image",
          source: { type: "base64", media_type: block.mediaType, data: block.dataBase64 }
        });
        break;
    }
  }
  return content;
}
function toAnthropicTools(tools) {
  if (tools === void 0 || tools.length === 0) {
    return void 0;
  }
  return tools.map((tool) => ({
    name: tool.name,
    description: tool.description,
    input_schema: tool.parameters
  }));
}
function toAnthropicToolChoice(choice) {
  switch (choice) {
    case "auto":
      return { type: "auto" };
    case "required":
      return { type: "any" };
    case "none":
      return { type: "none" };
    default:
      return void 0;
  }
}
function toGoogleContents(c) {
  const contents = [];
  for (const turn of c.turns) {
    const role = turn.role === "assistant" ? "model" : "user";
    contents.push({ role, parts: googleParts(turn) });
  }
  return contents;
}
function googleParts(turn) {
  const parts = [];
  for (const block of turn.blocks) {
    switch (block.kind) {
      case "text":
        parts.push({ text: block.text });
        break;
      case "thinking":
        parts.push({ text: block.text, thought: true });
        break;
      case "tool_call":
        parts.push({
          functionCall: {
            name: block.name,
            args: typeof block.input === "string" ? safeParse(block.input) : block.input ?? {}
          }
        });
        break;
      case "tool_result":
        parts.push({
          functionResponse: {
            name: block.callId,
            response: { result: stringifyToolOutput(block.output) }
          }
        });
        break;
      case "image":
        parts.push({ inlineData: { mimeType: block.mediaType, data: block.dataBase64 } });
        break;
    }
  }
  return parts;
}
function toGoogleSystemInstruction(c) {
  if (c.system === void 0 || c.system === "") {
    return void 0;
  }
  return { role: "system", parts: [{ text: c.system }] };
}
function toGoogleTools(tools) {
  if (tools === void 0 || tools.length === 0) {
    return void 0;
  }
  return [
    {
      functionDeclarations: tools.map((tool) => ({
        name: tool.name,
        description: tool.description,
        parameters: tool.parameters
      }))
    }
  ];
}
function toResponsesInput(c) {
  const input = [];
  for (const turn of c.turns) {
    switch (turn.role) {
      case "user":
        input.push({ type: "message", role: "user", content: responsesUserContent(turn.blocks) });
        break;
      case "assistant":
        for (const item of responsesAssistantItems(turn.blocks)) {
          input.push(item);
        }
        break;
      case "tool":
        for (const block of turn.blocks) {
          if (block.kind === "tool_result") {
            input.push({
              type: "function_call_output",
              call_id: block.callId,
              output: stringifyToolOutput(block.output)
            });
          }
        }
        break;
    }
  }
  return input;
}
function responsesUserContent(blocks) {
  const parts = [];
  for (const block of blocks) {
    if (block.kind === "text") {
      parts.push({ type: "input_text", text: block.text });
    } else if (block.kind === "image") {
      parts.push({
        type: "input_image",
        image_url: `data:${block.mediaType};base64,${block.dataBase64}`
      });
    }
  }
  return parts;
}
function responsesAssistantItems(blocks) {
  const items = [];
  const content = [];
  for (const block of blocks) {
    if (block.kind === "text") {
      content.push({ type: "output_text", text: block.text });
    } else if (block.kind === "tool_call") {
      items.push({
        type: "function_call",
        call_id: block.id,
        name: block.name,
        arguments: toArgumentString(block.input)
      });
    }
  }
  if (content.length > 0) {
    items.unshift({ type: "message", role: "assistant", content });
  }
  return items;
}
function toResponsesTools(tools) {
  if (tools === void 0 || tools.length === 0) {
    return void 0;
  }
  return tools.map((tool) => ({
    type: "function",
    name: tool.name,
    description: tool.description,
    parameters: tool.parameters
  }));
}
function safeParse(raw) {
  try {
    return JSON.parse(raw);
  } catch {
    return {};
  }
}

// src/llmgateway/connectors/anthropic.ts
var DEFAULT_BASE_URL = "https://api.anthropic.com";
var ANTHROPIC_VERSION = "2023-06-01";
function isObject(v) {
  return typeof v === "object" && v !== null;
}
function readString(o, key) {
  const v = o[key];
  return typeof v === "string" ? v : void 0;
}
function readNumber(o, key) {
  const v = o[key];
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function mapStopReason(reason) {
  switch (reason) {
    case "end_turn":
    case "stop_sequence":
      return "complete";
    case "tool_use":
      return "tool_calls";
    case "max_tokens":
      return "max_output";
    case "refusal":
      return "refusal";
    case null:
    case void 0:
    case "":
      return void 0;
    default:
      return "complete";
  }
}
function mergeUsage(current, raw) {
  if (!isObject(raw)) {
    return current;
  }
  const next = { ...current };
  const input = readNumber(raw, "input_tokens");
  if (input !== void 0) {
    next.inputTokens = input;
  }
  const output = readNumber(raw, "output_tokens");
  if (output !== void 0) {
    next.outputTokens = output;
  }
  const cacheRead = readNumber(raw, "cache_read_input_tokens");
  if (cacheRead !== void 0) {
    next.cacheReadTokens = cacheRead;
  }
  const cacheWrite = readNumber(raw, "cache_creation_input_tokens");
  if (cacheWrite !== void 0) {
    next.cacheWriteTokens = cacheWrite;
  }
  return next;
}
function parseEventData(event, provider) {
  if (event.data === "") {
    return void 0;
  }
  let parsed;
  try {
    parsed = JSON.parse(event.data);
  } catch (cause) {
    throw gatewayError("parse", "could not decode an Anthropic stream event", { provider, cause });
  }
  return isObject(parsed) ? parsed : void 0;
}
function* emitFromEvent(type, payload, state) {
  switch (type) {
    case "message_start": {
      const message = payload.message;
      if (isObject(message)) {
        state.usage = mergeUsage(state.usage, message.usage);
        yield { kind: "usage", usage: state.usage };
      }
      return;
    }
    case "content_block_start": {
      yield* emitBlockStart(payload, state);
      return;
    }
    case "content_block_delta": {
      yield* emitBlockDelta(payload, state);
      return;
    }
    case "content_block_stop": {
      return;
    }
    case "message_delta": {
      const delta = payload.delta;
      if (isObject(delta)) {
        const stop = mapStopReason(readString(delta, "stop_reason"));
        if (stop !== void 0) {
          yield { kind: "stop", stop };
        }
      }
      const usage = payload.usage;
      if (usage !== void 0) {
        state.usage = mergeUsage(state.usage, usage);
        yield { kind: "usage", usage: state.usage };
      }
      return;
    }
    case "message_stop":
    case "ping":
      return;
    case "error": {
      const error = payload.error;
      const detail = isObject(error) ? readString(error, "message") : void 0;
      throw gatewayError("http", `Anthropic reported a stream error${detail !== void 0 ? `: ${detail}` : ""}`);
    }
    default:
      return;
  }
}
function* emitBlockStart(payload, state) {
  const index = readNumber(payload, "index") ?? 0;
  const block = payload.content_block;
  if (!isObject(block)) {
    return;
  }
  if (readString(block, "type") === "tool_use") {
    const id = readString(block, "id") ?? `tool_${index}`;
    const name = readString(block, "name") ?? "";
    state.toolIds.set(index, id);
    yield { kind: "tool_call_start", id, name };
  }
}
function* emitBlockDelta(payload, state) {
  const index = readNumber(payload, "index") ?? 0;
  const delta = payload.delta;
  if (!isObject(delta)) {
    return;
  }
  switch (readString(delta, "type")) {
    case "text_delta": {
      const text = readString(delta, "text");
      if (text !== void 0 && text !== "") {
        yield { kind: "text", delta: text };
      }
      return;
    }
    case "thinking_delta": {
      const thinking = readString(delta, "thinking");
      if (thinking !== void 0 && thinking !== "") {
        yield { kind: "thinking", delta: thinking };
      }
      return;
    }
    case "input_json_delta": {
      const fragment = readString(delta, "partial_json");
      if (fragment !== void 0 && fragment !== "") {
        const id = state.toolIds.get(index);
        if (id !== void 0) {
          yield { kind: "tool_call_delta", id, argsDelta: fragment };
        }
      }
      return;
    }
    // `signature_delta` (thinking signatures) carries no user-visible content.
    default:
      return;
  }
}
function endpointFor(model) {
  const base = (model.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
  return `${base}/v1/messages`;
}
async function postMessages(url, apiKey, body, signal) {
  let response;
  try {
    response = await fetch(url, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-api-key": apiKey,
        "anthropic-version": ANTHROPIC_VERSION
      },
      body: JSON.stringify(body),
      ...signal !== void 0 ? { signal } : {}
    });
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "request canceled before Anthropic responded", {
        provider: "anthropic",
        cause
      });
    }
    throw gatewayError("transport", "network call to Anthropic failed", { provider: "anthropic", cause });
  }
  if (!response.ok) {
    const detail = await readErrorBody(response);
    const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
    throw gatewayError(kind, `Anthropic returned HTTP ${response.status}${detail}`, {
      provider: "anthropic",
      status: response.status
    });
  }
  if (response.body === null) {
    throw gatewayError("transport", "Anthropic response arrived with no body", {
      provider: "anthropic",
      status: response.status
    });
  }
  return response;
}
async function readErrorBody(response) {
  try {
    const text = await response.text();
    return text === "" ? "" : `: ${text.slice(0, 500)}`;
  } catch {
    return "";
  }
}
function asGatewayError(cause) {
  if (cause instanceof Error && cause.name === "GatewayError") {
    return cause;
  }
  return gatewayError("transport", "Anthropic stream failed", { provider: "anthropic", cause });
}
function createanthropicConnector(deps2) {
  const api = "anthropic-messages";
  async function requireKey(options) {
    const key = await deps2.resolveSecret("anthropic", options.apiKey);
    if (key === void 0 || key === "") {
      throw gatewayError("auth", "no Anthropic API key found (set ANTHROPIC_API_KEY or pass options.apiKey)", {
        provider: "anthropic"
      });
    }
    return key;
  }
  function stream2(model, conversation, options) {
    return channelOf(async function* run() {
      const collected = [];
      try {
        const apiKey = await requireKey(options);
        const body = toAnthropicRequest(conversation, model, options);
        body.stream = true;
        const response = await postMessages(endpointFor(model), apiKey, body, options.signal);
        const wire = response.body;
        const state = { toolIds: /* @__PURE__ */ new Map(), usage: { inputTokens: 0, outputTokens: 0 } };
        for await (const event of sseEvents(wire, options.signal)) {
          const payload = parseEventData(event, "anthropic");
          if (payload === void 0) {
            continue;
          }
          const type = event.event !== "message" ? event.event : readString(payload, "type") ?? event.event;
          for (const emission of emitFromEvent(type, payload, state)) {
            collected.push(emission);
            yield emission;
          }
        }
        const reply = foldReply(model.id, collected);
        yield { kind: "done", reply };
      } catch (cause) {
        yield { kind: "error", error: asGatewayError(cause) };
      }
    });
  }
  function complete2(model, conversation, options) {
    return collectReply(stream2(model, conversation, options));
  }
  return {
    id: "anthropic",
    api,
    stream: stream2,
    complete: complete2
  };
}

// src/llmgateway/conversion/openai-compatible.ts
function isObject2(v) {
  return typeof v === "object" && v !== null;
}
function str(o, key) {
  const v = o[key];
  return typeof v === "string" ? v : void 0;
}
function num(o, key) {
  const v = o[key];
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function mapFinishReason(reason) {
  switch (reason) {
    case "stop":
      return "complete";
    case "length":
      return "max_output";
    case "tool_calls":
    case "function_call":
      return "tool_calls";
    case "content_filter":
      return "refusal";
    case null:
    case void 0:
    case "":
      return void 0;
    default:
      return "complete";
  }
}
function readUsage(chunk) {
  const usage = chunk.usage;
  if (!isObject2(usage)) {
    return void 0;
  }
  const inputTokens = num(usage, "prompt_tokens") ?? 0;
  const outputTokens = num(usage, "completion_tokens") ?? 0;
  const result = { inputTokens, outputTokens };
  const details = usage.prompt_tokens_details;
  if (isObject2(details)) {
    const cached = num(details, "cached_tokens");
    if (cached !== void 0) {
      result.cacheReadTokens = cached;
    }
  }
  return result;
}
function buildBody(model, c, opts, cfg, stream2) {
  const body = {
    model: model.id,
    messages: toOpenAIChatMessages(c),
    stream: stream2
  };
  if (stream2) {
    body.stream_options = { include_usage: true };
  }
  const tools = toOpenAITools(c.tools);
  if (tools !== void 0) {
    body.tools = tools;
    const choice = toOpenAIToolChoice(opts.toolChoice);
    if (choice !== void 0) {
      body.tool_choice = choice;
    }
  }
  const maxTokens = opts.maxOutputTokens ?? model.maxOutputTokens;
  if (maxTokens > 0) {
    body.max_tokens = maxTokens;
  }
  if (opts.temperature !== void 0) {
    body.temperature = opts.temperature;
  }
  if (opts.topP !== void 0) {
    body.top_p = opts.topP;
  }
  if (cfg.bodyExtra !== void 0) {
    for (const [k, v] of Object.entries(cfg.bodyExtra)) {
      body[k] = v;
    }
  }
  return body;
}
async function postChat(cfg, body, provider, signal) {
  const headers = { "content-type": "application/json", ...cfg.headers };
  let response;
  try {
    response = await fetch(cfg.url, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
      ...signal !== void 0 ? { signal } : {}
    });
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "request aborted before a response arrived", { provider, cause });
    }
    throw gatewayError("transport", "network request to the provider failed", { provider, cause });
  }
  if (!response.ok) {
    const detail = await readErrorBody2(response);
    const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
    throw gatewayError(kind, `provider returned HTTP ${response.status}${detail}`, {
      provider,
      status: response.status
    });
  }
  if (response.body === null) {
    throw gatewayError("transport", "provider response had no body", { provider, status: response.status });
  }
  return response;
}
async function readErrorBody2(response) {
  try {
    const text = await response.text();
    return text === "" ? "" : `: ${text.slice(0, 500)}`;
  } catch {
    return "";
  }
}
function* emitFromChunk(chunk, tracker) {
  const usage = readUsage(chunk);
  if (usage !== void 0) {
    yield { kind: "usage", usage };
  }
  const choices = chunk.choices;
  if (!Array.isArray(choices) || choices.length === 0) {
    return;
  }
  const choice = choices[0];
  if (!isObject2(choice)) {
    return;
  }
  const delta = choice.delta;
  if (isObject2(delta)) {
    const content = str(delta, "content");
    if (content !== void 0 && content !== "") {
      yield { kind: "text", delta: content };
    }
    const reasoning = str(delta, "reasoning_content") ?? str(delta, "reasoning");
    if (reasoning !== void 0 && reasoning !== "") {
      yield { kind: "thinking", delta: reasoning };
    }
    const toolCalls = delta.tool_calls;
    if (Array.isArray(toolCalls)) {
      for (const tc of toolCalls) {
        yield* emitToolCall(tc, tracker);
      }
    }
  }
  const finish = mapFinishReason(str(choice, "finish_reason"));
  if (finish !== void 0) {
    yield { kind: "stop", stop: finish };
  }
}
function* emitToolCall(tc, tracker) {
  if (!isObject2(tc)) {
    return;
  }
  const index = num(tc, "index") ?? 0;
  const explicitId = str(tc, "id");
  const fn = tc.function;
  const name = isObject2(fn) ? str(fn, "name") : void 0;
  const args = isObject2(fn) ? str(fn, "arguments") : void 0;
  let id = tracker.ids.get(index);
  if (id === void 0) {
    id = explicitId ?? `call_${index}`;
    tracker.ids.set(index, id);
    yield { kind: "tool_call_start", id, name: name ?? "" };
  } else if (explicitId !== void 0 && explicitId !== id) {
  }
  if (args !== void 0 && args !== "") {
    yield { kind: "tool_call_delta", id, argsDelta: args };
  }
}
function streamOpenAICompatibleChat(cfg, model, c, opts) {
  return channelOf(async function* run() {
    const provider = model.provider;
    const collected = [];
    try {
      const body = buildBody(model, c, opts, cfg, true);
      const response = await postChat(cfg, body, provider, opts.signal);
      const stream2 = response.body;
      const tracker = { ids: /* @__PURE__ */ new Map() };
      for await (const sse of sseEvents(stream2, opts.signal)) {
        const payload = sse.data;
        if (payload === "[DONE]") {
          break;
        }
        let chunk;
        try {
          chunk = JSON.parse(payload);
        } catch (cause) {
          throw gatewayError("parse", "failed to parse an OpenAI stream chunk", { provider, cause });
        }
        if (!isObject2(chunk)) {
          continue;
        }
        for (const emission of emitFromChunk(chunk, tracker)) {
          collected.push(emission);
          yield emission;
        }
      }
      const reply = foldReply(model.id, collected);
      yield { kind: "done", reply };
    } catch (cause) {
      const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "OpenAI-compatible stream failed", { provider, cause });
      yield { kind: "error", error };
    }
  });
}

// src/llmgateway/connectors/openai-chat.ts
var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
function endpointFor2(model) {
  const base = (model.baseUrl ?? DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
  return `${base}/chat/completions`;
}
var OpenAIChatConnector = class {
  constructor(deps2) {
    this.deps = deps2;
  }
  deps;
  id = "openai-chat";
  api = "openai-completions";
  stream(model, conversation, options) {
    const deps2 = this.deps;
    return channelOf(async function* run() {
      let cfg;
      try {
        cfg = await resolveConfig(deps2, model, options);
      } catch (cause) {
        const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "could not prepare the OpenAI request", {
          provider: model.provider,
          cause
        });
        yield { kind: "error", error };
        return;
      }
      yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
    });
  }
  async complete(model, conversation, options) {
    return collectReply(this.stream(model, conversation, options));
  }
};
async function resolveConfig(deps2, model, options) {
  const key = await deps2.resolveSecret(model.provider, options.apiKey);
  if (key === void 0 || key === "") {
    throw gatewayError(
      "auth",
      "no OpenAI credential found \u2014 set OPENAI_API_KEY or pass options.apiKey",
      { provider: model.provider }
    );
  }
  return {
    url: endpointFor2(model),
    headers: { authorization: `Bearer ${key}` }
  };
}
function createopenaichatConnector(deps2) {
  return new OpenAIChatConnector(deps2);
}

// src/llmgateway/connectors/openai-responses.ts
var DEFAULT_ORIGIN = "https://api.openai.com/v1";
function isObject3(v) {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}
function readString2(o, key) {
  const v = o[key];
  return typeof v === "string" ? v : void 0;
}
function readNumber2(o, key) {
  const v = o[key];
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function reasoningEffort(level) {
  switch (level) {
    case "low":
      return "low";
    case "medium":
      return "medium";
    case "high":
    case "max":
      return "high";
  }
}
function mapStatus(status, incompleteReason) {
  switch (status) {
    case "completed":
      return "complete";
    case "incomplete":
      return incompleteReason === "max_output_tokens" ? "max_output" : "complete";
    case "failed":
    case "cancelled":
      return "error";
    default:
      return "complete";
  }
}
function joinUrl(origin, path) {
  return origin.endsWith("/") ? origin.slice(0, -1) + path : origin + path;
}
function buildBody2(model, c, opts) {
  const body = {
    model: model.id,
    input: toResponsesInput(c),
    stream: true
  };
  if (c.system !== void 0 && c.system !== "") {
    body.instructions = c.system;
  }
  const tools = toResponsesTools(c.tools);
  if (tools !== void 0) {
    body.tools = tools;
    if (opts.toolChoice !== void 0) {
      body.tool_choice = opts.toolChoice;
    }
  }
  const ceiling = opts.maxOutputTokens ?? model.maxOutputTokens;
  if (ceiling > 0) {
    body.max_output_tokens = ceiling;
  }
  if (opts.temperature !== void 0) {
    body.temperature = opts.temperature;
  }
  if (opts.topP !== void 0) {
    body.top_p = opts.topP;
  }
  if (model.reasoning && opts.thinking !== void 0 && opts.thinking !== "off") {
    body.reasoning = { effort: reasoningEffort(opts.thinking) };
  }
  return body;
}
async function postResponses(url, apiKey, body, provider, signal) {
  let response;
  try {
    response = await fetch(url, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: `Bearer ${apiKey}`
      },
      body: JSON.stringify(body),
      ...signal !== void 0 ? { signal } : {}
    });
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "the Responses request was cancelled before a reply arrived", {
        provider,
        cause
      });
    }
    throw gatewayError("transport", "could not reach the OpenAI Responses endpoint", { provider, cause });
  }
  if (!response.ok) {
    const detail = await describeFailure(response);
    const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
    throw gatewayError(kind, `OpenAI Responses replied with HTTP ${response.status}${detail}`, {
      provider,
      status: response.status
    });
  }
  if (response.body === null) {
    throw gatewayError("transport", "OpenAI Responses returned an empty body", {
      provider,
      status: response.status
    });
  }
  return response;
}
async function describeFailure(response) {
  try {
    const text = await response.text();
    return text === "" ? "" : ` \u2014 ${text.slice(0, 500)}`;
  } catch {
    return "";
  }
}
function extractUsage(response) {
  const usage = response.usage;
  if (!isObject3(usage)) {
    return void 0;
  }
  const inputTokens = readNumber2(usage, "input_tokens") ?? 0;
  const outputTokens = readNumber2(usage, "output_tokens") ?? 0;
  const result = { inputTokens, outputTokens };
  const inputDetails = usage.input_tokens_details;
  if (isObject3(inputDetails)) {
    const cached = readNumber2(inputDetails, "cached_tokens");
    if (cached !== void 0) {
      result.cacheReadTokens = cached;
    }
  }
  return result;
}
function* translateEvent(payload, calls) {
  const type = readString2(payload, "type");
  switch (type) {
    case "response.output_text.delta": {
      const delta = readString2(payload, "delta");
      if (delta !== void 0 && delta !== "") {
        yield { kind: "text", delta };
      }
      return;
    }
    case "response.reasoning_summary_text.delta":
    case "response.reasoning_text.delta": {
      const delta = readString2(payload, "delta");
      if (delta !== void 0 && delta !== "") {
        yield { kind: "thinking", delta };
      }
      return;
    }
    case "response.output_item.added": {
      yield* announceItem(payload, calls);
      return;
    }
    case "response.function_call_arguments.delta": {
      const itemId = readString2(payload, "item_id");
      const delta = readString2(payload, "delta");
      if (itemId === void 0 || delta === void 0 || delta === "") {
        return;
      }
      const id = calls.byItem.get(itemId) ?? itemId;
      yield { kind: "tool_call_delta", id, argsDelta: delta };
      return;
    }
    case "response.completed":
    case "response.incomplete":
    case "response.failed": {
      yield* finishResponse(payload);
      return;
    }
    default:
      return;
  }
}
function* announceItem(payload, calls) {
  const item = payload.item;
  if (!isObject3(item)) {
    return;
  }
  if (readString2(item, "type") !== "function_call") {
    return;
  }
  const name = readString2(item, "name") ?? "";
  const callId = readString2(item, "call_id") ?? readString2(item, "id") ?? `call_${calls.byItem.size}`;
  const itemId = readString2(item, "id");
  if (itemId !== void 0) {
    calls.byItem.set(itemId, callId);
  }
  yield { kind: "tool_call_start", id: callId, name };
}
function* finishResponse(payload) {
  const response = isObject3(payload.response) ? payload.response : payload;
  const usage = extractUsage(response);
  if (usage !== void 0) {
    yield { kind: "usage", usage };
  }
  const status = readString2(response, "status");
  const incomplete = isObject3(response.incomplete_details) ? readString2(response.incomplete_details, "reason") : void 0;
  yield { kind: "stop", stop: mapStatus(status, incomplete) };
}
function createopenairesponsesConnector(deps2) {
  const { resolveSecret: resolveSecret2 } = deps2;
  const stream2 = (model, conversation, options) => {
    return channelOf(async function* run() {
      const provider = model.provider;
      const collected = [];
      try {
        const apiKey = await resolveSecret2(provider, options.apiKey);
        if (apiKey === void 0 || apiKey === "") {
          throw gatewayError("auth", "no OpenAI API key was found for the Responses endpoint", { provider });
        }
        const origin = model.baseUrl ?? DEFAULT_ORIGIN;
        const url = joinUrl(origin, "/responses");
        const body = buildBody2(model, conversation, options);
        const response = await postResponses(url, apiKey, body, provider, options.signal);
        const wire = response.body;
        const calls = { byItem: /* @__PURE__ */ new Map() };
        for await (const sse of sseEvents(wire, options.signal)) {
          const raw = sse.data;
          if (raw === "" || raw === "[DONE]") {
            continue;
          }
          let payload;
          try {
            payload = JSON.parse(raw);
          } catch (cause) {
            throw gatewayError("parse", "could not parse an OpenAI Responses stream event", {
              provider,
              cause
            });
          }
          if (!isObject3(payload)) {
            continue;
          }
          for (const emission of translateEvent(payload, calls)) {
            collected.push(emission);
            yield emission;
          }
        }
        yield { kind: "done", reply: foldReply(model.id, collected) };
      } catch (cause) {
        const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "the OpenAI Responses stream failed", { provider, cause });
        yield { kind: "error", error };
      }
    });
  };
  const complete2 = (model, conversation, options) => {
    return collectReply(stream2(model, conversation, options));
  };
  return {
    id: "openai-responses",
    api: "openai-responses",
    stream: stream2,
    complete: complete2
  };
}

// src/llmgateway/streaming/ndjson.ts
async function* ndjsonLines(body, signal) {
  if (signal?.aborted) {
    throw gatewayError("aborted", "request aborted before reading the ndjson stream");
  }
  const reader = body.getReader();
  const decoder = new TextDecoder("utf-8");
  let pending = "";
  const onAbort = () => {
    void reader.cancel().catch(() => {
    });
  };
  signal?.addEventListener("abort", onAbort, { once: true });
  const parseLine = (raw) => {
    const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
    const trimmed = line.trim();
    if (trimmed === "") {
      return void 0;
    }
    try {
      return JSON.parse(trimmed);
    } catch (cause) {
      throw gatewayError("parse", "failed to parse an ndjson line as JSON", { cause });
    }
  };
  try {
    for (; ; ) {
      if (signal?.aborted) {
        throw gatewayError("aborted", "request aborted while reading the ndjson stream");
      }
      const { value, done } = await reader.read();
      if (done) {
        break;
      }
      pending += decoder.decode(value, { stream: true });
      let nl = pending.indexOf("\n");
      while (nl !== -1) {
        const raw = pending.slice(0, nl);
        pending = pending.slice(nl + 1);
        const parsed = parseLine(raw);
        if (parsed !== void 0) {
          yield parsed;
        }
        nl = pending.indexOf("\n");
      }
    }
    pending += decoder.decode();
    const tail = parseLine(pending);
    if (tail !== void 0) {
      yield tail;
    }
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "request aborted while reading the ndjson stream", { cause });
    }
    if (cause instanceof Error && cause.name === "GatewayError") {
      throw cause;
    }
    throw gatewayError("transport", "failed reading the ndjson stream", { cause });
  } finally {
    signal?.removeEventListener("abort", onAbort);
    reader.releaseLock();
  }
}

// src/llmgateway/connectors/google.ts
var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com";
var PROVIDER = "google";
function isObject4(value) {
  return typeof value === "object" && value !== null;
}
function readString3(o, key) {
  const v = o[key];
  return typeof v === "string" ? v : void 0;
}
function readNumber3(o, key) {
  const v = o[key];
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function geminiThinkingBudget(level, maxOutput) {
  const fraction = {
    low: 0.2,
    medium: 0.4,
    high: 0.6,
    max: 0.8
  };
  const budget = Math.floor(maxOutput * fraction[level]);
  return Math.max(1024, budget);
}
function buildGenerationConfig(model, opts) {
  const maxOutputTokens = opts.maxOutputTokens ?? model.maxOutputTokens;
  const config = { maxOutputTokens };
  if (opts.temperature !== void 0) {
    config.temperature = opts.temperature;
  }
  if (opts.topP !== void 0) {
    config.topP = opts.topP;
  }
  if (model.reasoning && opts.thinking !== void 0 && opts.thinking !== "off") {
    config.thinkingConfig = {
      includeThoughts: true,
      thinkingBudget: geminiThinkingBudget(opts.thinking, maxOutputTokens)
    };
  }
  return config;
}
function buildRequestBody(model, conversation, opts) {
  const body = {
    contents: toGoogleContents(conversation),
    generationConfig: buildGenerationConfig(model, opts)
  };
  const systemInstruction = toGoogleSystemInstruction(conversation);
  if (systemInstruction !== void 0) {
    body.systemInstruction = systemInstruction;
  }
  const tools = toGoogleTools(conversation.tools);
  if (tools !== void 0) {
    body.tools = tools;
  }
  return body;
}
function streamEndpoint(model) {
  const base = (model.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/u, "");
  return `${base}/v1beta/models/${model.id}:streamGenerateContent?alt=sse`;
}
async function openStream(url, apiKey, body, signal) {
  let response;
  try {
    response = await fetch(url, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-goog-api-key": apiKey
      },
      body: JSON.stringify(body),
      ...signal !== void 0 ? { signal } : {}
    });
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "the Gemini request was aborted before a response arrived", {
        provider: PROVIDER,
        cause
      });
    }
    throw gatewayError("transport", "could not reach the Gemini endpoint", {
      provider: PROVIDER,
      cause
    });
  }
  if (!response.ok) {
    const detail = await readErrorDetail(response);
    const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
    throw gatewayError(kind, `Gemini replied with HTTP ${response.status}${detail}`, {
      provider: PROVIDER,
      status: response.status
    });
  }
  if (response.body === null) {
    throw gatewayError("transport", "the Gemini response carried no body to stream", {
      provider: PROVIDER,
      status: response.status
    });
  }
  return response;
}
async function readErrorDetail(response) {
  try {
    const text = await response.text();
    return text === "" ? "" : `: ${text.slice(0, 500)}`;
  } catch {
    return "";
  }
}
function mapFinishReason2(reason) {
  switch (reason) {
    case "STOP":
      return "complete";
    case "MAX_TOKENS":
      return "max_output";
    case "SAFETY":
    case "RECITATION":
    case "BLOCKLIST":
    case "PROHIBITED_CONTENT":
    case "SPII":
      return "refusal";
    case void 0:
    case "":
    case "FINISH_REASON_UNSPECIFIED":
    case "OTHER":
      return void 0;
    default:
      return "complete";
  }
}
function readUsage2(frame) {
  const meta = frame.usageMetadata;
  if (!isObject4(meta)) {
    return void 0;
  }
  const inputTokens = readNumber3(meta, "promptTokenCount") ?? 0;
  const outputTokens = readNumber3(meta, "candidatesTokenCount") ?? 0;
  const usage = { inputTokens, outputTokens };
  const cachedRead = readNumber3(meta, "cachedContentTokenCount");
  if (cachedRead !== void 0) {
    usage.cacheReadTokens = cachedRead;
  }
  return usage;
}
function* emitFromParts(parts, tracker) {
  for (const part of parts) {
    if (!isObject4(part)) {
      continue;
    }
    const text = readString3(part, "text");
    if (text !== void 0 && text !== "") {
      if (part.thought === true) {
        yield { kind: "thinking", delta: text };
      } else {
        yield { kind: "text", delta: text };
      }
      continue;
    }
    const functionCall = part.functionCall;
    if (isObject4(functionCall)) {
      const name = readString3(functionCall, "name") ?? "";
      const id = `gemini_call_${tracker.next}`;
      tracker.next += 1;
      yield { kind: "tool_call_start", id, name };
      const args = "args" in functionCall ? functionCall.args : {};
      yield { kind: "tool_call_delta", id, argsDelta: serializeArgs(args) };
    }
  }
}
function serializeArgs(args) {
  if (typeof args === "string") {
    return args;
  }
  try {
    return JSON.stringify(args ?? {});
  } catch {
    return "{}";
  }
}
function* emitFromFrame(frame, tracker) {
  const usage = readUsage2(frame);
  if (usage !== void 0) {
    yield { kind: "usage", usage };
  }
  const candidates = frame.candidates;
  if (!Array.isArray(candidates) || candidates.length === 0) {
    return;
  }
  const candidate = candidates[0];
  if (!isObject4(candidate)) {
    return;
  }
  const content = candidate.content;
  if (isObject4(content) && Array.isArray(content.parts)) {
    yield* emitFromParts(content.parts, tracker);
  }
  const stop = mapFinishReason2(readString3(candidate, "finishReason"));
  if (stop !== void 0) {
    yield { kind: "stop", stop };
  }
}
function creategoogleConnector(deps2) {
  const { resolveSecret: resolveSecret2 } = deps2;
  return {
    id: "google-generative",
    api: "google-generative",
    stream(model, conversation, options) {
      return channelOf(async function* run() {
        const collected = [];
        try {
          const apiKey = await resolveSecret2(PROVIDER, options.apiKey);
          if (apiKey === void 0 || apiKey === "") {
            throw gatewayError("auth", "no Gemini API key was found (set GEMINI_API_KEY or pass apiKey)", {
              provider: PROVIDER
            });
          }
          const url = streamEndpoint(model);
          const body = buildRequestBody(model, conversation, options);
          const response = await openStream(url, apiKey, body, options.signal);
          const wire = response.body;
          const tracker = { next: 0 };
          for await (const event of sseEvents(wire, options.signal)) {
            const payload = event.data;
            if (payload === "" || payload === "[DONE]") {
              continue;
            }
            let frame;
            try {
              frame = JSON.parse(payload);
            } catch (cause) {
              throw gatewayError("parse", "could not parse a Gemini stream frame", {
                provider: PROVIDER,
                cause
              });
            }
            if (!isObject4(frame)) {
              continue;
            }
            for (const emission of emitFromFrame(frame, tracker)) {
              collected.push(emission);
              yield emission;
            }
          }
          const reply = foldReply(model.id, collected);
          yield { kind: "done", reply };
        } catch (cause) {
          const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "the Gemini stream failed", { provider: PROVIDER, cause });
          yield { kind: "error", error };
        }
      });
    },
    complete(model, conversation, options) {
      return collectReply(this.stream(model, conversation, options));
    }
  };
}

// src/llmgateway/connectors/google-vertex.ts
var PROVIDER2 = "google-vertex";
var API_KIND = "google-vertex";
var DEFAULT_LOCATION = "us-central1";
var defaultBaseUrl = (location) => `https://${location}-aiplatform.googleapis.com`;
function envValue(name) {
  const raw = process.env[name];
  if (raw === void 0) {
    return void 0;
  }
  const trimmed = raw.trim();
  return trimmed.length > 0 ? trimmed : void 0;
}
function firstEnv(names) {
  for (const name of names) {
    const value = envValue(name);
    if (value !== void 0) {
      return value;
    }
  }
  return void 0;
}
function resolveTarget(model) {
  const project = firstEnv(["GOOGLE_VERTEX_PROJECT", "GOOGLE_CLOUD_PROJECT"]);
  if (project === void 0) {
    throw gatewayError(
      "auth",
      "no Vertex project found; set GOOGLE_VERTEX_PROJECT or GOOGLE_CLOUD_PROJECT",
      { provider: PROVIDER2 }
    );
  }
  const location = firstEnv(["GOOGLE_VERTEX_LOCATION", "CLOUD_ML_REGION"]) ?? DEFAULT_LOCATION;
  const host = (model.baseUrl ?? defaultBaseUrl(location)).replace(/\/+$/, "");
  const url = `${host}/v1/projects/${encodeURIComponent(project)}/locations/${encodeURIComponent(location)}/publishers/google/models/${encodeURIComponent(model.id)}:streamGenerateContent?alt=sse`;
  return { project, location, url };
}
function toFunctionCallingConfig(choice) {
  switch (choice) {
    case "auto":
      return { functionCallingConfig: { mode: "AUTO" } };
    case "required":
      return { functionCallingConfig: { mode: "ANY" } };
    case "none":
      return { functionCallingConfig: { mode: "NONE" } };
    default:
      return void 0;
  }
}
function thinkingBudget(level, maxOutput) {
  if (level === void 0 || level === "off") {
    return void 0;
  }
  const fraction = {
    low: 0.2,
    medium: 0.4,
    high: 0.6,
    max: 0.8
  };
  return Math.max(0, Math.floor(maxOutput * fraction[level]));
}
function buildBody3(model, c, opts) {
  const body = {
    contents: toGoogleContents(c)
  };
  const systemInstruction = toGoogleSystemInstruction(c);
  if (systemInstruction !== void 0) {
    body.systemInstruction = systemInstruction;
  }
  const tools = toGoogleTools(c.tools);
  if (tools !== void 0) {
    body.tools = tools;
    const toolConfig = toFunctionCallingConfig(opts.toolChoice);
    if (toolConfig !== void 0) {
      body.toolConfig = toolConfig;
    }
  }
  const generationConfig = {};
  const maxTokens = opts.maxOutputTokens ?? model.maxOutputTokens;
  if (maxTokens > 0) {
    generationConfig.maxOutputTokens = maxTokens;
  }
  if (opts.temperature !== void 0) {
    generationConfig.temperature = opts.temperature;
  }
  if (opts.topP !== void 0) {
    generationConfig.topP = opts.topP;
  }
  if (model.reasoning) {
    const budget = thinkingBudget(opts.thinking, maxTokens);
    if (budget !== void 0) {
      generationConfig.thinkingConfig = { thinkingBudget: budget };
    }
  }
  if (Object.keys(generationConfig).length > 0) {
    body.generationConfig = generationConfig;
  }
  return body;
}
function isObject5(v) {
  return typeof v === "object" && v !== null;
}
function readString4(o, key) {
  const v = o[key];
  return typeof v === "string" ? v : void 0;
}
function readNumber4(o, key) {
  const v = o[key];
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function readBool(o, key) {
  return o[key] === true;
}
function mapFinishReason3(reason) {
  switch (reason) {
    case void 0:
    case "":
    case "FINISH_REASON_UNSPECIFIED":
      return void 0;
    case "STOP":
      return "complete";
    case "MAX_TOKENS":
      return "max_output";
    case "SAFETY":
    case "RECITATION":
    case "BLOCKLIST":
    case "PROHIBITED_CONTENT":
    case "SPII":
      return "refusal";
    default:
      return "complete";
  }
}
function readUsage3(chunk) {
  const meta = chunk.usageMetadata;
  if (!isObject5(meta)) {
    return void 0;
  }
  const inputTokens = readNumber4(meta, "promptTokenCount") ?? 0;
  const candidate = readNumber4(meta, "candidatesTokenCount") ?? 0;
  const thoughts = readNumber4(meta, "thoughtsTokenCount") ?? 0;
  const result = {
    inputTokens,
    outputTokens: candidate + thoughts
  };
  const cached = readNumber4(meta, "cachedContentTokenCount");
  if (cached !== void 0) {
    result.cacheReadTokens = cached;
  }
  return result;
}
function* emitFromChunk2(chunk, tracker) {
  const usage = readUsage3(chunk);
  if (usage !== void 0) {
    yield { kind: "usage", usage };
  }
  const candidates = chunk.candidates;
  if (!Array.isArray(candidates) || candidates.length === 0) {
    return;
  }
  const candidate = candidates[0];
  if (!isObject5(candidate)) {
    return;
  }
  const content = candidate.content;
  if (isObject5(content)) {
    const parts = content.parts;
    if (Array.isArray(parts)) {
      for (const part of parts) {
        yield* emitFromPart(part, tracker);
      }
    }
  }
  const finish = mapFinishReason3(readString4(candidate, "finishReason"));
  if (finish !== void 0) {
    yield { kind: "stop", stop: finish };
  }
}
function* emitFromPart(part, tracker) {
  if (!isObject5(part)) {
    return;
  }
  const functionCall = part.functionCall;
  if (isObject5(functionCall)) {
    const name = readString4(functionCall, "name") ?? "";
    const id = `vertex_call_${tracker.next}`;
    tracker.next += 1;
    yield { kind: "tool_call_start", id, name };
    const args = functionCall.args;
    const argsDelta = serializeArgs2(args);
    if (argsDelta !== "") {
      yield { kind: "tool_call_delta", id, argsDelta };
    }
    return;
  }
  const text = readString4(part, "text");
  if (text !== void 0 && text !== "") {
    if (readBool(part, "thought")) {
      yield { kind: "thinking", delta: text };
    } else {
      yield { kind: "text", delta: text };
    }
  }
}
function serializeArgs2(args) {
  if (args === void 0 || args === null) {
    return "";
  }
  if (typeof args === "string") {
    return args;
  }
  try {
    return JSON.stringify(args);
  } catch {
    return "";
  }
}
async function postStream(url, token, body, signal) {
  let response;
  try {
    response = await fetch(url, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: `Bearer ${token}`
      },
      body: JSON.stringify(body),
      ...signal !== void 0 ? { signal } : {}
    });
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "Vertex request was aborted before a response arrived", {
        provider: PROVIDER2,
        cause
      });
    }
    throw gatewayError("transport", "network request to Vertex AI failed", {
      provider: PROVIDER2,
      cause
    });
  }
  if (!response.ok) {
    const detail = await readErrorBody3(response);
    const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
    throw gatewayError(kind, `Vertex AI returned HTTP ${response.status}${detail}`, {
      provider: PROVIDER2,
      status: response.status
    });
  }
  if (response.body === null) {
    throw gatewayError("transport", "Vertex AI response carried no body", {
      provider: PROVIDER2,
      status: response.status
    });
  }
  return response;
}
async function readErrorBody3(response) {
  try {
    const text = await response.text();
    return text === "" ? "" : `: ${text.slice(0, 500)}`;
  } catch {
    return "";
  }
}
function creategooglevertexConnector(deps2) {
  const { resolveSecret: resolveSecret2 } = deps2;
  function stream2(model, conversation, options) {
    return channelOf(async function* run() {
      const collected = [];
      try {
        const token = await resolveSecret2(PROVIDER2, options.apiKey);
        if (token === void 0 || token === "") {
          throw gatewayError(
            "auth",
            "no Google access token available for Vertex AI; provide one via apiKey or the environment",
            { provider: PROVIDER2 }
          );
        }
        const target = resolveTarget(model);
        const body = buildBody3(model, conversation, options);
        const response = await postStream(target.url, token, body, options.signal);
        const sseBody = response.body;
        const tracker = { next: 0 };
        for await (const event of sseEvents(sseBody, options.signal)) {
          const payload = event.data;
          if (payload === "" || payload === "[DONE]") {
            continue;
          }
          let chunk;
          try {
            chunk = JSON.parse(payload);
          } catch (cause) {
            throw gatewayError("parse", "failed to parse a Vertex AI stream chunk", {
              provider: PROVIDER2,
              cause
            });
          }
          if (!isObject5(chunk)) {
            continue;
          }
          for (const emission of emitFromChunk2(chunk, tracker)) {
            collected.push(emission);
            yield emission;
          }
        }
        yield { kind: "done", reply: foldReply(model.id, collected) };
      } catch (cause) {
        const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "Vertex AI stream failed", { provider: PROVIDER2, cause });
        yield { kind: "error", error };
      }
    });
  }
  function complete2(model, conversation, options) {
    return collectReply(stream2(model, conversation, options));
  }
  return {
    id: "google-vertex",
    api: API_KIND,
    stream: stream2,
    complete: complete2
  };
}

// src/llmgateway/connectors/bedrock.ts
var PROVIDER3 = "amazon";
var DIALECT = "amazon-bedrock";
var FALLBACK_REGION = "us-east-1";
function discoverRegion() {
  const env = (typeof process !== "undefined" ? process.env : void 0) ?? {};
  const region = env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? env.AWS_BEDROCK_REGION;
  const trimmed = region?.trim();
  return trimmed !== void 0 && trimmed !== "" ? trimmed : void 0;
}
function converseStreamUrl(model, region) {
  const base = model.baseUrl !== void 0 && model.baseUrl !== "" ? model.baseUrl.replace(/\/+$/, "") : `https://bedrock-runtime.${region}.amazonaws.com`;
  return `${base}/model/${encodeURIComponent(model.id)}/converse-stream`;
}
function stringifyOutput(output) {
  if (typeof output === "string") {
    return output;
  }
  try {
    return JSON.stringify(output);
  } catch {
    return String(output);
  }
}
function toJsonInput(input) {
  if (input !== null && typeof input === "object" && !Array.isArray(input)) {
    return input;
  }
  if (typeof input === "string") {
    try {
      const parsed = JSON.parse(input);
      if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
        return parsed;
      }
    } catch {
    }
  }
  return { value: input };
}
function toContentBlocks(block) {
  switch (block.kind) {
    case "text":
      return [{ text: block.text }];
    case "thinking":
      return [
        {
          reasoningContent: {
            reasoningText: block.signature !== void 0 ? { text: block.text, signature: block.signature } : { text: block.text }
          }
        }
      ];
    case "image": {
      const slash = block.mediaType.indexOf("/");
      const format = slash >= 0 ? block.mediaType.slice(slash + 1) : block.mediaType;
      return [
        {
          image: {
            format,
            source: { bytes: block.dataBase64 }
          }
        }
      ];
    }
    case "tool_call":
      return [
        {
          toolUse: {
            toolUseId: block.id,
            name: block.name,
            input: toJsonInput(block.input)
          }
        }
      ];
    case "tool_result":
      return [
        {
          toolResult: {
            toolUseId: block.callId,
            content: [{ text: stringifyOutput(block.output) }],
            ...block.isError === true ? { status: "error" } : {}
          }
        }
      ];
    case "command": {
      const argv = block.args && block.args.length > 0 ? ` ${block.args.join(" ")}` : "";
      return [{ text: `$ ${block.command}${argv}` }];
    }
  }
}
function converseRole(turn) {
  return turn.role === "assistant" ? "assistant" : "user";
}
function toConverseMessages(conversation) {
  const messages = [];
  for (const turn of conversation.turns) {
    const content = [];
    for (const block of turn.blocks) {
      for (const wire of toContentBlocks(block)) {
        content.push(wire);
      }
    }
    if (content.length > 0) {
      messages.push({ role: converseRole(turn), content });
    }
  }
  return messages;
}
function toConverseSystem(conversation) {
  if (conversation.system === void 0 || conversation.system === "") {
    return void 0;
  }
  return [{ text: conversation.system }];
}
function toConverseTools(tools) {
  if (tools === void 0 || tools.length === 0) {
    return void 0;
  }
  return tools.map((tool) => ({
    toolSpec: {
      name: tool.name,
      description: tool.description,
      inputSchema: { json: tool.parameters }
    }
  }));
}
function toConverseToolChoice(options) {
  switch (options.toolChoice) {
    case "auto":
      return { auto: {} };
    case "required":
      return { any: {} };
    case "none":
    case void 0:
      return void 0;
  }
}
function toInferenceConfig(model, options) {
  const config = {
    maxTokens: options.maxOutputTokens ?? model.maxOutputTokens
  };
  if (options.temperature !== void 0) {
    config.temperature = options.temperature;
  }
  if (options.topP !== void 0) {
    config.topP = options.topP;
  }
  return config;
}
function buildConverseBody(model, conversation, options) {
  const body = {
    messages: toConverseMessages(conversation),
    inferenceConfig: toInferenceConfig(model, options)
  };
  const system = toConverseSystem(conversation);
  if (system !== void 0) {
    body.system = system;
  }
  const tools = toConverseTools(conversation.tools);
  if (tools !== void 0) {
    const toolConfig = { tools };
    const choice = toConverseToolChoice(options);
    if (choice !== void 0) {
      toolConfig.toolChoice = choice;
    }
    body.toolConfig = toolConfig;
  }
  return body;
}
async function requireSignedTransport(resolveSecret2, options) {
  const credential = await resolveSecret2(PROVIDER3, options.apiKey);
  const region = discoverRegion() ?? FALLBACK_REGION;
  void credential;
  void region;
  throw gatewayError(
    "unsupported",
    "bedrock streaming requires SigV4 (todo)",
    { provider: PROVIDER3 }
  );
}
function createbedrockConnector(deps2) {
  const { resolveSecret: resolveSecret2 } = deps2;
  return {
    id: "bedrock",
    api: DIALECT,
    stream(model, conversation, options) {
      return channelOf(async function* () {
        const region = discoverRegion() ?? FALLBACK_REGION;
        const url = converseStreamUrl(model, region);
        const body = buildConverseBody(model, conversation, options);
        void url;
        void body;
        await requireSignedTransport(resolveSecret2, options);
      });
    },
    complete(model, conversation, options) {
      return collectReply(this.stream(model, conversation, options));
    }
  };
}

// src/llmgateway/connectors/azure-openai.ts
var AZURE_API_VERSION = "2024-08-01-preview";
function deploymentName(model) {
  const id = model.id.trim();
  if (id === "") {
    throw gatewayError(
      "unsupported",
      "azure card is missing an id to use as the deployment name",
      { provider: model.provider }
    );
  }
  return id;
}
function endpointFor3(model) {
  const base = model.baseUrl?.replace(/\/+$/, "");
  if (base === void 0 || base === "") {
    throw gatewayError(
      "unsupported",
      "azure card has no baseUrl; set it to the resource endpoint, e.g. https://<resource>.openai.azure.com",
      { provider: model.provider }
    );
  }
  const deployment = encodeURIComponent(deploymentName(model));
  const query = new URLSearchParams({ "api-version": AZURE_API_VERSION });
  return `${base}/openai/deployments/${deployment}/chat/completions?${query.toString()}`;
}
var AzureOpenAIConnector = class {
  constructor(deps2) {
    this.deps = deps2;
  }
  deps;
  id = "azure-openai";
  api = "azure-openai";
  stream(model, conversation, options) {
    const deps2 = this.deps;
    return channelOf(async function* run() {
      let cfg;
      try {
        cfg = await resolveConfig2(deps2, model, options);
      } catch (cause) {
        const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "could not prepare the Azure OpenAI request", {
          provider: model.provider,
          cause
        });
        yield { kind: "error", error };
        return;
      }
      yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
    });
  }
  async complete(model, conversation, options) {
    return collectReply(this.stream(model, conversation, options));
  }
};
async function resolveConfig2(deps2, model, options) {
  const url = endpointFor3(model);
  const key = await deps2.resolveSecret(model.provider, options.apiKey);
  if (key === void 0 || key === "") {
    throw gatewayError(
      "auth",
      "no Azure OpenAI credential found \u2014 set AZURE_OPENAI_API_KEY (or AZURE_API_KEY), or pass options.apiKey",
      { provider: model.provider }
    );
  }
  return {
    url,
    // Azure authenticates with an api-key header, not an OpenAI bearer token.
    headers: { "api-key": key }
  };
}
function createazureopenaiConnector(deps2) {
  return new AzureOpenAIConnector(deps2);
}

// src/llmgateway/connectors/nvidia.ts
var DEFAULT_NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
function endpointFor4(model) {
  const base = (model.baseUrl ?? DEFAULT_NVIDIA_BASE_URL).replace(/\/+$/, "");
  return `${base}/chat/completions`;
}
function bodyOverridesFor(model) {
  if (!model.reasoning) {
    return void 0;
  }
  return { chat_template_kwargs: { thinking: true } };
}
var NvidiaConnector = class {
  constructor(deps2) {
    this.deps = deps2;
  }
  deps;
  id = "nvidia";
  api = "nvidia-openai-compatible";
  stream(model, conversation, options) {
    const deps2 = this.deps;
    return channelOf(async function* run() {
      let cfg;
      try {
        cfg = await assembleConfig(deps2, model, options);
      } catch (cause) {
        const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "unable to prepare the NVIDIA request", {
          provider: model.provider,
          cause
        });
        yield { kind: "error", error };
        return;
      }
      yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
    });
  }
  async complete(model, conversation, options) {
    return collectReply(this.stream(model, conversation, options));
  }
};
async function assembleConfig(deps2, model, options) {
  const key = await deps2.resolveSecret(model.provider, options.apiKey);
  if (key === void 0 || key === "") {
    throw gatewayError(
      "auth",
      "missing NVIDIA credential \u2014 set NVIDIA_API_KEY or pass options.apiKey",
      { provider: model.provider }
    );
  }
  const bodyExtra = bodyOverridesFor(model);
  return {
    url: endpointFor4(model),
    headers: { authorization: `Bearer ${key}` },
    ...bodyExtra ? { bodyExtra } : {}
  };
}
function createnvidiaConnector(deps2) {
  return new NvidiaConnector(deps2);
}

// src/llmgateway/connectors/kimi.ts
var DEFAULT_MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
function endpointFor5(model) {
  const base = (model.baseUrl ?? DEFAULT_MOONSHOT_BASE_URL).replace(/\/+$/, "");
  return `${base}/chat/completions`;
}
var KimiConnector = class {
  constructor(deps2) {
    this.deps = deps2;
  }
  deps;
  id = "kimi";
  api = "kimi-openai-compatible";
  stream(model, conversation, options) {
    const deps2 = this.deps;
    return channelOf(async function* run() {
      let cfg;
      try {
        cfg = await resolveConfig3(deps2, model, options);
      } catch (cause) {
        const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "could not prepare the Kimi request", {
          provider: model.provider,
          cause
        });
        yield { kind: "error", error };
        return;
      }
      yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
    });
  }
  async complete(model, conversation, options) {
    return collectReply(this.stream(model, conversation, options));
  }
};
async function resolveConfig3(deps2, model, options) {
  const key = await deps2.resolveSecret(model.provider, options.apiKey);
  if (key === void 0 || key === "") {
    throw gatewayError(
      "auth",
      "no Kimi credential found \u2014 set MOONSHOT_API_KEY (or KIMI_API_KEY), or pass options.apiKey",
      { provider: model.provider }
    );
  }
  return {
    url: endpointFor5(model),
    headers: { authorization: `Bearer ${key}` }
  };
}
function createkimiConnector(deps2) {
  return new KimiConnector(deps2);
}

// src/llmgateway/connectors/ollama.ts
var DEFAULT_BASE_URL3 = "http://localhost:11434";
var PROVIDER4 = "ollama";
function isObject6(value) {
  return typeof value === "object" && value !== null;
}
function readString5(o, key) {
  const v = o[key];
  return typeof v === "string" ? v : void 0;
}
function readNumber5(o, key) {
  const v = o[key];
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function buildOptions(model, opts) {
  const options = {};
  const numPredict = opts.maxOutputTokens ?? model.maxOutputTokens;
  if (numPredict > 0) {
    options.num_predict = numPredict;
  }
  if (opts.temperature !== void 0) {
    options.temperature = opts.temperature;
  }
  if (opts.topP !== void 0) {
    options.top_p = opts.topP;
  }
  return Object.keys(options).length > 0 ? options : void 0;
}
function buildRequestBody2(model, conversation, opts) {
  const body = {
    model: model.id,
    messages: toOpenAIChatMessages(conversation),
    stream: true
  };
  const tools = toOpenAITools(conversation.tools);
  if (tools !== void 0) {
    body.tools = tools;
  }
  const options = buildOptions(model, opts);
  if (options !== void 0) {
    body.options = options;
  }
  return body;
}
function chatEndpoint(model) {
  const base = (model.baseUrl ?? DEFAULT_BASE_URL3).replace(/\/+$/u, "");
  return `${base}/api/chat`;
}
function buildHeaders(secret) {
  const headers = { "content-type": "application/json" };
  if (secret !== void 0 && secret !== "") {
    headers.authorization = `Bearer ${secret}`;
  }
  return headers;
}
async function openStream2(url, headers, body, signal) {
  let response;
  try {
    response = await fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
      ...signal !== void 0 ? { signal } : {}
    });
  } catch (cause) {
    if (signal?.aborted) {
      throw gatewayError("aborted", "the Ollama request was aborted before a response arrived", {
        provider: PROVIDER4,
        cause
      });
    }
    throw gatewayError("transport", "could not reach the Ollama daemon (is `ollama serve` running?)", {
      provider: PROVIDER4,
      cause
    });
  }
  if (!response.ok) {
    const detail = await readErrorDetail2(response);
    const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
    throw gatewayError(kind, `Ollama replied with HTTP ${response.status}${detail}`, {
      provider: PROVIDER4,
      status: response.status
    });
  }
  if (response.body === null) {
    throw gatewayError("transport", "the Ollama response carried no body to stream", {
      provider: PROVIDER4,
      status: response.status
    });
  }
  return response;
}
async function readErrorDetail2(response) {
  try {
    const text = await response.text();
    return text === "" ? "" : `: ${text.slice(0, 500)}`;
  } catch {
    return "";
  }
}
function mapDoneReason(reason) {
  switch (reason) {
    case "stop":
      return "complete";
    case "length":
      return "max_output";
    case void 0:
    case "":
      return void 0;
    default:
      return "complete";
  }
}
function readUsage4(line) {
  const inputTokens = readNumber5(line, "prompt_eval_count");
  const outputTokens = readNumber5(line, "eval_count");
  if (inputTokens === void 0 && outputTokens === void 0) {
    return void 0;
  }
  return {
    inputTokens: inputTokens ?? 0,
    outputTokens: outputTokens ?? 0
  };
}
function serializeArgs3(args) {
  if (typeof args === "string") {
    return args;
  }
  try {
    return JSON.stringify(args ?? {});
  } catch {
    return "{}";
  }
}
function* emitToolCalls(toolCalls, tracker) {
  for (const entry of toolCalls) {
    if (!isObject6(entry)) {
      continue;
    }
    const fn = entry.function;
    if (!isObject6(fn)) {
      continue;
    }
    const name = readString5(fn, "name") ?? "";
    const id = `ollama_call_${tracker.next}`;
    tracker.next += 1;
    yield { kind: "tool_call_start", id, name };
    const args = "arguments" in fn ? fn.arguments : {};
    yield { kind: "tool_call_delta", id, argsDelta: serializeArgs3(args) };
  }
}
function* emitFromLine(line, tracker) {
  const message = line.message;
  if (isObject6(message)) {
    const content = readString5(message, "content");
    if (content !== void 0 && content !== "") {
      yield { kind: "text", delta: content };
    }
    const toolCalls = message.tool_calls;
    if (Array.isArray(toolCalls)) {
      yield* emitToolCalls(toolCalls, tracker);
    }
  }
  if (line.done === true) {
    const usage = readUsage4(line);
    if (usage !== void 0) {
      yield { kind: "usage", usage };
    }
    const stop = mapDoneReason(readString5(line, "done_reason"));
    if (stop !== void 0) {
      yield { kind: "stop", stop };
    }
  }
}
function createollamaConnector(deps2) {
  const { resolveSecret: resolveSecret2 } = deps2;
  return {
    id: "ollama",
    api: "ollama",
    stream(model, conversation, options) {
      return channelOf(async function* run() {
        const collected = [];
        try {
          const secret = await resolveSecret2(PROVIDER4, options.apiKey);
          const url = chatEndpoint(model);
          const headers = buildHeaders(secret);
          const body = buildRequestBody2(model, conversation, options);
          const response = await openStream2(url, headers, body, options.signal);
          const wire = response.body;
          const tracker = { next: 0 };
          for await (const value of ndjsonLines(wire, options.signal)) {
            if (!isObject6(value)) {
              continue;
            }
            for (const emission of emitFromLine(value, tracker)) {
              collected.push(emission);
              yield emission;
            }
            if (value.done === true) {
              break;
            }
          }
          const reply = foldReply(model.id, collected);
          yield { kind: "done", reply };
        } catch (cause) {
          const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "the Ollama stream failed", { provider: PROVIDER4, cause });
          yield { kind: "error", error };
        }
      });
    },
    complete(model, conversation, options) {
      return collectReply(this.stream(model, conversation, options));
    }
  };
}

// src/llmgateway/connectors/mock.ts
var ECHO_CALL_ID = "mock_call_0";
var SCRIPTED_USAGE = {
  inputTokens: 8,
  outputTokens: 5
};
function scriptedDeltas() {
  return [
    { kind: "text", delta: "Hello" },
    { kind: "text", delta: " world" },
    { kind: "tool_call_start", id: ECHO_CALL_ID, name: "echo" },
    { kind: "tool_call_delta", id: ECHO_CALL_ID, argsDelta: '{"msg":"hi"}' },
    { kind: "usage", usage: { ...SCRIPTED_USAGE } },
    { kind: "stop", stop: "tool_calls" }
  ];
}
function finalDeltas() {
  return [
    { kind: "text", delta: "Hello world" },
    { kind: "usage", usage: { ...SCRIPTED_USAGE } },
    { kind: "stop", stop: "complete" }
  ];
}
function hasToolResult(conversation) {
  return conversation.turns.some((turn) => turn.blocks.some((block) => block.kind === "tool_result"));
}
function echoOffered(conversation) {
  return (conversation.tools ?? []).some((tool) => tool.name === "echo");
}
function pickDeltas(conversation) {
  return !hasToolResult(conversation) && echoOffered(conversation) ? scriptedDeltas() : finalDeltas();
}
var MockConnector = class {
  id = "mock";
  api = "mock";
  // The resolver is accepted for factory symmetry with the live connectors; the
  // mock dialect needs no credential, so it is intentionally not retained.
  constructor(_deps) {
  }
  stream(model, conversation, options) {
    const isAborted = () => options.signal?.aborted ?? false;
    const deltas = pickDeltas(conversation);
    return channelOf(async function* run() {
      if (isAborted()) {
        yield abortedDone(model.id);
        return;
      }
      for (const delta of deltas) {
        if (isAborted()) {
          yield abortedDone(model.id);
          return;
        }
        yield delta;
      }
      yield { kind: "done", reply: foldReply(model.id, deltas) };
    });
  }
  async complete(model, conversation, options) {
    return collectReply(this.stream(model, conversation, options));
  }
};
function abortedDone(modelId) {
  const reply = {
    role: "assistant",
    model: modelId,
    blocks: [],
    usage: { inputTokens: 0, outputTokens: 0 },
    stop: "aborted"
  };
  return { kind: "done", reply };
}
function createmockConnector(deps2) {
  return new MockConnector(deps2);
}

// src/llmgateway/connectors/index.ts
var deps = { resolveSecret };
var CONNECTOR_REGISTRY = {
  "anthropic-messages": createanthropicConnector(deps),
  "openai-completions": createopenaichatConnector(deps),
  "openai-responses": createopenairesponsesConnector(deps),
  "google-generative": creategoogleConnector(deps),
  "google-vertex": creategooglevertexConnector(deps),
  "amazon-bedrock": createbedrockConnector(deps),
  "azure-openai": createazureopenaiConnector(deps),
  "nvidia-openai-compatible": createnvidiaConnector(deps),
  "kimi-openai-compatible": createkimiConnector(deps),
  ollama: createollamaConnector(deps),
  mock: createmockConnector(deps)
};
function connectorForApi(api) {
  return CONNECTOR_REGISTRY[api];
}

// src/llmgateway/gateway.ts
var NO_OPTIONS = {};
function stream(modelId, conversation, options) {
  const card = getCard(modelId);
  if (card === void 0) {
    throw gatewayError("unsupported", `unknown model: ${modelId}`);
  }
  return streamWithCard(card, conversation, options);
}
function streamWithCard(card, conversation, options) {
  const connector = connectorForApi(card.api);
  return connector.stream(card, conversation, options ?? NO_OPTIONS);
}
function complete(modelId, conversation, options) {
  const card = getCard(modelId);
  if (card === void 0) {
    throw gatewayError("unsupported", `unknown model: ${modelId}`);
  }
  return completeWithCard(card, conversation, options);
}
function completeWithCard(card, conversation, options) {
  const connector = connectorForApi(card.api);
  return connector.complete(card, conversation, options ?? NO_OPTIONS);
}
export {
  CONNECTOR_REGISTRY,
  CardSelection,
  GatewayError,
  MODEL_CARDS,
  complete,
  completeWithCard,
  connectorForApi,
  estimateCost,
  gatewayError,
  getCard,
  models,
  stream,
  streamWithCard
};
