跳到主要内容
路径文档

写一个服务

一句话版:服务 = 挂在 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.registerctx.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 归一化,与 applyinject 不是同一来源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

下一步