Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions website/docs/en/reference/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ disagree, the generated pages are correct.

## How the pages are organized

[/api/](/api/) lists the modules. Each module page is named after the entry point's source file
(`agent-bundle/routes` is compiled from `routes/public.ts`, so its page is `routes/public`; the
root `agent-bundle` export is `index`, served at `/api/index-1` because `/api/` is the module
index itself) and groups its members under `Classes`, `Interfaces`, `Type Aliases`, `Variables`,
and `Functions`. A name an entry point re-exports from another entry point is listed under
[/api/](/api/) lists the modules. Each module page is named after the entry point's source path
with `.ts` and a trailing `/index` dropped (`agent-bundle/routes` is compiled from
`routes/public.ts`, so its page is `routes/public`; `agent-bundle/app` from `app/index.ts` is
`app`; the root `agent-bundle` export is `index`, served at `/api/index-1` because `/api/` is the
module index itself) and groups its members under `Classes`, `Interfaces`, `Type Aliases`,
`Variables`, and `Functions`. A name an entry point re-exports from another entry point is listed under
`References` with a link to the page that documents it — `runEvals` on `index` points to
[`runEvals`](/api/api#runevals) on `api`.

Expand All @@ -65,7 +66,8 @@ this Reference — are complete translations.
## Where to start

- Compiling from a script: [`build`](/api/api#build-2) and [`validate`](/api/api#validate-1) in `agent-bundle/api`.
- Calling typed tools from an MCP App: `createAppClient` in `agent-bundle/app`, explained in
- Calling typed tools from an MCP App: [`createAppClient`](/api/app#createappclient) in
`agent-bundle/app`, explained in
[The typed App client](../guide/authoring/mcp.mdx#the-typed-app-client).
- Writing tests: [`renderRoute`](/api/test#renderroute), [`expectDocument`](/api/test#expectdocument), and the matrices in `agent-bundle/test`, explained in [Testing](../guide/development/testing.mdx).
- Writing evals: [`defineEvalSuite`](/api/eval#defineevalsuite) and the assertions in `agent-bundle/eval`, explained in [Evaluations](../guide/development/evaluations.mdx).
8 changes: 5 additions & 3 deletions website/docs/zh/reference/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ description: '生成的 agent-bundle 类型 API:每个公开入口点一页、

## 页面如何组织

[/zh/api/](/zh/api/) 列出了所有模块。每个模块页以入口点的源文件命名(`agent-bundle/routes` 由
`routes/public.ts` 编译而来,因此其页面是 `routes/public`;根导出 `agent-bundle` 是 `index`,位于
[/zh/api/](/zh/api/) 列出了所有模块。每个模块页以入口点的源路径命名,去掉 `.ts` 与末尾的
`/index`(`agent-bundle/routes` 由 `routes/public.ts` 编译而来,因此其页面是 `routes/public`;
`agent-bundle/app` 由 `app/index.ts` 编译而来,页面是 `app`;根导出 `agent-bundle` 是 `index`,位于
`/zh/api/index-1`,因为 `/zh/api/` 本身就是模块索引),并把成员归入 `Classes`、`Interfaces`、
`Type Aliases`、`Variables` 与 `Functions` 之下。一个入口点从另一个入口点重新导出的名称会列在
`References` 下,并链接到实际记录它的页面——`index` 页上的 `runEvals` 指向
Expand All @@ -59,7 +60,8 @@ description: '生成的 agent-bundle 类型 API:每个公开入口点一页、
## 从哪里开始

- 从脚本中编译:`agent-bundle/api` 中的 [`build`](/zh/api/api#build-2) 与 [`validate`](/zh/api/api#validate-1)。
- 在 MCP App 中调用带类型的工具:`agent-bundle/app` 中的 `createAppClient`,详见
- 在 MCP App 中调用带类型的工具:`agent-bundle/app` 中的
[`createAppClient`](/zh/api/app#createappclient),详见
[带类型的 App 客户端](../guide/authoring/mcp.mdx#带类型的-app-客户端)。
- 编写测试:`agent-bundle/test` 中的 [`renderRoute`](/zh/api/test#renderroute)、[`expectDocument`](/zh/api/test#expectdocument) 与那些矩阵,在[测试](../guide/development/testing.mdx)中介绍。
- 编写 eval:`agent-bundle/eval` 中的 [`defineEvalSuite`](/zh/api/eval#defineevalsuite) 与那些断言,在[评测](../guide/development/evaluations.mdx)中介绍。
41 changes: 26 additions & 15 deletions website/plugins/mirror-api-locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,35 +45,43 @@ function headingText(raw: string): string {

interface PageAnchors {
readonly ids: Set<string>;
readonly memberIds: Set<string>;
readonly members: Map<string, string>;
}

function memberName(text: string): string {
return text.replace(/\(\)$/, '');
}

function collectPageAnchors(markdown: string): PageAnchors {
const slugger = new GithubSlugger();
const ids = new Set<string>();
const memberIds = new Set<string>();
const outsideFences = markdown.replace(/^```[\s\S]*?^```[ \t]*$/gm, '');
const members = new Map<string, string>();
const outsideFences = markdown.replace(/^`{3,}[\s\S]*?^`{3,}[ \t]*$/gm, '');

for (const match of outsideFences.matchAll(/^(#{1,6}) (.+)$/gm)) {
const id = slugger.slug(headingText(match[2]));
const text = headingText(match[2]);
const id = slugger.slug(text);
ids.add(id);
if (match[1].length === 3) {
memberIds.add(id);
members.set(id, memberName(text));
}
}
return { ids, memberIds };
return { ids, members };
}

/**
* TypeDoc reserves some exported names while building its reflection URLs, so
* links to those `### Member` headings receive a spurious `-1`. Rspress runs
* TypeDoc's first anchor pass hands compound slugs to nested reflections
* (`EvalRunStoreError.code` → `evalrunstoreerrorcode`), so a later link to the
* `### EvalRunStoreErrorCode` heading receives a spurious `-1`. Rspress runs
* github-slugger over the rendered headings instead, where the member's first
* occurrence has the unsuffixed anchor, so those links are dead. Rewrite a
* `#name-N` link to `#name` only when the fragment does not exist on the
* target page (the link is actually dead — a legitimate `#protocol-v1` whose
* heading exists is never touched) and `name` is the id of a `###` member
* heading there. Anything else is left as TypeDoc wrote it for the build's
* anchor check to judge.
* heading exists is never touched), `name` is the id of a `###` member
* heading there, and the link's label is that member's exact name: ids are
* case-folded, so `FooCode` and `fooCode` share a base and only the label
* tells which one the link meant. Anything else is left as TypeDoc wrote it
* for the build's anchor check to judge.
*/
async function alignTypeDocMemberLinks(directory: string, files: string[]): Promise<void> {
const anchorsByPage = new Map<string, PageAnchors>();
Expand All @@ -87,13 +95,16 @@ async function alignTypeDocMemberLinks(directory: string, files: string[]): Prom
const filePath = path.resolve(directory, relativePath);
const markdown = await readFile(filePath, 'utf8');
const aligned = markdown.replace(
/(\]\()([^)\s#]*#)([^)\s#]+?)-\d+(\))/g,
(link, opening: string, target: string, base: string, closing: string) => {
/(\[([^\]]*)\]\()([^)\s#]*#)([^)\s#]+?)(-\d+)(\))/g,
(link, opening: string, label: string, target: string, base: string, suffix: string, closing: string) => {
const linkedPath = target.slice(0, -1);
const targetPath = linkedPath ? path.resolve(path.dirname(filePath), linkedPath) : filePath;
const anchors = anchorsByPage.get(targetPath);
const fragment = link.slice(opening.length + target.length, -closing.length);
if (!anchors || anchors.ids.has(fragment) || !anchors.memberIds.has(base)) {
if (
!anchors ||
anchors.ids.has(base + suffix) ||
anchors.members.get(base) !== memberName(headingText(label))
) {
return link;
}
return `${opening}${target}${base}${closing}`;
Expand Down
12 changes: 7 additions & 5 deletions website/scripts/check-built-links.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,14 @@ const main = () => {
if (target === null) continue;
members += 1;
const fragment = decode(member[1]);
const heading = new RegExp(`<h[1-6]\\b[^>]*\\sid="${fragment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"[^>]*>([\\s\\S]*?)</h[1-6]>`).exec(fs.readFileSync(target, 'utf8'));
// Colliding ids differ only in case or level (`build()` member vs `build`
// property, `FooCode` vs `fooCode`), so the label must equal the member
// heading's text exactly, and the heading must be a module member (h3).
const heading = new RegExp(`<h([1-6])\\b[^>]*\\sid="${fragment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"[^>]*>([\\s\\S]*?)</h[1-6]>`).exec(fs.readFileSync(target, 'utf8'));
if (heading === null) continue;
const headingLabel = visibleText(heading[1]).replace(/^#/, '').trim();
if (headingLabel.toLowerCase() !== label.toLowerCase()) {
if (!broken.has(href)) broken.set(href, `${href} — labelled "${label}" but #${fragment} is the heading "${headingLabel}" (e.g. in ${pagePath})`);
}
const headingLabel = visibleText(heading[2]).replace(/^#/, '').replace(/\?$/, '').trim();
const reason = heading[1] !== '3' ? `an <h${heading[1]}>, not a module member` : headingLabel !== label ? `the heading "${headingLabel}"` : null;
if (reason !== null && !broken.has(href)) broken.set(href, `${href} — labelled "${label}" but #${fragment} is ${reason} (e.g. in ${pagePath})`);
}
}
}
Expand Down
Loading