Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
4.6 KiB
7. 进入 harness
English | 中文
本章会向 harness 的 tools 服务注册一个可由模型调用的工具,通过 harness 工具流水线执行它,并观察结果事件。整个示例无需密钥,也不会调用模型。
工具插件
创建 greet-tool.ts,将它放在 tmp/cordis-tutorial 中:
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { CallId } from '@deepseek-ai/dsh-llm'
export const name = 'greet-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet',
description: 'Greet the named person.',
parameters: {
name: { type: 'string', required: true, description: 'Who to greet' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
return `Hello, ${args.name}!`
},
}))
// Drive one call through the real execution pipeline, standing in for
// the model. CallId brands the correlation id a provider would issue.
void (async () => {
const result = await ctx.tools.execute({
callId: CallId('demo-1'),
name: 'greet',
arguments: { name: 'Cordis' },
signal: new AbortController().signal,
})
console.log('tool replied:', JSON.stringify(result.content))
})()
}
这里的每个模式都来自前几章:inject: ['tools'](第 3 章)会让插件等待工具注册表就绪;ctx.tools.register(...) 会把注册 disposer 附着到插件(第 2 章),因此卸载时会注销工具。defineTool 将 parameters 规约转换为向模型展示的 JSON Schema,推导 args 的类型,并在 execute 运行前校验模型提供的参数。工具返回由 output.schema 声明的规范值;output.render 则作为 Native renderer(原生渲染器),另行生成可持久化的结果内容。
观察插件
创建 tool-logger.ts。这是一个独立插件,通过 harness 的 tools/result 事件观察应用中的每次工具调用:
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-tools'
export const name = 'tool-logger'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.on('tools/result', (exec, result) => {
const text = result.content
.map(block => (block.type === 'text' ? block.text : ''))
.join('')
console.log(`[tool-logger] ${exec.name} -> ${text}`)
})
}
import type {} from '@deepseek-ai/dsh-tools' 行会引入该包的声明合并,使 'tools/result' 及其 payload 具有类型。这与第 4 章导入 stats.ts 的做法相同,只是扩展到了包级别。
组合并运行
- name: '@deepseek-ai/dsh-system-prompt'
- name: '@deepseek-ai/dsh-tools'
- name: './tool-logger.ts'
- name: './greet-tool.ts'
@deepseek-ai/dsh-tools 会注入 systemPrompt 服务,因为工具需要向系统提示词贡献 schema,所以组合中也要列出该服务的提供方。缺少提供方时,工具插件会像第 6 章所述那样保持 PENDING。
node --import tsx ../../vendor/cordis/bin.js
[tool-logger] greet -> Hello, Cordis!
tool replied: [{"type":"text","text":"Hello, Cordis!"}]
logger 会先触发:tools/result 在结果物化过程中发出,发生在 execute 向调用方返回的 promise 兑现之前。两个插件都不知道另一个插件存在,它们由注册表服务和事件连接。
从这里走向完整 agent(智能体)
真实 agent 就是这套组合再加上更多插件:LLM(大语言模型)适配器、agent loop(智能体循环)、持久化和运行入口。对照 examples/headless-agent/cordis.yml,你现在已经可以读懂其中每个配置项。将 greet-tool.ts 加入该文件的副本即可。
后续可以阅读: