import { adapterErrorChunk, parseCompleteToolArgs, cancelBody, abortableSleep, isAbortError } from './chunk-6CIB2QCH.js';

// src/stream-lines.ts
async function* readSSELines(stream) {
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split("\n");
      buffer = lines.pop() ?? "";
      for (const line of lines) {
        if (!line.startsWith("data: ")) continue;
        const data = line.slice(6).trim();
        if (data) yield data;
      }
    }
  } finally {
    reader.releaseLock();
  }
}
async function* readNDJSONLines(stream) {
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split("\n");
      buffer = lines.pop() ?? "";
      for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed) yield trimmed;
      }
    }
  } finally {
    reader.releaseLock();
  }
}

// src/openai-stream.ts
var SUCCESS_FINISH_REASONS = /* @__PURE__ */ new Set(["stop", "tool_calls", "function_call"]);
async function* parseOpenAIStream(stream) {
  const pendingToolCalls = /* @__PURE__ */ new Map();
  let finishReason;
  const terminalError = (message, extra) => {
    const chunk = adapterErrorChunk(message);
    return {
      ...chunk,
      metadata: { ...chunk.metadata, ...extra, finishReason }
    };
  };
  const flushToolCalls = function* () {
    for (const [, toolCall] of pendingToolCalls) {
      const parsed = parseCompleteToolArgs(toolCall.args);
      if (!parsed.ok) {
        pendingToolCalls.clear();
        yield {
          type: "error",
          content: parsed.error.message,
          metadata: { error: parsed.error, finishReason }
        };
        return false;
      }
      yield {
        type: "tool_call",
        toolCall: { id: toolCall.id, name: toolCall.name, args: parsed.args }
      };
    }
    pendingToolCalls.clear();
    return true;
  };
  for await (const data of readSSELines(stream)) {
    if (data === "[DONE]") {
      if (finishReason !== void 0 && !SUCCESS_FINISH_REASONS.has(finishReason)) {
        pendingToolCalls.clear();
        yield terminalError(
          `OpenAI stream ended with non-success finish reason "${finishReason}".`,
          { finishReason }
        );
        return;
      }
      if (!(yield* flushToolCalls())) return;
      yield { type: "done" };
      return;
    }
    try {
      const event = JSON.parse(data);
      const delta = event.choices?.[0]?.delta;
      const candidateFinishReason = event.choices?.find(
        (choice) => typeof choice.finish_reason === "string"
      )?.finish_reason;
      if (typeof candidateFinishReason === "string") finishReason = candidateFinishReason;
      if (typeof delta?.content === "string") yield { type: "text", content: delta.content };
      if (Array.isArray(delta?.tool_calls)) {
        for (const toolCall of delta.tool_calls) {
          const index = toolCall.index ?? 0;
          const existing = pendingToolCalls.get(index);
          if (toolCall?.function?.name) {
            pendingToolCalls.set(index, {
              id: toolCall.id ?? existing?.id ?? `tool-${index}-${Date.now()}`,
              name: toolCall.function.name,
              args: (existing?.args ?? "") + (toolCall.function.arguments ?? "")
            });
          } else if (existing && toolCall?.function?.arguments) {
            existing.args += toolCall.function.arguments;
          }
        }
      }
      if (event.usage) {
        yield {
          type: "usage",
          usage: {
            promptTokens: event.usage.prompt_tokens ?? 0,
            completionTokens: event.usage.completion_tokens ?? 0,
            totalTokens: event.usage.total_tokens ?? 0
          }
        };
      }
    } catch {
    }
  }
  if (finishReason !== void 0 && !SUCCESS_FINISH_REASONS.has(finishReason)) {
    pendingToolCalls.clear();
    yield terminalError(
      `OpenAI stream ended with non-success finish reason "${finishReason}".`,
      { finishReason }
    );
    return;
  }
  if (!finishReason) {
    pendingToolCalls.clear();
    yield terminalError("OpenAI stream ended before a terminal marker.");
    return;
  }
  if (!(yield* flushToolCalls())) return;
  yield { type: "done" };
}

