Skip to content

24 函数级剖析:工具构建与策略过滤

模块目标

拆解工具是如何"先汇总候选池,再逐层限权"的。理解 splitSdkToolstoToolDefinitions、7 步策略管线的精确行为。

核心文件

  • src/agents/pi-tools.tscreateOpenClawCodingTools
  • src/agents/pi-tool-definition-adapter.tssplitSdkToolstoToolDefinitionstoClientToolDefinitions
  • src/agents/tool-policy-pipeline.tsbuildDefaultToolPolicyPipelineStepsapplyToolPolicyPipeline
  • src/agents/tool-policy.tsresolveEffectiveToolPolicyresolveSubagentToolPolicy

一、createOpenClawCodingTools(完整签名)

ts
// src/agents/pi-tools.ts:133-155

export function createOpenClawCodingTools(options?: {
  exec?: ExecToolDefaults & ProcessToolDefaults;
  messageProvider?: string;
  agentAccountId?: string;
  messageTo?: string;
  messageThreadId?: string | number;
  sandbox?: SandboxContext | null;
  sessionKey?: string;
  agentDir?: string;
  workspaceDir?: string;
  config?: OpenClawConfig;
  abortSignal?: AbortSignal;
  /** Provider of the currently selected model (e.g., "anthropic", "openai", "google") */
  modelProvider?: string;
  /** Model id for the current provider (used for model-specific tool gating). */
  modelId?: string;
  /** Auth mode for the current provider (used for Anthropic OAuth tool-name blocking). */
  authMode?: string;
  // ...更多可选参数
}): SdkTool[]

返回的工具候选池包含:

  • 基础 coding tools(read_file / write_file / list_directory 等)
  • exec / process / apply_patch
  • channel agent tools(message / sessions 等)
  • OpenClaw native tools(web / browser / memory 等)
  • plugin tools(扩展注入)

二、splitSdkTools(关键:builtInTools 永远为空)

ts
// src/agents/pi-tool-definition-adapter.ts

export function splitSdkTools(params: {
  tools: SdkTool[];
  sandboxEnabled?: boolean;
}): {
  builtInTools: ToolDefinition[];   // 永远是 []
  customTools: ToolDefinition[];    // 所有工具都在这里
}

// 实现:
return {
  builtInTools: [],                         // ← 永远空数组
  customTools: toToolDefinitions(params.tools),
};

为什么 builtInTools 永远为空? OpenClaw 的所有工具都走 custom 路径——即使是内置 coding tools 也通过 toToolDefinitions 适配,统一包装 before_tool_call hook 和异常处理。

三、toToolDefinitions(hook 包装)

ts
// src/agents/pi-tool-definition-adapter.ts

export function toToolDefinitions(tools: SdkTool[]): ToolDefinition[] {
  return tools.map((tool) => ({
    name: tool.name,
    label: tool.label ?? tool.name,
    description: tool.description,
    parameters: tool.parameters,
    execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
      // 包装 before_tool_call hook
      const { toolCallId, params } = splitToolExecuteArgs(args);
      await runBeforeToolCallHook({ toolCallId, toolName: tool.name, params });
      // 执行原始工具
      return tool.execute(...args);
    },
  }));
}

四、toClientToolDefinitions(OpenResponses client tools)

ts
// src/agents/pi-tool-definition-adapter.ts:190-204

// Convert client tools (OpenResponses hosted tools) to ToolDefinition format
// These tools are intercepted to return a "pending" result instead of executing
export function toClientToolDefinitions(
  tools: ClientToolDefinition[],
  onClientToolCall?: (toolName: string, params: Record<string, unknown>) => void,
  hookContext?: { agentId?: string; sessionKey?: string },
): ToolDefinition[] {
  return tools.map((tool) => {
    const func = tool.function;
    return {
      name: func.name,
      label: func.name,
      description: func.description ?? "",
      parameters: func.parameters as any,
      execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
        const { toolCallId, params } = splitToolExecuteArgs(args);
        // 不实际执行,而是记录调用并返回 "pending"
        onClientToolCall?.(func.name, params);
        return { result: "__pending__", toolCallId };
      },
    };
  });
}

toClientToolDefinitions 用于 OpenResponses 场景——工具"调用"被拦截,返回 "pending" 结果,由上层在 stopReason=tool_calls 时转交给客户端处理。

五、buildDefaultToolPolicyPipelineSteps(7 步管线,顺序有意义)

ts
// src/agents/tool-policy-pipeline.ts

