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 ```ts /**
  • 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 { /** UI 展示用的人类可读名称。 */ label: string; /**
    • 参数兼容处理钩子。 *
    • 模型或历史 transcript 可能使用旧字段名/旧结构;这里允许工具在 schema 校验前
    • 做轻量归一化。返回值必须符合 TParameters。 */ prepareArguments?: (args: unknown) => Static; /**
    • 执行工具调用。 *
    • 约定:工具失败时直接 throw,由 agent-loop 统一编码成错误 toolResult;
    • 工具自身不需要也不应该手动构造错误 transcript。 */ execute: ( toolCallId: string, params: Static, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, ) => Promise<AgentToolResult>; /**
    • 单工具执行模式覆盖。 *
    • 如果工具声明 “sequential”,包含它的整批 toolCall 都会串行执行。
    • 文件写入、shell 命令等有副作用工具通常应使用串行模式。 *
    • 未设置时使用 loop 的默认执行模式。 */ executionMode?: ToolExecutionMode; } ```
  • tool result ```ts /**
  • 工具产生的结果。 *
  • 同一个结构既用于最终结果,也用于 onUpdate 上报的流式部分结果。
  • content 会进入 transcript 并返回给模型,details 主要给 UI、日志和导出使用。 */ export interface AgentToolResult { /** 返回给模型的文本或图片内容。 */ 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 ```ts /**
  • 扩展和 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;

    /**

    • 单工具执行模式覆盖。 *
      • “sequential”:这个工具不能和同一批其他 toolCall 并发执行;
      • “parallel”:这个工具允许并发执行。 *
    • 未设置时使用 agent loop 的默认执行模式。 */ executionMode?: ToolExecutionMode;

    /**

    • 执行工具。 *
    • agent-core 会先完成工具查找、参数准备、schema 校验和 before hook,然后调用这里。
    • 工具执行失败时应抛错,agent-loop 会统一转成错误 toolResult。 */ execute( toolCallId: string, params: Static, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback | undefined, ctx: ExtensionContext, ): Promise<AgentToolResult>;

    /** 自定义 toolCall 展示。用于参数流式生成、工具开始执行前后的标题/摘要展示。 */ renderCall?: (args: Static, theme: Theme, context: ToolRenderContext<TState, Static>) => Component;

    /** 自定义工具结果展示。用于最终结果和流式 partial result 的 TUI 渲染。 */ renderResult?: ( result: AgentToolResult, options: ToolRenderResultOptions, theme: Theme, context: ToolRenderContext<TState, Static>, ) => Component; } ```

  • 通过wrapper包装成agentTool ```ts /**
  • 将 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会自动发现固定目录下的文件 ```ts /**
  • Discover extensions in a directory. *
  • Discovery rules:
    1. Direct files: extensions/*.ts or *.js → load
    1. Subdirectory with index: extensions/* /index.ts or index.js → load
    1. 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.