

























1+import type { OpenClawConfig } from "../config/types.openclaw.js";
2+import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js";
3+import { getActivePluginRegistry } from "../plugins/runtime.js";
4+import { buildPluginToolMetadataKey, getPluginToolMeta } from "../plugins/tools.js";
5+import {
6+normalizeLowercaseStringOrEmpty,
7+normalizeOptionalString,
8+} from "../shared/string-coerce.js";
9+import { getChannelAgentToolMeta } from "./channel-tools.js";
10+import { normalizeAgentRuntimeTools } from "./runtime-plan/tools.js";
11+import { summarizeToolDescriptionText } from "./tool-description-summary.js";
12+import { resolveToolDisplay } from "./tool-display.js";
13+import {
14+filterRuntimeCompatibleTools,
15+type RuntimeToolSchemaDiagnostic,
16+} from "./tool-schema-projection.js";
17+import { buildEffectiveToolInventoryGroups } from "./tools-effective-inventory-groups.js";
18+import type {
19+EffectiveToolInventoryEntry,
20+EffectiveToolInventoryNotice,
21+EffectiveToolSource,
22+} from "./tools-effective-inventory.types.js";
23+import type { AnyAgentTool } from "./tools/common.js";
24+25+function resolveEffectiveToolLabel(tool: AnyAgentTool): string {
26+const rawLabel = normalizeOptionalString(tool.label) ?? "";
27+if (
28+rawLabel &&
29+normalizeLowercaseStringOrEmpty(rawLabel) !== normalizeLowercaseStringOrEmpty(tool.name)
30+) {
31+return rawLabel;
32+}
33+return resolveToolDisplay({ name: tool.name }).title;
34+}
35+36+function resolveRawToolDescription(tool: AnyAgentTool): string {
37+return normalizeOptionalString(tool.description) ?? "";
38+}
39+40+function summarizeToolDescription(tool: AnyAgentTool): string {
41+return summarizeToolDescriptionText({
42+rawDescription: resolveRawToolDescription(tool),
43+displaySummary: tool.displaySummary,
44+});
45+}
46+47+function resolveEffectiveToolSource(
48+tool: AnyAgentTool,
49+fallbackTool?: AnyAgentTool,
50+): {
51+source: EffectiveToolSource;
52+pluginId?: string;
53+channelId?: string;
54+} {
55+const pluginMeta =
56+getPluginToolMeta(tool) ?? (fallbackTool ? getPluginToolMeta(fallbackTool) : undefined);
57+if (pluginMeta) {
58+if (pluginMeta.pluginId === "bundle-mcp") {
59+return { source: "mcp", pluginId: pluginMeta.pluginId };
60+}
61+return { source: "plugin", pluginId: pluginMeta.pluginId };
62+}
63+const channelMeta =
64+getChannelAgentToolMeta(tool as never) ??
65+(fallbackTool ? getChannelAgentToolMeta(fallbackTool as never) : undefined);
66+if (channelMeta) {
67+return { source: "channel", channelId: channelMeta.channelId };
68+}
69+return { source: "core" };
70+}
71+72+function buildUnsupportedToolSchemaNotice(params: {
73+diagnostic: RuntimeToolSchemaDiagnostic;
74+tool: AnyAgentTool | undefined;
75+fallbackTool: AnyAgentTool | undefined;
76+}): EffectiveToolInventoryNotice {
77+const source = params.tool
78+ ? resolveEffectiveToolSource(params.tool, params.fallbackTool)
79+ : { source: "core" as const };
80+const owner =
81+source.source === "plugin" && source.pluginId
82+ ? ` from plugin "${source.pluginId}"`
83+ : source.source === "channel" && source.channelId
84+ ? ` from channel "${source.channelId}"`
85+ : "";
86+return {
87+id: `unsupported-tool-schema:${params.diagnostic.toolName}`,
88+severity: "warning",
89+message: `Tool "${params.diagnostic.toolName}"${owner} has an unsupported runtime input schema (${params.diagnostic.violations.join(", ")}) and was quarantined before model projection. Fix or disable the owner, or remove the tool from active allowlists.`,
90+};
91+}
92+93+function buildUnsupportedToolSchemaNotices(params: {
94+diagnostics: readonly RuntimeToolSchemaDiagnostic[];
95+tools: readonly AnyAgentTool[];
96+rawToolsByName: ReadonlyMap<string, AnyAgentTool>;
97+}): EffectiveToolInventoryNotice[] {
98+return params.diagnostics.map((diagnostic) =>
99+buildUnsupportedToolSchemaNotice({
100+ diagnostic,
101+tool: params.tools[diagnostic.toolIndex],
102+fallbackTool: params.rawToolsByName.get(diagnostic.toolName),
103+}),
104+);
105+}
106+107+function disambiguateLabels(entries: EffectiveToolInventoryEntry[]): EffectiveToolInventoryEntry[] {
108+const counts = new Map<string, number>();
109+for (const entry of entries) {
110+counts.set(entry.label, (counts.get(entry.label) ?? 0) + 1);
111+}
112+return entries.map((entry) => {
113+if ((counts.get(entry.label) ?? 0) < 2) {
114+return entry;
115+}
116+const suffix = entry.pluginId ?? entry.channelId ?? entry.id;
117+return { ...entry, label: `${entry.label} (${suffix})` };
118+});
119+}
120+121+export function buildEffectiveToolInventoryEntries(
122+tools: readonly AnyAgentTool[],
123+rawToolsByName: ReadonlyMap<string, AnyAgentTool> = new Map(),
124+): EffectiveToolInventoryEntry[] {
125+// Key metadata by plugin ownership and tool name so only the owning plugin can
126+// project display/risk metadata for its own tool.
127+const pluginToolMetadata = new Map(
128+(getActivePluginRegistry()?.toolMetadata ?? []).map((entry) => [
129+buildPluginToolMetadataKey(entry.pluginId, entry.metadata.toolName),
130+entry.metadata,
131+]),
132+);
133+134+return disambiguateLabels(
135+tools
136+.map((tool) => {
137+const source = resolveEffectiveToolSource(tool, rawToolsByName.get(tool.name));
138+const metadata = source.pluginId
139+ ? pluginToolMetadata.get(buildPluginToolMetadataKey(source.pluginId, tool.name))
140+ : undefined;
141+return Object.assign(
142+{
143+id: tool.name,
144+label:
145+normalizeOptionalString(metadata?.displayName) ?? resolveEffectiveToolLabel(tool),
146+description:
147+normalizeOptionalString(metadata?.description) ?? summarizeToolDescription(tool),
148+rawDescription:
149+normalizeOptionalString(metadata?.description) ??
150+resolveRawToolDescription(tool) ??
151+summarizeToolDescription(tool),
152+ ...(metadata?.risk ? { risk: metadata.risk } : {}),
153+ ...(metadata?.tags ? { tags: metadata.tags } : {}),
154+},
155+source,
156+) satisfies EffectiveToolInventoryEntry;
157+})
158+.toSorted((a, b) => a.label.localeCompare(b.label)),
159+);
160+}
161+162+export function buildRuntimeCompatibleToolInventory(params: {
163+tools: readonly AnyAgentTool[];
164+cfg: OpenClawConfig;
165+workspaceDir?: string;
166+modelProvider?: string;
167+modelId?: string;
168+modelApi?: string | null;
169+runtimeModel?: ProviderRuntimeModel;
170+}): {
171+entries: EffectiveToolInventoryEntry[];
172+notices: EffectiveToolInventoryNotice[];
173+} {
174+const rawToolsByName = new Map(params.tools.map((tool) => [tool.name, tool]));
175+const normalizedTools = normalizeAgentRuntimeTools({
176+// Schema normalization can replace tool definitions, so hand the runtime
177+// policy a mutable copy while keeping this inventory API readonly.
178+tools: [...params.tools],
179+provider: params.modelProvider ?? "",
180+config: params.cfg,
181+workspaceDir: params.workspaceDir,
182+modelId: params.modelId,
183+modelApi: params.modelApi ?? undefined,
184+model: params.runtimeModel,
185+});
186+const projection = filterRuntimeCompatibleTools(normalizedTools);
187+return {
188+entries: buildEffectiveToolInventoryEntries(projection.tools, rawToolsByName),
189+notices: buildUnsupportedToolSchemaNotices({
190+diagnostics: projection.diagnostics,
191+tools: normalizedTools,
192+ rawToolsByName,
193+}),
194+};
195+}
196+197+export { buildEffectiveToolInventoryGroups };
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。