docs(coding-agent): add terminating structured output example closes #3525
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added `structured-output.ts` extension example plus extension docs for terminating tool results, showing how a custom tool can return `terminate: true` so the agent ends on the tool call without an extra follow-up LLM turn ([#3525](https://github.com/badlogic/pi-mono/issues/3525))
|
||||||
- Added OSC 9;4 terminal progress indicators during agent streaming and compaction, so terminals like iTerm2, WezTerm, Windows Terminal, and Kitty show activity in their tab bar
|
- Added OSC 9;4 terminal progress indicators during agent streaming and compaction, so terminals like iTerm2, WezTerm, Windows Terminal, and Kitty show activity in their tab bar
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|||||||
@@ -1691,6 +1691,9 @@ pi.registerTool({
|
|||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: "Done" }], // Sent to LLM
|
content: [{ type: "text", text: "Done" }], // Sent to LLM
|
||||||
details: { data: result }, // For rendering & state
|
details: { data: result }, // For rendering & state
|
||||||
|
// Optional: stop after this tool batch when every finalized tool result
|
||||||
|
// in the batch also returns terminate: true.
|
||||||
|
terminate: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1702,6 +1705,8 @@ pi.registerTool({
|
|||||||
|
|
||||||
**Signaling errors:** To mark a tool execution as failed (sets `isError: true` on the result and reports it to the LLM), throw an error from `execute`. Returning a value never sets the error flag regardless of what properties you include in the return object.
|
**Signaling errors:** To mark a tool execution as failed (sets `isError: true` on the result and reports it to the LLM), throw an error from `execute`. Returning a value never sets the error flag regardless of what properties you include in the return object.
|
||||||
|
|
||||||
|
**Early termination:** Return `terminate: true` from `execute()` to hint that the automatic follow-up LLM call should be skipped after the current tool batch. This only takes effect when every finalized tool result in that batch is terminating. See [examples/extensions/structured-output.ts](../examples/extensions/structured-output.ts) for a minimal example where the agent ends on a final structured-output tool call.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Correct: throw to signal an error
|
// Correct: throw to signal an error
|
||||||
async execute(toolCallId, params) {
|
async execute(toolCallId, params) {
|
||||||
@@ -2383,6 +2388,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|||||||
| `questionnaire.ts` | Multi-step wizard tool | `registerTool`, `ui.custom` |
|
| `questionnaire.ts` | Multi-step wizard tool | `registerTool`, `ui.custom` |
|
||||||
| `todo.ts` | Stateful tool with persistence | `registerTool`, `appendEntry`, `renderResult`, session events |
|
| `todo.ts` | Stateful tool with persistence | `registerTool`, `appendEntry`, `renderResult`, session events |
|
||||||
| `dynamic-tools.ts` | Register tools after startup and during commands | `registerTool`, `session_start`, `registerCommand` |
|
| `dynamic-tools.ts` | Register tools after startup and during commands | `registerTool`, `session_start`, `registerCommand` |
|
||||||
|
| `structured-output.ts` | Final structured-output tool with `terminate: true` | `registerTool`, terminating tool results |
|
||||||
| `truncated-tool.ts` | Output truncation example | `registerTool`, `truncateHead` |
|
| `truncated-tool.ts` | Output truncation example | `registerTool`, `truncateHead` |
|
||||||
| `tool-override.ts` | Override built-in read tool | `registerTool` (same name as built-in) |
|
| `tool-override.ts` | Override built-in read tool | `registerTool` (same name as built-in) |
|
||||||
| **Commands** |||
|
| **Commands** |||
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|||||||
| `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
|
| `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
|
||||||
| `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
|
| `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
|
||||||
| `dynamic-tools.ts` | Register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines |
|
| `dynamic-tools.ts` | Register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines |
|
||||||
|
| `structured-output.ts` | Final structured-output tool that returns `terminate: true` so the agent can end on the tool call |
|
||||||
| `built-in-tool-renderer.ts` | Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior |
|
| `built-in-tool-renderer.ts` | Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior |
|
||||||
| `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
|
| `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
|
||||||
| `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
|
| `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Structured Output Tool
|
||||||
|
*
|
||||||
|
* Demonstrates `terminate: true` so the agent can end on a tool call
|
||||||
|
* without paying for an extra follow-up LLM turn.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { Text } from "@mariozechner/pi-tui";
|
||||||
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
|
interface StructuredOutputDetails {
|
||||||
|
headline: string;
|
||||||
|
summary: string;
|
||||||
|
actionItems: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const structuredOutputTool = defineTool({
|
||||||
|
name: "structured_output",
|
||||||
|
label: "Structured Output",
|
||||||
|
description:
|
||||||
|
"Return a final structured answer. Use this as your last action when the user asks for structured output or a machine-readable summary.",
|
||||||
|
promptSnippet: "Emit a final structured answer as a terminating tool result",
|
||||||
|
promptGuidelines: [
|
||||||
|
"Use structured_output as your final action when the user asks for structured output, JSON-like output, or a machine-readable summary.",
|
||||||
|
"After calling structured_output, do not emit another assistant response in the same turn.",
|
||||||
|
],
|
||||||
|
parameters: Type.Object({
|
||||||
|
headline: Type.String({ description: "Short title for the result" }),
|
||||||
|
summary: Type.String({ description: "One-paragraph summary" }),
|
||||||
|
actionItems: Type.Array(Type.String(), { description: "Concrete next steps or key bullets" }),
|
||||||
|
}),
|
||||||
|
|
||||||
|
async execute(_toolCallId, params) {
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `Saved structured output: ${params.headline}` }],
|
||||||
|
details: {
|
||||||
|
headline: params.headline,
|
||||||
|
summary: params.summary,
|
||||||
|
actionItems: params.actionItems,
|
||||||
|
} satisfies StructuredOutputDetails,
|
||||||
|
terminate: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
renderResult(result, _options, theme) {
|
||||||
|
const details = result.details as StructuredOutputDetails | undefined;
|
||||||
|
if (!details) {
|
||||||
|
const text = result.content[0];
|
||||||
|
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
theme.fg("toolTitle", theme.bold(details.headline)),
|
||||||
|
theme.fg("text", details.summary),
|
||||||
|
"",
|
||||||
|
...details.actionItems.map((item, index) => theme.fg("muted", `${index + 1}. ${item}`)),
|
||||||
|
];
|
||||||
|
return new Text(lines.join("\n"), 0, 0);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
pi.registerTool(structuredOutputTool);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user