import { Agent } from "@earendil-works/pi-agent-core";
import { loadSessionId, loadUsageSnapshot, saveSessionId, saveUsageSnapshot } from "../../../config/index.js";
import { logger } from "../../../logger.js";
import { logSystemPrompt } from "../system-prompt-log.js";
import { createPiTools } from "./tools.js";
import { getPiApiKey, resolvePiModel } from "./shared/model.js";
import { convertPiAgentEventToStreamMessages } from "./shared/event-converter.js";
import { buildPiResultMessage, capturePiMessageUsage, createPiUsageAccumulator } from "./shared/usage.js";
const MAX_TURNS = 100;
export class PiAgentSession {
    config;
    buildSystemPrompt;
    getDualSessionEnabled;
    getAndroidUseEnabled;
    getCardAvailable;
    dynamicMcpServers;
    piAgent = null;
    inputClosed = true;
    stopRequested = false;
    lastUsageSnapshot = null;
    sessionId;
    pendingResetSummary = null;
    hasOrphanedInjections = false;
    mode;
    constructor({ config, buildSystemPrompt, sessionContext, }) {
        this.config = config;
        this.buildSystemPrompt = buildSystemPrompt;
        this.getDualSessionEnabled = sessionContext.getDualSessionEnabled;
        this.getAndroidUseEnabled = sessionContext.getAndroidUseEnabled;
        this.getCardAvailable = sessionContext.getCardAvailable;
        this.dynamicMcpServers = {};
        this.mode = sessionContext.mode;
        const persistedSessionId = loadSessionId(this.mode);
        this.sessionId = persistedSessionId ?? `pi-${this.mode}-${Date.now()}`;
        saveSessionId(this.sessionId, this.mode);
        const persistedUsage = loadUsageSnapshot(this.mode);
        if (persistedUsage) {
            this.lastUsageSnapshot = { ...persistedUsage, capturedAtMs: Date.now() };
        }
    }
    async warmUp() {
        // Pi sessions do not need pre-warming.
    }
    discardWarmQuery() {
        // No pre-warmed state to discard.
    }
    sendAndStream(content, _options) {
        const systemPrompt = this.buildSystemPrompt();
        logSystemPrompt(systemPrompt);
        this.inputClosed = false;
        this.stopRequested = false;
        const resetSummary = this.pendingResetSummary;
        this.pendingResetSummary = null;
        const finalContent = resetSummary
            ? prependResetSummary(content, resetSummary)
            : content;
        const userMessages = contentToAgentMessages(finalContent);
        return this.streamMessages(userMessages, systemPrompt);
    }
    injectMessage(content, _options) {
        if (!this.piAgent)
            return false;
        const messages = contentToAgentMessages(content);
        for (const msg of messages) {
            this.piAgent.steer(msg);
        }
        return true;
    }
    closeInput() {
        this.inputClosed = true;
    }
    requestStop() {
        this.stopRequested = true;
        this.inputClosed = true;
        this.piAgent?.abort();
    }
    clearStop() {
        this.stopRequested = false;
    }
    isStopRequested() {
        return this.stopRequested;
    }
    captureSessionId(_id) {
        // Pi session IDs are assigned locally at construction time and passed into the Pi agent.
    }
    captureUsageSnapshot(snapshot) {
        if (snapshot.contextWindow <= 0)
            return;
        this.lastUsageSnapshot = { ...snapshot, capturedAtMs: Date.now() };
        saveUsageSnapshot(snapshot, this.mode);
    }
    capturePostCompactionSnapshot(_postCompactionTokens) {
        // Pi handles compaction internally.
    }
    requestCompaction() {
        logger.info("[pi-session] Compaction request noted — Pi handles compaction internally");
        return Promise.resolve();
    }
    async maybeCompact() {
        // Compaction managed by Pi Agent harness.
    }
    getSessionId() {
        return this.sessionId;
    }
    resetForNewSession(summary) {
        this.lastUsageSnapshot = null;
        this.piAgent = null;
        this.pendingResetSummary = summary ?? null;
    }
    injectSummary(summary) {
        this.lastUsageSnapshot = null;
        this.piAgent = null;
        this.pendingResetSummary = summary;
    }
    getTranscriptPath() {
        return null;
    }
    getTranscriptInfo() {
        return { kind: "none" };
    }
    getUsageSnapshot() {
        return this.lastUsageSnapshot ? { ...this.lastUsageSnapshot } : null;
    }
    getCurrentQuery() {
        return null;
    }
    setDynamicMcpServers(servers) {
        this.dynamicMcpServers = { ...servers };
    }
    getDynamicMcpServers() {
        return { ...this.dynamicMcpServers };
    }
    get isInputClosed() {
        return this.inputClosed;
    }
    resolvePiModel() {
        return resolvePiModel(this.config, { useCase: "main_general" }).model;
    }
    getApiKey(provider) {
        return getPiApiKey(this.config, provider);
    }
    async *streamMessages(userMessages, systemPrompt) {
        const piModel = this.resolvePiModel();
        const tools = createPiTools({
            dualSession: this.getDualSessionEnabled(),
            androidUse: this.getAndroidUseEnabled(),
            cardAvailable: this.getCardAvailable(),
            mode: this.mode,
        });
        logger.debug("Pi Agent model resolved", {
            api: piModel.api,
            provider: piModel.provider,
            model: piModel.id,
            baseUrl: piModel.baseUrl,
        });
        // Mirror Claude's `system/init` event so log-side wake cycles can be
        // mapped back to the Pi session by data-processing. Yielded once per
        // turn, before any model output.
        yield {
            type: "system",
            subtype: "init",
            session_id: this.sessionId,
            model: piModel.id,
            mode: this.mode,
        };
        const agent = new Agent({
            initialState: {
                systemPrompt: renderSystemPrompt(systemPrompt),
                model: piModel,
                tools,
                thinkingLevel: piModel.reasoning ? "medium" : "off",
            },
            sessionId: this.sessionId,
            getApiKey: (provider) => this.getApiKey(provider),
            onResponse: (response, model) => {
                logger.debug("Pi provider response", {
                    status: response.status,
                    provider: model.provider,
                    api: model.api,
                    model: model.id,
                    baseUrl: model.baseUrl,
                });
            },
            steeringMode: "all",
        });
        this.piAgent = agent;
        const eventQueue = [];
        let resolveWaiting = null;
        let done = false;
        let runError = null;
        const usage = createPiUsageAccumulator();
        let turnCount = 0;
        agent.subscribe((event) => {
            const messages = convertPiAgentEventToStreamMessages(event, this.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) {
                    agent.abort();
                }
            }
            if (event.type === "agent_end") {
                done = true;
                resolveWaiting?.();
            }
        });
        agent.prompt(userMessages).catch((err) => {
            runError = err;
            done = true;
            resolveWaiting?.();
        });
        try {
            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- mutated in async callback
            while (!done || eventQueue.length > 0) {
                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- mutated in async callback
                if (eventQueue.length === 0 && !done) {
                    await new Promise((r) => { resolveWaiting = r; });
                    resolveWaiting = null;
                }
                while (eventQueue.length > 0) {
                    const nextEvent = eventQueue.shift();
                    if (nextEvent)
                        yield nextEvent;
                }
            }
            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 {
            this.inputClosed = true;
            this.piAgent = null;
        }
    }
}
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 contentToAgentMessages(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,
        };
    });
    return [{
            role: "user",
            content: piContent,
            timestamp: Date.now(),
        }];
}
function prependResetSummary(content, summary) {
    const summaryBlock = {
        type: "text",
        text: `Previous session summary after refresh:\n\n${summary}`,
    };
    if (typeof content === "string") {
        return [summaryBlock, { type: "text", text: content }];
    }
    return [summaryBlock, ...content];
}
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;
}
//# sourceMappingURL=session.js.map