Skip to content
English

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();
}

接口参考、使用示例与开发指南。