import { getErrorMessage } from "../hub/utils.js";
export class AcpGuestProjection {
    options;
    items = [];
    toolsById = new Map();
    finalizedToolCallIds = new Set();
    currentAssistant;
    currentAssistantMessageId;
    isRunning = false;
    runStartedAt;
    lastRunStartedAt;
    lastRunEndedAt;
    lastRunDurationMs;
    lastRunEndReason;
    assistantSequence = 0;
    constructor(options) {
        this.options = options;
        this.publish();
    }
    appendUserPrompt(text) {
        this.items.push({
            type: "message",
            message: {
                role: "user",
                content: text,
                timestamp: Date.now(),
            },
        });
        this.startRun();
        this.publish();
    }
    appendInboundAgentMessage(payload) {
        const prompt = [
            "[D-Pi agent message]",
            `from: ${payload.fromAgentId}`,
            `to: ${payload.toAgentId}`,
            `sent_at: ${payload.sentAt}`,
            "",
            payload.message,
        ].join("\n");
        this.appendUserPrompt(prompt);
        return prompt;
    }
    applySessionUpdate(notification) {
        const update = notification.update;
        switch (update.sessionUpdate) {
            case "agent_message_chunk":
                this.appendAssistantChunk(update);
                return;
            case "tool_call":
                this.upsertTool({
                    toolCallId: update.toolCallId,
                    toolName: update.title,
                    args: toRecord(update.rawInput),
                    partialResult: contentToToolResult(update.content),
                    result: update.status === "completed"
                        ? contentToToolResult(update.content, update.rawOutput, "completed")
                        : undefined,
                    isError: update.status === "failed",
                });
                return;
            case "tool_call_update":
                this.mergeToolUpdate(update);
                return;
            case "agent_thought_chunk":
            case "plan":
                this.setStatusMessage(getDisplayText(update) ?? update.sessionUpdate);
                return;
            default:
                return;
        }
    }
    completeTurn(stopReason) {
        this.isRunning = false;
        this.lastRunEndedAt = new Date().toISOString();
        this.lastRunEndReason = stopReason === "cancelled" || stopReason === "canceled" ? "interrupted" : "completed";
        if (this.runStartedAt) {
            this.lastRunDurationMs = Math.max(0, Date.parse(this.lastRunEndedAt) - Date.parse(this.runStartedAt));
        }
        if (this.currentAssistant) {
            this.currentAssistant.stopReason = this.lastRunEndReason === "interrupted" ? "aborted" : "stop";
        }
        this.currentAssistant = undefined;
        this.currentAssistantMessageId = undefined;
        this.publish();
    }
    failTurn(error) {
        this.isRunning = false;
        this.lastRunEndedAt = new Date().toISOString();
        this.lastRunEndReason = "error";
        if (this.currentAssistant) {
            this.currentAssistant.stopReason = "error";
            this.currentAssistant.errorMessage = getErrorMessage(error);
        }
        this.items.push({
            type: "message",
            message: {
                role: "assistant",
                content: [{ type: "text", text: `ACP error: ${getErrorMessage(error)}` }],
                timestamp: Date.now(),
                stopReason: "error",
            },
        });
        this.currentAssistant = undefined;
        this.currentAssistantMessageId = undefined;
        this.publish();
    }
    appendAssistantChunk(update) {
        const text = getDisplayText(update);
        if (!text) {
            return;
        }
        this.ensureAssistantMessage(update.messageId ?? undefined);
        const currentAssistant = this.currentAssistant;
        if (!currentAssistant) {
            return;
        }
        const first = currentAssistant.content[0];
        if (first?.type === "text") {
            first.text += text;
        }
        else {
            currentAssistant.content.push({ type: "text", text });
        }
        this.publish();
    }
    ensureAssistantMessage(messageId) {
        if (this.currentAssistant && (messageId === undefined || messageId === this.currentAssistantMessageId)) {
            return;
        }
        this.assistantSequence += 1;
        this.currentAssistant = createAssistantMessage("", this.assistantSequence);
        this.currentAssistantMessageId = messageId;
        this.items.push({ type: "message", message: this.currentAssistant });
    }
    mergeToolUpdate(update) {
        const existing = this.toolsById.get(update.toolCallId);
        this.upsertTool({
            toolCallId: update.toolCallId,
            toolName: update.title ?? existing?.toolName ?? update.toolCallId,
            args: toRecord(update.rawInput) ?? existing?.args,
            partialResult: contentToToolResult(update.content ?? undefined) ?? existing?.partialResult,
            result: update.status === "completed"
                ? (contentToToolResult(update.content ?? undefined, update.rawOutput, "completed") ?? existing?.result)
                : update.status === "failed"
                    ? (contentToToolResult(update.content ?? undefined, update.rawOutput, "failed") ?? existing?.result)
                    : existing?.result,
            isError: update.status === "failed" ? true : existing?.isError,
        });
    }
    upsertTool(tool) {
        const next = { ...this.toolsById.get(tool.toolCallId), ...tool };
        this.toolsById.set(tool.toolCallId, next);
        this.ensureAssistantToolCall(tool);
        if (next.result !== undefined || next.isError === true) {
            this.appendToolResult(next);
            this.finalizedToolCallIds.add(next.toolCallId);
            this.currentAssistant = undefined;
            this.currentAssistantMessageId = undefined;
        }
        this.publish();
    }
    appendToolResult(tool) {
        if (this.items.some((item) => item.type === "message" &&
            item.message.role === "toolResult" &&
            item.message.toolCallId === tool.toolCallId)) {
            return;
        }
        const result = tool.result ??
            tool.partialResult ??
            contentToToolResult(undefined, undefined, tool.isError ? "failed" : "completed");
        this.items.push({
            type: "message",
            message: {
                role: "toolResult",
                toolCallId: tool.toolCallId,
                toolName: tool.toolName,
                content: result?.content ?? [{ type: "text", text: tool.isError ? "failed" : "completed" }],
                details: result?.details,
                isError: tool.isError ?? false,
                timestamp: Date.now(),
            },
        });
    }
    ensureAssistantToolCall(tool) {
        this.ensureAssistantMessage(undefined);
        if (!this.currentAssistant) {
            return;
        }
        const existing = this.currentAssistant.content.find((content) => content.type === "toolCall" && content.id === tool.toolCallId);
        if (existing) {
            existing.name = tool.toolName;
            existing.arguments = tool.args ?? {};
            return;
        }
        this.currentAssistant.content.push({
            type: "toolCall",
            id: tool.toolCallId,
            name: tool.toolName,
            arguments: tool.args ?? {},
        });
    }
    setStatusMessage(message) {
        this.publish(message);
    }
    startRun() {
        this.isRunning = true;
        this.runStartedAt = new Date().toISOString();
        this.lastRunStartedAt = this.runStartedAt;
        this.lastRunEndedAt = undefined;
        this.lastRunDurationMs = undefined;
        this.lastRunEndReason = undefined;
        this.currentAssistant = undefined;
        this.currentAssistantMessageId = undefined;
        this.toolsById.clear();
        this.finalizedToolCallIds.clear();
    }
    publish(statusMessage) {
        const liveToolExecutions = [...this.toolsById.values()].filter((tool) => !this.finalizedToolCallIds.has(tool.toolCallId));
        const live = {
            streamingMessageId: this.currentAssistant ? getMessageId(this.currentAssistant) : undefined,
            streamingMessageIndex: this.currentAssistant ? this.items.length - 1 : undefined,
            streamingMessage: this.currentAssistant,
            toolExecutions: liveToolExecutions,
            statusMessage,
        };
        this.options.appState.applyLocalAgentProjection(this.createAgentView(live), live);
    }
    createAgentView(live) {
        return {
            agentId: this.options.agentId,
            kind: "guest",
            sessionId: this.options.sessionId ?? `guest-acp-${this.options.agentId}`,
            cwd: this.options.cwd,
            sessionFile: this.options.sessionFile ?? "",
            protocolVersion: 3,
            status: {
                isRunning: this.isRunning,
                runStartedAt: this.runStartedAt,
                lastRunStartedAt: this.lastRunStartedAt,
                lastRunEndedAt: this.lastRunEndedAt,
                lastRunDurationMs: this.lastRunDurationMs,
                lastRunEndReason: this.lastRunEndReason,
            },
            queue: { messages: [], steering: [], followUp: [], size: 0 },
            context: {
                model: null,
                thinkingLevel: "off",
                pendingToolCallIds: live.toolExecutions
                    .filter((tool) => tool.result === undefined && !tool.isError)
                    .map((tool) => tool.toolCallId),
            },
            messageCount: this.items.filter((item) => item.type === "message").length,
            items: this.items.map((item) => ({ ...item })),
            live: {
                streamingMessageId: live.streamingMessageId,
                streamingMessageIndex: live.streamingMessageIndex,
                itemIndicesById: {},
                toolOrder: live.toolExecutions.map((tool) => tool.toolCallId),
                toolsById: Object.fromEntries(live.toolExecutions.map((tool) => [
                    tool.toolCallId,
                    {
                        toolCallId: tool.toolCallId,
                        toolName: tool.toolName,
                        args: tool.args,
                        partialResult: tool.partialResult,
                        result: tool.result,
                        isError: tool.isError,
                    },
                ])),
                statusMessage: live.statusMessage,
            },
            availableModels: [],
            availableThinkingLevels: [],
            diagnostics: [],
        };
    }
}
function getMessageId(message) {
    return `assistant:${message.timestamp}`;
}
function createAssistantMessage(text, sequence) {
    return {
        role: "assistant",
        content: [{ type: "text", text }],
        api: "openai-completions",
        provider: "acp",
        model: "external-acp-agent",
        usage: {
            input: 0,
            output: 0,
            cacheRead: 0,
            cacheWrite: 0,
            totalTokens: 0,
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
        },
        stopReason: "stop",
        timestamp: Date.now() + sequence,
    };
}
function getDisplayText(update) {
    if ("content" in update && update.content.type === "text") {
        return update.content.text;
    }
    if ("entries" in update) {
        return update.entries.map((entry) => entry.content).join("\n");
    }
    return undefined;
}
function toRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value)
        ? value
        : undefined;
}
function contentToToolResult(content, rawOutput, fallbackText) {
    const textParts = content
        ?.map((entry) => (entry.type === "content" && entry.content.type === "text" ? entry.content.text : undefined))
        .filter((text) => text !== undefined) ?? [];
    if (textParts.length === 0 && rawOutput === undefined && fallbackText === undefined) {
        return undefined;
    }
    return {
        content: textParts.length > 0
            ? textParts.map((text) => ({ type: "text", text }))
            : [{ type: "text", text: rawOutput === undefined ? (fallbackText ?? "") : JSON.stringify(rawOutput) }],
        details: rawOutput,
    };
}
//# sourceMappingURL=acp-guest-projection.js.map