Pi - Tools
Pi - Tools
Tools
- 支持的内置工具 (coding-agent)
- read、bash、edit、write、grep、find、ls
- pi分为 core 和 coding-agent/tui 部分
Agent Tool Define
- runtime protocal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* agent-core 运行时使用的工具协议。
*
* 它继承 pi-ai 的基础 Tool,因此 name/description/parameters 会被传给 provider,
* 让模型知道有哪些工具以及参数 schema。execute 只在模型真的返回 toolCall 后由 agent-loop 调用。
*
* coding-agent 的 ToolDefinition 会在 session 层被包装成这个类型;agent-core 不关心
* TUI 渲染、promptSnippet 或扩展加载来源。
*/
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
/** UI 展示用的人类可读名称。 */
label: string;
/**
* 参数兼容处理钩子。
*
* 模型或历史 transcript 可能使用旧字段名/旧结构;这里允许工具在 schema 校验前
* 做轻量归一化。返回值必须符合 `TParameters`。
*/
prepareArguments?: (args: unknown) => Static<TParameters>;
/**
* 执行工具调用。
*
* 约定:工具失败时直接 throw,由 agent-loop 统一编码成错误 toolResult;
* 工具自身不需要也不应该手动构造错误 transcript。
*/
execute: (
toolCallId: string,
params: Static<TParameters>,
signal?: AbortSignal,
onUpdate?: AgentToolUpdateCallback<TDetails>,
) => Promise<AgentToolResult<TDetails>>;
/**
* 单工具执行模式覆盖。
*
* 如果工具声明 "sequential",包含它的整批 toolCall 都会串行执行。
* 文件写入、shell 命令等有副作用工具通常应使用串行模式。
*
* 未设置时使用 loop 的默认执行模式。
*/
executionMode?: ToolExecutionMode;
}
- tool result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 工具产生的结果。
*
* 同一个结构既用于最终结果,也用于 `onUpdate` 上报的流式部分结果。
* `content` 会进入 transcript 并返回给模型,`details` 主要给 UI、日志和导出使用。
*/
export interface AgentToolResult<T> {
/** 返回给模型的文本或图片内容。 */
content: (TextContent | ImageContent)[];
/** 任意结构化详情,不直接作为模型上下文的一部分。 */
details: T;
/**
* 提示当前工具批次结束后停止继续请求模型。
* 只有同一批所有 finalized 结果都为 true,批次才会真正 terminate。
*/
terminate?: boolean;
}
Execute Tool Calls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
async function prepareToolCall(
currentContext: AgentContext,
assistantMessage: AssistantMessage,
toolCall: AgentToolCall,
config: AgentLoopConfig,
signal: AbortSignal | undefined,
): Promise<PreparedToolCall | ImmediateToolCallOutcome> {
// prepare 阶段只负责“让工具变成可执行状态”,不真正跑工具。
// 它统一处理:查找工具、参数预处理、schema 校验、beforeToolCall 审批/拦截。
//
// 工具查找只发生在当前 context.tools 中,也就是本次 run 开始时 active tools 的快照。
// registry 中存在但未启用的工具,或者运行中才被启用的工具,都不会被本次 toolCall 找到。
const tool = currentContext.tools?.find((t) => t.name === toolCall.name);
if (!tool) {
return {
kind: "immediate",
result: createErrorToolResult(`Tool ${toolCall.name} not found`),
isError: true,
};
}
try {
const preparedToolCall = prepareToolCallArguments(tool, toolCall);
// validateToolArguments 会按工具的 TypeBox/JSON schema 校验,并尽量做安全的类型转换。
// 通过校验后的 args 才会传给 before hook 和真正的 tool.execute。
const validatedArgs = validateToolArguments(tool, preparedToolCall);
if (config.beforeToolCall) {
// before hook 常用于权限检查、审计、策略拦截、额度控制等。
const beforeResult = await config.beforeToolCall(
{
assistantMessage,
toolCall,
args: validatedArgs,
context: currentContext,
},
signal,
);
// ...
}
// ...
}
}
Tool Definition Extention 扩展工具定义
- define
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* 扩展和 coding-agent 内部使用的完整工具定义。
*
* 这个类型比 agent-core 的 AgentTool 更“上层”:
* - name/description/parameters 会传给模型,用于 provider tool/function calling;
* - execute 是真正的工具实现;
* - promptSnippet/promptGuidelines 会参与默认系统提示词构建;
* - renderCall/renderResult/renderShell 只服务于 TUI/HTML 展示;
* - ctx 参数让扩展工具能访问 session、UI、资源等 coding-agent 运行时能力。
*
* AgentSession 会先收集 ToolDefinition,再通过 tool-definition-wrapper 包装成 AgentTool 给 agent-core 执行。
*/
export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown, TState = any> {
/** 工具名。模型返回 toolCall.name 时必须匹配这个名字。 */
name: string;
/** UI 展示用的人类可读标签。 */
label: string;
/** 模型可见描述。provider 会把它作为工具说明发给 LLM。 */
description: string;
/**
* 可选的系统提示词摘要。
* 当工具处于 active 状态时,这段文本会出现在默认 system prompt 的 Available tools 区域。
* 自定义工具如果不提供该字段,就不会被加入这段提示。
*/
promptSnippet?: string;
/** 工具启用时追加到默认 system prompt Guidelines 区域的规则。 */
promptGuidelines?: string[];
/** 工具参数 schema。用于 provider tool schema,也用于执行前参数校验和类型转换。 */
parameters: TParams;
/** 控制 ToolExecutionComponent 使用标准外壳,还是由工具渲染器自己负责完整外框。 */
renderShell?: "default" | "self";
/** 参数兼容层:在 schema 校验前把模型返回的原始参数归一化成 TParams 形状。 */
prepareArguments?: (args: unknown) => Static<TParams>;
/**
* 单工具执行模式覆盖。
*
* - "sequential":这个工具不能和同一批其他 toolCall 并发执行;
* - "parallel":这个工具允许并发执行。
*
* 未设置时使用 agent loop 的默认执行模式。
*/
executionMode?: ToolExecutionMode;
/**
* 执行工具。
*
* agent-core 会先完成工具查找、参数准备、schema 校验和 before hook,然后调用这里。
* 工具执行失败时应抛错,agent-loop 会统一转成错误 toolResult。
*/
execute(
toolCallId: string,
params: Static<TParams>,
signal: AbortSignal | undefined,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: ExtensionContext,
): Promise<AgentToolResult<TDetails>>;
/** 自定义 toolCall 展示。用于参数流式生成、工具开始执行前后的标题/摘要展示。 */
renderCall?: (args: Static<TParams>, theme: Theme, context: ToolRenderContext<TState, Static<TParams>>) => Component;
/** 自定义工具结果展示。用于最终结果和流式 partial result 的 TUI 渲染。 */
renderResult?: (
result: AgentToolResult<TDetails>,
options: ToolRenderResultOptions,
theme: Theme,
context: ToolRenderContext<TState, Static<TParams>>,
) => Component;
}
- 通过wrapper包装成agentTool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 将 coding-agent 层的 ToolDefinition 包装成 agent-core 层的 AgentTool。
*
* ToolDefinition 比 AgentTool 信息更全:它知道 UI 渲染、promptSnippet/promptGuidelines、
* 以及扩展运行时上下文 ctx。agent-core 不应该依赖这些 coding-agent 概念,所以这里做一次边界适配:
* - schema、名称、描述和执行模式原样交给 agent-core;
* - execute 调用时临时创建 ExtensionContext,再传回 ToolDefinition.execute;
* - 渲染器和 prompt 元数据不进入 AgentTool,它们仍由 AgentSession 的定义注册表维护。
*/
export function wrapToolDefinition<TDetails = unknown>(
definition: ToolDefinition<any, TDetails>,
ctxFactory?: () => ExtensionContext,
): AgentTool<any, TDetails> {
return {
name: definition.name,
label: definition.label,
description: definition.description,
parameters: definition.parameters,
prepareArguments: definition.prepareArguments,
executionMode: definition.executionMode,
execute: (toolCallId, params, signal, onUpdate) =>
definition.execute(toolCallId, params, signal, onUpdate, ctxFactory?.() as ExtensionContext),
};
}
Register Tool
- 写ts func通过pi.registerTool()注册
1
2
3
4
5
6
7
8
registerTool(tool: ToolDefinition): void {
runtime.assertActive();
extension.tools.set(tool.name, {
definition: tool,
sourceInfo: extension.sourceInfo,
});
runtime.refreshTools();
},
- extensions会自动发现固定目录下的文件
1
2
3
4
5
6
7
8
9
10
11
/**
* Discover extensions in a directory.
*
* Discovery rules:
* 1. Direct files: `extensions/*.ts` or `*.js` → load
* 2. Subdirectory with index: `extensions/* /index.ts` or `index.js` → load
* 3. Subdirectory with package.json: `extensions/* /package.json` with "pi" field → load what it declares
*
* No recursion beyond one level. Complex packages must use package.json manifest.
*/
function discoverExtensionsInDir(dir: string): string[] {}
- extension定义
1
2
3
4
5
6
7
8
9
10
11
12
/** Loaded extension with all registered items. */
export interface Extension {
path: string;
resolvedPath: string;
sourceInfo: SourceInfo;
handlers: Map<string, HandlerFn[]>;
tools: Map<string, RegisteredTool>;
messageRenderers: Map<string, MessageRenderer>;
commands: Map<string, RegisteredCommand>;
flags: Map<string, ExtensionFlag>;
shortcuts: Map<KeyId, ExtensionShortcut>;
}
This post is licensed under CC BY 4.0 by the author.