// src/utils.ts
function toProviderMessages(messages) {
  const knownToolCallIds = /* @__PURE__ */ new Set();
  const output = [];
  for (const message of messages) {
    if (message.role === "tool") {
      const id = message.toolCallId;
      if (!id || !knownToolCallIds.has(id)) continue;
      output.push({
        role: "tool",
        content: message.content,
        tool_call_id: id
      });
      continue;
    }
    if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
      for (const tc of message.toolCalls) knownToolCallIds.add(tc.id);
      output.push({
        role: "assistant",
        content: message.content || null,
        tool_calls: message.toolCalls.map((tc) => ({
          id: tc.id,
          type: "function",
          function: {
            name: tc.name,
            arguments: typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args ?? {})
          }
        }))
      });
      continue;
    }
    if (message.role === "assistant" && !message.content) continue;
    output.push({ role: message.role, content: message.content });
  }
  return output;
}
async function* parseAnthropicStream(stream) {
  const pendingToolCalls = /* @__PURE__ */ new Map();
  let sawMessageStop = false;
  const emitToolCall = function* (tc) {
    const parsed = parseCompleteToolArgs(tc.args);
    if (!parsed.ok) {
      yield {
        type: "error",
        content: parsed.error.message,
        metadata: { error: parsed.error }
      };
      return false;
    }
    yield {
      type: "tool_call",
      toolCall: { id: tc.id, name: tc.name, args: parsed.args }
    };
    return true;
  };
  for await (const data of readSSELines(stream)) {
    if (data === "[DONE]") continue;
    try {
      const event = JSON.parse(data);
      if (event.type === "error") {
        const msg = event.error?.message ?? event.error?.type ?? "Anthropic stream error";
        yield adapterErrorChunk(String(msg));
        return;
      }
      if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta?.text) {
        yield { type: "text", content: event.delta.text };
      } else if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta") {
        const index = event.index ?? 0;
        const existing = pendingToolCalls.get(index);
        if (existing) {
          existing.args += event.delta.partial_json ?? "";
        }
      } else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
        const index = event.index ?? 0;
        pendingToolCalls.set(index, {
          id: event.content_block.id ?? `tool-${index}`,
          name: event.content_block.name ?? "unknown",
          args: ""
        });
      } else if (event.type === "content_block_stop") {
        const index = event.index ?? 0;
        const tc = pendingToolCalls.get(index);
        if (tc) {
          if (!(yield* emitToolCall(tc))) {
            pendingToolCalls.clear();
            return;
          }
          pendingToolCalls.delete(index);
        }
      } else if (event.type === "message_delta" && event.usage) {
        yield {
          type: "usage",
          usage: {
            promptTokens: event.usage.input_tokens ?? 0,
            completionTokens: event.usage.output_tokens ?? 0,
            totalTokens: (event.usage.input_tokens ?? 0) + (event.usage.output_tokens ?? 0)
          }
        };
      } else if (event.type === "message_start" && event.message?.usage) {
        yield {
          type: "usage",
          usage: {
            promptTokens: event.message.usage.input_tokens ?? 0,
            completionTokens: 0,
            totalTokens: event.message.usage.input_tokens ?? 0
          }
        };
      } else if (event.type === "message_stop") {
        sawMessageStop = true;
        for (const [, tc] of pendingToolCalls) {
          if (!(yield* emitToolCall(tc))) {
            pendingToolCalls.clear();
            return;
          }
        }
        pendingToolCalls.clear();
        yield { type: "done" };
        return;
      }
    } catch {
    }
  }
  pendingToolCalls.clear();
  if (!sawMessageStop) {
    yield adapterErrorChunk("Anthropic stream ended before message_stop");
    return;
  }
  yield { type: "done" };
}
var GEMINI_SUCCESS_FINISH = /* @__PURE__ */ new Set([
  "STOP",
  "stop",
  "FINISH_REASON_STOP"
]);
async function* parseGeminiStream(stream) {
  let finishReason;
  for await (const data of readSSELines(stream)) {
    try {
      const event = JSON.parse(data);
      if (event.usageMetadata) {
        yield {
          type: "usage",
          usage: {
            promptTokens: event.usageMetadata.promptTokenCount ?? 0,
            completionTokens: event.usageMetadata.candidatesTokenCount ?? 0,
            totalTokens: event.usageMetadata.totalTokenCount ?? 0
          }
        };
      }
      const candidate = event.candidates?.[0];
      if (typeof candidate?.finishReason === "string") {
        finishReason = candidate.finishReason;
      }
      const parts = candidate?.content?.parts;
      if (!Array.isArray(parts)) continue;
      for (const part of parts) {
        if (typeof part.text === "string" && part.text) {
          yield { type: "text", content: part.text };
        } else if (part.functionCall) {
          const fc = part.functionCall;
          const args = typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args ?? {});
          const parsed = parseCompleteToolArgs(args);
          if (!parsed.ok) {
            yield {
              type: "error",
              content: parsed.error.message,
              metadata: { error: parsed.error }
            };
            return;
          }
          yield {
            type: "tool_call",
            toolCall: {
              id: fc.id ?? `${fc.name}-${Date.now()}`,
              name: fc.name ?? "unknown",
              args: parsed.args
            }
          };
        }
      }
    } catch {
    }
  }
  if (!finishReason || !GEMINI_SUCCESS_FINISH.has(finishReason)) {
    if (finishReason && !GEMINI_SUCCESS_FINISH.has(finishReason)) {
      yield adapterErrorChunk(
        `Gemini stream ended with non-success finish reason "${finishReason}"`
      );
      return;
    }
    yield adapterErrorChunk("Gemini stream ended without a successful finishReason");
    return;
  }
  yield { type: "done" };
}
async function* parseOllamaStream(stream) {
  let sawDone = false;
  for await (const data of readNDJSONLines(stream)) {
    try {
      const event = JSON.parse(data);
      if (event.message?.content) {
        yield { type: "text", content: event.message.content };
      }
      if (Array.isArray(event.message?.tool_calls)) {
        for (const tc of event.message.tool_calls) {
          if (tc?.function?.name) {
            const args = typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments ?? {});
            const parsed = parseCompleteToolArgs(args);
            if (!parsed.ok) {
              yield {
                type: "error",
                content: parsed.error.message,
                metadata: { error: parsed.error }
              };
              return;
            }
            yield {
              type: "tool_call",
              toolCall: {
                id: tc.id ?? `${tc.function.name}-${Date.now()}`,
                name: tc.function.name,
                args: parsed.args
              }
            };
          }
        }
      }
      if (event.done) {
        sawDone = true;
        if (typeof event.prompt_eval_count === "number" || typeof event.eval_count === "number") {
          const promptTokens = event.prompt_eval_count ?? 0;
          const completionTokens = event.eval_count ?? 0;
          yield {
            type: "usage",
            usage: {
              promptTokens,
              completionTokens,
              totalTokens: promptTokens + completionTokens
            }
          };
        }
        yield { type: "done" };
        return;
      }
    } catch {
    }
  }
  if (!sawDone) {
    yield adapterErrorChunk("Ollama stream ended without done:true");
    return;
  }
  yield { type: "done" };
}
var DEFAULT_RETRY = {
  maxAttempts: 3,
  baseDelayMs: 500,
  maxDelayMs: 8e3,
  jitter: true,
  retryOn: ({ error, response }) => {
    if (response) {
      return [408, 429, 500, 502, 503, 504].includes(response.status);
    }
    if (isAbortError(error)) return false;
    return true;
  }
};
function defaultSleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function computeDelay(attempt, opts) {
  const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * Math.pow(2, attempt - 1));
  if (!opts.jitter) return exp;
  return Math.floor(Math.random() * exp);
}
function parseRetryAfter(value) {
  if (!value) return void 0;
  const n = Number(value);
  if (!Number.isNaN(n)) return n * 1e3;
  const date = Date.parse(value);
  if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
  return void 0;
}
async function fetchWithRetry(doFetch, signal, retryOpt = {}) {
  const opts = {
    ...DEFAULT_RETRY,
    ...retryOpt
  };
  const sleep = retryOpt.sleep ?? defaultSleep;
  let lastError;
  for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
    if (signal.aborted) throw new DOMException("Aborted", "AbortError");
    try {
      const response = await doFetch(signal);
      if (response.ok) return response;
      if (attempt >= opts.maxAttempts || !opts.retryOn({ response, attempt })) {
        return response;
      }
      const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
      const delay = retryAfterMs ?? computeDelay(attempt, opts);
      retryOpt.onRetry?.({ attempt, delayMs: delay, reason: `HTTP ${response.status}` });
      await cancelBody(response.body);
      await abortableSleep(delay, signal, sleep);
    } catch (err) {
      lastError = err;
      if (isAbortError(err)) throw err;
      if (attempt >= opts.maxAttempts || !opts.retryOn({ error: err, attempt })) throw err;
      const delay = computeDelay(attempt, opts);
      retryOpt.onRetry?.({
        attempt,
        delayMs: delay,
        reason: err instanceof Error ? err.message : String(err)
      });
      await abortableSleep(delay, signal, sleep);
    }
  }
  throw lastError ?? new Error("fetchWithRetry: exhausted attempts");
}
function chunkText(text, targetSize = 32) {
  if (text.length <= targetSize) return [text];
  const out = [];
  let i = 0;
  while (i < text.length) {
    let end = Math.min(text.length, i + targetSize);
    if (end < text.length) {
      const nextSpace = text.indexOf(" ", end);
      if (nextSpace !== -1 && nextSpace - end <= 8) end = nextSpace + 1;
    }
    out.push(text.slice(i, end));
    i = end;
  }
  return out;
}
function simulateStream(doFetch, extractText, errorLabel, options = {}) {
  const { chunkSize = 32, delayMs = 8, retry } = options;
  let abortController = new AbortController();
  return {
    stream: async function* () {
      const controller = abortController;
      if (!controller) return;
      try {
        const response = await fetchWithRetry(doFetch, controller.signal, retry ?? {});
        if (!response.ok) {
          await cancelBody(response.body);
          yield adapterErrorChunk(`${errorLabel} error: ${response.status}`);
          return;
        }
        let text;
        try {
          text = await extractText(response);
        } catch (err) {
          await cancelBody(response.body);
          if (isAbortError(err)) return;
          const message = err instanceof Error ? err.message : String(err);
          yield adapterErrorChunk(message, { cause: err });
          return;
        }
        const chunks = chunkText(text, chunkSize);
        for (const chunk of chunks) {
          if (abortController === null || controller.signal.aborted) return;
          if (delayMs > 0) {
            await abortableSleep(delayMs, controller.signal, defaultSleep);
          }
          yield { type: "text", content: chunk };
        }
        yield { type: "done" };
      } catch (err) {
        if (isAbortError(err)) return;
        const message = err instanceof Error ? err.message : String(err);
        yield adapterErrorChunk(message, { cause: err });
      }
    },
    abort: () => {
      abortController?.abort();
      abortController = null;
    }
  };
}

