mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(plugins): snapshot tool metadata
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// Tool metadata registration tests cover plugin-owned metadata snapshotting.
|
||||
import {
|
||||
createPluginRegistryFixture,
|
||||
registerTestPlugin,
|
||||
} from "openclaw/plugin-sdk/plugin-test-contracts";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { buildEffectiveToolInventoryEntries } from "../../agents/tools-effective-inventory-build.js";
|
||||
import type { AnyAgentTool } from "../../agents/tools/common.js";
|
||||
import type { PluginToolMetadataRegistration } from "../host-hooks.js";
|
||||
import { createEmptyPluginRegistry } from "../registry-empty.js";
|
||||
import { setActivePluginRegistry } from "../runtime.js";
|
||||
import { createPluginRecord } from "../status.test-helpers.js";
|
||||
import { setPluginToolMeta } from "../tools.js";
|
||||
|
||||
describe("plugin tool metadata registration", () => {
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createEmptyPluginRegistry());
|
||||
});
|
||||
|
||||
it("snapshots metadata before effective tool inventory projection", () => {
|
||||
let toolNameReads = 0;
|
||||
let displayNameReads = 0;
|
||||
let descriptionReads = 0;
|
||||
let riskReads = 0;
|
||||
let tagsReads = 0;
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "volatile-metadata",
|
||||
name: "Volatile Metadata",
|
||||
contracts: { tools: ["volatile_tool"] },
|
||||
}),
|
||||
register(api) {
|
||||
api.registerToolMetadata({
|
||||
get toolName() {
|
||||
toolNameReads += 1;
|
||||
if (toolNameReads > 1) {
|
||||
throw new Error("toolName getter re-read");
|
||||
}
|
||||
return "volatile_tool";
|
||||
},
|
||||
get displayName() {
|
||||
displayNameReads += 1;
|
||||
if (displayNameReads > 1) {
|
||||
throw new Error("displayName getter re-read");
|
||||
}
|
||||
return "Volatile Tool";
|
||||
},
|
||||
get description() {
|
||||
descriptionReads += 1;
|
||||
if (descriptionReads > 1) {
|
||||
throw new Error("description getter re-read");
|
||||
}
|
||||
return "Stable metadata description.";
|
||||
},
|
||||
get risk() {
|
||||
riskReads += 1;
|
||||
if (riskReads > 1) {
|
||||
throw new Error("risk getter re-read");
|
||||
}
|
||||
return "medium";
|
||||
},
|
||||
get tags() {
|
||||
tagsReads += 1;
|
||||
if (tagsReads > 1) {
|
||||
throw new Error("tags getter re-read");
|
||||
}
|
||||
return ["metadata", "fixture"];
|
||||
},
|
||||
} as PluginToolMetadataRegistration);
|
||||
},
|
||||
});
|
||||
setActivePluginRegistry(registry.registry);
|
||||
|
||||
const tool = {
|
||||
name: "volatile_tool",
|
||||
label: "Raw label",
|
||||
description: "Raw description",
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: async () => ({ text: "ok" }),
|
||||
} as unknown as AnyAgentTool;
|
||||
setPluginToolMeta(tool, { pluginId: "volatile-metadata", optional: false });
|
||||
|
||||
expect(buildEffectiveToolInventoryEntries([tool])).toEqual([
|
||||
{
|
||||
id: "volatile_tool",
|
||||
label: "Volatile Tool",
|
||||
description: "Stable metadata description.",
|
||||
rawDescription: "Stable metadata description.",
|
||||
source: "plugin",
|
||||
pluginId: "volatile-metadata",
|
||||
risk: "medium",
|
||||
tags: ["metadata", "fixture"],
|
||||
},
|
||||
]);
|
||||
expect(toolNameReads).toBe(1);
|
||||
expect(displayNameReads).toBe(1);
|
||||
expect(descriptionReads).toBe(1);
|
||||
expect(riskReads).toBe(1);
|
||||
expect(tagsReads).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -2274,8 +2274,48 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
});
|
||||
};
|
||||
|
||||
const readToolMetadataFields = (
|
||||
record: PluginRecord,
|
||||
metadata: PluginToolMetadataRegistration,
|
||||
):
|
||||
| {
|
||||
toolName: unknown;
|
||||
displayName: unknown;
|
||||
description: unknown;
|
||||
risk: unknown;
|
||||
tags: unknown;
|
||||
}
|
||||
| undefined => {
|
||||
let toolName: unknown;
|
||||
try {
|
||||
toolName = metadata.toolName;
|
||||
return {
|
||||
toolName,
|
||||
displayName: metadata.displayName,
|
||||
description: metadata.description,
|
||||
risk: metadata.risk,
|
||||
tags: metadata.tags,
|
||||
};
|
||||
} catch (error) {
|
||||
const normalizedToolName = normalizeOptionalHostHookString(toolName);
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message:
|
||||
`tool metadata registration has unreadable fields` +
|
||||
`${normalizedToolName ? `: ${normalizedToolName}` : ""}: ${formatErrorMessage(error)}`,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const registerToolMetadata = (record: PluginRecord, metadata: PluginToolMetadataRegistration) => {
|
||||
const toolName = normalizeHostHookString(metadata.toolName);
|
||||
const fields = readToolMetadataFields(record, metadata);
|
||||
if (!fields) {
|
||||
return;
|
||||
}
|
||||
const toolName = normalizeHostHookString(fields.toolName);
|
||||
if (!toolName) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
@@ -2317,14 +2357,16 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const displayName = normalizeOptionalHostHookString(metadata.displayName);
|
||||
const description = normalizeOptionalHostHookString(metadata.description);
|
||||
const tags = normalizeHostHookStringList(metadata.tags);
|
||||
const displayName = normalizeOptionalHostHookString(fields.displayName);
|
||||
const description = normalizeOptionalHostHookString(fields.description);
|
||||
const tags = normalizeHostHookStringList(fields.tags);
|
||||
const risk = fields.risk;
|
||||
if (
|
||||
displayName === "" ||
|
||||
description === "" ||
|
||||
tags === null ||
|
||||
(metadata.risk !== undefined && !["low", "medium", "high"].includes(metadata.risk))
|
||||
(risk !== undefined &&
|
||||
(typeof risk !== "string" || !["low", "medium", "high"].includes(risk)))
|
||||
) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
@@ -2338,10 +2380,12 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
pluginId: record.id,
|
||||
pluginName: record.name,
|
||||
metadata: {
|
||||
...metadata,
|
||||
toolName,
|
||||
...(displayName !== undefined ? { displayName } : {}),
|
||||
...(description !== undefined ? { description } : {}),
|
||||
...(risk !== undefined
|
||||
? { risk: risk as NonNullable<PluginToolMetadataRegistration["risk"]> }
|
||||
: {}),
|
||||
...(tags !== undefined ? { tags } : {}),
|
||||
},
|
||||
source: record.source,
|
||||
|
||||
Reference in New Issue
Block a user