"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodexAppServerClient = void 0;
exports.withCodexAppServerClient = withCodexAppServerClient;
exports.listCodexPlugins = listCodexPlugins;
exports.installCodexPlugin = installCodexPlugin;
exports.listCodexSkills = listCodexSkills;
exports.listCodexMcpServerStatuses = listCodexMcpServerStatuses;
const node_child_process_1 = require("node:child_process");
const node_readline_1 = __importDefault(require("node:readline"));
const DEFAULT_TIMEOUT_MS = 30_000;
class CodexAppServerClient {
    options;
    child = null;
    initialized = false;
    nextId = 1;
    pending = new Map();
    stderr = "";
    constructor(options) {
        this.options = {
            cwd: options.cwd,
            env: options.env,
            command: options.command ?? "codex",
            args: options.args ?? ["app-server"],
            timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
        };
    }
    async listPlugins(params) {
        return this.request("plugin/list", {
            cwds: [...params.cwds],
        });
    }
    async installPlugin(params) {
        return this.request("plugin/install", {
            pluginName: params.pluginName,
            marketplacePath: params.marketplacePath ?? null,
            remoteMarketplaceName: params.remoteMarketplaceName ?? null,
        });
    }
    async listSkills(params) {
        return this.request("skills/list", {
            cwds: [...params.cwds],
            forceReload: params.forceReload ?? true,
        });
    }
    async listMcpServerStatus(params = {}) {
        return this.request("mcpServerStatus/list", {
            detail: "toolsAndAuthOnly",
            cursor: params.cursor ?? null,
            limit: params.limit ?? 500,
        });
    }
    async request(method, params) {
        await this.connect();
        const id = this.nextId;
        this.nextId += 1;
        const value = await this.sendRequest(id, method, params);
        return value;
    }
    close() {
        const child = this.child;
        this.child = null;
        if (child !== null && !child.killed) {
            child.kill();
        }
        for (const pending of this.pending.values()) {
            clearTimeout(pending.timeout);
            pending.reject(new Error("Codex app-server connection closed."));
        }
        this.pending.clear();
    }
    async connect() {
        if (this.initialized) {
            return;
        }
        if (this.child === null) {
            this.startProcess();
        }
        await this.sendRequest(this.nextId, "initialize", {
            clientInfo: {
                name: "alpacaloop",
                title: "AlpacaLoop",
                version: "0.1.0",
            },
            capabilities: {
                experimentalApi: true,
            },
        });
        this.nextId += 1;
        this.sendNotification("initialized", {});
        this.initialized = true;
    }
    startProcess() {
        const child = (0, node_child_process_1.spawn)(this.options.command, this.options.args, {
            cwd: this.options.cwd,
            env: this.options.env,
            stdio: ["pipe", "pipe", "pipe"],
        });
        this.child = child;
        child.stderr.setEncoding("utf8");
        child.stderr.on("data", (chunk) => {
            this.stderr += chunk;
        });
        const lines = node_readline_1.default.createInterface({ input: child.stdout });
        lines.on("line", (line) => this.handleLine(line));
        child.on("error", (error) => this.rejectAll(error));
        child.on("close", (code) => {
            if (this.pending.size === 0) {
                return;
            }
            this.rejectAll(new Error(`Codex app-server exited with code ${code ?? 1}.${this.stderr.trim() ? ` ${this.stderr.trim()}` : ""}`));
        });
    }
    handleLine(line) {
        let message;
        try {
            message = JSON.parse(line);
        }
        catch {
            return;
        }
        if (typeof message.id !== "number") {
            return;
        }
        const pending = this.pending.get(message.id);
        if (pending === undefined) {
            return;
        }
        clearTimeout(pending.timeout);
        this.pending.delete(message.id);
        if (message.error !== undefined) {
            pending.reject(new Error(message.error.message ??
                `Codex app-server request ${message.id} failed.`));
            return;
        }
        pending.resolve(message.result);
    }
    sendRequest(id, method, params) {
        const child = this.child;
        if (child === null) {
            throw new Error("Codex app-server process is not running.");
        }
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                this.pending.delete(id);
                reject(new Error(`Codex app-server request timed out: ${method}`));
            }, this.options.timeoutMs);
            this.pending.set(id, { resolve, reject, timeout });
            child.stdin.write(`${JSON.stringify({ method, id, params })}\n`);
        });
    }
    sendNotification(method, params) {
        this.child?.stdin.write(`${JSON.stringify({ method, params })}\n`);
    }
    rejectAll(error) {
        for (const pending of this.pending.values()) {
            clearTimeout(pending.timeout);
            pending.reject(error);
        }
        this.pending.clear();
    }
}
exports.CodexAppServerClient = CodexAppServerClient;
async function withCodexAppServerClient(options, callback) {
    const client = new CodexAppServerClient(options);
    try {
        return await callback(client);
    }
    finally {
        client.close();
    }
}
async function listCodexPlugins(input) {
    return withCodexAppServerClient(input, (client) => client.listPlugins({ cwds: [input.cwd] }));
}
async function installCodexPlugin(input) {
    return withCodexAppServerClient(input, (client) => client.installPlugin({
        pluginName: input.pluginName,
        marketplacePath: input.marketplacePath ?? null,
        remoteMarketplaceName: input.remoteMarketplaceName ?? null,
    }));
}
async function listCodexSkills(input) {
    return withCodexAppServerClient(input, (client) => client.listSkills({
        cwds: [input.cwd],
        forceReload: input.forceReload ?? true,
    }));
}
async function listCodexMcpServerStatuses(input) {
    return withCodexAppServerClient(input, async (client) => {
        const statuses = [];
        let cursor = null;
        do {
            const response = await client.listMcpServerStatus({ cursor });
            statuses.push(...response.data);
            cursor = response.nextCursor ?? null;
        } while (cursor !== null);
        return statuses;
    });
}
