fix(docs): address documentation site review

This commit is contained in:
Yichen Jiang
2026-07-13 17:47:42 +08:00
parent 341b56ebc3
commit 6be219a0bc
12 changed files with 333 additions and 212 deletions

View File

@@ -4,10 +4,11 @@
## 定义 Config 类型 ## 定义 Config 类型
在插件中导出一个 `Config` 类型和可选的默认值: 在插件中导出一个 `Config` 类型和同名的 Schemastery schema;默认值直接写在 schema 中:
```typescript ```typescript
import type { Context } from 'cordis' import type { Context } from 'cordis'
import Schema from 'schemastery'
export const name = 'my-plugin' export const name = 'my-plugin'
@@ -17,11 +18,11 @@ export interface Config {
verbose?: boolean verbose?: boolean
} }
export const Config = { export const Config: Schema<Config> = Schema.object({
greeting: 'Hello', greeting: Schema.string().default('Hello'),
maxRetries: 3, maxRetries: Schema.number().default(3),
verbose: false, verbose: Schema.boolean().default(false),
} })
export function apply(ctx: Context, config: Config) { export function apply(ctx: Context, config: Config) {
console.log(config.greeting) // 用户配置或默认值 console.log(config.greeting) // 用户配置或默认值
@@ -37,7 +38,7 @@ export function apply(ctx: Context, config: Config) {
maxRetries: 5 maxRetries: 5
``` ```
未提供的字段使用导出的 `Config` 对象中的默认值。 插件加载时,Cordis 会通过导出的 schema 校验配置,并填充未提供字段的默认值。不要导出普通对象作为 `Config`,因为它不满足 Cordis 要求的 Standard Schema 接口。
## Schema 校验 ## Schema 校验
@@ -92,7 +93,7 @@ export interface Config {
```typescript ```typescript
export function apply(ctx: Context, config: Config) { export function apply(ctx: Context, config: Config) {
if (!ctx.llm.hasAdapter(config.model)) { if (!ctx.llm.models().includes(config.model)) {
throw new Error(`Model "${config.model}" is not registered by any LLM adapter`) throw new Error(`Model "${config.model}" is not registered by any LLM adapter`)
} }
} }

View File

@@ -28,10 +28,8 @@ import type { Context } from 'cordis'
export const name = 'hello-plugin' export const name = 'hello-plugin'
export function apply(ctx: Context) { export function apply(ctx: Context) {
// 监听 agent-loop 的 ready 事件 // apply 被调用时,插件的必选依赖已就绪
ctx.on('ready', () => { console.log('[hello-plugin] 插件已加载!')
console.log('[hello-plugin] 插件已加载!')
})
} }
``` ```
@@ -100,17 +98,14 @@ export default {
### 类形式 ### 类形式
```typescript ```typescript
import { Service } from 'cordis' import { Service, type Context } from 'cordis'
export default class MyService extends Service { export default class MyService extends Service {
static inject = ['tools'] static inject = ['tools']
constructor(ctx: Context) { constructor(ctx: Context) {
super(ctx, 'myService') super(ctx, 'myService')
} // 构造函数内完成同步初始化
start() {
// 服务启动逻辑
} }
} }
``` ```

View File

@@ -133,14 +133,14 @@ defineTool({
// ... // ...
presentCall(args) { presentCall(args) {
return { return {
intent: 'terminal', card: 'terminal',
title: `bash(${JSON.stringify(args.command).slice(0, 60)})`, title: args.command,
} }
}, },
presentResult(args, result) { presentResult(args, result) {
return { return {
intent: 'terminal', card: 'terminal',
body: result.content.map(b => b.type === 'text' ? b.text : '').join(''), output: result.content.map(b => b.type === 'text' ? b.text : '').join(''),
} }
}, },
}) })
@@ -156,9 +156,7 @@ defineTool({
// 这样就够了: // 这样就够了:
ctx.tools.register(defineTool({ /* ... */ })) ctx.tools.register(defineTool({ /* ... */ }))
// 不需要: // 不需要额外保存 disposer 或注册清理逻辑
// const dispose = ctx.tools.register(...)
// ctx.on('dispose', dispose)
``` ```
## 完整实战示例 ## 完整实战示例

View File

@@ -24,15 +24,15 @@ Cordis 提供多种事件触发模式,适用于不同场景:
### emit — 广播 ### emit — 广播
所有监听器并行执行,不关心返回值: 所有监听器同步执行,不关心返回值:
```typescript ```typescript
// 触发 // 触发
ctx.emit('agent/turn-end', { agentId, turnIndex }) ctx.emit('my-plugin/ready', { id: 'worker-1' })
// 监听 // 监听
ctx.on('agent/turn-end', ({ agentId, turnIndex }) => { ctx.on('my-plugin/ready', ({ id }) => {
console.log(`Turn ${turnIndex} ended`) console.log(`${id} is ready`)
}) })
``` ```
@@ -53,7 +53,7 @@ ctx.on('some-check', (input) => {
### serial — 顺序执行 ### serial — 顺序执行
所有监听器按注册顺序依次执行(异步安全): 监听器按注册顺序依次执行,并等待异步结果;第一个返回非空值的监听器会终止后续执行:
```typescript ```typescript
await ctx.serial('setup-phase', context) await ctx.serial('setup-phase', context)
@@ -61,18 +61,16 @@ await ctx.serial('setup-phase', context)
### waterfall — 管道 ### waterfall — 管道
每个监听器接收前一个的输出,形成数据管道。**必须调用 `next()` 传递给下游**,不调用即为否决: 每个监听器可以包装下游返回值,形成处理链。**必须调用 `next()` 传递给下游**,不调用即为否决:
```typescript ```typescript
// 触发 // 触发
const finalMessages = await ctx.waterfall('llm/pre-request', messages) const output = await ctx.waterfall('my-plugin/transform', input, async () => input)
// 监听(必须调用 next) // 监听(必须调用 next)
ctx.on('llm/pre-request', async (messages, next) => { ctx.on('my-plugin/transform', async (_input, next) => {
// 可以修改 messages const downstream = await next()
messages.push(extraMessage) return downstream.trim()
// 必须调用 next() 传递给下一个监听器
return next(messages)
}) })
``` ```
@@ -89,6 +87,7 @@ declare module 'cordis' {
interface Events { interface Events {
'my-plugin/ready': (payload: { id: string }) => void 'my-plugin/ready': (payload: { id: string }) => void
'my-plugin/check': (input: string) => boolean | undefined 'my-plugin/check': (input: string) => boolean | undefined
'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
} }
} }
@@ -96,20 +95,11 @@ declare module 'cordis' {
// 都有正确的类型推导 // 都有正确的类型推导
``` ```
## 命名约定 ## Cordis 事件与会话记录
Harness 事件遵循 `namespace/action` 命名: Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/pre-step`、`agent/request`、`agent/step-result`、`tools/result` 和 `session/event`。完整签名与触发模式见[Events 目录](../../../../cordis-catalog/events.md)。
``` `turn/*`、`step/*`、`tool/call`、`tool/result` 和 `compact/*` 是持久化的会话事件类型,不是同名 Cordis 事件。需要观察它们时,监听 `session/event` 并检查 `event.type`。
agent/pre-step — agent 执行一步之前
agent/post-step — agent 执行一步之后
tool/call — tool 被调用
tool/result — tool 返回结果
llm/pre-request — LLM 请求发送前
session/event — 会话事件被记录
compact/start — 压缩开始
compact/end — 压缩结束
```
## 事件也是效果 ## 事件也是效果
@@ -118,7 +108,7 @@ compact/end — 压缩结束
```typescript ```typescript
export function apply(ctx: Context) { export function apply(ctx: Context) {
// 这个监听器在插件 dispose 时自动清理 // 这个监听器在插件 dispose 时自动清理
ctx.on('agent/turn-end', handler) ctx.on('tools/result', handler)
} }
``` ```
@@ -132,14 +122,10 @@ import type { Context } from 'cordis'
export const name = 'tool-logger' export const name = 'tool-logger'
export function apply(ctx: Context) { export function apply(ctx: Context) {
ctx.on('tool/call', ({ name, args }) => { ctx.on('tools/result', (exec, result) => {
console.log(`[tool] ${name}(${JSON.stringify(args)})`) console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`)
})
ctx.on('tool/result', ({ name, result }) => {
const text = result.content const text = result.content
.filter(b => b.type === 'text') .map(block => block.type === 'text' ? block.text : '')
.map(b => b.text)
.join('') .join('')
console.log(`[tool result] ${text.slice(0, 100)}`) console.log(`[tool result] ${text.slice(0, 100)}`)
}) })

View File

@@ -105,14 +105,6 @@ fiber.dispose()
export function apply(ctx: Context) { export function apply(ctx: Context) {
console.log('plugin loading') console.log('plugin loading')
ctx.on('ready', () => {
console.log('context ready')
})
ctx.on('dispose', () => {
console.log('plugin disposing')
})
ctx.effect(() => { ctx.effect(() => {
console.log('effect registered') console.log('effect registered')
return () => console.log('effect cleaned up') return () => console.log('effect cleaned up')
@@ -124,12 +116,10 @@ export function apply(ctx: Context) {
``` ```
plugin loading plugin loading
effect registered effect registered
context ready
``` ```
卸载时输出(逆序): 卸载时输出:
``` ```
plugin disposing
effect cleaned up effect cleaned up
``` ```

View File

@@ -90,8 +90,11 @@ export default class MetricsService extends Service {
// 必选:服务不存在时,插件不会加载 // 必选:服务不存在时,插件不会加载
export const inject = ['tools'] export const inject = ['tools']
// 可选:服务不存在时,插件仍然加载,但 ctx.xxx 可能是 undefined // 可选:不写入 inject,使用时通过 ctx.get() 查询
export const inject = { optional: ['metrics'] } export function apply(ctx: Context) {
const metrics = ctx.get('metrics')
metrics?.record('plugin_loaded', 1)
}
``` ```
### 服务消失时的行为 ### 服务消失时的行为
@@ -109,7 +112,10 @@ export const inject = { optional: ['metrics'] }
```yaml ```yaml
- id: group-a - id: group-a
name: 'group:' name: '@cordisjs/plugin-group'
group: true
isolate:
bash: true
config: config:
- name: '@deepseek-ai/dsh-bash-local' - name: '@deepseek-ai/dsh-bash-local'
config: config:
@@ -117,7 +123,10 @@ export const inject = { optional: ['metrics'] }
- name: './src/plugin-a.ts' - name: './src/plugin-a.ts'
- id: group-b - id: group-b
name: 'group:' name: '@cordisjs/plugin-group'
group: true
isolate:
bash: true
config: config:
- name: '@deepseek-ai/dsh-bash-local' - name: '@deepseek-ai/dsh-bash-local'
config: config:

View File

@@ -114,6 +114,8 @@ interface GenerateOptions {
maxTokens?: number maxTokens?: number
/** 温度 */ /** 温度 */
temperature?: number temperature?: number
/** 取消或卸载时中止进行中的请求 */
signal?: AbortSignal
} }
``` ```
@@ -160,7 +162,10 @@ mock 适配器是学习 StreamChunk 协议的最佳起点——它用纯本地
```typescript ```typescript
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> { async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
const response = await fetch(this.endpoint, { /* ... */ }) const response = await fetch(this.endpoint, {
// ...method、headers 和 body
signal: options.signal,
})
if (!response.ok) { if (!response.ok) {
throw new Error(`API error: ${response.status}`) throw new Error(`API error: ${response.status}`)
} }

View File

@@ -13,7 +13,7 @@ hero:
link: /develop/basic/ link: /develop/basic/
features: features:
- title: 插件化架构 - title: 插件化架构
details: 基于 Cordis 效果系统,所有能力通过插件注册,加载即生效、卸载即还原。 details: 基于 Cordis 插件系统,所有能力通过插件注册,加载即生效、卸载即还原。
- title: 配置即组合 - title: 配置即组合
details: 一个 cordis.yml 决定整个 Agent 的能力组合——换模型、加工具,只需改一行配置。 details: 一个 cordis.yml 决定整个 Agent 的能力组合——换模型、加工具,只需改一行配置。
- title: 开箱即用 - title: 开箱即用

View File

@@ -4,7 +4,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os' import { tmpdir } from 'node:os'
import { join } from 'node:path' import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest' import { afterEach, describe, expect, it } from 'vitest'
import type { DocsPage } from '../website/docs.ts' import { docsPages, type DocsPage } from '../website/docs.ts'
import { addProjectionFrontmatter, rewriteMarkdown } from './project-doc-site.ts' import { addProjectionFrontmatter, rewriteMarkdown } from './project-doc-site.ts'
const roots: string[] = [] const roots: string[] = []
@@ -25,8 +25,10 @@ function fixture(): { root: string; pages: DocsPage[] } {
return { return {
root, root,
pages: [ pages: [
{ source: 'docs/a.md', route: 'en/a.md', label: 'A', sidebar: 'en-docs', section: 'Test', order: 1 }, { locale: 'root', contentLocale: 'en-US', source: 'docs/a.md', route: 'a.md', label: 'A', sidebar: 'zh-reference', section: 'Test', order: 1 },
{ source: 'docs/b.md', route: 'en/reference/b.md', label: 'B', sidebar: 'en-docs', section: 'Test', order: 2 }, { locale: 'root', contentLocale: 'en-US', source: 'docs/b.md', route: 'reference-root/b.md', label: 'B', sidebar: 'zh-reference', section: 'Test', order: 2 },
{ locale: 'en', contentLocale: 'en-US', source: 'docs/a.md', route: 'en/a.md', label: 'A', sidebar: 'en-reference', section: 'Test', order: 1 },
{ locale: 'en', contentLocale: 'en-US', source: 'docs/b.md', route: 'en/reference/b.md', label: 'B', sidebar: 'en-reference', section: 'Test', order: 2 },
], ],
} }
} }
@@ -36,6 +38,7 @@ describe('rewriteMarkdown', () => {
const { root, pages } = fixture() const { root, pages } = fixture()
const source = '[B](b.md#part) [source](../packages/tool.ts:2) [web](https://example.com)\n' const source = '[B](b.md#part) [source](../packages/tool.ts:2) [web](https://example.com)\n'
expect(rewriteMarkdown(source, { expect(rewriteMarkdown(source, {
locale: 'en',
sourcePath: 'docs/a.md', sourcePath: 'docs/a.md',
route: 'en/a.md', route: 'en/a.md',
pages, pages,
@@ -48,9 +51,22 @@ describe('rewriteMarkdown', () => {
) )
}) })
it('selects the published target in the current site locale', () => {
const { root, pages } = fixture()
expect(rewriteMarkdown('[B](b.md)\n', {
locale: 'root',
sourcePath: 'docs/a.md',
route: 'a.md',
pages,
repoRoot: root,
repositoryRef: 'abc123',
})).toBe('[B](./reference-root/b.md)\n')
})
it('uses raw GitHub content for unpublished images', () => { it('uses raw GitHub content for unpublished images', () => {
const { root, pages } = fixture() const { root, pages } = fixture()
expect(rewriteMarkdown('![logo](../packages/logo.svg)\n', { expect(rewriteMarkdown('![logo](../packages/logo.svg)\n', {
locale: 'en',
sourcePath: 'docs/a.md', sourcePath: 'docs/a.md',
route: 'en/a.md', route: 'en/a.md',
pages, pages,
@@ -63,6 +79,7 @@ describe('rewriteMarkdown', () => {
const { root, pages } = fixture() const { root, pages } = fixture()
const source = '```md\n[B](b.md)\n```\n' const source = '```md\n[B](b.md)\n```\n'
expect(rewriteMarkdown(source, { expect(rewriteMarkdown(source, {
locale: 'en',
sourcePath: 'docs/a.md', sourcePath: 'docs/a.md',
route: 'en/a.md', route: 'en/a.md',
pages, pages,
@@ -74,6 +91,7 @@ describe('rewriteMarkdown', () => {
it('fails loud when a relative target is missing', () => { it('fails loud when a relative target is missing', () => {
const { root, pages } = fixture() const { root, pages } = fixture()
expect(() => rewriteMarkdown('[missing](missing.md)\n', { expect(() => rewriteMarkdown('[missing](missing.md)\n', {
locale: 'en',
sourcePath: 'docs/a.md', sourcePath: 'docs/a.md',
route: 'en/a.md', route: 'en/a.md',
pages, pages,
@@ -83,6 +101,19 @@ describe('rewriteMarkdown', () => {
}) })
}) })
describe('docsPages locale routes', () => {
it('publishes the same canonical source at every corresponding locale route', () => {
const byRoute = new Map(docsPages.map(page => [page.route, page]))
for (const page of docsPages.filter(page => page.locale === 'root')) {
const counterpart = byRoute.get(`en/${page.route}`)
expect(counterpart, page.route).toBeDefined()
expect(counterpart?.locale).toBe('en')
expect(counterpart?.source).toBe(page.source)
expect(counterpart?.contentLocale).toBe(page.contentLocale)
}
})
})
describe('addProjectionFrontmatter', () => { describe('addProjectionFrontmatter', () => {
it('adds frontmatter to an ordinary Markdown page', () => { it('adds frontmatter to an ordinary Markdown page', () => {
expect(addProjectionFrontmatter('# Guide\n', 'docs/guide.md')).toBe( expect(addProjectionFrontmatter('# Guide\n', 'docs/guide.md')).toBe(

View File

@@ -11,7 +11,7 @@ import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmFromMarkdown } from 'mdast-util-gfm' import { gfmFromMarkdown } from 'mdast-util-gfm'
import { gfm } from 'micromark-extension-gfm' import { gfm } from 'micromark-extension-gfm'
import type { Nodes } from 'mdast' import type { Nodes } from 'mdast'
import { docsPages, type DocsPage } from '../website/docs.ts' import { docsPages, type DocsLocale, type DocsPage } from '../website/docs.ts'
const REPOSITORY_URL = 'https://github.com/deepseek-harness/deepseek-harness' const REPOSITORY_URL = 'https://github.com/deepseek-harness/deepseek-harness'
const root = resolve(import.meta.dirname, '..') const root = resolve(import.meta.dirname, '..')
@@ -25,6 +25,7 @@ interface Replacement {
/** Inputs for rewriting one canonical Markdown page. */ /** Inputs for rewriting one canonical Markdown page. */
export interface RewriteMarkdownOptions { export interface RewriteMarkdownOptions {
locale: DocsLocale
sourcePath: string sourcePath: string
route: string route: string
pages: DocsPage[] pages: DocsPage[]
@@ -62,14 +63,16 @@ function routeTarget(fromRoute: string, toRoute: string, suffix: string): string
return `${target.startsWith('.') ? target : `./${target}`}${suffix}` return `${target.startsWith('.') ? target : `./${target}`}${suffix}`
} }
function sourceMap(pages: DocsPage[]): Map<string, DocsPage> { function sourceMap(pages: DocsPage[]): Map<string, Map<DocsLocale, DocsPage>> {
const map = new Map<string, DocsPage>() const map = new Map<string, Map<DocsLocale, DocsPage>>()
for (const page of pages) { for (const page of pages) {
for (const source of [page.source, ...(page.sourceAliases ?? [])]) { for (const source of [page.source, ...(page.sourceAliases ?? [])]) {
if (map.has(source)) { const localized = map.get(source) ?? new Map<DocsLocale, DocsPage>()
throw new Error(`project-doc-site: duplicate source or alias ${JSON.stringify(source)}.`) if (localized.has(page.locale)) {
throw new Error(`project-doc-site: duplicate source or alias ${JSON.stringify(source)} for locale ${JSON.stringify(page.locale)}.`)
} }
map.set(source, page) localized.set(page.locale, page)
map.set(source, localized)
} }
} }
return map return map
@@ -132,7 +135,7 @@ export function rewriteMarkdown(source: string, options: RewriteMarkdownOptions)
if (path === '') return if (path === '') return
const { absPath, line } = resolveRepositoryTarget(sourceAbs, path, options.repoRoot) const { absPath, line } = resolveRepositoryTarget(sourceAbs, path, options.repoRoot)
const targetPath = repoPath(absPath, options.repoRoot) const targetPath = repoPath(absPath, options.repoRoot)
const page = published.get(targetPath) const page = published.get(targetPath)?.get(options.locale)
const nextUrl = page === undefined const nextUrl = page === undefined
? githubTarget(absPath, line, suffix, options.repositoryRef, options.repoRoot, node.type === 'image') ? githubTarget(absPath, line, suffix, options.repositoryRef, options.repoRoot, node.type === 'image')
: routeTarget(options.route, page.route, suffix) : routeTarget(options.route, page.route, suffix)
@@ -205,6 +208,7 @@ export function projectDocs(): void {
const markdown = readFileSync(sourceAbs, 'utf8') const markdown = readFileSync(sourceAbs, 'utf8')
const projected = rewriteMarkdown(markdown, { const projected = rewriteMarkdown(markdown, {
sourcePath: page.source, sourcePath: page.source,
locale: page.locale,
route: page.route, route: page.route,
pages: docsPages, pages: docsPages,
repoRoot: root, repoRoot: root,

View File

@@ -13,6 +13,14 @@ const sectionOrder = [
'基础', '基础',
'框架能力', '框架能力',
'实战', '实战',
'概念',
'生成参考',
'数据结构',
'开发手册',
'Guide',
'Basics',
'Framework',
'Practice',
'Concepts', 'Concepts',
'Generated reference', 'Generated reference',
'Data structures', 'Data structures',
@@ -20,7 +28,7 @@ const sectionOrder = [
] ]
function sidebar(collection: DocsPage['sidebar']): DefaultTheme.SidebarItem[] { function sidebar(collection: DocsPage['sidebar']): DefaultTheme.SidebarItem[] {
const pages = docsPages.filter(page => page.sidebar === collection && page.route !== 'index.md') const pages = docsPages.filter(page => page.sidebar === collection)
const sections = new Map<string, DocsPage[]>() const sections = new Map<string, DocsPage[]>()
for (const page of pages) { for (const page of pages) {
const entries = sections.get(page.section) ?? [] const entries = sections.get(page.section) ?? []
@@ -51,7 +59,36 @@ function escapeVueInterpolation(html: string): string {
} }
const sharedTheme: Pick<DefaultTheme.Config, 'search' | 'socialLinks' | 'editLink'> = { const sharedTheme: Pick<DefaultTheme.Config, 'search' | 'socialLinks' | 'editLink'> = {
search: { provider: 'local' }, search: {
provider: 'local',
options: {
locales: {
root: {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档',
},
modal: {
displayDetails: '显示详细列表',
resetButtonTitle: '清除搜索',
backButtonTitle: '关闭搜索',
noResultsText: '未找到相关结果',
footer: {
selectText: '选择',
selectKeyAriaLabel: '回车键',
navigateText: '切换',
navigateUpKeyAriaLabel: '上方向键',
navigateDownKeyAriaLabel: '下方向键',
closeText: '关闭',
closeKeyAriaLabel: 'Esc 键',
},
},
},
},
},
},
},
socialLinks: [ socialLinks: [
{ icon: 'github', link: 'https://github.com/deepseek-harness/deepseek-harness' }, { icon: 'github', link: 'https://github.com/deepseek-harness/deepseek-harness' },
], ],
@@ -81,14 +118,22 @@ export default withMermaid({
nav: [ nav: [
{ text: '入门', link: '/guide/', activeMatch: '^/guide/' }, { text: '入门', link: '/guide/', activeMatch: '^/guide/' },
{ text: '开发', link: '/develop/basic/', activeMatch: '^/develop/' }, { text: '开发', link: '/develop/basic/', activeMatch: '^/develop/' },
{ text: 'Reference', link: '/en/', activeMatch: '^/en/' }, { text: '参考', link: '/reference/', activeMatch: '^/reference/' },
], ],
sidebar: { sidebar: {
'/guide/': sidebar('zh-guide'), '/guide/': sidebar('zh-guide'),
'/develop/': sidebar('zh-develop'), '/develop/': sidebar('zh-develop'),
'/reference/': sidebar('zh-reference'),
}, },
outline: { label: '本页目录' }, outline: { label: '本页目录' },
docFooter: { prev: '上一篇', next: '下一篇' }, docFooter: { prev: '上一篇', next: '下一篇' },
darkModeSwitchLabel: '外观',
lightModeSwitchTitle: '切换到浅色主题',
darkModeSwitchTitle: '切换到深色主题',
sidebarMenuLabel: '菜单',
returnToTopLabel: '返回顶部',
langMenuLabel: '切换语言',
skipToContentLabel: '跳至内容',
}, },
}, },
en: { en: {
@@ -97,12 +142,14 @@ export default withMermaid({
link: '/en/', link: '/en/',
themeConfig: { themeConfig: {
nav: [ nav: [
{ text: 'Concepts', link: '/en/' }, { text: 'Guide', link: '/en/guide/', activeMatch: '^/en/guide/' },
{ text: 'Reference', link: '/en/config-catalog' }, { text: 'Develop', link: '/en/develop/basic/', activeMatch: '^/en/develop/' },
{ text: '中文指南', link: '/guide/' }, { text: 'Reference', link: '/en/reference/', activeMatch: '^/en/reference/' },
], ],
sidebar: { sidebar: {
'/en/': sidebar('en-docs'), '/en/guide/': sidebar('en-guide'),
'/en/develop/': sidebar('en-develop'),
'/en/reference/': sidebar('en-reference'),
}, },
editLink: { editLink: {
pattern: ({ frontmatter }: PageData) => { pattern: ({ frontmatter }: PageData) => {

View File

@@ -1,20 +1,38 @@
/** /**
* Canonical publication manifest for the documentation website. * Canonical publication manifest for the documentation website.
* *
* Markdown stays in its owning repository tier. This manifest only maps a * Markdown stays in its owning repository tier. This manifest maps each
* source file to its public route and navigation placement. * canonical source into matching route trees for both site locales; when a
* translation is absent, both routes intentionally project the available
* source instead of copying Markdown.
*/ */
/** Locale key used by the VitePress site. */
export type DocsLocale = 'root' | 'en'
/** Sidebar collection rendered for one locale and top-level module. */
type DocsSidebar =
| 'zh-guide'
| 'zh-develop'
| 'zh-reference'
| 'en-guide'
| 'en-develop'
| 'en-reference'
/** A page projected into the VitePress source tree. */ /** A page projected into the VitePress source tree. */
export interface DocsPage { export interface DocsPage {
/** VitePress locale whose route tree owns this projection. */
locale: DocsLocale
/** Language of the canonical source currently projected at this route. */
contentLocale: 'zh-CN' | 'en-US'
/** Repository-relative canonical Markdown source. */ /** Repository-relative canonical Markdown source. */
source: string source: string
/** VitePress route, including the `.md` suffix. */ /** VitePress route, including the `.md` suffix. */
route: string route: string
/** Navigation label shown in the sidebar. */ /** Navigation label shown in the sidebar. */
label: string label: string
/** Sidebar collection that owns the page. */ /** Sidebar collection that owns the page, or null for a locale home page. */
sidebar: 'zh-guide' | 'zh-develop' | 'en-docs' sidebar: DocsSidebar | null
/** Section label within the sidebar. */ /** Section label within the sidebar. */
section: string section: string
/** Stable order within the section. */ /** Stable order within the section. */
@@ -23,191 +41,228 @@ export interface DocsPage {
sourceAliases?: string[] sourceAliases?: string[]
} }
const zhGuide: DocsPage[] = [ interface MirroredPage {
source: string
route: string
contentLocale: DocsPage['contentLocale']
label: Record<DocsLocale, string>
sidebar: Record<DocsLocale, DocsSidebar | null>
section: Record<DocsLocale, string>
order: number
sourceAliases?: string[]
}
function mirroredPages(pages: MirroredPage[]): DocsPage[] {
return pages.flatMap(page => (['root', 'en'] as const).map(locale => ({
locale,
contentLocale: page.contentLocale,
source: page.source,
route: locale === 'root' ? page.route : `en/${page.route}`,
label: page.label[locale],
sidebar: page.sidebar[locale],
section: page.section[locale],
order: page.order,
...(page.sourceAliases === undefined ? {} : { sourceAliases: page.sourceAliases }),
})))
}
const homeAndGuide = mirroredPages([
{ {
source: 'docs/user/zh-CN/index.md', source: 'docs/user/zh-CN/index.md',
route: 'index.md', route: 'index.md',
label: 'DeepSeek Harness', contentLocale: 'zh-CN',
sidebar: 'zh-guide', label: { root: 'DeepSeek Harness', en: 'DeepSeek Harness' },
section: '入门', sidebar: { root: null, en: null },
section: { root: '首页', en: 'Home' },
order: 0, order: 0,
}, },
{ {
source: 'docs/user/zh-CN/guide/index.md', source: 'docs/user/zh-CN/guide/index.md',
route: 'guide/index.md', route: 'guide/index.md',
label: '介绍', contentLocale: 'zh-CN',
sidebar: 'zh-guide', label: { root: '介绍', en: 'Introduction' },
section: '入门', sidebar: { root: 'zh-guide', en: 'en-guide' },
section: { root: '入门', en: 'Guide' },
order: 1, order: 1,
sourceAliases: ['docs/user/zh-CN/guide'], sourceAliases: ['docs/user/zh-CN/guide'],
}, },
{ {
source: 'docs/user/zh-CN/guide/quickstart.md', source: 'docs/user/zh-CN/guide/quickstart.md',
route: 'guide/quickstart.md', route: 'guide/quickstart.md',
label: '快速开始', contentLocale: 'zh-CN',
sidebar: 'zh-guide', label: { root: '快速开始', en: 'Quick start' },
section: '入门', sidebar: { root: 'zh-guide', en: 'en-guide' },
section: { root: '入门', en: 'Guide' },
order: 2, order: 2,
}, },
{ {
source: 'docs/user/zh-CN/guide/config.md', source: 'docs/user/zh-CN/guide/config.md',
route: 'guide/config.md', route: 'guide/config.md',
label: '配置文件', contentLocale: 'zh-CN',
sidebar: 'zh-guide', label: { root: '配置文件', en: 'Configuration' },
section: '入门', sidebar: { root: 'zh-guide', en: 'en-guide' },
section: { root: '入门', en: 'Guide' },
order: 3, order: 3,
}, },
] ])
const zhDevelop: DocsPage[] = [ const develop = mirroredPages([
{ {
source: 'docs/user/zh-CN/develop/basic/index.md', source: 'docs/user/zh-CN/develop/basic/index.md',
route: 'develop/basic/index.md', route: 'develop/basic/index.md',
label: '第一个插件', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '第一个插件', en: 'First plugin' },
section: '基础', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '基础', en: 'Basics' },
order: 1, order: 1,
sourceAliases: ['docs/user/zh-CN/develop/basic'], sourceAliases: ['docs/user/zh-CN/develop/basic'],
}, },
{ {
source: 'docs/user/zh-CN/develop/basic/tool.md', source: 'docs/user/zh-CN/develop/basic/tool.md',
route: 'develop/basic/tool.md', route: 'develop/basic/tool.md',
label: '开发一个 Tool', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '开发一个 Tool', en: 'Build a tool' },
section: '基础', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '基础', en: 'Basics' },
order: 2, order: 2,
}, },
{ {
source: 'docs/user/zh-CN/develop/basic/config.md', source: 'docs/user/zh-CN/develop/basic/config.md',
route: 'develop/basic/config.md', route: 'develop/basic/config.md',
label: '插件配置', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '插件配置', en: 'Plugin configuration' },
section: '基础', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '基础', en: 'Basics' },
order: 3, order: 3,
}, },
{ {
source: 'docs/user/zh-CN/develop/framework/index.md', source: 'docs/user/zh-CN/develop/framework/index.md',
route: 'develop/framework/index.md', route: 'develop/framework/index.md',
label: '插件与生命周期', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '插件与生命周期', en: 'Plugin lifecycle' },
section: '框架能力', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '框架能力', en: 'Framework' },
order: 1, order: 1,
sourceAliases: ['docs/user/zh-CN/develop/framework'], sourceAliases: ['docs/user/zh-CN/develop/framework'],
}, },
{ {
source: 'docs/user/zh-CN/develop/framework/service.md', source: 'docs/user/zh-CN/develop/framework/service.md',
route: 'develop/framework/service.md', route: 'develop/framework/service.md',
label: '服务与依赖', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '服务与依赖', en: 'Services and dependencies' },
section: '框架能力', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '框架能力', en: 'Framework' },
order: 2, order: 2,
}, },
{ {
source: 'docs/user/zh-CN/develop/framework/events.md', source: 'docs/user/zh-CN/develop/framework/events.md',
route: 'develop/framework/events.md', route: 'develop/framework/events.md',
label: '事件系统', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '事件系统', en: 'Event system' },
section: '框架能力', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '框架能力', en: 'Framework' },
order: 3, order: 3,
}, },
{ {
source: 'docs/user/zh-CN/develop/practice/index.md', source: 'docs/user/zh-CN/develop/practice/index.md',
route: 'develop/practice/index.md', route: 'develop/practice/index.md',
label: '能力的三层拆分', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: '能力的三层拆分', en: 'Capability layering' },
section: '实战', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '实战', en: 'Practice' },
order: 1, order: 1,
sourceAliases: ['docs/user/zh-CN/develop/practice'], sourceAliases: ['docs/user/zh-CN/develop/practice'],
}, },
{ {
source: 'docs/user/zh-CN/develop/practice/llm-adapter.md', source: 'docs/user/zh-CN/develop/practice/llm-adapter.md',
route: 'develop/practice/llm-adapter.md', route: 'develop/practice/llm-adapter.md',
label: 'LLM 适配器', contentLocale: 'zh-CN',
sidebar: 'zh-develop', label: { root: 'LLM 适配器', en: 'LLM adapter' },
section: '实战', sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '实战', en: 'Practice' },
order: 2, order: 2,
}, },
] ])
const enOverview: DocsPage[] = ([ const reference = mirroredPages([
['docs/architecture.md', 'en/index.md', 'Architecture'], ...([
['docs/cordis-primer.md', 'en/cordis-primer.md', 'Cordis primer'], ['docs/architecture.md', 'reference/index.md', '架构', 'Architecture'],
['docs/capability-seams.md', 'en/capability-seams.md', 'Capability services'], ['docs/cordis-primer.md', 'reference/cordis-primer.md', 'Cordis 入门', 'Cordis primer'],
['docs/agent-lifecycle.md', 'en/agent-lifecycle.md', 'Agent lifecycle'], ['docs/capability-seams.md', 'reference/capability-seams.md', '能力服务', 'Capability services'],
['docs/tool-execution-pipeline.md', 'en/tool-execution-pipeline.md', 'Tool execution'], ['docs/agent-lifecycle.md', 'reference/agent-lifecycle.md', 'Agent 生命周期', 'Agent lifecycle'],
] as const).map(([source, route, label], order) => ({ ['docs/tool-execution-pipeline.md', 'reference/tool-execution-pipeline.md', 'Tool 执行', 'Tool execution'],
source, ] as const).map(([source, route, rootLabel, enLabel], order): MirroredPage => ({
route, source,
label, route,
sidebar: 'en-docs', contentLocale: 'en-US',
section: 'Concepts', label: { root: rootLabel, en: enLabel },
order, sidebar: { root: 'zh-reference', en: 'en-reference' },
})) section: { root: '概念', en: 'Concepts' },
order,
const enCatalogs: DocsPage[] = ([ })),
['docs/config-catalog.md', 'en/config-catalog.md', 'Plugin configuration'], ...([
['docs/tool-catalog.md', 'en/tool-catalog.md', 'Tool schemas'], ['docs/config-catalog.md', 'reference/config-catalog.md', '插件配置', 'Plugin configuration'],
['docs/cordis-catalog/services.md', 'en/cordis-catalog/services.md', 'Services'], ['docs/tool-catalog.md', 'reference/tool-catalog.md', 'Tool Schema', 'Tool schemas'],
['docs/cordis-catalog/events.md', 'en/cordis-catalog/events.md', 'Events'], ['docs/cordis-catalog/services.md', 'reference/cordis-catalog/services.md', '服务', 'Services'],
['docs/persistence-catalog.md', 'en/persistence-catalog.md', 'Persistence events'], ['docs/cordis-catalog/events.md', 'reference/cordis-catalog/events.md', '事件', 'Events'],
] as const).map(([source, route, label], order) => ({ ['docs/persistence-catalog.md', 'reference/persistence-catalog.md', '持久化事件', 'Persistence events'],
source, ] as const).map(([source, route, rootLabel, enLabel], order): MirroredPage => ({
route, source,
label, route,
sidebar: 'en-docs', contentLocale: 'en-US',
section: 'Generated reference', label: { root: rootLabel, en: enLabel },
order, sidebar: { root: 'zh-reference', en: 'en-reference' },
})) section: { root: '生成参考', en: 'Generated reference' },
order,
const corePages = [ })),
['core.md', 'Core data structures'], ...([
['session.md', 'Sessions'], ['core.md', '核心数据结构', 'Core data structures'],
['tools.md', 'Tools'], ['scope.md', '作用域', 'Scopes'],
['llm-streaming.md', 'LLM streaming'], ['session.md', '会话', 'Sessions'],
['bash.md', 'Bash execution'], ['system-prompt.md', '系统提示词', 'System prompts'],
['filesystem.md', 'Filesystem'], ['tools.md', '工具', 'Tools'],
['code-runtime.md', 'Code runtime'], ['llm-streaming.md', 'LLM 流式响应', 'LLM streaming'],
['compaction.md', 'Compaction'], ['bash.md', 'Bash 执行', 'Bash execution'],
['subagent.md', 'Subagents'], ['filesystem.md', '文件系统', 'Filesystem'],
['workflow.md', 'Workflows'], ['code-runtime.md', '代码运行时', 'Code runtime'],
['skills.md', 'Skills'], ['compaction.md', '上下文压缩', 'Compaction'],
['approval.md', 'Approvals'], ['subagent.md', '子代理', 'Subagents'],
['user-interaction.md', 'User interaction'], ['workflow.md', '工作流', 'Workflows'],
['sandbox.md', 'Sandboxing'], ['skills.md', '技能', 'Skills'],
['web.md', 'Web access'], ['approval.md', '审批', 'Approvals'],
['persistence.md', 'Session persistence'], ['user-interaction.md', '用户交互', 'User interaction'],
] as const ['sandbox.md', '沙箱', 'Sandboxing'],
['web.md', 'Web 访问', 'Web access'],
const enCore: DocsPage[] = corePages.map(([file, label], order) => ({ ['persistence.md', '会话持久化', 'Session persistence'],
source: `docs/core-data-structures/${file}`, ] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({
route: `en/core-data-structures/${file}`, source: `docs/core-data-structures/${file}`,
label, route: `reference/core-data-structures/${file}`,
sidebar: 'en-docs', contentLocale: 'en-US',
section: 'Data structures', label: { root: rootLabel, en: enLabel },
order, sidebar: { root: 'zh-reference', en: 'en-reference' },
...(file === 'core.md' ? { sourceAliases: ['docs/core-data-structures'] } : {}), section: { root: '数据结构', en: 'Data structures' },
})) order,
...(file === 'core.md' ? { sourceAliases: ['docs/core-data-structures'] } : {}),
const enCookbook: DocsPage[] = ([ })),
['adding-a-package.md', 'Adding a package'], ...([
['adding-a-tool.md', 'Adding a tool'], ['adding-a-package.md', '新增 Package', 'Adding a package'],
['adding-an-llm-adapter.md', 'Adding an LLM adapter'], ['adding-a-tool.md', '新增 Tool', 'Adding a tool'],
['extension-cookbook.md', 'Extension patterns'], ['adding-an-llm-adapter.md', '新增 LLM Adapter', 'Adding an LLM adapter'],
] as const).map(([file, label], order) => ({ ['extension-cookbook.md', '扩展模式', 'Extension patterns'],
source: `docs/cookbook/${file}`, ] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({
route: `en/cookbook/${file}`, source: `docs/cookbook/${file}`,
label, route: `reference/cookbook/${file}`,
sidebar: 'en-docs', contentLocale: 'en-US',
section: 'Cookbook', label: { root: rootLabel, en: enLabel },
order, sidebar: { root: 'zh-reference', en: 'en-reference' },
})) section: { root: '开发手册', en: 'Cookbook' },
order,
})),
])
/** Every canonical page published by the documentation website. */ /** Every canonical page published by the documentation website. */
export const docsPages: DocsPage[] = [ export const docsPages: DocsPage[] = [
...zhGuide, ...homeAndGuide,
...zhDevelop, ...develop,
...enOverview, ...reference,
...enCatalogs,
...enCore,
...enCookbook,
] ]