返回

DeepSeek Harness TypeScript SDK:运行任务、保持会话与处理结果

固定使用 dsh 与 dsh-sdk-client 0.2.0-rc.2,说明新版构造参数、多轮 sessionId、子进程回收和 finalResponse 边界,并区分旧版 launch API。

当你需要在自己的程序里显示 Agent 的中间过程、继续同一个会话,或者统一处理失败时,SDK 比拼接 shell 输出更合适。它管理的是一个 Harness 子进程和它的会话;程序仍要为任务结果负责。

本文将 @deepseek-ai/dsh 与 @deepseek-ai/dsh-sdk-client 都固定为 0.2.0-rc.2。构造参数与结果类型已对照发布包的 .d.ts 和该版本源码核对。下面的模型调用示例需要自己的凭据,本轮不附没有重新运行过的回答或耗时。

先把两个包的版本对齐

在独立 Node 项目中安装:

npm init -y
npm install --save-exact \
  @deepseek-ai/[email protected] \
  @deepseek-ai/[email protected]
npm ls @deepseek-ai/dsh @deepseek-ai/dsh-sdk-client

使用已验证的 Node 24 LTS 环境,保存锁文件。不要假定两个包的 latest 标签一定同步;更新前先检查实际解析的版本与类型声明。

旧版 @deepseek-ai/[email protected] 使用必填的 launch 对象,而 0.2.0-rc.2 把 profile、patches、dshHome 等参数放在顶层。这是两套 API,不能一边升级依赖,一边继续复制旧构造代码。旧版示例与主干 README 不一致的经历值得保留,但它不能证明新版示例也有同样问题。

一个任务,从创建到回收

把下面代码存为 demo.mjs:

import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client'

if (!process.env.DEEPSEEK_API_KEY) {
  throw new Error('Set DEEPSEEK_API_KEY before running this example')
}

await using harness = new DeepSeekHarness({
  profile: 'sdk',
  cwd: process.cwd(),
  ...(process.env.DSH_HOME ? { dshHome: process.env.DSH_HOME } : {}),
  initializeTimeoutMs: 30_000,
  requestTimeoutMs: 120_000,
})

const result = await harness.run(
  'Read tutorial-input.txt and report only its port field. Do not modify files.',
)
if (!result.finalResponse.trim()) {
  throw new Error(`No final text for session ${result.sessionId}`)
}

console.log('sessionId:', result.sessionId)
console.log(result.finalResponse)

DEEPSEEK_API_KEY 从启动进程的环境注入。tutorial-input.txt 可以使用安装篇的文件,答案应与其中的 port 字段一致。启动前在当前 shell 配好凭据,然后运行 node demo.mjs。

await using 负责离开作用域时回收实例;不使用这个语法时,用 try/finally 调用 await harness.close()。实例拥有子进程,不应只处理成功路径而忘记异常后的清理。

SDK 默认选择 sdk profile,并能解析同版本运行时。只有要指定特殊启动入口时,再使用 dshBin;不需要自己拼一个指向 .bin 的文件 URL,更不要把 URL 的 .pathname 当作所有系统都正确的文件路径。

多轮对话要显式保持 sessionId

下面的代码放在同一个 harness 作用域内:

const first = await harness.run(
  'For this conversation, remember that the test port is 8421. Acknowledge briefly.',
)
const second = await harness.run(
  'What test port did I ask you to remember?',
  { sessionId: first.sessionId },
)
console.log(second.finalResponse)

harness 实例复用不代表每次 run() 自动复用对话。传入上次的 sessionId,才是在指定会话中继续任务;省略时会创建新的会话。这条约定可在高层 API 声明中核对。

应用需要把会话 ID 作为自己的状态保存,而不是从答案文字里猜。这里讲的是同一运行实例内的续接;跨进程恢复需要另外核对该版本的恢复协议和持久化要求,不能仅凭这个例子保证重启后恢复所有外部资源。

finalResponse 是文字结果,不是成功凭证

RunResult 还包含 events 与 notifications。它们帮助你观察工具调用和任务过程,finalResponse 则是活动区间最后一条提交的 assistant 文本,可能为空。

因此,消费端至少应处理三类情况:

情况 程序怎样处理
启动、协议或超时异常 记录诊断,回收实例,按明确的重试策略处理
调用结束但文字为空 作为独立结果处理,检查事件,不把空文本当作正常答案
文字声称任务完成 检查对应文件、测试或外部系统状态,不能只看一句话

有并发输入、上下文注入等行为时,也不能把最后一条文字当成与某个 prompt 严格一一对应的响应。需要这种关联时,应依据所用版本的事件协议建立关联;直接扫描文本或随意挑一条 notification 都不够。

先从顺序调用和单会话开始,再决定是否需要并发。这样出问题时,可以把一个输入、一个会话和一组事件对应起来。

环境变量与超时容易被忽略

env 参数传入的是子进程的完整环境,不是追加几项的补丁。只传 { DEEPSEEK_API_KEY: ... } 可能同时丢掉 PATH 等启动所需变量。默认继承环境或自己构造允许的完整环境,两种做法都要明确,不把进程启动失败误判成模型故障。

initializeTimeoutMs 与 requestTimeoutMs 也不是整个应用的总时限。多次调用、重试与子进程清理都可能发生在它们之外;应用层仍需控制请求生命周期和关闭流程。

需要的只是单次任务和文件输出时,headless更容易接入。需要在界面显示过程、维护多轮会话时,再使用 SDK;需要改变运行能力,继续看profile 管理。

最后更新于 2026年10月15日 00点00分00秒