diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index ce922f2aeaf..87a660551f7 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -324,6 +324,7 @@ user sends another prompt ◄───────────────── └─► session_tree /model or Ctrl+P (model selection/cycling) + ├─► model_selector_open (when the picker opens, fire-and-forget) ├─► thinking_level_select (if model change changes/clamps thinking level) └─► model_select @@ -654,6 +655,19 @@ pi.on("model_select", async (event, ctx) => { Use this to update UI elements (status bars, footers) or perform model-specific initialization when the active model changes. +#### model_selector_open + +Fired when the interactive model picker opens (via `/model` or `Ctrl+P`). Dispatched fire-and-forget so the picker is never delayed by extension work, which means provider state changes made in this handler (e.g. re-registering a provider after a remote `/v1/models` fetch) are reflected on the **next** picker open, not the current one. + +```typescript +pi.on("model_selector_open", async () => { + // Refresh dynamically discovered models so the next open sees them. + await refreshRemoteModels(); +}); +``` + +Use this for providers backed by a live endpoint where the model list can change between sessions (e.g. a running llama-server with hot-swappable models). + #### thinking_level_select Fired when the thinking level changes. This is notification-only; handler return values are ignored. diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index 58b85227378..0046d61385c 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -718,6 +718,11 @@ export interface ModelSelectEvent { source: ModelSelectSource; } +/** Fired when the interactive model picker opens. Dispatched fire-and-forget so handlers do not block the UI — any provider-state refresh (e.g. fetching a remote model list) will be reflected on the next open. */ +export interface ModelSelectorOpenEvent { + type: "model_selector_open"; +} + /** Fired when a new thinking level is selected */ export interface ThinkingLevelSelectEvent { type: "thinking_level_select"; @@ -965,6 +970,7 @@ export type ExtensionEvent = | ToolExecutionUpdateEvent | ToolExecutionEndEvent | ModelSelectEvent + | ModelSelectorOpenEvent | ThinkingLevelSelectEvent | UserBashEvent | InputEvent @@ -1119,6 +1125,7 @@ export interface ExtensionAPI { on(event: "tool_execution_update", handler: ExtensionHandler): void; on(event: "tool_execution_end", handler: ExtensionHandler): void; on(event: "model_select", handler: ExtensionHandler): void; + on(event: "model_selector_open", handler: ExtensionHandler): void; on(event: "thinking_level_select", handler: ExtensionHandler): void; on(event: "tool_call", handler: ExtensionHandler): void; on(event: "tool_result", handler: ExtensionHandler): void; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index aa8db52b729..d9da66125a8 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -4078,6 +4078,7 @@ export class InteractiveMode { } private showModelSelector(initialSearchInput?: string): void { + void this.session.extensionRunner.emit({ type: "model_selector_open" }); this.showSelector((done) => { const selector = new ModelSelectorComponent( this.ui,