import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { AuthStorage, ModelRegistry, SessionManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, } from "@mariozechner/pi-coding-agent";
import { z } from "zod";
import { getAgentStreamEventTurnId, } from "../agent-sdk-types.js";
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
import { findExecutable, isCommandAvailable } from "../../../utils/executable.js";
import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, resolveBinaryVersion, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
import { applyPiSessionRecoveryPolicy } from "./pi-session-recovery-policy.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 PI_CAPABILITIES = {
    supportsStreaming: true,
    supportsSessionPersistence: true,
    supportsDynamicModes: true,
    supportsMcpServers: false,
    supportsReasoningStream: true,
    supportsToolInvocations: true,
};
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" },
];
const PiPromptTextBlockSchema = z.object({
    type: z.literal("text"),
    text: z.string(),
});
const PiToolResultTextContentSchema = z.object({
    type: z.literal("text"),
    text: z.string(),
});
const PiToolResultUnknownContentSchema = z
    .object({
    type: z.string(),
})
    .passthrough();
const PiToolResultContentSchema = z.union([
    PiToolResultTextContentSchema,
    PiToolResultUnknownContentSchema,
]);
const PiToolResultDetailsSchema = z
    .object({
    diff: z.string().optional(),
})
    .passthrough();
const PiToolResultObjectSchema = z
    .object({
    output: z.string().optional(),
    stdout: z.string().optional(),
    text: z.string().optional(),
    content: z.array(PiToolResultContentSchema).optional(),
    exitCode: z.number().optional(),
    code: z.number().optional(),
    details: PiToolResultDetailsSchema.optional(),
})
    .passthrough();
const PiToolResultSchema = z.union([z.string(), PiToolResultObjectSchema, z.null()]);
const PiPersistenceMetadataSchema = z
    .object({
    cwd: z.string().optional(),
})
    .passthrough();
