/**
 * Codex JSON-RPC Client
 *
 * JSON-RPC 2.0 over JSONL stdio protocol layer for communicating
 * with the Codex app-server. Handles request/response multiplexing,
 * server-initiated requests, and notification dispatch.
 *
 * Note: Codex app-server omits the "jsonrpc":"2.0" header —
 * we do not require or emit it.
 *
 * @module
 */
// ============================================================================
// JSON-RPC Client
// ============================================================================
/**
 * JSON-RPC 2.0 client over JSONL stdio.
 *
 * Multiplexes incoming messages by inspecting `id` and `method` fields:
 * - Has `id` + no `method` → response → resolve/reject pending promise
 * - Has `method` + no `id` → notification → dispatch to notification handler
 * - Has both `method` + `id` → server-initiated request → dispatch to request handler
 */
export class CodexJsonRpcClient {
    stdin;
    nextId = 1;
    pending = new Map();
    notificationHandler = null;
    serverRequestHandler = null;
    lineBuffer = '';
    closed = false;
    constructor(stdin, stdout) {
        this.stdin = stdin;
        stdout.on('data', (chunk) => {
            this.onData(chunk.toString());
        });
    }
    /** Send a request and await the response */
    request(method, params) {
        if (this.closed) {
            return Promise.reject(new Error('Client is closed'));
        }
        const id = this.nextId++;
        const message = { method, id };
        if (params !== undefined) {
            message.params = params;
        }
        return new Promise((resolve, reject) => {
            this.pending.set(id, { resolve, reject });
            this.writeLine(message);
        });
    }
    /** Send a notification (no response expected) */
    notify(method, params) {
        if (this.closed)
            return;
        const message = { method };
        if (params !== undefined) {
            message.params = params;
        }
        this.writeLine(message);
    }
    /** Reply to a server-initiated request */
    respond(id, result) {
        if (this.closed)
            return;
        this.writeLine({ id, result });
    }
    /** Register a handler for server notifications */
    onNotification(handler) {
        this.notificationHandler = handler;
    }
    /** Register a handler for server-initiated requests */
    onServerRequest(handler) {
        this.serverRequestHandler = handler;
    }
    /** Close the client, rejecting all pending requests */
    close() {
        if (this.closed)
            return;
        this.closed = true;
        for (const [, pending] of this.pending) {
            pending.reject(new Error('Client closed'));
        }
        this.pending.clear();
        this.notificationHandler = null;
        this.serverRequestHandler = null;
    }
    // ----------------------------------------
    // Private
    // ----------------------------------------
    /** Feed stdout data for JSONL line-buffered parsing */
    onData(chunk) {
        this.lineBuffer += chunk;
        let newlineIndex;
        while ((newlineIndex = this.lineBuffer.indexOf('\n')) !== -1) {
            const line = this.lineBuffer.slice(0, newlineIndex).trim();
            this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
            if (line.length === 0)
                continue;
            let message;
            try {
                message = JSON.parse(line);
            }
            catch {
                // Skip malformed lines
                continue;
            }
            this.dispatch(message);
        }
    }
    dispatch(message) {
        const hasId = message.id !== undefined;
        const hasMethod = message.method !== undefined;
        if (hasId && hasMethod) {
            // Server-initiated request — has both id and method
            this.serverRequestHandler?.(message.id, message.method, message.params);
        }
        else if (hasId && !hasMethod) {
            // Response to our request
            const pending = this.pending.get(message.id);
            if (pending) {
                this.pending.delete(message.id);
                if (message.error) {
                    pending.reject(new Error(message.error.message ?? 'RPC error'));
                }
                else {
                    pending.resolve(message.result);
                }
            }
        }
        else if (hasMethod && !hasId) {
            // Server notification
            this.notificationHandler?.(message.method, message.params);
        }
    }
    writeLine(message) {
        this.stdin.write(JSON.stringify(message) + '\n');
    }
}
//# sourceMappingURL=jsonrpc-client.js.map