mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(agents): guard message provider tool name reads
This commit is contained in:
@@ -4,7 +4,10 @@
|
||||
* unsafe or redundant for the active channel.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { filterToolNamesByMessageProvider } from "./agent-tools.message-provider-policy.js";
|
||||
import {
|
||||
filterToolNamesByMessageProvider,
|
||||
filterToolsByMessageProvider,
|
||||
} from "./agent-tools.message-provider-policy.js";
|
||||
|
||||
const DEFAULT_TOOL_NAMES = ["read", "write", "tts", "web_search"];
|
||||
|
||||
@@ -21,4 +24,32 @@ describe("createOpenClawCodingTools message provider policy", () => {
|
||||
const names = new Set(filterToolNamesByMessageProvider(DEFAULT_TOOL_NAMES, "guildchat"));
|
||||
expect(names.has("tts")).toBe(true);
|
||||
});
|
||||
|
||||
it("omits unreadable tool names while applying provider policy", () => {
|
||||
const readTool = { name: "read" };
|
||||
const malformedTool = {
|
||||
get name(): string {
|
||||
throw new Error("fuzzed unreadable tool name");
|
||||
},
|
||||
};
|
||||
const ttsTool = { name: "tts" };
|
||||
|
||||
expect(filterToolsByMessageProvider([readTool, malformedTool, ttsTool], "voice")).toEqual([
|
||||
readTool,
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not read tool names when no provider policy applies", () => {
|
||||
const readTool = { name: "read" };
|
||||
const malformedTool = {
|
||||
get name(): string {
|
||||
throw new Error("fuzzed unreadable tool name");
|
||||
},
|
||||
};
|
||||
|
||||
expect(filterToolsByMessageProvider([readTool, malformedTool], "guildchat")).toEqual([
|
||||
readTool,
|
||||
malformedTool,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,19 @@ const TOOL_ALLOW_BY_MESSAGE_PROVIDER: Readonly<Record<string, readonly string[]>
|
||||
node: ["canvas", "image", "pdf", "tts", "web_fetch", "web_search"],
|
||||
};
|
||||
|
||||
type ReadableToolName<TTool> = {
|
||||
readonly name: string;
|
||||
readonly tool: TTool;
|
||||
};
|
||||
|
||||
function readToolName(tool: { name: string }): string | undefined {
|
||||
try {
|
||||
return tool.name;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/** Filters tool names by the active message-provider allow/deny policy. */
|
||||
export function filterToolNamesByMessageProvider(
|
||||
toolNames: readonly string[],
|
||||
@@ -41,22 +54,36 @@ export function filterToolsByMessageProvider<TTool extends { name: string }>(
|
||||
tools: readonly TTool[],
|
||||
messageProvider?: string,
|
||||
): TTool[] {
|
||||
const normalizedProvider = normalizeOptionalLowercaseString(messageProvider);
|
||||
if (!normalizedProvider) {
|
||||
return [...tools];
|
||||
}
|
||||
const allowedTools = TOOL_ALLOW_BY_MESSAGE_PROVIDER[normalizedProvider];
|
||||
const deniedTools = TOOL_DENY_BY_MESSAGE_PROVIDER[normalizedProvider];
|
||||
if ((!allowedTools || allowedTools.length === 0) && (!deniedTools || deniedTools.length === 0)) {
|
||||
return [...tools];
|
||||
}
|
||||
const readableTools: ReadableToolName<TTool>[] = [];
|
||||
for (const tool of tools) {
|
||||
const name = readToolName(tool);
|
||||
if (name) {
|
||||
readableTools.push({ name, tool });
|
||||
}
|
||||
}
|
||||
const filteredToolNames = filterToolNamesByMessageProvider(
|
||||
tools.map((tool) => tool.name),
|
||||
messageProvider,
|
||||
readableTools.map((tool) => tool.name),
|
||||
normalizedProvider,
|
||||
);
|
||||
const remainingCounts = new Map<string, number>();
|
||||
for (const toolName of filteredToolNames) {
|
||||
remainingCounts.set(toolName, (remainingCounts.get(toolName) ?? 0) + 1);
|
||||
}
|
||||
return tools.filter((tool) => {
|
||||
// Counted matching preserves the original order and duplicate instances
|
||||
// after name-level policy filtering.
|
||||
const remaining = remainingCounts.get(tool.name) ?? 0;
|
||||
return readableTools.flatMap(({ name, tool }) => {
|
||||
const remaining = remainingCounts.get(name) ?? 0;
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
remainingCounts.set(tool.name, remaining - 1);
|
||||
return true;
|
||||
remainingCounts.set(name, remaining - 1);
|
||||
return [tool];
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user