import { join } from "node:path";
import { AuthStorage, createAgentSessionFromServices, createAgentSessionServices, createEventBus, createExtensionRuntime, getAgentDir, loadExtensionFromFactory, ModelRegistry, } from "@sheason/pi-coding-agent";
import { createAggregatedAgentSessionServices, materializeAggregatedModelsConfig, } from "../config-aggregation/agent-config-services.js";
import { appendDPiExtensionFactory, createDPiExtensionFactory, isDynamicMcpToolName, } from "../extensions/d-pi-extension.js";
import { materializeMergedModelsConfig } from "../models-config.js";
import { HubResourceLoader } from "../resources/hub-resource-loader.js";
import { DEFAULT_AVAILABLE_THINKING_LEVELS, serializeAvailableModels } from "../session/session-options.js";
import { getErrorMessage } from "../utils.js";
import { HubAgentSessionLogger } from "./hub-agent-session-logger.js";
import { createAgentMessageSource, createHostMessageSource, createPeerMessageSource, createSourceMessageSource, } from "./types.js";
function collectConfiguredModelKeysBySource(layers) {
    const hub = new Set();
    const peer = new Set();
    for (const layer of layers ?? []) {
        if (!layer || !layer.models || typeof layer.models !== "object" || Array.isArray(layer.models)) {
            continue;
        }
        const providers = "providers" in layer.models ? layer.models.providers : undefined;
        if (!providers || typeof providers !== "object" || Array.isArray(providers)) {
            continue;
        }
        for (const [providerName, provider] of Object.entries(providers)) {
            if (!provider || typeof provider !== "object" || Array.isArray(provider)) {
                continue;
            }
            const models = "models" in provider ? provider.models : undefined;
            if (!Array.isArray(models)) {
                continue;
            }
            for (const model of models) {
                if (!model || typeof model !== "object" || Array.isArray(model)) {
                    continue;
                }
                const modelId = "id" in model ? model.id : undefined;
                if (typeof modelId !== "string") {
                    continue;
                }
                const key = `${providerName}:${modelId}`;
                if (layer.source?.kind === "peer") {
                    peer.add(key);
                }
                else {
                    hub.add(key);
                }
            }
        }
    }
    return { hub, peer };
}
function createPersistableModelChangeHandler(options) {
    if (!options.onModelChange) {
        return undefined;
    }
    const readConfigLayers = () => options.getConfigLayers?.() ?? options.configLayers;
    return (modelRef) => {
        const { hub, peer } = collectConfiguredModelKeysBySource(readConfigLayers());
        const key = `${modelRef.provider}:${modelRef.modelId}`;
        if (peer.has(key) && !hub.has(key)) {
            return;
        }
        options.onModelChange?.(modelRef);
    };
}
function queuedInputMessageToAgentMessage(message, timestamp = Date.now()) {
    const content = [{ type: "text", text: message.text }];
    if (message.images?.length) {
        content.push(...message.images.map((img) => ({
            type: "image",
            data: img.data,
            mimeType: img.mimeType,
        })));
    }
    const sourceAwareMessage = {
        role: "user",
        content,
        timestamp,
        messageSource: message.messageSource,
    };
    return sourceAwareMessage;
}
async function appendLoadedExtensionToResourceLoader(resourceLoader, factory, cwd) {
    const extensionPath = "<d-pi-inline>";
    const extension = await loadExtensionFromFactory(factory, cwd, createEventBus(), createExtensionRuntime(), extensionPath);
    return {
        getExtensions() {
            const result = resourceLoader.getExtensions();
            return {
                ...result,
                extensions: result.extensions.some((existing) => existing.path === extensionPath)
                    ? result.extensions
                    : [...result.extensions, extension],
            };
        },
        getSkills: () => resourceLoader.getSkills(),
        getPrompts: () => resourceLoader.getPrompts(),
        getThemes: () => resourceLoader.getThemes(),
        getAgentsFiles: () => resourceLoader.getAgentsFiles(),
        getSystemPrompt: () => resourceLoader.getSystemPrompt(),
        getAppendSystemPrompt: () => resourceLoader.getAppendSystemPrompt(),
        extendResources: (paths) => resourceLoader.extendResources(paths),
        reload: () => resourceLoader.reload(),
    };
}
function getAssistantErrorMessage(message) {
    if (message.role !== "assistant") {
        return undefined;
    }
    if (!("errorMessage" in message)) {
        return undefined;
    }
    if ("stopReason" in message && message.stopReason === "aborted") {
        return undefined;
    }
    return typeof message.errorMessage === "string" && message.errorMessage.length > 0
        ? message.errorMessage
        : undefined;
}
function getToolExecutionErrorMessage(toolName, result) {
    const text = result.content
        ?.filter((content) => content.type === "text" && !!content.text)
        .map((content) => content.text)
        .join("\n")
        .trim();
    return text && text.length > 0 ? text : `Tool "${toolName}" failed.`;
}
function getAssistantMessageId(message) {
    if (message.role !== "assistant") {
        return undefined;
    }
    return `assistant:${message.timestamp}`;
}
function getMessageId(message) {
    if (message.role === "assistant") {
        return getAssistantMessageId(message);
    }
    if (message.role === "toolResult") {
        return `tool:${message.toolCallId}:${message.timestamp}`;
    }
    return `${message.role}:${message.timestamp}`;
}
function getMessageLiveKey(message) {
    const source = "messageSource" in message ? message.messageSource : undefined;
    const toolCallId = message.role === "toolResult" ? message.toolCallId : undefined;
    return JSON.stringify({
        role: message.role,
        timestamp: message.timestamp,
        source,
        toolCallId,
        text: getMessageTextForId(message),
    });
}
function getMessageTextForId(message) {
    if (!("content" in message)) {
        return "";
    }
    const content = message.content;
    if (typeof content === "string") {
        return content;
    }
    if (!Array.isArray(content)) {
        return "";
    }
    return content
        .filter((part) => part.type === "text" && typeof part.text === "string")
        .map((part) => part.text)
        .join("\n");
}
export function mapAgentSessionEventToLiveRenderEvent(event) {
    switch (event.type) {
        case "message_start":
        case "message_update":
        case "message_end": {
            const message = event.message;
            const messageId = getMessageId(message);
            if (!messageId) {
                return undefined;
            }
            if (message.role !== "assistant") {
                return {
                    type: event.type,
                    messageId,
                    message,
                };
            }
            return {
                type: event.type === "message_start"
                    ? "assistant_message_start"
                    : event.type === "message_update"
                        ? "assistant_message_update"
                        : "assistant_message_end",
                messageId,
                message,
            };
        }
        case "tool_execution_start":
            return {
                type: "tool_execution_start",
                toolCallId: event.toolCallId,
                toolName: event.toolName,
                args: event.args,
            };
        case "tool_execution_update":
            return {
                type: "tool_execution_update",
                toolCallId: event.toolCallId,
                toolName: event.toolName,
                args: event.args,
                partialResult: event.partialResult,
            };
        case "tool_execution_end":
            return {
                type: "tool_execution_end",
                toolCallId: event.toolCallId,
                toolName: event.toolName,
                result: event.result,
                isError: event.isError,
            };
        case "compaction_start":
            return {
                type: "status",
                message: "Compacting conversation...",
            };
        case "compaction_end":
            return {
                type: "status",
                message: event.aborted
                    ? "Compaction aborted."
                    : event.errorMessage
                        ? `Compaction failed: ${event.errorMessage}`
                        : "Compaction finished.",
            };
        case "auto_retry_start":
            return {
                type: "status",
                message: `Retrying assistant response (${event.attempt}/${event.maxAttempts})...`,
            };
        case "auto_retry_end":
            return {
                type: "status",
                message: event.success ? "Retry succeeded." : `Retry failed: ${event.finalError ?? "unknown error"}`,
            };
        default:
            return undefined;
    }
}
export class HubAgentAdapter {
    session;
    services;
    extensionsResult;
    resourceLoader;
    diagnostics;
    /** Hub tool catalog, including static D-Pi tools and dynamic MCP tools. */
    tools;
    /** Dynamic MCP tools passed by reference to AgentSession customTools. */
    dynamicTools;
    sessionService;
    refreshModelsConfig;
    refreshSources;
    refreshMcp;
    beforeInputQueueDrain;
    agentId;
    logs;
    onModelChange;
    sessionLogger;
    unsubscribeSessionEvents;
    liveEventListeners = new Set();
    inputQueue = [];
    _steeringShadow = [];
    _followUpShadow = [];
    inputQueuePumpPromise;
    explicitFlushDepth = 0;
    queuedInputTimes = new WeakMap();
    activeAssistantLiveMessageId;
    assistantLiveMessageSeq = 0;
    activeMessageLiveIds = new Map();
    messageLiveSeq = 0;
    abortPromise;
    /** Timestamp (ms) of the last throttled sync triggered by message_update for contextUsage updates. */
    lastMessageUpdateSyncMs = 0;
    static MESSAGE_UPDATE_SYNC_INTERVAL_MS = 16;
    constructor(options) {
        this.sessionService = options.sessionService;
        this.session = options.session;
        this.inputQueue.push(...options.sessionService.getInputQueue());
        this.services = options.services;
        this.extensionsResult = options.extensionsResult;
        this.resourceLoader = options.resourceLoader;
        this.diagnostics = options.diagnostics;
        this.tools = options.tools;
        this.dynamicTools = options.dynamicTools ?? [];
        this.refreshModelsConfig = options.refreshModelsConfig;
        this.refreshSources = options.refreshSources;
        this.refreshMcp = options.refreshMcp;
        this.beforeInputQueueDrain = options.beforeInputQueueDrain;
        this.agentId = options.agentId;
        this.logs = options.logs;
        this.onModelChange = options.onModelChange;
        this.sessionLogger = new HubAgentSessionLogger({ agentId: options.agentId, logs: options.logs });
        this.sessionService.bindAgentSession(this.session, {
            diagnostics: this.diagnostics.map((diagnostic) => `[${diagnostic.type}] ${diagnostic.message}`),
        });
        this.unsubscribeSessionEvents = this.session.subscribe((event) => {
            this.handleSessionEvent(event);
        });
        this.sessionService.syncBoundAgentSession();
    }
    static async create(options) {
        const cwd = options.cwd ?? process.cwd();
        const agentDir = options.agentDir ?? getAgentDir();
        const canInjectInlineExtension = options.services === undefined;
        const extensionTools = options.tools.filter((tool) => !isDynamicMcpToolName(tool.name));
        const dynamicTools = options.tools.filter((tool) => isDynamicMcpToolName(tool.name));
        const dPiExtensionFactory = createDPiExtensionFactory({ tools: extensionTools });
        const resourceLoaderOptions = canInjectInlineExtension
            ? appendDPiExtensionFactory(options.resourceLoaderOptions, dPiExtensionFactory)
            : options.resourceLoaderOptions;
        const getConfigLayers = () => options.getConfigLayers?.() ?? options.configLayers;
        const initialConfigLayers = getConfigLayers();
        console.log("[d-pi hub] 创建智能体会话: 加载配置层");
        const aggregatedConfig = options.services === undefined && initialConfigLayers
            ? await createAggregatedAgentSessionServices({
                cwd,
                agentDir,
                layers: initialConfigLayers,
                peerMcpSnapshots: options.getPeerMcpSnapshots?.(),
                resourceLoaderOptions,
            })
            : undefined;
        const modelsConfig = options.services === undefined && aggregatedConfig === undefined
            ? materializeMergedModelsConfig(cwd, agentDir)
            : undefined;
        let services = options.services;
        if (!services && aggregatedConfig) {
            services = aggregatedConfig.services;
        }
        if (!services) {
            console.log("[d-pi hub] 创建智能体会话: 初始化服务");
            const authStorage = AuthStorage.create(join(agentDir, "auth.json"));
            services = await createAgentSessionServices({
                cwd,
                agentDir,
                authStorage,
                modelRegistry: modelsConfig ? ModelRegistry.create(authStorage, modelsConfig.mergedModelsFile) : undefined,
                resourceLoaderOptions,
            });
        }
        if (!canInjectInlineExtension) {
            console.log("[d-pi hub] 创建智能体会话: 加载扩展");
            services.resourceLoader = await appendLoadedExtensionToResourceLoader(services.resourceLoader, dPiExtensionFactory, cwd);
        }
        const resourceLoader = HubResourceLoader.wrap(services.resourceLoader);
        services.resourceLoader = resourceLoader;
        await options.prepareServices?.(services);
        console.log("[d-pi hub] 创建智能体会话: 创建Agent会话");
        let resolvedModel = options.model;
        if (!resolvedModel && options.persistedModel) {
            const found = services.modelRegistry.find(options.persistedModel.provider, options.persistedModel.modelId);
            if (found) {
                resolvedModel = found;
            }
        }
        const created = await createAgentSessionFromServices({
            services,
            sessionManager: options.sessionService.getSessionManager(),
            model: resolvedModel,
            thinkingLevel: options.thinkingLevel,
            scopedModels: options.scopedModels,
            customTools: dynamicTools,
        });
        console.log("[d-pi hub] 创建智能体会话: 设置活跃工具");
        created.session.setActiveToolsByName(options.tools.map((tool) => tool.name));
        const adapter = new HubAgentAdapter({
            sessionService: options.sessionService,
            session: created.session,
            services,
            extensionsResult: created.extensionsResult,
            resourceLoader,
            diagnostics: services.diagnostics,
            tools: options.tools,
            dynamicTools,
            refreshModelsConfig: modelsConfig
                ? () => materializeMergedModelsConfig(cwd, agentDir)
                : aggregatedConfig
                    ? () => materializeAggregatedModelsConfig({
                        cwd,
                        layers: getConfigLayers() ?? initialConfigLayers ?? [],
                        mergedModelsFile: aggregatedConfig.mergedModelsFile,
                    })
                    : undefined,
            refreshSources: options.refreshSources,
            refreshMcp: options.refreshMcp,
            beforeInputQueueDrain: options.beforeInputQueueDrain,
            agentId: options.agentId ?? "root",
            logs: options.logs,
            onModelChange: createPersistableModelChangeHandler(options),
        });
        await adapter.refreshSessionOptions();
        console.log("[d-pi hub] 创建智能体会话: 完成");
        return adapter;
    }
    async enqueueFromPeer(peerId, text, metadata, images, queueMode) {
        this.enqueueInput({ text, messageSource: createPeerMessageSource(peerId, metadata), images, queueMode });
        this.scheduleInputQueuePump();
    }
    async enqueueFromHost(hostId, text, metadata, images, queueMode) {
        this.enqueueInput({ text, messageSource: createHostMessageSource(hostId, metadata), images, queueMode });
        this.scheduleInputQueuePump();
    }
    async enqueueFromSource(sourceName, text) {
        this.enqueueInput({ text, messageSource: createSourceMessageSource(sourceName) });
        this.scheduleInputQueuePump();
    }
    async enqueueFromAgent(agentId, text, options) {
        const messageSource = createAgentMessageSource(agentId);
        if (options?.messageId)
            messageSource.messageId = options.messageId;
        if (options?.responseIds && options.responseIds.length > 0)
            messageSource.responseIds = options.responseIds;
        this.enqueueInput({ text, messageSource, responseTimeout: options?.responseTimeout });
        this.scheduleInputQueuePump();
    }
    continueCurrentTranscript() {
        void this.session.agent
            .continue()
            .then(() => this.session.agent.waitForIdle())
            .catch((error) => {
            this.sessionService.recordError(getErrorMessage(error));
        })
            .finally(() => {
            this.sessionService.syncBoundAgentSession();
        });
    }
    requestInputQueuePump() {
        this.scheduleInputQueuePump();
    }
    async flushInputQueue() {
        const queuedMessageCount = this.getQueuedInputMessageCount();
        if (queuedMessageCount === 0) {
            return { flushed: false, messages: 0 };
        }
        const messages = this.drainInputQueue();
        this.sessionService.setInputQueue(this.inputQueue);
        if (messages.length === 0) {
            return { flushed: false, messages: 0 };
        }
        this.startInputQueueFlush(messages);
        return { flushed: true, messages: queuedMessageCount };
    }
    startInputQueueFlush(messages) {
        this.explicitFlushDepth++;
        void this.runInputQueueFlush(messages).catch((error) => {
            this.sessionService.recordError(getErrorMessage(error));
            this.sessionService.syncBoundAgentSession();
        });
    }
    async runInputQueueFlush(messages) {
        let promptSucceeded = false;
        try {
            if (this.session.isStreaming) {
                const abortStartedAt = Date.now();
                this.sessionService.setRunState(false, "interrupted");
                this.session.agent.clearAllQueues();
                this._steeringShadow = [];
                this._followUpShadow = [];
                await this.session.abort();
                this.sessionService.syncBoundAgentSession();
                const abortDurationMs = Date.now() - abortStartedAt;
                this.logQueueSubmit("flush", messages, { abortDurationMs, restartDelayMs: abortDurationMs });
            }
            else {
                this.logQueueSubmit("flush", messages);
            }
            await this.promptQueuedInputMessages(messages, "flush");
            promptSucceeded = true;
        }
        catch (error) {
            this.restoreInputQueue(messages);
            throw error;
        }
        finally {
            this.explicitFlushDepth--;
            this.sessionService.syncBoundAgentSession();
            if (promptSucceeded &&
                this.explicitFlushDepth === 0 &&
                this.getQueuedInputMessageCount() > 0 &&
                !this.session.isStreaming) {
                this.scheduleInputQueuePump();
            }
        }
    }
    async dequeue() {
        const cleared = this.drainInputQueue();
        const steeringCleared = this._steeringShadow.splice(0);
        const followUpCleared = this._followUpShadow.splice(0);
        this.session.agent.clearAllQueues();
        this.sessionService.setInputQueue(this.inputQueue, this._steeringShadow, this._followUpShadow);
        this.sessionService.syncBoundAgentSession();
        return [...steeringCleared, ...followUpCleared, ...cleared];
    }
    enqueueInput(message) {
        this.queuedInputTimes.set(message, Date.now());
        if (this.session.isStreaming && !this.session.isCompacting) {
            const queueMode = message.queueMode ?? "steer";
            const agentMessage = queuedInputMessageToAgentMessage(message);
            if (queueMode === "followUp") {
                this._followUpShadow.push(message);
                this.session.agent.followUp(agentMessage);
            }
            else {
                this._steeringShadow.push(message);
                this.session.agent.steer(agentMessage);
            }
        }
        else {
            this.inputQueue.push(message);
        }
        this.sessionService.setInputQueue(this.inputQueue, this._steeringShadow, this._followUpShadow);
        this.scheduleInputQueuePumpAfterSessionSettles();
    }
    getQueuedInputMessageCount() {
        return this.inputQueue.length;
    }
    getUserMessageText(message) {
        if (message.role !== "user")
            return "";
        const content = message.content;
        if (typeof content === "string")
            return content;
        return content
            .filter((c) => c.type === "text")
            .map((c) => c.text)
            .join("");
    }
    drainInputQueue() {
        return this.inputQueue.splice(0);
    }
    drainNextInputMessage() {
        const next = this.inputQueue.shift();
        return next ? [next] : [];
    }
    restoreInputQueue(messages) {
        this.inputQueue.unshift(...messages);
        this.sessionService.setInputQueue(this.inputQueue);
    }
    scheduleInputQueuePump() {
        if (this.explicitFlushDepth > 0) {
            return;
        }
        void this.ensureInputQueuePump().catch((error) => {
            this.sessionService.recordError(getErrorMessage(error));
            this.sessionService.syncBoundAgentSession();
        });
    }
    scheduleInputQueuePumpAfterSessionSettles() {
        this.scheduleInputQueuePumpWhenIdle(8);
    }
    scheduleInputQueuePumpWhenIdle(attemptsRemaining) {
        setTimeout(() => {
            if (this.explicitFlushDepth > 0 || this.getQueuedInputMessageCount() === 0) {
                return;
            }
            if (this.session.isStreaming || this.session.isCompacting) {
                if (attemptsRemaining > 0) {
                    this.scheduleInputQueuePumpWhenIdle(attemptsRemaining - 1);
                }
                return;
            }
            this.scheduleInputQueuePump();
        }, 0);
    }
    ensureInputQueuePump() {
        if (this.inputQueuePumpPromise) {
            return this.inputQueuePumpPromise;
        }
        const pump = this.pumpInputQueue();
        this.inputQueuePumpPromise = pump;
        void pump.then(() => {
            if (this.inputQueuePumpPromise === pump) {
                this.inputQueuePumpPromise = undefined;
            }
            if (this.explicitFlushDepth === 0 &&
                this.getQueuedInputMessageCount() > 0 &&
                !this.session.isStreaming &&
                !this.session.isCompacting) {
                this.scheduleInputQueuePump();
            }
        }, () => {
            if (this.inputQueuePumpPromise === pump) {
                this.inputQueuePumpPromise = undefined;
            }
        });
        return pump;
    }
    async pumpInputQueue() {
        while (true) {
            if (this.explicitFlushDepth > 0) {
                return;
            }
            if (this.abortPromise) {
                return;
            }
            if (this.session.isStreaming) {
                return;
            }
            if (this.session.isCompacting) {
                return;
            }
            const shouldContinue = await this.beforeInputQueueDrain?.();
            if (shouldContinue === false) {
                return;
            }
            if (this.session.isStreaming) {
                return;
            }
            if (this.session.isCompacting) {
                return;
            }
            const messages = this.drainNextInputMessage();
            this.sessionService.setInputQueue(this.inputQueue);
            if (messages.length === 0) {
                return;
            }
            try {
                this.logQueueSubmit("auto", messages);
                await this.promptQueuedInputMessages(messages, "auto");
            }
            catch (error) {
                this.restoreInputQueue(messages);
                throw error;
            }
            finally {
                this.sessionService.syncBoundAgentSession();
            }
        }
    }
    async promptQueuedInputMessages(messages, drainMode) {
        if (messages.length === 0) {
            return;
        }
        const now = Date.now();
        let preflightSucceeded = false;
        this.sessionService.setRunState(true);
        try {
            const agentMessages = messages.map((message, index) => queuedInputMessageToAgentMessage(message, now + index));
            preflightSucceeded = true;
            this.log("info", "prompt preflight timing", {
                agentId: this.agentId,
                phase: "preflight",
                drainMode,
                queuedMessages: messages.length,
                durationMs: Math.max(0, Date.now() - now),
                success: true,
            });
            await this.session.agent.prompt(agentMessages);
        }
        catch (error) {
            if (!preflightSucceeded) {
                this.sessionService.setRunState(false, "error");
            }
            throw error;
        }
    }
    logQueueSubmit(drainMode, messages, details = {}) {
        this.log("info", drainMode === "flush" ? "queue flush submitted" : "queue drain submitted", {
            agentId: this.agentId,
            phase: "queue",
            drainMode,
            queuedMessages: messages.length,
            queueWaitMs: this.getQueueWaitMs(messages),
            ...details,
        });
    }
    getQueueWaitMs(messages) {
        const now = Date.now();
        let earliest;
        for (const message of messages) {
            const queuedAt = this.queuedInputTimes.get(message);
            if (queuedAt === undefined) {
                continue;
            }
            earliest = earliest === undefined ? queuedAt : Math.min(earliest, queuedAt);
        }
        return earliest === undefined ? 0 : Math.max(0, now - earliest);
    }
    log(level, message, details) {
        try {
            this.logs?.[level](message, details);
        }
        catch {
            // Logging must never affect agent execution.
        }
    }
    async abort() {
        if (this.abortPromise) {
            return this.abortPromise;
        }
        this.abortPromise = this.abortActiveSession();
        try {
            await this.abortPromise;
        }
        finally {
            this.abortPromise = undefined;
            this.scheduleInputQueuePumpAfterSessionSettles();
        }
    }
    async abortActiveSession() {
        if (this.session.isStreaming) {
            this.sessionService.setRunState(false, "interrupted");
        }
        await this.session.abort();
        this.sessionService.syncBoundAgentSession();
    }
    async setModel(model) {
        await this.session.setModel(model, { persistDefault: false });
        this.onModelChange?.({ provider: model.provider, modelId: model.id });
        this.sessionService.syncBoundAgentSession();
    }
    async cycleModel(direction = "forward") {
        const result = await this.session.cycleModel(direction);
        if (result) {
            this.onModelChange?.({ provider: result.model.provider, modelId: result.model.id });
        }
        this.sessionService.syncBoundAgentSession();
        return result;
    }
    setThinkingLevel(level) {
        this.session.setThinkingLevel(level);
        this.sessionService.syncBoundAgentSession();
    }
    cycleThinkingLevel() {
        const result = this.session.cycleThinkingLevel();
        this.sessionService.syncBoundAgentSession();
        return result;
    }
    async compact(customInstructions) {
        const result = await this.session.compact(customInstructions);
        this.sessionService.syncBoundAgentSession();
        return result;
    }
    async reload() {
        this.refreshModelsConfig?.();
        await this.refreshSources?.();
        await this.refreshMcp?.();
        this.syncDynamicTools();
        await this.session.reload();
        this.services.modelRegistry.refresh();
        await this.refreshCurrentModelFromRegistry();
        this.session.setActiveToolsByName(this.tools.map((tool) => tool.name));
        await this.refreshSessionOptions();
        this.sessionService.syncBoundAgentSession();
    }
    async getAvailableModels() {
        return this.services.modelRegistry.getAvailable();
    }
    async refreshCurrentModelFromRegistry() {
        const current = this.session.model;
        if (!current) {
            return;
        }
        const refreshed = this.services.modelRegistry.find(current.provider, current.id);
        if (!refreshed) {
            // Model no longer available — fall back to any available model
            const available = this.services.modelRegistry.getAvailable();
            if (available.length > 0) {
                const fallback = available[0];
                await this.session.setModel(fallback, { persistDefault: false });
                this.onModelChange?.({ provider: fallback.provider, modelId: fallback.id });
            }
            return;
        }
        if (refreshed === current) {
            return;
        }
        await this.session.setModel(refreshed, { persistDefault: false });
    }
    syncDynamicTools() {
        let writeIndex = 0;
        for (const tool of this.tools) {
            if (isDynamicMcpToolName(tool.name)) {
                this.dynamicTools[writeIndex] = tool;
                writeIndex += 1;
            }
        }
        this.dynamicTools.length = writeIndex;
    }
    subscribeLiveEvents(listener) {
        this.liveEventListeners.add(listener);
        return () => {
            this.liveEventListeners.delete(listener);
        };
    }
    dispose() {
        this.unsubscribeSessionEvents();
        this.sessionService.unbindAgentSession();
        this.session.dispose();
    }
    handleSessionEvent(event) {
        this.sessionLogger.handle(event);
        this.emitLiveRenderEventForSessionEvent(event);
        switch (event.type) {
            case "agent_start":
                this.sessionService.clearError();
                this.sessionService.setRunState(true);
                this.lastMessageUpdateSyncMs = 0;
                this.sessionService.syncBoundAgentSession();
                return;
            case "agent_end":
                this.sessionService.setRunState(false);
                this.sessionService.syncBoundAgentSession();
                this.scheduleInputQueuePump();
                this.scheduleInputQueuePumpAfterSessionSettles();
                if (!this.session.agent.hasQueuedMessages()) {
                    if (this._steeringShadow.length > 0 || this._followUpShadow.length > 0) {
                        this._steeringShadow = [];
                        this._followUpShadow = [];
                        this.sessionService.setInputQueue(this.inputQueue, this._steeringShadow, this._followUpShadow);
                    }
                }
                return;
            case "message_end": {
                const errorMessage = getAssistantErrorMessage(event.message);
                if (errorMessage) {
                    this.sessionService.recordError(errorMessage);
                }
                else if (event.message.role === "assistant") {
                    this.sessionService.clearError();
                }
                this.sessionService.syncBoundAgentSession();
                this.scheduleInputQueuePumpAfterSessionSettles();
                return;
            }
            case "message_update": {
                // message_update fires on every streaming token; throttle to ~60fps (16ms).
                // d-pi typically has few peers, so near-realtime sync is acceptable.
                // Footer token stats only update on message_end (matches upstream behavior).
                const now = Date.now();
                if (now - this.lastMessageUpdateSyncMs >= HubAgentAdapter.MESSAGE_UPDATE_SYNC_INTERVAL_MS) {
                    this.lastMessageUpdateSyncMs = now;
                    this.sessionService.syncBoundAgentSession();
                }
                return;
            }
            case "message_start": {
                if (event.message.role === "user") {
                    const messageText = this.getUserMessageText(event.message);
                    if (messageText) {
                        const steeringIndex = this._steeringShadow.findIndex((m) => m.text === messageText);
                        if (steeringIndex !== -1) {
                            this._steeringShadow.splice(steeringIndex, 1);
                            this.sessionService.setInputQueue(this.inputQueue, this._steeringShadow, this._followUpShadow);
                        }
                        else {
                            const followUpIndex = this._followUpShadow.findIndex((m) => m.text === messageText);
                            if (followUpIndex !== -1) {
                                this._followUpShadow.splice(followUpIndex, 1);
                                this.sessionService.setInputQueue(this.inputQueue, this._steeringShadow, this._followUpShadow);
                            }
                        }
                    }
                }
                this.sessionService.syncBoundAgentSession();
                return;
            }
            case "tool_execution_start":
            case "tool_execution_update":
            case "turn_start":
            case "turn_end":
            case "auto_retry_start":
            case "auto_retry_end":
                this.sessionService.syncBoundAgentSession();
                return;
            case "compaction_start":
            case "compaction_end":
                this.emitLiveRenderEventForSessionEvent(event);
                this.sessionService.syncBoundAgentSession();
                if (event.type === "compaction_end") {
                    this.scheduleInputQueuePumpAfterSessionSettles();
                }
                return;
            case "tool_execution_end":
                if (event.isError) {
                    this.sessionService.recordError(getToolExecutionErrorMessage(event.toolName, event.result), {
                        endRun: false,
                    });
                }
                this.sessionService.syncBoundAgentSession();
                return;
            default:
                this.sessionService.syncBoundAgentSession();
        }
    }
    emitLiveRenderEventForSessionEvent(event) {
        const liveEvent = this.mapSessionEventToLiveRenderEvent(event);
        if (!liveEvent) {
            return;
        }
        for (const listener of this.liveEventListeners) {
            listener(liveEvent);
        }
    }
    mapSessionEventToLiveRenderEvent(event) {
        if ((event.type === "message_start" || event.type === "message_update" || event.type === "message_end") &&
            event.message.role === "assistant") {
            if (event.type === "message_start" || !this.activeAssistantLiveMessageId) {
                this.assistantLiveMessageSeq += 1;
                this.activeAssistantLiveMessageId = `${this.agentId}:assistant-live:${event.message.timestamp}:${this.assistantLiveMessageSeq}`;
            }
            const messageId = this.activeAssistantLiveMessageId;
            if (event.type === "message_end") {
                this.activeAssistantLiveMessageId = undefined;
            }
            return {
                type: event.type === "message_start"
                    ? "assistant_message_start"
                    : event.type === "message_update"
                        ? "assistant_message_update"
                        : "assistant_message_end",
                messageId,
                message: event.message,
            };
        }
        if (event.type === "message_start" || event.type === "message_update" || event.type === "message_end") {
            const messageId = this.getLiveMessageId(event);
            return {
                type: event.type,
                messageId,
                message: event.message,
            };
        }
        return mapAgentSessionEventToLiveRenderEvent(event);
    }
    getLiveMessageId(event) {
        const key = getMessageLiveKey(event.message);
        let messageId = this.activeMessageLiveIds.get(key);
        if (event.type === "message_start" || !messageId) {
            this.messageLiveSeq += 1;
            messageId = `${this.agentId}:${event.message.role}:${event.message.timestamp}:${this.messageLiveSeq}`;
            this.activeMessageLiveIds.set(key, messageId);
        }
        if (event.type === "message_end") {
            this.activeMessageLiveIds.delete(key);
        }
        return messageId;
    }
    async refreshSessionOptions() {
        this.sessionService.updateSessionOptions({
            availableModels: serializeAvailableModels(await this.getAvailableModels()),
            availableThinkingLevels: [...DEFAULT_AVAILABLE_THINKING_LEVELS],
        });
    }
}
//# sourceMappingURL=hub-agent-adapter.js.map