feat(extensions): forward message and tool execution events to extensions (#1375)

The extension system currently only forwards agent_start, agent_end,
turn_start, and turn_end events. This means extensions cannot access
streaming text (token-by-token), message lifecycle, or tool execution
progress — all of which are available to internal subscribers.

This adds forwarding for the remaining 6 agent event types:
- message_start, message_update, message_end
- tool_execution_start, tool_execution_update, tool_execution_end

These follow the exact same pattern as the existing forwarded events:
new interfaces in types.ts, exports in index.ts, and else-if blocks
in _emitExtensionEvent(). The new types are included in ExtensionEvent
and automatically flow through RunnerEmitEvent (they're not in the
exclusion list).

This enables extensions to build real-time UIs, streaming WebSocket
bridges, and other integrations that need fine-grained event access.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
This commit is contained in:
Sumeet Agarwal 2026-02-12 11:30:46 -08:00 committed by GitHub
parent 4793f7c92d
commit ff5148e7cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 117 additions and 0 deletions

View file

@ -50,12 +50,18 @@ import {
ExtensionRunner,
type ExtensionUIContext,
type InputSource,
type MessageEndEvent,
type MessageStartEvent,
type MessageUpdateEvent,
type SessionBeforeCompactResult,
type SessionBeforeForkResult,
type SessionBeforeSwitchResult,
type SessionBeforeTreeResult,
type ShutdownHandler,
type ToolDefinition,
type ToolExecutionEndEvent,
type ToolExecutionStartEvent,
type ToolExecutionUpdateEvent,
type ToolInfo,
type TreePreparation,
type TurnEndEvent,
@ -444,6 +450,51 @@ export class AgentSession {
};
await this._extensionRunner.emit(extensionEvent);
this._turnIndex++;
} else if (event.type === "message_start") {
const extensionEvent: MessageStartEvent = {
type: "message_start",
message: event.message,
};
await this._extensionRunner.emit(extensionEvent);
} else if (event.type === "message_update") {
const extensionEvent: MessageUpdateEvent = {
type: "message_update",
message: event.message,
assistantMessageEvent: event.assistantMessageEvent,
};
await this._extensionRunner.emit(extensionEvent);
} else if (event.type === "message_end") {
const extensionEvent: MessageEndEvent = {
type: "message_end",
message: event.message,
};
await this._extensionRunner.emit(extensionEvent);
} else if (event.type === "tool_execution_start") {
const extensionEvent: ToolExecutionStartEvent = {
type: "tool_execution_start",
toolCallId: event.toolCallId,
toolName: event.toolName,
args: event.args,
};
await this._extensionRunner.emit(extensionEvent);
} else if (event.type === "tool_execution_update") {
const extensionEvent: ToolExecutionUpdateEvent = {
type: "tool_execution_update",
toolCallId: event.toolCallId,
toolName: event.toolName,
args: event.args,
partialResult: event.partialResult,
};
await this._extensionRunner.emit(extensionEvent);
} else if (event.type === "tool_execution_end") {
const extensionEvent: ToolExecutionEndEvent = {
type: "tool_execution_end",
toolCallId: event.toolCallId,
toolName: event.toolName,
result: event.result,
isError: event.isError,
};
await this._extensionRunner.emit(extensionEvent);
}
}

View file

@ -81,9 +81,13 @@ export type {
LoadExtensionsResult,
LsToolCallEvent,
LsToolResultEvent,
// Events - Message
MessageEndEvent,
// Message Rendering
MessageRenderer,
MessageRenderOptions,
MessageStartEvent,
MessageUpdateEvent,
ModelSelectEvent,
ModelSelectSource,
// Provider Registration
@ -124,6 +128,10 @@ export type {
ToolCallEventResult,
// Tools
ToolDefinition,
// Events - Tool Execution
ToolExecutionEndEvent,
ToolExecutionStartEvent,
ToolExecutionUpdateEvent,
ToolInfo,
ToolRenderResultOptions,
ToolResultEvent,

View file

@ -16,6 +16,7 @@ import type {
} from "@mariozechner/pi-agent-core";
import type {
Api,
AssistantMessageEvent,
AssistantMessageEventStream,
Context,
ImageContent,
@ -512,6 +513,51 @@ export interface TurnEndEvent {
toolResults: ToolResultMessage[];
}
/** Fired when a message starts (user, assistant, or toolResult) */
export interface MessageStartEvent {
type: "message_start";
message: AgentMessage;
}
/** Fired during assistant message streaming with token-by-token updates */
export interface MessageUpdateEvent {
type: "message_update";
message: AgentMessage;
assistantMessageEvent: AssistantMessageEvent;
}
/** Fired when a message ends */
export interface MessageEndEvent {
type: "message_end";
message: AgentMessage;
}
/** Fired when a tool starts executing */
export interface ToolExecutionStartEvent {
type: "tool_execution_start";
toolCallId: string;
toolName: string;
args: any;
}
/** Fired during tool execution with partial/streaming output */
export interface ToolExecutionUpdateEvent {
type: "tool_execution_update";
toolCallId: string;
toolName: string;
args: any;
partialResult: any;
}
/** Fired when a tool finishes executing */
export interface ToolExecutionEndEvent {
type: "tool_execution_end";
toolCallId: string;
toolName: string;
result: any;
isError: boolean;
}
// ============================================================================
// Model Events
// ============================================================================
@ -752,6 +798,12 @@ export type ExtensionEvent =
| AgentEndEvent
| TurnStartEvent
| TurnEndEvent
| MessageStartEvent
| MessageUpdateEvent
| MessageEndEvent
| ToolExecutionStartEvent
| ToolExecutionUpdateEvent
| ToolExecutionEndEvent
| ModelSelectEvent
| UserBashEvent
| InputEvent
@ -883,6 +935,12 @@ export interface ExtensionAPI {
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
on(event: "message_start", handler: ExtensionHandler<MessageStartEvent>): void;
on(event: "message_update", handler: ExtensionHandler<MessageUpdateEvent>): void;
on(event: "message_end", handler: ExtensionHandler<MessageEndEvent>): void;
on(event: "tool_execution_start", handler: ExtensionHandler<ToolExecutionStartEvent>): void;
on(event: "tool_execution_update", handler: ExtensionHandler<ToolExecutionUpdateEvent>): void;
on(event: "tool_execution_end", handler: ExtensionHandler<ToolExecutionEndEvent>): void;
on(event: "model_select", handler: ExtensionHandler<ModelSelectEvent>): void;
on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): void;
on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): void;