fix(coding-agent): align ToolDefinition.execute signature with AgentTool

BREAKING CHANGE: ToolDefinition.execute parameter order changed from
(id, params, onUpdate, ctx, signal) to (id, params, signal, onUpdate, ctx).

This aligns with AgentTool.execute so wrapping built-in tools no longer
requires parameter reordering. Update extensions by swapping signal and
onUpdate parameters.
This commit is contained in:
Mario Zechner 2026-02-02 00:29:47 +01:00
parent 287a0b606d
commit 0a26db53ef
17 changed files with 36 additions and 25 deletions

View file

@ -2,6 +2,17 @@
## [Unreleased] ## [Unreleased]
### Breaking Changes
- **Extension tool signature change**: `ToolDefinition.execute` now uses `(toolCallId, params, signal, onUpdate, ctx)` parameter order to match `AgentTool.execute`. Previously it was `(toolCallId, params, onUpdate, ctx, signal)`. This makes wrapping built-in tools trivial since the first four parameters now align. Update your extensions by swapping the `signal` and `onUpdate` parameters:
```ts
// Before
async execute(toolCallId, params, onUpdate, ctx, signal) { ... }
// After
async execute(toolCallId, params, signal, onUpdate, ctx) { ... }
```
### New Features ### New Features
- **Android/Termux support**: Pi now runs on Android via Termux. Install with: - **Android/Termux support**: Pi now runs on Android via Termux. Install with:

View file