// src/stream-source.ts
function createStreamSource(doFetch, parse, errorLabel, retry) {
  let abortController = new AbortController();
  return {
    stream: async function* () {
      const controller = abortController;
      if (!controller) return;
      try {
        const response = await fetchWithRetry(doFetch, controller.signal, retry ?? {});
        if (!response.ok) {
          await cancelBody(response.body);
          yield adapterErrorChunk(`${errorLabel} error: ${response.status}`);
          return;
        }
        if (!response.body) {
          yield adapterErrorChunk(`${errorLabel} error: empty response body`);
          return;
        }
        for await (const chunk of parse(response.body, response)) {
          if (controller.signal.aborted || abortController === null) return;
          yield chunk;
        }
      } catch (err) {
        if (isAbortError(err) || controller.signal.aborted || abortController === null) return;
        const message = err instanceof Error ? err.message : String(err);
        yield adapterErrorChunk(message, { cause: err });
      }
    },
    abort: () => {
      abortController?.abort();
      abortController = null;
    }
  };
}

// src/openai.ts
function openai(config) {
  const { apiKey, model, baseUrl = "https://api.openai.com", retry } = config;
  const apiRoot = baseUrl.replace(/\/v1\/?$/, "");
  const isCanonicalOpenAI = (() => {
    try {
      return new URL(baseUrl).host === "api.openai.com";
    } catch {
      return false;
    }
  })();
  const includeUsage = config.includeUsage ?? isCanonicalOpenAI;
  return {
    capabilities: {
      streaming: true,
      tools: true,
      // o1 / o3 models emit reasoning; older models don't. Accurate per-model
      // detection would need a model registry; 'true' is the safer default here.
      reasoning: model.startsWith("o1") || model.startsWith("o3"),
      multiModal: model.startsWith("gpt-4") || model.startsWith("o"),
      usage: true
    },
    createSource: (request) => {
      const body = {
        model,
        messages: toProviderMessages(request.messages),
        tools: request.context?.tools?.map((tool) => ({
          type: "function",
          function: {
            name: tool.name,
            description: tool.description,
            parameters: tool.schema
          }
        })),
        temperature: request.context?.temperature,
        max_tokens: request.context?.maxTokens,
        stream: true
      };
      if (includeUsage) body.stream_options = { include_usage: true };
      return createStreamSource(
        (signal) => fetch(`${apiRoot}/v1/chat/completions`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${apiKey}`
          },
          body: JSON.stringify(body),
          signal
        }),
        parseOpenAIStream,
        "OpenAI API",
        retry
      );
    }
  };
}

export { chunkText, createStreamSource, fetchWithRetry, openai, parseAnthropicStream, parseGeminiStream, parseOllamaStream, parseOpenAIStream, readSSELines, simulateStream, toProviderMessages };
//# sourceMappingURL=chunk-4KZTWGRB.js.map
//# sourceMappingURL=chunk-4KZTWGRB.js.map