Merge pull request #1446 from frizynn/refactor/extension-type-guards-consolidation

refactor: consolidate extension type guards and inline handler aliases
This commit is contained in:
TÂCHES 2026-03-19 15:44:17 -06:00 committed by GitHub
commit 671b72b684
5 changed files with 58 additions and 103 deletions

View file

@ -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
}
});

View file

@ -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,

View file

@ -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<TName extends string, TDetails = unknown>(
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<unknown>;
export type SendMessageHandler = <T = unknown>(
message: Pick<CustomMessage<T>, "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 = <T = unknown>(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<ToolDefinition, "name" | "description" | "parameters">;
export type GetAllToolsHandler = () => ToolInfo[];
export type GetCommandsHandler = () => SlashCommandInfo[];
export type SetActiveToolsHandler = (toolNames: string[]) => void;
export type RefreshToolsHandler = () => void;
export type SetModelHandler = (model: Model<any>, options?: { persist?: boolean }) => Promise<boolean>;
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: <T = unknown>(
message: Pick<CustomMessage<T>, "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: <T = unknown>(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<any>, options?: { persist?: boolean }) => Promise<boolean>;
getThinkingLevel: () => ThinkingLevel;
setThinkingLevel: (level: ThinkingLevel) => void;
}
/**

View file

@ -128,14 +128,8 @@ export {
discoverAndLoadExtensions,
ExtensionRunner,
importExtensionModule,
isBashToolResult,
isEditToolResult,
isFindToolResult,
isGrepToolResult,
isLsToolResult,
isReadToolResult,
isToolCallEventType,
isWriteToolResult,
isToolResultEventType,
wrapRegisteredTool,
wrapRegisteredTools,
wrapToolsWithExtensions,

View file

@ -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: