mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 18:05:11 +00:00
Merge model-select-hook: add model_select extension hook (#628)
This commit is contained in:
commit
741262d89d
6 changed files with 102 additions and 0 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- `model_select` extension hook fires when model changes via `/model`, model cycling, or session restore with `source` field and `previousModel` ([#628](https://github.com/badlogic/pi-mono/pull/628) by [@marckrenn](https://github.com/marckrenn))
|
||||||
- `ctx.ui.setWorkingMessage()` extension API to customize the "Working..." message during streaming ([#625](https://github.com/badlogic/pi-mono/pull/625) by [@nicobailon](https://github.com/nicobailon))
|
- `ctx.ui.setWorkingMessage()` extension API to customize the "Working..." message during streaming ([#625](https://github.com/badlogic/pi-mono/pull/625) by [@nicobailon](https://github.com/nicobailon))
|
||||||
- Skill slash commands: loaded skills are registered as `/skill:name` commands for quick access. Toggle via `/settings` or `skills.enableSkillCommands` in settings.json. ([#630](https://github.com/badlogic/pi-mono/pull/630) by [@Dwsy](https://github.com/Dwsy))
|
- Skill slash commands: loaded skills are registered as `/skill:name` commands for quick access. Toggle via `/settings` or `skills.enableSkillCommands` in settings.json. ([#630](https://github.com/badlogic/pi-mono/pull/630) by [@Dwsy](https://github.com/Dwsy))
|
||||||
- Slash command autocomplete now uses fuzzy matching (type `/skbra` to match `/skill:brave-search`)
|
- Slash command autocomplete now uses fuzzy matching (type `/skbra` to match `/skill:brave-search`)
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,9 @@ user sends another prompt ◄─────────────────
|
||||||
├─► session_before_tree (can cancel or customize)
|
├─► session_before_tree (can cancel or customize)
|
||||||
└─► session_tree
|
└─► session_tree
|
||||||
|
|
||||||
|
/model or Ctrl+P (model selection/cycling)
|
||||||
|
└─► model_select
|
||||||
|
|
||||||
exit (Ctrl+C, Ctrl+D)
|
exit (Ctrl+C, Ctrl+D)
|
||||||
└─► session_shutdown
|
└─► session_shutdown
|
||||||
```
|
```
|
||||||
|
|
@ -481,6 +484,31 @@ pi.on("context", async (event, ctx) => {
|
||||||
|
|
||||||
**Examples:** [plan-mode.ts](../examples/extensions/plan-mode.ts)
|
**Examples:** [plan-mode.ts](../examples/extensions/plan-mode.ts)
|
||||||
|
|
||||||
|
### Model Events
|
||||||
|
|
||||||
|
#### model_select
|
||||||
|
|
||||||
|
Fired when the model changes via `/model` command, model cycling (`Ctrl+P`), or session restore.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
pi.on("model_select", async (event, ctx) => {
|
||||||
|
// event.model - newly selected model
|
||||||
|
// event.previousModel - previous model (undefined if first selection)
|
||||||
|
// event.source - "set" | "cycle" | "restore"
|
||||||
|
|
||||||
|
const prev = event.previousModel
|
||||||
|
? `${event.previousModel.provider}/${event.previousModel.id}`
|
||||||
|
: "none";
|
||||||
|
const next = `${event.model.provider}/${event.model.id}`;
|
||||||
|
|
||||||
|
ctx.ui.notify(`Model changed (${event.source}): ${prev} -> ${next}`, "info");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this to update UI elements (status bars, footers) or perform model-specific initialization when the active model changes.
|
||||||
|
|
||||||
|
**Examples:** [model-status.ts](../examples/extensions/model-status.ts)
|
||||||
|
|
||||||
### Tool Events
|
### Tool Events
|
||||||
|
|
||||||
#### tool_call
|
#### tool_call
|
||||||
|
|
|
||||||
31
packages/coding-agent/examples/extensions/model-status.ts
Normal file
31
packages/coding-agent/examples/extensions/model-status.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* Model status extension - shows model changes in the status bar.
|
||||||
|
*
|
||||||
|
* Demonstrates the `model_select` hook which fires when the model changes
|
||||||
|
* via /model command, Ctrl+P cycling, or session restore.
|
||||||
|
*
|
||||||
|
* Usage: pi -e ./model-status.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
pi.on("model_select", async (event, ctx) => {
|
||||||
|
const { model, previousModel, source } = event;
|
||||||
|
|
||||||
|
// Format model identifiers
|
||||||
|
const next = `${model.provider}/${model.id}`;
|
||||||
|
const prev = previousModel ? `${previousModel.provider}/${previousModel.id}` : "none";
|
||||||
|
|
||||||
|
// Show notification on change
|
||||||
|
if (source !== "restore") {
|
||||||
|
ctx.ui.notify(`Model: ${next}`, "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status bar with current model
|
||||||
|
ctx.ui.setStatus("model", `🤖 ${model.id}`);
|
||||||
|
|
||||||
|
// Log change details (visible in debug output)
|
||||||
|
console.log(`[model_select] ${prev} → ${next} (${source})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -947,6 +947,21 @@ export class AgentSession {
|
||||||
// Model Management
|
// Model Management
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
|
private async _emitModelSelect(
|
||||||
|
nextModel: Model<any>,
|
||||||
|
previousModel: Model<any> | undefined,
|
||||||
|
source: "set" | "cycle" | "restore",
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this._extensionRunner) return;
|
||||||
|
if (modelsAreEqual(previousModel, nextModel)) return;
|
||||||
|
await this._extensionRunner.emit({
|
||||||
|
type: "model_select",
|
||||||
|
model: nextModel,
|
||||||
|
previousModel,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set model directly.
|
* Set model directly.
|
||||||
* Validates API key, saves to session and settings.
|
* Validates API key, saves to session and settings.
|
||||||
|
|
@ -958,12 +973,15 @@ export class AgentSession {
|
||||||
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previousModel = this.model;
|
||||||
this.agent.setModel(model);
|
this.agent.setModel(model);
|
||||||
this.sessionManager.appendModelChange(model.provider, model.id);
|
this.sessionManager.appendModelChange(model.provider, model.id);
|
||||||
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
||||||
|
|
||||||
// Re-clamp thinking level for new model's capabilities
|
// Re-clamp thinking level for new model's capabilities
|
||||||
this.setThinkingLevel(this.thinkingLevel);
|
this.setThinkingLevel(this.thinkingLevel);
|
||||||
|
|
||||||
|
await this._emitModelSelect(model, previousModel, "set");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1004,6 +1022,8 @@ export class AgentSession {
|
||||||
// Apply thinking level (setThinkingLevel clamps to model capabilities)
|
// Apply thinking level (setThinkingLevel clamps to model capabilities)
|
||||||
this.setThinkingLevel(next.thinkingLevel);
|
this.setThinkingLevel(next.thinkingLevel);
|
||||||
|
|
||||||
|
await this._emitModelSelect(next.model, currentModel, "cycle");
|
||||||
|
|
||||||
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1031,6 +1051,8 @@ export class AgentSession {
|
||||||
// Re-clamp thinking level for new model's capabilities
|
// Re-clamp thinking level for new model's capabilities
|
||||||
this.setThinkingLevel(this.thinkingLevel);
|
this.setThinkingLevel(this.thinkingLevel);
|
||||||
|
|
||||||
|
await this._emitModelSelect(nextModel, currentModel, "cycle");
|
||||||
|
|
||||||
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1783,12 +1805,14 @@ export class AgentSession {
|
||||||
|
|
||||||
// Restore model if saved
|
// Restore model if saved
|
||||||
if (sessionContext.model) {
|
if (sessionContext.model) {
|
||||||
|
const previousModel = this.model;
|
||||||
const availableModels = await this._modelRegistry.getAvailable();
|
const availableModels = await this._modelRegistry.getAvailable();
|
||||||
const match = availableModels.find(
|
const match = availableModels.find(
|
||||||
(m) => m.provider === sessionContext.model!.provider && m.id === sessionContext.model!.modelId,
|
(m) => m.provider === sessionContext.model!.provider && m.id === sessionContext.model!.modelId,
|
||||||
);
|
);
|
||||||
if (match) {
|
if (match) {
|
||||||
this.agent.setModel(match);
|
this.agent.setModel(match);
|
||||||
|
await this._emitModelSelect(match, previousModel, "restore");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ export type {
|
||||||
// Message Rendering
|
// Message Rendering
|
||||||
MessageRenderer,
|
MessageRenderer,
|
||||||
MessageRenderOptions,
|
MessageRenderOptions,
|
||||||
|
ModelSelectEvent,
|
||||||
|
ModelSelectSource,
|
||||||
ReadToolResultEvent,
|
ReadToolResultEvent,
|
||||||
// Commands
|
// Commands
|
||||||
RegisteredCommand,
|
RegisteredCommand,
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,20 @@ export interface TurnEndEvent {
|
||||||
toolResults: ToolResultMessage[];
|
toolResults: ToolResultMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Model Events
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type ModelSelectSource = "set" | "cycle" | "restore";
|
||||||
|
|
||||||
|
/** Fired when a new model is selected */
|
||||||
|
export interface ModelSelectEvent {
|
||||||
|
type: "model_select";
|
||||||
|
model: Model<any>;
|
||||||
|
previousModel: Model<any> | undefined;
|
||||||
|
source: ModelSelectSource;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// User Bash Events
|
// User Bash Events
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -521,6 +535,7 @@ export type ExtensionEvent =
|
||||||
| AgentEndEvent
|
| AgentEndEvent
|
||||||
| TurnStartEvent
|
| TurnStartEvent
|
||||||
| TurnEndEvent
|
| TurnEndEvent
|
||||||
|
| ModelSelectEvent
|
||||||
| UserBashEvent
|
| UserBashEvent
|
||||||
| ToolCallEvent
|
| ToolCallEvent
|
||||||
| ToolResultEvent;
|
| ToolResultEvent;
|
||||||
|
|
@ -645,6 +660,7 @@ export interface ExtensionAPI {
|
||||||
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
|
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
|
||||||
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
||||||
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
|
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
|
||||||
|
on(event: "model_select", handler: ExtensionHandler<ModelSelectEvent>): void;
|
||||||
on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): void;
|
on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): void;
|
||||||
on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): void;
|
on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): void;
|
||||||
on(event: "user_bash", handler: ExtensionHandler<UserBashEvent, UserBashEventResult>): void;
|
on(event: "user_bash", handler: ExtensionHandler<UserBashEvent, UserBashEventResult>): void;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue