From e2f29b05238929ff0ef6dd03a12d3322f5e902cd Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 1 Apr 2026 23:07:14 +0200 Subject: [PATCH] add(coding-agent): add defineTool helper closes #2746 --- packages/coding-agent/CHANGELOG.md | 2 ++ packages/coding-agent/docs/sdk.md | 21 ++++++----- .../coding-agent/examples/extensions/hello.ts | 35 ++++++++++--------- .../coding-agent/src/core/extensions/index.ts | 1 + .../coding-agent/src/core/extensions/types.ts | 15 ++++++++ packages/coding-agent/src/core/index.ts | 1 + packages/coding-agent/src/index.ts | 1 + 7 files changed, 50 insertions(+), 26 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0b203157..2a97e33b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -73,6 +73,8 @@ After runtime replacement, use `runtimeHost.session` as the new live session and - Added public SDK runtime-host exports `createAgentSessionRuntime()` and `AgentSessionRuntimeHost` for apps that need runtime-backed session replacement and mode-style session switching +- Added `defineTool()` so standalone and array-based custom tool definitions keep inferred parameter types without manual casts ([#2746](https://github.com/badlogic/pi-mono/issues/2746)) + - Added label timestamps to the session tree with a `Shift+T` toggle in `/tree`, smart date formatting, and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter)) ### Fixed diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md index de4d26ae..a4a66e47 100644 --- a/packages/coding-agent/docs/sdk.md +++ b/packages/coding-agent/docs/sdk.md @@ -493,21 +493,21 @@ const { session } = await createAgentSession({ ```typescript import { Type } from "@sinclair/typebox"; -import { createAgentSession, type ToolDefinition } from "@mariozechner/pi-coding-agent"; +import { createAgentSession, defineTool } from "@mariozechner/pi-coding-agent"; // Inline custom tool -const myTool: ToolDefinition = { +const myTool = defineTool({ name: "my_tool", label: "My Tool", description: "Does something useful", parameters: Type.Object({ input: Type.String({ description: "Input value" }), }), - execute: async (toolCallId, params, onUpdate, ctx, signal) => ({ + execute: async (_toolCallId, params) => ({ content: [{ type: "text", text: `Result: ${params.input}` }], details: {}, }), -}; +}); // Pass custom tools directly const { session } = await createAgentSession({ @@ -515,6 +515,8 @@ const { session } = await createAgentSession({ }); ``` +Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly. + Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`. > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts) @@ -828,14 +830,14 @@ import { getModel } from "@mariozechner/pi-ai"; import { Type } from "@sinclair/typebox"; import { AuthStorage, + bashTool, createAgentSession, DefaultResourceLoader, + defineTool, ModelRegistry, + readTool, SessionManager, SettingsManager, - readTool, - bashTool, - type ToolDefinition, } from "@mariozechner/pi-coding-agent"; // Set up auth storage (custom location) @@ -850,7 +852,7 @@ if (process.env.MY_KEY) { const modelRegistry = ModelRegistry.create(authStorage); // Inline tool -const statusTool: ToolDefinition = { +const statusTool = defineTool({ name: "status", label: "Status", description: "Get system status", @@ -859,7 +861,7 @@ const statusTool: ToolDefinition = { content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }], details: {}, }), -}; +}); const model = getModel("anthropic", "claude-opus-4-5"); if (!model) throw new Error("Model not found"); @@ -1030,6 +1032,7 @@ type ResourceLoader createEventBus // Helpers +defineTool // Session management SessionManager diff --git a/packages/coding-agent/examples/extensions/hello.ts b/packages/coding-agent/examples/extensions/hello.ts index 16f27b81..d56da649 100644 --- a/packages/coding-agent/examples/extensions/hello.ts +++ b/packages/coding-agent/examples/extensions/hello.ts @@ -3,23 +3,24 @@ */ import { Type } from "@mariozechner/pi-ai"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +const helloTool = defineTool({ + name: "hello", + label: "Hello", + description: "A simple greeting tool", + parameters: Type.Object({ + name: Type.String({ description: "Name to greet" }), + }), + + async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { + return { + content: [{ type: "text", text: `Hello, ${params.name}!` }], + details: { greeted: params.name }, + }; + }, +}); export default function (pi: ExtensionAPI) { - pi.registerTool({ - name: "hello", - label: "Hello", - description: "A simple greeting tool", - parameters: Type.Object({ - name: Type.String({ description: "Name to greet" }), - }), - - async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { - const { name } = params as { name: string }; - return { - content: [{ type: "text", text: `Hello, ${name}!` }], - details: { greeted: name }, - }; - }, - }); + pi.registerTool(helloTool); } diff --git a/packages/coding-agent/src/core/extensions/index.ts b/packages/coding-agent/src/core/extensions/index.ts index 7853cd4a..ff8e4a4a 100644 --- a/packages/coding-agent/src/core/extensions/index.ts +++ b/packages/coding-agent/src/core/extensions/index.ts @@ -154,6 +154,7 @@ export type { } from "./types.js"; // Type guards export { + defineTool, isBashToolResult, isEditToolResult, isFindToolResult, diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index bf05f991..b5bef944 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -404,6 +404,21 @@ export interface ToolDefinition Component; } +type AnyToolDefinition = ToolDefinition; + +/** + * Preserve parameter inference for standalone tool definitions. + * + * Use this when assigning a tool to a variable or passing it through arrays such + * as `customTools`, where contextual typing would otherwise widen params to + * `unknown`. + */ +export function defineTool( + tool: ToolDefinition, +): ToolDefinition & AnyToolDefinition { + return tool; +} + // ============================================================================ // Resource Events // ============================================================================ diff --git a/packages/coding-agent/src/core/index.ts b/packages/coding-agent/src/core/index.ts index d649535e..21ab5ca5 100644 --- a/packages/coding-agent/src/core/index.ts +++ b/packages/coding-agent/src/core/index.ts @@ -28,6 +28,7 @@ export { type AgentToolUpdateCallback, type BeforeAgentStartEvent, type ContextEvent, + defineTool, discoverAndLoadExtensions, type ExecOptions, type ExecResult, diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index c8e3b666..d9a159cd 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -125,6 +125,7 @@ export type { } from "./core/extensions/index.js"; export { createExtensionRuntime, + defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult,