Skip to content

服务端是 SDK 的运行时侧半边。它是一个 Cordis 插件,名叫 sdk-jsonrpc-server,由 @deepseek-ai/dsh-sdk-jsonrpc-server 实现。它通过 stdio 服务线路协议,让进程外的 SDK 客户端(TypeScript 与 Python)无头地驱动 harness agent——没有终端 UI、没有审批界面、不监听端口。本页引用 packages/sdk/server/src 及其周边组合。

字段
name@deepseek-ai/dsh-sdk-jsonrpc-server
version
角色面向进程外 SDK 客户端的 stdio JSON-RPC 服务端插件
协议对端@deepseek-ai/dsh-sdk-protocol
依赖@deepseek-ai/schemastery(Config 类型)
peer 依赖dsh-agentdsh-llmdsh-llm-deepseekdsh-scopedsh-sessiondsh-subagentdsh-sdk-protocoldsh-invariants,外加 cordis
服务端从 ./server.ts 再导出 HarnessSdkJsonRpcServer,而 src/index.ts 声明插件本身(nameinjectConfigapply)。只有具名导出、没有 default 导出——这样 Loader 的 unwrapExports 才能保留插件形态。

插件契约

ts
// packages/sdk/server/src/index.ts(节选)
export const name = 'sdk-jsonrpc-server'
export const inject = ['agents']          // 只需要 Agent 工厂

export interface JsonRpcConfig {
  maxTokensAsSuccess?: boolean            // 把达到 max-tokens 的结束上报为 'ok'
  input?: Readable                        // 仅运行时的钩子;生产用 process.stdin
  output?: Writable                       // 仅运行时的钩子;生产用 process.stdout
  exit?: (code: number) => void           // 仅运行时的钩子;生产用 process.exit
}
export const Config: Schema<JsonRpcConfig> = Schema.object({
  maxTokensAsSuccess: Schema.boolean().default(false),
})

input/output/exit 是仅供测试的运行时传输钩子;生产使用进程 stdio 与 process.exit。只有 maxTokensAsSuccess 可从 cordis.yml 设置。

是否加载该插件完全由周边的 cordis.yml 决定。examples/jsonrpc-agent/cordis.yml 是参考部署:它在 DeepSeek 适配器、bash 执行器、agent spine、JSONL 持久化、compaction、进程内子 agent spawn provider 旁边叠放服务端插件。

stdout 即协议

stdout 只承载 JSON-RPC 帧。部署方不得再组合一个 stdout logger;诊断信息属于 stderr。这一点靠组合而非检查来保证——插件不会否决兄弟 logger,因此一个加载了 stdout logger 的周边配置会破坏通道。

服务端生命周期

apply 把一条 JsonRpcLineTransport 接到所选流上,并构造服务端:

ts
// packages/sdk/server/src/index.ts(节选)
const transport = new JsonRpcLineTransport(input, output)
const server = new HarnessSdkJsonRpcServer(ctx, transport, {
  maxTokensAsSuccess: resolvedConfig.maxTokensAsSuccess,
})
transport.onRequest(async (method, params) => {
  const result = await server.handleRequest(method, params)
  if (method === 'shutdown') setImmediate(() => { void disposeAndExit() })
  return result
})
ctx.effect(() => {
  transport.start()
  return async () => { await server.shutdown(); transport.close() }
}, 'jsonrpc.serve')

关闭语义是精确的:

  • shutdown 请求的处理器先运行,然后 setImmediate 编排 disposeAndExit:它会flush 响应、处置根上下文ctx.root.fiber),让 SDK 拥有的 agent、订阅与持久化达到静默状态,然后以代码 0 退出。因此协议级 shutdown 拥有整个运行时进程的所有权。
  • EOF 与信号退出属于应用 bin——loader 运行器(packages/examples/jsonrpc-demo/src/runner.ts 里的 dsh-jsonrpc-agent)处理 stdin 'end'SIGTERMSIGINT 时处置根 fiber 并退出。
  • 只卸载这个插件(例如经 HMR)会停止对外服务但不会退出进程——ctx.effect 的处置路径会先运行 server.shutdown()transport.close()

共享的 exitTask 确保并发的 shutdown 请求不会多次处置根或退出进程。

HarnessSdkJsonRpcServer

该类(src/server.ts)拥有协议方法、会话注册表与事件再投射。构造时会订阅会话/agent/子 agent 生命周期事件,直到 shutdown;不支持重新初始化。

ts
export class HarnessSdkJsonRpcServer {
  async initialize(params: InitializeParams): Promise<InitializeResult>
  async prompt(params: SessionPromptParams): Promise<SessionPromptResult>
  shutdown(): Promise<Record<string, never>>
  async handleRequest(method, params): Promise<unknown>   // 分发 switch
  // 私有:getOrCreateSession / createSession / hasAdapterFor
}

handleRequest 是 JSON-RPC 分发 switch:initializesession/promptshutdown,否则抛 unknown DeepSeek Harness SDK runtime method

模型与 agent 主循环接线

每个 SDK 会话映射到注册在 ctx.agents 上的一个存活 agent。getOrCreateSession 是一个记忆化工厂,会对进行中的创建去重:

ts
// packages/sdk/server/src/server.ts(节选)
private async createSession(sessionId: string): Promise<SessionRecord> {
  const handle = await this.ctx.agents.create({
    sessionId: SessionId(sessionId),
    meta: { cwd: this.cwd },
    agentOptions: {
      provider: this.provider,
      model: this.model,
      ...(this.maxTokens === undefined ? {} : { maxTokens: this.maxTokens }),
    },
  })
  // 记录 handle 到 this.sessions
}

initialize 选定 provider/model 路线并处理适配器 seam:

  • 若已有已注册适配器服务所请求的 provider,则复用它。
  • 未拥有的 deepseek-official 路线会以 llmFiber 方式挂载 dsh-llm-deepseek
  • 任何其他未拥有的 provider 都会在初始化时失败。

Agent 工厂是唯一的硬性 inject;可选的 LLM seam 通过 ctx.get('llm') 读取(hasAdapterFor 会列出 providers)。组合把模型相关的行放在 host 平面,因此 SDK 创建的 agent 从全局层读取它们,而不是从一个预设名单读取(按 agent-presets README 中关于组合子 agent 的说明)。

prompt 在投递前会验证存活注册表(仅重载 agent-loop 可能会处置掉一个 agent,而此记录仍存活,届时该 SDK agent 不再等于 ctx.agents.get(...)),然后入队:

ts
const message = createUserMessage({ content: params.contentBlocks, source: { kind: 'user' } })
rec.handle.agent.followup(message)
return { messageId: message.id }

这就是“入队一条具名的用户消息,立即返回”的回执。followup 唤醒 agent 主循环;该响应没有任何针对提示词的结果。

事件再投射

构造函数注册四个 ctx.on 订阅,fan-out 成通知:

Cordis 事件通知
session/eventsession.event(运行时内每条会话,不过滤)
agent/statussession.status(整个 agent 的 running/idle
parentSessionsession/createdsubagent.started
info.local 为真的 subagent/endsubagent.finished

subagent.finished 的本地性规则很微妙:服务会对运行的 local 标志做快照(通过 subagentParentOfcarrierKeyOf 从作用域载体 Scoped<SubagentRuntime> 中恢复委托父 agent),并且只有进程内的子会话才发出该通知。provider 名、子 id 与持久血缘本身都不足以确立本地性。

配置键

键(cordis.yml 的 config:默认含义
maxTokensAsSuccessfalsesubagent.finished 上把 max-tokens 结束上报为 status: 'ok';根会话的提示词没有提示词级状态

initialize.maxTokens(线路参数,而非配置键)是可选的、必须为正的输出 token 上限,由 SDK 创建的 agent 及其进程内后代继承;非法值拒绝握手,省略时则不发送 SDK 上限,从而让适配器或 provider 路线的默认值生效。

单发与持久:与 dsh-headless 交叉引用

SDK 服务端是仓库中两种“无头服务端”模式之一。对比:

方面dsh-sdk-jsonrpc-server(持久)dsh-headless@deepseek-ai/dsh-headless
传输基于 stdio 的新行分隔 JSON-RPC无——直接在进程内驱动 Agent
交互模型多条 session/prompt 轮次、会话、订阅恰好一个任务、一个新 Agent、退出
结果没有针对提示词的结果;事件流向客户端把最后一条 assistant 文本写到 stdout,以 0/1 退出
组合由外部 cordis.yml 挂载cordis.patch.yml 叠加在 dsh-base 上,无 Host/HTTP
退出shutdown 请求、EOF 或信号(bin 持有)由启动器提供的 ctx.appExit 钩子

dsh-headlesspackages/bundle/headless/src)通过核心注册表创建一个 Agent,把任务驱动到静默状态,flush 它的 Session,打印最终 assistant 文本,然后经由启动器提供的 appExit host 钩子退出——绝不打开监听端口。它是“单发”兄弟;SDK 服务端则是“持久、协议驱动”的兄弟。apps/cli@deepseek-ai/dsh)依赖 dsh-headless 提供 dsh --profile headless "<task>" 单发模式。

已知局限

局限后果
线上没有按会话关闭或提示词取消防法SDK 创建的 agent 保持存活直到进程关闭
没有针对提示词的结果messageId 只标识收件箱准入;拥有自动化时间区间的客户端必须自行定义并观察该区间
stdout 纯净性靠部署保证周边配置仍可能加载 stdout logger 并破坏通道
自动适配器挂载是 DeepSeek 特定的initialize 复用任何已注册适配器,但它唯一的回退只挂载 dsh-llm-deepseek
仅进程内子 agentsubagent.finished 上报本地子运行;远程运行不会上报

nameversion
SDK 服务端@deepseek-ai/dsh-sdk-jsonrpc-server
SDK 线路协议(对端)@deepseek-ai/dsh-sdk-protocol
示例 bin@deepseek-ai/dsh-sdk-jsonrpc-demodsh-jsonrpc-agent
单发 bundle@deepseek-ai/dsh-headless
CLI@deepseek-ai/dsh
参考组合 examples/jsonrpc-agent/package.json 是一个私有示例包(jsonrpc-agent-example)——不是已发布的 SDK 工件。

延伸阅读

  • SDK 协议——本插件服务的帧格式、方法与类型。
  • SDK 客户端——线的另一端。
  • shutdown 语义见 SDK:线路协议packages/examples/jsonrpc-demo/src/runner.ts 中的 bin 生命周期。
  • 单发对比:@deepseek-ai/dsh-headlesspackages/bundle/headless/src/startup.ts
  • 运行时、agent 与会话概念:运行时与 Agent 生命周期会话管理
  • 仓库内相对路径:packages/sdk/server/src/server.tspackages/sdk/server/src/index.tsexamples/jsonrpc-agent/cordis.yml