这是开发预览网站。请访问正式文档 lynxjs.org

A2UI Catalog Extractor

English | 简体中文

@lynx-js/genui/a2ui-catalog-extractorgenui a2ui generate catalog 背后的内部 TypeDoc extraction engine。它会把 TypeScript 组件接口转换成 A2UI 组件 catalog JSON。你只需要用 TypeScript interface 写一次组件的公开契约, 用普通 TypeDoc 注释描述字段,然后通过公开的 genui a2ui 命令生成 A2UI agent 可以读取的 JSON Schema。

用户脚本请使用 genui a2ui generate catalog。这个包主要作为 extraction 行为和测试的实现层。

它解决什么问题

A2UI catalog 用来描述 renderer 支持哪些组件。对每个组件,catalog 会告诉 agent 哪些 props 合法、哪些 props 必填、哪些 enum 值可用,以及每个字段的 含义。

这个 extractor 生成 A2UI v0.9 catalog 中的 components 部分:

{
  "QuickStartCard": {
    "properties": {
      "title": { "type": "string" }
    },
    "required": ["title"]
  }
}

它也可以通过 createA2UICatalog 把生成的 components 包装进带 catalogIdfunctionstheme 的完整 catalog 对象。

它不做什么

  • 它不渲染 A2UI UI。
  • 它不手写解析 TypeScript 源码文本。
  • 它不直接使用 TypeScript compiler API。
  • 它不要求你在注释里写 JSON Schema。
  • 它不会展开任意导入的 type alias 或外部 interface。
  • 它不会调用 LLM,也不会替你选择模型。

这个包消费 TypeDoc reflection 数据。这样实现更小,但也意味着面向 catalog 的类型形状应该直接内联写在被标记的 interface 中。

环境要求

  • Node.js 22 或更新版本。
  • TypeDoc 可以读取的 TypeScript 或 TSX 源文件。
  • 每个 catalog 组件契约使用一个 TypeScript interface

安装

包管理器

安装 @lynx-js/genui 后执行公开 CLI:

genui a2ui --help

然后在你的 package 中加入脚本:

{
  "scripts": {
    "build:catalog": "genui a2ui generate catalog --catalog-dir src/catalog --out-dir dist"
  }
}

运行:

pnpm build:catalog

快速开始

这个示例会完整演示如何从 TypeScript interface 生成 catalog JSON。

1. 创建面向 catalog 的 interface

创建 src/catalog/QuickStartCard.tsx

/**
 * Quick start card.
 *
 * @remarks Use this contract as a compact card example.
 * @a2uiCatalog QuickStartCard
 */
export interface QuickStartCardProps {
  /** Card title text or data binding. */
  title: string | { path: string };
  /** Visual tone used by the renderer. */
  tone?: 'neutral' | 'accent';
  /**
   * Tags shown below the title.
   *
   * @defaultValue `[]`
   */
  tags?: string[];
  /** Author metadata rendered in the card footer. */
  author: {
    /** Display name. */
    name: string;
    /** Optional profile URL. */
    url?: string;
  };
  /**
   * Extra analytics context sent with user actions.
   *
   * @defaultValue `{}`
   */
  context?: Record<string, string | number | boolean>;
}

最关键的是 @a2uiCatalog QuickStartCard。它告诉 extractor:这个 interface 应该生成名为 QuickStartCard 的 catalog 组件。

2. 生成 catalog 文件

运行:

genui a2ui generate catalog --catalog-dir src/catalog --out-dir dist

extractor 会扫描 catalog 目录,找到带 @a2uiCatalog 的 interface,并为每个 组件写出一个文件:

dist/
  catalog.json
dist/catalog/
  QuickStartCard/
    catalog.json

3. 查看生成的 schema

dist/catalog/QuickStartCard/catalog.json 会类似下面这样:

