From bbc180a3b6fb9b12a0f53174433103957c388c68 Mon Sep 17 00:00:00 2001 From: frizynn Date: Thu, 19 Mar 2026 14:55:00 -0300 Subject: [PATCH] refactor: consolidate extension type guards and inline handler type aliases Replace 7 individual ToolResultEvent type guards (isBashToolResult, isReadToolResult, etc.) with a unified isToolResultEventType() function, mirroring the existing isToolCallEventType() pattern. Inline 14 handler type aliases (SendMessageHandler, SetModelHandler, etc.) directly into the ExtensionActions interface since they were only used there and added no semantic value. Update documentation examples to use the new unified guard. --- .../07-events-the-nervous-system.md | 4 +- .../src/core/extensions/index.ts | 22 +--- .../src/core/extensions/types.ts | 119 ++++++++---------- packages/pi-coding-agent/src/index.ts | 8 +- .../references/events-reference.md | 8 +- 5 files changed, 58 insertions(+), 103 deletions(-) diff --git a/docs/extending-pi/07-events-the-nervous-system.md b/docs/extending-pi/07-events-the-nervous-system.md index 5b991106c..e2380231b 100644 --- a/docs/extending-pi/07-events-the-nervous-system.md +++ b/docs/extending-pi/07-events-the-nervous-system.md @@ -72,7 +72,7 @@ pi.on("event_name", async (event, ctx: ExtensionContext) => { ### Type Narrowing for Tool Events ```typescript -import { isToolCallEventType, isBashToolResult } from "@mariozechner/pi-coding-agent"; +import { isToolCallEventType, isToolResultEventType } from "@mariozechner/pi-coding-agent"; pi.on("tool_call", async (event, ctx) => { if (isToolCallEventType("bash", event)) { @@ -84,7 +84,7 @@ pi.on("tool_call", async (event, ctx) => { }); pi.on("tool_result", async (event, ctx) => { - if (isBashToolResult(event)) { + if (isToolResultEventType("bash", event)) { // event.details is typed as BashToolDetails } }); diff --git a/packages/pi-coding-agent/src/core/extensions/index.ts b/packages/pi-coding-agent/src/core/extensions/index.ts index 12dc60ed4..0c86d2d72 100644 --- a/packages/pi-coding-agent/src/core/extensions/index.ts +++ b/packages/pi-coding-agent/src/core/extensions/index.ts @@ -30,7 +30,6 @@ export type { AgentToolUpdateCallback, // App keybindings (for custom editors) AppAction, - AppendEntryHandler, // Events - Tool (ToolCallEvent types) BashToolCallEvent, BashToolResultEvent, @@ -73,10 +72,6 @@ export type { ExtensionWidgetOptions, FindToolCallEvent, FindToolResultEvent, - GetActiveToolsHandler, - GetAllToolsHandler, - GetCommandsHandler, - GetThinkingLevelHandler, GrepToolCallEvent, GrepToolResultEvent, // Events - Input @@ -107,8 +102,6 @@ export type { // Events - Resources ResourcesDiscoverEvent, ResourcesDiscoverResult, - SendMessageHandler, - SendUserMessageHandler, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeForkEvent, @@ -128,10 +121,6 @@ export type { SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, - SetActiveToolsHandler, - SetLabelHandler, - SetModelHandler, - SetThinkingLevelHandler, TerminalInputHandler, // Events - Tool ToolCallEvent, @@ -157,16 +146,7 @@ export type { WriteToolResultEvent, } from "./types.js"; // Type guards -export { - isBashToolResult, - isEditToolResult, - isFindToolResult, - isGrepToolResult, - isLsToolResult, - isReadToolResult, - isToolCallEventType, - isWriteToolResult, -} from "./types.js"; +export { isToolCallEventType, isToolResultEventType } from "./types.js"; export { wrapRegisteredTool, wrapRegisteredTools, diff --git a/packages/pi-coding-agent/src/core/extensions/types.ts b/packages/pi-coding-agent/src/core/extensions/types.ts index bd78388c7..22b05a1a6 100644 --- a/packages/pi-coding-agent/src/core/extensions/types.ts +++ b/packages/pi-coding-agent/src/core/extensions/types.ts @@ -761,27 +761,36 @@ export type ToolResultEvent = | LsToolResultEvent | CustomToolResultEvent; -// Type guards for ToolResultEvent -export function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent { - return e.toolName === "bash"; -} -export function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent { - return e.toolName === "read"; -} -export function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent { - return e.toolName === "edit"; -} -export function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent { - return e.toolName === "write"; -} -export function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent { - return e.toolName === "grep"; -} -export function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent { - return e.toolName === "find"; -} -export function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent { - return e.toolName === "ls"; +/** + * Type guard for narrowing ToolResultEvent by tool name. + * + * Built-in tools narrow automatically (no type params needed): + * ```ts + * if (isToolResultEventType("bash", event)) { + * event.details; // BashToolDetails | undefined + * } + * ``` + * + * Custom tools require explicit type parameters: + * ```ts + * if (isToolResultEventType<"my_tool", MyDetails>("my_tool", event)) { + * event.details; // typed + * } + * ``` + */ +export function isToolResultEventType(toolName: "bash", event: ToolResultEvent): event is BashToolResultEvent; +export function isToolResultEventType(toolName: "read", event: ToolResultEvent): event is ReadToolResultEvent; +export function isToolResultEventType(toolName: "edit", event: ToolResultEvent): event is EditToolResultEvent; +export function isToolResultEventType(toolName: "write", event: ToolResultEvent): event is WriteToolResultEvent; +export function isToolResultEventType(toolName: "grep", event: ToolResultEvent): event is GrepToolResultEvent; +export function isToolResultEventType(toolName: "find", event: ToolResultEvent): event is FindToolResultEvent; +export function isToolResultEventType(toolName: "ls", event: ToolResultEvent): event is LsToolResultEvent; +export function isToolResultEventType( + toolName: TName, + event: ToolResultEvent, +): event is ToolResultEvent & { toolName: TName; details: TDetails }; +export function isToolResultEventType(toolName: string, event: ToolResultEvent): boolean { + return event.toolName === toolName; } /** @@ -1274,43 +1283,9 @@ export interface ExtensionShortcut { type HandlerFn = (...args: unknown[]) => Promise; -export type SendMessageHandler = ( - message: Pick, "customType" | "content" | "display" | "details">, - options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }, -) => void; - -export type SendUserMessageHandler = ( - content: string | (TextContent | ImageContent)[], - options?: { deliverAs?: "steer" | "followUp" }, -) => void; - -export type AppendEntryHandler = (customType: string, data?: T) => void; - -export type SetSessionNameHandler = (name: string) => void; - -export type GetSessionNameHandler = () => string | undefined; - -export type GetActiveToolsHandler = () => string[]; - /** Tool info with name, description, and parameter schema */ export type ToolInfo = Pick; -export type GetAllToolsHandler = () => ToolInfo[]; - -export type GetCommandsHandler = () => SlashCommandInfo[]; - -export type SetActiveToolsHandler = (toolNames: string[]) => void; - -export type RefreshToolsHandler = () => void; - -export type SetModelHandler = (model: Model, options?: { persist?: boolean }) => Promise; - -export type GetThinkingLevelHandler = () => ThinkingLevel; - -export type SetThinkingLevelHandler = (level: ThinkingLevel) => void; - -export type SetLabelHandler = (entryId: string, label: string | undefined) => void; - /** * Shared state created by loader, used during registration and runtime. * Contains flag values (defaults set during registration, CLI values set after). @@ -1334,21 +1309,27 @@ export interface ExtensionRuntimeState { * Provided to runner.initialize(), copied into the shared runtime. */ export interface ExtensionActions { - sendMessage: SendMessageHandler; - sendUserMessage: SendUserMessageHandler; + sendMessage: ( + message: Pick, "customType" | "content" | "display" | "details">, + options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }, + ) => void; + sendUserMessage: ( + content: string | (TextContent | ImageContent)[], + options?: { deliverAs?: "steer" | "followUp" }, + ) => void; retryLastTurn: () => void; - appendEntry: AppendEntryHandler; - setSessionName: SetSessionNameHandler; - getSessionName: GetSessionNameHandler; - setLabel: SetLabelHandler; - getActiveTools: GetActiveToolsHandler; - getAllTools: GetAllToolsHandler; - setActiveTools: SetActiveToolsHandler; - refreshTools: RefreshToolsHandler; - getCommands: GetCommandsHandler; - setModel: SetModelHandler; - getThinkingLevel: GetThinkingLevelHandler; - setThinkingLevel: SetThinkingLevelHandler; + appendEntry: (customType: string, data?: T) => void; + setSessionName: (name: string) => void; + getSessionName: () => string | undefined; + setLabel: (entryId: string, label: string | undefined) => void; + getActiveTools: () => string[]; + getAllTools: () => ToolInfo[]; + setActiveTools: (toolNames: string[]) => void; + refreshTools: () => void; + getCommands: () => SlashCommandInfo[]; + setModel: (model: Model, options?: { persist?: boolean }) => Promise; + getThinkingLevel: () => ThinkingLevel; + setThinkingLevel: (level: ThinkingLevel) => void; } /** diff --git a/packages/pi-coding-agent/src/index.ts b/packages/pi-coding-agent/src/index.ts index 1e947d95c..79df03296 100644 --- a/packages/pi-coding-agent/src/index.ts +++ b/packages/pi-coding-agent/src/index.ts @@ -128,14 +128,8 @@ export { discoverAndLoadExtensions, ExtensionRunner, importExtensionModule, - isBashToolResult, - isEditToolResult, - isFindToolResult, - isGrepToolResult, - isLsToolResult, - isReadToolResult, isToolCallEventType, - isWriteToolResult, + isToolResultEventType, wrapRegisteredTool, wrapRegisteredTools, wrapToolsWithExtensions, diff --git a/src/resources/skills/create-gsd-extension/references/events-reference.md b/src/resources/skills/create-gsd-extension/references/events-reference.md index 82028f5de..593eeaf10 100644 --- a/src/resources/skills/create-gsd-extension/references/events-reference.md +++ b/src/resources/skills/create-gsd-extension/references/events-reference.md @@ -61,10 +61,10 @@ pi.on("tool_call", async (event, ctx) => { **tool_result** — Fired after tool executes. Can modify result. Handlers chain like middleware. ```typescript -import { isBashToolResult } from "@mariozechner/pi-coding-agent"; +import { isToolResultEventType } from "@mariozechner/pi-coding-agent"; pi.on("tool_result", async (event, ctx) => { - if (isBashToolResult(event)) { + if (isToolResultEventType("bash", event)) { // event.details is typed as BashToolDetails } // Return partial patch: { content, details, isError } @@ -105,7 +105,7 @@ pi.on("model_select", async (event, ctx) => { Built-in type guards for tool events: ```typescript -import { isToolCallEventType, isBashToolResult } from "@mariozechner/pi-coding-agent"; +import { isToolCallEventType, isToolResultEventType } from "@mariozechner/pi-coding-agent"; // Tool calls — narrows event.input type if (isToolCallEventType("bash", event)) { /* event.input: { command, timeout? } */ } @@ -114,7 +114,7 @@ if (isToolCallEventType("write", event)) { /* event.input: { path, content } */ if (isToolCallEventType("edit", event)) { /* event.input: { path, oldText, newText } */ } // Tool results — narrows event.details type -if (isBashToolResult(event)) { /* event.details: BashToolDetails */ } +if (isToolResultEventType("bash", event)) { /* event.details: BashToolDetails */ } ``` For custom tools, export your input type and use explicit type params: