Skip to content
中文

Text registration and strict header parsing ​

The policy implementation follows.

ts
export function defineTextSkillLoader(
  definition: TextSkillLoaderDefinition,
): import('../structs/kits/text-skills.js').TextSkillLoader {
  const { maxInstructionChars, maxDescriptionChars, maxIdChars = 64 } = definition;
  breakWhile([
    {
      occur: () =>
        ![maxInstructionChars, maxDescriptionChars, maxIdChars].every(Number.isSafeInteger),
      error: 'Invalid text skill limits',
      tags: ['integer'],
    },
    {
      occur: () => Math.min(maxInstructionChars, maxDescriptionChars, maxIdChars) < 1,
      error: 'Invalid text skill limits',
      tags: ['range'],
    },
  ]);
  const idPattern = new RegExp(`^[a-z][a-z0-9-]{0,${maxIdChars - 1}}$`);
  const entryFor = definition.entryFor?.bind(definition) ?? ((id) => `${id}/SKILL.md`);
  const parseHeader = definition.parseHeader?.bind(definition) ?? strictHeader;
  const catalogError =
    (typeof definition.errors?.catalog === 'function'
      ? definition.errors.catalog.bind(definition.errors)
      : definition.errors?.catalog) ?? 'Text skill catalog must be an array';
  const registrationError =
    (typeof definition.errors?.registration === 'function'
      ? definition.errors.registration.bind(definition.errors)
      : definition.errors?.registration) ?? 'Invalid text skill registration';
  const documentError =
    (typeof definition.errors?.document === 'function'
      ? definition.errors.document.bind(definition.errors)
      : definition.errors?.document) ?? 'Invalid text skill document';
  const tags = [...(definition.tags ?? [])];

  return Object.freeze({
    load(input: { entries: unknown }, ports: TextSkillReadPorts): readonly TextSkill[] {
      breakWhile(!input || typeof input !== 'object', catalogError);
      const entries = input.entries,
        read = ports.read.bind(ports);
      breakWhile(!Array.isArray(entries), catalogError);
      const ids = new Set<string>();
      return Object.freeze(
        (entries as Record<string, unknown>[]).map((raw) => {
          breakWhile(!raw || typeof raw !== 'object' || Array.isArray(raw), registrationError, [
            ...tags,
            'registration',
            'shape',
          ]);
          const entry = {
            id: raw.id,
            entry: raw.entry,
            kind: raw.kind,
            toolIds: raw.toolIds,
          } as unknown as TextSkillRegistration;
          breakWhile(
            [
              {
                occur: () => !entry || typeof entry !== 'object',
                error: registrationError,
                tags: ['shape'],
              },
              {
                occur: [
                  () => typeof entry.id !== 'string' || !idPattern.test(entry.id),
                  () => ids.has(entry.id),
                ],
                error: registrationError,
                tags: ['id'],
              },
              {
                occur: [
                  () =>
                    entry.entry !==
                    synchronous(entryFor(entry.id), 'Skill entry selectors must be synchronous'),
                  () => entry.kind !== 'text-workflow',
                ],
                error: registrationError,
                tags: ['workflow'],
              },
              {
                occur: [
                  () => !Array.isArray(entry.toolIds),
                  () => entry.toolIds.some((tool) => typeof tool !== 'string' || !tool),
                  () => new Set(entry.toolIds).size !== entry.toolIds.length,
                ],
                error: registrationError,
                tags: ['tools'],
              },
            ],
            [...tags, 'registration'],
          );
          ids.add(entry.id);
          const instructions = synchronous(read(entry.entry), 'Skill reads must be synchronous');
          breakWhile(
            typeof instructions !== 'string' || instructions.length > maxInstructionChars,
            documentError,
            [...tags, 'document', 'instructions'],
          );
          const header = synchronous(
            parseHeader(instructions),
            'Skill header parsing must be synchronous',
          );
          breakWhile(
            [
              {
                occur: [() => !header, () => header!.id !== entry.id],
                error: documentError,
                tags: ['identity'],
              },
              {
                occur: () =>
                  !header!.description.trim() || header!.description.length > maxDescriptionChars,
                error: documentError,
                tags: ['description'],
              },
            ],
            [...tags, 'document'],
          );
          return Object.freeze({
            id: entry.id,
            entry: entry.entry,
            kind: entry.kind,
            toolIds: Object.freeze([...entry.toolIds]),
            description: header!.description.trim(),
            instructions,
          });
        }),
      );
    },
  });
}

function strictHeader(instructions: string): TextSkillHeader | undefined {
  const header = instructions.match(
    /^---\r?\nname: ([a-z][a-z0-9-]*)\r?\ndescription: ([^\r\n]+)\r?\n---(?:\r?\n|$)/,
  );
  return header ? { id: header[1], description: header[2] } : undefined;
}

API reference, usage examples and developer guides.