import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { Readable, Writable } from "node:stream";
import { ClientSideConnection, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
import { getAgentStreamEventTurnId, } from "../agent-sdk-types.js";
import { checkProviderLaunchAvailable, createProviderEnvSpec, resolveProviderLaunch, } from "../provider-launch-config.js";
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
import { platformShell, spawnProcess } from "../../../utils/spawn.js";
function assertChildWithPipes(child) {
    if (!child.stdin || !child.stdout || !child.stderr) {
        throw new Error("Child process did not expose stdio pipes");
    }
}
function isRecord(value) {
    return value != null && typeof value === "object" && !Array.isArray(value);
}
function isACPError(value) {
    return isRecord(value) && typeof value.message === "string" && typeof value.code === "number";
}
function summarizeACPRequestError(error) {
    // Promise rejections are untyped, but the ACP SDK rejects JSON-RPC failures as response.error.
    if (isACPError(error)) {
        const code = String(error.code);
        const data = error.data === undefined ? "" : ` | data=${JSON.stringify(error.data)}`;
        return {
            message: error.message,
            code,
            diagnostic: `${error.message} | code=${code}${data}`,
        };
    }
    if (error instanceof Error) {
        return { message: error.message };
    }
    return { message: String(error) };
}
function resolveTerminalCommand(command, args) {
    if (args && args.length > 0) {
        return { command, args };
    }
    if (!/\s/.test(command.trim())) {
        return { command, args: [] };
    }
    const shell = platformShell();
    return { command: shell.command, args: [...shell.flag, command] };
}
const DEFAULT_ACP_CAPABILITIES = {
    supportsStreaming: true,
    supportsSessionPersistence: true,
    supportsDynamicModes: true,
    supportsMcpServers: true,
    supportsReasoningStream: true,
    supportsToolInvocations: true,
    supportsRewindConversation: false,
    supportsRewindFiles: false,
    supportsRewindBoth: false,
};
const ACP_CLIENT_CAPABILITIES = {
    fs: {
        readTextFile: true,
        writeTextFile: true,
    },
    terminal: true,
};
// Suppress interactive auth side-effects (e.g. Gemini CLI opening a Google
// sign-in URL in the browser) when probing an ACP agent for models/modes.
// NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
const PROBE_ENV = { NO_BROWSER: "true" };
function summarizeMalformedACPStdoutError(error) {
    return {
        type: error instanceof Error ? error.name : typeof error,
        message: "ACP stdout line was not valid JSON",
    };
}
function normalizeACPIncomingMessage(message) {
    if ("id" in message &&
        !("method" in message) &&
        typeof message.id === "string" &&
        /^\d+$/.test(message.id)) {
        const numericId = Number(message.id);
        if (Number.isSafeInteger(numericId)) {
            return {
                ...message,
                // COMPAT(deepseek-tui-acp-id): added v0.1.78, remove after 2026-11-19
                // once the ACP SDK accepts stringified numeric response IDs.
                id: numericId,
            };
        }
    }
    return message;
}
export function createLoggedNdJsonStream(output, input, options) {
    const textEncoder = new TextEncoder();
    const textDecoder = new TextDecoder();
    const readable = new ReadableStream({
        async start(controller) {
            let content = "";
            const reader = input.getReader();
            try {
                while (true) {
                    const { value, done } = await reader.read();
                    if (done) {
                        break;
                    }
                    if (!value) {
                        continue;
                    }
                    content += textDecoder.decode(value, { stream: true });
                    const lines = content.split("\n");
                    content = lines.pop() || "";
                    for (const line of lines) {
                        const trimmedLine = line.trim();
                        if (!trimmedLine) {
                            continue;
                        }
                        try {
                            const message = JSON.parse(trimmedLine);
                            controller.enqueue(normalizeACPIncomingMessage(message));
                        }
                        catch (error) {
                            options.logger.warn({
                                err: summarizeMalformedACPStdoutError(error),
                                provider: options.provider,
                            }, "ACP agent emitted non-JSON stdout; ignoring line");
                        }
                    }
                }
            }
            finally {
                reader.releaseLock();
                controller.close();
            }
        },
    });
    const writable = new WritableStream({
        async write(message) {
            const writer = output.getWriter();
            try {
                await writer.write(textEncoder.encode(`${JSON.stringify(message)}\n`));
            }
            finally {
                writer.releaseLock();
            }
        },
    });
    return { readable, writable };
}
export function mapACPUsage(usage) {
    if (!usage) {
        return undefined;
    }
    return {
        inputTokens: usage.inputTokens ?? undefined,
        outputTokens: usage.outputTokens ?? undefined,
        cachedInputTokens: usage.cachedReadTokens ?? undefined,
    };
}
export function resolveACPModeSelection({ modeId, availableModes, configOptions, }) {
    const configOption = findSelectConfigOption({ configOptions, category: "mode" });
    return {
        availableMode: availableModes.find((mode) => mode.id === modeId) ?? null,
        configOption,
        configChoice: findSelectConfigChoice({ option: configOption, value: modeId }),
        hasAvailableModes: availableModes.length > 0,
    };
}
export function resolveACPModelSelection({ modelId, availableModels, configOptions, }) {
    const configOption = findSelectConfigOption({ configOptions, category: "model" });
    return {
        availableModel: availableModels?.find((model) => model.modelId === modelId) ?? null,
        configOption,
        configChoice: findSelectConfigChoice({ option: configOption, value: modelId }),
        hasAvailableModels: Boolean(availableModels?.length),
    };
}
export function deriveModesFromACP(fallbackModes, modeState, configOptions) {
    if (modeState?.availableModes?.length) {
        return {
            modes: modeState.availableModes.map((mode) => ({
                id: mode.id,
                label: mode.name,
                description: mode.description ?? undefined,
            })),
            currentModeId: modeState.currentModeId ?? null,
        };
    }
    const modeOption = findSelectConfigOption({ configOptions, category: "mode" });
    if (modeOption) {
        const flatOptions = flattenSelectOptions(modeOption.options);
        return {
            modes: flatOptions.map((option) => ({
                id: option.value,
                label: option.name,
                description: option.description ?? undefined,
            })),
            currentModeId: modeOption.currentValue,
        };
    }
    return {
        modes: fallbackModes,
        currentModeId: null,
    };
}
export function deriveModelDefinitionsFromACP(provider, models, configOptions) {
    const thinkingOptions = deriveSelectorOptions(configOptions, "thought_level");
    const defaultThinkingOptionId = thinkingOptions.find((option) => option.isDefault)?.id ?? null;
    if (models?.availableModels?.length) {
        return models.availableModels.map((model) => ({
            provider,
            id: model.modelId,
            label: model.name,
            description: model.description ?? undefined,
            isDefault: model.modelId === models.currentModelId,
            thinkingOptions: thinkingOptions.length > 0 ? thinkingOptions : undefined,
            defaultThinkingOptionId: defaultThinkingOptionId ?? undefined,
        }));
    }
    const modelOptions = deriveSelectorOptions(configOptions, "model");
    return modelOptions.map((option) => ({
        provider,
        id: option.id,
        label: option.label,
        description: option.description,
        isDefault: option.isDefault,
        thinkingOptions: thinkingOptions.length > 0 ? thinkingOptions : undefined,
        defaultThinkingOptionId: defaultThinkingOptionId ?? undefined,
        metadata: option.metadata,
    }));
}
export class ACPAgentClient {
    constructor(options) {
        this.provider = options.provider;
        this.capabilities = options.capabilities ?? DEFAULT_ACP_CAPABILITIES;
        this.logger = options.logger.child({
            module: "agent",
            provider: options.provider,
        });
        this.runtimeSettings = options.runtimeSettings;
        this.defaultCommand = options.defaultCommand;
        this.defaultModes = options.defaultModes ?? [];
        this.modelTransformer = options.modelTransformer;
        this.sessionResponseTransformer = options.sessionResponseTransformer;
        this.configOptionsTransformer = options.configOptionsTransformer;
        this.modeIdTransformer = options.modeIdTransformer;
        this.toolSnapshotTransformer = options.toolSnapshotTransformer;
        this.providerModeWriter = options.providerModeWriter;
        this.beforeModeWriter = options.beforeModeWriter;
        this.thinkingOptionWriter = options.thinkingOptionWriter;
        this.waitForInitialCommands = options.waitForInitialCommands ?? false;
        this.initialCommandsWaitTimeoutMs = options.initialCommandsWaitTimeoutMs ?? 1500;
    }
    async createSession(config, launchContext) {
        this.assertProvider(config);
        const session = new ACPAgentSession({ ...config, provider: this.provider }, {
            provider: this.provider,
            logger: this.logger,
            runtimeSettings: this.runtimeSettings,
            defaultCommand: this.defaultCommand,
            defaultModes: this.defaultModes,
            modelTransformer: this.modelTransformer,
            sessionResponseTransformer: this.sessionResponseTransformer,
            configOptionsTransformer: this.configOptionsTransformer,
            modeIdTransformer: this.modeIdTransformer,
            toolSnapshotTransformer: this.toolSnapshotTransformer,
            providerModeWriter: this.providerModeWriter,
            beforeModeWriter: this.beforeModeWriter,
            thinkingOptionWriter: this.thinkingOptionWriter,
            capabilities: this.capabilities,
            agentId: launchContext?.agentId,
            launchEnv: launchContext?.env,
            waitForInitialCommands: this.waitForInitialCommands,
            initialCommandsWaitTimeoutMs: this.initialCommandsWaitTimeoutMs,
        });
        await session.initializeNewSession();
        return session;
    }
    async resumeSession(handle, overrides, launchContext) {
        if (handle.provider !== this.provider) {
            throw new Error(`Cannot resume ${handle.provider} handle with ${this.provider} provider`);
        }
        const storedConfig = coerceSessionConfigMetadata(handle.metadata);
        const cwd = overrides?.cwd ?? storedConfig.cwd;
        if (!cwd) {
            throw new Error(`${this.provider} resume requires the original working directory`);
        }
        const mergedConfig = {
            ...storedConfig,
            ...overrides,
            provider: this.provider,
            cwd,
        };
        const session = new ACPAgentSession(mergedConfig, {
            provider: this.provider,
            logger: this.logger,
            runtimeSettings: this.runtimeSettings,
            defaultCommand: this.defaultCommand,
            defaultModes: this.defaultModes,
            modelTransformer: this.modelTransformer,
            sessionResponseTransformer: this.sessionResponseTransformer,
            configOptionsTransformer: this.configOptionsTransformer,
            modeIdTransformer: this.modeIdTransformer,
            toolSnapshotTransformer: this.toolSnapshotTransformer,
            providerModeWriter: this.providerModeWriter,
            beforeModeWriter: this.beforeModeWriter,
            thinkingOptionWriter: this.thinkingOptionWriter,
            capabilities: this.capabilities,
            handle,
            agentId: launchContext?.agentId,
            launchEnv: launchContext?.env,
            waitForInitialCommands: this.waitForInitialCommands,
            initialCommandsWaitTimeoutMs: this.initialCommandsWaitTimeoutMs,
        });
        await session.initializeResumedSession();
        return session;
    }
    async listModels(options) {
        const { cwd } = options;
        const probe = await this.spawnProcess(PROBE_ENV);
        try {
            const response = await probe.connection.newSession({
                cwd,
                mcpServers: [],
            });
            const transformed = this.transformSessionResponse(response);
            const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
            return this.modelTransformer ? this.modelTransformer(models) : models;
        }
        finally {
            await this.closeProbe(probe);
        }
    }
    async listModes(options) {
        const { cwd } = options;
        const probe = await this.spawnProcess(PROBE_ENV);
        try {
            const response = await probe.connection.newSession({
                cwd,
                mcpServers: [],
            });
            const transformed = this.transformSessionResponse(response);
            const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
            return modeInfo.modes;
        }
        finally {
            await this.closeProbe(probe);
        }
    }
    async listPersistedAgents(options) {
        const probe = await this.spawnProcess(PROBE_ENV);
        try {
            if (!probe.initialize.agentCapabilities?.sessionCapabilities?.list) {
                return [];
            }
            const sessions = [];
            let cursor;
            for (;;) {
                const page = await probe.connection.listSessions(cursor ? { cursor } : {});
                for (const session of page.sessions) {
                    sessions.push({
                        provider: this.provider,
                        sessionId: session.sessionId,
                        cwd: session.cwd,
                        title: session.title ?? null,
                        lastActivityAt: session.updatedAt ? new Date(session.updatedAt) : new Date(0),
                        persistence: {
                            provider: this.provider,
                            sessionId: session.sessionId,
                            nativeHandle: session.sessionId,
                            metadata: {
                                provider: this.provider,
                                cwd: session.cwd,
                                title: session.title ?? null,
                            },
                        },
                        timeline: [],
                    });
                }
                cursor = page.nextCursor ?? null;
                if (!cursor)
                    break;
                if (options?.limit && sessions.length >= options.limit)
                    break;
            }
            return typeof options?.limit === "number" ? sessions.slice(0, options.limit) : sessions;
        }
        finally {
            await this.closeProbe(probe);
        }
    }
    async isAvailable() {
        try {
            await this.resolveLaunchCommand();
            return true;
        }
        catch {
            return false;
        }
    }
    async spawnProcess(launchEnv, options) {
        const { command, args } = await this.resolveLaunchCommand();
        const child = spawnProcess(command, args, {
            cwd: process.cwd(),
            ...createProviderEnvSpec({
                runtimeSettings: this.runtimeSettings,
                overlays: [launchEnv],
            }),
            stdio: ["pipe", "pipe", "pipe"],
        });
        assertChildWithPipes(child);
        const stderrChunks = [];
        child.stderr.on("data", (chunk) => {
            stderrChunks.push(chunk.toString());
        });
        const spawnErrorPromise = new Promise((_, reject) => {
            child.once("error", (error) => {
                const stderr = stderrChunks.join("").trim();
                reject(new Error(stderr ? `${String(error)}\n${stderr}` : String(error)));
            });
        });
        const stream = createLoggedNdJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout), { logger: this.logger, provider: this.provider });
        const connection = new ClientSideConnection(() => this.buildProbeClient(), stream);
        let timeout = null;
        const initializeTimeoutPromise = options?.initializeTimeoutMs
            ? new Promise((_, reject) => {
                timeout = setTimeout(() => {
                    reject(new Error(`ACP initialize timed out after ${options.initializeTimeoutMs}ms`));
                }, options.initializeTimeoutMs);
            })
            : null;
        let initialize;
        try {
            initialize = await Promise.race([
                connection.initialize({
                    protocolVersion: PROTOCOL_VERSION,
                    clientCapabilities: ACP_CLIENT_CAPABILITIES,
                    clientInfo: { name: "Paseo", version: "dev" },
                }),
                spawnErrorPromise,
                ...(initializeTimeoutPromise ? [initializeTimeoutPromise] : []),
            ]);
        }
        catch (error) {
            await terminateChildProcess(child, 2000);
            throw error;
        }
        finally {
            if (timeout) {
                clearTimeout(timeout);
            }
        }
        return { child, connection, initialize };
    }
    buildProbeClient() {
        return {
            async requestPermission() {
                return { outcome: { outcome: "cancelled" } };
            },
            async sessionUpdate() { },
            async readTextFile(params) {
                const content = await fs.readFile(params.path, "utf8");
                return { content };
            },
            async writeTextFile(params) {
                await fs.mkdir(path.dirname(params.path), { recursive: true });
                await fs.writeFile(params.path, params.content, "utf8");
                return {};
            },
            async createTerminal() {
                throw new Error("ACP model probe does not support terminal execution");
            },
        };
    }
    async closeProbe(probe) {
        try {
            if (probe.initialize.agentCapabilities?.sessionCapabilities?.close) {
                // No active session to close here; ignore capability.
            }
        }
        finally {
            await terminateChildProcess(probe.child, 2000);
        }
    }
    async resolveLaunchCommand() {
        const prefix = await resolveProviderLaunch({
            commandConfig: this.runtimeSettings?.command,
            defaultBinary: this.defaultCommand[0],
        });
        const availability = await checkProviderLaunchAvailable(prefix);
        if (!availability.available) {
            throw new Error(`${this.provider} command '${this.defaultCommand[0]}' not found`);
        }
        return {
            command: prefix.command,
            args: [...prefix.args, ...this.defaultCommand.slice(1)],
        };
    }
    assertProvider(config) {
        if (config.provider !== this.provider) {
            throw new Error(`Expected ${this.provider} config, received ${config.provider}`);
        }
    }
    transformSessionResponse(response) {
        const transformed = this.sessionResponseTransformer
            ? this.sessionResponseTransformer(response)
            : response;
        if (!this.configOptionsTransformer || !transformed.configOptions) {
            return transformed;
        }
        return {
            ...transformed,
            configOptions: this.configOptionsTransformer(transformed.configOptions),
        };
    }
}
export class ACPAgentSession {
    constructor(config, options) {
        this.subscribers = new Set();
        this.pendingPermissions = new Map();
        this.messageAssemblies = new Map();
        this.submittedUserMessageIds = new Set();
        this.toolCalls = new Map();
        this.terminalEntries = new Map();
        this.persistedHistory = [];
        this.child = null;
        this.connection = null;
        this.agentCapabilities = null;
        this.sessionId = null;
        this.currentMode = null;
        this.currentModel = null;
        this.availableModels = null;
        this.thinkingOptionId = null;
        this.currentTitle = null;
        this.lastActivityAt = null;
        this.configOptions = [];
        this.cachedCommands = [];
        this.commandsReadyDeferred = null;
        this.commandsReadySettled = false;
        this.activeForegroundTurnId = null;
        this.closed = false;
        this.historyPending = false;
        this.replayingHistory = false;
        this.bootstrapThreadEventPending = false;
        this.provider = options.provider;
        this.capabilities = options.capabilities;
        this.logger = options.logger.child({ module: "agent", provider: options.provider });
        this.runtimeSettings = options.runtimeSettings;
        this.defaultCommand = options.defaultCommand;
        this.defaultModes = options.defaultModes;
        this.modelTransformer = options.modelTransformer;
        this.sessionResponseTransformer = options.sessionResponseTransformer;
        this.configOptionsTransformer = options.configOptionsTransformer;
        this.modeIdTransformer = options.modeIdTransformer;
        this.toolSnapshotTransformer = options.toolSnapshotTransformer;
        this.providerModeWriter = options.providerModeWriter;
        this.beforeModeWriter = options.beforeModeWriter;
        this.thinkingOptionWriter = options.thinkingOptionWriter;
        this.availableModes = options.defaultModes;
        this.agentId = options.agentId;
        this.launchEnv = options.launchEnv;
        this.initialHandle = options.handle;
        this.config = { ...config, provider: options.provider };
        this.currentMode = config.modeId ?? null;
        this.currentModel = config.model ?? null;
        this.thinkingOptionId = config.thinkingOptionId ?? null;
        this.currentTitle = config.title ?? null;
        this.waitForInitialCommands = options.waitForInitialCommands ?? false;
        this.initialCommandsWaitTimeoutMs = options.initialCommandsWaitTimeoutMs ?? 1500;
    }
    get id() {
        return this.sessionId;
    }
    async initializeNewSession() {
        const spawned = await this.spawnProcess();
        this.child = spawned.child;
        this.connection = spawned.connection;
        this.agentCapabilities = spawned.initialize.agentCapabilities ?? null;
        const response = await this.connection.newSession({
            cwd: this.config.cwd,
            mcpServers: normalizeMcpServers(this.config.mcpServers),
        });
        this.sessionId = response.sessionId;
        this.bootstrapThreadEventPending = true;
        this.applySessionState(response);
        await this.applyConfiguredOverrides();
    }
    async initializeResumedSession() {
        const handle = this.initialHandle;
        if (!handle) {
            throw new Error("Resume requested without persistence handle");
        }
        const spawned = await this.spawnProcess();
        this.child = spawned.child;
        this.connection = spawned.connection;
        this.agentCapabilities = spawned.initialize.agentCapabilities ?? null;
        this.sessionId = handle.sessionId;
        this.bootstrapThreadEventPending = true;
        const sessionCapabilities = this.agentCapabilities?.sessionCapabilities;
        if (this.agentCapabilities?.loadSession) {
            this.replayingHistory = true;
            const response = await this.connection.loadSession({
                sessionId: handle.sessionId,
                cwd: this.config.cwd,
                mcpServers: normalizeMcpServers(this.config.mcpServers),
            });
            this.replayingHistory = false;
            this.historyPending = this.persistedHistory.length > 0;
            this.applySessionState(response);
        }
        else if (sessionCapabilities?.resume) {
            const response = await this.connection.unstable_resumeSession({
                sessionId: handle.sessionId,
                cwd: this.config.cwd,
                mcpServers: normalizeMcpServers(this.config.mcpServers),
            });
            this.applySessionState(response);
        }
        else {
            throw new Error(`${this.provider} does not support ACP session resume`);
        }
        await this.applyConfiguredOverrides();
    }
    async run(prompt, options) {
        const result = await runProviderTurn({
            prompt,
            runOptions: options,
            startTurn: (p, o) => this.startTurn(p, o),
            subscribe: (callback) => this.subscribe(callback),
            getSessionId: () => this.sessionId ?? "",
            reduceFinalText: appendOrReplaceGrowingAssistantMessage,
        });
        if (!this.sessionId) {
            throw new Error("ACP session did not expose a session id");
        }
        return result;
    }
    async startTurn(prompt, options) {
        if (this.closed) {
            throw new Error(`${this.provider} session is closed`);
        }
        if (!this.connection || !this.sessionId) {
            throw new Error(`${this.provider} session is not initialized`);
        }
        if (this.activeForegroundTurnId) {
            throw new Error("A foreground turn is already active");
        }
        const turnId = randomUUID();
        const messageId = options?.messageId ?? randomUUID();
        this.activeForegroundTurnId = turnId;
        this.emitBootstrapThreadEvent();
        this.pushEvent({ type: "turn_started", provider: this.provider, turnId });
        this.emitSubmittedUserMessage(prompt, messageId, turnId);
        void this.connection
            .prompt({
            sessionId: this.sessionId,
            messageId,
            prompt: toACPContentBlocks(prompt),
        })
            .then((response) => {
            this.handlePromptResponse(response, turnId);
            return;
        })
            .catch((error) => {
            const summary = summarizeACPRequestError(error);
            this.finishTurn({
                type: "turn_failed",
                provider: this.provider,
                error: summary.message,
                code: summary.code,
                diagnostic: this.collectDiagnostic(summary.diagnostic ?? summary.message),
                turnId,
            });
        });
        return { turnId };
    }
    subscribe(callback) {
        this.subscribers.add(callback);
        if (this.sessionId) {
            callback({
                type: "thread_started",
                provider: this.provider,
                sessionId: this.sessionId,
            });
        }
        return () => {
            this.subscribers.delete(callback);
        };
    }
    async *streamHistory() {
        if (!this.historyPending || this.persistedHistory.length === 0) {
            return;
        }
        const history = [...this.persistedHistory];
        this.persistedHistory.length = 0;
        this.historyPending = false;
        for (const item of history) {
            yield { type: "timeline", provider: this.provider, item };
        }
    }
    async getRuntimeInfo() {
        return this.runtimeInfo();
    }
    async getAvailableModes() {
        return [...this.availableModes];
    }
    async getCurrentMode() {
        return this.currentMode;
    }
    ensureCommandsReadyDeferred() {
        if (this.commandsReadyDeferred || this.commandsReadySettled || this.cachedCommands.length > 0) {
            return;
        }
        let resolve;
        const promise = new Promise((r) => {
            resolve = r;
        });
        this.commandsReadyDeferred = { promise, resolve };
    }
    settleCommandsReady() {
        if (this.commandsReadySettled) {
            return;
        }
        this.commandsReadySettled = true;
        this.commandsReadyDeferred?.resolve();
        this.commandsReadyDeferred = null;
    }
    async waitForCommandsReady() {
        const deferred = this.commandsReadyDeferred;
        if (!deferred) {
            return;
        }
        let timer = null;
        try {
            await Promise.race([
                deferred.promise,
                new Promise((resolve) => {
                    timer = setTimeout(resolve, this.initialCommandsWaitTimeoutMs);
                }),
            ]);
        }
        finally {
            if (timer) {
                clearTimeout(timer);
            }
        }
    }
    async listCommands() {
        if (this.cachedCommands.length > 0) {
            return this.cachedCommands;
        }
        if (!this.waitForInitialCommands || this.closed) {
            return this.cachedCommands;
        }
        this.ensureCommandsReadyDeferred();
        await this.waitForCommandsReady();
        this.settleCommandsReady();
        return this.cachedCommands;
    }
    async setMode(modeId) {
        if (!this.connection || !this.sessionId) {
            throw new Error("ACP session not initialized");
        }
        const selection = resolveACPModeSelection({
            modeId,
            availableModes: this.availableModes,
            configOptions: this.configOptions,
        });
        await this.setModeWithSelection({ modeId, selection });
    }
    // Mode/model selection updates stay after ACP RPC success; this intentionally diverges from Zed's optimistic rollback path (acp.rs:3080-3104).
    async setModeWithSelection({ modeId, selection, }) {
        if (!this.connection || !this.sessionId) {
            throw new Error("ACP session not initialized");
        }
        const context = this.createProviderModeWriterContext(modeId, selection);
        const providerResult = this.providerModeWriter
            ? await this.providerModeWriter(context)
            : { handled: false };
        if (providerResult.handled) {
            this.currentMode = providerResult.currentModeId ?? modeId;
            if (providerResult.configOptions) {
                this.configOptions = this.transformConfigOptions(providerResult.configOptions);
            }
            this.availableModes = deriveModesFromACP(this.defaultModes, null, this.configOptions).modes;
            this.pushEvent({
                type: "mode_changed",
                provider: this.provider,
                currentModeId: this.currentMode,
                availableModes: [...this.availableModes],
            });
            return;
        }
        if (selection.hasAvailableModes) {
            if (!selection.availableMode) {
                this.warnInvalidSelection(modeId, `is not valid ${this.provider} mode. Available options: ${this.availableModes
                    .map((mode) => mode.id)
                    .join(", ")}`);
                return;
            }
        }
        else {
            const modeOption = selection.configOption;
            if (!modeOption) {
                throw new Error(`${this.provider} does not expose ACP mode switching`);
            }
            if (!selection.configChoice) {
                this.warnInvalidSelection(modeId, `is not valid ${this.provider} mode config option. Available options: ${flattenSelectOptions(modeOption.options)
                    .map((option) => option.value)
                    .join(", ")}`);
                return;
            }
        }
        if (this.beforeModeWriter) {
            const beforeResult = await this.beforeModeWriter(context);
            if (beforeResult?.configOptions) {
                this.configOptions = this.transformConfigOptions(beforeResult.configOptions);
            }
        }
        if (selection.hasAvailableModes) {
            await this.connection.setSessionMode({ sessionId: this.sessionId, modeId });
            this.currentMode = modeId;
            this.pushEvent({
                type: "mode_changed",
                provider: this.provider,
                currentModeId: this.currentMode,
                availableModes: [...this.availableModes],
            });
            return;
        }
        const modeOption = selection.configOption;
        if (!modeOption) {
            throw new Error(`${this.provider} does not expose ACP mode switching`);
        }
        const response = await this.connection.setSessionConfigOption({
            sessionId: this.sessionId,
            configId: modeOption.id,
            value: modeId,
        });
        this.currentMode = this.applyConfigOptionResponse({
            response,
            configId: modeOption.id,
            category: "mode",
            requestedValue: modeId,
            label: "mode",
        });
        this.availableModes = deriveModesFromACP(this.defaultModes, null, this.configOptions).modes;
        this.pushEvent({
            type: "mode_changed",
            provider: this.provider,
            currentModeId: this.currentMode,
            availableModes: [...this.availableModes],
        });
    }
    createProviderModeWriterContext(requestedModeId, selection) {
        if (!this.connection || !this.sessionId) {
            throw new Error("ACP session not initialized");
        }
        return {
            connection: this.connection,
            sessionId: this.sessionId,
            requestedModeId,
            currentModeId: this.currentMode,
            selection,
            configOptions: this.configOptions,
            logger: this.logger,
        };
    }
    async setModel(modelId) {
        if (!this.connection || !this.sessionId) {
            throw new Error("ACP session not initialized");
        }
        if (!modelId) {
            this.currentModel = null;
            return;
        }
        const selection = resolveACPModelSelection({
            modelId,
            availableModels: this.availableModels,
            configOptions: this.configOptions,
        });
        await this.setModelWithSelection({ modelId, selection });
    }
    async setModelWithSelection({ modelId, selection, }) {
        if (!this.connection || !this.sessionId) {
            throw new Error("ACP session not initialized");
        }
        if (selection.hasAvailableModels) {
            if (!selection.availableModel) {
                this.warnInvalidSelection(modelId, `is not a valid ${this.provider} model. Available options: ${this.availableModels
                    ?.map((model) => model.modelId)
                    .join(", ")}`);
                return;
            }
            if (typeof this.connection.unstable_setSessionModel !== "function") {
                throw new Error(this.modelSelectionUnavailableMessage());
            }
            try {
                await this.connection.unstable_setSessionModel({
                    sessionId: this.sessionId,
                    modelId,
                });
                this.currentModel = modelId;
                this.pushEvent({
                    type: "model_changed",
                    provider: this.provider,
                    runtimeInfo: this.runtimeInfo(),
                });
                return;
            }
            catch {
                // Fall through to config option path.
            }
        }
        const modelOption = selection.configOption;
        if (!modelOption) {
            throw new Error(this.modelSelectionUnavailableMessage());
        }
        if (!selection.configChoice) {
            this.warnInvalidSelection(modelId, `is not a valid ${this.provider} model config option. Available options: ${flattenSelectOptions(modelOption.options)
                .map((option) => option.value)
                .join(", ")}`);
            return;
        }
        const response = await this.connection.setSessionConfigOption({
            sessionId: this.sessionId,
            configId: modelOption.id,
            value: modelId,
        });
        this.currentModel = this.applyConfigOptionResponse({
            response,
            configId: modelOption.id,
            category: "model",
            requestedValue: modelId,
            label: "model",
        });
        this.pushEvent({
            type: "model_changed",
            provider: this.provider,
            runtimeInfo: this.runtimeInfo(),
        });
    }
    async setThinkingOption(thinkingOptionId) {
        if (!this.connection || !this.sessionId) {
            throw new Error("ACP session not initialized");
        }
        if (!thinkingOptionId) {
            this.thinkingOptionId = null;
            return;
        }
        if (this.thinkingOptionWriter) {
            await this.thinkingOptionWriter(this.connection, this.sessionId, thinkingOptionId);
            this.thinkingOptionId = thinkingOptionId;
            this.pushEvent({
                type: "thinking_option_changed",
                provider: this.provider,
                thinkingOptionId: this.thinkingOptionId,
            });
            return;
        }
        const option = findSelectConfigOption({
            configOptions: this.configOptions,
            category: "thought_level",
        });
        if (!option) {
            throw new Error(`${this.provider} does not expose ACP thought-level selection`);
        }
        const response = await this.connection.setSessionConfigOption({
            sessionId: this.sessionId,
            configId: option.id,
            value: thinkingOptionId,
        });
        this.thinkingOptionId = this.applyConfigOptionResponse({
            response,
            configId: option.id,
            category: "thought_level",
            requestedValue: thinkingOptionId,
            label: "thought-level",
        });
        this.pushEvent({
            type: "thinking_option_changed",
            provider: this.provider,
            thinkingOptionId: this.thinkingOptionId,
        });
    }
    applyConfigOptionResponse({ response, configId, category, requestedValue, label, }) {
        this.configOptions = this.transformConfigOptions(response.configOptions);
        const responseOption = findSelectConfigOption({
            configOptions: this.configOptions,
            category,
            id: configId,
        });
        if (responseOption?.currentValue != null) {
            return responseOption.currentValue;
        }
        this.logger.warn({ configId, value: requestedValue }, `ACP setSessionConfigOption response did not include the requested ${label} option currentValue; using requested value`);
        return requestedValue;
    }
    getPendingPermissions() {
        return Array.from(this.pendingPermissions.values(), (entry) => entry.request);
    }
    async respondToPermission(requestId, response) {
        const pending = this.pendingPermissions.get(requestId);
        if (!pending) {
            throw new Error(`No pending permission request with id '${requestId}'`);
        }
        this.pendingPermissions.delete(requestId);
        const selectedOption = selectPermissionOption(pending.options, response);
        pending.resolve(selectedOption
            ? {
                outcome: {
                    outcome: "selected",
                    optionId: selectedOption.optionId,
                },
            }
            : { outcome: { outcome: "cancelled" } });
        this.pushEvent({
            type: "permission_resolved",
            provider: this.provider,
            requestId,
            resolution: response,
            turnId: pending.turnId ?? undefined,
        });
        if (response.behavior === "deny" && response.interrupt && this.connection && this.sessionId) {
            await this.connection.cancel({ sessionId: this.sessionId });
        }
    }
    describePersistence() {
        if (!this.sessionId) {
            return null;
        }
        return {
            provider: this.provider,
            sessionId: this.sessionId,
            nativeHandle: this.sessionId,
            metadata: {
                ...this.config,
                title: this.currentTitle,
            },
        };
    }
    async interrupt() {
        if (!this.connection || !this.sessionId) {
            return;
        }
        for (const pending of this.pendingPermissions.values()) {
            pending.resolve({ outcome: { outcome: "cancelled" } });
        }
        this.pendingPermissions.clear();
        if (this.activeForegroundTurnId) {
            await this.connection.cancel({ sessionId: this.sessionId });
        }
    }
    async close() {
        if (this.closed) {
            return;
        }
        this.closed = true;
        this.settleCommandsReady();
        for (const pending of this.pendingPermissions.values()) {
            pending.resolve({ outcome: { outcome: "cancelled" } });
        }
        this.pendingPermissions.clear();
        if (this.connection && this.sessionId) {
            try {
                if (this.activeForegroundTurnId) {
                    await this.connection.cancel({ sessionId: this.sessionId });
                }
            }
            catch { }
            try {
                if (this.agentCapabilities?.sessionCapabilities?.close) {
                    await this.connection.unstable_closeSession({ sessionId: this.sessionId });
                }
            }
            catch (error) {
                this.logger.debug({ err: error }, "ACP closeSession failed during shutdown");
            }
        }
        for (const terminal of this.terminalEntries.values()) {
            terminal.child.kill("SIGTERM");
        }
        this.terminalEntries.clear();
        if (this.child) {
            this.child.kill("SIGTERM");
            await waitForChildExit(this.child, 2000);
        }
        this.subscribers.clear();
        this.connection = null;
        this.child = null;
        this.activeForegroundTurnId = null;
    }
    async requestPermission(params) {
        // Match Zed acp.rs:3189-3220: generic ACP permission requests stay pure pass-through.
        const requestId = randomUUID();
        let toolSnapshot = this.toolCalls.get(params.toolCall.toolCallId) ??
            mergeToolSnapshot(params.toolCall.toolCallId, params.toolCall);
        if (this.toolSnapshotTransformer) {
            toolSnapshot = this.toolSnapshotTransformer(toolSnapshot);
        }
        const request = mapPermissionRequest(this.provider, requestId, params, toolSnapshot);
        const promise = new Promise((resolve, reject) => {
            this.pendingPermissions.set(requestId, {
                request,
                options: params.options,
                resolve,
                reject,
                turnId: this.activeForegroundTurnId,
            });
        });
        this.pushEvent({
            type: "permission_requested",
            provider: this.provider,
            request,
            turnId: this.activeForegroundTurnId ?? undefined,
        });
        return promise;
    }
    async sessionUpdate(params) {
        this.logger.trace({
            agentId: this.agentId,
            provider: this.provider,
            sessionId: params.sessionId,
            rawEvent: params,
        }, "provider.acp.raw_event");
        if (params.sessionId !== this.sessionId) {
            return;
        }
        const events = this.translateSessionUpdate(params.update);
        this.logger.trace({
            agentId: this.agentId,
            provider: this.provider,
            sessionId: this.sessionId,
            turnId: this.activeForegroundTurnId ?? undefined,
            rawEvent: params,
            events,
        }, "provider.acp.parsed_event");
        if (this.replayingHistory) {
            for (const event of events) {
                if (event.type === "timeline") {
                    this.persistedHistory.push(event.item);
                }
            }
            return;
        }
        for (const event of events) {
            this.pushEvent(event);
        }
    }
    async extNotification(method, params) {
        this.logger.trace({
            agentId: this.agentId,
            provider: this.provider,
            sessionId: typeof params.sessionId === "string" ? params.sessionId : undefined,
            method,
            rawEvent: params,
        }, "provider.acp.extension_notification");
    }
    async readTextFile(params) {
        const raw = await fs.readFile(params.path, "utf8");
        if (!params.line && !params.limit) {
            return { content: raw };
        }
        const lines = raw.split(/\r?\n/);
        const start = Math.max((params.line ?? 1) - 1, 0);
        const end = params.limit ? start + params.limit : undefined;
        return { content: lines.slice(start, end).join("\n") };
    }
    async writeTextFile(params) {
        await fs.mkdir(path.dirname(params.path), { recursive: true });
        await fs.writeFile(params.path, params.content, "utf8");
        return {};
    }
    async createTerminal(params) {
        const terminalId = randomUUID();
        const env = Object.fromEntries((params.env ?? []).map((entry) => [entry.name, entry.value]));
        const terminalCommand = resolveTerminalCommand(params.command, params.args);
        const child = spawnProcess(terminalCommand.command, terminalCommand.args, {
            cwd: params.cwd ?? this.config.cwd,
            ...createProviderEnvSpec({
                runtimeSettings: this.runtimeSettings,
                overlays: [env],
            }),
            stdio: ["ignore", "pipe", "pipe"],
        });
        let resolveExit;
        let rejectExit;
        const waitForExit = new Promise((resolve, reject) => {
            resolveExit = resolve;
            rejectExit = reject;
        });
        waitForExit.catch(() => undefined);
        const entry = {
            id: terminalId,
            child,
            output: "",
            truncated: false,
            outputByteLimit: params.outputByteLimit ?? null,
            exit: null,
            waitForExit,
            resolveExit,
            rejectExit,
        };
        child.stdout.on("data", (chunk) => appendTerminalOutput(entry, chunk.toString()));
        child.stderr.on("data", (chunk) => appendTerminalOutput(entry, chunk.toString()));
        child.once("error", (error) => {
            const spawnError = error instanceof Error ? error : new Error(String(error));
            appendTerminalOutput(entry, `${spawnError.message}\n`);
            rejectExit(spawnError);
        });
        child.once("exit", (code, signal) => {
            const exit = { exitCode: code, signal };
            entry.exit = exit;
            resolveExit(exit);
        });
        this.terminalEntries.set(terminalId, entry);
        return { terminalId };
    }
    async terminalOutput(params) {
        const entry = this.getTerminalEntry(params.terminalId);
        return {
            output: entry.output,
            truncated: entry.truncated,
            exitStatus: entry.exit ?? undefined,
        };
    }
    async waitForTerminalExit(params) {
        const entry = this.getTerminalEntry(params.terminalId);
        return entry.waitForExit;
    }
    async releaseTerminal(params) {
        const entry = this.getTerminalEntry(params.terminalId);
        if (!entry.exit) {
            entry.child.kill("SIGTERM");
        }
        this.terminalEntries.delete(params.terminalId);
    }
    async killTerminal(params) {
        const entry = this.getTerminalEntry(params.terminalId);
        if (!entry.exit) {
            entry.child.kill("SIGTERM");
        }
        return {};
    }
    async spawnProcess() {
        const prefix = await resolveProviderLaunch({
            commandConfig: this.runtimeSettings?.command,
            defaultBinary: this.defaultCommand[0],
        });
        const availability = await checkProviderLaunchAvailable(prefix);
        if (!availability.available) {
            throw new Error(`${this.provider} command '${this.defaultCommand[0]}' not found`);
        }
        const command = prefix.command;
        const args = [...prefix.args, ...this.defaultCommand.slice(1)];
        const child = spawnProcess(command, args, {
            cwd: this.config.cwd,
            ...createProviderEnvSpec({
                runtimeSettings: this.runtimeSettings,
                overlays: [this.launchEnv],
            }),
            stdio: ["pipe", "pipe", "pipe"],
        });
        assertChildWithPipes(child);
        const stderrChunks = [];
        child.stderr.on("data", (chunk) => {
            stderrChunks.push(chunk.toString());
        });
        child.once("exit", (code, signal) => {
            if (this.closed) {
                return;
            }
            if (this.activeForegroundTurnId) {
                this.synthesizeCanceledToolCalls();
                this.finishTurn({
                    type: "turn_failed",
                    provider: this.provider,
                    error: `ACP agent exited unexpectedly (${code ?? "null"}${signal ? `, ${signal}` : ""})`,
                    diagnostic: stderrChunks.join("").trim() || undefined,
                    turnId: this.activeForegroundTurnId,
                });
            }
        });
        const stream = createLoggedNdJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout), { logger: this.logger, provider: this.provider });
        const connection = new ClientSideConnection(() => this, stream);
        const initialize = await connection.initialize({
            protocolVersion: PROTOCOL_VERSION,
            clientCapabilities: ACP_CLIENT_CAPABILITIES,
            clientInfo: { name: "Paseo", version: "dev" },
        });
        return { child, connection, initialize };
    }
    applySessionState(response) {
        const transformed = this.sessionResponseTransformer
            ? this.sessionResponseTransformer(response)
            : response;
        this.configOptions = this.transformConfigOptions(transformed.configOptions ?? []);
        const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, this.configOptions);
        this.availableModes = modeInfo.modes;
        this.currentMode = modeInfo.currentModeId ?? this.currentMode;
        this.availableModels = transformed.models?.availableModels ?? null;
        this.currentModel =
            transformed.models?.currentModelId ?? deriveCurrentConfigValue(this.configOptions, "model");
        this.thinkingOptionId =
            deriveCurrentConfigValue(this.configOptions, "thought_level") ?? this.thinkingOptionId;
    }
    transformConfigOptions(configOptions) {
        return this.configOptionsTransformer
            ? this.configOptionsTransformer(configOptions)
            : configOptions;
    }
    transformModeId(modeId) {
        return this.modeIdTransformer ? this.modeIdTransformer(modeId) : modeId;
    }
    async applyConfiguredOverrides() {
        const configuredModeId = this.config.modeId;
        if (configuredModeId && configuredModeId !== this.currentMode) {
            const selection = resolveACPModeSelection({
                modeId: configuredModeId,
                availableModes: this.availableModes,
                configOptions: this.configOptions,
            });
            await this.setModeWithSelection({ modeId: configuredModeId, selection });
        }
        const configuredModelId = this.config.model;
        if (configuredModelId && configuredModelId !== this.currentModel) {
            const selection = resolveACPModelSelection({
                modelId: configuredModelId,
                availableModels: this.availableModels,
                configOptions: this.configOptions,
            });
            try {
                await this.setModelWithSelection({ modelId: configuredModelId, selection });
            }
            catch (error) {
                if (!this.isModelSelectionUnavailableError(error)) {
                    throw error;
                }
                this.logger.warn({ value: configuredModelId }, `${this.provider} does not expose ACP model selection; using provider default model`);
            }
        }
        if (this.config.thinkingOptionId && this.config.thinkingOptionId !== this.thinkingOptionId) {
            await this.setThinkingOption(this.config.thinkingOptionId);
        }
    }
    warnInvalidSelection(value, message) {
        this.logger.warn({ value }, message);
    }
    modelSelectionUnavailableMessage() {
        return `${this.provider} does not expose ACP model selection`;
    }
    isModelSelectionUnavailableError(error) {
        return error instanceof Error && error.message === this.modelSelectionUnavailableMessage();
    }
    translateSessionUpdate(update) {
        switch (update.sessionUpdate) {
            case "user_message_chunk": {
                const item = this.createMessageTimelineItem("user_message", update);
                if (!item) {
                    return [];
                }
                if (update.messageId && this.submittedUserMessageIds.has(update.messageId)) {
                    return [];
                }
                return [this.wrapTimeline(item)];
            }
            case "agent_message_chunk": {
                const item = this.createMessageTimelineItem("assistant_message", update);
                return item ? [this.wrapTimeline(item)] : [];
            }
            case "agent_thought_chunk": {
                const item = this.createMessageTimelineItem("reasoning", update);
                return item ? [this.wrapTimeline(item)] : [];
            }
            case "tool_call":
                return this.handleToolCallUpdate(update.toolCallId, update, undefined);
            case "tool_call_update":
                return this.handleToolCallUpdate(update.toolCallId, update, this.toolCalls.get(update.toolCallId));
            case "plan":
                return [this.wrapTimeline(mapPlanToTimeline(update))];
            case "current_mode_update":
                this.handleCurrentModeUpdate(update);
                return [
                    {
                        type: "mode_changed",
                        provider: this.provider,
                        currentModeId: this.currentMode,
                        availableModes: [...this.availableModes],
                    },
                ];
            case "config_option_update":
                return this.handleConfigOptionUpdate(update);
            case "session_info_update":
                this.handleSessionInfoUpdate(update);
                return [];
            case "usage_update":
                this.handleUsageUpdate(update);
                return [];
            case "available_commands_update":
                this.cachedCommands = update.availableCommands.map((command) => ({
                    name: command.name,
                    description: command.description,
                    argumentHint: "",
                }));
                this.settleCommandsReady();
                return [];
            default:
                return [];
        }
    }
    handleToolCallUpdate(toolCallId, update, previous) {
        let snapshot = mergeToolSnapshot(toolCallId, update, previous);
        if (this.toolSnapshotTransformer) {
            snapshot = this.toolSnapshotTransformer(snapshot);
        }
        this.toolCalls.set(toolCallId, snapshot);
        return [this.wrapTimeline(mapToolSnapshotToTimeline(snapshot, this.terminalEntries))];
    }
    createMessageTimelineItem(type, update) {
        const chunkText = contentBlockToText(update.content);
        if (!chunkText) {
            return null;
        }
        const key = `${type}:${update.messageId ?? "default"}`;
        const state = this.messageAssemblies.get(key) ?? { text: "" };
        state.text += chunkText;
        this.messageAssemblies.set(key, state);
        if (type === "user_message") {
            return { type: "user_message", text: state.text, messageId: update.messageId ?? undefined };
        }
        if (type === "assistant_message") {
            return { type: "assistant_message", text: chunkText };
        }
        return { type: "reasoning", text: chunkText };
    }
    handleCurrentModeUpdate(update) {
        this.currentMode = this.transformModeId(update.currentModeId);
    }
    handleConfigOptionUpdate(update) {
        this.configOptions = this.transformConfigOptions(update.configOptions);
        const modeInfo = deriveModesFromACP(this.defaultModes, null, this.configOptions);
        const nextMode = modeInfo.currentModeId;
        const nextModel = deriveCurrentConfigValue(this.configOptions, "model");
        const nextThinkingOptionId = deriveCurrentConfigValue(this.configOptions, "thought_level");
        this.availableModes = modeInfo.modes;
        this.currentMode = nextMode ?? this.currentMode;
        this.currentModel = nextModel ?? this.currentModel;
        this.thinkingOptionId = nextThinkingOptionId ?? this.thinkingOptionId;
        const events = [];
        if (nextMode !== null) {
            events.push({
                type: "mode_changed",
                provider: this.provider,
                currentModeId: this.currentMode,
                availableModes: [...this.availableModes],
            });
        }
        if (nextModel !== null) {
            events.push({
                type: "model_changed",
                provider: this.provider,
                runtimeInfo: this.runtimeInfo(),
            });
        }
        if (nextThinkingOptionId !== null) {
            events.push({
                type: "thinking_option_changed",
                provider: this.provider,
                thinkingOptionId: this.thinkingOptionId,
            });
        }
        return events;
    }
    handleSessionInfoUpdate(update) {
        if ("title" in update) {
            this.currentTitle = update.title ?? null;
        }
        if ("updatedAt" in update) {
            this.lastActivityAt = update.updatedAt ?? null;
        }
    }
    handleUsageUpdate(update) {
        void update;
    }
    handlePromptResponse(response, turnId) {
        this.currentTurnUsage = mapACPUsage(response.usage) ?? this.currentTurnUsage;
        switch (response.stopReason) {
            case "cancelled":
                this.synthesizeCanceledToolCalls();
                this.finishTurn({
                    type: "turn_canceled",
                    provider: this.provider,
                    reason: "Interrupted",
                    turnId,
                });
                break;
            case "end_turn":
            case "max_tokens":
            case "max_turn_requests":
            case "refusal":
            default:
                this.finishTurn({
                    type: "turn_completed",
                    provider: this.provider,
                    usage: this.currentTurnUsage,
                    turnId,
                });
                break;
        }
    }
    wrapTimeline(item) {
        return {
            type: "timeline",
            provider: this.provider,
            item,
            turnId: this.activeForegroundTurnId ?? undefined,
        };
    }
    pushEvent(event) {
        this.logger.trace({
            agentId: this.agentId,
            provider: this.provider,
            sessionId: this.sessionId,
            turnId: getAgentStreamEventTurnId(event) ?? this.activeForegroundTurnId ?? undefined,
            event,
        }, "provider.acp.event_emit");
        for (const subscriber of this.subscribers) {
            subscriber(event);
        }
    }
    emitSubmittedUserMessage(prompt, messageId, turnId) {
        const text = extractPromptText(prompt);
        if (text.trim().length === 0) {
            return;
        }
        this.submittedUserMessageIds.add(messageId);
        this.pushEvent({
            type: "timeline",
            provider: this.provider,
            turnId,
            item: { type: "user_message", text, messageId },
        });
    }
    runtimeInfo() {
        return {
            provider: this.provider,
            sessionId: this.sessionId,
            model: this.currentModel,
            thinkingOptionId: this.thinkingOptionId,
            modeId: this.currentMode,
            extra: {
                title: this.currentTitle,
                updatedAt: this.lastActivityAt,
            },
        };
    }
    finishTurn(event) {
        this.activeForegroundTurnId = null;
        this.pushEvent(event);
    }
    emitBootstrapThreadEvent() {
        if (!this.bootstrapThreadEventPending || !this.sessionId) {
            return;
        }
        this.bootstrapThreadEventPending = false;
        this.pushEvent({
            type: "thread_started",
            provider: this.provider,
            sessionId: this.sessionId,
        });
    }
    synthesizeCanceledToolCalls() {
        for (const snapshot of this.toolCalls.values()) {
            const mapped = mapToolSnapshotToTimeline(snapshot, this.terminalEntries);
            if (mapped.status === "running") {
                this.pushEvent(this.wrapTimeline({
                    ...mapped,
                    status: "canceled",
                    error: null,
                }));
            }
        }
    }
    collectDiagnostic(message) {
        const parts = [message];
        if (this.child?.exitCode != null) {
            parts.push(`exitCode=${this.child.exitCode}`);
        }
        if (this.child?.signalCode) {
            parts.push(`signal=${this.child.signalCode}`);
        }
        return parts.length > 0 ? parts.join(" | ") : undefined;
    }
    getTerminalEntry(terminalId) {
        const entry = this.terminalEntries.get(terminalId);
        if (!entry) {
            throw new Error(`Unknown terminal '${terminalId}'`);
        }
        return entry;
    }
}
function findSelectConfigOption({ configOptions, category, id, }) {
    const option = configOptions?.find((entry) => entry.type === "select" && entry.category === category && (!id || entry.id === id));
    return option ?? null;
}
function findSelectConfigChoice({ option, value, }) {
    if (!option) {
        return null;
    }
    return flattenSelectOptions(option.options).find((choice) => choice.value === value) ?? null;
}
function flattenSelectOptions(options) {
    const flattened = [];
    for (const option of options) {
        if ("value" in option) {
            flattened.push(option);
            continue;
        }
        for (const groupOption of option.options) {
            flattened.push({ ...groupOption, group: option.group });
        }
    }
    return flattened;
}
function deriveSelectorOptions(configOptions, category) {
    const option = findSelectConfigOption({ configOptions, category });
    if (!option) {
        return [];
    }
    return flattenSelectOptions(option.options).map((value) => ({
        id: value.value,
        label: value.name,
        description: value.description ?? undefined,
        isDefault: value.value === option.currentValue,
        metadata: value.group ? { group: value.group } : undefined,
    }));
}
function deriveCurrentConfigValue(configOptions, category) {
    const option = configOptions?.find((entry) => entry.type === "select" && entry.category === category);
    return option?.currentValue ?? null;
}
function normalizeMcpServers(servers) {
    if (!servers) {
        return [];
    }
    return Object.entries(servers).map(([name, config]) => {
        if (config.type === "stdio") {
            return {
                name,
                command: config.command,
                args: config.args ?? [],
                env: Object.entries(config.env ?? {}).map(([envName, value]) => ({
                    name: envName,
                    value,
                })),
            };
        }
        if (config.type === "http") {
            return {
                type: "http",
                name,
                url: config.url,
                headers: Object.entries(config.headers ?? {}).map(([headerName, value]) => ({
                    name: headerName,
                    value,
                })),
            };
        }
        return {
            type: "sse",
            name,
            url: config.url,
            headers: Object.entries(config.headers ?? {}).map(([headerName, value]) => ({
                name: headerName,
                value,
            })),
        };
    });
}
function toACPContentBlocks(prompt) {
    if (typeof prompt === "string") {
        return [{ type: "text", text: prompt }];
    }
    const contentBlocks = [];
    for (const block of prompt) {
        switch (block.type) {
            case "text":
                contentBlocks.push({ type: "text", text: block.text });
                break;
            case "image":
                contentBlocks.push({ type: "image", data: block.data, mimeType: block.mimeType });
                break;
            default:
                contentBlocks.push({ type: "text", text: renderPromptAttachmentAsText(block) });
                break;
        }
    }
    return contentBlocks;
}
function extractPromptText(prompt) {
    if (typeof prompt === "string") {
        return prompt;
    }
    return prompt
        .filter((block) => block.type === "text")
        .map((block) => block.text)
        .join("");
}
function contentBlockToText(content) {
    switch (content.type) {
        case "text":
            return content.text;
        case "resource_link":
            return content.title ?? content.uri;
        case "resource":
            return "text" in content.resource
                ? content.resource.text
                : `[resource:${content.resource.mimeType ?? "binary"}]`;
        case "image":
            return "[image]";
        case "audio":
            return "[audio]";
        default:
            return "";
    }
}
function coalesceDefined(next, previous, fallback) {
    if (next !== undefined) {
        return next;
    }
    if (previous !== undefined) {
        return previous;
    }
    return fallback;
}
function mergeToolSnapshot(toolCallId, update, previous) {
    return {
        toolCallId,
        title: update.title ?? previous?.title ?? toolCallId,
        kind: update.kind ?? previous?.kind ?? null,
        status: update.status ?? previous?.status ?? null,
        content: coalesceDefined(update.content, previous?.content, null),
        locations: coalesceDefined(update.locations, previous?.locations, null),
        rawInput: update.rawInput !== undefined ? update.rawInput : previous?.rawInput,
        rawOutput: update.rawOutput !== undefined ? update.rawOutput : previous?.rawOutput,
    };
}
function mapPlanToTimeline(plan) {
    return {
        type: "todo",
        items: plan.entries.map((entry) => ({
            text: entry.content,
            completed: entry.status === "completed",
        })),
    };
}
function mapToolSnapshotToTimeline(snapshot, terminals) {
    const status = mapToolStatus(snapshot.status);
    const detail = mapToolDetail(snapshot, terminals);
    const base = {
        type: "tool_call",
        callId: snapshot.toolCallId,
        name: snapshot.kind ?? snapshot.title,
        detail,
        metadata: {
            kind: snapshot.kind ?? undefined,
            title: snapshot.title,
        },
    };
    if (status === "failed") {
        return {
            ...base,
            status: "failed",
            error: { message: readErrorMessage(snapshot.rawOutput) },
        };
    }
    if (status === "completed") {
        return {
            ...base,
            status: "completed",
            error: null,
        };
    }
    return {
        ...base,
        status: "running",
        error: null,
    };
}
function mapToolStatus(status) {
    switch (status) {
        case "completed":
            return "completed";
        case "failed":
            return "failed";
        case "pending":
        case "in_progress":
        default:
            return "running";
    }
}
function mapToolDetail(snapshot, terminals) {
    const context = {
        snapshot,
        firstLocation: snapshot.locations?.[0]?.path,
        textContent: extractToolText(snapshot.content),
        diffContent: extractDiffContent(snapshot.content),
        terminalContent: extractTerminalContent(snapshot.content, terminals),
        rawInput: readRecord(snapshot.rawInput),
        rawOutput: readRecord(snapshot.rawOutput),
    };
    switch (snapshot.kind) {
        case "read":
            return buildReadToolDetail(context);
        case "edit":
        case "delete":
            return buildEditToolDetail(context);
        case "search":
            return buildSearchAcpToolDetail(context);
        case "execute":
            return buildShellToolDetail(context);
        case "fetch":
            return buildFetchToolDetail(context);
        case "think":
            return {
                type: "plain_text",
                label: snapshot.title,
                icon: "brain",
                text: context.textContent ?? stringifyUnknown(snapshot.rawOutput),
            };
        case "switch_mode":
            return {
                type: "plain_text",
                label: snapshot.title,
                icon: "sparkles",
                text: context.textContent ?? stringifyUnknown(snapshot.rawInput),
            };
        default:
            return buildDefaultToolDetail(context);
    }
}
function buildReadToolDetail(context) {
    const { snapshot, firstLocation, textContent, rawInput, rawOutput } = context;
    return {
        type: "read",
        filePath: firstLocation ?? readString(rawInput, ["path", "filePath", "file"]) ?? snapshot.title,
        content: textContent ?? readString(rawOutput, ["content", "text"]),
        offset: readNumber(rawInput, ["offset", "line"]),
        limit: readNumber(rawInput, ["limit"]),
    };
}
function buildEditToolDetail(context) {
    const { snapshot, firstLocation, textContent, diffContent, rawInput } = context;
    return {
        type: "edit",
        filePath: firstLocation ?? readString(rawInput, ["path", "filePath", "file"]) ?? snapshot.title,
        oldString: diffContent?.oldText ?? readString(rawInput, ["oldText", "oldString"]),
        newString: snapshot.kind === "delete"
            ? ""
            : (diffContent?.newText ?? readString(rawInput, ["newText", "newString"])),
        unifiedDiff: textContent ?? undefined,
    };
}
function buildSearchAcpToolDetail(context) {
    const { snapshot, textContent, rawInput, rawOutput } = context;
    return {
        type: "search",
        query: readString(rawInput, ["query", "pattern"]) ?? snapshot.title,
        toolName: "search",
        content: textContent ?? readString(rawOutput, ["content", "text"]),
        filePaths: snapshot.locations?.map((location) => location.path),
    };
}
function buildShellToolDetail(context) {
    const { snapshot, textContent, terminalContent, rawInput, rawOutput } = context;
    return {
        type: "shell",
        command: terminalContent?.command ??
            buildShellCommand(rawInput) ??
            readString(rawInput, ["command"]) ??
            snapshot.title,
        cwd: terminalContent?.cwd ?? readString(rawInput, ["cwd"]),
        output: terminalContent?.output ?? textContent ?? readString(rawOutput, ["output", "text"]),
        exitCode: terminalContent?.exitCode ?? readNumber(rawOutput, ["exitCode"]),
    };
}
function buildFetchToolDetail(context) {
    const { snapshot, textContent, rawInput, rawOutput } = context;
    return {
        type: "fetch",
        url: readString(rawInput, ["url"]) ?? snapshot.title,
        prompt: readString(rawInput, ["prompt"]),
        result: textContent ?? readString(rawOutput, ["result", "text", "content"]),
        code: readNumber(rawOutput, ["status", "code"]),
    };
}
function buildDefaultToolDetail(context) {
    const { snapshot, textContent, terminalContent } = context;
    if (terminalContent) {
        return {
            type: "shell",
            command: terminalContent.command ?? snapshot.title,
            cwd: terminalContent.cwd,
            output: terminalContent.output,
            exitCode: terminalContent.exitCode,
        };
    }
    if (textContent) {
        return {
            type: "plain_text",
            label: snapshot.title,
            text: textContent,
            icon: "wrench",
        };
    }
    return {
        type: "unknown",
        input: snapshot.rawInput ?? null,
        output: snapshot.rawOutput ?? null,
    };
}
function extractToolText(content) {
    if (!content) {
        return undefined;
    }
    const parts = [];
    for (const item of content) {
        if (item.type === "content") {
            const text = contentBlockToText(item.content);
            if (text) {
                parts.push(text);
            }
        }
    }
    return parts.length > 0 ? parts.join("\n") : undefined;
}
function extractDiffContent(content) {
    const diff = content?.find((item) => item.type === "diff");
    return diff ? { oldText: diff.oldText ?? undefined, newText: diff.newText } : null;
}
function extractTerminalContent(content, terminals) {
    const terminal = content?.find((item) => item.type === "terminal");
    if (!terminal) {
        return undefined;
    }
    const entry = terminals.get(terminal.terminalId);
    if (!entry) {
        return undefined;
    }
    return {
        output: entry.output,
        exitCode: entry.exit?.exitCode ?? null,
    };
}
function mapPermissionRequest(provider, requestId, params, snapshot) {
    const kind = snapshot.kind === "switch_mode" ? "mode" : "tool";
    return {
        id: requestId,
        provider,
        name: snapshot.kind ?? snapshot.title,
        kind,
        title: params.toolCall.title ?? snapshot.title,
        detail: mapToolDetail(snapshot, new Map()),
        metadata: {
            toolCallId: params.toolCall.toolCallId,
            rawRequest: params,
            options: params.options,
        },
    };
}
function selectPermissionOption(options, response) {
    const order = response.behavior === "allow"
        ? ["allow_once", "allow_always"]
        : ["reject_once", "reject_always"];
    for (const kind of order) {
        const match = options.find((option) => option.kind === kind);
        if (match) {
            return match;
        }
    }
    return null;
}
function appendTerminalOutput(entry, chunk) {
    entry.output += chunk;
    const limit = entry.outputByteLimit;
    if (!limit) {
        return;
    }
    while (Buffer.byteLength(entry.output, "utf8") > limit && entry.output.length > 0) {
        entry.output = entry.output.slice(1);
        entry.truncated = true;
    }
}
function readRecord(value) {
    return isRecord(value) ? value : null;
}
function readString(record, keys) {
    if (!record) {
        return undefined;
    }
    for (const key of keys) {
        const value = record[key];
        if (typeof value === "string" && value.trim().length > 0) {
            return value;
        }
    }
    return undefined;
}
function readNumber(record, keys) {
    if (!record) {
        return undefined;
    }
    for (const key of keys) {
        const value = record[key];
        if (typeof value === "number" && Number.isFinite(value)) {
            return value;
        }
    }
    return undefined;
}
function buildShellCommand(record) {
    if (!record) {
        return undefined;
    }
    const command = readString(record, ["command"]);
    const args = Array.isArray(record["args"])
        ? record["args"].filter((value) => typeof value === "string")
        : [];
    if (!command) {
        return undefined;
    }
    return args.length > 0 ? `${command} ${args.join(" ")}` : command;
}
function readErrorMessage(value) {
    if (typeof value === "string") {
        return value;
    }
    const record = readRecord(value);
    return readString(record, ["message", "error"]) ?? "Tool call failed";
}
function stringifyUnknown(value) {
    if (value == null) {
        return undefined;
    }
    if (typeof value === "string") {
        return value;
    }
    try {
        return JSON.stringify(value);
    }
    catch {
        return typeof value === "bigint" ? String(value) : "[unserializable]";
    }
}
function coerceSessionConfigMetadata(metadata) {
    if (!metadata || typeof metadata !== "object") {
        return {};
    }
    return metadata;
}
async function waitForChildExit(child, timeoutMs) {
    if (child.exitCode !== null || child.signalCode !== null) {
        return;
    }
    await Promise.race([
        new Promise((resolve) => child.once("exit", () => resolve())),
        new Promise((resolve) => setTimeout(resolve, timeoutMs)),
    ]);
    if (child.exitCode === null && child.signalCode === null) {
        child.kill("SIGKILL");
    }
}
async function terminateChildProcess(child, timeoutMs) {
    child.kill("SIGTERM");
    child.stdin.destroy();
    child.stdout.destroy();
    child.stderr.destroy();
    await waitForChildExit(child, timeoutMs);
}
//# sourceMappingURL=acp-agent.js.map