From d353e5e2198d2f1b129b1a9d17af6037f90b71a5 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 19 Dec 2025 00:48:03 +0100 Subject: [PATCH] Add type guards for tool_result event narrowing - Export isBashToolResult, isReadToolResult, etc. type guards - Update hooks.md with type guard usage examples - Document custom tool handling in hooks.md --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/docs/hooks.md | 37 ++++++++++++++----- packages/coding-agent/src/core/hooks/index.ts | 9 +++++ packages/coding-agent/src/core/hooks/types.ts | 23 ++++++++++++ packages/coding-agent/src/index.ts | 11 +++++- 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 104e8da5..5962c092 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -11,6 +11,7 @@ - `ToolResultEventResult.result` renamed to `ToolResultEventResult.text` (removed), use `content` instead - Hook handlers returning `{ result: "..." }` must change to `{ content: [{ type: "text", text: "..." }] }` - Built-in tool details types exported: `BashToolDetails`, `ReadToolDetails`, `GrepToolDetails`, `FindToolDetails`, `LsToolDetails`, `TruncationResult` + - Type guards exported for narrowing: `isBashToolResult`, `isReadToolResult`, `isEditToolResult`, `isWriteToolResult`, `isGrepToolResult`, `isFindToolResult`, `isLsToolResult` ### Changed diff --git a/packages/coding-agent/docs/hooks.md b/packages/coding-agent/docs/hooks.md index 6d73ded1..43899cde 100644 --- a/packages/coding-agent/docs/hooks.md +++ b/packages/coding-agent/docs/hooks.md @@ -234,20 +234,26 @@ pi.on("tool_result", async (event, ctx) => { }); ``` -The event type is a discriminated union based on `toolName`. TypeScript will narrow `details` to the correct type: +The event type is a discriminated union based on `toolName`. Use the provided type guards to narrow `details` to the correct type: ```typescript -pi.on("tool_result", async (event, ctx) => { - if (event.toolName === "bash") { - // event.details is BashToolDetails | undefined - if (event.details?.truncation?.truncated) { - // Access full output from temp file - const fullPath = event.details.fullOutputPath; +import { isBashToolResult, type HookAPI } from "@mariozechner/pi-coding-agent/hooks"; + +export default function (pi: HookAPI) { + pi.on("tool_result", async (event, ctx) => { + if (isBashToolResult(event)) { + // event.details is BashToolDetails | undefined + if (event.details?.truncation?.truncated) { + // Access full output from temp file + const fullPath = event.details.fullOutputPath; + } } - } -}); + }); +} ``` +Available type guards: `isBashToolResult`, `isReadToolResult`, `isEditToolResult`, `isWriteToolResult`, `isGrepToolResult`, `isFindToolResult`, `isLsToolResult`. + #### Tool Details Types Each built-in tool has a typed `details` field. Types are exported from `@mariozechner/pi-coding-agent`: @@ -272,7 +278,18 @@ Common fields in details: - `totalLines`, `totalBytes` - original size - `outputLines`, `outputBytes` - truncated size -Custom tools use `CustomToolResultEvent` with `details: unknown`. +Custom tools use `CustomToolResultEvent` with `details: unknown`. You'll need to cast or validate: + +```typescript +pi.on("tool_result", async (event, ctx) => { + if (event.toolName === "my-custom-tool") { + // Cast to your tool's details type + const details = event.details as MyCustomToolDetails; + } +}); +``` + +Custom tools define their own details type in their `execute` function return value. The hook receives whatever the tool returned, but since the hook system doesn't know about custom tool types at compile time, it's typed as `unknown`. **Note:** If you modify `content`, you should also update `details` accordingly. The TUI uses `details` (e.g., truncation info) for rendering, so inconsistent values will cause display issues. diff --git a/packages/coding-agent/src/core/hooks/index.ts b/packages/coding-agent/src/core/hooks/index.ts index b8335913..a2efab7a 100644 --- a/packages/coding-agent/src/core/hooks/index.ts +++ b/packages/coding-agent/src/core/hooks/index.ts @@ -29,3 +29,12 @@ export type { TurnStartEvent, WriteToolResultEvent, } from "./types.js"; +export { + isBashToolResult, + isEditToolResult, + isFindToolResult, + isGrepToolResult, + isLsToolResult, + isReadToolResult, + isWriteToolResult, +} from "./types.js"; diff --git a/packages/coding-agent/src/core/hooks/types.ts b/packages/coding-agent/src/core/hooks/types.ts index e1677fbc..7d6eb570 100644 --- a/packages/coding-agent/src/core/hooks/types.ts +++ b/packages/coding-agent/src/core/hooks/types.ts @@ -224,6 +224,29 @@ export type ToolResultEvent = | LsToolResultEvent | CustomToolResultEvent; +// Type guards for narrowing ToolResultEvent to specific tool types +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"; +} + /** * Event data for branch event. */ diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index 7184d4cd..2b0e6827 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -35,7 +35,6 @@ export type { ToolUIContext, } from "./core/custom-tools/index.js"; export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools/index.js"; -// Hook system types export type { AgentEndEvent, AgentStartEvent, @@ -62,6 +61,16 @@ export type { TurnStartEvent, WriteToolResultEvent, } from "./core/hooks/index.js"; +// Hook system types and type guards +export { + isBashToolResult, + isEditToolResult, + isFindToolResult, + isGrepToolResult, + isLsToolResult, + isReadToolResult, + isWriteToolResult, +} from "./core/hooks/index.js"; export { messageTransformer } from "./core/messages.js"; export { type CompactionEntry,