add(coding-agent): add defineTool helper closes #2746

This commit is contained in:
Mario Zechner
2026-04-01 23:07:14 +02:00
parent 746f770b10
commit e2f29b0523
7 changed files with 50 additions and 26 deletions

View File

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

View File

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

View File

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

View File

@@ -154,6 +154,7 @@ export type {
} from "./types.js";
// Type guards
export {
defineTool,
isBashToolResult,
isEditToolResult,
isFindToolResult,

View File

@@ -404,6 +404,21 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
) => Component;
}
type AnyToolDefinition = ToolDefinition<any, any, any>;
/**
* 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<TParams extends TSchema, TDetails = unknown, TState = any>(
tool: ToolDefinition<TParams, TDetails, TState>,
): ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition {
return tool;
}
// ============================================================================
// Resource Events
// ============================================================================

View File

@@ -28,6 +28,7 @@ export {
type AgentToolUpdateCallback,
type BeforeAgentStartEvent,
type ContextEvent,
defineTool,
discoverAndLoadExtensions,
type ExecOptions,
type ExecResult,

View File

@@ -125,6 +125,7 @@ export type {
} from "./core/extensions/index.js";
export {
createExtensionRuntime,
defineTool,
discoverAndLoadExtensions,
ExtensionRunner,
isBashToolResult,