写一个服务
一句话版:服务 = 挂在
ctx上的具名能力(ctx.myService),其他插件通过inject: ['myService']依赖它:用于多个工具/插件共享逻辑,而不是重复实现。
这是"给进程内共享能力"的一篇。工具给模型,服务给自己人(插件/工具)。
一、最小服务
import { Context, Service } from '@deepseek-ai/cordis'
export const name = 'my-service'
declare module '@deepseek-ai/cordis' {
interface Context { myService: MyService }
}
export class MyService extends Service {
static inject = ['sessions'] // 服务自身的依赖
constructor(ctx: Context) {
super(ctx, 'myService')
}
async summarize(sessionId: string): Promise<string> {
return '...' // 真实逻辑:读会话、调 llm、返回摘要
}
}
export async function apply(ctx: Context) {
await ctx.plugin(MyService) // await 可选(见下)
}
二、服务 vs 工具
| 工具 | 服务 | |
|---|---|---|
| 谁用 | 模型 | 插件(同进程) |
| 入口 | ctx.tools.register | ctx.plugin(ServiceClass) |
| 依赖 | inject: ['tools'] | inject: ['myService'] |
| 能力 | 单次执行 | 任意方法集 |
典型模式:服务实现逻辑,工具做薄壳
ctx.tools.register({
name: 'my_summarize',
parameters: { sessionId: { type: 'string' } },
output: { schema: { type: 'string' }, render: (_a, v) => [{ type: 'text', text: String(v) }] },
execute: ({ sessionId }) => ctx.myService.summarize(sessionId),
})
三、服务注入声明
export const inject = ['myService'] // 未声明访问 → Proxy 拒绝(capability-based)
消费方要么在 inject(函数式)/static inject(类式)声明,要么用 ctx.inject(['x'], cb) 异步取。
四、Service 生命周期钩子与高级形态
| 钩子/符号 | 作用 |
|---|---|
[Service.init] | 构造后异步初始化(await 加载/发布) |
[Service.check] | 可用性谓词(fiber 刷新用) |
[Service.config] / resolveConfig | 合并祖先 intercept 配置 |
[Service.filter] | isolate 作用域边界 |
[Service.invoke] | 把服务包成可调用对象(ctx.logger() 式) |
[Service.tracker] | 绑定调用者 |
@Inject 装饰器 | 类属性注入 |
依赖声明:class 插件的依赖从 static inject 读取并经 Inject.resolve 归一化,与 apply 的 inject 不是同一来源。await ctx.plugin(MyService) 返回 fiber 的 thenable,await 可选:它的价值是保证 fiber 达 ACTIVE 并抛出启动/配置错误。
五、可调用服务(把服务当函数)
如果服务定义 [Service.invoke],构造函数会把它包成可调用对象:像 ctx.logger() 那样用 ctx.myService(...):
export class Logger extends Service {
get [Service.invoke]() {
return (msg: string) => this.log(msg)
}
log(msg: string) { ... }
}
// 消费者: ctx.logger('hello') —— 不是 ctx.logger.log('hello')
六、消费侧注入与配置覆写
| 方式 | 作用 |
|---|---|
ctx.inject(['x'], cb) | 异步注入(在 cb 里拿到 x 再用) |
ctx.get | 注入绕过 |
ctx.intercept(name, config) | 流入 Service.resolveConfig,覆写服务配置 |
七、什么时候用服务
| 场景 | 用服务 |
|---|---|
| 多个工具/插件共享同一段逻辑 | ✅ 服务实现,工具调它 |
| 一个能力要跨会话/跨 agent 复用 | ✅ |
| 给模型一个新能力 | ❌ 用工具 |
| 只是本插件内部一个函数 | ❌ 不必服务 |
经验:如果你发现自己写了两个工具做同样的事,把它抽成服务 + 两个薄壳工具。
八、验证
# 服务在组合树里能看到
dsh web --dump-config | grep -A2 my-service