import { AuthStorage, createAgentSession, DefaultResourceLoader, ModelRegistry, SessionManager as PiSessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
import crypto from "crypto";
import fs from "fs";
import path from "path";
import { getConfigDir, getSkillsDir, loadSessionId, loadUsageSnapshot, saveSessionId, saveUsageSnapshot } from "../../../config/index.js";
import { logger } from "../../../logger.js";
import { logSystemPrompt } from "../system-prompt-log.js";
import { resolvePiModel } from "../pi/shared/model.js";
import { convertPiAgentEventToStreamMessages } from "../pi/shared/event-converter.js";
import { buildPiResultMessage, capturePiMessageUsage, createPiUsageAccumulator } from "../pi/shared/usage.js";
import { createPiCodingCustomTools } from "./tools.js";
import { pruneSessionImages, sanitizeTranscript } from "../../image-pruner.js";
import { deriveArchivePath, trimTranscriptAfterCompaction } from "../../session-trimmer.js";
import { writePiMcpConfig } from "./mcp-config.js";
import { ensureBrowserForTool, isPlaywrightToolName } from "../browser-tool-policy.js";
const MAX_TURNS = 100;
const MAX_BASE64_IMAGE_BLOCKS = 10;
async function loadMcpAdapter() {
    const module = await import("pi-mcp-adapter");
    return module.default;
}
export class PiCodingAgentSession {
    config;
    buildSystemPrompt;
    runtimeSurface;
    getDualSessionEnabled;
    getAndroidUseEnabled;
    getCardAvailable;
    agentDir;
    sessionDir;
    dynamicMcpServers = {};
    session = null;
    sessionFilePath = null;
    sessionOpenMode = "new";
    currentSystemPrompt = null;
    currentMcpFingerprint = null;
    currentRuntimeFingerprint = null;
    pendingResetSummary = null;
    _screenshotInjector = null;
    inputClosed = true;
    stopRequested = false;
    playwriterConnectionsResetThisTurn = false;
    lastUsageSnapshot = null;
    sessionId;
    orphanedInjections = 0;
    mode;
    set screenshotInjector(injector) {
        this._screenshotInjector = injector;
    }
    constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }) {
        this.config = config;
        this.buildSystemPrompt = buildSystemPrompt;
        this.runtimeSurface = runtimeSurface;
        this.getDualSessionEnabled = sessionContext.getDualSessionEnabled;
        this.getAndroidUseEnabled = sessionContext.getAndroidUseEnabled;
        this.getCardAvailable = sessionContext.getCardAvailable;
        this.mode = sessionContext.mode;
        this.agentDir = path.join(getConfigDir(), "pi-agent", this.mode);
        this.sessionDir = path.join(this.agentDir, "sessions");
        const persistedSessionId = loadSessionId(this.mode);
        this.sessionId = persistedSessionId ?? createSessionId();
        saveSessionId(this.sessionId, this.mode);
        const persistedUsage = loadUsageSnapshot(this.mode);
        if (persistedUsage) {
            this.lastUsageSnapshot = { ...persistedUsage, capturedAtMs: Date.now() };
        }
    }
    async warmUp() {
        // Pi Coding Agent sessions are created lazily per run.
    }
    discardWarmQuery() {
        this.disposeCurrentSession();
        this.currentSystemPrompt = null;
        this.currentMcpFingerprint = null;
        this.currentRuntimeFingerprint = null;
    }
    sendAndStream(content, options) {
        const systemPrompt = this.buildSystemPrompt();
        logSystemPrompt(systemPrompt);
        this.inputClosed = false;
        this.stopRequested = false;
        this.orphanedInjections = 0;
        this.playwriterConnectionsResetThisTurn = false;
        return this.streamMessages(content, systemPrompt, options?.effort);
    }
    injectMessage(content, _options) {
        if (!this.session || this.inputClosed)
            return false;
        this.orphanedInjections++;
        const text = contentToText(content);
        const images = contentToImages(content);
        this.session.steer(text, images).then(() => {
            this.orphanedInjections = Math.max(0, this.orphanedInjections - 1);
        }).catch((err) => {
            this.orphanedInjections = Math.max(0, this.orphanedInjections - 1);
            logger.warn(`[pi-coding-session] Failed to steer: ${err instanceof Error ? err.message : String(err)}`);
        });
        return true;
    }
    closeInput() {
        this.inputClosed = true;
        if (this.orphanedInjections > 0) {
            logger.warn(`closeInput: ${this.orphanedInjections} Pi Coding injected message(s) may be orphaned`);
        }
    }
    requestStop() {
        this.stopRequested = true;
        this.inputClosed = true;
        this.session?.abort().catch((err) => {
            logger.warn(`[pi-coding-session] Abort failed: ${err instanceof Error ? err.message : String(err)}`);
        });
    }
    clearStop() {
        this.stopRequested = false;
    }
    isStopRequested() {
        return this.stopRequested;
    }
    captureSessionId(_id) {
        // Pi Coding Agent owns its session id through SessionManager.
    }
    captureUsageSnapshot(snapshot) {
        if (snapshot.contextWindow <= 0)
            return;
        this.lastUsageSnapshot = { ...snapshot, capturedAtMs: Date.now() };
        saveUsageSnapshot(snapshot, this.mode);
    }
    capturePostCompactionSnapshot(postCompactionTokens) {
        const contextWindow = this.lastUsageSnapshot?.contextWindow ?? 0;
        if (contextWindow <= 0 || postCompactionTokens < 0)
            return;
        this.captureUsageSnapshot({
            usedInputTokens: postCompactionTokens,
            contextWindow,
            usedPct: postCompactionTokens / contextWindow,
            allImageBlocks: 0,
        });
    }
    async requestCompaction() {
        if (!this.session)
            return;
        await this.session.compact();
    }
    async maybeCompact() {
        await this.pruneTranscriptImages("post-query");
    }
    getSessionId() {
        return this.session?.sessionId ?? this.sessionId;
    }
    resetForNewSession(summary) {
        this.disposeCurrentSession();
        this.lastUsageSnapshot = null;
        this.sessionFilePath = null;
        this.currentSystemPrompt = null;
        this.currentMcpFingerprint = null;
        this.currentRuntimeFingerprint = null;
        this.pendingResetSummary = summary ?? null;
        this.sessionId = createSessionId();
        saveSessionId(this.sessionId, this.mode);
    }
    injectSummary(summary) {
        this.disposeCurrentSession();
        this.lastUsageSnapshot = null;
        this.sessionFilePath = null;
        this.currentSystemPrompt = null;
        this.currentMcpFingerprint = null;
        this.currentRuntimeFingerprint = null;
        this.pendingResetSummary = summary;
    }
    getTranscriptPath() {
        return this.session?.sessionFile ?? this.sessionFilePath ?? this.findSessionFileById(this.sessionId) ?? this.deriveSessionFilePath(this.sessionId);
    }
    getTranscriptInfo() {
        const sessionFilePath = this.getTranscriptPath();
        return {
            kind: "pi-coding-agent",
            ...(sessionFilePath ? { sessionFilePath } : {}),
            sessionDir: this.sessionDir,
        };
    }
    getUsageSnapshot() {
        return this.lastUsageSnapshot ? { ...this.lastUsageSnapshot } : null;
    }
    getCurrentQuery() {
        return null;
    }
    setDynamicMcpServers(servers) {
        this.dynamicMcpServers = { ...servers };
        const nextFingerprint = this.getMcpFingerprint();
        if (this.currentMcpFingerprint && this.currentMcpFingerprint !== nextFingerprint) {
            this.disposeCurrentSession();
            this.currentSystemPrompt = null;
            this.currentMcpFingerprint = null;
            this.currentRuntimeFingerprint = null;
        }
    }
    getDynamicMcpServers() {
        return { ...this.dynamicMcpServers };
    }
    get isInputClosed() {
        return this.inputClosed;
    }
    get hasOrphanedInjections() {
        return this.orphanedInjections > 0;
    }
    disposeCurrentSession() {
        this.session?.dispose();
        this.session = null;
    }
    async trimTranscriptAfterCompaction() {
        const transcriptPath = this.getTranscriptPath();
        if (!transcriptPath)
            return;
        const archivePath = deriveArchivePath(transcriptPath);
        await trimTranscriptAfterCompaction({ transcriptPath, archivePath });
    }
    async pruneTranscriptImages(reason) {
        const transcriptPath = this.getTranscriptPath();
        if (!transcriptPath)
            return;
        try {
            await sanitizeTranscript(transcriptPath);
            const res = await pruneSessionImages({
                transcriptPath,
                keepLastNBase64Images: MAX_BASE64_IMAGE_BLOCKS,
            });
            if (res.fileChanged) {
                logger.system(`Pruned Pi Coding transcript images (${reason}): pruned=${res.prunedImageBlocks} resized=${res.resizedImageBlocks} kept=${res.keptImageBlocks} total=${res.totalImageBlocks}`);
            }
            if (this.lastUsageSnapshot) {
                this.captureUsageSnapshot({
                    ...this.lastUsageSnapshot,
                    allImageBlocks: res.allImageBlocks,
                });
            }
        }
        catch (err) {
            logger.warn(`[pi-coding-session] Image pruning failed (${reason}): ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    getMcpServers() {
        return {
            ...this.runtimeSurface.externalMcpServers,
            ...this.dynamicMcpServers,
        };
    }
    getMcpFingerprint() {
        return stableStringify(this.getMcpServers());
    }
    getRuntimeFingerprint(model = resolvePiModel(this.config, { useCase: "main_coding" }).model) {
        return stableStringify({
            provider: this.config.provider,
            modelProvider: model.provider,
            modelApi: model.api,
            modelId: model.id,
            modelName: model.name,
            baseUrl: model.baseUrl,
            reasoning: model.reasoning,
            dualSession: this.getDualSessionEnabled(),
            androidUse: this.getAndroidUseEnabled(),
            cardAvailable: this.getCardAvailable(),
        });
    }
    prepareMcpConfig() {
        // pi-mcp-adapter reads this process-global env var to locate mcp.json.
        // Running multiple Pi Coding profiles in one process is intentionally unsupported.
        process.env.PI_CODING_AGENT_DIR = this.agentDir;
        writePiMcpConfig({
            agentDir: this.agentDir,
            servers: this.getMcpServers(),
        });
        return this.getMcpFingerprint();
    }
    deriveSessionFilePath(sessionId) {
        return path.join(this.sessionDir, `${sessionId}.jsonl`);
    }
    findSessionFileById(sessionId) {
        const sessionFile = this.deriveSessionFilePath(sessionId);
        return fs.existsSync(sessionFile) && fs.lstatSync(sessionFile).isFile() ? sessionFile : null;
    }
    getSessionManager() {
        const sessionFile = this.sessionFilePath ?? this.deriveSessionFilePath(this.sessionId);
        this.sessionFilePath = sessionFile;
        const existingSessionFile = this.findSessionFileById(this.sessionId);
        if (existingSessionFile) {
            this.sessionOpenMode = "resume";
            return PiSessionManager.open(existingSessionFile, this.sessionDir, getConfigDir());
        }
        this.sessionOpenMode = "new";
        const sessionManager = PiSessionManager.open(sessionFile, this.sessionDir, getConfigDir());
        this.forceNewSessionId(sessionManager, this.sessionId);
        return sessionManager;
    }
    forceNewSessionId(sessionManager, sessionId) {
        const header = sessionManager.getHeader();
        if (!header)
            return;
        header.id = sessionId;
        sessionManager.sessionId = sessionId;
    }
    async createSession(systemPrompt, effort) {
        const renderedSystemPrompt = renderSystemPrompt(systemPrompt);
        const mcpFingerprint = this.prepareMcpConfig();
        const { model, apiKey } = resolvePiModel(this.config, { useCase: "main_coding" });
        const runtimeFingerprint = this.getRuntimeFingerprint(model);
        const thinkingLevel = resolveThinkingLevel(model, effort, this.mode);
        if (this.session &&
            this.currentSystemPrompt === renderedSystemPrompt &&
            this.currentMcpFingerprint === mcpFingerprint &&
            this.currentRuntimeFingerprint === runtimeFingerprint) {
            if (this.session.thinkingLevel !== thinkingLevel) {
                this.session.setThinkingLevel(thinkingLevel);
            }
            this.logLoadedResources(this.session, "reuse", mcpFingerprint, this.sessionOpenMode);
            return this.session;
        }
        this.disposeCurrentSession();
        const authStorage = AuthStorage.inMemory({
            [model.provider]: { type: "api_key", key: apiKey },
        });
        authStorage.setFallbackResolver((provider) => provider === model.provider ? apiKey : undefined);
        const modelRegistry = ModelRegistry.inMemory(authStorage);
        modelRegistry.registerProvider(model.provider, {
            baseUrl: model.baseUrl,
            apiKey,
            api: model.api,
            models: [{
                    id: model.id,
                    name: model.name,
                    api: model.api,
                    baseUrl: model.baseUrl,
                    reasoning: model.reasoning,
                    input: [...model.input],
                    cost: model.cost,
                    contextWindow: model.contextWindow,
                    maxTokens: model.maxTokens,
                }],
        });
        const settingsManager = SettingsManager.inMemory({
            defaultProvider: model.provider,
            defaultModel: model.id,
            defaultThinkingLevel: thinkingLevel,
            steeringMode: "all",
            compaction: { enabled: true },
        });
        const sessionManager = this.getSessionManager();
        const mcpAdapter = await loadMcpAdapter();
        const profileDir = getConfigDir();
        // Share the Claude `.claude/skills` directory so PiCodingAgentSession and
        // ClaudeAgentSession see the same set of skills (installed by
        // `installBuiltinSkills()` and the `skill` tool). Also use the profile
        // directory as the Pi resource-loader `agentDir` and enable context-file
        // loading so GPT/Pi sessions pick up profile-level CLAUDE.md/AGENTS.md in
        // the same profile cwd that Claude Code sees.
        const skillsDir = getSkillsDir();
        const additionalSkillPaths = fs.existsSync(skillsDir) ? [skillsDir] : [];
        const loader = new DefaultResourceLoader({
            cwd: profileDir,
            agentDir: profileDir,
            settingsManager,
            extensionFactories: [this.createVisionClawRuntimeExtension(), mcpAdapter],
            additionalSkillPaths,
            noSkills: false,
            noPromptTemplates: true,
            noThemes: true,
            noContextFiles: false,
            systemPrompt: renderedSystemPrompt,
        });
        await loader.reload();
        const customTools = createPiCodingCustomTools({
            dualSession: this.getDualSessionEnabled(),
            androidUse: this.getAndroidUseEnabled(),
            cardAvailable: this.getCardAvailable(),
            mode: this.mode,
        });
        const { session } = await createAgentSession({
            cwd: getConfigDir(),
            agentDir: this.agentDir,
            authStorage,
            modelRegistry,
            settingsManager,
            sessionManager,
            resourceLoader: loader,
            model,
            thinkingLevel,
            customTools,
            sessionStartEvent: {
                type: "session_start",
                reason: this.sessionOpenMode === "resume" ? "resume" : "new",
            },
        });
        await session.bindExtensions({});
        this.sessionId = session.sessionId;
        this.sessionFilePath = session.sessionFile ?? this.sessionFilePath;
        this.currentSystemPrompt = renderedSystemPrompt;
        this.currentMcpFingerprint = mcpFingerprint;
        this.currentRuntimeFingerprint = runtimeFingerprint;
        this.session = session;
        saveSessionId(this.sessionId, this.mode);
        this.logLoadedResources(session, "create", mcpFingerprint, this.sessionOpenMode);
        return session;
    }
    logLoadedResources(session, phase, mcpFingerprint, openMode) {
        try {
            const allTools = session.getAllTools();
            const activeToolNames = session.getActiveToolNames();
            const skillsResult = session.resourceLoader.getSkills();
            const extensionsResult = session.resourceLoader.getExtensions();
            const toolNames = allTools.map((tool) => tool.name).sort();
            const mcpToolNames = toolNames.filter((name) => name === "mcp" || name.includes("__"));
            const skillSummaries = skillsResult.skills
                .map((skill) => ({ name: skill.name, path: skill.filePath }))
                .sort((a, b) => a.name.localeCompare(b.name));
            const mcpServers = this.getMcpServers();
            const mcpServerNames = Object.keys(mcpServers).sort();
            logger.info("Pi Coding Agent resources loaded", {
                phase,
                openMode,
                sessionId: session.sessionId,
                sessionFile: session.sessionFile,
                thinkingLevel: session.thinkingLevel,
                toolCount: toolNames.length,
                activeToolCount: activeToolNames.length,
                activeToolNames: [...activeToolNames].sort(),
                toolNames,
                mcpToolNames,
                hasGenericMcpProxyTool: toolNames.includes("mcp"),
                mcpServerCount: mcpServerNames.length,
                mcpServerNames,
                mcpFingerprint,
                mcpConfigPath: path.join(this.agentDir, "mcp.json"),
                mcpSettings: { directTools: true, disableProxyTool: true, autoAuth: true },
                piCodingAgentDir: process.env.PI_CODING_AGENT_DIR,
                skillCount: skillSummaries.length,
                skills: skillSummaries,
                skillDiagnostics: skillsResult.diagnostics,
                extensionCount: extensionsResult.extensions.length,
                extensionErrors: extensionsResult.errors,
            });
            logger.system(`[pi-coding-resources] phase=${phase} open=${openMode} session=${session.sessionId} thinking=${session.thinkingLevel}`);
            //logger.system(`[pi-coding-tools] active(${activeToolNames.length}): ${formatConsoleList([...activeToolNames].sort())}`);
            //logger.system(`[pi-coding-tools] all(${toolNames.length}): ${formatConsoleList(toolNames)}`);
            logger.system(`[pi-coding-mcps] servers(${mcpServerNames.length}): ${formatConsoleList(mcpServerNames)}`);
            //logger.system(`[pi-coding-mcps] tools(${mcpToolNames.length}): ${formatConsoleList(mcpToolNames)}`);
            logger.system(`[pi-coding-skills] loaded(${skillSummaries.length}): ${formatConsoleList(skillSummaries.map((skill) => skill.name))}`);
            if (skillsResult.diagnostics.length > 0) {
                logger.system(`[pi-coding-skills] diagnostics: ${formatConsoleList(skillsResult.diagnostics.map((diag) => JSON.stringify(diag)))}`);
            }
            if (extensionsResult.errors.length > 0) {
                logger.system(`[pi-coding-extensions] errors: ${formatConsoleList(extensionsResult.errors.map((err) => JSON.stringify(err)))}`);
            }
            if (toolNames.includes("mcp")) {
                logger.warn("Pi Coding Agent still loaded the generic MCP proxy tool despite disableProxyTool=true", {
                    mcpToolNames,
                    mcpServerNames,
                    mcpConfigPath: path.join(this.agentDir, "mcp.json"),
                });
            }
        }
        catch (err) {
            logger.warn(`[pi-coding-session] Failed to log loaded resources: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    createVisionClawRuntimeExtension() {
        return (pi) => {
            pi.on("tool_call", async (event) => {
                const browserToolName = getBrowserToolName(event);
                if (!browserToolName)
                    return {};
                const result = await ensureBrowserForTool({
                    toolName: browserToolName,
                    browserBackend: this.config.browserBackend,
                    playwriterConnectionsResetThisTurn: this.playwriterConnectionsResetThisTurn,
                    playwriterRelayHost: this.config.playwriterRelayHost,
                    playwriterRelayPort: this.config.playwriterRelayPort,
                });
                this.playwriterConnectionsResetThisTurn = result.playwriterConnectionsResetThisTurn;
                return {};
            });
        };
    }
    consumeResetSummaryContent(content) {
        if (!this.pendingResetSummary)
            return content;
        const summary = this.pendingResetSummary;
        this.pendingResetSummary = null;
        return [
            { type: "text", text: `Previous session summary after refresh:\n\n${summary}` },
            ...normalizeInputContent(content),
        ];
    }
    async *streamMessages(content, systemPrompt, effort) {
        const piModel = resolvePiModel(this.config, { useCase: "main_coding" }).model;
        const session = await this.createSession(systemPrompt, effort);
        await this.pruneTranscriptImages("pre-send");
        const effectiveContent = this.consumeResetSummaryContent(content);
        logger.debug("Pi Coding Agent model resolved", {
            api: piModel.api,
            provider: piModel.provider,
            model: piModel.id,
            baseUrl: piModel.baseUrl,
            sessionFile: session.sessionFile,
        });
        // Mirror Claude's `system/init` event so log-side wake cycles can be
        // mapped back to the Pi transcript by data-processing
        // (WakeCycle.systemInitEntries → getSessionTurns). Yielded once per
        // turn, before any model output.
        yield {
            type: "system",
            subtype: "init",
            session_id: session.sessionId,
            model: piModel.id,
            mode: this.mode,
            open_mode: this.sessionOpenMode,
            mcp_servers: Object.keys(this.getMcpServers()).sort().map((name) => ({ name, status: "connected" })),
        };
        const eventQueue = [];
        let resolveWaiting = null;
        let done = false;
        let runError = null;
        const isDone = () => done;
        const usage = createPiUsageAccumulator();
        let turnCount = 0;
        const compactionTrim = { needed: false };
        let unsubscribed = false;
        const unsubscribe = session.subscribe((event) => {
            const messages = piCodingEventToStreamMessages(event, session.sessionId);
            if (messages.length > 0) {
                eventQueue.push(...messages);
                resolveWaiting?.();
            }
            if (event.type === "message_end") {
                const msg = event.message;
                capturePiMessageUsage(usage, msg);
            }
            if (event.type === "turn_end") {
                turnCount++;
                if (turnCount >= MAX_TURNS) {
                    session.abort().catch((err) => {
                        logger.warn(`[pi-coding-session] Abort failed after max turns: ${err instanceof Error ? err.message : String(err)}`);
                    });
                }
            }
            if (event.type === "compaction_end" && event.result) {
                this.capturePostCompactionSnapshot(estimatePostCompactionTokens(event.result.summary));
                compactionTrim.needed = true;
            }
            if (event.type === "tool_execution_end") {
                // TODO: detect MCP-proxied Playwright browser actions when Pi reports them through the generic mcp tool name.
                const toolName = event.toolName;
                if (toolName && this._screenshotInjector) {
                    this._screenshotInjector.onToolComplete(toolName, this).catch((err) => {
                        logger.warn(`[pi-coding-session] Screenshot injection failed: ${err instanceof Error ? err.message : String(err)}`);
                    });
                }
            }
            if (event.type === "agent_end") {
                this.inputClosed = true;
                done = true;
                resolveWaiting?.();
            }
        });
        session.sendUserMessage(contentToPiContent(effectiveContent)).catch((err) => {
            runError = err;
            done = true;
            resolveWaiting?.();
        });
        try {
            for (;;) {
                const nextEvent = eventQueue.shift();
                if (nextEvent) {
                    yield nextEvent;
                    continue;
                }
                if (isDone())
                    break;
                await new Promise((r) => { resolveWaiting = r; });
                resolveWaiting = null;
            }
            unsubscribe();
            unsubscribed = true;
            if (compactionTrim.needed) {
                try {
                    await this.trimTranscriptAfterCompaction();
                }
                catch (err) {
                    logger.warn(`[pi-coding-session] Post-compaction JSONL trim failed: ${err instanceof Error ? err.message : String(err)}`);
                }
            }
            const assistantError = usage.assistantErrors.at(-1);
            yield buildPiResultMessage({
                error: runError ?? (assistantError ? new Error(assistantError) : null),
                inputTokens: usage.inputTokens,
                outputTokens: usage.outputTokens,
                numTurns: turnCount,
                config: this.config,
                contextWindow: piModel.contextWindow,
            });
        }
        finally {
            if (!unsubscribed)
                unsubscribe();
            this.inputClosed = true;
            if (runError) {
                const runErrorMessage = runError instanceof Error
                    ? runError.message
                    : typeof runError === "string" ? runError : JSON.stringify(runError);
                logger.warn(`[pi-coding-session] Disposing session after sendUserMessage failure: ${runErrorMessage}`);
                this.disposeCurrentSession();
            }
        }
    }
}
function createSessionId() {
    return crypto.randomUUID();
}
function stableStringify(value) {
    if (Array.isArray(value))
        return `[${value.map((item) => stableStringify(item)).join(",")}]`;
    if (value && typeof value === "object") {
        return `{${Object.entries(value)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
            .join(",")}}`;
    }
    return JSON.stringify(value);
}
function estimatePostCompactionTokens(summary) {
    return Math.ceil(summary.length / 4);
}
function renderSystemPrompt(prompt) {
    if (typeof prompt === "string")
        return prompt;
    const presetIntro = "You are in a coding-focused session. Prefer careful, multi-step engineering work.";
    return [presetIntro, prompt.append ?? ""].filter(Boolean).join("\n\n");
}
function contentToText(content) {
    return normalizeInputContent(content)
        .filter((block) => block.type === "text")
        .map((block) => block.text)
        .join("\n\n");
}
function contentToImages(content) {
    return normalizeInputContent(content)
        .filter((block) => block.type === "image")
        .map((block) => {
        if (block.source.type === "url") {
            return { type: "image", data: block.source.url, mimeType: "image/png" };
        }
        return { type: "image", data: block.source.data, mimeType: block.source.media_type };
    });
}
function contentToPiContent(content) {
    const blocks = normalizeInputContent(content);
    const piContent = blocks.map((block) => {
        if (block.type === "text") {
            return { type: "text", text: block.text };
        }
        if (block.source.type === "url") {
            return { type: "image", data: block.source.url, mimeType: "image/png" };
        }
        return { type: "image", data: block.source.data, mimeType: block.source.media_type };
    });
    if (piContent.length === 1 && piContent[0]?.type === "text")
        return piContent[0].text;
    return piContent;
}
function normalizeInputContent(content) {
    if (typeof content === "string") {
        return [{ type: "text", text: content }];
    }
    const blocks = [];
    for (const block of content) {
        if (block.type === "text") {
            blocks.push({ type: "text", text: block.text });
        }
        else if (block.type === "image") {
            if (block.source.type === "url") {
                blocks.push({ type: "image", source: { type: "url", url: block.source.url } });
            }
            else {
                blocks.push({
                    type: "image",
                    source: { type: "base64", media_type: block.source.media_type, data: block.source.data },
                });
            }
        }
    }
    return blocks;
}
function piCodingEventToStreamMessages(event, sessionId) {
    if (event.type === "compaction_start") {
        return [{ type: "system", subtype: "compaction_start", session_id: sessionId, reason: event.reason }];
    }
    if (event.type === "compaction_end") {
        return [{
                type: "system",
                subtype: "compaction_end",
                session_id: sessionId,
                reason: event.reason,
                aborted: event.aborted,
                willRetry: event.willRetry,
                errorMessage: event.errorMessage,
            }];
    }
    // Pi Coding AgentSessionEvent extends the core Pi AgentEvent union with
    // compaction events; those are handled above before delegating here.
    return convertPiAgentEventToStreamMessages(event, sessionId);
}
/**
 * Map a Claude-style {@link EffortLevel} to a Pi {@link ThinkingLevel}.
 *
 * Mirrors Claude's behaviour:
 * - When no per-turn effort is supplied, use "high" for coding mode and
 *   "medium" otherwise.
 * - For models that don't support reasoning, force "off".
 */
function formatConsoleList(items, maxItems = 80) {
    if (items.length === 0)
        return "(none)";
    const visible = items.slice(0, maxItems);
    const suffix = items.length > visible.length ? `, ... +${items.length - visible.length} more` : "";
    return `${visible.join(", ")}${suffix}`;
}
function getBrowserToolName(event) {
    if (isPlaywrightToolName(event.toolName)) {
        return event.toolName;
    }
    if (event.toolName !== "mcp")
        return null;
    const serverName = typeof event.input.server === "string" ? event.input.server : undefined;
    const nestedToolName = typeof event.input.tool === "string" ? event.input.tool : undefined;
    if (serverName === "playwright")
        return nestedToolName ?? "playwright";
    if (nestedToolName && isPlaywrightToolName(nestedToolName)) {
        return nestedToolName;
    }
    return null;
}
function resolveThinkingLevel(model, effort, mode) {
    if (!model.reasoning)
        return "off";
    const resolved = effort ?? (mode === "coding" ? "high" : "medium");
    switch (resolved) {
        case "low":
            return "low";
        case "medium":
            return "medium";
        case "high":
            return "high";
        case "max":
            return "xhigh";
    }
}
//# sourceMappingURL=session.js.map