/**
 * LLM call recorder fetch wrapper.
 *
 * Creates a fetch wrapper that records LLM calls passing through
 * the Clark provider. Handles both streaming (doStream) and
 * non-streaming (doGenerate) requests.
 */
/**
 * Creates a recording fetch wrapper for LLM calls.
 *
 * This wrapper intercepts requests to the LLM provider and records
 * both the request and response data. It handles:
 * - Non-streaming requests (doGenerate): Clones and parses the JSON response
 * - Streaming requests (doStream): Buffers SSE chunks to reconstruct the response
 *
 * @param recorder - The RecordingManager instance
 * @param baseFetch - The underlying fetch function to wrap (defaults to global fetch)
 * @returns A wrapped fetch function that records LLM calls
 *
 * @example
 * ```typescript
 * const recordingFetch = createRecordingFetch(recorder);
 *
 * // Use in provider settings
 * const settings = {
 *   clark: {
 *     fetch: recordingFetch
 *   }
 * };
 * ```
 */
export function createRecordingFetch(recorder, baseFetch = fetch) {
    return async (url, init) => {
        // If recording is not enabled, just pass through
        if (!recorder.isEnabled()) {
            return baseFetch(url, init);
        }
        const startTime = Date.now();
        const urlString = url.toString();
        // Parse request body
        let requestBody = {};
        try {
            if (init?.body && typeof init.body === "string") {
                requestBody = JSON.parse(init.body);
            }
        }
        catch {
            // Failed to parse request body, continue with empty object
        }
        // Determine if this is a streaming request
        const isStreaming = requestBody.stream === true || urlString.includes("/stream");
        const endpoint = isStreaming ? "stream" : "generate";
        // Execute the actual fetch
        const response = await baseFetch(url, init);
        // Record based on response type
        if (isStreaming && response.body) {
            // For streaming responses, wrap the body to capture chunks
            return createRecordingStreamResponse(response, recorder, requestBody, endpoint, startTime);
        }
        else {
            // For non-streaming responses, clone and parse
            return recordNonStreamingResponse(response, recorder, requestBody, endpoint, startTime);
        }
    };
}
/**
 * Records a non-streaming LLM response.
 */
async function recordNonStreamingResponse(response, recorder, requestBody, endpoint, startTime) {
    try {
        // Clone response to read body without consuming it
        const clonedResponse = response.clone();
        const responseBody = await clonedResponse.json();
        const recording = {
            endpoint,
            request: {
                model: requestBody.model ?? "unknown",
                messages: requestBody.messages ?? [],
                tools: requestBody.tools,
                system: requestBody.system,
            },
            response: {
                content: responseBody.text ?? responseBody.content ?? "",
                toolCalls: responseBody.toolCalls ?? [],
                usage: responseBody.usage
                    ? {
                        inputTokens: responseBody.usage.inputTokens,
                        outputTokens: responseBody.usage.outputTokens,
                        totalTokens: responseBody.usage.totalTokens,
                    }
                    : undefined,
                finishReason: responseBody.finishReason,
            },
            durationMs: Date.now() - startTime,
        };
        recorder.recordLLM(recording);
    }
    catch {
        // Failed to parse response, record what we have
        recorder.recordLLM({
            endpoint,
            request: {
                model: requestBody.model ?? "unknown",
                messages: requestBody.messages ?? [],
                tools: requestBody.tools,
                system: requestBody.system,
            },
            response: {
                content: "[failed to parse response]",
            },
            durationMs: Date.now() - startTime,
        });
    }
    return response;
}
/**
 * Creates a response with a recording stream wrapper.
 *
 * The stream passes through all chunks while accumulating data
 * for recording. When the stream completes, the accumulated
 * response is recorded.
 */
function createRecordingStreamResponse(response, recorder, requestBody, endpoint, startTime) {
    if (!response.body) {
        return response;
    }
    const accumulated = {
        content: "",
        toolCalls: [],
    };
    const transformedBody = response.body.pipeThrough(new TransformStream({
        transform(chunk, controller) {
            // Pass through the chunk
            controller.enqueue(chunk);
            // Try to parse and accumulate SSE data
            try {
                const text = new TextDecoder().decode(chunk);
                accumulateSSEData(text, accumulated);
            }
            catch {
                // Failed to parse chunk, continue
            }
        },
        flush() {
            // Stream completed, record the accumulated response
            const recording = {
                endpoint,
                request: {
                    model: requestBody.model ?? "unknown",
                    messages: requestBody.messages ?? [],
                    tools: requestBody.tools,
                    system: requestBody.system,
                },
                response: {
                    content: accumulated.content,
                    toolCalls: accumulated.toolCalls.length > 0
                        ? accumulated.toolCalls
                        : undefined,
                    usage: accumulated.usage,
                    finishReason: accumulated.finishReason,
                },
                durationMs: Date.now() - startTime,
            };
            recorder.recordLLM(recording);
        },
    }));
    return new Response(transformedBody, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
    });
}
/**
 * Accumulates data from SSE chunks.
 *
 * Parses SSE data lines and extracts relevant content for recording.
 */
function accumulateSSEData(text, accumulated) {
    // Split by SSE data lines
    const lines = text.split("\n");
    for (const line of lines) {
        if (!line.startsWith("data: "))
            continue;
        const data = line.slice(6); // Remove "data: " prefix
        if (data === "[DONE]")
            continue;
        try {
            const parsed = JSON.parse(data);
            // Handle different chunk types from the Clark provider
            switch (parsed.type) {
                case "text-delta":
                    accumulated.content += parsed.textDelta ?? parsed.text ?? "";
                    break;
                case "tool-call":
                    accumulated.toolCalls.push({
                        toolCallId: parsed.toolCallId,
                        toolName: parsed.toolName,
                        input: parsed.input,
                    });
                    break;
                case "finish":
                case "step-finish":
                    if (parsed.usage) {
                        accumulated.usage = {
                            inputTokens: parsed.usage.inputTokens ?? parsed.usage.promptTokens,
                            outputTokens: parsed.usage.outputTokens ?? parsed.usage.completionTokens,
                            totalTokens: parsed.usage.totalTokens,
                        };
                    }
                    if (parsed.finishReason) {
                        accumulated.finishReason = parsed.finishReason;
                    }
                    break;
            }
        }
        catch {
            // Failed to parse SSE data, continue
        }
    }
}
//# sourceMappingURL=llm-recorder.js.map