mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 05:00:16 +00:00
Merge pull request #315 from mitsuhiko/model-switcher
Reverse model switching and binding for dialog
This commit is contained in:
commit
4edfff41a7
5 changed files with 57 additions and 12 deletions
|
|
@ -580,18 +580,19 @@ export class AgentSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle to next model.
|
* Cycle to next/previous model.
|
||||||
* Uses scoped models (from --models flag) if available, otherwise all available models.
|
* Uses scoped models (from --models flag) if available, otherwise all available models.
|
||||||
|
* @param direction - "forward" (default) or "backward"
|
||||||
* @returns The new model info, or null if only one model available
|
* @returns The new model info, or null if only one model available
|
||||||
*/
|
*/
|
||||||
async cycleModel(): Promise<ModelCycleResult | null> {
|
async cycleModel(direction: "forward" | "backward" = "forward"): Promise<ModelCycleResult | null> {
|
||||||
if (this._scopedModels.length > 0) {
|
if (this._scopedModels.length > 0) {
|
||||||
return this._cycleScopedModel();
|
return this._cycleScopedModel(direction);
|
||||||
}
|
}
|
||||||
return this._cycleAvailableModel();
|
return this._cycleAvailableModel(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _cycleScopedModel(): Promise<ModelCycleResult | null> {
|
private async _cycleScopedModel(direction: "forward" | "backward"): Promise<ModelCycleResult | null> {
|
||||||
if (this._scopedModels.length <= 1) return null;
|
if (this._scopedModels.length <= 1) return null;
|
||||||
|
|
||||||
const currentModel = this.model;
|
const currentModel = this.model;
|
||||||
|
|
@ -600,7 +601,8 @@ export class AgentSession {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentIndex === -1) currentIndex = 0;
|
if (currentIndex === -1) currentIndex = 0;
|
||||||
const nextIndex = (currentIndex + 1) % this._scopedModels.length;
|
const len = this._scopedModels.length;
|
||||||
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
||||||
const next = this._scopedModels[nextIndex];
|
const next = this._scopedModels[nextIndex];
|
||||||
|
|
||||||
// Validate API key
|
// Validate API key
|
||||||
|
|
@ -620,7 +622,7 @@ export class AgentSession {
|
||||||
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _cycleAvailableModel(): Promise<ModelCycleResult | null> {
|
private async _cycleAvailableModel(direction: "forward" | "backward"): Promise<ModelCycleResult | null> {
|
||||||
const availableModels = await this._modelRegistry.getAvailable();
|
const availableModels = await this._modelRegistry.getAvailable();
|
||||||
if (availableModels.length <= 1) return null;
|
if (availableModels.length <= 1) return null;
|
||||||
|
|
||||||
|
|
@ -630,7 +632,8 @@ export class AgentSession {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentIndex === -1) currentIndex = 0;
|
if (currentIndex === -1) currentIndex = 0;
|
||||||
const nextIndex = (currentIndex + 1) % availableModels.length;
|
const len = availableModels.length;
|
||||||
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
||||||
const nextModel = availableModels[nextIndex];
|
const nextModel = availableModels[nextIndex];
|
||||||
|
|
||||||
const apiKey = await this._modelRegistry.getApiKey(nextModel);
|
const apiKey = await this._modelRegistry.getApiKey(nextModel);
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import {
|
||||||
isCtrlC,
|
isCtrlC,
|
||||||
isCtrlD,
|
isCtrlD,
|
||||||
isCtrlG,
|
isCtrlG,
|
||||||
|
isCtrlL,
|
||||||
isCtrlO,
|
isCtrlO,
|
||||||
isCtrlP,
|
isCtrlP,
|
||||||
isCtrlT,
|
isCtrlT,
|
||||||
isCtrlZ,
|
isCtrlZ,
|
||||||
isEscape,
|
isEscape,
|
||||||
|
isShiftCtrlP,
|
||||||
isShiftTab,
|
isShiftTab,
|
||||||
} from "@mariozechner/pi-tui";
|
} from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
|
|
@ -20,6 +22,8 @@ export class CustomEditor extends Editor {
|
||||||
public onCtrlD?: () => void;
|
public onCtrlD?: () => void;
|
||||||
public onShiftTab?: () => void;
|
public onShiftTab?: () => void;
|
||||||
public onCtrlP?: () => void;
|
public onCtrlP?: () => void;
|
||||||
|
public onShiftCtrlP?: () => void;
|
||||||
|
public onCtrlL?: () => void;
|
||||||
public onCtrlO?: () => void;
|
public onCtrlO?: () => void;
|
||||||
public onCtrlT?: () => void;
|
public onCtrlT?: () => void;
|
||||||
public onCtrlG?: () => void;
|
public onCtrlG?: () => void;
|
||||||
|
|
@ -44,12 +48,24 @@ export class CustomEditor extends Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intercept Ctrl+L for model selector
|
||||||
|
if (isCtrlL(data) && this.onCtrlL) {
|
||||||
|
this.onCtrlL();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Intercept Ctrl+O for tool output expansion
|
// Intercept Ctrl+O for tool output expansion
|
||||||
if (isCtrlO(data) && this.onCtrlO) {
|
if (isCtrlO(data) && this.onCtrlO) {
|
||||||
this.onCtrlO();
|
this.onCtrlO();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intercept Shift+Ctrl+P for backward model cycling (check before Ctrl+P)
|
||||||
|
if (isShiftCtrlP(data) && this.onShiftCtrlP) {
|
||||||
|
this.onShiftCtrlP();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Intercept Ctrl+P for model cycling
|
// Intercept Ctrl+P for model cycling
|
||||||
if (isCtrlP(data) && this.onCtrlP) {
|
if (isCtrlP(data) && this.onCtrlP) {
|
||||||
this.onCtrlP();
|
this.onCtrlP();
|
||||||
|
|
|
||||||
|
|
@ -213,9 +213,12 @@ export class InteractiveMode {
|
||||||
theme.fg("dim", "shift+tab") +
|
theme.fg("dim", "shift+tab") +
|
||||||
theme.fg("muted", " to cycle thinking") +
|
theme.fg("muted", " to cycle thinking") +
|
||||||
"\n" +
|
"\n" +
|
||||||
theme.fg("dim", "ctrl+p") +
|
theme.fg("dim", "ctrl+p/shift+ctrl+p") +
|
||||||
theme.fg("muted", " to cycle models") +
|
theme.fg("muted", " to cycle models") +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
theme.fg("dim", "ctrl+l") +
|
||||||
|
theme.fg("muted", " to select model") +
|
||||||
|
"\n" +
|
||||||
theme.fg("dim", "ctrl+o") +
|
theme.fg("dim", "ctrl+o") +
|
||||||
theme.fg("muted", " to expand tools") +
|
theme.fg("muted", " to expand tools") +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
|
@ -580,7 +583,9 @@ export class InteractiveMode {
|
||||||
this.editor.onCtrlD = () => this.handleCtrlD();
|
this.editor.onCtrlD = () => this.handleCtrlD();
|
||||||
this.editor.onCtrlZ = () => this.handleCtrlZ();
|
this.editor.onCtrlZ = () => this.handleCtrlZ();
|
||||||
this.editor.onShiftTab = () => this.cycleThinkingLevel();
|
this.editor.onShiftTab = () => this.cycleThinkingLevel();
|
||||||
this.editor.onCtrlP = () => this.cycleModel();
|
this.editor.onCtrlP = () => this.cycleModel("forward");
|
||||||
|
this.editor.onShiftCtrlP = () => this.cycleModel("backward");
|
||||||
|
this.editor.onCtrlL = () => this.showModelSelector();
|
||||||
this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
|
this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
|
||||||
this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
|
this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
|
||||||
this.editor.onCtrlG = () => this.openExternalEditor();
|
this.editor.onCtrlG = () => this.openExternalEditor();
|
||||||
|
|
@ -1200,9 +1205,9 @@ export class InteractiveMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cycleModel(): Promise<void> {
|
private async cycleModel(direction: "forward" | "backward"): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const result = await this.session.cycleModel();
|
const result = await this.session.cycleModel(direction);
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
const msg = this.session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available";
|
const msg = this.session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available";
|
||||||
this.showStatus(msg);
|
this.showStatus(msg);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export {
|
||||||
isCtrlE,
|
isCtrlE,
|
||||||
isCtrlG,
|
isCtrlG,
|
||||||
isCtrlK,
|
isCtrlK,
|
||||||
|
isCtrlL,
|
||||||
isCtrlLeft,
|
isCtrlLeft,
|
||||||
isCtrlO,
|
isCtrlO,
|
||||||
isCtrlP,
|
isCtrlP,
|
||||||
|
|
@ -49,6 +50,7 @@ export {
|
||||||
isEnter,
|
isEnter,
|
||||||
isEscape,
|
isEscape,
|
||||||
isHome,
|
isHome,
|
||||||
|
isShiftCtrlP,
|
||||||
isShiftEnter,
|
isShiftEnter,
|
||||||
isShiftTab,
|
isShiftTab,
|
||||||
isTab,
|
isTab,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ const CODEPOINTS = {
|
||||||
e: 101,
|
e: 101,
|
||||||
g: 103,
|
g: 103,
|
||||||
k: 107,
|
k: 107,
|
||||||
|
l: 108,
|
||||||
o: 111,
|
o: 111,
|
||||||
p: 112,
|
p: 112,
|
||||||
t: 116,
|
t: 116,
|
||||||
|
|
@ -164,6 +165,7 @@ export const Keys = {
|
||||||
CTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),
|
CTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),
|
||||||
CTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),
|
CTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),
|
||||||
CTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),
|
CTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),
|
||||||
|
CTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),
|
||||||
CTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),
|
CTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),
|
||||||
CTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),
|
CTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),
|
||||||
CTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),
|
CTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),
|
||||||
|
|
@ -220,6 +222,7 @@ const RAW = {
|
||||||
CTRL_E: "\x05",
|
CTRL_E: "\x05",
|
||||||
CTRL_G: "\x07",
|
CTRL_G: "\x07",
|
||||||
CTRL_K: "\x0b",
|
CTRL_K: "\x0b",
|
||||||
|
CTRL_L: "\x0c",
|
||||||
CTRL_O: "\x0f",
|
CTRL_O: "\x0f",
|
||||||
CTRL_P: "\x10",
|
CTRL_P: "\x10",
|
||||||
CTRL_T: "\x14",
|
CTRL_T: "\x14",
|
||||||
|
|
@ -285,6 +288,14 @@ export function isCtrlK(data: string): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if input matches Ctrl+L (raw byte or Kitty protocol).
|
||||||
|
* Ignores lock key bits.
|
||||||
|
*/
|
||||||
|
export function isCtrlL(data: string): boolean {
|
||||||
|
return data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if input matches Ctrl+O (raw byte or Kitty protocol).
|
* Check if input matches Ctrl+O (raw byte or Kitty protocol).
|
||||||
* Ignores lock key bits.
|
* Ignores lock key bits.
|
||||||
|
|
@ -301,6 +312,14 @@ export function isCtrlP(data: string): boolean {
|
||||||
return data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);
|
return data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if input matches Shift+Ctrl+P (Kitty protocol only).
|
||||||
|
* Ignores lock key bits.
|
||||||
|
*/
|
||||||
|
export function isShiftCtrlP(data: string): boolean {
|
||||||
|
return matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if input matches Ctrl+T (raw byte or Kitty protocol).
|
* Check if input matches Ctrl+T (raw byte or Kitty protocol).
|
||||||
* Ignores lock key bits.
|
* Ignores lock key bits.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue