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` 类型和同名的 Schemastery schema默认值直接写在 schema 中
```typescript
import type { Context } from 'cordis'
import Schema from 'schemastery'
export const name = 'my-plugin'
@@ -17,11 +18,11 @@ export interface Config {
verbose?: boolean
}
export const Config = {
greeting: 'Hello',
maxRetries: 3,
verbose: false,
}
export const Config: Schema<Config> = Schema.object({
greeting: Schema.string().default('Hello'),
maxRetries: Schema.number().default(3),
verbose: Schema.boolean().default(false),
})
export function apply(ctx: Context, config: Config) {
console.log(config.greeting) // 用户配置或默认值
@@ -37,7 +38,7 @@ export function apply(ctx: Context, config: Config) {
maxRetries: 5
```
未提供字段使用导出的 `Config` 对象中的默认值。
插件加载时Cordis 会通过导出的 schema 校验配置,并填充未提供字段的默认值。不要导出普通对象作为 `Config`,因为它不满足 Cordis 要求的 Standard Schema 接口
## Schema 校验
@@ -92,7 +93,7 @@ export interface Config {
```typescript
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`)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,8 +90,11 @@ export default class MetricsService extends Service {
// 必选:服务不存在时,插件不会加载
export const inject = ['tools']
// 可选:服务不存在时,插件仍然加载,但 ctx.xxx 可能是 undefined
export const inject = { optional: ['metrics'] }
// 可选:不写入 inject使用时通过 ctx.get() 查询
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
- id: group-a
name: 'group:'
name: '@cordisjs/plugin-group'
group: true
isolate:
bash: true
config:
- name: '@deepseek-ai/dsh-bash-local'
config:
@@ -117,7 +123,10 @@ export const inject = { optional: ['metrics'] }
- name: './src/plugin-a.ts'
- id: group-b
name: 'group:'
name: '@cordisjs/plugin-group'
group: true
isolate:
bash: true
config:
- name: '@deepseek-ai/dsh-bash-local'
config:

View File

@@ -114,6 +114,8 @@ interface GenerateOptions {
maxTokens?: number
/** 温度 */
temperature?: number
/** 取消或卸载时中止进行中的请求 */
signal?: AbortSignal
}
```
@@ -160,7 +162,10 @@ mock 适配器是学习 StreamChunk 协议的最佳起点——它用纯本地
```typescript
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) {
throw new Error(`API error: ${response.status}`)
}

View File

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