import { spawn, spawnSync } from "node:child_process";
import { EventEmitter } from "node:events";
import { existsSync, readFileSync, rmSync } from "node:fs";
import { homedir } from "node:os";
import { isAbsolute, join } from "node:path";
import { Readable, Writable } from "node:stream";
import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream, } from "@agentclientprotocol/sdk";
import { uuidv7 } from "../db/uuidv7.js";
import { NO_PROVIDER_AUTH, killChildTree as killAuthChildTree, runAuthCommand, startDeviceLoginCommand, unsupportedLogin, unsupportedLogout, } from "./provider-auth.js";
const ACP_PROBE_TIMEOUT_MS = process.platform === "win32" ? 20_000 : 5_000;
class JaitAcpClient {
    provider;
    sessionId;
    approvals;
    runtimeMode;
    constructor(provider, sessionId, approvals, runtimeMode) {
        this.provider = provider;
        this.sessionId = sessionId;
        this.approvals = approvals;
        this.runtimeMode = runtimeMode;
    }
    async requestPermission(params) {
        const allowOptionId = params.options.find((option) => option.kind.startsWith("allow"))?.optionId ?? params.options[0]?.optionId ?? "allow";
        const rejectOptionId = params.options.find((option) => option.kind.startsWith("reject"))?.optionId ?? params.options.at(-1)?.optionId ?? "reject";
        // In full-access mode, auto-approve all tool requests without prompting.
        if (this.runtimeMode === "full-access") {
            return { outcome: { outcome: "selected", optionId: allowOptionId } };
        }
        const requestId = uuidv7();
        const response = new Promise((resolve) => {
            this.approvals.set(requestId, { allowOptionId, rejectOptionId, resolve });
        });
        this.provider.emitEvent({
            type: "tool.approval-required",
            sessionId: this.sessionId,
            tool: params.toolCall.title ?? params.toolCall.toolCallId,
            args: params.toolCall.rawInput,
            requestId,
        });
        return response;
    }
    async sessionUpdate(params) {
        this.provider.handleSessionUpdate(this.sessionId, params);
    }
}
export class AcpProvider {
    id;
    info;
    config;
    authKind;
    sessions = new Map();
    emitter = new EventEmitter();
    authLoginProcess = null;
    cachedModels = null;
    cachedAuthStatus = null;
    authProbeInFlight = null;
    constructor(config) {
        this.id = config.id;
        this.authKind = config.auth === false ? null : "acp";
        this.config = {
            ...config,
            args: config.args ?? [],
            modes: config.modes ?? ["full-access", "supervised"],
            auth: config.auth ?? false,
        };
        this.info = {
            id: config.id,
            name: config.name,
            description: config.description,
            available: false,
            modes: this.config.modes,
            auth: this.authKind ? { login: true, logout: false, deviceCode: false } : NO_PROVIDER_AUTH,
        };
    }
    async checkAvailability() {
        const command = this.config.command;
        const probe = spawnSync(command, ["--version"], {
            stdio: "ignore",
            shell: false,
            env: { ...process.env, ...this.config.env },
        });
        if (probe.error && command !== "npx") {
            this.info.available = false;
            this.info.unavailableReason = `ACP provider command not found: ${command}`;
            return false;
        }
        this.info.available = true;
        this.info.unavailableReason = undefined;
        return true;
    }
    async listModels() {
        if (this.cachedModels)
            return this.cachedModels;
        const probe = await this.probeAcpAuth();
        try {
            const newSession = await withTimeout(probe.connection.newSession({
                cwd: process.cwd(),
                mcpServers: [],
            }), ACP_PROBE_TIMEOUT_MS);
            const models = [];
            const modelState = newSession.models;
            if (modelState && modelState.availableModels?.length) {
                for (const model of modelState.availableModels) {
                    models.push({
                        id: model.modelId,
                        name: model.name,
                        description: model.description ?? undefined,
                        isDefault: model.modelId === modelState.currentModelId,
                    });
                }
            }
            await probe.connection.closeSession?.({ sessionId: newSession.sessionId }).catch(() => { });
            this.cachedModels = models;
            return models;
        }
        catch (error) {
            const message = extractErrorMessage(error, "ACP model discovery failed");
            if (isAuthenticationRequiredMessage(message) && this.hasLocalProviderCredential()) {
                throw new Error(`${this.info.name} is logged in, but the provider rejected model discovery: ${message}. Check provider usage limits or account access.`);
            }
            throw error instanceof Error ? error : new Error(message);
        }
        finally {
            probe.child.kill();
        }
    }
    async getAuthStatus() {
        if (!this.authKind) {
            return { ...NO_PROVIDER_AUTH, authenticated: null, detail: "Auth is managed by the ACP agent." };
        }
        // Return cached result if still fresh (30s TTL).
        const now = Date.now();
        if (this.cachedAuthStatus && now < this.cachedAuthStatus.expiresAt) {
            return this.cachedAuthStatus.status;
        }
        // Deduplicate concurrent callers — only one probe at a time.
        if (this.authProbeInFlight)
            return this.authProbeInFlight;
        this.authProbeInFlight = this._computeAuthStatus().finally(() => {
            this.authProbeInFlight = null;
        });
        return this.authProbeInFlight;
    }
    async _computeAuthStatus() {
        // Claude Code supports env-var API-key auth outside the CLI credential store.
        if (this.id === "claude-code" && Boolean(process.env.ANTHROPIC_API_KEY?.trim())) {
            const cliAuthenticated = await this.getProviderCliAuthenticated();
            const logout = cliAuthenticated === true;
            const status = {
                login: false,
                logout,
                deviceCode: false,
                authenticated: true,
                detail: logout
                    ? "Authenticated via ANTHROPIC_API_KEY environment variable and local Claude Code credentials. Logout clears the local Claude Code credentials."
                    : "Authenticated via ANTHROPIC_API_KEY environment variable. Manage the key directly to change access.",
            };
            this.cachedAuthStatus = { status, expiresAt: Date.now() + 30_000 };
            return status;
        }
        const providerCliAuthenticated = await this.getProviderCliAuthenticated();
        const probe = await this.probeAcpAuth().catch(() => null);
        const login = (probe?.authMethods.length ?? 0) > 0;
        const acpLogout = Boolean(probe?.initialized.agentCapabilities?.auth?.logout);
        probe?.child.kill();
        const authenticated = this.id === "codex"
            ? await this.checkProviderAuthenticated()
            : providerCliAuthenticated ?? await this.checkProviderAuthenticated();
        // Only offer logout when authenticated via a revocable credential.
        const logout = this.id === "codex" ? (providerCliAuthenticated === true || this.hasCodexAuthFile()) : authenticated === true && (acpLogout || this.hasProviderCliLogout());
        const result = {
            login,
            logout,
            deviceCode: false,
            authenticated,
            detail: probe
                ? formatAcpAuthDetail(this.info.name, authenticated)
                : formatCliAuthDetail(this.info.name, authenticated, logout),
        };
        this.cachedAuthStatus = { status: result, expiresAt: Date.now() + 30_000 };
        return result;
    }
    async startLogin() {
        if (!this.authKind)
            return unsupportedLogin(this.id, "Auth is managed by the ACP agent.");
        if (this.authLoginProcess) {
            killAuthChildTree(this.authLoginProcess);
            this.authLoginProcess = null;
        }
        if (this.id === "codex") {
            return this.startCodexDeviceLogin("ChatGPT");
        }
        const probe = await this.probeAcpAuth();
        const method = chooseAcpAuthMethod(probe.authMethods);
        if (!method) {
            probe.child.kill();
            return unsupportedLogin(this.id, "ACP agent did not advertise a login method.");
        }
        if (isTerminalAuthMethod(method)) {
            probe.child.kill();
            const { result, child } = await startDeviceLoginCommand({
                providerId: this.id,
                label: method.name,
                commandLine: this.config.command,
                args: [...this.config.args, ...(method.args ?? [])],
                env: { ...this.config.env, ...method.env },
            });
            if (child) {
                this.authLoginProcess = child;
                child.on("exit", () => {
                    if (this.authLoginProcess === child)
                        this.authLoginProcess = null;
                    this.cachedModels = null;
                    this.cachedAuthStatus = null; // invalidate so next /api/providers reflects new state
                    void this.checkAvailability();
                });
            }
            else {
                this.cachedModels = null;
                this.cachedAuthStatus = null;
                void this.checkAvailability();
            }
            return result;
        }
        const child = probe.child;
        this.authLoginProcess = child;
        void probe.connection.authenticate({ methodId: method.id })
            .catch(() => { })
            .finally(() => {
            if (this.authLoginProcess === child)
                this.authLoginProcess = null;
            child.kill();
            this.cachedModels = null;
            this.cachedAuthStatus = null; // invalidate so next /api/providers reflects new state
            void this.checkAvailability();
        });
        return {
            ok: true,
            status: "started",
            providerId: this.id,
            message: `${method.name} login started through ACP.`,
        };
    }
    async startCodexDeviceLogin(label) {
        const { result, child } = await startDeviceLoginCommand({
            providerId: this.id,
            label,
            commandLine: this.config.env?.JAIT_CODEX_LOGIN_COMMAND ?? process.env.JAIT_CODEX_LOGIN_COMMAND ?? "codex",
            args: ["login", "--device-auth"],
            env: this.config.env,
        });
        if (child) {
            this.authLoginProcess = child;
            child.on("exit", () => {
                if (this.authLoginProcess === child)
                    this.authLoginProcess = null;
                this.cachedModels = null;
                this.cachedAuthStatus = null; // invalidate so next /api/providers reflects new state
                void this.checkAvailability();
            });
        }
        else {
            this.cachedModels = null;
            this.cachedAuthStatus = null;
            void this.checkAvailability();
        }
        return result;
    }
    async checkProviderAuthenticated() {
        if (this.id === "codex") {
            const cliAuthenticated = await this.getProviderCliAuthenticated();
            return cliAuthenticated === true || checkCodexAuthFile();
        }
        if (this.id === "claude-code") {
            const status = await runAuthCommand(this.id, "claude", ["auth", "status"], 10_000).catch(() => null);
            return Boolean(process.env.ANTHROPIC_API_KEY?.trim()) || Boolean(status?.ok);
        }
        return null;
    }
    async getProviderCliAuthenticated() {
        if (this.id === "codex") {
            const status = await runAuthCommand(this.id, "codex", ["login", "status"], 10_000).catch(() => null);
            return status ? status.ok : null;
        }
        if (this.id === "claude-code") {
            const status = await runAuthCommand(this.id, "claude", ["auth", "status"], 10_000).catch(() => null);
            return status ? status.ok : null;
        }
        return null;
    }
    hasProviderCliLogout() {
        return this.id === "codex" || this.id === "claude-code";
    }
    /**
     * Returns true when Codex has local auth.json credentials that can be cleared
     * from the UI.
     */
    hasCodexAuthFile() {
        if (this.id !== "codex")
            return false;
        return checkCodexAuthFile();
    }
    hasLocalProviderCredential() {
        if (this.id === "codex")
            return checkCodexAuthFile();
        if (this.id === "claude-code")
            return Boolean(process.env.ANTHROPIC_API_KEY?.trim());
        return this.cachedAuthStatus?.status.authenticated === true;
    }
    async logout() {
        if (this.authLoginProcess) {
            killAuthChildTree(this.authLoginProcess);
            this.authLoginProcess = null;
        }
        if (!this.authKind) {
            return unsupportedLogout(this.id, "Auth is managed by the ACP agent.");
        }
        const probe = await this.probeAcpAuth().catch(() => null);
        try {
            if (probe?.initialized.agentCapabilities?.auth?.logout) {
                await probe.connection.unstable_logout({}).catch(() => { });
            }
            // Always delete the local Codex auth file for Codex providers — this is the
            // source of truth that checkProviderAuthenticated() reads, regardless of whether
            // the ACP logout RPC succeeded or even ran.
            if (this.id === "codex") {
                if (!process.env.CODEX_HOME) {
                    await runAuthCommand(this.id, "codex", ["logout"], 20_000).catch(() => null);
                }
                removeCodexAuthFiles();
                this.cachedAuthStatus = null; // invalidate cache
                return {
                    ok: true,
                    status: "completed",
                    providerId: this.id,
                    message: `${this.info.name} logout completed.`,
                };
            }
            if (!probe) {
                if (this.id === "claude-code") {
                    const result = await runAuthCommand(this.id, "claude", ["auth", "logout"], 20_000);
                    this.cachedAuthStatus = null;
                    return result.ok
                        ? {
                            ok: true,
                            status: "completed",
                            providerId: this.id,
                            message: `${this.info.name} logout completed.`,
                            rawOutput: result.rawOutput,
                        }
                        : result;
                }
                return unsupportedLogout(this.id, "Could not connect to ACP agent.");
            }
            if (!probe.initialized.agentCapabilities?.auth?.logout) {
                if (this.id === "claude-code") {
                    const result = await runAuthCommand(this.id, "claude", ["auth", "logout"], 20_000);
                    this.cachedAuthStatus = null;
                    return result.ok
                        ? {
                            ok: true,
                            status: "completed",
                            providerId: this.id,
                            message: `${this.info.name} logout completed.`,
                            rawOutput: result.rawOutput,
                        }
                        : result;
                }
                return unsupportedLogout(this.id, "ACP agent did not advertise logout support.");
            }
            return {
                ok: true,
                status: "completed",
                providerId: this.id,
                message: `${this.info.name} logout completed.`,
            };
        }
        finally {
            probe?.child.kill();
            await this.checkAvailability().catch(() => false);
        }
    }
    async startSession(options) {
        const sessionId = uuidv7();
        const session = {
            id: sessionId,
            providerId: this.id,
            threadId: options.threadId,
            status: "starting",
            runtimeMode: options.mode,
            startedAt: new Date().toISOString(),
        };
        const child = spawn(this.config.command, this.config.args, {
            cwd: options.workingDirectory,
            stdio: ["pipe", "pipe", "pipe"],
            env: { ...process.env, ...this.config.env, ...options.env },
            shell: needsShell(this.config.command),
        });
        child.on("error", () => { }); // prevent unhandled ENOENT on Windows
        child.stderr?.on("data", (chunk) => {
            const text = chunk.toString("utf8").trim();
            if (text) {
                this.emitEvent({ type: "activity", sessionId, kind: "stderr", summary: text });
            }
        });
        const approvals = new Map();
        let capturedAgent = null;
        const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
        const connection = new ClientSideConnection((agent) => {
            capturedAgent = agent;
            return new JaitAcpClient(this, sessionId, approvals, options.mode);
        }, stream);
        child.once("exit", (code, signal) => {
            const current = this.sessions.get(sessionId);
            if (!current)
                return;
            current.session.status = current.session.status === "completed" ? "completed" : "interrupted";
            current.session.completedAt = new Date().toISOString();
            this.emitEvent({
                type: current.session.status === "completed" ? "session.completed" : "session.error",
                sessionId,
                ...(current.session.status === "completed"
                    ? {}
                    : { error: `ACP provider exited (${signal ?? code ?? "unknown"})` }),
            });
            this.sessions.delete(sessionId);
        });
        let initialized;
        let newSession;
        try {
            initialized = await connection.initialize({
                protocolVersion: PROTOCOL_VERSION,
                clientInfo: { name: "Jait", version: "0.1" },
                clientCapabilities: {
                    fs: { readTextFile: false, writeTextFile: false },
                    terminal: false,
                },
            });
            newSession = await connection.newSession({
                cwd: options.workingDirectory,
                mcpServers: (options.mcpServers ?? []).map(toAcpMcpServer),
            });
            if (options.mode) {
                await connection.setSessionMode({ sessionId: newSession.sessionId, modeId: options.mode }).catch(() => { });
            }
            if (options.model) {
                await connection.unstable_setSessionModel({ sessionId: newSession.sessionId, modelId: options.model }).catch(() => { });
            }
        }
        catch (error) {
            child.kill();
            throw error;
        }
        session.status = "running";
        const state = {
            session,
            child,
            connection,
            agent: capturedAgent ?? connection,
            acpSessionId: newSession.sessionId,
            approvals,
            initialized,
        };
        this.sessions.set(sessionId, state);
        this.emitEvent({ type: "session.started", sessionId });
        return session;
    }
    async sendTurn(sessionId, message, attachments) {
        const state = this.getSession(sessionId);
        this.emitEvent({ type: "turn.started", sessionId });
        const prompt = [
            { type: "text", text: message },
            ...(attachments ?? []).map((path) => ({
                type: "resource_link",
                uri: `file://${path}`,
                name: path.split("/").pop() || path,
            })),
        ];
        try {
            const result = await state.connection.prompt({
                sessionId: state.acpSessionId,
                prompt,
            });
            if (result.stopReason === "cancelled") {
                this.emitEvent({ type: "session.error", sessionId, error: "Turn cancelled" });
            }
            this.emitEvent({ type: "turn.completed", sessionId });
        }
        catch (error) {
            const messageText = error instanceof Error ? error.message : "ACP prompt failed";
            state.session.status = "error";
            state.session.error = messageText;
            this.emitEvent({ type: "session.error", sessionId, error: messageText });
            throw error;
        }
    }
    async interruptTurn(sessionId) {
        const state = this.getSession(sessionId);
        await state.connection.cancel({ sessionId: state.acpSessionId });
    }
    async respondToApproval(sessionId, requestId, approved) {
        const state = this.getSession(sessionId);
        const pending = state.approvals.get(requestId);
        if (!pending)
            return;
        state.approvals.delete(requestId);
        pending.resolve({
            outcome: approved
                ? { outcome: "selected", optionId: pending.allowOptionId }
                : { outcome: "selected", optionId: pending.rejectOptionId },
        });
    }
    async stopSession(sessionId) {
        const state = this.sessions.get(sessionId);
        if (!state)
            return;
        await withTimeout(state.connection.closeSession?.({ sessionId: state.acpSessionId }) ?? Promise.resolve(), 2_000).catch(() => { });
        state.session.status = "completed";
        state.session.completedAt = new Date().toISOString();
        state.child.kill();
        this.sessions.delete(sessionId);
        this.emitEvent({ type: "session.completed", sessionId });
    }
    onEvent(handler) {
        this.emitter.on("event", handler);
        return () => this.emitter.off("event", handler);
    }
    emitEvent(event) {
        this.emitter.emit("event", event);
    }
    handleSessionUpdate(sessionId, params) {
        const update = params.update;
        switch (update.sessionUpdate) {
            case "agent_message_chunk":
                if (update.content.type === "text") {
                    this.emitEvent({ type: "token", sessionId, content: update.content.text });
                }
                break;
            case "tool_call":
                this.emitEvent({
                    type: "tool.start",
                    sessionId,
                    tool: update.title,
                    args: update.rawInput,
                    callId: update.toolCallId,
                });
                break;
            case "tool_call_update": {
                if (update.content) {
                    this.emitEvent({
                        type: "tool.output",
                        sessionId,
                        callId: update.toolCallId,
                        content: stringifyToolContent(update.content),
                    });
                }
                if (update.status === "completed" || update.status === "failed") {
                    this.emitEvent({
                        type: "tool.result",
                        sessionId,
                        tool: update.title ?? update.toolCallId,
                        ok: update.status === "completed",
                        message: stringifyUnknown(update.rawOutput ?? update.status),
                        callId: update.toolCallId,
                        data: update.rawOutput,
                    });
                }
                break;
            }
            case "plan":
                this.emitEvent({
                    type: "activity",
                    sessionId,
                    kind: "plan",
                    summary: update.entries.map((entry) => `[${entry.status}] ${entry.content}`).join("\n"),
                    payload: update.entries,
                });
                break;
            case "agent_thought_chunk":
            case "user_message_chunk":
            case "available_commands_update":
            case "current_mode_update":
            case "config_option_update":
            case "session_info_update":
            case "usage_update":
                this.emitEvent({
                    type: "activity",
                    sessionId,
                    kind: update.sessionUpdate,
                    summary: update.sessionUpdate,
                    payload: update,
                });
                break;
        }
    }
    getSession(sessionId) {
        const state = this.sessions.get(sessionId);
        if (!state)
            throw new Error(`Unknown ACP session: ${sessionId}`);
        return state;
    }
    async probeAcpAuth() {
        const child = spawn(this.config.command, this.config.args, {
            cwd: process.cwd(),
            stdio: ["pipe", "pipe", "pipe"],
            env: { ...process.env, ...this.config.env },
            shell: needsShell(this.config.command),
        });
        // Prevent unhandled 'error' events from crashing the process (e.g. ENOENT on Windows)
        const spawnError = new Promise((_, reject) => {
            child.on("error", (err) => reject(err));
        });
        const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
        const connection = new ClientSideConnection(() => ({
            async requestPermission() {
                return { outcome: { outcome: "cancelled" } };
            },
            async sessionUpdate() { },
        }), stream);
        try {
            const initialized = await Promise.race([
                connection.initialize({
                    protocolVersion: PROTOCOL_VERSION,
                    clientInfo: { name: "Jait", version: "0.1" },
                    clientCapabilities: {
                        auth: { terminal: true },
                        fs: { readTextFile: false, writeTextFile: false },
                        terminal: false,
                    },
                }),
                spawnError,
                new Promise((_, reject) => setTimeout(() => reject(new Error(`ACP auth probe timed out after ${ACP_PROBE_TIMEOUT_MS / 1000}s`)), ACP_PROBE_TIMEOUT_MS)),
            ]);
            return { child, connection, initialized, authMethods: initialized.authMethods ?? [] };
        }
        catch (error) {
            child.kill();
            throw error;
        }
    }
}
/**
 * On Windows, bare command names like `npx` or `codex` resolve to `.cmd` wrappers
 * that require the shell to execute. Absolute or relative paths are real executables
 * and must NOT go through shell (cmd.exe would mangle complex arguments).
 */
function needsShell(command) {
    return process.platform === "win32" && !isAbsolute(command) && !command.includes("/") && !command.includes("\\");
}
function chooseAcpAuthMethod(methods) {
    return (methods.find(isTerminalAuthMethod) ??
        methods.find((method) => method.id === "chat-gpt") ??
        methods[0] ??
        null);
}
function isTerminalAuthMethod(method) {
    return "type" in method && method.type === "terminal";
}
function formatAcpAuthDetail(providerName, authenticated) {
    if (authenticated === true)
        return `${providerName} credentials are configured. Login and logout are managed through ACP.`;
    if (authenticated === false)
        return `${providerName} credentials are not configured. Login and logout are managed through ACP.`;
    return `${providerName} authentication is managed through ACP.`;
}
function formatCliAuthDetail(providerName, authenticated, logout) {
    if (authenticated === true && logout)
        return `${providerName} credentials are configured. Logout is available through the provider CLI.`;
    if (authenticated === true)
        return `${providerName} credentials are configured, but Jait could not find a logout method.`;
    if (authenticated === false)
        return `${providerName} credentials are not configured.`;
    return `Could not read ${providerName} authentication capabilities.`;
}
function extractErrorMessage(error, fallback) {
    if (error instanceof Error && error.message.trim())
        return error.message;
    if (typeof error === "string" && error.trim())
        return error;
    return fallback;
}
function isAuthenticationRequiredMessage(message) {
    const lower = message.trim().toLowerCase();
    return lower === "authentication required"
        || lower === "not authenticated"
        || lower.includes("not logged in")
        || lower.includes("login required")
        || lower.includes("credentials are not configured");
}
function getCodexAuthPaths() {
    if (process.env.CODEX_HOME)
        return [join(process.env.CODEX_HOME, "auth.json")];
    return [
        join(homedir(), ".codex", "auth.json"),
        process.env.USERPROFILE ? join(process.env.USERPROFILE, ".codex", "auth.json") : undefined,
        process.env.HOME ? join(process.env.HOME, ".codex", "auth.json") : undefined,
    ].filter((path, index, paths) => Boolean(path) && paths.indexOf(path) === index);
}
function readCodexAuthFile() {
    for (const authPath of getCodexAuthPaths()) {
        try {
            if (!existsSync(authPath))
                continue;
            const raw = readFileSync(authPath, "utf-8");
            return JSON.parse(raw);
        }
        catch {
            continue;
        }
    }
    return null;
}
function checkCodexAuthFile() {
    const auth = readCodexAuthFile();
    if (!auth)
        return false;
    try {
        if (auth.OPENAI_API_KEY)
            return true;
        if (auth.tokens?.access_token)
            return true;
        return false;
    }
    catch {
        return false;
    }
}
function removeCodexAuthFiles() {
    for (const authPath of getCodexAuthPaths()) {
        if (existsSync(authPath))
            rmSync(authPath, { force: true });
    }
}
function toAcpMcpServer(server) {
    if (server.transport === "stdio") {
        return {
            name: server.name,
            command: server.command ?? "",
            args: server.args ?? [],
            env: Object.entries(server.env ?? {}).map(([name, value]) => ({ name, value })),
        };
    }
    if (server.transport === "http") {
        return {
            type: "http",
            name: server.name,
            url: server.url ?? "",
            headers: [],
        };
    }
    return {
        type: "sse",
        name: server.name,
        url: server.url ?? "",
        headers: [],
    };
}
function stringifyToolContent(content) {
    if (!Array.isArray(content))
        return stringifyUnknown(content);
    return content.map((item) => stringifyUnknown(item)).join("\n");
}
function stringifyUnknown(value) {
    if (typeof value === "string")
        return value;
    try {
        return JSON.stringify(value);
    }
    catch {
        return String(value);
    }
}
function withTimeout(promise, timeoutMs) {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => reject(new Error("Operation timed out")), timeoutMs);
        promise.then((value) => {
            clearTimeout(timeout);
            resolve(value);
        }, (error) => {
            clearTimeout(timeout);
            reject(error);
        });
    });
}
export function loadAcpProviderConfigs() {
    const defaults = [
        {
            id: "codex",
            name: "Codex",
            description: "OpenAI Codex via Agent Client Protocol",
            command: "npx",
            args: ["-y", "@agentclientprotocol/codex-acp"],
        },
        {
            id: "claude-code",
            name: "Claude Code",
            description: "Claude Code via Agent Client Protocol",
            command: "npx",
            args: ["-y", "@agentclientprotocol/claude-agent-acp"],
        },
        {
            id: "cursor",
            name: "Cursor",
            description: "Cursor Agent via Agent Client Protocol",
            command: "npx",
            args: ["-y", "@blowmage/cursor-agent-acp"],
            auth: false,
        },
        {
            id: "pi",
            name: "Pi",
            description: "Pi coding agent via Agent Client Protocol",
            command: "npx",
            args: ["-y", "pi-acp"],
            auth: false,
        },
        {
            id: "pi-gemini",
            name: "Pi Gemini",
            description: "Gemini-backed Pi ACP provider",
            command: "npx",
            args: ["-y", "pi-gemini-acp"],
            auth: false,
        },
        {
            id: "deepagents",
            name: "DeepAgents",
            description: "DeepAgents via Agent Client Protocol",
            command: "npx",
            args: ["-y", "deepagents-acp"],
            auth: false,
        },
    ];
    const raw = process.env.JAIT_ACP_PROVIDERS?.trim();
    if (!raw)
        return defaults;
    try {
        const parsed = JSON.parse(raw);
        if (!Array.isArray(parsed))
            return defaults;
        return parsed.filter((item) => item && typeof item.id === "string" && typeof item.command === "string");
    }
    catch {
        return defaults;
    }
}
//# sourceMappingURL=acp-provider.js.map