mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(agents): guard system prompt tool schema stats
This commit is contained in:
@@ -216,4 +216,83 @@ describe("buildSystemPromptReport", () => {
|
||||
});
|
||||
expect(report.tools.entries[0]?.schemaHash).toMatch(/^[a-f0-9]{64}$/u);
|
||||
});
|
||||
|
||||
it("keeps reporting when a tool schema getter throws", () => {
|
||||
const file = makeBootstrapFile({ path: "/tmp/workspace/AGENTS.md" });
|
||||
const brokenTool = {
|
||||
name: "broken",
|
||||
description: "Broken schema",
|
||||
};
|
||||
Object.defineProperty(brokenTool, "parameters", {
|
||||
get() {
|
||||
throw new Error("schema getter exploded");
|
||||
},
|
||||
});
|
||||
|
||||
const report = buildSystemPromptReport({
|
||||
source: "run",
|
||||
generatedAt: 0,
|
||||
bootstrapMaxChars: 20_000,
|
||||
systemPrompt: "system",
|
||||
bootstrapFiles: [file],
|
||||
injectedFiles: [],
|
||||
skillsPrompt: "",
|
||||
tools: [
|
||||
brokenTool,
|
||||
{
|
||||
name: "healthy",
|
||||
description: "Healthy schema",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: { path: { type: "string" } },
|
||||
},
|
||||
},
|
||||
] as never,
|
||||
});
|
||||
|
||||
expect(report.tools.entries).toHaveLength(2);
|
||||
expect(report.tools.entries[0]).toMatchObject({
|
||||
name: "broken",
|
||||
schemaChars: 0,
|
||||
propertiesCount: null,
|
||||
});
|
||||
expect(report.tools.entries[1]).toMatchObject({
|
||||
name: "healthy",
|
||||
propertiesCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps reporting when a tool schema properties getter throws", () => {
|
||||
const file = makeBootstrapFile({ path: "/tmp/workspace/AGENTS.md" });
|
||||
const schema = { type: "object" };
|
||||
Object.defineProperty(schema, "properties", {
|
||||
get() {
|
||||
throw new Error("properties getter exploded");
|
||||
},
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
const report = buildSystemPromptReport({
|
||||
source: "run",
|
||||
generatedAt: 0,
|
||||
bootstrapMaxChars: 20_000,
|
||||
systemPrompt: "system",
|
||||
bootstrapFiles: [file],
|
||||
injectedFiles: [],
|
||||
skillsPrompt: "",
|
||||
tools: [
|
||||
{
|
||||
name: "broken",
|
||||
description: "Broken schema",
|
||||
parameters: schema,
|
||||
},
|
||||
] as never,
|
||||
});
|
||||
|
||||
expect(report.tools.entries[0]).toMatchObject({
|
||||
name: "broken",
|
||||
schemaChars: 0,
|
||||
propertiesCount: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ function parseSkillBlocks(skillsPrompt: string): Array<{ name: string; blockChar
|
||||
}
|
||||
|
||||
function buildToolSchemaStats(
|
||||
parameters: AgentTool["parameters"],
|
||||
parameters: AgentTool["parameters"] | undefined,
|
||||
): Pick<ToolReportEntry, "propertiesCount" | "schemaChars" | "schemaHash"> {
|
||||
if (!parameters || typeof parameters !== "object") {
|
||||
return { schemaChars: 0, schemaHash: sha256(""), propertiesCount: null };
|
||||
@@ -67,14 +67,7 @@ function buildToolSchemaStats(
|
||||
const stats = {
|
||||
schemaChars: schemaJson.length,
|
||||
schemaHash: sha256(schemaJson),
|
||||
propertiesCount: (() => {
|
||||
const schema = parameters as Record<string, unknown>;
|
||||
const props = typeof schema.properties === "object" ? schema.properties : null;
|
||||
if (!props || typeof props !== "object") {
|
||||
return null;
|
||||
}
|
||||
return Object.keys(props as Record<string, unknown>).length;
|
||||
})(),
|
||||
propertiesCount: countToolSchemaProperties(parameters),
|
||||
};
|
||||
// Tool parameter objects are reused across runs; cache their stable size/hash
|
||||
// so report generation stays cheap during frequent prompt rebuilds.
|
||||
@@ -82,6 +75,27 @@ function buildToolSchemaStats(
|
||||
return stats;
|
||||
}
|
||||
|
||||
function countToolSchemaProperties(parameters: object): number | null {
|
||||
try {
|
||||
const schema = parameters as Record<string, unknown>;
|
||||
const props = typeof schema.properties === "object" ? schema.properties : null;
|
||||
if (!props || typeof props !== "object") {
|
||||
return null;
|
||||
}
|
||||
return Object.keys(props as Record<string, unknown>).length;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readToolParameters(tool: AgentTool): AgentTool["parameters"] | undefined {
|
||||
try {
|
||||
return tool.parameters;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function buildToolsEntries(tools: AgentTool[]): SessionSystemPromptReport["tools"]["entries"] {
|
||||
return tools.map((tool) => {
|
||||
const cached = toolReportEntryCache.get(tool);
|
||||
@@ -91,7 +105,7 @@ function buildToolsEntries(tools: AgentTool[]): SessionSystemPromptReport["tools
|
||||
const name = tool.name;
|
||||
const summary = tool.description?.trim() || tool.label?.trim() || "";
|
||||
const summaryChars = summary.length;
|
||||
const schemaStats = buildToolSchemaStats(tool.parameters);
|
||||
const schemaStats = buildToolSchemaStats(readToolParameters(tool));
|
||||
const entry = { name, summaryChars, summaryHash: sha256(summary), ...schemaStats };
|
||||
toolReportEntryCache.set(tool, entry);
|
||||
return entry;
|
||||
|
||||
Reference in New Issue
Block a user