@deepseek-ai/dsh-sdk-jsonrpc-server 0.0.1-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +48 -0
- package/README.zh.md +48 -0
- package/lib/index.js +254 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +36 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/server.d.ts +65 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/sdk/server/README.md
|
|
5
|
+
README.md: 5377b4fcf425cc5e10497e9d40fdddc075d52a10
|
|
6
|
+
README.zh.md: dcca65a7175bb2774460bf265d98e41439df6a01
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-sdk-jsonrpc-server
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
The `jsonrpc` plugin serves newline-delimited JSON-RPC over stdio so out-of-process SDK clients can drive harness agents. [`HarnessSdkJsonRpcServer`](src/server.ts) owns the protocol methods and notifications; the transport and the named wire types live in [`dsh-sdk-protocol`](../protocol/README.md), shared with the client SDKs; [`jsonrpc-demo`](../../examples/jsonrpc-demo/README.md) supplies the surrounding `cordis.yml` application.
|
|
6
|
+
|
|
7
|
+
## Wiring
|
|
8
|
+
|
|
9
|
+
`inject: ['agents']`. The server gets or creates one agent per `sessionId`. It forwards subagent completions only when the service-snapshotted lifecycle `local` flag is true; provider names, child ids, and durable lineage never establish locality. A registered adapter wins, an unowned `deepseek-official` route mounts `dsh-llm-deepseek`, and any other unowned provider fails initialization. Other capabilities come from the surrounding `cordis.yml`.
|
|
10
|
+
|
|
11
|
+
## Config
|
|
12
|
+
|
|
13
|
+
`maxTokensAsSuccess` defaults to `false` and affects only the deployment-mapped status on `subagent.finished`; root-session prompts have no prompt-level status. `JsonRpcConfig.input`, `output`, and `exit` are runtime-only transport hooks; production uses process stdio and `process.exit`.
|
|
14
|
+
|
|
15
|
+
## stdout is the protocol
|
|
16
|
+
|
|
17
|
+
Stdout carries only JSON-RPC frames. The deployment must not compose a stdout logger; diagnostics belong on stderr.
|
|
18
|
+
|
|
19
|
+
## Shutdown and exit semantics
|
|
20
|
+
|
|
21
|
+
The plugin answers `shutdown`, flushes the response, disposes the root context so SDK-owned agents, subscriptions, and persistence reach quiescence, then exits with code 0. EOF and signal exits belong to the app bin, which also disposes the root context. Unloading only this plugin stops serving without exiting the process.
|
|
22
|
+
|
|
23
|
+
## Wire notes
|
|
24
|
+
|
|
25
|
+
`initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. An optional positive `initialize.maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants; invalid values reject initialization, while omission sends no SDK cap and allows the selected adapter or provider route default to apply. `session/prompt` queues one identified user message and immediately returns `{ messageId }`. The server streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`; it does not assign an assistant message or `turn/end` to that prompt. Independent requests may enqueue more work on the same session. Persistence roots and persona come from `cordis.yml`.
|
|
26
|
+
|
|
27
|
+
## Model Experience
|
|
28
|
+
|
|
29
|
+
### SDK user message
|
|
30
|
+
|
|
31
|
+
#### What the model sees
|
|
32
|
+
|
|
33
|
+
For each accepted `session/prompt`, the conversation model receives the caller-supplied `contentBlocks` verbatim as one user message in that SDK session. This package adds no system-prompt prose or tool schema; those come from the plugins in the surrounding `cordis.yml`.
|
|
34
|
+
|
|
35
|
+
#### Token effect
|
|
36
|
+
|
|
37
|
+
Data-dependent user-message tokens enter retained session history and are resent on later turns until another package compacts them. The JSON-RPC frames, session notifications, and server bookkeeping add zero model-context tokens.
|
|
38
|
+
|
|
39
|
+
#### KV Cache effect
|
|
40
|
+
|
|
41
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
42
|
+
|
|
43
|
+
## Known Limitations and Deferred Work
|
|
44
|
+
|
|
45
|
+
- **The wire has no per-session close or prompt-cancel method** — SDK-created agents remain live until process shutdown.
|
|
46
|
+
- **There is no per-prompt result** — `MessageId` identifies inbox admission only; clients that own an automation interval must define and observe that interval themselves.
|
|
47
|
+
- **stdout purity is deployment-enforced** — a surrounding config can still load a stdout logger and corrupt the JSON-RPC channel; this plugin does not inspect or veto sibling loggers.
|
|
48
|
+
- **Automatic adapter mounting is DeepSeek-specific** — `initialize` can reuse any pre-registered model adapter, but its only fallback mounts `dsh-llm-deepseek`.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-sdk-jsonrpc-server
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
`jsonrpc` 插件通过 stdio 提供以换行符分隔的 JSON-RPC,使进程外 SDK 客户端能够驱动 harness agent(智能体)。[`HarnessSdkJsonRpcServer`](src/server.ts) 负责协议方法和通知;传输与具名协议类型位于 [`dsh-sdk-protocol`](../protocol/README.md),与客户端 SDK 共享;[`jsonrpc-demo`](../../examples/jsonrpc-demo/README.md) 提供外围的 `cordis.yml` 应用。
|
|
6
|
+
|
|
7
|
+
## 组装
|
|
8
|
+
|
|
9
|
+
`inject: ['agents']`。服务器按 `sessionId` 获取或创建一个 agent。只有服务对生命周期建立快照时记录的 `local` 标志为 true,服务器才会转发 subagent 完成事件;提供方名称、子级 id 和持久化谱系均不能证明本地性。已注册的适配器优先;尚无适配器负责的 `deepseek-official` 路由会挂载 `dsh-llm-deepseek`,任何其他尚无适配器负责的提供方都会导致初始化失败。其他能力由外围 `cordis.yml` 提供。
|
|
10
|
+
|
|
11
|
+
## 配置
|
|
12
|
+
|
|
13
|
+
`maxTokensAsSuccess` 默认为 `false`,且只影响 `subagent.finished` 上由部署映射的状态;根会话提示词没有提示词级状态。`JsonRpcConfig.input`、`output` 和 `exit` 是仅供运行时使用的传输钩子;生产环境使用进程 stdio 和 `process.exit`。
|
|
14
|
+
|
|
15
|
+
## stdout 即协议
|
|
16
|
+
|
|
17
|
+
Stdout 只承载 JSON-RPC 帧。部署不得组合 stdout logger;诊断应写入 stderr。
|
|
18
|
+
|
|
19
|
+
## 关闭与退出语义
|
|
20
|
+
|
|
21
|
+
插件响应 `shutdown`,刷新响应并 dispose(资源释放)根上下文,使 SDK 持有的 agent、订阅和持久化达到完全停稳,然后以代码 0 退出。EOF 和信号退出由 app bin 处理,后者也会 dispose 根上下文。仅卸载此插件会停止服务,但不会退出进程。
|
|
22
|
+
|
|
23
|
+
## 协议说明
|
|
24
|
+
|
|
25
|
+
`initialize.serverInfo.name` 的协议稳定值为 `deepseek-harness-sdk-runtime`。可选的正整数 `initialize.maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限;非法值会使初始化失败,省略时则不发送 SDK 上限,并应用所选适配器或提供方路由的默认值。`session/prompt` 将一条带标识的用户消息排入队列,并立即返回 `{ messageId }`。服务器将每个持久事实作为 `session.event` 流式发出,并将整个 agent 生命周期的每次状态转换作为 `session.status` 发出;它不会把某条助手消息或 `turn/end` 归属于该提示词。同一会话上的独立请求可以继续排入更多工作。持久化根目录和 persona 由 `cordis.yml` 提供。
|
|
26
|
+
|
|
27
|
+
## 模型体验
|
|
28
|
+
|
|
29
|
+
### SDK 用户消息
|
|
30
|
+
|
|
31
|
+
#### 模型看到的内容
|
|
32
|
+
|
|
33
|
+
对于每个已接受的 `session/prompt`,对话模型会将调用方提供的 `contentBlocks` 原样作为该 SDK 会话中的一条用户消息接收。此包不会添加系统提示词文本或工具 schema;这些内容来自外围 `cordis.yml` 中的插件。
|
|
34
|
+
|
|
35
|
+
#### Token 影响
|
|
36
|
+
|
|
37
|
+
依数据而定的用户消息 token 会进入保留的会话历史,并在后续轮次中重复发送,直至另一个包将其压缩(compaction)。JSON-RPC 帧、会话通知和服务器内部记录不会增加模型上下文 token。
|
|
38
|
+
|
|
39
|
+
#### KV Cache 影响
|
|
40
|
+
|
|
41
|
+
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
42
|
+
|
|
43
|
+
## 已知限制与暂缓事项
|
|
44
|
+
|
|
45
|
+
- **协议没有逐会话关闭或提示词取消方法**:SDK 创建的 agent 会一直存活到进程关闭。
|
|
46
|
+
- **没有逐提示词结果**:`MessageId` 只标识 inbox 准入;拥有自动化活动区间的客户端必须自行定义并观察该区间。
|
|
47
|
+
- **stdout 纯净性由部署保证**:外围配置仍可能加载 stdout logger 并破坏 JSON-RPC 通道;此插件不会检查或否决同级 logger。
|
|
48
|
+
- **自动挂载适配器仅支持 DeepSeek**:`initialize` 可以复用任何预先注册的模型适配器,但唯一的回退行为是挂载 `dsh-llm-deepseek`。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
2
|
+
import { JsonRpcLineTransport } from "@deepseek-ai/dsh-sdk-protocol";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
|
+
import { carrierKeyOf } from "@deepseek-ai/dsh-scope";
|
|
6
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
7
|
+
import * as LlmDeepSeek from "@deepseek-ai/dsh-llm-deepseek";
|
|
8
|
+
//#region lib/types/server.js
|
|
9
|
+
/**
|
|
10
|
+
* JSON-RPC methods and notifications for out-of-process harness SDKs.
|
|
11
|
+
* The surrounding context owns plugins, persistence, and configured adapters.
|
|
12
|
+
*
|
|
13
|
+
* @module @deepseek-ai/dsh-sdk-jsonrpc-server/server
|
|
14
|
+
*/
|
|
15
|
+
/** Recover the delegating parent from the service-owned scoped carrier. */
|
|
16
|
+
function subagentParentOf(carrier) {
|
|
17
|
+
return carrierKeyOf(carrier);
|
|
18
|
+
}
|
|
19
|
+
function successStatus(reason, options) {
|
|
20
|
+
if (reason === "completed") return "ok";
|
|
21
|
+
return reason === "max-tokens" && options.maxTokensAsSuccess === true ? "ok" : "error";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* SDK server over one booted harness context and transport peer. Construction
|
|
25
|
+
* subscribes to session, agent, and subagent lifecycle events until shutdown;
|
|
26
|
+
* reinitialization is unsupported.
|
|
27
|
+
*/
|
|
28
|
+
var HarnessSdkJsonRpcServer = class {
|
|
29
|
+
ctx;
|
|
30
|
+
transport;
|
|
31
|
+
options;
|
|
32
|
+
cwd = process.cwd();
|
|
33
|
+
provider = "deepseek-official";
|
|
34
|
+
model = "deepseek-official";
|
|
35
|
+
maxTokens;
|
|
36
|
+
llmFiber;
|
|
37
|
+
sessions = /* @__PURE__ */ new Map();
|
|
38
|
+
sessionCreations = /* @__PURE__ */ new Map();
|
|
39
|
+
disposers = [];
|
|
40
|
+
shutdownTask;
|
|
41
|
+
shuttingDown = false;
|
|
42
|
+
constructor(ctx, transport, options = {}) {
|
|
43
|
+
this.ctx = ctx;
|
|
44
|
+
this.transport = transport;
|
|
45
|
+
this.options = options;
|
|
46
|
+
const serverOptions = this.options;
|
|
47
|
+
this.disposers.push(ctx.on("session/event", (session, event) => {
|
|
48
|
+
const payload = {
|
|
49
|
+
sessionId: String(session.id),
|
|
50
|
+
event
|
|
51
|
+
};
|
|
52
|
+
this.transport.notify("session.event", payload);
|
|
53
|
+
}));
|
|
54
|
+
this.disposers.push(ctx.on("agent/status", ({ agent, status }) => {
|
|
55
|
+
this.transport.notify("session.status", {
|
|
56
|
+
sessionId: String(agent.session.id),
|
|
57
|
+
status
|
|
58
|
+
});
|
|
59
|
+
}));
|
|
60
|
+
this.disposers.push(ctx.on("session/created", (session) => {
|
|
61
|
+
const parentSession = session.header.parentSession;
|
|
62
|
+
if (parentSession === void 0) return;
|
|
63
|
+
const payload = {
|
|
64
|
+
parentSessionId: String(parentSession),
|
|
65
|
+
childSessionId: String(session.id)
|
|
66
|
+
};
|
|
67
|
+
this.transport.notify("subagent.started", payload);
|
|
68
|
+
}));
|
|
69
|
+
this.disposers.push(ctx.on("subagent/end", function(info) {
|
|
70
|
+
const parent = subagentParentOf(this);
|
|
71
|
+
if (!info.local) return;
|
|
72
|
+
const payload = {
|
|
73
|
+
provider: info.provider,
|
|
74
|
+
agentId: String(info.id),
|
|
75
|
+
parentSessionId: String(parent.session.id),
|
|
76
|
+
childSessionId: String(info.id),
|
|
77
|
+
status: successStatus(info.stopReason, serverOptions),
|
|
78
|
+
stopReason: info.stopReason,
|
|
79
|
+
...info.lastAssistantMessage === void 0 ? {} : { lastAssistantMessage: info.lastAssistantMessage }
|
|
80
|
+
};
|
|
81
|
+
transport.notify("subagent.finished", payload);
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Configure the SDK route, mounting the DeepSeek fallback only when unowned.
|
|
86
|
+
* @param params - SDK handshake parameters.
|
|
87
|
+
* @returns server identity for the handshake.
|
|
88
|
+
*/
|
|
89
|
+
async initialize(params) {
|
|
90
|
+
if (params.maxTokens !== void 0 && (!Number.isSafeInteger(params.maxTokens) || params.maxTokens <= 0)) throw new TypeError("initialize maxTokens must be a positive safe integer");
|
|
91
|
+
this.cwd = resolve(params.cwd);
|
|
92
|
+
this.provider = params.provider;
|
|
93
|
+
this.model = params.model;
|
|
94
|
+
this.maxTokens = params.maxTokens;
|
|
95
|
+
if (!this.hasAdapterFor(this.provider)) {
|
|
96
|
+
if (this.provider !== "deepseek-official") throw new Error(`no adapter registered for provider "${this.provider}"`);
|
|
97
|
+
this.llmFiber = await this.ctx.plugin(LlmDeepSeek, {});
|
|
98
|
+
}
|
|
99
|
+
return { serverInfo: {
|
|
100
|
+
name: "deepseek-harness-sdk-runtime",
|
|
101
|
+
version: "0.0.1"
|
|
102
|
+
} };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Queue one identified prompt without assigning later activity to it.
|
|
106
|
+
* @param params - target session and user content.
|
|
107
|
+
* @returns the durable message identity.
|
|
108
|
+
*/
|
|
109
|
+
async prompt(params) {
|
|
110
|
+
const rec = await this.getOrCreateSession(params.sessionId);
|
|
111
|
+
if (this.ctx.agents.get(rec.handle.agent.id) !== rec.handle.agent) throw new Error(`session agent was disposed outside the server: ${params.sessionId}`);
|
|
112
|
+
const message = createUserMessage({
|
|
113
|
+
content: params.contentBlocks,
|
|
114
|
+
source: { kind: "user" }
|
|
115
|
+
});
|
|
116
|
+
rec.handle.agent.followup(message);
|
|
117
|
+
return { messageId: message.id };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Dispose server-owned agents, adapter, and subscriptions to quiescence.
|
|
121
|
+
* The surrounding context remains running.
|
|
122
|
+
* @returns empty JSON-RPC result.
|
|
123
|
+
*/
|
|
124
|
+
shutdown() {
|
|
125
|
+
this.shutdownTask ??= this.performShutdown();
|
|
126
|
+
return this.shutdownTask;
|
|
127
|
+
}
|
|
128
|
+
async performShutdown() {
|
|
129
|
+
this.shuttingDown = true;
|
|
130
|
+
const pendingCreations = [...this.sessionCreations.values()];
|
|
131
|
+
await Promise.allSettled(pendingCreations);
|
|
132
|
+
this.sessionCreations.clear();
|
|
133
|
+
const records = [...this.sessions.values()];
|
|
134
|
+
this.sessions.clear();
|
|
135
|
+
const failures = [];
|
|
136
|
+
while (this.disposers.length > 0) try {
|
|
137
|
+
this.disposers.pop()?.();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
failures.push(error);
|
|
140
|
+
}
|
|
141
|
+
const teardownResults = await Promise.allSettled([...records.map((rec) => Promise.resolve().then(() => rec.handle.dispose())), ...this.llmFiber === void 0 ? [] : [Promise.resolve().then(() => this.llmFiber?.dispose())]]);
|
|
142
|
+
this.llmFiber = void 0;
|
|
143
|
+
failures.push(...teardownResults.filter((result) => result.status === "rejected").map((result) => result.reason));
|
|
144
|
+
if (failures.length === 1) throw failures[0];
|
|
145
|
+
if (failures.length > 1) throw new AggregateError(failures, "SDK server teardown failed");
|
|
146
|
+
return {};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Dispatch one incoming JSON-RPC request to its typed handler. Throws (→ a
|
|
150
|
+
* JSON-RPC error response) on an unknown method.
|
|
151
|
+
* @param method - the JSON-RPC method name.
|
|
152
|
+
* @param params - the raw params object from the wire.
|
|
153
|
+
* @returns the handler's result, to be serialized as the response.
|
|
154
|
+
*/
|
|
155
|
+
async handleRequest(method, params) {
|
|
156
|
+
switch (method) {
|
|
157
|
+
case "initialize": return this.initialize(params);
|
|
158
|
+
case "session/prompt": return this.prompt(params);
|
|
159
|
+
case "shutdown": return this.shutdown();
|
|
160
|
+
default: throw new Error(`unknown DeepSeek Harness SDK runtime method: ${method}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async getOrCreateSession(sessionId) {
|
|
164
|
+
if (this.shuttingDown) throw new Error("SDK server is shutting down");
|
|
165
|
+
const existing = this.sessions.get(sessionId);
|
|
166
|
+
if (existing) return existing;
|
|
167
|
+
const pending = this.sessionCreations.get(sessionId);
|
|
168
|
+
if (pending) return pending;
|
|
169
|
+
const creation = this.createSession(sessionId);
|
|
170
|
+
this.sessionCreations.set(sessionId, creation);
|
|
171
|
+
creation.then(() => {
|
|
172
|
+
this.sessionCreations.delete(sessionId);
|
|
173
|
+
}, () => {
|
|
174
|
+
this.sessionCreations.delete(sessionId);
|
|
175
|
+
});
|
|
176
|
+
return creation;
|
|
177
|
+
}
|
|
178
|
+
async createSession(sessionId) {
|
|
179
|
+
const rec = { handle: await this.ctx.agents.create({
|
|
180
|
+
sessionId: SessionId(sessionId),
|
|
181
|
+
meta: { cwd: this.cwd },
|
|
182
|
+
agentOptions: {
|
|
183
|
+
provider: this.provider,
|
|
184
|
+
model: this.model,
|
|
185
|
+
...this.maxTokens === void 0 ? {} : { maxTokens: this.maxTokens }
|
|
186
|
+
}
|
|
187
|
+
}) };
|
|
188
|
+
this.sessions.set(sessionId, rec);
|
|
189
|
+
return rec;
|
|
190
|
+
}
|
|
191
|
+
hasAdapterFor(provider) {
|
|
192
|
+
return this.ctx.get("llm")?.listProviders().some((entry) => entry.id === provider) ?? false;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region lib/types/index.js
|
|
197
|
+
/**
|
|
198
|
+
* SDK-facing JSON-RPC plugin over stdio. An external `cordis.yml` decides
|
|
199
|
+
* whether to load it; see the single-executable Agent Note and package README.
|
|
200
|
+
* Stdout is reserved for protocol frames, so the tree must not load a stdout logger.
|
|
201
|
+
* This plugin answers `shutdown`, disposes the complete root runtime, and exits 0; the app bin
|
|
202
|
+
* owns EOF and signal exits. Keep named plugin exports with no default export so
|
|
203
|
+
* Loader `unwrapExports` preserves `name`, `inject`, `Config`, and `apply`.
|
|
204
|
+
*
|
|
205
|
+
* @module @deepseek-ai/dsh-sdk-jsonrpc-server
|
|
206
|
+
*/
|
|
207
|
+
const name = "sdk-jsonrpc-server";
|
|
208
|
+
const inject = ["agents"];
|
|
209
|
+
const Config = Schema.object({ maxTokensAsSuccess: Schema.boolean().default(false) });
|
|
210
|
+
/**
|
|
211
|
+
* Serve SDK requests over the configured streams. Effect disposal shuts down
|
|
212
|
+
* SDK-created agents and closes the transport. A `shutdown` response is flushed
|
|
213
|
+
* before the root runtime is disposed and the process exits 0; the app bin
|
|
214
|
+
* owns root-context disposal for EOF and signals.
|
|
215
|
+
*/
|
|
216
|
+
function apply(ctx, config) {
|
|
217
|
+
const resolvedConfig = config;
|
|
218
|
+
const rootFiber = ctx.root.fiber;
|
|
219
|
+
/* v8 ignore next -- production stdio wiring; tests always inject the runtime hooks */
|
|
220
|
+
const input = config.input ?? process.stdin;
|
|
221
|
+
/* v8 ignore next -- production stdio wiring; tests always inject the runtime hooks */
|
|
222
|
+
const output = config.output ?? process.stdout;
|
|
223
|
+
/* v8 ignore next -- production exit wiring; tests always inject the runtime hooks */
|
|
224
|
+
const exit = config.exit ?? ((code) => {
|
|
225
|
+
process.exit(code);
|
|
226
|
+
});
|
|
227
|
+
const transport = new JsonRpcLineTransport(input, output);
|
|
228
|
+
const server = new HarnessSdkJsonRpcServer(ctx, transport, { maxTokensAsSuccess: resolvedConfig.maxTokensAsSuccess });
|
|
229
|
+
let exitTask;
|
|
230
|
+
const disposeAndExit = () => {
|
|
231
|
+
exitTask ??= (async () => {
|
|
232
|
+
await Promise.allSettled([Promise.resolve().then(() => transport.flush())]);
|
|
233
|
+
await Promise.allSettled([Promise.resolve().then(() => rootFiber.dispose())]);
|
|
234
|
+
exit(0);
|
|
235
|
+
})();
|
|
236
|
+
return exitTask;
|
|
237
|
+
};
|
|
238
|
+
transport.onRequest(async (method, params) => {
|
|
239
|
+
const result = await server.handleRequest(method, params);
|
|
240
|
+
if (method === "shutdown") setImmediate(() => {
|
|
241
|
+
disposeAndExit();
|
|
242
|
+
});
|
|
243
|
+
return result;
|
|
244
|
+
});
|
|
245
|
+
ctx.effect(() => {
|
|
246
|
+
transport.start();
|
|
247
|
+
return async () => {
|
|
248
|
+
await server.shutdown();
|
|
249
|
+
transport.close();
|
|
250
|
+
};
|
|
251
|
+
}, "jsonrpc.serve");
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { Config, HarnessSdkJsonRpcServer, apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-sdk-jsonrpc-server`.
|
|
4
|
+
* @module @deepseek-ai/dsh-sdk-jsonrpc-server/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-sdk-jsonrpc-server";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "sdk-jsonrpc-server-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this presentation adapter owns no durable package-local event stream;
|
|
13
|
+
* boundary and replay tests cover its protocol mapping.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK-facing JSON-RPC plugin over stdio. An external `cordis.yml` decides
|
|
3
|
+
* whether to load it; see the single-executable Agent Note and package README.
|
|
4
|
+
* Stdout is reserved for protocol frames, so the tree must not load a stdout logger.
|
|
5
|
+
* This plugin answers `shutdown`, disposes the complete root runtime, and exits 0; the app bin
|
|
6
|
+
* owns EOF and signal exits. Keep named plugin exports with no default export so
|
|
7
|
+
* Loader `unwrapExports` preserves `name`, `inject`, `Config`, and `apply`.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-sdk-jsonrpc-server
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { Readable, Writable } from 'node:stream';
|
|
13
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
14
|
+
export * from './server.ts';
|
|
15
|
+
export declare const name = "sdk-jsonrpc-server";
|
|
16
|
+
export declare const inject: string[];
|
|
17
|
+
/** JSON-RPC deployment config plus runtime-only test hooks. */
|
|
18
|
+
export interface JsonRpcConfig {
|
|
19
|
+
/** Report max-token turn/subagent termination as a successful SDK result. */
|
|
20
|
+
maxTokensAsSuccess?: boolean;
|
|
21
|
+
/** Transport input override; production uses `process.stdin`. */
|
|
22
|
+
input?: Readable;
|
|
23
|
+
/** Transport output override; production uses `process.stdout`. */
|
|
24
|
+
output?: Writable;
|
|
25
|
+
/** Process-exit override; production uses `process.exit`. */
|
|
26
|
+
exit?: (code: number) => void;
|
|
27
|
+
}
|
|
28
|
+
export declare const Config: Schema<JsonRpcConfig>;
|
|
29
|
+
/**
|
|
30
|
+
* Serve SDK requests over the configured streams. Effect disposal shuts down
|
|
31
|
+
* SDK-created agents and closes the transport. A `shutdown` response is flushed
|
|
32
|
+
* before the root runtime is disposed and the process exits 0; the app bin
|
|
33
|
+
* owns root-context disposal for EOF and signals.
|
|
34
|
+
*/
|
|
35
|
+
export declare function apply(ctx: Context, config: JsonRpcConfig): void;
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-sdk-jsonrpc-server`.
|
|
3
|
+
* @module @deepseek-ai/dsh-sdk-jsonrpc-server/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "sdk-jsonrpc-server-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC methods and notifications for out-of-process harness SDKs.
|
|
3
|
+
* The surrounding context owns plugins, persistence, and configured adapters.
|
|
4
|
+
*
|
|
5
|
+
* @module @deepseek-ai/dsh-sdk-jsonrpc-server/server
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import type { InitializeParams, InitializeResult, JsonRpcTransportPeer, SessionPromptParams, SessionPromptResult } from '@deepseek-ai/dsh-sdk-protocol';
|
|
9
|
+
/** Deployment-specific status mapping for SDK turn and subagent outcomes. */
|
|
10
|
+
export interface HarnessSdkJsonRpcServerOptions {
|
|
11
|
+
/** Report max-token termination as an accepted result instead of an infrastructure error. */
|
|
12
|
+
maxTokensAsSuccess?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* SDK server over one booted harness context and transport peer. Construction
|
|
16
|
+
* subscribes to session, agent, and subagent lifecycle events until shutdown;
|
|
17
|
+
* reinitialization is unsupported.
|
|
18
|
+
*/
|
|
19
|
+
export declare class HarnessSdkJsonRpcServer {
|
|
20
|
+
private readonly ctx;
|
|
21
|
+
private readonly transport;
|
|
22
|
+
private readonly options;
|
|
23
|
+
private cwd;
|
|
24
|
+
private provider;
|
|
25
|
+
private model;
|
|
26
|
+
private maxTokens;
|
|
27
|
+
private llmFiber;
|
|
28
|
+
private readonly sessions;
|
|
29
|
+
private readonly sessionCreations;
|
|
30
|
+
private readonly disposers;
|
|
31
|
+
private shutdownTask;
|
|
32
|
+
private shuttingDown;
|
|
33
|
+
constructor(ctx: Context, transport: JsonRpcTransportPeer, options?: HarnessSdkJsonRpcServerOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Configure the SDK route, mounting the DeepSeek fallback only when unowned.
|
|
36
|
+
* @param params - SDK handshake parameters.
|
|
37
|
+
* @returns server identity for the handshake.
|
|
38
|
+
*/
|
|
39
|
+
initialize(params: InitializeParams): Promise<InitializeResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Queue one identified prompt without assigning later activity to it.
|
|
42
|
+
* @param params - target session and user content.
|
|
43
|
+
* @returns the durable message identity.
|
|
44
|
+
*/
|
|
45
|
+
prompt(params: SessionPromptParams): Promise<SessionPromptResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Dispose server-owned agents, adapter, and subscriptions to quiescence.
|
|
48
|
+
* The surrounding context remains running.
|
|
49
|
+
* @returns empty JSON-RPC result.
|
|
50
|
+
*/
|
|
51
|
+
shutdown(): Promise<Record<string, never>>;
|
|
52
|
+
private performShutdown;
|
|
53
|
+
/**
|
|
54
|
+
* Dispatch one incoming JSON-RPC request to its typed handler. Throws (→ a
|
|
55
|
+
* JSON-RPC error response) on an unknown method.
|
|
56
|
+
* @param method - the JSON-RPC method name.
|
|
57
|
+
* @param params - the raw params object from the wire.
|
|
58
|
+
* @returns the handler's result, to be serialized as the response.
|
|
59
|
+
*/
|
|
60
|
+
handleRequest(method: string, params: Record<string, unknown> | undefined): Promise<unknown>;
|
|
61
|
+
private getOrCreateSession;
|
|
62
|
+
private createSession;
|
|
63
|
+
private hasAdapterFor;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=server.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-sdk-jsonrpc-server",
|
|
3
|
+
"description": "Stdio JSON-RPC server plugin for out-of-process DeepSeek Harness SDK clients",
|
|
4
|
+
"version": "0.0.1-rc.5",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/sdk/server"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.4"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
|
|
39
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.5",
|
|
40
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.5",
|
|
41
|
+
"@deepseek-ai/dsh-scope": "^0.0.1-rc.5",
|
|
42
|
+
"@deepseek-ai/dsh-sdk-protocol": "^0.0.1-rc.5",
|
|
43
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.5",
|
|
44
|
+
"@deepseek-ai/dsh-subagent": "^0.0.1-rc.5",
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.4",
|
|
46
|
+
"@deepseek-ai/dsh-llm-deepseek": "^0.0.1-rc.5"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2-rc.4",
|
|
50
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.5",
|
|
51
|
+
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1-rc.5",
|
|
52
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
|
|
53
|
+
"@deepseek-ai/dsh-scope": "^0.0.1-rc.5",
|
|
54
|
+
"@deepseek-ai/dsh-sdk-protocol": "^0.0.1-rc.5",
|
|
55
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.5",
|
|
56
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1-rc.5",
|
|
57
|
+
"@deepseek-ai/dsh-subagent": "^0.0.1-rc.5",
|
|
58
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.4",
|
|
59
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.5",
|
|
60
|
+
"@deepseek-ai/dsh-llm-deepseek": "^0.0.1-rc.5"
|
|
61
|
+
}
|
|
62
|
+
}
|