export function buildDefaultToolPolicyPipelineSteps(params: {
  agentId?: string;
  groupId?: string;
  provider?: string;
}): PolicyPipelineStep[] {
  return [
    // Step 1: profile 级全局限制(最先执行)
    {
      source: "tools.profile",
      stripPluginOnlyAllowlist: true,
    },
    // Step 2: byProvider 的 profile 限制
    {
      source: `tools.byProvider.profile`,
      provider: params.provider,
      stripPluginOnlyAllowlist: true,
    },
    // Step 3: 全局 allow 策略
    {
      source: "tools.allow",
      stripPluginOnlyAllowlist: true,
    },
    // Step 4: byProvider 全局策略
    {
      source: "tools.byProvider.allow",
      provider: params.provider,
      stripPluginOnlyAllowlist: true,
    },
    // Step 5: agent 级 allow 策略
    {
      source: `agents.${params.agentId}.tools.allow`,
      agentId: params.agentId,
      stripPluginOnlyAllowlist: true,
    },
    // Step 6: agent 级 byProvider 策略
    {
      source: `agents.${params.agentId}.tools.byProvider.allow`,
      agentId: params.agentId,
      provider: params.provider,
      stripPluginOnlyAllowlist: true,
    },
    // Step 7: group 级 allow 策略(最后执行)
    {
      source: "group tools.allow",
      groupId: params.groupId,
      stripPluginOnlyAllowlist: true,
    },
  ];
}

管线执行顺序(从宽到严):

profile(全局)
    → profile byProvider
    → allow(全局)
    → allow byProvider
    → allow(agent 级)
    → allow byProvider(agent 级)
    → allow(group 级)

六、applyToolPolicyPipeline

ts
// 逐步应用策略,每步缩小工具集

export function applyToolPolicyPipeline(params: {
  tools: ToolDefinition[];
  steps: PolicyPipelineStep[];
  config: OpenClawConfig;
}): ToolDefinition[] {
  let current = params.tools;
  for (const step of params.steps) {
    const policy = resolveStepPolicy(step, params.config);
    if (policy) {
      current = filterToolsByPolicy(current, policy, step);
    }
  }
  return current;
}

七、resolveSubagentToolPolicy(子 agent 工具策略)

ts
// 子 agent 使用更严格的默认 deny 列表

export function resolveSubagentToolPolicy(params: {
  config?: OpenClawConfig;
  agentId?: string;
}): ToolPolicy {
  const deny = [
    ...DEFAULT_SUBAGENT_TOOL_DENY,   // 系统默认禁止列表(如危险系统工具)
    ...(params.config?.agents?.defaults?.subagent?.tools?.deny ?? []),
  ];
  return { deny };
}

DEFAULT_SUBAGENT_TOOL_DENY 包含不允许子 agent 使用的工具列表,防止递归调用或权限提升。

八、完整工具构建流程

createOpenClawCodingTools(options)


SdkTool[] 候选池(coding + exec + native + plugin)


splitSdkTools({ tools, sandboxEnabled })
    ├── builtInTools: []        ← 永远为空
    └── customTools: ToolDefinition[]  ← 所有工具(已包装 hook)


[可选] toClientToolDefinitions(clientTools)  ← OpenResponses hosted tools


allCustomTools = [...customTools, ...clientToolDefs]


createAgentSession({ customTools: allCustomTools, ... })


applyToolPolicyPipeline(7 步管线过滤)


最终可用工具集 → LLM

九、自检清单

  1. splitSdkToolsbuiltInTools 永远是空数组,所有工具都在 customTools
  2. toToolDefinitions 为每个工具包装 runBeforeToolCallHook(在工具执行前触发)。
  3. toClientToolDefinitions 返回的工具不会真正执行,只记录调用并返回 "__pending__"
  4. 7 步管线中 stripPluginOnlyAllowlist: true 防止"只写了插件 allowlist 导致核心工具被误关"。
  5. 管线从 profile 到 group,越靠后越细粒度,后面的 step 在前面结果的基础上继续过滤。
  6. 子 agent 有独立的 DEFAULT_SUBAGENT_TOOL_DENY 基础禁用列表。

十、开发避坑

  1. 策略管线顺序不可随意调换:profile 必须在 allow 之前,先过 profile 收窄,再用 allow 细化。
  2. 插件工具分组:支持 group:plugins / pluginId 分组展开,使用 stripPluginOnlyAllowlist 防止误伤核心工具。
  3. client tools 是 OpenResponses 专用:只在 openResponsesEnabled 时传入,普通 chat 不使用。
  4. 工具 execute 异常toToolDefinitions 包装后会把异常转为结构化 tool error,不会炸主流程。

用工程视角拆解 AI 智能体框架