24 函数级剖析:工具构建与策略过滤
模块目标
拆解工具是如何"先汇总候选池,再逐层限权"的。理解 splitSdkTools、toToolDefinitions、7 步策略管线的精确行为。
核心文件
src/agents/pi-tools.ts—createOpenClawCodingToolssrc/agents/pi-tool-definition-adapter.ts—splitSdkTools、toToolDefinitions、toClientToolDefinitionssrc/agents/tool-policy-pipeline.ts—buildDefaultToolPolicyPipelineSteps、applyToolPolicyPipelinesrc/agents/tool-policy.ts—resolveEffectiveToolPolicy、resolveSubagentToolPolicy
一、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九、自检清单
splitSdkTools的builtInTools永远是空数组,所有工具都在customTools。toToolDefinitions为每个工具包装runBeforeToolCallHook(在工具执行前触发)。toClientToolDefinitions返回的工具不会真正执行,只记录调用并返回"__pending__"。- 7 步管线中
stripPluginOnlyAllowlist: true防止"只写了插件 allowlist 导致核心工具被误关"。 - 管线从 profile 到 group,越靠后越细粒度,后面的 step 在前面结果的基础上继续过滤。
- 子 agent 有独立的
DEFAULT_SUBAGENT_TOOL_DENY基础禁用列表。
十、开发避坑
- 策略管线顺序不可随意调换:profile 必须在 allow 之前,先过 profile 收窄,再用 allow 细化。
- 插件工具分组:支持
group:plugins/pluginId分组展开,使用stripPluginOnlyAllowlist防止误伤核心工具。 - client tools 是 OpenResponses 专用:只在
openResponsesEnabled时传入,普通 chat 不使用。 - 工具 execute 异常:
toToolDefinitions包装后会把异常转为结构化 tool error,不会炸主流程。