{
  "QuickStartCard": {
    "properties": {
      "title": {
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "object",
            "properties": {
              "path": {
                "type": "string"
              }
            },
            "required": [
              "path"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Card title text or data binding."
      },
      "tone": {
        "type": "string",
        "enum": [
          "neutral",
          "accent"
        ],
        "description": "Visual tone used by the renderer."
      },
      "tags": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "Tags shown below the title.",
        "default": []
      },
      "author": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "url": {
            "type": "string",
            "description": "Optional profile URL."
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "description": "Author metadata rendered in the card footer."
      },
      "context": {
        "type": "object",
        "additionalProperties": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "number"
            },
            {
              "type": "boolean"
            }
          ]
        },
        "description": "Extra analytics context sent with user actions.",
        "default": {}
      }
    },
    "required": [
      "title",
      "author"
    ],
    "description": "Quick start card.\n\nUse this contract as a compact card example."
  }
}

注意这些转换:

  • title 没有 ?,所以是必填。
  • tone 变成字符串 enum。
  • tags?: string[] 变成可选的字符串数组。
  • author 变成严格的内联对象,并带有 additionalProperties: false
  • context?: Record<string, string | number | boolean> 变成 object map,并用 additionalProperties 描述值类型。
  • TypeDoc 注释变成 JSON Schema description。

编写规则

只标记 catalog 契约

只有 TypeScript interface reflection 会被转换。把 @a2uiCatalog 放在 agent 被允许发送的 props interface 上:

/**
 * @a2uiCatalog Text
 */
export interface TextProps {
  text: string;
}

不要把 tag 放在组件函数上:

export function Text(_props: TextProps) {
  return null;
}

组件名

你可以显式写组件名:

/**
 * @a2uiCatalog Text
 */
export interface TextProps {}

如果 tag 内容为空,extractor 会从 interface 名推断组件名,规则是去掉结尾的 PropsComponentProps

/**
 * @a2uiCatalog
 */
export interface DemoTextProps {}

这会生成 DemoText

注释会变成 schema 元数据

使用普通 TypeDoc 注释:

/**
 * User-facing card.
 *
 * @remarks Use this for compact summaries.
 * @a2uiCatalog SummaryCard
 */
export interface SummaryCardProps {
  /**
   * Optional display density.
   *
   * @defaultValue `"comfortable"`
   */
  density?: 'compact' | 'comfortable';
}

extractor 的映射规则如下:

TypeDoc 注释JSON Schema 输出
summary 文本description
@remarks追加到 description
@defaultValue@defaultdefault
@deprecateddeprecated: true
可选属性 ?不放入 required

对象和数组默认值建议把 JSON 放在 code span 里:

/**
 * @defaultValue `{}`
 */
context?: Record<string, string>;

如果不用 code span,TypeDoc 可能传入格式化后的文本,而不是原始 JSON 值。

支持的 TypeScript 形状

TypeScript 形状JSON Schema 形状
string{ "type": "string" }
number{ "type": "number" }
boolean{ "type": "boolean" }
'a' | 'b'{ "type": "string", "enum": ["a", "b"] }
string | { path: string }{ "oneOf": [...] }
T[]{ "type": "array", "items": ... }
Array<T>{ "type": "array", "items": ... }
ReadonlyArray<T>{ "type": "array", "items": ... }
{ name: string }additionalProperties: false 的严格对象
Record<string, T>additionalProperties: ... 的 object map

不支持或含义不明确的类型

这些类型会故意报错:

  • any
  • unknown
  • null
  • undefined
  • never
  • void
  • string | null 这样的 nullable union
  • 大多数导入的 alias 和被引用的外部 interface
  • Record<number, T> 或其他非 string record key

建议写明确的 catalog 契约:

// 不建议在 catalog-facing interface 中这样写。
type ExternalCardData = {
  title: string;
};

export interface CardProps {
  data: ExternalCardData;
}

改成内联形状:

export interface CardProps {
  data: {
    title: string;
  };
}

