mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 19:04:37 +00:00
Expose full tool result content and details in hook tool_result event
Breaking change: ToolResultEvent now exposes content and typed details
instead of just a result string. Hook handlers returning { result: ... }
must change to { content: [...] }.
- ToolResultEvent is now a discriminated union based on toolName
- Each built-in tool has typed details (BashToolDetails, etc.)
- Export tool details types and TruncationResult
- Update hooks.md documentation
Closes #233
This commit is contained in:
parent
05b7b81338
commit
3d9bad8fb6
12 changed files with 187 additions and 34 deletions
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- **Hook `tool_result` event restructured**: The `ToolResultEvent` now exposes full tool result data instead of just text. ([#233](https://github.com/badlogic/pi-mono/pull/233))
|
||||
- Removed: `result: string` field
|
||||
- Added: `content: (TextContent | ImageContent)[]` - full content array
|
||||
- Added: `details: unknown` - tool-specific details (typed per tool via discriminated union on `toolName`)
|
||||
- `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`
|
||||
|
||||
### Changed
|
||||
|
||||
- **Skills standard compliance**: Skills now adhere to the [Agent Skills standard](https://agentskills.io/specification). Validates name (must match parent directory, lowercase, max 64 chars), description (required, max 1024 chars), and frontmatter fields. Warns on violations but remains lenient. Prompt format changed to XML structure. Removed `{baseDir}` placeholder in favor of relative paths. ([#231](https://github.com/badlogic/pi-mono/issues/231))
|
||||
|
|
|
|||
|
|
@ -222,13 +222,60 @@ Fired after tool executes. **Can modify result.**
|
|||
|
||||
```typescript
|
||||
pi.on("tool_result", async (event, ctx) => {
|
||||
// event.toolName, event.toolCallId, event.input
|
||||
// event.result: string
|
||||
// event.toolName: string
|
||||
// event.toolCallId: string
|
||||
// event.input: Record<string, unknown>
|
||||
// event.content: (TextContent | ImageContent)[]
|
||||
// event.details: tool-specific (see below)
|
||||
// event.isError: boolean
|
||||
return { result: "modified" }; // or undefined to keep original
|
||||
|
||||
// Return modified content/details, or undefined to keep original
|
||||
return { content: [...], details: {...} };
|
||||
});
|
||||
```
|
||||
|
||||
The event type is a discriminated union based on `toolName`. TypeScript will 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Tool Details Types
|
||||
|
||||
Each built-in tool has a typed `details` field. Types are exported from `@mariozechner/pi-coding-agent`:
|
||||
|
||||
| Tool | Details Type | Source |
|
||||
|------|-------------|--------|
|
||||
| `bash` | `BashToolDetails` | `src/core/tools/bash.ts` |
|
||||
| `read` | `ReadToolDetails` | `src/core/tools/read.ts` |
|
||||
| `edit` | `undefined` | - |
|
||||
| `write` | `undefined` | - |
|
||||
| `grep` | `GrepToolDetails` | `src/core/tools/grep.ts` |
|
||||
| `find` | `FindToolDetails` | `src/core/tools/find.ts` |
|
||||
| `ls` | `LsToolDetails` | `src/core/tools/ls.ts` |
|
||||
|
||||
Common fields in details:
|
||||
- `truncation?: TruncationResult` - present when output was truncated
|
||||
- `fullOutputPath?: string` - path to temp file with full output (bash only)
|
||||
|
||||
`TruncationResult` contains:
|
||||
- `truncated: boolean` - whether truncation occurred
|
||||
- `truncatedBy: "lines" | "bytes" | null` - which limit was hit
|
||||
- `totalLines`, `totalBytes` - original size
|
||||
- `outputLines`, `outputBytes` - truncated size
|
||||
|
||||
Custom tools use `CustomToolResultEvent` with `details: 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.
|
||||
|
||||
## Context API
|
||||
|
||||
Every event handler receives a context object with these methods:
|
||||
|
|
|
|||
|
|
@ -4,15 +4,22 @@ export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper.js";
|
|||
export type {
|
||||
AgentEndEvent,
|
||||
AgentStartEvent,
|
||||
BashToolResultEvent,
|
||||
BranchEvent,
|
||||
BranchEventResult,
|
||||
CustomToolResultEvent,
|
||||
EditToolResultEvent,
|
||||
ExecResult,
|
||||
FindToolResultEvent,
|
||||
GrepToolResultEvent,
|
||||
HookAPI,
|
||||
HookError,
|
||||
HookEvent,
|
||||
HookEventContext,
|
||||
HookFactory,
|
||||
HookUIContext,
|
||||
LsToolResultEvent,
|
||||
ReadToolResultEvent,
|
||||
SessionEvent,
|
||||
ToolCallEvent,
|
||||
ToolCallEventResult,
|
||||
|
|
@ -20,4 +27,5 @@ export type {
|
|||
ToolResultEventResult,
|
||||
TurnEndEvent,
|
||||
TurnStartEvent,
|
||||
WriteToolResultEvent,
|
||||
} from "./types.js";
|
||||
|
|
|
|||
|
|
@ -44,26 +44,21 @@ export function wrapToolWithHooks<T>(tool: AgentTool<any, T>, hookRunner: HookRu
|
|||
|
||||
// Emit tool_result event - hooks can modify the result
|
||||
if (hookRunner.hasHandlers("tool_result")) {
|
||||
// Extract text from result for hooks
|
||||
const resultText = result.content
|
||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
||||
.map((c) => c.text)
|
||||
.join("\n");
|
||||
|
||||
const resultResult = (await hookRunner.emit({
|
||||
type: "tool_result",
|
||||
toolName: tool.name,
|
||||
toolCallId,
|
||||
input: params,
|
||||
result: resultText,
|
||||
content: result.content,
|
||||
details: result.details,
|
||||
isError: false,
|
||||
})) as ToolResultEventResult | undefined;
|
||||
|
||||
// Apply modifications if any
|
||||
if (resultResult?.result !== undefined) {
|
||||
if (resultResult) {
|
||||
return {
|
||||
...result,
|
||||
content: [{ type: "text", text: resultResult.result }],
|
||||
content: resultResult.content ?? result.content,
|
||||
details: (resultResult.details ?? result.details) as T,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,15 @@
|
|||
*/
|
||||
|
||||
import type { AppMessage, Attachment } from "@mariozechner/pi-agent-core";
|
||||
import type { ToolResultMessage } from "@mariozechner/pi-ai";
|
||||
import type { ImageContent, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
|
||||
import type { SessionEntry } from "../session-manager.js";
|
||||
import type {
|
||||
BashToolDetails,
|
||||
FindToolDetails,
|
||||
GrepToolDetails,
|
||||
LsToolDetails,
|
||||
ReadToolDetails,
|
||||
} from "../tools/index.js";
|
||||
|
||||
// ============================================================================
|
||||
// Execution Context
|
||||
|
|
@ -140,23 +147,83 @@ export interface ToolCallEvent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Event data for tool_result event.
|
||||
* Fired after a tool is executed. Hooks can modify the result.
|
||||
* Base interface for tool_result events.
|
||||
*/
|
||||
export interface ToolResultEvent {
|
||||
interface ToolResultEventBase {
|
||||
type: "tool_result";
|
||||
/** Tool name (e.g., "bash", "edit", "write") */
|
||||
toolName: string;
|
||||
/** Tool call ID */
|
||||
toolCallId: string;
|
||||
/** Tool input parameters */
|
||||
input: Record<string, unknown>;
|
||||
/** Tool result content (text) */
|
||||
result: string;
|
||||
/** Full content array (text and images) */
|
||||
content: (TextContent | ImageContent)[];
|
||||
/** Whether the tool execution was an error */
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
/** Tool result event for bash tool */
|
||||
export interface BashToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "bash";
|
||||
details: BashToolDetails | undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for read tool */
|
||||
export interface ReadToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "read";
|
||||
details: ReadToolDetails | undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for edit tool */
|
||||
export interface EditToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "edit";
|
||||
details: undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for write tool */
|
||||
export interface WriteToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "write";
|
||||
details: undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for grep tool */
|
||||
export interface GrepToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "grep";
|
||||
details: GrepToolDetails | undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for find tool */
|
||||
export interface FindToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "find";
|
||||
details: FindToolDetails | undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for ls tool */
|
||||
export interface LsToolResultEvent extends ToolResultEventBase {
|
||||
toolName: "ls";
|
||||
details: LsToolDetails | undefined;
|
||||
}
|
||||
|
||||
/** Tool result event for custom/unknown tools */
|
||||
export interface CustomToolResultEvent extends ToolResultEventBase {
|
||||
toolName: string;
|
||||
details: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data for tool_result event.
|
||||
* Fired after a tool is executed. Hooks can modify the result.
|
||||
* Use toolName to discriminate and get typed details.
|
||||
*/
|
||||
export type ToolResultEvent =
|
||||
| BashToolResultEvent
|
||||
| ReadToolResultEvent
|
||||
| EditToolResultEvent
|
||||
| WriteToolResultEvent
|
||||
| GrepToolResultEvent
|
||||
| FindToolResultEvent
|
||||
| LsToolResultEvent
|
||||
| CustomToolResultEvent;
|
||||
|
||||
/**
|
||||
* Event data for branch event.
|
||||
*/
|
||||
|
|
@ -201,8 +268,10 @@ export interface ToolCallEventResult {
|
|||
* Allows hooks to modify tool results.
|
||||
*/
|
||||
export interface ToolResultEventResult {
|
||||
/** Modified result text (if not set, original result is used) */
|
||||
result?: string;
|
||||
/** Replacement content array (text and images) */
|
||||
content?: (TextContent | ImageContent)[];
|
||||
/** Replacement details */
|
||||
details?: unknown;
|
||||
/** Override isError flag */
|
||||
isError?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const bashSchema = Type.Object({
|
|||
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
||||
});
|
||||
|
||||
interface BashToolDetails {
|
||||
export interface BashToolDetails {
|
||||
truncation?: TruncationResult;
|
||||
fullOutputPath?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const findSchema = Type.Object({
|
|||
|
||||
const DEFAULT_LIMIT = 1000;
|
||||
|
||||
interface FindToolDetails {
|
||||
export interface FindToolDetails {
|
||||
truncation?: TruncationResult;
|
||||
resultLimitReached?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const grepSchema = Type.Object({
|
|||
|
||||
const DEFAULT_LIMIT = 100;
|
||||
|
||||
interface GrepToolDetails {
|
||||
export interface GrepToolDetails {
|
||||
truncation?: TruncationResult;
|
||||
matchLimitReached?: number;
|
||||
linesTruncated?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
export { bashTool } from "./bash.js";
|
||||
export { type BashToolDetails, bashTool } from "./bash.js";
|
||||
export { editTool } from "./edit.js";
|
||||
export { findTool } from "./find.js";
|
||||
export { grepTool } from "./grep.js";
|
||||
export { lsTool } from "./ls.js";
|
||||
export { readTool } from "./read.js";
|
||||
export { type FindToolDetails, findTool } from "./find.js";
|
||||
export { type GrepToolDetails, grepTool } from "./grep.js";
|
||||
export { type LsToolDetails, lsTool } from "./ls.js";
|
||||
export { type ReadToolDetails, readTool } from "./read.js";
|
||||
export type { TruncationResult } from "./truncate.js";
|
||||
export { writeTool } from "./write.js";
|
||||
|
||||
import { bashTool } from "./bash.js";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const lsSchema = Type.Object({
|
|||
|
||||
const DEFAULT_LIMIT = 500;
|
||||
|
||||
interface LsToolDetails {
|
||||
export interface LsToolDetails {
|
||||
truncation?: TruncationResult;
|
||||
entryLimitReached?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const readSchema = Type.Object({
|
|||
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
||||
});
|
||||
|
||||
interface ReadToolDetails {
|
||||
export interface ReadToolDetails {
|
||||
truncation?: TruncationResult;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,20 @@ export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools
|
|||
export type {
|
||||
AgentEndEvent,
|
||||
AgentStartEvent,
|
||||
BashToolResultEvent,
|
||||
BranchEvent,
|
||||
BranchEventResult,
|
||||
CustomToolResultEvent,
|
||||
EditToolResultEvent,
|
||||
FindToolResultEvent,
|
||||
GrepToolResultEvent,
|
||||
HookAPI,
|
||||
HookEvent,
|
||||
HookEventContext,
|
||||
HookFactory,
|
||||
HookUIContext,
|
||||
LsToolResultEvent,
|
||||
ReadToolResultEvent,
|
||||
SessionEvent,
|
||||
ToolCallEvent,
|
||||
ToolCallEventResult,
|
||||
|
|
@ -53,6 +60,7 @@ export type {
|
|||
ToolResultEventResult,
|
||||
TurnEndEvent,
|
||||
TurnStartEvent,
|
||||
WriteToolResultEvent,
|
||||
} from "./core/hooks/index.js";
|
||||
export { messageTransformer } from "./core/messages.js";
|
||||
export {
|
||||
|
|
@ -89,7 +97,22 @@ export {
|
|||
type SkillWarning,
|
||||
} from "./core/skills.js";
|
||||
// Tools
|
||||
export { bashTool, codingTools, editTool, readTool, writeTool } from "./core/tools/index.js";
|
||||
export {
|
||||
type BashToolDetails,
|
||||
bashTool,
|
||||
codingTools,
|
||||
editTool,
|
||||
type FindToolDetails,
|
||||
findTool,
|
||||
type GrepToolDetails,
|
||||
grepTool,
|
||||
type LsToolDetails,
|
||||
lsTool,
|
||||
type ReadToolDetails,
|
||||
readTool,
|
||||
type TruncationResult,
|
||||
writeTool,
|
||||
} from "./core/tools/index.js";
|
||||
|
||||
// Main entry point
|
||||
export { main } from "./main.js";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue