From ac5ab3d98ddd330063e2fe80df453d93a47729d3 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sun, 28 Dec 2025 16:54:43 +0100 Subject: [PATCH] Remove allowDuringStreaming flag - commands always run immediately Hook commands now always execute immediately, even during streaming. If a command needs to interact with the LLM, it uses pi.sendMessage() which handles queueing automatically. This simplifies the API and eliminates the issue of queued slash commands being sent to the LLM instead of executing. --- .pi/hooks/test-command.ts | 8 -- packages/ai/src/models.generated.ts | 114 +++++++++--------- packages/coding-agent/examples/hooks/snake.ts | 2 +- packages/coding-agent/src/core/hooks/types.ts | 10 +- .../src/modes/interactive/interactive-mode.ts | 10 +- 5 files changed, 66 insertions(+), 78 deletions(-) diff --git a/.pi/hooks/test-command.ts b/.pi/hooks/test-command.ts index a9bdc16ea..52382d858 100644 --- a/.pi/hooks/test-command.ts +++ b/.pi/hooks/test-command.ts @@ -29,14 +29,6 @@ export default function (pi: HookAPI) { return box; }); - pi.registerCommand("no-stream", { - description: "Send a message without streaming", - handler: async (ctx) => { - ctx.ui.notify("Sending message after streaming is done..."); - }, - allowDuringStreaming: true, - }) - // Register /test-msg command pi.registerCommand("test-msg", { description: "Send a test custom message", diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 6392308c7..2df269d3c 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -6104,9 +6104,9 @@ export const MODELS = { contextWindow: 32768, maxTokens: 4096, } satisfies Model<"openai-completions">, - "anthropic/claude-3.5-haiku": { - id: "anthropic/claude-3.5-haiku", - name: "Anthropic: Claude 3.5 Haiku", + "anthropic/claude-3.5-haiku-20241022": { + id: "anthropic/claude-3.5-haiku-20241022", + name: "Anthropic: Claude 3.5 Haiku (2024-10-22)", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", @@ -6121,9 +6121,9 @@ export const MODELS = { contextWindow: 200000, maxTokens: 8192, } satisfies Model<"openai-completions">, - "anthropic/claude-3.5-haiku-20241022": { - id: "anthropic/claude-3.5-haiku-20241022", - name: "Anthropic: Claude 3.5 Haiku (2024-10-22)", + "anthropic/claude-3.5-haiku": { + id: "anthropic/claude-3.5-haiku", + name: "Anthropic: Claude 3.5 Haiku", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", @@ -6359,6 +6359,23 @@ export const MODELS = { contextWindow: 128000, maxTokens: 16384, } satisfies Model<"openai-completions">, + "meta-llama/llama-3.1-8b-instruct": { + id: "meta-llama/llama-3.1-8b-instruct", + name: "Meta: Llama 3.1 8B Instruct", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text"], + cost: { + input: 0.02, + output: 0.03, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 16384, + } satisfies Model<"openai-completions">, "meta-llama/llama-3.1-405b-instruct": { id: "meta-llama/llama-3.1-405b-instruct", name: "Meta: Llama 3.1 405B Instruct", @@ -6393,23 +6410,6 @@ export const MODELS = { contextWindow: 131072, maxTokens: 4096, } satisfies Model<"openai-completions">, - "meta-llama/llama-3.1-8b-instruct": { - id: "meta-llama/llama-3.1-8b-instruct", - name: "Meta: Llama 3.1 8B Instruct", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text"], - cost: { - input: 0.02, - output: 0.03, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 131072, - maxTokens: 16384, - } satisfies Model<"openai-completions">, "mistralai/mistral-nemo": { id: "mistralai/mistral-nemo", name: "Mistral: Mistral Nemo", @@ -6546,6 +6546,23 @@ export const MODELS = { contextWindow: 128000, maxTokens: 4096, } satisfies Model<"openai-completions">, + "openai/gpt-4o-2024-05-13": { + id: "openai/gpt-4o-2024-05-13", + name: "OpenAI: GPT-4o (2024-05-13)", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text", "image"], + cost: { + input: 5, + output: 15, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 4096, + } satisfies Model<"openai-completions">, "openai/gpt-4o": { id: "openai/gpt-4o", name: "OpenAI: GPT-4o", @@ -6580,23 +6597,6 @@ export const MODELS = { contextWindow: 128000, maxTokens: 64000, } satisfies Model<"openai-completions">, - "openai/gpt-4o-2024-05-13": { - id: "openai/gpt-4o-2024-05-13", - name: "OpenAI: GPT-4o (2024-05-13)", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text", "image"], - cost: { - input: 5, - output: 15, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 128000, - maxTokens: 4096, - } satisfies Model<"openai-completions">, "meta-llama/llama-3-70b-instruct": { id: "meta-llama/llama-3-70b-instruct", name: "Meta: Llama 3 70B Instruct", @@ -6835,23 +6835,6 @@ export const MODELS = { contextWindow: 8191, maxTokens: 4096, } satisfies Model<"openai-completions">, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "OpenAI: GPT-3.5 Turbo", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text"], - cost: { - input: 0.5, - output: 1.5, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 16385, - maxTokens: 4096, - } satisfies Model<"openai-completions">, "openai/gpt-4": { id: "openai/gpt-4", name: "OpenAI: GPT-4", @@ -6869,6 +6852,23 @@ export const MODELS = { contextWindow: 8191, maxTokens: 4096, } satisfies Model<"openai-completions">, + "openai/gpt-3.5-turbo": { + id: "openai/gpt-3.5-turbo", + name: "OpenAI: GPT-3.5 Turbo", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text"], + cost: { + input: 0.5, + output: 1.5, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 16385, + maxTokens: 4096, + } satisfies Model<"openai-completions">, "openrouter/auto": { id: "openrouter/auto", name: "OpenRouter: Auto Router", diff --git a/packages/coding-agent/examples/hooks/snake.ts b/packages/coding-agent/examples/hooks/snake.ts index ea7031da8..3b5ffe8d5 100644 --- a/packages/coding-agent/examples/hooks/snake.ts +++ b/packages/coding-agent/examples/hooks/snake.ts @@ -309,7 +309,7 @@ const SNAKE_SAVE_TYPE = "snake-save"; export default function (pi: HookAPI) { pi.registerCommand("snake", { description: "Play Snake!", - allowDuringStreaming: true, // Run even during streaming, not queued + handler: async (ctx) => { if (!ctx.hasUI) { ctx.ui.notify("Snake requires interactive mode", "error"); diff --git a/packages/coding-agent/src/core/hooks/types.ts b/packages/coding-agent/src/core/hooks/types.ts index 45943521c..fa753b7ec 100644 --- a/packages/coding-agent/src/core/hooks/types.ts +++ b/packages/coding-agent/src/core/hooks/types.ts @@ -442,8 +442,7 @@ export interface HookCommandContext { export interface RegisteredCommand { name: string; description?: string; - /** If true, command runs during streaming instead of being queued */ - allowDuringStreaming?: boolean; + handler: (ctx: HookCommandContext) => Promise; } @@ -456,9 +455,9 @@ export interface HookAPI { on(event: "session", handler: HookHandler): void; // biome-ignore lint/suspicious/noConfusingVoidType: void allows handlers to not return anything on(event: "context", handler: HookHandler): void; - // biome-ignore lint/suspicious/noConfusingVoidType: void allows handlers to not return anything on( event: "before_agent_start", + // biome-ignore lint/suspicious/noConfusingVoidType: void allows handlers to not return anything handler: HookHandler, ): void; on(event: "agent_start", handler: HookHandler): void; @@ -527,10 +526,7 @@ export interface HookAPI { * Register a custom slash command. * Handler receives HookCommandContext. */ - registerCommand( - name: string, - options: { description?: string; allowDuringStreaming?: boolean; handler: RegisteredCommand["handler"] }, - ): void; + registerCommand(name: string, options: { description?: string; handler: RegisteredCommand["handler"] }): void; /** * Execute a shell command and return stdout/stderr/code. diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 68681cb96..d1e12f6cd 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -744,13 +744,13 @@ export class InteractiveMode { return; } - // Check if this hook command can run during streaming (not queued) - if (text.startsWith("/") && this.session.hookRunner && this.session.isStreaming) { + // Hook commands always run immediately, even during streaming + // (if they need to interact with LLM, they use pi.sendMessage which handles queueing) + if (text.startsWith("/") && this.session.hookRunner) { const spaceIndex = text.indexOf(" "); const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex); const command = this.session.hookRunner.getCommand(commandName); - if (command?.allowDuringStreaming) { - // Execute hook command right away + if (command) { this.editor.addToHistory(text); this.editor.setText(""); await this.session.prompt(text); @@ -758,7 +758,7 @@ export class InteractiveMode { } } - // Queue message if agent is streaming + // Queue regular messages if agent is streaming if (this.session.isStreaming) { await this.session.queueMessage(text); this.updatePendingMessagesDisplay();