Post

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

  • 在loop中串行或并发执行
  • 执行方法为AgentTool中定义的execute

    真正执行前会有prepare 阶段

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.