import { randomUUID } from "node:crypto";
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { join } from "node:path";
import { runProviderTurn } from "../provider-runner.js";
import { checkProviderLaunchAvailable, resolveProviderLaunch, } from "../../provider-launch-config.js";
import { renderPromptAttachmentAsText } from "../../prompt-attachments.js";
import { composeSystemPromptParts } from "../../system-prompt.js";
import { buildBinaryDiagnosticRows, formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, toDiagnosticErrorMessage, } from "../diagnostic-utils.js";
import { getUserMessageText, streamPiHistory, } from "./history-mapper.js";
import { PiCliRuntime } from "./cli-runtime.js";
import { revertPiConversation } from "./rewind.js";
import { mapToolDetail, parseToolArgs, parseToolResult, resolveToolCallName, } from "./tool-call-mapper.js";
const PI_PROVIDER = "pi";
const DEFAULT_PI_THINKING_LEVEL = "medium";
const PI_BINARY_COMMAND = process.env.PI_COMMAND ?? process.env.PI_ACP_PI_COMMAND ?? "pi";
const PASEO_PI_TREE_EXTENSION_COMMAND = "paseo_tree";
const PASEO_PI_CAPTURE_EXTENSION_COMMAND = "paseo_capture_entries";
const PASEO_PI_ENTRY_CAPTURE_MARKER = "PASEO_ENTRY_CAPTURE";
const PASEO_PI_COMMAND_RESULT_MARKER = "PASEO_COMMAND_RESULT";
const PASEO_PI_EXTENSION_RESULT_TIMEOUT_MS = 10000;
const QUESTION_RESPONSE_HEADER = "Response";
const QUESTION_COMMENT_HEADER = "Comment";
const PI_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
const COMBINED_ASK_USER_METADATA = "ask_user_select_optional_comment";
const PI_CAPABILITIES = {
    supportsStreaming: true,
    supportsSessionPersistence: true,
    supportsDynamicModes: true,
    supportsMcpServers: false,
    supportsReasoningStream: true,
    supportsToolInvocations: true,
    supportsRewindConversation: true,
    supportsRewindFiles: false,
    supportsRewindBoth: false,
};
const PI_THINKING_OPTIONS = [
    { id: "off", label: "Off", description: "No extra reasoning" },
    { id: "minimal", label: "Minimal", description: "Light reasoning" },
    { id: "low", label: "Low", description: "Faster reasoning" },
    { id: "medium", label: "Medium", description: "Balanced reasoning", isDefault: true },
    { id: "high", label: "High", description: "Deeper reasoning" },
    { id: "xhigh", label: "XHigh", description: "Maximum reasoning" },
];
function normalizePiModelLabel(label) {
    return label.trim().replace(/[_\s]+/g, " ");
}
export function transformPiModels(models) {
    return models.map((model) => {
        if (!model.label.includes("/")) {
            return model;
        }
        const segments = model.label.split("/").filter((segment) => segment.length > 0);
        const rawLabel = segments.at(-1);
        if (!rawLabel) {
            return model;
        }
        return {
            ...model,
            label: normalizePiModelLabel(rawLabel),
            description: model.description ?? model.label,
        };
    });
}
function isPiThinkingLevel(value) {
    return (value === "off" ||
        value === "minimal" ||
        value === "low" ||
        value === "medium" ||
        value === "high" ||
        value === "xhigh");
}
function normalizePiThinkingOption(value) {
    if (!value) {
        return null;
    }
    return isPiThinkingLevel(value) ? value : null;
}
function mapThinkingOption(option) {
    const mappedOption = {
        id: option.id,
        label: option.label,
        description: option.description,
    };
    if (option.isDefault) {
        return {
            ...mappedOption,
            isDefault: true,
        };
    }
    return mappedOption;
}
function toAgentUsage(stats) {
    const inputTokens = stats.tokens?.input ?? 0;
    const cachedInputTokens = stats.tokens?.cacheRead ?? 0;
    const outputTokens = stats.tokens?.output ?? 0;
    const totalCostUsd = stats.cost ?? 0;
    const contextWindowMaxTokens = stats.contextUsage?.contextWindow ?? undefined;
    const contextWindowUsedTokens = stats.contextUsage?.tokens ?? undefined;
    if (inputTokens === 0 &&
        cachedInputTokens === 0 &&
        outputTokens === 0 &&
        totalCostUsd === 0 &&
        contextWindowMaxTokens === undefined &&
        contextWindowUsedTokens === undefined) {
        return undefined;
    }
    return {
        inputTokens,
        cachedInputTokens,
        outputTokens,
        totalCostUsd,
        ...(typeof contextWindowMaxTokens === "number" ? { contextWindowMaxTokens } : {}),
        ...(typeof contextWindowUsedTokens === "number" ? { contextWindowUsedTokens } : {}),
    };
}
function convertPromptInput(prompt) {
    if (typeof prompt === "string") {
        return { text: prompt };
    }
    const textParts = [];
    const images = [];
    for (const block of prompt) {
        if (block.type === "text") {
            textParts.push(block.text);
            continue;
        }
        if (block.type === "image") {
            images.push({
                type: "image",
                data: block.data,
                mimeType: block.mimeType,
            });
            continue;
        }
        textParts.push(renderPromptAttachmentAsText(block));
    }
    const payload = {
        text: textParts.join("\n\n"),
    };
    if (images.length > 0) {
        payload.images = images;
    }
    return payload;
}
function parseModelReference(modelId) {
    if (!modelId) {
        return null;
    }
    if (modelId.includes("/")) {
        const [provider, ...rest] = modelId.split("/");
        const id = rest.join("/");
        if (provider && id) {
            return { provider, id };
        }
    }
    if (modelId.includes(":")) {
        const [provider, ...rest] = modelId.split(":");
        const id = rest.join(":");
        if (provider && id) {
            return { provider, id };
        }
    }
    return { id: modelId };
}
function parsePersistenceMetadata(metadata) {
    if (!metadata) {
        return {};
    }
    return {
        ...(typeof metadata.cwd === "string" ? { cwd: metadata.cwd } : {}),
        ...(typeof metadata.model === "string" ? { model: metadata.model } : {}),
        ...(typeof metadata.thinkingOptionId === "string"
            ? { thinkingOptionId: metadata.thinkingOptionId }
            : {}),
        ...(typeof metadata.systemPrompt === "string" ? { systemPrompt: metadata.systemPrompt } : {}),
    };
}
function buildResumeConfig(metadata, overrides) {
    const overrideConfig = overrides ?? {};
    const cwd = overrideConfig.cwd ?? metadata.cwd ?? process.cwd();
    const model = overrideConfig.model ?? metadata.model;
    const thinkingOptionId = overrideConfig.thinkingOptionId ?? metadata.thinkingOptionId;
    return {
        cwd,
        model,
        thinkingOptionId,
        config: {
            ...overrideConfig,
            provider: PI_PROVIDER,
            cwd,
            model,
            thinkingOptionId,
            systemPrompt: overrideConfig.systemPrompt ?? metadata.systemPrompt,
        },
    };
}
function toPiMcpConfig(config) {
    if (config.type === "stdio") {
        return {
            command: config.command,
            ...(config.args ? { args: config.args } : {}),
            ...(config.env ? { env: config.env } : {}),
        };
    }
    return {
        url: config.url,
        ...(config.headers ? { headers: config.headers } : {}),
        auth: false,
        oauth: false,
    };
}
function createPiMcpConfigFile(servers) {
    const dir = mkdtempSync(join(tmpdir(), "paseo-pi-mcp-"));
    const filePath = join(dir, "mcp.json");
    const mcpServers = {};
    for (const [name, serverConfig] of Object.entries(servers)) {
        mcpServers[name] = toPiMcpConfig(serverConfig);
    }
    writeFileSync(filePath, `${JSON.stringify({ mcpServers }, null, 2)}\n`, "utf8");
    return {
        path: filePath,
        cleanup: () => rmSync(dir, { recursive: true, force: true }),
    };
}
function createPiPaseoExtensionFile() {
    const dir = mkdtempSync(join(tmpdir(), "paseo-pi-extension-"));
    const filePath = join(dir, "paseo-integration.mjs");
    writeFileSync(filePath, `
	function decodePayload(encoded) {
	  return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
	}

	function readTextContent(content) {
	  if (typeof content === "string") {
	    return content;
	  }
	  if (!Array.isArray(content)) {
	    return "";
	  }
	  return content
	    .filter((part) => part && part.type === "text" && typeof part.text === "string")
	    .map((part) => part.text)
	    .join("\\n\\n");
	}

	function getCapturedUserEntries(ctx) {
	  return ctx.sessionManager
	    .getEntries()
	    .filter((entry) => entry.type === "message" && entry.message?.role === "user")
	    .map((entry) => ({
	      id: entry.id,
	      parentId: entry.parentId ?? null,
	      text: readTextContent(entry.message.content),
	    }));
	}

	function emitEntryCapture(ctx, reason, requestId) {
	  ctx.ui.notify(
	    "${PASEO_PI_ENTRY_CAPTURE_MARKER} " +
	      JSON.stringify({ reason, requestId, entries: getCapturedUserEntries(ctx) }),
	    "info",
	  );
	}

	function emitCommandResult(ctx, requestId, result) {
	  ctx.ui.notify(
	    "${PASEO_PI_COMMAND_RESULT_MARKER} " + JSON.stringify({ requestId, ...result }),
	    result.ok ? "info" : "error",
	  );
	}
	
	export default function paseoIntegration(pi) {
	  pi.on("session_start", async (_event, ctx) => {
	    emitEntryCapture(ctx, "session_start");
	  });

	  pi.on("turn_end", async (_event, ctx) => {
	    emitEntryCapture(ctx, "turn_end");
	  });

	  pi.registerCommand("${PASEO_PI_CAPTURE_EXTENSION_COMMAND}", {
	    description: "Internal Paseo entry capture bridge",
	    handler: async (args, ctx) => {
	      const payload = decodePayload(args.trim());
	      emitEntryCapture(ctx, "command", payload.requestId);
	    },
	  });

	  pi.registerCommand("${PASEO_PI_TREE_EXTENSION_COMMAND}", {
	    description: "Internal Paseo tree navigation bridge",
	    handler: async (args, ctx) => {
	      const payload = decodePayload(args.trim());
	      try {
	        const result = await ctx.navigateTree(payload.targetId, { summarize: false });
	        emitEntryCapture(ctx, "tree_navigation");
	        emitCommandResult(ctx, payload.requestId, { ok: true, result });
	      } catch (error) {
	        const message = error instanceof Error ? error.message : String(error);
	        emitCommandResult(ctx, payload.requestId, { ok: false, error: message });
	        throw error;
	      }
	    },
	  });
	}
`.trimStart(), "utf8");
    return {
        path: filePath,
        cleanup: () => rmSync(dir, { recursive: true, force: true }),
    };
}
function combineCleanup(cleanups) {
    const activeCleanups = cleanups.filter((cleanup) => Boolean(cleanup));
    if (activeCleanups.length === 0) {
        return undefined;
    }
    return () => {
        for (const cleanup of activeCleanups) {
            cleanup();
        }
    };
}
function isPiMcpAdapterCommand(command) {
    if (command.source !== "extension" || !/^mcp(?::\d+)?$/.test(command.name)) {
        return false;
    }
    if (!command.sourceInfo) {
        return true;
    }
    return JSON.stringify(command.sourceInfo).includes("pi-mcp-adapter");
}
function withPiMcpCapability(supportsMcpServers) {
    return {
        ...PI_CAPABILITIES,
        supportsMcpServers,
    };
}
function isPiRequestAbortError(error) {
    if (error instanceof Error && error.name === "AbortError") {
        return true;
    }
    return /\brequest was aborted\b|\babort(ed)?\b/i.test(toDiagnosticErrorMessage(error));
}
function resolveThinkingOptionId(cachedThinkingOptionId, sessionThinkingLevel) {
    const currentThinking = cachedThinkingOptionId ?? sessionThinkingLevel;
    return normalizePiThinkingOption(currentThinking);
}
function modelToId(model) {
    return model?.provider && model.id ? `${model.provider}/${model.id}` : null;
}
function piAssistantText(message) {
    const text = message.content
        .flatMap((part) => {
        if (part.type === "text") {
            return [part.text];
        }
        if (part.type === "thinking") {
            return [part.thinking];
        }
        return [];
    })
        .join("\n\n")
        .trim();
    return text.length > 0 ? text : null;
}
function formatPiErrorMessage(message) {
    const headline = message.errorMessage?.trim() || "Pi turn failed";
    const details = [
        message.stopReason ? `stopReason=${message.stopReason}` : null,
        message.provider && message.model ? `model=${message.provider}/${message.model}` : null,
        message.responseModel ? `responseModel=${message.responseModel}` : null,
        message.responseId ? `responseId=${message.responseId}` : null,
    ].filter((detail) => detail !== null);
    const partialText = piAssistantText(message);
    if (partialText) {
        details.push(`partial=${JSON.stringify(partialText.slice(0, 500))}`);
    }
    return details.length > 0 ? `${headline} (${details.join(", ")})` : headline;
}
function latestPiErrorMessage(messages) {
    const latestAssistant = messages.findLast((message) => message.role === "assistant");
    if (!latestAssistant || !latestAssistant.errorMessage?.trim()) {
        return null;
    }
    return formatPiErrorMessage(latestAssistant);
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function optionalString(value) {
    return typeof value === "string" ? value : undefined;
}
function parseExtensionMarkerPayload(message, marker) {
    const prefix = `${marker} `;
    if (!message.startsWith(prefix)) {
        return null;
    }
    try {
        const parsed = JSON.parse(message.slice(prefix.length));
        return isRecord(parsed) ? parsed : null;
    }
    catch {
        return null;
    }
}
function parseCapturedEntries(value) {
    if (!Array.isArray(value)) {
        return [];
    }
    return value.flatMap((entry) => {
        if (!isRecord(entry)) {
            return [];
        }
        const id = optionalString(entry.id)?.trim();
        const text = optionalString(entry.text);
        if (!id || text === undefined) {
            return [];
        }
        const parentId = entry.parentId === null ? null : optionalString(entry.parentId)?.trim();
        return [
            {
                id,
                parentId: parentId || null,
                text,
            },
        ];
    });
}
function optionalBoolean(value) {
    return typeof value === "boolean" ? value : undefined;
}
function readActiveAskUserDialog(toolName, args) {
    if (toolName !== "ask_user" || !isRecord(args)) {
        return null;
    }
    return {
        allowComment: optionalBoolean(args.allowComment) ?? false,
        allowFreeform: optionalBoolean(args.allowFreeform) ?? true,
        allowMultiple: optionalBoolean(args.allowMultiple) ?? false,
    };
}
function isOptionalInputPlaceholder(placeholder) {
    return /\boptional\b|\bskip\b/i.test(placeholder ?? "");
}
function getInputQuestionTitle(title, placeholder) {
    if (!isOptionalInputPlaceholder(placeholder)) {
        return title ?? "Enter a value";
    }
    if (/\bcomment\b/i.test(`${title ?? ""}\n${placeholder ?? ""}`)) {
        return "Optional comment";
    }
    return "Optional response";
}
function readStringArray(value) {
    return Array.isArray(value)
        ? value.filter((item) => typeof item === "string")
        : [];
}
function isPiAskUserFreeformOption(option) {
    return option === PI_ASK_USER_FREEFORM_SENTINEL;
}
function mapExtensionUiRequestToPermission(event, options = {}) {
    switch (event.method) {
        case "select": {
            const selectOptions = readStringArray(event.options);
            if (options.combineOptionalComment) {
                return buildCombinedAskUserQuestionPermission(event, {
                    question: optionalString(event.title) ?? "Select an option",
                    options: selectOptions,
                    allowFreeform: options.allowFreeform === true,
                });
            }
            return buildExtensionUiQuestionPermission(event, {
                question: optionalString(event.title) ?? "Select an option",
                options: selectOptions,
                multiSelect: false,
            });
        }
        case "input": {
            const placeholder = optionalString(event.placeholder);
            const title = optionalString(event.title);
            const allowEmpty = isOptionalInputPlaceholder(placeholder);
            return buildExtensionUiQuestionPermission(event, {
                question: getInputQuestionTitle(title, placeholder),
                options: [],
                multiSelect: false,
                ...(placeholder ? { placeholder } : {}),
                ...(allowEmpty ? { allowEmpty: true, dismissLabel: "Skip" } : {}),
            });
        }
        case "editor":
            return buildExtensionUiQuestionPermission(event, {
                question: optionalString(event.title) ?? "Edit text",
                options: [],
                multiSelect: false,
            });
        case "confirm":
            return buildExtensionUiQuestionPermission(event, {
                question: [optionalString(event.title), optionalString(event.message)]
                    .filter(Boolean)
                    .join("\n\n"),
                options: ["Yes", "No"],
                multiSelect: false,
            });
        default:
            return null;
    }
}
function buildExtensionUiQuestionPermission(event, input) {
    return {
        id: event.id,
        provider: PI_PROVIDER,
        name: `Pi ${event.method}`,
        kind: "question",
        title: input.question,
        input: {
            questions: [
                {
                    question: input.question,
                    header: QUESTION_RESPONSE_HEADER,
                    options: input.options.map((label) => ({ label })),
                    multiSelect: input.multiSelect,
                    ...(input.placeholder ? { placeholder: input.placeholder } : {}),
                    ...(input.allowEmpty ? { allowEmpty: true } : {}),
                    ...(input.dismissLabel ? { dismissLabel: input.dismissLabel } : {}),
                },
            ],
        },
        metadata: {
            extensionUiMethod: event.method,
            answerHeader: QUESTION_RESPONSE_HEADER,
        },
    };
}
function buildCombinedAskUserQuestionPermission(event, input) {
    const visibleOptions = input.options.filter((option) => !isPiAskUserFreeformOption(option));
    const allowOther = input.allowFreeform || visibleOptions.length !== input.options.length;
    return {
        id: event.id,
        provider: PI_PROVIDER,
        name: "Pi ask_user",
        kind: "question",
        title: input.question,
        input: {
            questions: [
                {
                    question: input.question,
                    header: QUESTION_RESPONSE_HEADER,
                    options: visibleOptions.map((label) => ({ label })),
                    multiSelect: false,
                    ...(allowOther ? { allowOther: true } : {}),
                },
                {
                    question: "Optional comment",
                    header: QUESTION_COMMENT_HEADER,
                    options: [],
                    multiSelect: false,
                    placeholder: "Optional comment (press Enter to skip)...",
                    allowEmpty: true,
                },
            ],
        },
        metadata: {
            extensionUiMethod: event.method,
            answerHeader: QUESTION_RESPONSE_HEADER,
            commentHeader: QUESTION_COMMENT_HEADER,
            combinedAskUser: COMBINED_ASK_USER_METADATA,
            selectOptions: visibleOptions,
            ...(allowOther ? { freeformSentinel: PI_ASK_USER_FREEFORM_SENTINEL } : {}),
        },
    };
}
function permissionAnswer(input, header) {
    const answers = isRecord(input?.answers) ? input.answers : null;
    if (!answers) {
        return null;
    }
    const answer = answers[header];
    return typeof answer === "string" ? answer : null;
}
function firstPermissionAnswer(input) {
    const answers = isRecord(input?.answers) ? input.answers : null;
    if (!answers) {
        return null;
    }
    const first = Object.values(answers).find((value) => typeof value === "string");
    return typeof first === "string" ? first : null;
}
function isCombinedAskUserPermission(request) {
    return request.metadata?.combinedAskUser === COMBINED_ASK_USER_METADATA;
}
function buildCombinedAskUserSelectionResponse(request, response) {
    if (response.behavior === "deny") {
        return { uiResponse: { cancelled: true }, pendingResponse: null };
    }
    const answer = permissionAnswer(response.updatedInput, QUESTION_RESPONSE_HEADER);
    if (answer === null) {
        return { uiResponse: { cancelled: true }, pendingResponse: null };
    }
    const selectOptions = readStringArray(request.metadata?.selectOptions);
    const freeformSentinel = optionalString(request.metadata?.freeformSentinel);
    const isFreeform = Boolean(freeformSentinel) && !selectOptions.includes(answer);
    const comment = permissionAnswer(response.updatedInput, QUESTION_COMMENT_HEADER) ?? "";
    return {
        uiResponse: { value: isFreeform ? freeformSentinel : answer },
        pendingResponse: {
            comment,
            freeform: isFreeform ? answer : null,
        },
    };
}
function buildExtensionUiResponse(request, response) {
    if (response.behavior === "deny") {
        return { cancelled: true };
    }
    const method = optionalString(request.metadata?.extensionUiMethod);
    const answer = firstPermissionAnswer(response.updatedInput);
    if (answer === null) {
        return { cancelled: true };
    }
    if (method === "confirm") {
        return { confirmed: /^yes$/i.test(answer.trim()) };
    }
    return { value: answer };
}
function mapPiModel(model) {
    return {
        provider: PI_PROVIDER,
        id: `${model.provider}/${model.id}`,
        label: `${model.provider}/${model.name ?? model.id}`,
        description: `${model.provider}/${model.id}`,
        metadata: {
            provider: model.provider,
            modelId: model.id,
        },
        thinkingOptions: model.reasoning ? PI_THINKING_OPTIONS.map(mapThinkingOption) : undefined,
        defaultThinkingOptionId: model.reasoning ? DEFAULT_PI_THINKING_LEVEL : undefined,
    };
}
function createRuntime(logger, runtimeSettings) {
    return new PiCliRuntime({ logger, runtimeSettings });
}
export class PiRpcAgentSession {
    constructor(options) {
        this.provider = PI_PROVIDER;
        this.subscribers = new Set();
        this.activeToolCalls = new Map();
        this.pendingExtensionUiRequests = new Map();
        this.activeAskUserDialog = null;
        this.pendingCombinedAskUserResponse = null;
        this.activeTurnId = null;
        this.capturedUserEntries = [];
        this.capturedUserEntriesById = new Map();
        this.seenUserEntryIds = new Set();
        this.pendingUserMessages = [];
        this.pendingExtensionResults = new Map();
        this.closed = false;
        this.runtimeSession = options.runtimeSession;
        this.config = options.config;
        this.state = options.initialState;
        this.capabilities = options.capabilities;
        this.cleanup = options.cleanup;
        this.lastKnownThinkingOptionId =
            normalizePiThinkingOption(options.config.thinkingOptionId) ??
                this.state.thinkingLevel ??
                null;
        this.runtimeSession.onEvent((event) => {
            this.handleRuntimeEvent(event);
        });
    }
    get id() {
        return this.state.sessionId;
    }
    async run(prompt, options) {
        return runProviderTurn({
            prompt,
            runOptions: options,
            startTurn: (p, o) => this.startTurn(p, o),
            subscribe: (callback) => this.subscribe(callback),
            getSessionId: () => this.state.sessionId,
            reduceFinalText: ({ current, item }) => item.type === "assistant_message" ? `${current}${item.text}` : current,
        });
    }
    async startTurn(prompt, _options) {
        if (this.activeTurnId) {
            throw new Error("A Pi turn is already active");
        }
        const payload = convertPromptInput(prompt);
        const turnId = randomUUID();
        this.activeTurnId = turnId;
        void this.runtimeSession.prompt(payload.text, payload.images).catch((error) => {
            const failedTurnId = this.activeTurnId ?? turnId;
            this.activeTurnId = null;
            if (isPiRequestAbortError(error)) {
                this.emit({
                    type: "turn_canceled",
                    provider: PI_PROVIDER,
                    turnId: failedTurnId,
                    reason: toDiagnosticErrorMessage(error),
                });
                return;
            }
            this.emit({
                type: "turn_failed",
                provider: PI_PROVIDER,
                turnId: failedTurnId,
                error: toDiagnosticErrorMessage(error),
            });
        });
        return { turnId };
    }
    subscribe(callback) {
        this.subscribers.add(callback);
        return () => {
            this.subscribers.delete(callback);
        };
    }
    async *streamHistory() {
        await this.requestEntryCapture("history");
        yield* streamPiHistory(PI_PROVIDER, await this.runtimeSession.getMessages(), this.capturedUserEntries);
    }
    async getRuntimeInfo() {
        await this.refreshState();
        return {
            provider: PI_PROVIDER,
            sessionId: this.state.sessionId,
            model: modelToId(this.state.model),
            thinkingOptionId: resolveThinkingOptionId(this.lastKnownThinkingOptionId, this.state.thinkingLevel),
            modeId: null,
        };
    }
    async getAvailableModes() {
        return [];
    }
    async getCurrentMode() {
        return null;
    }
    async setMode(modeId) {
        void modeId;
        throw new Error("Pi does not expose selectable modes");
    }
    getPendingPermissions() {
        return [...this.pendingExtensionUiRequests.values()];
    }
    async respondToPermission(requestId, response) {
        const request = this.pendingExtensionUiRequests.get(requestId);
        if (!request) {
            throw new Error(`No pending permission request with id '${requestId}'`);
        }
        this.pendingExtensionUiRequests.delete(requestId);
        if (isCombinedAskUserPermission(request)) {
            const combined = buildCombinedAskUserSelectionResponse(request, response);
            this.pendingCombinedAskUserResponse = combined.pendingResponse;
            this.runtimeSession.respondToExtensionUiRequest(requestId, combined.uiResponse);
        }
        else {
            this.runtimeSession.respondToExtensionUiRequest(requestId, buildExtensionUiResponse(request, response));
        }
        this.emit({
            type: "permission_resolved",
            provider: PI_PROVIDER,
            requestId,
            resolution: response,
            turnId: this.currentTurnIdForEvent(),
        });
    }
    describePersistence() {
        return {
            provider: PI_PROVIDER,
            sessionId: this.state.sessionId,
            nativeHandle: this.state.sessionFile,
            metadata: {
                cwd: this.config.cwd,
                ...(this.config.model ? { model: this.config.model } : {}),
                ...(this.config.thinkingOptionId ? { thinkingOptionId: this.config.thinkingOptionId } : {}),
            },
        };
    }
    async interrupt() {
        await this.runtimeSession.abort();
    }
    async revertConversation(input) {
        if (this.activeTurnId) {
            throw new Error("Cannot rewind the Pi conversation while a Pi turn is active");
        }
        await this.refreshState().catch(() => undefined);
        await this.requestEntryCapture("rewind");
        const targetEntry = this.capturedUserEntriesById.get(input.messageId);
        if (!targetEntry) {
            throw new Error(`Pi rewind target ${input.messageId} was not found in captured tree entries`);
        }
        await revertPiConversation({
            messageId: input.messageId,
            navigator: {
                navigateTree: (treeEntryId) => this.runPiTreeExtensionCommand(treeEntryId),
            },
        });
        // Pi keeps all tree nodes, so selecting the previous leaf later reverses this rewind.
        this.currentLeafOverrideId = targetEntry.parentId;
        this.activeToolCalls.clear();
    }
    async runPiTreeExtensionCommand(targetId) {
        const requestId = randomUUID();
        const resultPromise = this.waitForExtensionResult(requestId);
        const payload = Buffer.from(JSON.stringify({ targetId, requestId })).toString("base64url");
        await this.runtimeSession.prompt(`/${PASEO_PI_TREE_EXTENSION_COMMAND} ${payload}`);
        return await resultPromise;
    }
    async close() {
        if (this.closed) {
            return;
        }
        this.closed = true;
        try {
            await this.runtimeSession.close();
        }
        finally {
            this.rejectAllExtensionResults(new Error("Pi session closed"));
            this.cleanup?.();
        }
    }
    async listCommands() {
        const commands = await this.runtimeSession.getCommands();
        return commands.map((command) => ({
            name: command.name,
            description: command.description ?? command.source,
            argumentHint: "",
        }));
    }
    async setModel(modelId) {
        const parsedReference = parseModelReference(modelId);
        if (!parsedReference) {
            return;
        }
        if (!parsedReference.provider) {
            throw new Error(`Pi model id must include a provider: ${modelId}`);
        }
        const model = await this.runtimeSession.setModel(parsedReference.provider, parsedReference.id);
        this.state = {
            ...this.state,
            model,
        };
        this.config.model = `${model.provider}/${model.id}`;
    }
    async setThinkingOption(thinkingOptionId) {
        const thinkingLevel = normalizePiThinkingOption(thinkingOptionId) ?? DEFAULT_PI_THINKING_LEVEL;
        await this.runtimeSession.setThinkingLevel(thinkingLevel);
        this.lastKnownThinkingOptionId = thinkingLevel;
        this.config.thinkingOptionId = thinkingLevel;
        this.state = {
            ...this.state,
            thinkingLevel,
        };
    }
    emit(event) {
        for (const subscriber of this.subscribers) {
            subscriber(event);
        }
    }
    currentTurnIdForEvent() {
        return this.activeTurnId ?? undefined;
    }
    async requestEntryCapture(reason) {
        const requestId = randomUUID();
        const resultPromise = this.waitForExtensionResult(requestId);
        const payload = Buffer.from(JSON.stringify({ requestId, reason })).toString("base64url");
        await this.runtimeSession.prompt(`/${PASEO_PI_CAPTURE_EXTENSION_COMMAND} ${payload}`);
        await resultPromise;
    }
    waitForExtensionResult(requestId) {
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                this.pendingExtensionResults.delete(requestId);
                reject(new Error(`Pi extension result timed out for request ${requestId}`));
            }, PASEO_PI_EXTENSION_RESULT_TIMEOUT_MS);
            this.pendingExtensionResults.set(requestId, { resolve, reject, timer });
        });
    }
    resolveExtensionResult(requestId, result) {
        const pending = this.pendingExtensionResults.get(requestId);
        if (!pending) {
            return;
        }
        clearTimeout(pending.timer);
        this.pendingExtensionResults.delete(requestId);
        pending.resolve(result);
    }
    rejectExtensionResult(requestId, error) {
        const pending = this.pendingExtensionResults.get(requestId);
        if (!pending) {
            return;
        }
        clearTimeout(pending.timer);
        this.pendingExtensionResults.delete(requestId);
        pending.reject(error);
    }
    rejectAllExtensionResults(error) {
        for (const requestId of this.pendingExtensionResults.keys()) {
            this.rejectExtensionResult(requestId, error);
        }
    }
    recordCapturedUserEntries(entries) {
        const previouslySeenEntryIds = new Set(this.seenUserEntryIds);
        this.capturedUserEntries.splice(0, this.capturedUserEntries.length, ...entries);
        this.capturedUserEntriesById.clear();
        for (const entry of entries) {
            this.capturedUserEntriesById.set(entry.id, entry);
        }
        this.flushPendingUserMessages(previouslySeenEntryIds);
        for (const entry of entries) {
            this.seenUserEntryIds.add(entry.id);
        }
    }
    flushPendingUserMessages(previouslySeenEntryIds) {
        for (let index = 0; index < this.pendingUserMessages.length; index += 1) {
            const pending = this.pendingUserMessages[index];
            const entry = this.capturedUserEntries.find((candidate) => !previouslySeenEntryIds.has(candidate.id));
            if (!entry) {
                continue;
            }
            previouslySeenEntryIds.add(entry.id);
            this.pendingUserMessages.splice(index, 1);
            index -= 1;
            this.emit({
                type: "timeline",
                provider: PI_PROVIDER,
                turnId: pending.turnId,
                item: {
                    type: "user_message",
                    text: pending.text,
                    messageId: entry.id,
                },
            });
        }
    }
    handleEntryCaptureMarker(message) {
        const payload = parseExtensionMarkerPayload(message, PASEO_PI_ENTRY_CAPTURE_MARKER);
        if (!payload) {
            return false;
        }
        const entries = parseCapturedEntries(payload.entries);
        this.recordCapturedUserEntries(entries);
        if (typeof payload.requestId === "string") {
            this.resolveExtensionResult(payload.requestId, entries);
        }
        return true;
    }
    handleCommandResultMarker(message) {
        const payload = parseExtensionMarkerPayload(message, PASEO_PI_COMMAND_RESULT_MARKER);
        if (!payload) {
            return false;
        }
        if (typeof payload.requestId !== "string") {
            return true;
        }
        if (payload.ok === true) {
            this.resolveExtensionResult(payload.requestId, payload.result);
            return true;
        }
        const error = typeof payload.error === "string" ? payload.error : "Pi extension command failed";
        this.rejectExtensionResult(payload.requestId, new Error(error));
        return true;
    }
    handleExtensionUiRequest(event) {
        const message = optionalString(event.message);
        if (event.method === "notify" && message) {
            if (this.handleEntryCaptureMarker(message) || this.handleCommandResultMarker(message)) {
                return;
            }
        }
        if (this.respondToCombinedAskUserFollowUp(event)) {
            return;
        }
        const shouldCombineOptionalComment = event.method === "select" &&
            this.activeAskUserDialog?.allowComment === true &&
            this.activeAskUserDialog.allowMultiple === false;
        const request = mapExtensionUiRequestToPermission(event, {
            combineOptionalComment: shouldCombineOptionalComment,
            allowFreeform: this.activeAskUserDialog?.allowFreeform,
        });
        if (!request) {
            return;
        }
        this.pendingExtensionUiRequests.set(request.id, request);
        this.emit({
            type: "permission_requested",
            provider: PI_PROVIDER,
            request,
            turnId: this.currentTurnIdForEvent(),
        });
    }
    respondToCombinedAskUserFollowUp(event) {
        const pending = this.pendingCombinedAskUserResponse;
        if (!pending || event.method !== "input") {
            return false;
        }
        const placeholder = optionalString(event.placeholder);
        if (pending.freeform !== null && !isOptionalInputPlaceholder(placeholder)) {
            this.pendingCombinedAskUserResponse = {
                ...pending,
                freeform: null,
            };
            this.runtimeSession.respondToExtensionUiRequest(event.id, { value: pending.freeform });
            return true;
        }
        if (isOptionalInputPlaceholder(placeholder)) {
            this.pendingCombinedAskUserResponse = null;
            this.runtimeSession.respondToExtensionUiRequest(event.id, { value: pending.comment });
            return true;
        }
        return false;
    }
    handleRuntimeEvent(event) {
        if (event.type === "extension_ui_request") {
            this.handleExtensionUiRequest(event);
            return;
        }
        if (event.type === "process_exit") {
            this.handleProcessExit(event.error);
            return;
        }
        this.handleSessionEvent(event);
    }
    handleProcessExit(error) {
        this.rejectAllExtensionResults(new Error(error));
        if (!this.activeTurnId) {
            return;
        }
        const turnId = this.activeTurnId;
        this.activeTurnId = null;
        this.emit({
            type: "turn_failed",
            provider: PI_PROVIDER,
            turnId,
            error,
        });
    }
    handleSessionEvent(event) {
        const turnId = this.currentTurnIdForEvent();
        switch (event.type) {
            case "agent_start":
                this.emit({
                    type: "thread_started",
                    provider: PI_PROVIDER,
                    sessionId: this.state.sessionId,
                });
                return;
            case "turn_start":
                this.emit({
                    type: "turn_started",
                    provider: PI_PROVIDER,
                    turnId,
                });
                return;
            case "message_start":
                return;
            case "message_end":
                this.handleMessageEnd(event, turnId);
                return;
            case "message_update":
                this.handleMessageUpdate(event, turnId);
                return;
            case "tool_execution_start": {
                const toolCall = parseToolArgs(event.toolName, event.args);
                this.activeToolCalls.set(event.toolCallId, toolCall);
                this.activeAskUserDialog = readActiveAskUserDialog(event.toolName, event.args);
                this.emitToolCallEvent(event.toolCallId, toolCall, "running", null, null);
                return;
            }
            case "tool_execution_update": {
                const toolCall = this.activeToolCalls.get(event.toolCallId);
                if (!toolCall) {
                    return;
                }
                const partialResult = parseToolResult(event.partialResult);
                this.emitToolCallEvent(event.toolCallId, toolCall, "running", partialResult, null);
                return;
            }
            case "tool_execution_end": {
                const toolCall = this.activeToolCalls.get(event.toolCallId) ?? parseToolArgs(event.toolName, null);
                this.activeToolCalls.delete(event.toolCallId);
                if (event.toolName === "ask_user") {
                    this.activeAskUserDialog = null;
                    this.pendingCombinedAskUserResponse = null;
                }
                const result = parseToolResult(event.result);
                const error = event.isError ? event.result : null;
                const status = event.isError ? "failed" : "completed";
                this.emitToolCallEvent(event.toolCallId, toolCall, status, result, error);
                return;
            }
            case "compaction_start":
                this.emit({
                    type: "timeline",
                    provider: PI_PROVIDER,
                    turnId,
                    item: {
                        type: "compaction",
                        status: "loading",
                        trigger: event.reason === "manual" ? "manual" : "auto",
                    },
                });
                return;
            case "compaction_end":
                this.emit({
                    type: "timeline",
                    provider: PI_PROVIDER,
                    turnId,
                    item: {
                        type: "compaction",
                        status: "completed",
                    },
                });
                return;
            case "agent_end":
                this.completeTurn(turnId, event.messages ?? []);
                return;
            default:
                return;
        }
    }
    handleMessageUpdate(event, turnId) {
        if (event.message.role !== "assistant") {
            return;
        }
        if (event.assistantMessageEvent.type === "text_delta") {
            this.emit({
                type: "timeline",
                provider: PI_PROVIDER,
                turnId,
                item: {
                    type: "assistant_message",
                    text: event.assistantMessageEvent.delta ?? "",
                },
            });
            return;
        }
        if (event.assistantMessageEvent.type === "thinking_delta") {
            this.emit({
                type: "timeline",
                provider: PI_PROVIDER,
                turnId,
                item: {
                    type: "reasoning",
                    text: event.assistantMessageEvent.delta ?? "",
                },
            });
        }
    }
    handleMessageEnd(event, turnId) {
        if (event.message.role === "custom") {
            const text = getUserMessageText(event.message.content);
            if (text) {
                this.emit({
                    type: "timeline",
                    provider: PI_PROVIDER,
                    turnId,
                    item: { type: "assistant_message", text },
                });
            }
            this.completeTurn(turnId, []);
            return;
        }
        if (event.message.role !== "user") {
            return;
        }
        const text = getUserMessageText(event.message.content);
        if (!text) {
            return;
        }
        this.pendingUserMessages.push({ text, turnId });
        void this.requestEntryCapture("message_end").catch((error) => {
            const message = error instanceof Error ? error.message : String(error);
            this.emit({
                type: "turn_failed",
                provider: PI_PROVIDER,
                turnId,
                error: message,
            });
        });
    }
    emitToolCallEvent(toolCallId, toolCall, status, result, error) {
        const turnId = this.currentTurnIdForEvent();
        const detail = mapToolDetail(toolCall, result);
        const baseItem = {
            type: "tool_call",
            callId: toolCallId,
            name: resolveToolCallName(toolCall, result),
            detail,
        };
        const item = status === "failed" ? { ...baseItem, status, error } : { ...baseItem, status, error: null };
        this.emit({
            type: "timeline",
            provider: PI_PROVIDER,
            turnId,
            item,
        });
    }
    completeTurn(turnId, messages) {
        this.activeTurnId = null;
        const errorMessage = latestPiErrorMessage(messages);
        if (typeof errorMessage === "string" && errorMessage.length > 0) {
            this.emit({
                type: "turn_failed",
                provider: PI_PROVIDER,
                turnId,
                error: errorMessage,
            });
            return;
        }
        this.emit({
            type: "turn_completed",
            provider: PI_PROVIDER,
            turnId,
        });
        void this.refreshAfterTurn(turnId);
    }
    async refreshState() {
        this.state = await this.runtimeSession.getState();
    }
    async refreshAfterTurn(turnId) {
        await this.refreshState().catch(() => undefined);
        const usage = await this.runtimeSession
            .getSessionStats()
            .then(toAgentUsage)
            .catch(() => undefined);
        if (usage) {
            this.emit({
                type: "usage_updated",
                provider: PI_PROVIDER,
                turnId,
                usage,
            });
        }
    }
}
export class PiRpcAgentClient {
    constructor(options) {
        this.provider = PI_PROVIDER;
        this.capabilities = PI_CAPABILITIES;
        this.logger = options.logger;
        this.runtimeSettings = options.runtimeSettings;
        this.runtime = options.runtime ?? createRuntime(options.logger, options.runtimeSettings);
    }
    async createSession(config, launchContext) {
        const mcpConfig = await this.prepareMcpConfig(config.cwd, config.mcpServers);
        const paseoExtension = createPiPaseoExtensionFile();
        let runtimeSession;
        try {
            runtimeSession = await this.runtime.startSession({
                cwd: config.cwd,
                model: config.model,
                thinkingOptionId: normalizePiThinkingOption(config.thinkingOptionId) ?? DEFAULT_PI_THINKING_LEVEL,
                systemPrompt: composeSystemPromptParts(config.systemPrompt, config.daemonAppendSystemPrompt),
                env: launchContext?.env,
                mcpConfigPath: mcpConfig?.path,
                extensionPaths: [paseoExtension.path],
            });
        }
        catch (error) {
            mcpConfig?.cleanup();
            paseoExtension.cleanup();
            throw error;
        }
        try {
            return new PiRpcAgentSession({
                runtimeSession,
                config,
                initialState: await runtimeSession.getState(),
                capabilities: withPiMcpCapability(mcpConfig !== null),
                cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension.cleanup]),
            });
        }
        catch (error) {
            await runtimeSession.close().catch(() => undefined);
            mcpConfig?.cleanup();
            paseoExtension.cleanup();
            throw error;
        }
    }
    async resumeSession(handle, overrides, _launchContext) {
        const sessionFile = handle.nativeHandle;
        if (!sessionFile) {
            throw new Error("Pi resume requires a native session file handle");
        }
        const persistenceMetadata = parsePersistenceMetadata(handle.metadata);
        const resumeConfig = buildResumeConfig(persistenceMetadata, overrides);
        const mcpConfig = await this.prepareMcpConfig(resumeConfig.cwd, resumeConfig.config.mcpServers);
        const paseoExtension = createPiPaseoExtensionFile();
        let runtimeSession;
        try {
            runtimeSession = await this.runtime.startSession({
                cwd: resumeConfig.cwd,
                session: sessionFile,
                model: resumeConfig.model,
                thinkingOptionId: normalizePiThinkingOption(resumeConfig.thinkingOptionId) ?? undefined,
                systemPrompt: composeSystemPromptParts(resumeConfig.config.systemPrompt, resumeConfig.config.daemonAppendSystemPrompt),
                mcpConfigPath: mcpConfig?.path,
                extensionPaths: [paseoExtension.path],
            });
        }
        catch (error) {
            mcpConfig?.cleanup();
            paseoExtension.cleanup();
            throw error;
        }
        try {
            return new PiRpcAgentSession({
                runtimeSession,
                config: resumeConfig.config,
                initialState: await runtimeSession.getState(),
                capabilities: withPiMcpCapability(mcpConfig !== null),
                cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension.cleanup]),
            });
        }
        catch (error) {
            await runtimeSession.close().catch(() => undefined);
            mcpConfig?.cleanup();
            paseoExtension.cleanup();
            throw error;
        }
    }
    async listModels(options) {
        const runtimeSession = await this.runtime.startSession({ cwd: options.cwd });
        try {
            return transformPiModels((await runtimeSession.getAvailableModels()).map(mapPiModel));
        }
        finally {
            await runtimeSession.close();
        }
    }
    async listModes(_options) {
        return [];
    }
    async listPersistedAgents(_options) {
        return [];
    }
    async isAvailable() {
        const launch = await this.resolvePiLaunch();
        const availability = await checkProviderLaunchAvailable(launch);
        if (!availability.available) {
            return false;
        }
        const runtimeSession = await this.runtime.startSession({ cwd: homedir() }).catch(() => null);
        if (!runtimeSession) {
            return false;
        }
        try {
            return (await runtimeSession.getAvailableModels()).length > 0;
        }
        catch {
            return false;
        }
        finally {
            await runtimeSession.close().catch(() => undefined);
        }
    }
    async getDiagnostic() {
        try {
            const launch = await this.resolvePiLaunch();
            const availability = await checkProviderLaunchAvailable(launch);
            const available = availability.available;
            const authConfigPath = join(homedir(), ".pi", "agent", "auth.json");
            let modelsValue = "Not checked";
            let configuredProvidersValue = "none";
            let mcpToolsValue = "Not checked";
            let status = formatDiagnosticStatus(available);
            if (availability.available) {
                const runtimeSession = await this.runtime
                    .startSession({ cwd: homedir() })
                    .catch((error) => {
                    status = formatDiagnosticStatus(false, {
                        source: "startup",
                        cause: error,
                    });
                    return null;
                });
                if (runtimeSession) {
                    try {
                        const models = await runtimeSession.getAvailableModels();
                        modelsValue = String(models.length);
                        const configuredProviders = Array.from(new Set(models.map((model) => model.provider))).sort();
                        configuredProvidersValue =
                            configuredProviders.length > 0 ? configuredProviders.join(", ") : "none";
                        const commands = await runtimeSession.getCommands();
                        mcpToolsValue = commands.some(isPiMcpAdapterCommand)
                            ? "yes (pi-mcp-adapter loaded)"
                            : "no (install pi-mcp-adapter)";
                    }
                    catch (error) {
                        modelsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
                        mcpToolsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
                        status = formatDiagnosticStatus(available, {
                            source: "model fetch",
                            cause: error,
                        });
                    }
                    finally {
                        await runtimeSession.close().catch(() => undefined);
                    }
                }
            }
            return {
                diagnostic: formatProviderDiagnostic("Pi", [
                    ...(await buildBinaryDiagnosticRows(launch, availability)),
                    { label: "Configured providers", value: configuredProvidersValue },
                    {
                        label: "Auth config (~/.pi/agent/auth.json)",
                        value: existsSync(authConfigPath) ? "found" : "not found",
                    },
                    { label: "Models", value: modelsValue },
                    { label: "Paseo MCP tools", value: mcpToolsValue },
                    { label: "Status", value: status },
                ]),
            };
        }
        catch (error) {
            this.logger.debug({ err: error }, "Pi diagnostic lookup failed");
            return {
                diagnostic: formatProviderDiagnosticError("Pi", error),
            };
        }
    }
    async prepareMcpConfig(cwd, servers) {
        if (!servers || Object.keys(servers).length === 0) {
            return null;
        }
        if (!(await this.detectMcpAdapter(cwd))) {
            return null;
        }
        return createPiMcpConfigFile(servers);
    }
    async detectMcpAdapter(cwd) {
        const runtimeSession = await this.runtime.startSession({ cwd }).catch((error) => {
            this.logger.debug({ err: error, cwd }, "Pi MCP adapter probe failed to start");
            return null;
        });
        if (!runtimeSession) {
            return false;
        }
        try {
            return (await runtimeSession.getCommands()).some(isPiMcpAdapterCommand);
        }
        catch (error) {
            this.logger.debug({ err: error, cwd }, "Pi MCP adapter probe failed");
            return false;
        }
        finally {
            await runtimeSession.close().catch(() => undefined);
        }
    }
    async resolvePiLaunch() {
        return resolveProviderLaunch({
            commandConfig: this.runtimeSettings?.command,
            defaultBinary: PI_BINARY_COMMAND,
        });
    }
}
//# sourceMappingURL=agent.js.map