import { createInterface } from "node:readline";
import { wrapCoreEvent } from "../events/types.js";
import { createRpcErrorResponse, createRpcEventMessage, createRpcReadyMessage, createRpcSuccessResponse, parseRpcRequestLine, RPC_ERROR_CODES, RPC_METHODS, RPC_PROTOCOL_VERSION, serializeRpcMessage, } from "./rpc_protocol.js";
export class RpcServer {
    runtime;
    send;
    unsubscribeEvent;
    activeSubmit;
    activeSubmitRequestId;
    submitRequestByUserHistoryEntryId = new Map();
    mutationQueue = Promise.resolve();
    pendingSessionMutations = 0;
    initialized = false;
    rpcShutdown = false;
    closed = false;
    constructor(options) {
        this.runtime = options.runtime;
        this.send = options.send;
        this.unsubscribeEvent = this.runtime.session.onEvent((event) => {
            if (this.rpcShutdown || this.closed) {
                return;
            }
            const requestId = this.resolveEventRequestId(event);
            this.sendMessage(createRpcEventMessage(wrapCoreEvent(event), { requestId }));
        });
        if (options.emitReadyOnStart ?? true) {
            this.emitReady();
        }
    }
    async handleLine(line) {
        if (this.closed) {
            return;
        }
        const parsed = parseRpcRequestLine(line);
        if (!parsed.ok) {
            this.sendParseFailure(parsed);
            return;
        }
        const request = parsed.request;
        if (this.rpcShutdown && request.method !== "initialize") {
            this.sendServerShutdownError(request.id);
            return;
        }
        try {
            switch (request.method) {
                case "initialize":
                    this.handleInitialize(request);
                    return;
                case "session.submit":
                    await this.handleSubmit(request);
                    return;
                case "session.interrupt":
                    this.handleInterrupt(request);
                    return;
                case "session.snapshot":
                    this.handleSnapshot(request);
                    return;
                case "session.reset":
                    await this.handleReset(request);
                    return;
                case "session.shutdown":
                    await this.handleShutdown(request);
                    return;
            }
        }
        catch (error) {
            this.sendMessage(createRpcErrorResponse(request.id, RPC_ERROR_CODES.internalError, "rpc request failed", {
                cause: error instanceof Error ? error.message : String(error),
            }));
        }
    }
    async close(options = {}) {
        if (this.closed) {
            return;
        }
        this.closed = true;
        this.clearEventCorrelationState();
        this.unsubscribeEventListener();
        if (options.interruptActiveSubmit && (this.activeSubmit || this.runtime.isTurnRunning)) {
            this.runtime.interruptTurn();
        }
        this.runtime.session.dispose();
    }
    async shutdown(options = {}) {
        await this.close(options);
    }
    emitReady() {
        this.sendMessage(createRpcReadyMessage({
            sessionId: this.runtime.session.sessionId,
        }));
    }
    sendParseFailure(parsed) {
        this.sendMessage(createRpcErrorResponse(parsed.id, parsed.error.code, parsed.error.message, parsed.error.data));
    }
    handleInitialize(request) {
        const result = {
            protocolVersion: RPC_PROTOCOL_VERSION,
            sessionId: this.runtime.session.sessionId,
            methods: [...RPC_METHODS],
            alreadyInitialized: this.initialized,
        };
        this.initialized = true;
        this.sendMessage(createRpcSuccessResponse(request.id, result));
    }
    async handleSubmit(request) {
        if (this.pendingSessionMutations > 0) {
            this.sendSubmitBusy(request.id);
            return;
        }
        const startedSubmit = await this.enqueueMutation(() => {
            if (this.rpcShutdown) {
                this.sendServerShutdownError(request.id);
                return undefined;
            }
            if (this.activeSubmit || this.runtime.isTurnRunning) {
                this.sendSubmitBusy(request.id);
                return undefined;
            }
            const addOptions = request.params.historyEntryId
                ? { historyEntryId: request.params.historyEntryId }
                : undefined;
            const userHistoryEntryId = this.runtime.session.addUserText(request.params.text, addOptions);
            this.submitRequestByUserHistoryEntryId.set(userHistoryEntryId, request.id);
            this.activeSubmitRequestId = request.id;
            const submitPromise = this.executeSubmit(request.id, userHistoryEntryId);
            this.activeSubmit = submitPromise;
            return {
                submitPromise,
            };
        });
        if (!startedSubmit) {
            return;
        }
        const { submitPromise } = startedSubmit;
        try {
            await submitPromise;
        }
        finally {
            await this.enqueueMutation(() => {
                if (this.activeSubmit === submitPromise) {
                    this.activeSubmit = undefined;
                    this.activeSubmitRequestId = undefined;
                }
            });
        }
    }
    async executeSubmit(requestId, userHistoryEntryId) {
        try {
            const turnResult = await this.runtime.runTurn();
            const result = {
                userHistoryEntryId,
                turn: {
                    aborted: turnResult.aborted,
                    ...(turnResult.blocked ? { blocked: turnResult.blocked } : {}),
                },
            };
            this.sendMessage(createRpcSuccessResponse(requestId, result));
        }
        catch (error) {
            this.sendMessage(createRpcErrorResponse(requestId, RPC_ERROR_CODES.internalError, "failed to run session turn", { cause: error instanceof Error ? error.message : String(error) }));
        }
    }
    handleInterrupt(request) {
        const interrupted = this.runtime.interruptTurn();
        const result = {
            interrupted,
            isTurnRunning: this.runtime.isTurnRunning,
        };
        this.sendMessage(createRpcSuccessResponse(request.id, result));
    }
    handleSnapshot(request) {
        const result = {
            sessionId: this.runtime.session.sessionId,
            isTurnRunning: this.runtime.isTurnRunning,
            historyLength: this.runtime.session.history.length,
            history: [...this.runtime.session.history],
            historyEntries: this.runtime.session.historyEntries.map((entry) => ({
                id: entry.id,
                message: entry.message,
            })),
        };
        this.sendMessage(createRpcSuccessResponse(request.id, result));
    }
    async handleReset(request) {
        await this.runSessionMutation(async () => {
            if (this.rpcShutdown) {
                this.sendServerShutdownError(request.id);
                return;
            }
            await this.interruptAndWaitForActiveSubmit();
            const previousSessionId = this.runtime.session.sessionId;
            this.runtime.session.reset();
            this.clearEventCorrelationState();
            const result = {
                previousSessionId,
                sessionId: this.runtime.session.sessionId,
            };
            this.sendMessage(createRpcSuccessResponse(request.id, result));
        });
    }
    async handleShutdown(request) {
        await this.runSessionMutation(async () => {
            if (this.rpcShutdown) {
                const result = {
                    shutdown: true,
                };
                this.sendMessage(createRpcSuccessResponse(request.id, result));
                return;
            }
            await this.interruptAndWaitForActiveSubmit();
            this.rpcShutdown = true;
            this.clearEventCorrelationState();
            this.unsubscribeEventListener();
            const result = {
                shutdown: true,
            };
            this.sendMessage(createRpcSuccessResponse(request.id, result));
        });
    }
    sendServerShutdownError(id) {
        this.sendMessage(createRpcErrorResponse(id, RPC_ERROR_CODES.invalidRequest, "rpc server is shut down"));
    }
    sendSubmitBusy(id) {
        const message = this.pendingSessionMutations > 0
            ? "a mutating session request is in progress"
            : "a session turn is already running";
        this.sendMessage(createRpcErrorResponse(id, RPC_ERROR_CODES.busy, message));
    }
    enqueueMutation(handler) {
        const run = this.mutationQueue.then(handler);
        this.mutationQueue = run.then(() => undefined, () => undefined);
        return run;
    }
    async runSessionMutation(handler) {
        this.pendingSessionMutations += 1;
        try {
            return await this.enqueueMutation(handler);
        }
        finally {
            this.pendingSessionMutations -= 1;
        }
    }
    async interruptAndWaitForActiveSubmit() {
        if (!this.activeSubmit && !this.runtime.isTurnRunning) {
            return;
        }
        this.runtime.interruptTurn();
        if (!this.activeSubmit) {
            return;
        }
        try {
            await this.activeSubmit;
        }
        catch {
            // ignore submit failure while finishing active mutation
        }
    }
    resolveEventRequestId(event) {
        if (event.type === "subagent_ui") {
            return this.submitRequestByUserHistoryEntryId.get(event.originHistoryEntryId);
        }
        return this.activeSubmitRequestId;
    }
    clearEventCorrelationState() {
        this.activeSubmitRequestId = undefined;
        this.submitRequestByUserHistoryEntryId.clear();
    }
    unsubscribeEventListener() {
        if (!this.unsubscribeEvent) {
            return;
        }
        this.unsubscribeEvent();
        this.unsubscribeEvent = undefined;
    }
    sendMessage(message) {
        this.send(serializeRpcMessage(message));
    }
}
export async function runRpcServer(options) {
    const server = new RpcServer({
        runtime: options.runtime,
        send: (line) => {
            options.output.write(`${line}\n`);
        },
        emitReadyOnStart: true,
    });
    const lineReader = createInterface({
        input: options.input,
        crlfDelay: Number.POSITIVE_INFINITY,
    });
    const inFlightHandlers = new Set();
    let firstError;
    let stopRequested = false;
    const trackInFlight = (promise) => {
        inFlightHandlers.add(promise);
        void promise
            .catch((error) => {
            if (firstError === undefined) {
                firstError = error;
            }
        })
            .finally(() => {
            inFlightHandlers.delete(promise);
        });
    };
    const requestStop = () => {
        if (stopRequested) {
            return;
        }
        stopRequested = true;
        lineReader.close();
        if (typeof options.input.pause === "function") {
            options.input.pause();
        }
    };
    const onLine = (line) => {
        const handler = server.handleLine(line);
        trackInFlight(handler);
    };
    const onAbort = () => {
        requestStop();
    };
    lineReader.on("line", onLine);
    const closePromise = new Promise((resolve) => {
        lineReader.once("close", resolve);
    });
    if (options.signal) {
        if (options.signal.aborted) {
            requestStop();
        }
        options.signal.addEventListener("abort", onAbort);
    }
    try {
        await closePromise;
    }
    finally {
        lineReader.off("line", onLine);
        options.signal?.removeEventListener("abort", onAbort);
        await server.shutdown({ interruptActiveSubmit: true });
        if (inFlightHandlers.size > 0) {
            await Promise.allSettled([...inFlightHandlers]);
        }
    }
    if (firstError !== undefined) {
        throw firstError;
    }
}
//# sourceMappingURL=rpc_server.js.map