mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-29 17:23:34 +00:00
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
// Plans usable tools from descriptors, availability, and request constraints.
|
|
import { evaluateToolAvailability } from "./availability.js";
|
|
import { ToolPlanContractError } from "./diagnostics.js";
|
|
import type {
|
|
BuildToolPlanOptions,
|
|
HiddenToolPlanEntry,
|
|
ToolDescriptor,
|
|
ToolPlan,
|
|
ToolPlanEntry,
|
|
} from "./types.js";
|
|
|
|
/**
|
|
* Deterministic planner for descriptor-backed tools.
|
|
*
|
|
* The planner sorts descriptors, hides unavailable tools with diagnostics, and
|
|
* throws only when visible tool descriptors violate executor/name contracts.
|
|
*/
|
|
function compareDescriptors(left: ToolDescriptor, right: ToolDescriptor): number {
|
|
return (
|
|
(left.sortKey ?? left.name).localeCompare(right.sortKey ?? right.name) ||
|
|
left.name.localeCompare(right.name)
|
|
);
|
|
}
|
|
|
|
function assertUniqueNames(descriptors: readonly ToolDescriptor[]): void {
|
|
const seen = new Set<string>();
|
|
for (const descriptor of descriptors) {
|
|
if (seen.has(descriptor.name)) {
|
|
throw new ToolPlanContractError({
|
|
code: "duplicate-tool-name",
|
|
toolName: descriptor.name,
|
|
message: `Duplicate tool descriptor name: ${descriptor.name}`,
|
|
});
|
|
}
|
|
seen.add(descriptor.name);
|
|
}
|
|
}
|
|
|
|
/** Build the visible and hidden tool plan for a runtime context. */
|
|
export function buildToolPlan(options: BuildToolPlanOptions): ToolPlan {
|
|
const descriptors = options.descriptors.toSorted(compareDescriptors);
|
|
assertUniqueNames(descriptors);
|
|
|
|
const visible: ToolPlanEntry[] = [];
|
|
const hidden: HiddenToolPlanEntry[] = [];
|
|
|
|
for (const descriptor of descriptors) {
|
|
const diagnostics = [
|
|
...evaluateToolAvailability({ descriptor, context: options.availability }),
|
|
];
|
|
if (diagnostics.length > 0) {
|
|
hidden.push({ descriptor, diagnostics });
|
|
continue;
|
|
}
|
|
if (!descriptor.executor) {
|
|
// Hidden tools may omit executors; visible tools must be callable after planning.
|
|
throw new ToolPlanContractError({
|
|
code: "missing-executor",
|
|
toolName: descriptor.name,
|
|
message: `Visible tool descriptor has no executor ref: ${descriptor.name}`,
|
|
});
|
|
}
|
|
visible.push({ descriptor, executor: descriptor.executor });
|
|
}
|
|
|
|
return { visible, hidden };
|
|
}
|