@ -79,7 +79,7 @@ export default function (pi: ExtensionAPI) {
parameters: Type.Object({ parameters: Type.Object({
name: Type.String({ description: "Name to greet" }), name: Type.String({ description: "Name to greet" }),
}), }),
async execute(toolCallId, params, onUpdate, ctx, signal) { async execute(toolCallId, params, signal, onUpdate, ctx) {
return { return {
content: [{ type: "text", text: `Hello, ${params.name}!` }], content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: {}, details: {},
@ -789,7 +789,7 @@ pi.registerTool({
text: Type.Optional(Type.String()), text: Type.Optional(Type.String()),
}), }),
async execute(toolCallId, params, onUpdate, ctx, signal) { async execute(toolCallId, params, signal, onUpdate, ctx) {
// Stream progress // Stream progress
onUpdate?.({ content: [{ type: "text", text: "Working..." }] }); onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
@ -1115,7 +1115,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
name: "my_tool", name: "my_tool",
// ... // ...
async execute(toolCallId, params, onUpdate, ctx, signal) { async execute(toolCallId, params, signal, onUpdate, ctx) {
items.push("new item"); items.push("new item");
return { return {
content: [{ type: "text", text: "Added" }], content: [{ type: "text", text: "Added" }],
@ -1146,7 +1146,7 @@ pi.registerTool({
text: Type.Optional(Type.String()), text: Type.Optional(Type.String()),
}), }),
async execute(toolCallId, params, onUpdate, ctx, signal) { async execute(toolCallId, params, signal, onUpdate, ctx) {
// Check for cancellation // Check for cancellation
if (signal?.aborted) { if (signal?.aborted) {
return { content: [{ type: "text", text: "Cancelled" }] }; return { content: [{ type: "text", text: "Cancelled" }] };
@ -1224,7 +1224,7 @@ const remoteRead = createReadTool(cwd, {
// Register, checking flag at execution time // Register, checking flag at execution time
pi.registerTool({ pi.registerTool({
...remoteRead, ...remoteRead,
async execute(id, params, onUpdate, _ctx, signal) { async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSshConfig(); const ssh = getSshConfig();
if (ssh) { if (ssh) {
const tool = createReadTool(cwd, { operations: createRemoteOps(ssh) }); const tool = createReadTool(cwd, { operations: createRemoteOps(ssh) });
@ -1272,7 +1272,7 @@ import {
DEFAULT_MAX_LINES, // 2000 DEFAULT_MAX_LINES, // 2000
} from "@mariozechner/pi-coding-agent"; } from "@mariozechner/pi-coding-agent";
async execute(toolCallId, params, onUpdate, ctx, signal) { async execute(toolCallId, params, signal, onUpdate, ctx) {
const output = await runCommand(); const output = await runCommand();
// Apply truncation // Apply truncation

View file

@ -352,7 +352,7 @@ export default function antigravityImageGen(pi: ExtensionAPI) {
description: description:
"Generate an image via Google Antigravity image models. Returns the image as a tool result attachment. Optional saving via save=project|global|custom|none, or PI_IMAGE_SAVE_MODE/PI_IMAGE_SAVE_DIR.", "Generate an image via Google Antigravity image models. Returns the image as a tool result attachment. Optional saving via save=project|global|custom|none, or PI_IMAGE_SAVE_MODE/PI_IMAGE_SAVE_DIR.",
parameters: TOOL_PARAMS, parameters: TOOL_PARAMS,
async execute(_toolCallId, params: ToolParams, onUpdate, ctx, signal) { async execute(_toolCallId, params: ToolParams, signal, onUpdate, ctx) {
const { accessToken, projectId } = await getCredentials(ctx); const { accessToken, projectId } = await getCredentials(ctx);
const model = params.model || DEFAULT_MODEL; const model = params.model || DEFAULT_MODEL;
const aspectRatio = params.aspectRatio || DEFAULT_ASPECT_RATIO; const aspectRatio = params.aspectRatio || DEFAULT_ASPECT_RATIO;

View file

@ -23,7 +23,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
...bashTool, ...bashTool,
execute: async (id, params, onUpdate, _ctx, signal) => { execute: async (id, params, signal, onUpdate, _ctx) => {
return bashTool.execute(id, params, signal, onUpdate); return bashTool.execute(id, params, signal, onUpdate);
}, },
}); });

View file

@ -14,7 +14,7 @@ export default function (pi: ExtensionAPI) {
name: Type.String({ description: "Name to greet" }), name: Type.String({ description: "Name to greet" }),
}), }),
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) { async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
const { name } = params as { name: string }; const { name } = params as { name: string };
return { return {
content: [{ type: "text", text: `Hello, ${name}!` }], content: [{ type: "text", text: `Hello, ${name}!` }],

View file

@ -40,7 +40,7 @@ export default function question(pi: ExtensionAPI) {
description: "Ask the user a question and let them pick from options. Use when you need user input to proceed.", description: "Ask the user a question and let them pick from options. Use when you need user input to proceed.",
parameters: QuestionParams, parameters: QuestionParams,
async execute(_toolCallId, params, _onUpdate, ctx, _signal) { async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
if (!ctx.hasUI) { if (!ctx.hasUI) {
return { return {
content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }], content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }],

View file

@ -81,7 +81,7 @@ export default function questionnaire(pi: ExtensionAPI) {
"Ask the user one or more questions. Use for clarifying requirements, getting preferences, or confirming decisions. For single questions, shows a simple option list. For multiple questions, shows a tab-based interface.", "Ask the user one or more questions. Use for clarifying requirements, getting preferences, or confirming decisions. For single questions, shows a simple option list. For multiple questions, shows a tab-based interface.",
parameters: QuestionnaireParams, parameters: QuestionnaireParams,
async execute(_toolCallId, params, _onUpdate, ctx, _signal) { async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
if (!ctx.hasUI) { if (!ctx.hasUI) {
return errorResult("Error: UI not available (running in non-interactive mode)"); return errorResult("Error: UI not available (running in non-interactive mode)");
} }

View file

@ -211,7 +211,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
...localBash, ...localBash,
label: "bash (sandboxed)", label: "bash (sandboxed)",
async execute(id, params, onUpdate, _ctx, signal) { async execute(id, params, signal, onUpdate, _ctx) {
if (!sandboxEnabled || !sandboxInitialized) { if (!sandboxEnabled || !sandboxInitialized) {
return localBash.execute(id, params, signal, onUpdate); return localBash.execute(id, params, signal, onUpdate);
} }

View file

@ -23,7 +23,7 @@ export default function (pi: ExtensionAPI) {
label: "Finish and Exit", label: "Finish and Exit",
description: "Complete a task and exit pi", description: "Complete a task and exit pi",
parameters: Type.Object({}), parameters: Type.Object({}),
async execute(_toolCallId, _params, _onUpdate, ctx, _signal) { async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
// Do any final work here... // Do any final work here...
// Request graceful shutdown (deferred until agent is idle) // Request graceful shutdown (deferred until agent is idle)
ctx.shutdown(); ctx.shutdown();
@ -44,7 +44,7 @@ export default function (pi: ExtensionAPI) {
parameters: Type.Object({ parameters: Type.Object({
environment: Type.String({ description: "Target environment (e.g., production, staging)" }), environment: Type.String({ description: "Target environment (e.g., production, staging)" }),
}), }),
async execute(_toolCallId, params, onUpdate, ctx, _signal) { async execute(_toolCallId, params, _signal, onUpdate, ctx) {
onUpdate?.({ content: [{ type: "text", text: `Deploying to ${params.environment}...` }], details: {} }); onUpdate?.({ content: [{ type: "text", text: `Deploying to ${params.environment}...` }], details: {} });
// Example deployment logic // Example deployment logic

View file

@ -127,7 +127,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
...localRead, ...localRead,
async execute(id, params, onUpdate, _ctx, signal) { async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSsh(); const ssh = getSsh();
if (ssh) { if (ssh) {
const tool = createReadTool(localCwd, { const tool = createReadTool(localCwd, {
@ -141,7 +141,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
...localWrite, ...localWrite,
async execute(id, params, onUpdate, _ctx, signal) { async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSsh(); const ssh = getSsh();
if (ssh) { if (ssh) {
const tool = createWriteTool(localCwd, { const tool = createWriteTool(localCwd, {
@ -155,7 +155,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
...localEdit, ...localEdit,
async execute(id, params, onUpdate, _ctx, signal) { async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSsh(); const ssh = getSsh();
if (ssh) { if (ssh) {
const tool = createEditTool(localCwd, { const tool = createEditTool(localCwd, {
@ -169,7 +169,7 @@ export default function (pi: ExtensionAPI) {
pi.registerTool({ pi.registerTool({
...localBash, ...localBash,
async execute(id, params, onUpdate, _ctx, signal) { async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSsh(); const ssh = getSsh();
if (ssh) { if (ssh) {
const tool = createBashTool(localCwd, { const tool = createBashTool(localCwd, {

View file

@ -416,7 +416,7 @@ export default function (pi: ExtensionAPI) {
].join(" "), ].join(" "),
parameters: SubagentParams, parameters: SubagentParams,
async execute(_toolCallId, params, onUpdate, ctx, signal) { async execute(_toolCallId, params, signal, onUpdate, ctx) {
const agentScope: AgentScope = params.agentScope ?? "user"; const agentScope: AgentScope = params.agentScope ?? "user";
const discovery = discoverAgents(ctx.cwd, agentScope); const discovery = discoverAgents(ctx.cwd, agentScope);
const agents = discovery.agents; const agents = discovery.agents;

View file

@ -141,7 +141,7 @@ export default function (pi: ExtensionAPI) {
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear", description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
parameters: TodoParams, parameters: TodoParams,
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) { async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
switch (params.action) { switch (params.action) {
case "list": case "list":
return { return {

View file

@ -72,7 +72,7 @@ export default function (pi: ExtensionAPI) {
"Read the contents of a file with access logging. Some sensitive paths (.env, secrets, credentials) are blocked.", "Read the contents of a file with access logging. Some sensitive paths (.env, secrets, credentials) are blocked.",
parameters: readSchema, parameters: readSchema,
async execute(_toolCallId, params, _onUpdate, ctx) { async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { path, offset, limit } = params; const { path, offset, limit } = params;
const absolutePath = resolve(ctx.cwd, path); const absolutePath = resolve(ctx.cwd, path);

View file

@ -52,7 +52,7 @@ export default function (pi: ExtensionAPI) {
description: `Search file contents using ripgrep. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} (whichever is hit first). If truncated, full output is saved to a temp file.`, description: `Search file contents using ripgrep. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} (whichever is hit first). If truncated, full output is saved to a temp file.`,
parameters: RgParams, parameters: RgParams,
async execute(_toolCallId, params, _onUpdate, ctx) { async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { pattern, path: searchPath, glob } = params; const { pattern, path: searchPath, glob } = params;
// Build the ripgrep command // Build the ripgrep command

View file

@ -71,7 +71,7 @@ export default function (pi: ExtensionAPI) {
parameters: Type.Object({ parameters: Type.Object({
input: Type.String(), input: Type.String(),
}), }),
execute: async (_toolCallId, params, _onUpdate, _ctx, _signal) => ({ execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => ({
content: [{ type: "text", text: \`Processed: \${params.input}\` }], content: [{ type: "text", text: \`Processed: \${params.input}\` }],
details: {}, details: {},
}), }),

View file

@ -324,9 +324,9 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
execute( execute(
toolCallId: string, toolCallId: string,
params: Static<TParams>, params: Static<TParams>,
signal: AbortSignal | undefined,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined, onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: ExtensionContext, ctx: ExtensionContext,
signal?: AbortSignal,
): Promise<AgentToolResult<TDetails>>; ): Promise<AgentToolResult<TDetails>>;
/** Custom rendering for tool call display */ /** Custom rendering for tool call display */

View file

@ -18,7 +18,7 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, runner: Exten
description: definition.description, description: definition.description,
parameters: definition.parameters, parameters: definition.parameters,
execute: (toolCallId, params, signal, onUpdate) => execute: (toolCallId, params, signal, onUpdate) =>
definition.execute(toolCallId, params, onUpdate, runner.createContext(), signal), definition.execute(toolCallId, params, signal, onUpdate, runner.createContext()),
}; };
} }