/**
 * SSE reader for the shared `/api/llm/stream` proxy. Concatenates `text_delta`
 * events; surfaces `error` events and non-OK HTTP responses as
 * `LlmStreamError` instances that carry the same structured envelope the
 * model-stream contract uses. `fetch` + auth-header resolver are injected so
 * this module stays host-agnostic.
 */
import { extractStructuredHttpErrorPayload, parseStreamErrorPayload } from '../../../contracts/model-stream.js';
/**
 * Error thrown by `readLlmStreamText` carrying the structured stream-error
 * envelope (`provider_error_type`/`retryable`/`retry_after_ms`/`status`) so
 * callers can inspect failure semantics instead of regexing the message.
 */
export class LlmStreamError extends Error {
    details;
    constructor(message, details) {
        super(message);
        this.name = 'LlmStreamError';
        this.details = details;
    }
}
/** POST `/api/llm/stream`, consume the SSE response, return concatenated `text_delta`. */
export async function readLlmStreamText(options) {
    const headers = await options.authHeaders();
    headers['X-Agent-Runtime'] = 'shortcutxl_embedded';
    const response = await options.fetch(`${options.baseUrl}/api/llm/stream`, {
        method: 'POST',
        headers,
        body: JSON.stringify(options.body),
        credentials: 'include',
        signal: options.signal
    });
    if (!response.ok) {
        const rawBody = await response.text().catch(() => '');
        let parsedBody;
        try {
            parsedBody = rawBody ? JSON.parse(rawBody) : undefined;
        }
        catch {
            parsedBody = undefined;
        }
        const payload = extractStructuredHttpErrorPayload(parsedBody);
        const details = parseStreamErrorPayload(payload, response.status) ?? {
            status: response.status
        };
        const message = typeof payload?.message === 'string'
            ? `LLM proxy error ${response.status}: ${payload.message}`
            : rawBody
                ? `LLM proxy error ${response.status}: ${rawBody.split('\n').slice(0, 5).join('\n')}`
                : `LLM proxy error (${response.status})`;
        throw new LlmStreamError(message, details);
    }
    if (!response.body)
        throw new LlmStreamError('LLM stream returned no response body', undefined);
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = '';
    let text = '';
    const handleBlock = (block) => {
        if (!block.trim())
            return;
        let event = null;
        const dataLines = [];
        for (const line of block.split('\n')) {
            if (line.startsWith('event:'))
                event = line.slice(6).trim();
            else if (line.startsWith('data:'))
                dataLines.push(line.slice(5).trimStart());
        }
        if (!event)
            return;
        let payload;
        try {
            payload = JSON.parse(dataLines.join('\n'));
        }
        catch {
            return;
        }
        if (event === 'text_delta') {
            const delta = payload.delta;
            if (typeof delta === 'string')
                text += delta;
        }
        else if (event === 'error') {
            const errorPayload = payload;
            const msg = typeof errorPayload.message === 'string' ? errorPayload.message : 'LLM stream failed';
            const details = parseStreamErrorPayload(errorPayload);
            throw new LlmStreamError(msg, details);
        }
    };
    while (true) {
        const { done, value } = await reader.read();
        if (done)
            break;
        buffer += decoder.decode(value, { stream: true });
        const blocks = buffer.split('\n\n');
        buffer = blocks.pop() ?? '';
        for (const block of blocks)
            handleBlock(block);
    }
    buffer += decoder.decode();
    if (buffer.trim())
        handleBlock(buffer);
    return text;
}
//# sourceMappingURL=llm-stream.js.map