CLI 参考

公开 CLI 入口是 genui a2ui generate catalog。它会在内部委托给这个包。

生成 catalog artifacts

genui a2ui generate catalog [options]
选项说明默认值
--catalog-dir <dir>要扫描的源码目录。可重复。src/catalog
--source <path>要扫描的源码文件或目录。可重复。
--typedoc-json <file>读取已有 TypeDoc JSON project,不重新运行 TypeDoc conversion。
--out-dir <dir>写出 catalog artifacts 的根目录。dist
--catalog-id <id>写入全集 catalog 文件的 catalog ID。catalog.json
--version, -v打印包版本。
--help, -h打印用法。

--source--catalog-dir 可以一起使用。extractor 会合并全部输入、去重、 排序,然后运行 TypeDoc。

extractor 会同时写出 dist/catalog/QuickStartCard/catalog.json 这类单组件文件, 以及 dist/catalog.json 全集 catalog 文件。

扫描器接受 .ts.tsx.js.jsx.mts.cts 文件。它会忽略 .d.tsnode_modulesdist.turbo

仓库内部编程 API

包入口会刻意只暴露仓库工具和测试所需的提取 helper。它不是给产品代码接入的外部集成面。 产品构建脚本应该调用 genui a2ui generate catalog,而不是直接 import 这个包。

import {
  createA2UICatalog,
  extractCatalogComponents,
  extractCatalogFunctions,
  findCatalogSourceFiles,
} from '@lynx-js/genui/a2ui-catalog-extractor';

const sourceFiles = findCatalogSourceFiles('src/catalog');
const components = await extractCatalogComponents({
  sourceFiles,
  tsconfig: 'tsconfig.json',
});
const functions = await extractCatalogFunctions({
  sourceFiles,
  tsconfig: 'tsconfig.json',
});

const catalog = createA2UICatalog({
  catalogId: 'https://example.com/catalogs/basic/v1/catalog.json',
  components,
  functions,
  theme: {
    accentColor: { type: 'string' },
  },
});

如果路径需要相对某个项目目录解析,可以在提取选项中使用 cwd。artifact 写入和 TypeDoc JSON 复用属于 CLI 实现细节;这些流程请使用 genui a2ui generate catalog

故障排查和 FAQ

Unsupported ambiguous intrinsic TypeDoc type "unknown"

catalog 需要明确 schema。把 unknownany 改成具体类型:

// 会失败。
payload: unknown;

// 可以工作。
payload: {
  id: string;
  count: number;
}

Unsupported nullable union

nullable union 不被接受:

// 会失败。
label: string | null;

如果字段可以省略,把它设为可选:

label?: string;

或者显式建模状态:

label: string | { path: string };

Unsupported TypeDoc reference

extractor 只理解少量 reference:Array<T>ReadonlyArray<T>Record<string, T>。请在 catalog-facing interface 中内联对象形状,不要导入 alias。

输出目录为空

检查这些点:

  • 被扫描文件里有 interface,而不只是 type
  • interface 带有 @a2uiCatalog
  • 传给 --catalog-dir--source 的路径存在。
  • 文件不是 .d.ts
  • TypeDoc 可以用你的 tsconfig 解析这些文件。

生成的 schema 为什么没有继承来的 props

继承成员会被跳过。这是有意设计,因为 renderer context 这类运行时字段不应该 成为 agent-facing catalog 的一部分。请把所有面向 catalog 的 props 直接写在被 标记的 interface 上。

我应该手写 JSON Schema 吗

不应该。请把契约保留在 TypeScript 和注释里。手写 schema 很容易和组件 props 漂移,而这个包会让 catalog 成为可重复生成的构建产物。

这能替代 TypeScript 类型检查吗

不能。TypeDoc conversion 只是用来读取 reflection 数据,不是用来验证完整应用。 请继续运行正常的 TypeScript、lint 和测试命令。

参考资料