"use strict";
/**
 * Cursor Streaming Frame Parser
 * Incrementally parses ConnectRPC frames from arbitrary chunk boundaries
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamingFrameParser = exports.decompressPayload = exports.mapCursorConnectError = exports.CursorConnectFrameError = void 0;
const zlib = __importStar(require("zlib"));
const cursor_protobuf_schema_js_1 = require("./cursor-protobuf-schema.js");
const cursor_protobuf_decoder_js_1 = require("./cursor-protobuf-decoder.js");
class CursorConnectFrameError extends Error {
    constructor(message, status = 502, errorType = 'server_error') {
        super(message);
        this.status = status;
        this.errorType = errorType;
        this.name = 'CursorConnectFrameError';
    }
}
exports.CursorConnectFrameError = CursorConnectFrameError;
function mapCursorConnectError(code) {
    switch (code?.toLowerCase()) {
        case 'resource_exhausted':
            return { status: 429, errorType: 'rate_limit_error' };
        case 'unauthenticated':
            return { status: 401, errorType: 'authentication_error' };
        case 'permission_denied':
            return { status: 403, errorType: 'permission_error' };
        case 'not_found':
            return { status: 404, errorType: 'api_error' };
        case 'already_exists':
        case 'aborted':
            return { status: 409, errorType: 'api_error' };
        case 'deadline_exceeded':
            return { status: 504, errorType: 'api_error' };
        case 'unimplemented':
            return { status: 501, errorType: 'api_error' };
        case 'unavailable':
            return { status: 503, errorType: 'api_error' };
        case 'invalid_argument':
        case 'failed_precondition':
        case 'out_of_range':
            return { status: 400, errorType: 'api_error' };
        default:
            return { status: 500, errorType: 'api_error' };
    }
}
exports.mapCursorConnectError = mapCursorConnectError;
function formatConnectFrameFlags(flags) {
    return `0x${flags.toString(16).padStart(2, '0')}`;
}
function toFrameErrorResult(error) {
    if (error instanceof CursorConnectFrameError) {
        return {
            type: 'error',
            message: error.message,
            status: error.status,
            errorType: error.errorType,
        };
    }
    return {
        type: 'error',
        message: error instanceof Error ? error.message : 'Cursor stream parsing failed.',
        status: 502,
        errorType: 'server_error',
    };
}
function createTruncatedFrameError() {
    return new CursorConnectFrameError('Truncated Cursor ConnectRPC frame.', 502, 'server_error');
}
/**
 * Decompress payload if gzip-compressed.
 * Skips decompression for JSON error payloads.
 * NOTE: Uses synchronous gzip for single-request CLI tool. Async not warranted for small payloads.
 */
function decompressPayload(payload, flags) {
    if ((0, cursor_protobuf_schema_js_1.hasUnknownConnectFrameFlags)(flags)) {
        throw new CursorConnectFrameError(`Unsupported ConnectRPC frame flags: ${formatConnectFrameFlags(flags)}`, 502, 'server_error');
    }
    if (payload.length > 10 && payload[0] === 0x7b && payload[1] === 0x22) {
        try {
            const text = payload.toString('utf-8');
            if (text.startsWith('{"error"'))
                return payload;
        }
        catch {
            // Not JSON, continue
        }
    }
    if ((0, cursor_protobuf_schema_js_1.isCompressedConnectFrame)(flags)) {
        try {
            return zlib.gunzipSync(payload);
        }
        catch (err) {
            if (process.env.CCS_DEBUG) {
                console.error('[cursor] gzip decompression failed:', err);
            }
            throw new CursorConnectFrameError('Failed to decompress Cursor ConnectRPC frame.', 502, 'server_error');
        }
    }
    return payload;
}
exports.decompressPayload = decompressPayload;
/**
 * Incrementally parses ConnectRPC frames from arbitrary chunk boundaries.
 *
 * Usage:
 *   const parser = new StreamingFrameParser();
 *   req.on('data', (chunk) => {
 *     for (const frame of parser.push(chunk)) { handle(frame); }
 *   });
 */
class StreamingFrameParser {
    constructor() {
        this.buffer = Buffer.alloc(0);
    }
    /** Feed a new chunk. Returns zero or more parsed frames. */
    push(chunk) {
        this.buffer = Buffer.concat([this.buffer, chunk]);
        const results = [];
        while (this.buffer.length >= 5) {
            const length = this.buffer.readUInt32BE(1);
            const frameSize = 5 + length;
            if (this.buffer.length < frameSize)
                break;
            const flags = this.buffer[0];
            let payload = this.buffer.slice(5, frameSize);
            this.buffer = this.buffer.slice(frameSize);
            try {
                payload = decompressPayload(payload, flags);
            }
            catch (error) {
                results.push(toFrameErrorResult(error));
                return results;
            }
            if ((0, cursor_protobuf_schema_js_1.isEndStreamConnectFrame)(flags)) {
                try {
                    const text = payload.toString('utf-8');
                    const json = JSON.parse(text);
                    const msg = json?.error?.details?.[0]?.debug?.details?.title ||
                        json?.error?.details?.[0]?.debug?.details?.detail ||
                        json?.error?.message;
                    if (msg) {
                        const mappedError = mapCursorConnectError(json?.error?.code);
                        results.push({
                            type: 'error',
                            message: msg,
                            status: mappedError.status,
                            errorType: mappedError.errorType,
                        });
                        return results;
                    }
                }
                catch {
                    // Ignore successful end-stream metadata trailers.
                }
                continue;
            }
            // Check for JSON error
            try {
                const text = payload.toString('utf-8');
                if (text.startsWith('{') && text.includes('"error"')) {
                    const json = JSON.parse(text);
                    const msg = json?.error?.details?.[0]?.debug?.details?.title ||
                        json?.error?.details?.[0]?.debug?.details?.detail ||
                        json?.error?.message ||
                        'API Error';
                    const mappedError = mapCursorConnectError(json?.error?.code);
                    results.push({
                        type: 'error',
                        message: msg,
                        status: mappedError.status,
                        errorType: mappedError.errorType,
                    });
                    return results;
                }
            }
            catch {
                // Not JSON, continue to protobuf parsing
            }
            const result = (0, cursor_protobuf_decoder_js_1.extractTextFromResponse)(new Uint8Array(payload));
            if (result.error) {
                const errorLower = result.error.toLowerCase();
                const isRateLimit = errorLower.includes('rate limit') ||
                    errorLower.includes('resource_exhausted') ||
                    errorLower.includes('too many requests');
                results.push({
                    type: 'error',
                    message: result.error,
                    status: isRateLimit ? 429 : 502,
                    errorType: isRateLimit ? 'rate_limit_error' : 'server_error',
                });
                return results;
            }
            if (result.toolCall)
                results.push({ type: 'toolCall', toolCall: result.toolCall });
            if (result.text)
                results.push({ type: 'text', text: result.text });
            if (result.thinking)
                results.push({ type: 'thinking', text: result.thinking });
        }
        return results;
    }
    hasPartial() {
        return this.buffer.length > 0;
    }
    finish() {
        if (this.buffer.length === 0) {
            return [];
        }
        this.buffer = Buffer.alloc(0);
        return [toFrameErrorResult(createTruncatedFrameError())];
    }
}
exports.StreamingFrameParser = StreamingFrameParser;
//# sourceMappingURL=cursor-stream-parser.js.map