const BashToolInputSchema = z.object({
    command: z.string(),
    timeout: z.number().optional(),
});
const ReadToolInputSchema = z.object({
    path: z.string(),
    offset: z.number().optional(),
    limit: z.number().optional(),
});
const EditToolInputSchema = z.object({
    path: z.string(),
    edits: z.array(z.object({
        oldText: z.string(),
        newText: z.string(),
    })),
});
const LegacyEditToolInputSchema = z.object({
    path: z.string(),
    old_string: z.string().optional(),
    oldString: z.string().optional(),
    new_string: z.string().optional(),
    newString: z.string().optional(),
});
const WriteToolInputSchema = z.object({
    path: z.string(),
    content: z.string(),
});
const FindToolInputSchema = z.object({
    pattern: z.string(),
    path: z.string().optional(),
    limit: z.number().optional(),
});
const GrepToolInputSchema = z.object({
    pattern: z.string(),
    path: z.string().optional(),
    glob: z.string().optional(),
    ignoreCase: z.boolean().optional(),
    literal: z.boolean().optional(),
    context: z.number().optional(),
    limit: z.number().optional(),
});
const LsToolInputSchema = z.object({
    path: z.string().optional(),
    limit: z.number().optional(),
});
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 toAgentUsage(stats) {
    const inputTokens = stats.tokens.input;
    const cachedInputTokens = stats.tokens.cacheRead;
    const outputTokens = stats.tokens.output;
    const totalCostUsd = stats.cost;
    if (inputTokens === 0 && cachedInputTokens === 0 && outputTokens === 0 && totalCostUsd === 0) {
        return undefined;
    }
    return {
        inputTokens,
        cachedInputTokens,
        outputTokens,
        totalCostUsd,
    };
}
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 parseToolResult(rawResult) {
    const parsed = PiToolResultSchema.safeParse(rawResult);
    if (parsed.success) {
        return parsed.data;
    }
    return null;
}
function extractTextFromToolResult(result) {
    if (typeof result === "string") {
        return result;
    }
    if (!result) {
        return undefined;
    }
    const directText = result.output ?? result.stdout ?? result.text;
    if (directText) {
        return directText;
    }
    if (!result.content) {
        return undefined;
    }
    const textParts = [];
    for (const block of result.content) {
        if (block.type === "text" && "text" in block) {
            textParts.push(block.text);
        }
    }
    if (textParts.length === 0) {
        return undefined;
    }
    return textParts.join("\n");
}
function resolveToolCallOutput(result) {
    if (typeof result === "string") {
        return { output: result };
    }
    if (!result) {
        return {};
    }
    const summary = {
        output: extractTextFromToolResult(result),
    };
    if (typeof result.exitCode === "number") {
        summary.exitCode = result.exitCode;
        return summary;
    }
    if (typeof result.code === "number") {
        summary.exitCode = result.code;
        return summary;
    }
    summary.exitCode = null;
    return summary;
}
function normalizeLegacyEditArgs(rawArgs) {
    const parsed = LegacyEditToolInputSchema.safeParse(rawArgs);
    if (!parsed.success) {
        return null;
    }
    const oldText = parsed.data.old_string ?? parsed.data.oldString;
    const newText = parsed.data.new_string ?? parsed.data.newString;
    if (!oldText || newText === undefined) {
        return null;
    }
    return {
        path: parsed.data.path,
        edits: [{ oldText, newText }],
    };
}
function parseEditToolArgs(rawArgs) {
    const parsed = EditToolInputSchema.safeParse(rawArgs);
    if (parsed.success) {
        return { kind: "edit", toolName: "edit", args: parsed.data };
    }
    const legacyArgs = normalizeLegacyEditArgs(rawArgs);
    if (legacyArgs) {
        return { kind: "edit", toolName: "edit", args: legacyArgs };
    }
    return { kind: "unknown", toolName: "edit", args: rawArgs ?? null };
}
const SIMPLE_TOOL_SCHEMAS = {
    bash: BashToolInputSchema,
    read: ReadToolInputSchema,
    write: WriteToolInputSchema,
    find: FindToolInputSchema,
    grep: GrepToolInputSchema,
    ls: LsToolInputSchema,
};
function parseToolArgs(toolName, rawArgs) {
    if (toolName === "edit") {
        return parseEditToolArgs(rawArgs);
    }
    const schema = SIMPLE_TOOL_SCHEMAS[toolName];
    if (schema) {
        const parsed = schema.safeParse(rawArgs);
        if (parsed.success) {
            return { kind: toolName, toolName, args: parsed.data };
        }
    }
    return { kind: "unknown", toolName, args: rawArgs ?? null };
}
function mapFindToolDetail(args, result) {
    return {
        type: "search",
        query: args.pattern,
        toolName: "search",
        content: typeof result === "string" ? result : undefined,
    };
}
function mapGrepToolDetail(args, result) {
    return {
        type: "search",
        query: args.pattern,
        toolName: "grep",
        content: typeof result === "string" ? result : undefined,
    };
}
function mapLsToolDetail(args, result) {
    const query = args.path ?? "ls";
    return {
        type: "search",
        query,
        content: typeof result === "string" ? result : undefined,
    };
}
function mapToolDetail(toolCall, result) {
    const parsedResult = result ?? null;
    switch (toolCall.kind) {
        case "bash": {
            const summary = resolveToolCallOutput(parsedResult);
            return {
                type: "shell",
                command: toolCall.args.command,
                output: summary.output,
                exitCode: summary.exitCode,
            };
        }
        case "read":
            return {
                type: "read",
                filePath: toolCall.args.path,
                content: extractTextFromToolResult(parsedResult),
                offset: toolCall.args.offset,
                limit: toolCall.args.limit,
            };
        case "edit": {
            const firstEdit = toolCall.args.edits[0];
            const unifiedDiff = parsedResult && typeof parsedResult !== "string" ? parsedResult.details?.diff : undefined;
            return {
                type: "edit",
                filePath: toolCall.args.path,
                oldString: firstEdit?.oldText,
                newString: firstEdit?.newText,
                unifiedDiff,
            };
        }
        case "write":
            return {
                type: "write",
                filePath: toolCall.args.path,
                content: toolCall.args.content,
            };
        case "find":
            return mapFindToolDetail(toolCall.args, parsedResult);
        case "grep":
            return mapGrepToolDetail(toolCall.args, parsedResult);
        case "ls":
            return mapLsToolDetail(toolCall.args, parsedResult);
        default:
            return {
                type: "unknown",
                input: toolCall.args,
                output: parsedResult,
            };
    }
}
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 mapResolvedCommand(command) {
    return {
        name: command.invocationName,
        description: command.description ?? "Extension command",
        argumentHint: "",
    };
}
function mapSkillCommand(skill) {
    return {
        name: `skill:${skill.name}`,
        description: skill.description || "Skill",
        argumentHint: "",
    };
}
function buildSlashCommands(session) {
    const commands = [];
    const extensionCommands = session.extensionRunner?.getRegisteredCommands() ?? [];
    for (const command of extensionCommands) {
        commands.push(mapResolvedCommand(command));
    }
    for (const template of session.promptTemplates) {
        commands.push({
            name: template.name,
            description: template.description ?? "Prompt template",
            argumentHint: "",
        });
    }
    const resourceLoader = session.resourceLoader;
    const skills = resourceLoader.getSkills().skills;
    for (const skill of skills) {
        commands.push(mapSkillCommand(skill));
    }
    return commands;
}
function applySystemPrompt(session, systemPrompt) {
    const trimmed = systemPrompt?.trim();
    if (!trimmed) {
        return;
    }
    // Pi does not expose a public setter for composing an additional system prompt,
    // so this escape hatch is isolated to one typed boundary.
    const sessionObject = session;
    const baseSystemPrompt = Reflect.get(sessionObject, "_baseSystemPrompt");
    const currentBase = typeof baseSystemPrompt === "string" ? baseSystemPrompt : session.agent.state.systemPrompt;
    const combinedPrompt = currentBase ? `${currentBase}\n\n${trimmed}` : trimmed;
    Reflect.set(sessionObject, "_baseSystemPrompt", combinedPrompt);
    session.agent.state.systemPrompt = combinedPrompt;
}
function isTextContentBlock(block) {
    return PiPromptTextBlockSchema.safeParse(block).success;
}
function getUserMessageText(content) {
    if (typeof content === "string") {
        return content;
    }
    const textParts = [];
    for (const block of content) {
        if (isTextContentBlock(block)) {
            textParts.push(block.text);
        }
    }
    return textParts.join("\n\n");
}
function parsePersistenceMetadata(metadata) {
    const parsed = PiPersistenceMetadataSchema.safeParse(metadata);
    if (parsed.success) {
        return parsed.data;
    }
    return {};
}
function isPiRequestAbortError(error) {
    if (error instanceof Error && error.name === "AbortError") {
        return true;
    }
    return /\brequest was aborted\b/i.test(toDiagnosticErrorMessage(error));
}
function resolveThinkingOptionId(cachedThinkingOptionId, sessionThinkingLevel) {
    const currentThinking = cachedThinkingOptionId ?? sessionThinkingLevel;
    return normalizePiThinkingOption(currentThinking);
}
function mapThinkingOption(option) {
    const mappedOption = {
        id: option.id,
        label: option.label,
        description: option.description,
    };
    if (option.isDefault) {
        return {
            ...mappedOption,
            isDefault: true,
        };
    }
    return mappedOption;
}
function findModelInRegistry(registry, parsedReference) {
    if (parsedReference.provider) {
        return (registry.find(parsedReference.provider, parsedReference.id) ??
            createProviderModelFallback(registry, {
                provider: parsedReference.provider,
                id: parsedReference.id,
            }));
    }
    return registry.getAll().find((entry) => {
        if (entry.id === parsedReference.id) {
            return true;
        }
        return `${entry.provider}/${entry.id}` === parsedReference.id;
    });
}
function createProviderModelFallback(registry, parsedReference) {
    const providerDefault = registry
        .getAll()
        .find((model) => model.provider === parsedReference.provider);
    if (!providerDefault) {
        return undefined;
    }
    return {
        id: parsedReference.id,
        name: parsedReference.id,
        api: providerDefault.api,
        provider: parsedReference.provider,
        baseUrl: providerDefault.baseUrl,
        reasoning: false,
        input: ["text"],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        contextWindow: 128000,
        maxTokens: 16384,
        compat: providerDefault.compat,
    };
}
export class PiDirectAgentSession {
    constructor(runtime, modelRegistry, config) {
        this.runtime = runtime;
        this.modelRegistry = modelRegistry;
        this.config = config;
        this.provider = PI_PROVIDER;
        this.capabilities = PI_CAPABILITIES;
        this.subscribers = new Set();
        this.activeToolCalls = new Map();
        this.activeTurnId = null;
        const session = this.session;
        this.lastKnownThinkingOptionId =
            normalizePiThinkingOption(config.thinkingOptionId) ?? session.thinkingLevel ?? null;
        session.subscribe((event) => {
            this.handleSessionEvent(event);
        });
    }
    get session() {
        return this.runtime.session;
    }
    get id() {
        return this.session.sessionId;
    }
    emit(event) {
        for (const subscriber of this.subscribers) {
            subscriber(event);
        }
    }
    currentTurnIdForEvent() {
        return this.activeTurnId ?? undefined;
    }
    emitToolCallEvent(toolCallId, toolCall, status, result, error) {
        const turnId = this.currentTurnIdForEvent();
        const detail = mapToolDetail(toolCall, result);
        const baseItem = {
            type: "tool_call",
            callId: toolCallId,
            name: toolCall.toolName,
            detail,
        };
        const item = status === "failed" ? { ...baseItem, status, error } : { ...baseItem, status, error: null };
        this.emit({
            type: "timeline",
            provider: PI_PROVIDER,
            turnId,
            item,
        });
    }
    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 ?? "",
                },
            });
        }
    }
    handleSessionEvent(event) {
        const turnId = this.currentTurnIdForEvent();
        switch (event.type) {
            case "agent_start":
                this.emit({
                    type: "thread_started",
                    provider: PI_PROVIDER,
                    sessionId: this.session.sessionId,
                });
                return;
            case "turn_start":
                this.emit({
                    type: "turn_started",
                    provider: PI_PROVIDER,
                    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.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);
                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 "turn_end":
                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.latestUsage = toAgentUsage(this.session.getSessionStats());
                const currentTurnId = turnId;
                this.activeTurnId = null;
                if (this.session.agent.state.errorMessage) {
                    this.emit({
                        type: "turn_failed",
                        provider: PI_PROVIDER,
                        turnId: currentTurnId,
                        error: this.session.agent.state.errorMessage,
                    });
                    return;
                }
                this.emit({
                    type: "turn_completed",
                    provider: PI_PROVIDER,
                    turnId: currentTurnId,
                    usage: this.latestUsage,
                });
                return;
            }
            default:
                return;
        }
    }
    async run(prompt, options) {
        const timeline = [];
        let finalText = "";
        let usage;
        let turnId = null;
        const bufferedEvents = [];
        let settled = false;
        let resolveCompletion;
        let rejectCompletion;
        function processEvent(event) {
            if (settled) {
                return;
            }
            const eventTurnId = getAgentStreamEventTurnId(event);
            if (turnId && eventTurnId && eventTurnId !== turnId) {
                return;
            }
            if (event.type === "timeline") {
                timeline.push(event.item);
                if (event.item.type === "assistant_message") {
                    finalText += event.item.text;
                }
                return;
            }
            if (event.type === "turn_completed") {
                usage = event.usage;
                settled = true;
                resolveCompletion();
                return;
            }
            if (event.type === "turn_failed") {
                settled = true;
                rejectCompletion(new Error(event.error));
            }
        }
        const completion = new Promise((resolve, reject) => {
            resolveCompletion = resolve;
            rejectCompletion = reject;
        });
        const unsubscribe = this.subscribe((event) => {
            if (!turnId) {
                bufferedEvents.push(event);
                return;
            }
            processEvent(event);
        });
        try {
            const result = await this.startTurn(prompt, options);
            turnId = result.turnId;
            for (const event of bufferedEvents) {
                processEvent(event);
            }
            if (!settled) {
                await completion;
            }
        }
        finally {
            unsubscribe();
        }
        return {
            sessionId: this.session.sessionId,
            finalText,
            usage,
            timeline,
        };
    }
    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;
        try {
            await applyPiSessionRecoveryPolicy(this.session);
        }
        catch (error) {
            this.activeTurnId = null;
            this.emit({
                type: "turn_failed",
                provider: PI_PROVIDER,
                turnId,
                error: toDiagnosticErrorMessage(error),
            });
            return { turnId };
        }
        void this.session
            .prompt(payload.text, payload.images ? { images: payload.images } : undefined)
            .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() {
        const pendingToolCalls = new Map();
        let userIndex = 0;
        for (const message of this.session.messages) {
            if (message.role === "user") {
                const text = getUserMessageText(message.content);
                if (text) {
                    yield {
                        type: "timeline",
                        provider: PI_PROVIDER,
                        item: {
                            type: "user_message",
                            text,
                            messageId: `pi-user-${userIndex}`,
                        },
                    };
                }
                userIndex += 1;
                continue;
            }
            if (message.role === "assistant") {
                for (const content of message.content) {
                    if (content.type === "text" && content.text) {
                        yield {
                            type: "timeline",
                            provider: PI_PROVIDER,
                            item: { type: "assistant_message", text: content.text },
                        };
                        continue;
                    }
                    if (content.type === "thinking" && content.thinking) {
                        yield {
                            type: "timeline",
                            provider: PI_PROVIDER,
                            item: { type: "reasoning", text: content.thinking },
                        };
                        continue;
                    }
                    if (content.type === "toolCall") {
                        const tracked = parseToolArgs(content.name, content.arguments);
                        pendingToolCalls.set(content.id, tracked);
                        yield {
                            type: "timeline",
                            provider: PI_PROVIDER,
                            item: {
                                type: "tool_call",
                                callId: content.id,
                                name: tracked.toolName,
                                status: "running",
                                detail: mapToolDetail(tracked, null),
                                error: null,
                            },
                        };
                    }
                }
                continue;
            }
            if (message.role === "toolResult") {
                const tracked = pendingToolCalls.get(message.toolCallId) ?? parseToolArgs(message.toolName, null);
                pendingToolCalls.delete(message.toolCallId);
                const result = parseToolResult({ content: message.content });
                const detail = mapToolDetail(tracked, result);
                if (message.isError) {
                    const errorText = extractTextFromToolResult(result) ?? "Tool call failed";
                    yield {
                        type: "timeline",
                        provider: PI_PROVIDER,
                        item: {
                            type: "tool_call",
                            callId: message.toolCallId,
                            name: tracked.toolName,
                            status: "failed",
                            detail,
                            error: errorText,
                        },
                    };
                }
                else {
                    yield {
                        type: "timeline",
                        provider: PI_PROVIDER,
                        item: {
                            type: "tool_call",
                            callId: message.toolCallId,
                            name: tracked.toolName,
                            status: "completed",
                            detail,
                            error: null,
                        },
                    };
                }
                continue;
            }
            if (message.role === "bashExecution") {
                const callId = `pi-bash-${message.timestamp}`;
                const exitCode = message.exitCode ?? null;
                const detail = {
                    type: "shell",
                    command: message.command,
                    output: message.output,
                    exitCode,
                };
                yield {
                    type: "timeline",
                    provider: PI_PROVIDER,
                    item: {
                        type: "tool_call",
                        callId,
                        name: "bash",
                        status: message.cancelled ? "canceled" : "completed",
                        detail,
                        error: null,
                    },
                };
            }
        }
    }
    async getRuntimeInfo() {
        const thinkingOptionId = resolveThinkingOptionId(this.lastKnownThinkingOptionId, this.session.thinkingLevel);
        return {
            provider: PI_PROVIDER,
            sessionId: this.session.sessionId,
            model: this.session.model ? `${this.session.model.provider}/${this.session.model.id}` : null,
            thinkingOptionId,
            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 [];
    }
    async respondToPermission(requestId, response) {
        void requestId;
        void response;
    }
    describePersistence() {
        return {
            provider: PI_PROVIDER,
            sessionId: this.session.sessionId,
            nativeHandle: this.session.sessionManager.getSessionFile(),
            metadata: {
                cwd: this.session.sessionManager.getCwd(),
            },
        };
    }
    async interrupt() {
        await this.session.abort();
    }
    async close() {
        await this.runtime.dispose();
    }
    async listCommands() {
        return buildSlashCommands(this.session);
    }
    async setModel(modelId) {
        const parsedReference = parseModelReference(modelId);
        if (!parsedReference) {
            return;
        }
        const model = findModelInRegistry(this.modelRegistry, parsedReference);
        if (!model) {
            throw new Error(`Unknown Pi model: ${modelId}`);
        }
        await this.session.setModel(model);
        this.config.model = `${model.provider}/${model.id}`;
    }
    async setThinkingOption(thinkingOptionId) {
        const thinkingLevel = normalizePiThinkingOption(thinkingOptionId) ?? DEFAULT_PI_THINKING_LEVEL;
        this.session.setThinkingLevel(thinkingLevel);
        this.lastKnownThinkingOptionId = thinkingLevel;
        this.config.thinkingOptionId = thinkingLevel;
    }
}
export class PiDirectAgentClient {
    constructor(options) {
        this.provider = PI_PROVIDER;
        this.capabilities = PI_CAPABILITIES;
        this.modelRegistry = null;
        this.logger = options.logger;
        this.runtimeSettings = options.runtimeSettings;
    }
    async getSessionServices(cwd, agentDir) {
        return createAgentSessionServices({
            cwd,
            ...(agentDir ? { agentDir } : {}),
            ...(this.modelRegistry ? { modelRegistry: this.modelRegistry } : {}),
        });
    }
    resolveConfiguredModel(registry, modelId) {
        const parsedReference = parseModelReference(modelId ?? null);
        if (!parsedReference) {
            return undefined;
        }
        return findModelInRegistry(registry, parsedReference);
    }
    async createSdkRuntime(config, sessionManager, options = {}) {
        const createRuntime = async ({ cwd, agentDir, sessionManager: runtimeSessionManager, sessionStartEvent, }) => {
            const thinkingLevel = normalizePiThinkingOption(config.thinkingOptionId) ?? options.defaultThinkingLevel;
            const services = await this.getSessionServices(cwd, agentDir);
            const model = this.resolveConfiguredModel(services.modelRegistry, config.model);
            return {
                ...(await createAgentSessionFromServices({
                    services,
                    sessionManager: runtimeSessionManager,
                    sessionStartEvent,
                    ...(thinkingLevel ? { thinkingLevel } : {}),
                    ...(model ? { model } : {}),
                })),
                services,
                diagnostics: services.diagnostics,
            };
        };
        const runtime = await createAgentSessionRuntime(createRuntime, {
            cwd: sessionManager.getCwd(),
            agentDir: getAgentDir(),
            sessionManager,
        });
        await runtime.session.bindExtensions({});
        applySystemPrompt(runtime.session, config.systemPrompt);
        return { runtime, modelRegistry: runtime.services.modelRegistry };
    }
    async createSession(config, _launchContext) {
        const { runtime, modelRegistry } = await this.createSdkRuntime(config, SessionManager.create(config.cwd), {
            defaultThinkingLevel: DEFAULT_PI_THINKING_LEVEL,
        });
        return new PiDirectAgentSession(runtime, modelRegistry, config);
    }
    async resumeSession(handle, overrides, _launchContext) {
        const sessionFile = handle.nativeHandle;
        if (!sessionFile) {
            throw new Error("Pi resume requires a native session file handle");
        }
        const initialManager = SessionManager.open(sessionFile);
        const persistenceMetadata = parsePersistenceMetadata(handle.metadata);
        const cwd = overrides?.cwd ?? persistenceMetadata.cwd ?? initialManager.getCwd();
        const resumedManager = SessionManager.open(sessionFile, undefined, cwd);
        const mergedConfig = {
            provider: PI_PROVIDER,
            cwd,
            model: overrides?.model,
            thinkingOptionId: overrides?.thinkingOptionId,
            systemPrompt: overrides?.systemPrompt,
            featureValues: overrides?.featureValues,
            title: overrides?.title,
            approvalPolicy: overrides?.approvalPolicy,
            sandboxMode: overrides?.sandboxMode,
            networkAccess: overrides?.networkAccess,
            webSearch: overrides?.webSearch,
            extra: overrides?.extra,
            mcpServers: overrides?.mcpServers,
            internal: overrides?.internal,
            modeId: overrides?.modeId,
        };
        const { runtime, modelRegistry } = await this.createSdkRuntime(mergedConfig, resumedManager);
        return new PiDirectAgentSession(runtime, modelRegistry, mergedConfig);
    }
    async listModels(options) {
        const services = await this.getSessionServices(options.cwd);
        const models = services.modelRegistry.getAvailable().map((model) => ({
            provider: PI_PROVIDER,
            id: `${model.provider}/${model.id}`,
            label: `${model.provider}/${model.name}`,
            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,
        }));
        return transformPiModels(models);
    }
    async listModes(_options) {
        return [];
    }
    async isAvailable() {
        const command = this.runtimeSettings?.command;
        if (command?.mode === "replace" && command.argv[0]) {
            if (!existsSync(command.argv[0])) {
                return false;
            }
        }
        else if (!(await isCommandAvailable(PI_BINARY_COMMAND))) {
            return false;
        }
        const registry = ModelRegistry.create(AuthStorage.create());
        return registry.getAvailable().length > 0;
    }
    async getDiagnostic() {
        try {
            const available = await this.isAvailable();
            const binaryOverride = this.runtimeSettings?.command;
            const binary = binaryOverride?.mode === "replace" && binaryOverride.argv[0]
                ? binaryOverride.argv[0]
                : await findExecutable(PI_BINARY_COMMAND);
            const version = binary ? await resolveBinaryVersion(binary) : "unknown";
            const authConfigPath = join(homedir(), ".pi", "agent", "auth.json");
            let modelsValue = "Not checked";
            let status = formatDiagnosticStatus(available);
            const registry = ModelRegistry.create(AuthStorage.create());
            const configuredProviders = Array.from(new Set(registry.getAvailable().map((model) => model.provider))).sort();
            if (available) {
                try {
                    const models = await this.listModels({ cwd: homedir(), force: false });
                    modelsValue = String(models.length);
                }
                catch (error) {
                    modelsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
                    status = formatDiagnosticStatus(available, {
                        source: "model fetch",
                        cause: error,
                    });
                }
            }
            return {
                diagnostic: formatProviderDiagnostic("Pi", [
                    { label: "Binary", value: binary ?? "not found" },
                    { label: "Version", value: version },
                    {
                        label: "Configured providers",
                        value: configuredProviders.length > 0 ? configuredProviders.join(", ") : "none",
                    },
                    {
                        label: "Auth config (~/.pi/agent/auth.json)",
                        value: existsSync(authConfigPath) ? "found" : "not found",
                    },
                    { label: "Models", value: modelsValue },
                    { label: "Status", value: status },
                ]),
            };
        }
        catch (error) {
            this.logger.debug({ err: error }, "Pi diagnostic lookup failed");
            return {
                diagnostic: formatProviderDiagnosticError("Pi", error),
            };
        }
    }
}
//# sourceMappingURL=pi-direct-agent.js.map