Tool 校验与封存
策略的具体实现如下。
ts
export class IntentRegistry<Definition extends IntentToolRegistration = IntentToolRegistration> {
/** Create and seal a complete catalog in one call. */
static readonly create: typeof createIntentRegistry = createIntentRegistry;
private readonly byId = new Map<string, RegisteredIntent<Definition>>();
private sealed = false;
private modelView?: ModelToolRegistry;
private readonly policy: IntentPolicy<Definition>;
/**
* @param policy - Optional id pattern, vocabulary lists, error wording and assertion hook.
* Lists and the pattern are copied so later mutation of the input cannot weaken checks.
*/
constructor(policy: IntentPolicy<Definition> = {}) {
const pattern = policy.idPattern;
const errorPorts = policy.errors,
{ sealed, invalid, duplicate } = errorPorts ?? {};
const listFields = [
'scopeModes',
'targetModes',
'contextTiers',
'actions',
'delegates',
'handoffValues',
'tags',
] as const;
const lists = Object.fromEntries(
listFields.map((key) => {
const value = policy[key];
return [key, value === undefined ? undefined : Object.freeze([...value])];
}),
) as Pick<IntentPolicy<Definition>, (typeof listFields)[number]>;
this.policy = Object.freeze({
...registrationData(policy, ['idPattern', 'assertDefinition', 'errors', ...listFields]),
...lists,
idPattern: pattern ? new RegExp(pattern.source, pattern.flags) : undefined,
errors:
errorPorts &&
Object.freeze({
sealed: typeof sealed === 'function' ? sealed.bind(errorPorts) : sealed,
invalid: typeof invalid === 'function' ? invalid.bind(errorPorts) : invalid,
duplicate: duplicate?.bind(errorPorts),
}),
assertDefinition: policy.assertDefinition?.bind(policy),
});
}
/**
* Validate, freeze and add one domain Tool.
*
* Checks run in a fixed order — sealed, identity, context contract, auxiliary actions,
* handoff, plan, delegates — so the reported violation is stable when several rules fail.
*
* @param input - Goal declaration; the App's own assertion receives this original object,
* not the compiled copy, so helpers and prototype methods keep working.
* @returns This registry, for chaining.
* @throws A configured error for a sealed registry, invalid identity or vocabulary values,
* empty/duplicate delegates, or a duplicate Tool id.
*/
register(input: Definition): this {
const id: Definition['id'] = input.id;
const definition = {
...registrationData(input, [
'id',
'description',
'delegates',
'scopePolicy',
'targetPolicy',
'contextTier',
'planIntent',
'auxiliaryActions',
'preparedHandoff',
]),
id,
description: input.description,
delegates:
(typeof input.asIntent === 'object' && input.asIntent !== null
? input.asIntent.delegates
: undefined) ??
input.delegates ??
[],
scopePolicy: input.scopePolicy,
targetPolicy: input.targetPolicy,
contextTier: input.contextTier,
planIntent: input.planIntent,
auxiliaryActions: input.auxiliaryActions,
preparedHandoff: input.preparedHandoff,
};
const policy = this.policy;
const invalid = policy.errors?.invalid ?? 'Invalid intent Tool definition';
const tags = policy.tags ?? [];
const validName = (name: string) => {
if (typeof name !== 'string' || !name.trim()) return false;
if (!policy.idPattern) return true;
policy.idPattern.lastIndex = 0;
return policy.idPattern.test(name);
};
breakWhile(
[
{
occur: () => this.sealed,
error: policy.errors?.sealed ?? 'Intent registry is sealed',
tags: ['sealed'],
},
{
occur: [() => !validName(definition.id), () => !definition.description.trim()],
error: invalid,
tags: ['identity'],
},
{
occur: [
() =>
policy.scopeModes !== undefined &&
!policy.scopeModes.includes(definition.scopePolicy!),
() =>
policy.targetModes !== undefined &&
!policy.targetModes.includes(definition.targetPolicy!),
() =>
policy.contextTiers !== undefined &&
!policy.contextTiers.includes(definition.contextTier!),
],
error: invalid,
tags: ['context-contract'],
},
{
occur: [
() => definition.auxiliaryActions !== undefined && !definition.auxiliaryActions.length,
() =>
definition.auxiliaryActions !== undefined &&
definition.auxiliaryActions.some(
(action) => policy.actions !== undefined && !policy.actions.includes(action),
),
() =>
definition.auxiliaryActions !== undefined &&
new Set(definition.auxiliaryActions).size !== definition.auxiliaryActions.length,
],
error: invalid,
tags: ['auxiliary-actions'],
},
{
occur: () =>
definition.preparedHandoff !== undefined &&
policy.handoffValues !== undefined &&
!policy.handoffValues.includes(definition.preparedHandoff),
error: invalid,
tags: ['handoff'],
},
{
occur: () =>
policy.actions !== undefined && !policy.actions.includes(definition.planIntent!),
error: invalid,
tags: ['plan'],
},
{
occur: [
() =>
(input.delegates !== undefined ||
(typeof input.asIntent === 'object' &&
input.asIntent !== null &&
input.asIntent.delegates !== undefined)) &&
!definition.delegates.length,
() =>
definition.delegates.some(
(delegate) =>
policy.delegates !== undefined && !policy.delegates.includes(delegate),
),
() => new Set(definition.delegates).size !== definition.delegates.length,
],
error: invalid,
tags: ['delegates'],
},
],
tags,
);
// App assertions may use implementation helpers; never pass a compiled view as the source
// instance.
assertion(
policy.assertDefinition?.(input),
'Registry definition assertions must return void synchronously',
);
breakWhile(
this.byId.has(definition.id),
() =>
policy.errors?.duplicate?.(definition.id) ?? new Error(`Duplicate tool: ${definition.id}`),
[...tags, 'duplicate'],
);
this.byId.set(
definition.id,
Object.freeze({
...definition,
...new ModelToolRegistry([
{
id: input.id,
description: input.description,
active: typeof input.active === 'function' ? input.active.bind(input) : input.active,
asIntent:
typeof input.asIntent === 'function' ? input.asIntent.bind(input) : input.asIntent,
inputSchema: input.inputSchema ?? {
jsonSchema: { type: 'object' },
parse(value: unknown) {
if (!value || typeof value !== 'object' || Array.isArray(value))
throw new TypeError('Tool input must be an object');
return value;
},
},
resolve:
input.resolve?.bind(input) ?? ((value: unknown) => ({ toolId: id, input: value })),
},
]).require(input.id),
delegates: Object.freeze([...definition.delegates]),
...(definition.auxiliaryActions
? { auxiliaryActions: Object.freeze([...definition.auxiliaryActions]) }
: {}),
}) as RegisteredIntent<Definition>,
);
this.modelView = undefined;
return this;
}
/** Register every domain Tool in iteration order. */
registerAll(definitions: Iterable<Definition>): this {
for (const definition of definitions) this.register(definition);
return this;
}
/** Registered domain Tool for `id`, or `undefined` when it is absent. */
get(goal: string): RegisteredIntent<Definition> | undefined {
return this.byId.get(goal);
}
/** Whether a Tool id is registered. */
has(goal: string): boolean {
return this.byId.has(goal);
}
/** Registered Tool ids in registration order. */
ids(): readonly IntentId<Definition>[] {
return Object.freeze([...this.byId.keys()]) as readonly IntentId<Definition>[];
}
/** Registered goals in registration order. */
get definitions(): readonly RegisteredIntent<Definition>[] {
return Object.freeze([...this.byId.values()]);
}
/** Iterate the registered goals. */
values() {
return this.byId.values();
}
/** Demand discovery uses tool metadata; domain fields remain attached to these same IDs. */
withIntent(context: unknown): ModelToolRegistry {
return this.models().withIntent(context);
}
/** Resolve a grouped transport's selected Tool through that Tool's own parser and resolver. */
resolve(
selection: import('../../structs/capabilities/model.js').ModelSelection,
context: Readonly<{ stageId: string }>,
): unknown {
return this.models().resolve(selection, context);
}
/** Explicit node selection uses the same immutable model projection. */
select(ids: readonly string[], ...context: [] | [unknown]): ModelToolRegistry {
return context.length ? this.models().select(ids, context[0]) : this.models().select(ids);
}
/** Cache only immutable declarations; request activation is evaluated by each projection. */
private models(): ModelToolRegistry {
return (this.modelView ??= new ModelToolRegistry(this.byId.values()).seal());
}
/** Iterate `[id, tool]` pairs. */
entries() {
return this.byId.entries();
}
/**
* Registered domain Tool for `id`.
*
* @throws TypeError When the id is not registered.
*/
require(id: string): RegisteredIntent<Definition> {
const value = this.byId.get(id);
if (!value) throw new TypeError(`Unknown tool: ${id}`);
return value;
}
/** Whether the Tool declares this delegate; unknown goals allow nothing. */
allows(goal: string, delegate: string): boolean {
return this.byId.get(goal)?.delegates.includes(delegate) ?? false;
}
/**
* Copy the selected goals out of the registry.
*
* @param goals - Ids to include; the default is every registered id, in registry order.
* @returns Descriptions whose arrays are copied so callers cannot mutate registry state.
*/
describe(goals: readonly string[] = this.ids()): IntentDescription<Definition>[] {
const selected = new Set(goals);
return [...this.byId.values()]
.filter((item) => selected.has(item.id))
.map((item) => ({
...item,
delegates: [...item.delegates],
})) as IntentDescription<Definition>[];
}
/** Finalize the registry; no registration is accepted afterwards. */
seal(): this {
this.sealed = true;
return this;
}
}
export function createIntentRegistry<Definition extends IntentToolRegistration>(
definitions: Iterable<Definition>,
policy: IntentPolicy<Definition> = {},
): IntentRegistry<Definition> {
return new IntentRegistry(policy).registerAll(definitions).seal();
}