@deepseek-ai/dsh-sdk-protocol 0.0.1-rc.1
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 +39 -0
- package/README.zh.md +39 -0
- package/lib/index.js +252 -0
- package/lib/invariant.js +24 -0
- package/lib/types/index.d.ts +13 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/transport.d.ts +104 -0
- package/lib/types/types.d.ts +106 -0
- package/package.json +47 -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/scaffold/protocol/README.md
|
|
5
|
+
README.md: 88a48957d0d44cec9f776d31eab7d25bd353de5f
|
|
6
|
+
README.zh.md: 6618d8838a00f945c79d7ec24b1e7491df08a3f1
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-sdk-protocol
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delimited JSON-RPC 2.0 transport class plus the named request, result, and notification types both wire ends speak. The package root enumerates the protocol consumer interface; source modules are not exported as deep imports. The server side is the [`dsh-jsonrpc`](../server/README.md) plugin; clients are [`dsh-sdk-client`](../client/README.md) (TypeScript) and the [Python SDK](../../../python/README.md) (which mirrors these shapes but does not import them). A pure library — no plugin, no Config, no registration.
|
|
6
|
+
|
|
7
|
+
## Transport
|
|
8
|
+
|
|
9
|
+
`JsonRpcLineTransport` frames JSON-RPC 2.0 over caller-owned byte streams, one compact JSON frame per `\n`-terminated line. Frames with `id` and `method` are requests, `id` alone is a response, `method` alone is a notification; malformed JSON lines are ignored. `start()` attaches stream listeners, `close()` detaches them and rejects pending requests without destroying the streams. Missing request handlers answer `-32601`; handler rejections answer `-32603` with the error message. An error response rejects the pending `request()` with `JsonRpcResponseError`, which preserves the wire `code` and optional `data`. `JsonRpcTransportPeer` is the outbound surface (request/notify) the server class is typed against.
|
|
10
|
+
|
|
11
|
+
## Wire types
|
|
12
|
+
|
|
13
|
+
`types.ts` names every payload of the protocol served by `HarnessSdkServer`:
|
|
14
|
+
|
|
15
|
+
| Direction | Method | Types |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| client→server | `initialize` | `InitializeParams` → `InitializeResult` |
|
|
18
|
+
| client→server | `session/prompt` | `SessionPromptParams` → `SessionPromptResult` (durable enqueue receipt) |
|
|
19
|
+
| client→server | `shutdown` | no params → `{}` |
|
|
20
|
+
| server→client | `session.event` | `SessionEventNotification` (every session in the runtime, unfiltered) |
|
|
21
|
+
| server→client | `session.status` | `SessionStatusNotification` (whole-agent `running`/`idle` transition) |
|
|
22
|
+
| server→client | `subagent.started` | `SubagentStartedNotification` |
|
|
23
|
+
| server→client | `subagent.finished` | `SubagentFinishedNotification` (in-process runs only) |
|
|
24
|
+
|
|
25
|
+
`HarnessSdkRequestMap` and `HarnessSdkNotificationMap` index these by method name. `SessionPromptResult.messageId` identifies the queued `UserMessage`; it does not identify a later assistant message, turn ending, or prompt result. Clients combine the open-ended `session.event` stream with agent-wide `session.status` according to their own activity ownership. `InitializeParams.maxTokens` is an optional positive safe integer that caps each conversation-model output for SDK-created agents and their in-process descendants; omission allows the selected adapter's exact-model default to apply, or otherwise preserves provider behavior. The notification payload types depend on `SessionEvent` (`dsh-session`), `ContentBlock` (`dsh-llm`), and `SubagentStopReason` (`dsh-subagent`) — the protocol streams full session-log envelopes, so the session vocabulary is part of the wire contract. `serverInfo.name` stays the wire-stable `deepseek-harness-sdk-runtime`.
|
|
26
|
+
|
|
27
|
+
## Model Experience
|
|
28
|
+
|
|
29
|
+
None, as this package defines the client-facing wire protocol; the model-visible surfaces belong to the runtime plugins composed behind the serving [`dsh-jsonrpc`](../server/README.md) entry.
|
|
30
|
+
|
|
31
|
+
#### KV Cache effect
|
|
32
|
+
|
|
33
|
+
None; this package neither assembles nor sends a provider request.
|
|
34
|
+
|
|
35
|
+
## Known Limitations and Deferred Work
|
|
36
|
+
|
|
37
|
+
- **No protocol-version negotiation** — the handshake carries only `serverInfo.version` (`0.0.1`, unvalidated by clients); pre-release stance, no compatibility promise.
|
|
38
|
+
- **No cancel or session-close methods** — a client abandons a turn by closing the runtime process; see the [`dsh-jsonrpc` README](../server/README.md).
|
|
39
|
+
- **Server→client requests are dead capability** — the transport supports them, but the server never sends one; the Python SDK's responder surface exists for future approval flows.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-sdk-protocol
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness SDK 运行时的共享协议格式(wire format):一个按换行分帧的 JSON-RPC 2.0 传输类,加上协议两端共同使用的具名请求、结果与通知类型。包根枚举协议消费方接口;源模块不支持深层导入。服务端是 [`dsh-jsonrpc`](../server/README.md) 插件;客户端是 [`dsh-sdk-client`](../client/README.md)(TypeScript)与 [Python SDK](../../../python/README.md)(后者复现这些结构但不导入它们)。纯库——无插件、无 Config、无注册。
|
|
6
|
+
|
|
7
|
+
## 传输
|
|
8
|
+
|
|
9
|
+
`JsonRpcLineTransport` 在调用方持有的字节流上为 JSON-RPC 2.0 分帧,每行一个紧凑 JSON 帧、以 `\n` 结尾。带 `id` 与 `method` 的帧是请求,仅 `id` 是响应,仅 `method` 是通知;非法 JSON 行被忽略。`start()` 挂接流监听器,`close()` 移除监听器并拒绝挂起请求,但不销毁流。缺失请求处理器时应答 `-32601`;处理器返回的 Promise 被拒绝时,则应答携带错误消息的 `-32603`。错误响应会以 `JsonRpcResponseError` 拒绝挂起的 `request()` Promise,并保留协议格式中的 `code` 与可选 `data`。`JsonRpcTransportPeer` 是服务器类据以进行类型声明的出站接口(request/notify)。
|
|
10
|
+
|
|
11
|
+
## 协议类型
|
|
12
|
+
|
|
13
|
+
`types.ts` 为 `HarnessSdkServer` 所服务协议的每个载荷命名:
|
|
14
|
+
|
|
15
|
+
| 方向 | 方法 | 类型 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| client→server | `initialize` | `InitializeParams` → `InitializeResult` |
|
|
18
|
+
| client→server | `session/prompt` | `SessionPromptParams` → `SessionPromptResult`(持久入队回执) |
|
|
19
|
+
| client→server | `shutdown` | 无参数 → `{}` |
|
|
20
|
+
| server→client | `session.event` | `SessionEventNotification`(运行时内每个会话,不过滤) |
|
|
21
|
+
| server→client | `session.status` | `SessionStatusNotification`(整个 agent(智能体)的 `running`/`idle` 转换) |
|
|
22
|
+
| server→client | `subagent.started` | `SubagentStartedNotification` |
|
|
23
|
+
| server→client | `subagent.finished` | `SubagentFinishedNotification`(仅进程内运行) |
|
|
24
|
+
|
|
25
|
+
`HarnessSdkRequestMap` 与 `HarnessSdkNotificationMap` 按方法名索引这些类型。`SessionPromptResult.messageId` 标识已排队的 `UserMessage`;它不标识后续的助手消息、轮次结束或提示词结果。客户端根据自己对活动区间的所有权,组合持续开放的 `session.event` 流与 agent 级的 `session.status`。`InitializeParams.maxTokens` 是可选的正的安全整数,用于限制 SDK 创建的 agent 及其进程内后代的每次对话模型输出;省略时会应用所选适配器的确切模型默认值,否则提供方行为保持不变。通知载荷类型依赖 `SessionEvent`(`dsh-session`)、`ContentBlock`(`dsh-llm`)与 `SubagentStopReason`(`dsh-subagent`)——协议以完整会话日志封套进行流式传输,因此会话词汇是协议格式约定的一部分。`serverInfo.name` 的协议值固定为 `deepseek-harness-sdk-runtime`。
|
|
26
|
+
|
|
27
|
+
## 模型体验
|
|
28
|
+
|
|
29
|
+
无,因为此包定义面向客户端的协议格式;模型可见接口属于组合在对外服务入口 [`dsh-jsonrpc`](../server/README.md) 后方的运行时插件。
|
|
30
|
+
|
|
31
|
+
#### KV Cache 影响
|
|
32
|
+
|
|
33
|
+
无;此包既不组装也不发送提供方请求。
|
|
34
|
+
|
|
35
|
+
## 已知限制与暂缓事项
|
|
36
|
+
|
|
37
|
+
- **无协议版本协商**——握手只携带 `serverInfo.version`(`0.0.1`,客户端不校验);处于预发布阶段,无兼容承诺。
|
|
38
|
+
- **无取消与会话关闭方法**——客户端放弃轮次的方式是关闭运行时进程;见 [`dsh-jsonrpc` README](../server/README.md)。
|
|
39
|
+
- **server→client 请求是未使用的功能**——传输层支持,但服务器从不发送;Python SDK 的应答接口为未来审批流程预留。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
|
+
//#region lib/types/transport.js
|
|
4
|
+
/**
|
|
5
|
+
* Newline-delimited JSON-RPC 2.0 over byte streams. Frames with `id` and
|
|
6
|
+
* `method` are requests, `id` alone is a response, and `method` alone is a
|
|
7
|
+
* notification. Malformed lines are ignored; handler failures become error frames.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-sdk-protocol/transport
|
|
10
|
+
*/
|
|
11
|
+
/** A JSON-RPC error response, preserving the wire `code` and optional `data`. */
|
|
12
|
+
var JsonRpcResponseError = class extends Error {
|
|
13
|
+
code;
|
|
14
|
+
data;
|
|
15
|
+
/**
|
|
16
|
+
* @param code - the wire error code, or `undefined` when the peer sent none.
|
|
17
|
+
* @param message - the wire error message.
|
|
18
|
+
* @param data - the optional structured error payload, verbatim.
|
|
19
|
+
*/
|
|
20
|
+
constructor(code, message, data) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.data = data;
|
|
24
|
+
this.name = "JsonRpcResponseError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Line-delimited endpoint over caller-owned streams. {@link start} attaches
|
|
29
|
+
* listeners; {@link close} detaches them and rejects pending requests without
|
|
30
|
+
* destroying the streams. Missing request handlers return `-32601`; handler
|
|
31
|
+
* failures return `-32603`. Notifications without a handler are dropped.
|
|
32
|
+
*/
|
|
33
|
+
var JsonRpcLineTransport = class {
|
|
34
|
+
input;
|
|
35
|
+
output;
|
|
36
|
+
buffer = "";
|
|
37
|
+
decoder = new StringDecoder("utf8");
|
|
38
|
+
started = false;
|
|
39
|
+
requestHandler;
|
|
40
|
+
notificationHandler;
|
|
41
|
+
pending = /* @__PURE__ */ new Map();
|
|
42
|
+
constructor(input, output) {
|
|
43
|
+
this.input = input;
|
|
44
|
+
this.output = output;
|
|
45
|
+
}
|
|
46
|
+
/** Attach the input listeners and begin reading frames. Idempotent. */
|
|
47
|
+
start() {
|
|
48
|
+
if (this.started) return;
|
|
49
|
+
this.started = true;
|
|
50
|
+
this.input.on("data", this.onData);
|
|
51
|
+
this.input.on("error", this.onInputError);
|
|
52
|
+
this.input.on("end", this.onInputEnd);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detach listeners and reject pending requests. Safe before {@link start}.
|
|
56
|
+
*/
|
|
57
|
+
close() {
|
|
58
|
+
this.input.off("data", this.onData);
|
|
59
|
+
this.input.off("error", this.onInputError);
|
|
60
|
+
this.input.off("end", this.onInputEnd);
|
|
61
|
+
this.failPending(/* @__PURE__ */ new Error("JSON-RPC transport closed"));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Install the request handler, replacing any prior handler.
|
|
65
|
+
* @param handler - resolves to the response `result`; a rejection becomes a
|
|
66
|
+
* `-32603` error response carrying the message.
|
|
67
|
+
*/
|
|
68
|
+
onRequest(handler) {
|
|
69
|
+
this.requestHandler = handler;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Install the notification handler, replacing any prior handler.
|
|
73
|
+
* @param handler - invoked per notification with the method and normalized
|
|
74
|
+
* params object.
|
|
75
|
+
*/
|
|
76
|
+
onNotification(handler) {
|
|
77
|
+
this.notificationHandler = handler;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Send a request and await its response.
|
|
81
|
+
* @param method - the JSON-RPC method name.
|
|
82
|
+
* @param params - the request parameters object.
|
|
83
|
+
* @param signal - optional abandonment signal: aborting removes the pending
|
|
84
|
+
* entry (no state is retained for a response that may never come) and
|
|
85
|
+
* rejects with the signal's reason.
|
|
86
|
+
* @returns the result; rejects per {@link JsonRpcTransportPeer.request}.
|
|
87
|
+
*/
|
|
88
|
+
request(method, params, signal) {
|
|
89
|
+
const id = `req_${randomUUID().replaceAll("-", "")}`;
|
|
90
|
+
const message = {
|
|
91
|
+
jsonrpc: "2.0",
|
|
92
|
+
id,
|
|
93
|
+
method,
|
|
94
|
+
params
|
|
95
|
+
};
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
let detach = () => {};
|
|
98
|
+
if (signal !== void 0) {
|
|
99
|
+
if (signal.aborted) {
|
|
100
|
+
reject(abortError(signal.reason));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const onAbort = () => {
|
|
104
|
+
this.pending.delete(id);
|
|
105
|
+
reject(abortError(signal.reason));
|
|
106
|
+
};
|
|
107
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
108
|
+
detach = () => {
|
|
109
|
+
signal.removeEventListener("abort", onAbort);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
this.pending.set(id, {
|
|
113
|
+
resolve: (value) => {
|
|
114
|
+
detach();
|
|
115
|
+
resolve(value);
|
|
116
|
+
},
|
|
117
|
+
reject: (error) => {
|
|
118
|
+
detach();
|
|
119
|
+
reject(error);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
this.write(message);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.pending.delete(id);
|
|
126
|
+
detach();
|
|
127
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
notify(method, params) {
|
|
132
|
+
this.write(params === void 0 ? {
|
|
133
|
+
jsonrpc: "2.0",
|
|
134
|
+
method
|
|
135
|
+
} : {
|
|
136
|
+
jsonrpc: "2.0",
|
|
137
|
+
method,
|
|
138
|
+
params
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Wait for prior frame write callbacks. The empty barrier emits no bytes.
|
|
143
|
+
* @returns a promise that settles with the output write callback.
|
|
144
|
+
*/
|
|
145
|
+
flush() {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
this.output.write("", (error) => {
|
|
148
|
+
if (error) reject(error);
|
|
149
|
+
else resolve();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
onData = (chunk) => {
|
|
154
|
+
this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
155
|
+
this.drainLines();
|
|
156
|
+
};
|
|
157
|
+
drainLines() {
|
|
158
|
+
for (;;) {
|
|
159
|
+
const newline = this.buffer.indexOf("\n");
|
|
160
|
+
if (newline < 0) break;
|
|
161
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
162
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
163
|
+
if (!line) continue;
|
|
164
|
+
this.handleLine(line);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
onInputError = (error) => {
|
|
168
|
+
this.failPending(error);
|
|
169
|
+
};
|
|
170
|
+
onInputEnd = () => {
|
|
171
|
+
this.buffer += this.decoder.end();
|
|
172
|
+
this.drainLines();
|
|
173
|
+
this.failPending(/* @__PURE__ */ new Error("JSON-RPC input closed"));
|
|
174
|
+
};
|
|
175
|
+
async handleLine(line) {
|
|
176
|
+
let message;
|
|
177
|
+
try {
|
|
178
|
+
message = JSON.parse(line);
|
|
179
|
+
} catch {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!message || typeof message !== "object") return;
|
|
183
|
+
const frame = message;
|
|
184
|
+
const id = frame.id;
|
|
185
|
+
const method = frame.method;
|
|
186
|
+
if ((typeof id === "string" || typeof id === "number") && typeof method === "string") {
|
|
187
|
+
await this.handleIncomingRequest(id, method, objectParams(frame.params));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
191
|
+
this.handleIncomingResponse(id, frame);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (typeof method === "string") this.notificationHandler?.(method, objectParams(frame.params));
|
|
195
|
+
}
|
|
196
|
+
async handleIncomingRequest(id, method, params) {
|
|
197
|
+
const handler = this.requestHandler;
|
|
198
|
+
if (!handler) {
|
|
199
|
+
this.writeError(id, -32601, `method not found: ${method}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const result = await handler(method, params);
|
|
204
|
+
this.write({
|
|
205
|
+
jsonrpc: "2.0",
|
|
206
|
+
id,
|
|
207
|
+
result
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.writeError(id, -32603, error instanceof Error ? error.message : String(error));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
handleIncomingResponse(id, frame) {
|
|
214
|
+
const pending = this.pending.get(id);
|
|
215
|
+
if (!pending) return;
|
|
216
|
+
this.pending.delete(id);
|
|
217
|
+
if (frame.error && typeof frame.error === "object") {
|
|
218
|
+
const error = frame.error;
|
|
219
|
+
pending.reject(new JsonRpcResponseError(typeof error.code === "number" ? error.code : void 0, typeof error.message === "string" ? error.message : "JSON-RPC error", error.data));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
pending.resolve(frame.result);
|
|
223
|
+
}
|
|
224
|
+
writeError(id, code, message) {
|
|
225
|
+
this.write({
|
|
226
|
+
jsonrpc: "2.0",
|
|
227
|
+
id,
|
|
228
|
+
error: {
|
|
229
|
+
code,
|
|
230
|
+
message
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
write(message) {
|
|
235
|
+
this.output.write(`${JSON.stringify(message)}\n`);
|
|
236
|
+
}
|
|
237
|
+
failPending(error) {
|
|
238
|
+
const pending = [...this.pending.values()];
|
|
239
|
+
this.pending.clear();
|
|
240
|
+
for (const waiter of pending) waiter.reject(error);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
/** Normalize JSON-RPC `params` to a plain object (arrays and scalars collapse to `{}`). */
|
|
244
|
+
function objectParams(params) {
|
|
245
|
+
return params && typeof params === "object" && !Array.isArray(params) ? params : {};
|
|
246
|
+
}
|
|
247
|
+
/** Normalize an abort reason into the rejection Error (a non-Error reason is stringified). */
|
|
248
|
+
function abortError(reason) {
|
|
249
|
+
return reason instanceof Error ? reason : /* @__PURE__ */ new Error(`JSON-RPC request aborted: ${String(reason)}`);
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
export { JsonRpcLineTransport, JsonRpcResponseError };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-sdk-protocol`.
|
|
4
|
+
* @module @deepseek-ai/dsh-sdk-protocol/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-sdk-protocol";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "sdk-protocol-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: a pure wire library (transport class + type
|
|
13
|
+
* declarations) with no event stream or mutable data relation of its own;
|
|
14
|
+
* both wire ends own their protocol behavior.
|
|
15
|
+
*/
|
|
16
|
+
const install = () => {};
|
|
17
|
+
/**
|
|
18
|
+
* Register this package's invariant companion.
|
|
19
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
20
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
21
|
+
*/
|
|
22
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
23
|
+
//#endregion
|
|
24
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire protocol for the DeepSeek Harness SDK runtime: the
|
|
3
|
+
* newline-delimited JSON-RPC stdio transport plus the named request, result,
|
|
4
|
+
* and notification types both wire ends speak. The runtime server plugin
|
|
5
|
+
* (`@deepseek-ai/dsh-jsonrpc`) serves this protocol; SDK clients
|
|
6
|
+
* (`@deepseek-ai/dsh-sdk-client`, the Python SDK) drive it.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-sdk-protocol
|
|
9
|
+
*/
|
|
10
|
+
export { JsonRpcLineTransport, JsonRpcResponseError } from './transport.ts';
|
|
11
|
+
export type { JsonRpcTransportPeer } from './transport.ts';
|
|
12
|
+
export type { HarnessSdkNotificationMap, HarnessSdkRequestMap, InitializeParams, InitializeResult, SdkRunStatus, SessionEventNotification, SessionStatusNotification, SessionPromptParams, SessionPromptResult, SubagentFinishedNotification, SubagentStartedNotification, } from './types.ts';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-sdk-protocol`.
|
|
3
|
+
* @module @deepseek-ai/dsh-sdk-protocol/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "sdk-protocol-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,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Newline-delimited JSON-RPC 2.0 over byte streams. Frames with `id` and
|
|
3
|
+
* `method` are requests, `id` alone is a response, and `method` alone is a
|
|
4
|
+
* notification. Malformed lines are ignored; handler failures become error frames.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-sdk-protocol/transport
|
|
7
|
+
*/
|
|
8
|
+
import type { Readable, Writable } from 'node:stream';
|
|
9
|
+
type RequestHandler = (method: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
10
|
+
type NotificationHandler = (method: string, params: Record<string, unknown>) => void;
|
|
11
|
+
/** A JSON-RPC error response, preserving the wire `code` and optional `data`. */
|
|
12
|
+
export declare class JsonRpcResponseError extends Error {
|
|
13
|
+
readonly code: number | undefined;
|
|
14
|
+
readonly data?: unknown | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* @param code - the wire error code, or `undefined` when the peer sent none.
|
|
17
|
+
* @param message - the wire error message.
|
|
18
|
+
* @param data - the optional structured error payload, verbatim.
|
|
19
|
+
*/
|
|
20
|
+
constructor(code: number | undefined, message: string, data?: unknown | undefined);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Outbound request and notification surface used by the runtime server and
|
|
24
|
+
* SDK clients.
|
|
25
|
+
*/
|
|
26
|
+
export interface JsonRpcTransportPeer {
|
|
27
|
+
/**
|
|
28
|
+
* Send a request and await its response.
|
|
29
|
+
* @param method - the JSON-RPC method name.
|
|
30
|
+
* @param params - the request parameters object.
|
|
31
|
+
* @returns the result; rejects with {@link JsonRpcResponseError} on an error
|
|
32
|
+
* response, and with a plain `Error` on a write failure or closure.
|
|
33
|
+
*/
|
|
34
|
+
request(method: string, params: object): Promise<unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* Send a notification; omitted params produce no `params` member.
|
|
37
|
+
* @param method - the JSON-RPC method name.
|
|
38
|
+
* @param params - the optional notification parameters object.
|
|
39
|
+
*/
|
|
40
|
+
notify(method: string, params?: object): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Line-delimited endpoint over caller-owned streams. {@link start} attaches
|
|
44
|
+
* listeners; {@link close} detaches them and rejects pending requests without
|
|
45
|
+
* destroying the streams. Missing request handlers return `-32601`; handler
|
|
46
|
+
* failures return `-32603`. Notifications without a handler are dropped.
|
|
47
|
+
*/
|
|
48
|
+
export declare class JsonRpcLineTransport implements JsonRpcTransportPeer {
|
|
49
|
+
private readonly input;
|
|
50
|
+
private readonly output;
|
|
51
|
+
private buffer;
|
|
52
|
+
private readonly decoder;
|
|
53
|
+
private started;
|
|
54
|
+
private requestHandler;
|
|
55
|
+
private notificationHandler;
|
|
56
|
+
private readonly pending;
|
|
57
|
+
constructor(input: Readable, output: Writable);
|
|
58
|
+
/** Attach the input listeners and begin reading frames. Idempotent. */
|
|
59
|
+
start(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Detach listeners and reject pending requests. Safe before {@link start}.
|
|
62
|
+
*/
|
|
63
|
+
close(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Install the request handler, replacing any prior handler.
|
|
66
|
+
* @param handler - resolves to the response `result`; a rejection becomes a
|
|
67
|
+
* `-32603` error response carrying the message.
|
|
68
|
+
*/
|
|
69
|
+
onRequest(handler: RequestHandler): void;
|
|
70
|
+
/**
|
|
71
|
+
* Install the notification handler, replacing any prior handler.
|
|
72
|
+
* @param handler - invoked per notification with the method and normalized
|
|
73
|
+
* params object.
|
|
74
|
+
*/
|
|
75
|
+
onNotification(handler: NotificationHandler): void;
|
|
76
|
+
/**
|
|
77
|
+
* Send a request and await its response.
|
|
78
|
+
* @param method - the JSON-RPC method name.
|
|
79
|
+
* @param params - the request parameters object.
|
|
80
|
+
* @param signal - optional abandonment signal: aborting removes the pending
|
|
81
|
+
* entry (no state is retained for a response that may never come) and
|
|
82
|
+
* rejects with the signal's reason.
|
|
83
|
+
* @returns the result; rejects per {@link JsonRpcTransportPeer.request}.
|
|
84
|
+
*/
|
|
85
|
+
request(method: string, params: object, signal?: AbortSignal): Promise<unknown>;
|
|
86
|
+
notify(method: string, params?: object): void;
|
|
87
|
+
/**
|
|
88
|
+
* Wait for prior frame write callbacks. The empty barrier emits no bytes.
|
|
89
|
+
* @returns a promise that settles with the output write callback.
|
|
90
|
+
*/
|
|
91
|
+
flush(): Promise<void>;
|
|
92
|
+
private readonly onData;
|
|
93
|
+
private drainLines;
|
|
94
|
+
private readonly onInputError;
|
|
95
|
+
private readonly onInputEnd;
|
|
96
|
+
private handleLine;
|
|
97
|
+
private handleIncomingRequest;
|
|
98
|
+
private handleIncomingResponse;
|
|
99
|
+
private writeError;
|
|
100
|
+
private write;
|
|
101
|
+
private failPending;
|
|
102
|
+
}
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named wire types for the DeepSeek Harness SDK runtime protocol: the three
|
|
3
|
+
* request/result pairs and the four server-to-client notification payloads
|
|
4
|
+
* exchanged over the newline-delimited JSON-RPC stdio transport. The server
|
|
5
|
+
* plugin (`@deepseek-ai/dsh-jsonrpc`) and SDK clients share these shapes;
|
|
6
|
+
* `serverInfo.name` stays the wire-stable `deepseek-harness-sdk-runtime`.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-sdk-protocol/types
|
|
9
|
+
*/
|
|
10
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
12
|
+
import type { SubagentStopReason } from '@deepseek-ai/dsh-subagent';
|
|
13
|
+
/** Parameters for the process-wide SDK handshake. */
|
|
14
|
+
export interface InitializeParams {
|
|
15
|
+
/** Working directory recorded on every SDK-created session's header. */
|
|
16
|
+
cwd: string;
|
|
17
|
+
/** Provider route every SDK-created agent runs on. */
|
|
18
|
+
provider: string;
|
|
19
|
+
/** Model name every SDK-created agent runs on (the server may mount a fallback adapter; see `HarnessSdkServer.initialize`). */
|
|
20
|
+
model: string;
|
|
21
|
+
/** Optional positive output-token cap inherited by SDK-created agents and their in-process descendants. */
|
|
22
|
+
maxTokens?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Wire-stable server identity returned by initialization. */
|
|
25
|
+
export interface InitializeResult {
|
|
26
|
+
/** Wire-stable server identity (`deepseek-harness-sdk-runtime`) and version. */
|
|
27
|
+
serverInfo: {
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** One user turn on one SDK session. */
|
|
33
|
+
export interface SessionPromptParams {
|
|
34
|
+
/** The SDK-side session id; an unknown id lazily creates the agent+session pair. */
|
|
35
|
+
sessionId: string;
|
|
36
|
+
/** The prompt content blocks, sent verbatim as the user message. */
|
|
37
|
+
contentBlocks: ContentBlock[];
|
|
38
|
+
}
|
|
39
|
+
/** Durable enqueue receipt for one prompt. */
|
|
40
|
+
export interface SessionPromptResult {
|
|
41
|
+
/** Identity of the queued user message. */
|
|
42
|
+
messageId: string;
|
|
43
|
+
}
|
|
44
|
+
/** Deployment-mapped SDK outcome: `ok` for an accepted result, `error` otherwise. */
|
|
45
|
+
export type SdkRunStatus = 'ok' | 'error';
|
|
46
|
+
/** `session.event` payload: one session-log event, streamed as it is recorded. */
|
|
47
|
+
export interface SessionEventNotification {
|
|
48
|
+
/** Session the event belongs to (every session in the runtime, not only SDK-created ones). */
|
|
49
|
+
sessionId: string;
|
|
50
|
+
/** The full session-log event envelope. */
|
|
51
|
+
event: SessionEvent;
|
|
52
|
+
}
|
|
53
|
+
/** Whole-agent lifecycle state for one session. */
|
|
54
|
+
export interface SessionStatusNotification {
|
|
55
|
+
/** Session whose live agent changed status. */
|
|
56
|
+
sessionId: string;
|
|
57
|
+
/** The whole-agent state after the transition. */
|
|
58
|
+
status: 'idle' | 'running';
|
|
59
|
+
}
|
|
60
|
+
/** `subagent.started` payload: an in-runtime child session was created. */
|
|
61
|
+
export interface SubagentStartedNotification {
|
|
62
|
+
/** The delegating session. */
|
|
63
|
+
parentSessionId: string;
|
|
64
|
+
/** The new child session. */
|
|
65
|
+
childSessionId: string;
|
|
66
|
+
}
|
|
67
|
+
/** `subagent.finished` payload: an in-process subagent run ended (remote runs are not reported). */
|
|
68
|
+
export interface SubagentFinishedNotification {
|
|
69
|
+
/** Subagent provider name that ran the child. */
|
|
70
|
+
provider: string;
|
|
71
|
+
/** The child agent's id (equals {@link childSessionId} for local runs). */
|
|
72
|
+
agentId: string;
|
|
73
|
+
/** The delegating session. */
|
|
74
|
+
parentSessionId: string;
|
|
75
|
+
/** The child session. */
|
|
76
|
+
childSessionId: string;
|
|
77
|
+
/** Deployment-mapped run outcome. */
|
|
78
|
+
status: SdkRunStatus;
|
|
79
|
+
/** The provider-reported stop reason. */
|
|
80
|
+
stopReason: SubagentStopReason;
|
|
81
|
+
/** The child's final assistant message, when it produced one. */
|
|
82
|
+
lastAssistantMessage?: ContentBlock[];
|
|
83
|
+
}
|
|
84
|
+
/** Server-to-client notifications by JSON-RPC method name. */
|
|
85
|
+
export interface HarnessSdkNotificationMap {
|
|
86
|
+
'session.event': SessionEventNotification;
|
|
87
|
+
'session.status': SessionStatusNotification;
|
|
88
|
+
'subagent.started': SubagentStartedNotification;
|
|
89
|
+
'subagent.finished': SubagentFinishedNotification;
|
|
90
|
+
}
|
|
91
|
+
/** Client-to-server request methods with their param and result shapes. */
|
|
92
|
+
export interface HarnessSdkRequestMap {
|
|
93
|
+
'initialize': {
|
|
94
|
+
params: InitializeParams;
|
|
95
|
+
result: InitializeResult;
|
|
96
|
+
};
|
|
97
|
+
'session/prompt': {
|
|
98
|
+
params: SessionPromptParams;
|
|
99
|
+
result: SessionPromptResult;
|
|
100
|
+
};
|
|
101
|
+
'shutdown': {
|
|
102
|
+
params: undefined;
|
|
103
|
+
result: Record<string, never>;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-sdk-protocol",
|
|
3
|
+
"description": "Shared wire protocol for the DeepSeek Harness SDK runtime: the newline-delimited JSON-RPC stdio transport and the named request, result, and notification types spoken between the runtime server and SDK clients",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
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/scaffold/protocol"
|
|
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
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib/index.js",
|
|
29
|
+
"lib/invariant.js",
|
|
30
|
+
"lib/types/**/*.d.ts"
|
|
31
|
+
],
|
|
32
|
+
"license": "BSD-3-Clause",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
35
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-subagent": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-subagent": "^0.0.1-rc.1",
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
46
|
+
}
|
|
47
|
+
}
|