/**
 * Extension runner - executes extensions and manages their lifecycle.
 */
import { createExtensionCommandContext, createExtensionContext, } from "./runner-context.js";
import { runBeforeAgentStartHandlers, runBeforeProviderRequestHandlers, runContextHandlers, runGenericHandlers, runInputHandlers, runMessageEndHandlers, runResourcesDiscoverHandlers, runToolCallHandlers, runToolResultHandlers, runUserBashHandlers, } from "./runner-events.js";
import { collectFlags, collectRegisteredTools, findMessageRenderer, findToolDefinition, hasExtensionHandlers, resolveRegisteredCommands, } from "./runner-registries.js";
import { resolveExtensionShortcuts } from "./runner-shortcuts.js";
import { noOpUIContext } from "./runner-ui.js";
export { emitProjectTrustEvent } from "./runner-project-trust.js";
/**
 * Helper function to emit session_shutdown event to extensions.
 * Returns true if the event was emitted, false if there were no handlers.
 */
export async function emitSessionShutdownEvent(extensionRunner, event) {
    if (extensionRunner.hasHandlers("session_shutdown")) {
        await extensionRunner.emit(event);
        return true;
    }
    return false;
}
export class ExtensionRunner {
    constructor(extensions, runtime, cwd, sessionManager, modelRegistry, orchestrationContext) {
        this.mode = "print";
        this.errorListeners = new Set();
        this.getModel = () => undefined;
        this.isIdleFn = () => true;
        this.isProjectTrustedFn = () => true;
        this.getSignalFn = () => undefined;
        this.waitForIdleFn = async () => { };
        this.abortFn = () => { };
        this.hasPendingMessagesFn = () => false;
        this.getContextUsageFn = () => undefined;
        this.compactFn = () => { };
        this.getSystemPromptFn = () => "";
        this.getSystemPromptOptionsFn = () => ({ cwd: this.cwd });
        this.newSessionHandler = async () => ({ cancelled: false });
        this.forkHandler = async () => ({ cancelled: false });
        this.navigateTreeHandler = async () => ({ cancelled: false });
        this.switchSessionHandler = async () => ({ cancelled: false });
        this.reloadHandler = async () => { };
        this.shutdownHandler = () => { };
        this.shortcutDiagnostics = [];
        this.commandDiagnostics = [];
        this.extensions = extensions;
        this.runtime = runtime;
        this.uiContext = noOpUIContext;
        this.cwd = cwd;
        this.sessionManager = sessionManager;
        this.modelRegistry = modelRegistry;
        this.orchestrationContext = orchestrationContext;
    }
    bindCore(actions, contextActions, providerActions) {
        // Copy actions into the shared runtime (all extension APIs reference this)
        this.runtime.sendMessage = actions.sendMessage;
        this.runtime.sendUserMessage = actions.sendUserMessage;
        this.runtime.appendEntry = actions.appendEntry;
        this.runtime.setSessionName = actions.setSessionName;
        this.runtime.getSessionName = actions.getSessionName;
        this.runtime.setLabel = actions.setLabel;
        this.runtime.getActiveTools = actions.getActiveTools;
        this.runtime.getAllTools = actions.getAllTools;
        this.runtime.setActiveTools = actions.setActiveTools;
        this.runtime.refreshTools = actions.refreshTools;
        this.runtime.getCommands = actions.getCommands;
        this.runtime.setModel = actions.setModel;
        this.runtime.getThinkingLevel = actions.getThinkingLevel;
        this.runtime.setThinkingLevel = actions.setThinkingLevel;
        // Context actions (required)
        this.getModel = contextActions.getModel;
        this.isIdleFn = contextActions.isIdle;
        this.isProjectTrustedFn = contextActions.isProjectTrusted;
        this.getSignalFn = contextActions.getSignal;
        this.abortFn = contextActions.abort;
        this.hasPendingMessagesFn = contextActions.hasPendingMessages;
        this.shutdownHandler = contextActions.shutdown;
        this.getContextUsageFn = contextActions.getContextUsage;
        this.compactFn = contextActions.compact;
        this.getSystemPromptFn = contextActions.getSystemPrompt;
        this.getSystemPromptOptionsFn = contextActions.getSystemPromptOptions ?? (() => ({ cwd: this.cwd }));
        // Flush provider registrations queued during extension loading
        for (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) {
            try {
                if (providerActions?.registerProvider) {
                    providerActions.registerProvider(name, config);
                }
                else {
                    this.modelRegistry.registerProvider(name, config);
                }
            }
            catch (error) {
                this.emitError({
                    extensionPath,
                    event: "register_provider",
                    error: error instanceof Error ? error.message : String(error),
                    stack: error instanceof Error ? error.stack : undefined,
                });
            }
        }
        this.runtime.pendingProviderRegistrations = [];
        // From this point on, provider registration/unregistration takes effect immediately
        // without requiring a /reload.
        this.runtime.registerProvider = (name, config) => {
            if (providerActions?.registerProvider) {
                providerActions.registerProvider(name, config);
                return;
            }
            this.modelRegistry.registerProvider(name, config);
        };
        this.runtime.unregisterProvider = (name) => {
            if (providerActions?.unregisterProvider) {
                providerActions.unregisterProvider(name);
                return;
            }
            this.modelRegistry.unregisterProvider(name);
        };
    }
    bindCommandContext(actions) {
        if (actions) {
            this.waitForIdleFn = actions.waitForIdle;
            this.newSessionHandler = actions.newSession;
            this.forkHandler = actions.fork;
            this.navigateTreeHandler = actions.navigateTree;
            this.switchSessionHandler = actions.switchSession;
            this.reloadHandler = actions.reload;
            return;
        }
        this.waitForIdleFn = async () => { };
        this.newSessionHandler = async () => ({ cancelled: false });
        this.forkHandler = async () => ({ cancelled: false });
        this.navigateTreeHandler = async () => ({ cancelled: false });
        this.switchSessionHandler = async () => ({ cancelled: false });
        this.reloadHandler = async () => { };
    }
    setUIContext(uiContext, mode = "print") {
        this.uiContext = uiContext ?? noOpUIContext;
        this.mode = mode;
    }
    getUIContext() {
        return this.uiContext;
    }
    hasUI() {
        return this.uiContext !== noOpUIContext;
    }
    getExtensionPaths() {
        return this.extensions.map((extension) => extension.path);
    }
    /** Get all registered tools from all extensions (first registration per name wins). */
    getAllRegisteredTools() {
        return collectRegisteredTools(this.extensions);
    }
    /** Get a tool definition by name. Returns undefined if not found. */
    getToolDefinition(toolName) {
        return findToolDefinition(this.extensions, toolName);
    }
    getFlags() {
        return collectFlags(this.extensions);
    }
    setFlagValue(name, value) {
        this.runtime.flagValues.set(name, value);
    }
    getFlagValues() {
        return new Map(this.runtime.flagValues);
    }
    getShortcuts(resolvedKeybindings) {
        const resolution = resolveExtensionShortcuts(this.extensions, resolvedKeybindings, this.hasUI());
        this.shortcutDiagnostics = resolution.diagnostics;
        return resolution.shortcuts;
    }
    getShortcutDiagnostics() {
        return this.shortcutDiagnostics;
    }
    invalidate(message = "This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().") {
        if (!this.staleMessage) {
            this.staleMessage = message;
            this.runtime.invalidate(message);
        }
    }
    assertActive() {
        if (this.staleMessage) {
            throw new Error(this.staleMessage);
        }
    }
    onError(listener) {
        this.errorListeners.add(listener);
        return () => this.errorListeners.delete(listener);
    }
    emitError(error) {
        for (const listener of this.errorListeners) {
            listener(error);
        }
    }
    hasHandlers(eventType) {
        return hasExtensionHandlers(this.extensions, eventType);
    }
    getMessageRenderer(customType) {
        return findMessageRenderer(this.extensions, customType);
    }
    getRegisteredCommands() {
        this.commandDiagnostics = [];
        return resolveRegisteredCommands(this.extensions);
    }
    getCommandDiagnostics() {
        return this.commandDiagnostics;
    }
    getCommand(name) {
        return resolveRegisteredCommands(this.extensions).find((command) => command.invocationName === name);
    }
    /**
     * Request a graceful shutdown. Called by extension tools and event handlers.
     * The actual shutdown behavior is provided by the mode via bindExtensions().
     */
    shutdown() {
        this.shutdownHandler();
    }
    createContext() {
        return createExtensionContext(this.createContextSource());
    }
    createCommandContext() {
        return createExtensionCommandContext(this.createContextSource());
    }
    createContextSource() {
        return {
            assertActive: () => this.assertActive(),
            getUIContext: () => this.uiContext,
            getMode: () => this.mode,
            hasUI: () => this.hasUI(),
            getCwd: () => this.cwd,
            getSessionManager: () => this.sessionManager,
            getModelRegistry: () => this.modelRegistry,
            getModel: () => this.getModel(),
            getOrchestrationContext: () => this.orchestrationContext,
            isIdle: () => this.isIdleFn(),
            isProjectTrusted: () => this.isProjectTrustedFn(),
            getSignal: () => this.getSignalFn(),
            abort: () => this.abortFn(),
            hasPendingMessages: () => this.hasPendingMessagesFn(),
            shutdown: () => this.shutdownHandler(),
            getContextUsage: () => this.getContextUsageFn(),
            compact: (options) => this.compactFn(options),
            getSystemPrompt: () => this.getSystemPromptFn(),
            getSystemPromptOptions: () => this.getSystemPromptOptionsFn(),
            waitForIdle: () => this.waitForIdleFn(),
            newSession: (options) => this.newSessionHandler(options),
            fork: (entryId, options) => this.forkHandler(entryId, options),
            navigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),
            switchSession: (sessionPath, options) => this.switchSessionHandler(sessionPath, options),
            reload: () => this.reloadHandler(),
        };
    }
    async emit(event) {
        return runGenericHandlers(this.extensions, this.createContext(), event, (error) => this.emitError(error));
    }
    async emitMessageEnd(event) {
        return runMessageEndHandlers(this.extensions, this.createContext(), event, (error) => this.emitError(error));
    }
    async emitToolResult(event) {
        return runToolResultHandlers(this.extensions, this.createContext(), event, (error) => this.emitError(error));
    }
    async emitToolCall(event) {
        return runToolCallHandlers(this.extensions, this.createContext(), event);
    }
    async emitUserBash(event) {
        return runUserBashHandlers(this.extensions, this.createContext(), event, (error) => this.emitError(error));
    }
    async emitContext(messages) {
        return runContextHandlers(this.extensions, this.createContext(), messages, (error) => this.emitError(error));
    }
    emitBeforeProviderRequest(payload) {
        return runBeforeProviderRequestHandlers(this.extensions, this.createContext(), payload, (error) => this.emitError(error));
    }
    emitBeforeAgentStart(prompt, images, systemPrompt, systemPromptOptions) {
        return runBeforeAgentStartHandlers(this.extensions, this.createContext(), () => this.assertActive(), prompt, images, systemPrompt, systemPromptOptions, (error) => this.emitError(error));
    }
    emitResourcesDiscover(cwd, reason) {
        return runResourcesDiscoverHandlers(this.extensions, this.createContext(), cwd, reason, (error) => this.emitError(error));
    }
    /** Emit input event. Transforms chain, "handled" short-circuits. */
    emitInput(text, images, source, streamingBehavior) {
        return runInputHandlers(this.extensions, this.createContext(), text, images, source, streamingBehavior, (error) => this.emitError(error));
    }
}
//# sourceMappingURL=runner.js.map