/** * [WHO]: Provides ModelOverlayController + ModelOverlayContext (ModelSessionPort/ModelCatalogPort/ * ModelSettingsPort/ProviderConfigPort/ModelOverlaySurface/ModelOverlayFooter) — interactive model selection * [FROM]: Depends on @catui/ai (Model), agent-core (ThinkingLevel), core/runtime/agent-session * (CycleModelError), @catui/tui (Component/TUI), core/model-resolver (resolveModelScope), * components (Model/Provider/ScopedModels selectors) * [TO]: Consumed by modes/interactive/interactive-mode.ts (held as `this.modelOverlay`; /model, cycle keybindings, * /scoped-models delegate here) * [HERE]: modes/interactive/controllers/model-overlay-controller.ts — P5 model-overlay slice (UI08, hybrid) * * Interactive TUI orchestration of model selection (UI08). It DELEGATES the reusable model capability * (set/cycle model, thinking level, scoped models) to AgentSession via ModelSessionPort — it does NOT * own model switching rules, thinking clamping/persistence, API-key validation, or provider credentials. * Provider configuration is consumed through ProviderConfigPort (points to mount during transition, * repointed to auth/provider-config later). The context is intentionally the widest P5 controller, but * grouped by capability and serving one workflow; it must not keep growing. No InteractiveMode reference. */ import type { Model } from "@catui/ai/types"; import type { ThinkingLevel } from "@catui/agent-core"; import type { Component, TUI } from "@catui/tui"; import type { ModelRegistry } from "../../../core/model-registry.js"; type AnyModel = Model; type ScopedModel = { model: AnyModel; thinkingLevel: ThinkingLevel; }; /** Reusable model capability — delegated to AgentSession; model-overlay never reimplements it. */ export interface ModelSessionPort { getModel(): AnyModel | undefined; setModel(model: AnyModel): Promise; cycleModel(direction: "forward" | "backward"): Promise<{ model: AnyModel; thinkingLevel: ThinkingLevel; } | undefined>; getThinkingLevel(): string; setThinkingLevel(level: ThinkingLevel): void; cycleThinkingLevel(): string | undefined; getAvailableThinkingLevels(): string[]; getScopedModels(): ReadonlyArray; setScopedModels(models: ScopedModel[]): void; } /** Registry/catalog access needed by interactive selection. */ export interface ModelCatalogPort { refresh(): void; getAvailable(): AnyModel[]; getAll(): AnyModel[]; find(provider: string, id: string): AnyModel | undefined; appendOpenRouterModel(id: string, opts: { name?: string; }): void; /** Credential type for a provider (to suggest /login on OAuth cycle failures). */ getCredentialType(provider: string): string | undefined; /** * The concrete ModelRegistry object — required by ModelSelectorComponent and resolveModelScope, * which consume the registry directly. ModelRegistry is the catalog domain object (not AgentSession * or InteractiveMode), so exposing it is allowed under UI-G2. */ getRegistry(): ModelRegistry; } /** Enabled/default model settings only — not the whole settings surface. */ export interface ModelSettingsPort { getEnabledModels(): string[] | undefined; setEnabledModels(patterns: string[] | undefined): void; setDefaultModelAndProvider(provider: string, id: string): void; } /** Provider configuration precondition (points to mount in transition; repointed to auth/provider-config). */ export interface ProviderConfigPort { ensureProviderConfiguredForSelection(model: AnyModel): Promise; handleProviderSelectionFromSelector(provider: string, done: () => void): Promise; promptForProviderApiKey(provider: string, options?: { title?: string; }): Promise; } /** Selector/status/error/prompt/render TUI surface. */ export interface ModelOverlaySurface { showSelector(create: (done: () => void) => { component: Component; focus: Component; }): void; showStatus(message: string): void; showError(message: string): void; promptInput(title: string, placeholder?: string, opts?: { initialValue?: string; }): Promise; getUi(): TUI; } /** Footer/editor-border refresh after selection. */ export interface ModelOverlayFooter { invalidate(): void; setAvailableProviderCount(count: number): void; updateEditorBorderColor(): void; } export interface ModelOverlayContext { modelSession: ModelSessionPort; modelCatalog: ModelCatalogPort; modelSettings: ModelSettingsPort; providerConfig: ProviderConfigPort; surface: ModelOverlaySurface; footer: ModelOverlayFooter; /** Interactive-only side effect after a model is applied (the daxnuts easter egg). */ playDaxnuts(): void; } export declare class ModelOverlayController { private readonly ctx; constructor(ctx: ModelOverlayContext); cycleThinkingLevel(): void; handleThinkingCommand(text: string): void; cycleModel(direction: "forward" | "backward"): Promise; handleModelCommand(searchTerm?: string): Promise; showModelSelector(initialSearchInput?: string, filterByProvider?: string): void; showProviderThenModelSelector(): Promise; showModelsSelector(): Promise; /** Update the footer's available provider count from current model candidates. */ updateAvailableProviderCount(): Promise; private findExactModelMatch; private getModelCandidates; /** * Apply a chosen model: persist as default, refresh footer/border, status, daxnuts hook. * Public because the provider-config path (mount, future provider-config-controller) applies a * model after configuring a custom protocol provider. Selection mutation still goes through the * modelSession port (AgentSession), keeping UI08's runtime-owned capability boundary. */ applySelectedModel(model: AnyModel): Promise; private selectModelWithProviderEnsure; private checkDaxnutsEasterEgg; } export {};