dsh-remote-bridge 2.4.2 → 2.5.0
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/README.en.md +33 -8
- package/README.md +27 -8
- package/cordis.patch.yml +36 -1
- package/docs/api/asyncapi.yaml +1 -1
- package/docs/api/openapi.yaml +183 -2
- package/kotlin-sdk/src/main/kotlin/ai/deepseek/dsh/workspace/DshWorkspaceClient.kt +134 -0
- package/lib/index.d.ts +258 -5
- package/lib/index.js +573 -31
- package/package.json +3 -2
package/README.en.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img alt="Version" src="https://img.shields.io/badge/version-v2.
|
|
12
|
+
<img alt="Version" src="https://img.shields.io/badge/version-v2.5.0-087f8c">
|
|
13
13
|
<img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
|
|
14
14
|
<img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
|
|
15
15
|
<img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
|
|
@@ -27,6 +27,26 @@ The interface is bilingual and defaults to Chinese. Prebuilt packages run on Win
|
|
|
27
27
|
> with `dsh-api-gateway`, leaving the upstream release unable to start on 0.2.x — it surfaces as
|
|
28
28
|
> "adding an authorized root returns 405". Background, rationale, changes, and test results are in
|
|
29
29
|
> [DSH 0.2.x compatibility notes](./docs/project/DSH-0.2-COMPAT.md).
|
|
30
|
+
>
|
|
31
|
+
> **Adaptation to DSH 0.2.1-rc.2** (rechecked 2026-10-20; see `docs/project/DSH-0.2.1-ADAPTATION.md`):
|
|
32
|
+
> this plugin **deliberately imports no `@deepseek-ai/*` package** — every host service is consumed
|
|
33
|
+
> through a structural type injected by the running Host (see the header comment in
|
|
34
|
+
> `src/host/dsh-0.2-bridge.ts`). Checked one by one against the installed `0.2.1-rc.2` packages:
|
|
35
|
+
> all 10 `inject` service names in `cordis.patch.yml` **still exist with unchanged signatures**, and
|
|
36
|
+
> `engines.dsh` is recognized by the 0.2.1 kernel (`prepareProfileEntries` in
|
|
37
|
+
> `dsh-app-boot/lib/index.js`) — an unmet range only sets that entry to `disabled` and prints to
|
|
38
|
+
> stderr; **the host process does not exit**. So no code change is needed to run on `0.2.1-rc.2`.
|
|
39
|
+
>
|
|
40
|
+
> **Functional differences between the two versions (important)**: the `inject` list holds **only
|
|
41
|
+
> services that exist in both versions** — cordis treats `inject` as a **hard dependency**, and a
|
|
42
|
+
> single missing service leaves the whole plugin inactive (proven by CI on the 0.2.0-rc.2 baseline:
|
|
43
|
+
> writing a 0.2.1-only service into `inject` once made the plugin fail to activate on 0.2.0).
|
|
44
|
+
> The services that only 0.2.1 provides (**background jobs** `jobController`, the **permission
|
|
45
|
+
> catalog** `permissionPresets`, file upload `fileUploads`, attachment storage `attachments`) are
|
|
46
|
+
> therefore read at runtime through `ctx.reflect.get(name, false)`: **all of them work on
|
|
47
|
+
> 0.2.1-rc.2 and degrade automatically on 0.2.0-rc.2** (the jobs endpoints answer 404 there, the
|
|
48
|
+
> permission catalog falls back to its built-in one) — the plugin itself runs on both kernels.
|
|
49
|
+
> `GET /api/v1/debug/host-services` reports which optional capabilities the current machine has.
|
|
30
50
|
|
|
31
51
|
## What it looks like
|
|
32
52
|
|
|
@@ -63,7 +83,12 @@ So seeing `dsh-workspace` in a REST address or the state directory is **expected
|
|
|
63
83
|
### Requirements
|
|
64
84
|
|
|
65
85
|
- A working DeepSeek Harness WebUI installation
|
|
66
|
-
- DSH kernel
|
|
86
|
+
- DSH kernel **`>=0.2.0-rc.2 <0.3.0`** (compatible with `0.2.0-rc.2` and every later `0.2.x`, including
|
|
87
|
+
`0.2.1-rc.2`; supported by this branch's compatibility layer — see
|
|
88
|
+
[DSH 0.2.x compatibility notes](./docs/project/DSH-0.2-COMPAT.md)).
|
|
89
|
+
`package.json` declares the same range under `engines.dsh`; the kernel resolves it with semver and
|
|
90
|
+
only **disables** the plugin when unmet (the host process keeps running), with the user able to
|
|
91
|
+
grant an exact-version exemption as the kernel suggests.
|
|
67
92
|
- Node.js `^22.19.0` or `>=24.0.0`
|
|
68
93
|
- Port `3090`, or another available port
|
|
69
94
|
|
|
@@ -77,10 +102,10 @@ dsh plugin --profile web add dsh-remote-bridge
|
|
|
77
102
|
dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
|
|
78
103
|
|
|
79
104
|
# 2. tarball from the GitHub release
|
|
80
|
-
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
105
|
+
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.5.0/dsh-remote-bridge-v2.5.0.tgz
|
|
81
106
|
|
|
82
107
|
# 3. tarball from the Gitee release (faster from mainland China)
|
|
83
|
-
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
108
|
+
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.5.0/dsh-remote-bridge-v2.5.0.tgz
|
|
84
109
|
|
|
85
110
|
npx @deepseek-ai/dsh web
|
|
86
111
|
```
|
|
@@ -97,7 +122,7 @@ Restart DSH after installation, then open settings — The section is titled **
|
|
|
97
122
|
To install a downloaded release archive:
|
|
98
123
|
|
|
99
124
|
```sh
|
|
100
|
-
dsh plugin --profile web add ./dsh-remote-bridge-v2.
|
|
125
|
+
dsh plugin --profile web add ./dsh-remote-bridge-v2.5.0.tgz
|
|
101
126
|
```
|
|
102
127
|
|
|
103
128
|
## First connection
|
|
@@ -329,13 +354,13 @@ pnpm pack
|
|
|
329
354
|
|
|
330
355
|
`pnpm check` covers type-checking, documentation consistency, and tests. It currently passes **30 test files / 226 cases**, plus **4 browser-layer cases** (Playwright measuring real computed styles).
|
|
331
356
|
|
|
332
|
-
`npm pack` produces roughly a **0.
|
|
357
|
+
`npm pack` produces roughly a **0.55 MB** archive (v2.5.0 measured 572,951 bytes, SHA-256 `38f25c10…299c93`): 2.2.0 dropped the standalone page and its editor dependencies and turned source maps off, down from 1.72 MB; it has since grown slightly with projection passthrough and the permission BFF.
|
|
333
358
|
|
|
334
|
-
v2.
|
|
359
|
+
v2.5.0 was verified on DSH **0.2.1-rc.2** on real hardware: all 14/14 host services injected, the three jobs endpoints exercised, and the permission catalog returned the kernel's real three presets. The development baseline is upstream `dsh-v0.2.1-rc.2` (commit `687f282e8625c6b8d11ff58211a90524be2e4317`); see `engines.dsh` above for the supported range. CI covers Windows, Ubuntu, and macOS.
|
|
335
360
|
|
|
336
361
|
## Project
|
|
337
362
|
|
|
338
|
-
- Version: `v2.
|
|
363
|
+
- Version: `v2.5.0`
|
|
339
364
|
- Author: upstream [Hakunm](https://github.com/Hakunm); this fork maintained by [westanke](https://github.com/westanke)
|
|
340
365
|
- Repository: [GitHub](https://github.com/westanke/dsh-remote-bridge) · [Gitee](https://gitee.com/westanke/dsh-remote-bridge)
|
|
341
366
|
- npm: [`dsh-remote-bridge`](https://www.npmjs.com/package/dsh-remote-bridge) ([mainland-China mirror](https://registry.npmmirror.com/dsh-remote-bridge))
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img alt="Version" src="https://img.shields.io/badge/version-v2.
|
|
12
|
+
<img alt="Version" src="https://img.shields.io/badge/version-v2.5.0-087f8c">
|
|
13
13
|
<img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
|
|
14
14
|
<img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
|
|
15
15
|
<img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
|
|
@@ -25,6 +25,22 @@
|
|
|
25
25
|
> 0.2.x 用 `dsh-api-gateway` 替换了提供 `apiProxy` 服务的 `dsh-host-apiproxy`,导致上游版本在
|
|
26
26
|
> 0.2.x 上无法启动(表现为「添加根目录返回 405」)。兼容层的完整说明——基于什么、为什么、改了什么、
|
|
27
27
|
> 测了什么——见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md)。
|
|
28
|
+
>
|
|
29
|
+
> **对 DSH 0.2.1-rc.2 的适配结论**(2026-10-20 复核,依据 `docs/project/DSH-0.2.1-ADAPTATION.md`):
|
|
30
|
+
> 本插件**刻意不 import 任何 `@deepseek-ai/*` 包**,全部宿主服务经结构类型 + Host 注入消费
|
|
31
|
+
> (见 `src/host/dsh-0.2-bridge.ts` 头注释)。逐一到 `0.2.1-rc.2` 实装包核对:`cordis.patch.yml`
|
|
32
|
+
> 的 10 项 `inject` 服务名**全部仍存在且签名未变**,`engines.dsh` 在 0.2.1 被内核识别
|
|
33
|
+
> (`dsh-app-boot/lib/index.js` 的 `prepareProfileEntries`),不满足时只把该 entry 置
|
|
34
|
+
> `disabled` 并打 stderr,**宿主进程不退出**。故本插件无需改码即可运行于 `0.2.1-rc.2`。
|
|
35
|
+
>
|
|
36
|
+
> **两版的功能差异(重要)**:`inject` 里只放**两个版本都存在**的服务 —— cordis 的 inject 是
|
|
37
|
+
> **硬依赖**,任何一个服务缺失就整个插件不激活(CI 在 0.2.0-rc.2 基线上实锤过:
|
|
38
|
+
> 一度因为把 0.2.1 才有的服务写进 inject 而让插件在 0.2.0 上整体不激活)。
|
|
39
|
+
> 因此 0.2.1 才提供的服务(**后台任务** `jobController`、**权限档位目录**
|
|
40
|
+
> `permissionPresets`、文件上传 `fileUploads`、附件存储 `attachments`)改为运行时经
|
|
41
|
+
> `ctx.reflect.get(name, false)` 读取:**0.2.1-rc.2 上这些功能全部可用,0.2.0-rc.2 上
|
|
42
|
+
> 自动降级**(如后台任务端点返回 404、权限档位走内置目录),插件本体两种内核都能跑。
|
|
43
|
+
> 用 `GET /api/v1/debug/host-services` 可以查这台机器上哪些可选能力可用。
|
|
28
44
|
|
|
29
45
|
## 实际效果
|
|
30
46
|
|
|
@@ -62,7 +78,8 @@
|
|
|
62
78
|
### 环境要求
|
|
63
79
|
|
|
64
80
|
- 已安装并能正常启动的 DeepSeek Harness WebUI
|
|
65
|
-
- DSH 内核 `0.
|
|
81
|
+
- DSH 内核 **`>=0.2.0-rc.2 <0.3.0`**(兼容 `0.2.0-rc.2` 及其后全部 `0.2.x`,含 `0.2.1-rc.2`;由本分支的兼容层支持,见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md))。
|
|
82
|
+
`package.json` 的 `engines.dsh` 已声明同一范围 —— 内核按 semver 裁决,不满足时该插件被**禁用**(宿主进程不退出),用户可按内核提示授予精确版本豁免。
|
|
66
83
|
- Node.js `^22.19.0` 或 `>=24.0.0`
|
|
67
84
|
- 默认端口 `3090` 可用,或准备一个其他端口
|
|
68
85
|
|
|
@@ -76,10 +93,10 @@ dsh plugin --profile web add dsh-remote-bridge
|
|
|
76
93
|
dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
|
|
77
94
|
|
|
78
95
|
# ② GitHub Release 的 tarball
|
|
79
|
-
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
96
|
+
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.5.0/dsh-remote-bridge-v2.5.0.tgz
|
|
80
97
|
|
|
81
98
|
# ③ Gitee Release 的 tarball(国内访问更快)
|
|
82
|
-
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
99
|
+
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.5.0/dsh-remote-bridge-v2.5.0.tgz
|
|
83
100
|
|
|
84
101
|
npx @deepseek-ai/dsh web
|
|
85
102
|
```
|
|
@@ -95,7 +112,7 @@ npx @deepseek-ai/dsh web
|
|
|
95
112
|
从 GitHub Release 下载离线安装包时,可直接安装 tarball:
|
|
96
113
|
|
|
97
114
|
```sh
|
|
98
|
-
dsh plugin --profile web add ./dsh-remote-bridge-v2.
|
|
115
|
+
dsh plugin --profile web add ./dsh-remote-bridge-v2.5.0.tgz
|
|
99
116
|
```
|
|
100
117
|
|
|
101
118
|
## 第一次使用
|
|
@@ -323,13 +340,15 @@ pnpm pack
|
|
|
323
340
|
|
|
324
341
|
`pnpm check` 覆盖类型检查、文档一致性与测试;当前 **30 个测试文件 / 226 个用例**,外加 **4 个浏览器层用例**(Playwright 实测量出的计算样式),全部通过。
|
|
325
342
|
|
|
326
|
-
`npm pack` 的产物约 **0.
|
|
343
|
+
`npm pack` 的产物约 **0.55 MB**(v2.5.0 实测 572,951 字节,SHA-256 `38f25c10…299c93`):2.2.0 删除独立页及其编辑器依赖、并关闭 source map 之后从 1.72 MB 降下来,之后随投影透传与权限 BFF 小幅增长。
|
|
327
344
|
|
|
328
|
-
v2.
|
|
345
|
+
v2.5.0 在 DSH **0.2.1-rc.2** 上完成真机验证:14/14 宿主服务全部可注入、三个 jobs 端点实测通过、
|
|
346
|
+
权限档位目录返回内核真实三档。开发基线为上游 `dsh-v0.2.1-rc.2`(commit `687f282e8625c6b8d11ff58211a90524be2e4317`),
|
|
347
|
+
兼容范围见上文 `engines.dsh`。CI 覆盖 Windows、Ubuntu 和 macOS。
|
|
329
348
|
|
|
330
349
|
## 项目信息
|
|
331
350
|
|
|
332
|
-
- 当前版本:`v2.
|
|
351
|
+
- 当前版本:`v2.5.0`
|
|
333
352
|
- 作者:上游 [Hakunm](https://github.com/Hakunm),本 fork 维护 [westanke](https://github.com/westanke)
|
|
334
353
|
- 仓库:[GitHub](https://github.com/westanke/dsh-remote-bridge) · [Gitee](https://gitee.com/westanke/dsh-remote-bridge)
|
|
335
354
|
- npm:[`dsh-remote-bridge`](https://www.npmjs.com/package/dsh-remote-bridge)([国内镜像](https://registry.npmmirror.com/dsh-remote-bridge))
|
package/cordis.patch.yml
CHANGED
|
@@ -6,7 +6,42 @@
|
|
|
6
6
|
# DSH 0.2.x host services (see src/host/dsh-0.2-bridge.ts). The pre-0.2
|
|
7
7
|
# `apiProxy` facade is rebuilt from these; `@deepseek-ai/dsh-host-apiproxy`
|
|
8
8
|
# stopped shipping at 0.1.1-rc.2 and nothing provides `apiProxy` after it.
|
|
9
|
-
|
|
9
|
+
#
|
|
10
|
+
# ⚠️ 这份列表**必须**与 `src/index.ts` 导出的 `inject` 完全一致。
|
|
11
|
+
# 原因(2026-10-20 实测,代价是三次重启):patch 里的 `inject` **覆盖**
|
|
12
|
+
# 插件自己导出的 `inject`,不是取并集。于是插件声明了、这里漏了的服务
|
|
13
|
+
# 读起来会报 `cannot get property "x" without inject` ——
|
|
14
|
+
# 与「内核压根没有这个服务」的报错**一模一样**,肉眼分不出来。
|
|
15
|
+
#
|
|
16
|
+
# 这一条曾长期掩盖真相:`permissionPresets` 一直以为「web profile 注不进、
|
|
17
|
+
# 只能走内置兜底目录」,实际是这里没声明它。`jobController` 加进来之后
|
|
18
|
+
# 同样注不进,才顺着 `_refresh()` 的 INACTIVE 逻辑反查到这条覆盖规则。
|
|
19
|
+
# ⚠️ 这份列表**只能放「两个支持版本上都存在的服务」**。
|
|
20
|
+
# cordis 的 inject 是**硬依赖**:任何一个服务缺失 → 整个 entry 永久 INACTIVE。
|
|
21
|
+
# 2026-10-20 CI 实锤:把 jobController / fileUploads / attachments /
|
|
22
|
+
# permissionPresets 加进来后,插件在 **0.2.0-rc.2**(smoke-profile-install
|
|
23
|
+
# 检出的上游基线)上整体不激活,报
|
|
24
|
+
# "waiting for services: ..., jobController, fileUploads" ——
|
|
25
|
+
# 那一版内核的 web profile 没挂这些服务。
|
|
26
|
+
#
|
|
27
|
+
# 可选服务(permissionPresets / jobController / fileUploads / attachments /
|
|
28
|
+
# workingDirectory)**不在这里声明**,由插件在运行时经
|
|
29
|
+
# `ctx.reflect.get(name, false)` 读取(cordis 给「不带 inject 要求」读服务
|
|
30
|
+
# 准备的正路,见 dsh-0.2-bridge.ts 的 optionalService 注释):
|
|
31
|
+
# 0.2.1-rc.2 上读到就走全功能,0.2.0-rc.2 上读不到就按能力位降级。
|
|
32
|
+
# 探针(GET /debug/host-services)继续报告这 5 个名字的真实可用性。
|
|
33
|
+
inject: [
|
|
34
|
+
sessionController,
|
|
35
|
+
workspaceController,
|
|
36
|
+
workspaceRegistry,
|
|
37
|
+
settingsController,
|
|
38
|
+
credentialsController,
|
|
39
|
+
agentPresets,
|
|
40
|
+
llm,
|
|
41
|
+
webServer,
|
|
42
|
+
agents,
|
|
43
|
+
commands,
|
|
44
|
+
]
|
|
10
45
|
config:
|
|
11
46
|
# 状态目录**不改名**:已装的机器上有既有数据库与回收站,换目录等于让用户
|
|
12
47
|
# 的授权根配置和回收站全部消失。改名只动「装的时候叫什么、bundle 叫什么」。
|
package/docs/api/asyncapi.yaml
CHANGED
package/docs/api/openapi.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: DeepSeek Harness Mobile API
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
description: Versioned file and chat BFF for mobile and third-party DSH clients. HTTP and HTTPS are both supported.
|
|
6
6
|
servers:
|
|
7
7
|
- url: http://127.0.0.1:3090/api/v1
|
|
@@ -25,6 +25,62 @@ paths:
|
|
|
25
25
|
ok: { type: boolean, const: true }
|
|
26
26
|
version: { type: string, const: v1 }
|
|
27
27
|
pluginVersion: { type: string, const: 2.4.2 }
|
|
28
|
+
/debug/host-services:
|
|
29
|
+
get:
|
|
30
|
+
operationId: hostServices
|
|
31
|
+
summary: Host service injection probe
|
|
32
|
+
description: |
|
|
33
|
+
Reports which DSH host services the plugin actually resolved on the running
|
|
34
|
+
kernel. This plugin deliberately imports no `@deepseek-ai/*` package and
|
|
35
|
+
consumes every host service through a structural type injected by the Host,
|
|
36
|
+
so "is the service there at all" is otherwise unanswerable without guessing.
|
|
37
|
+
|
|
38
|
+
`injected` covers the services named in the plugin's `inject` declaration;
|
|
39
|
+
`observed` covers services confirmed present in the web profile but not yet
|
|
40
|
+
consumed (currently only `workingDirectory`, added by DSH 0.2.1).
|
|
41
|
+
|
|
42
|
+
Requires a device token or loopback admin, like every other authenticated
|
|
43
|
+
endpoint: it exposes host internals.
|
|
44
|
+
responses:
|
|
45
|
+
'200':
|
|
46
|
+
description: Injection probe snapshot taken while the plugin applied.
|
|
47
|
+
content:
|
|
48
|
+
application/json:
|
|
49
|
+
schema:
|
|
50
|
+
type: object
|
|
51
|
+
required: [pluginVersion, injected, observed, summary]
|
|
52
|
+
properties:
|
|
53
|
+
pluginVersion: { type: string }
|
|
54
|
+
injected:
|
|
55
|
+
type: array
|
|
56
|
+
items:
|
|
57
|
+
type: object
|
|
58
|
+
required: [name, available, resolvable]
|
|
59
|
+
properties:
|
|
60
|
+
name: { type: string }
|
|
61
|
+
available: { type: boolean }
|
|
62
|
+
resolvable: { type: boolean }
|
|
63
|
+
error: { type: string }
|
|
64
|
+
observed:
|
|
65
|
+
type: array
|
|
66
|
+
items:
|
|
67
|
+
type: object
|
|
68
|
+
required: [name, available, resolvable]
|
|
69
|
+
properties:
|
|
70
|
+
name: { type: string }
|
|
71
|
+
available: { type: boolean }
|
|
72
|
+
resolvable: { type: boolean }
|
|
73
|
+
error: { type: string }
|
|
74
|
+
summary:
|
|
75
|
+
type: object
|
|
76
|
+
required: [injectedTotal, injectedAvailable, injectedMissing, observedAvailable]
|
|
77
|
+
properties:
|
|
78
|
+
injectedTotal: { type: integer }
|
|
79
|
+
injectedAvailable: { type: integer }
|
|
80
|
+
injectedMissing: { type: array, items: { type: string } }
|
|
81
|
+
observedAvailable: { type: array, items: { type: string } }
|
|
82
|
+
'404':
|
|
83
|
+
description: The probe is not compiled into this build.
|
|
28
84
|
/pairings/exchange:
|
|
29
85
|
post:
|
|
30
86
|
security: []
|
|
@@ -583,6 +639,94 @@ paths:
|
|
|
583
639
|
responses:
|
|
584
640
|
'200': { description: Session archived. }
|
|
585
641
|
'404': { $ref: '#/components/responses/Error' }
|
|
642
|
+
/chat/sessions/{sessionId}/jobs:
|
|
643
|
+
parameters:
|
|
644
|
+
- name: sessionId
|
|
645
|
+
in: path
|
|
646
|
+
required: true
|
|
647
|
+
schema: { type: string }
|
|
648
|
+
get:
|
|
649
|
+
operationId: listSessionJobs
|
|
650
|
+
summary: Background jobs a session can see
|
|
651
|
+
description: |
|
|
652
|
+
The roster this session can observe: its own jobs plus every unowned job.
|
|
653
|
+
The kernel's owner fence is the only access rule, so `sessionId` is
|
|
654
|
+
required and must not be guessed by the client.
|
|
655
|
+
responses:
|
|
656
|
+
'200':
|
|
657
|
+
description: The complete roster (whole-set, not a page).
|
|
658
|
+
content:
|
|
659
|
+
application/json:
|
|
660
|
+
schema:
|
|
661
|
+
type: object
|
|
662
|
+
required: [jobs]
|
|
663
|
+
properties:
|
|
664
|
+
jobs:
|
|
665
|
+
type: array
|
|
666
|
+
items: { $ref: '#/components/schemas/JobView' }
|
|
667
|
+
'404': { $ref: '#/components/responses/Error' }
|
|
668
|
+
/chat/sessions/{sessionId}/jobs/{jobId}/output:
|
|
669
|
+
parameters:
|
|
670
|
+
- name: sessionId
|
|
671
|
+
in: path
|
|
672
|
+
required: true
|
|
673
|
+
schema: { type: string }
|
|
674
|
+
- name: jobId
|
|
675
|
+
in: path
|
|
676
|
+
required: true
|
|
677
|
+
schema: { type: string }
|
|
678
|
+
- name: from
|
|
679
|
+
in: query
|
|
680
|
+
required: false
|
|
681
|
+
description: Absolute byte offset to resume from (a prior response's `next`). Omitted starts at the oldest retained byte.
|
|
682
|
+
schema: { type: integer }
|
|
683
|
+
get:
|
|
684
|
+
operationId: readJobOutput
|
|
685
|
+
summary: Retained output of one background job
|
|
686
|
+
responses:
|
|
687
|
+
'200':
|
|
688
|
+
description: Flattened output plus the offset to resume from.
|
|
689
|
+
content:
|
|
690
|
+
application/json:
|
|
691
|
+
schema:
|
|
692
|
+
type: object
|
|
693
|
+
required: [jobId, text, next, lossy]
|
|
694
|
+
properties:
|
|
695
|
+
jobId: { type: string }
|
|
696
|
+
text: { type: string }
|
|
697
|
+
next: { type: integer }
|
|
698
|
+
lossy: { type: boolean }
|
|
699
|
+
status: { $ref: '#/components/schemas/JobStatus' }
|
|
700
|
+
'404': { $ref: '#/components/responses/Error' }
|
|
701
|
+
/chat/sessions/{sessionId}/jobs/{jobId}/kill:
|
|
702
|
+
parameters:
|
|
703
|
+
- name: sessionId
|
|
704
|
+
in: path
|
|
705
|
+
required: true
|
|
706
|
+
schema: { type: string }
|
|
707
|
+
- name: jobId
|
|
708
|
+
in: path
|
|
709
|
+
required: true
|
|
710
|
+
schema: { type: string }
|
|
711
|
+
post:
|
|
712
|
+
operationId: killSessionJob
|
|
713
|
+
summary: Stop one background job on a human's behalf
|
|
714
|
+
description: |
|
|
715
|
+
Records `cancelled by the user` as the reason. The owning agent still
|
|
716
|
+
receives its completion notice, because a human kill is not one the
|
|
717
|
+
model requested.
|
|
718
|
+
responses:
|
|
719
|
+
'200':
|
|
720
|
+
description: The registry's admission.
|
|
721
|
+
content:
|
|
722
|
+
application/json:
|
|
723
|
+
schema:
|
|
724
|
+
type: object
|
|
725
|
+
required: [jobId, outcome]
|
|
726
|
+
properties:
|
|
727
|
+
jobId: { type: string }
|
|
728
|
+
outcome: { type: string, enum: [requested, already-finished] }
|
|
729
|
+
'404': { $ref: '#/components/responses/Error' }
|
|
586
730
|
/chat/sessions/{sessionId}/messages:
|
|
587
731
|
parameters:
|
|
588
732
|
- name: sessionId
|
|
@@ -1264,8 +1408,26 @@ components:
|
|
|
1264
1408
|
configurable: { type: boolean }
|
|
1265
1409
|
configured: { type: boolean }
|
|
1266
1410
|
removable: { type: boolean }
|
|
1411
|
+
defaultBaseURL: { type: string, description: '目录供应商的适配器默认 API 地址(未配置时使用)。' }
|
|
1412
|
+
baseURLEnv:
|
|
1413
|
+
type: array
|
|
1414
|
+
items: { type: string }
|
|
1415
|
+
description: '可能覆盖默认地址的环境变量,按优先级排序。'
|
|
1416
|
+
nativeAuth: { $ref: '#/components/schemas/NativeAuthHint' }
|
|
1417
|
+
settingsNs:
|
|
1418
|
+
type: string
|
|
1419
|
+
description: 目录条目的设置命名空间;客户端用它捕获 expectedRevision(打开卡片那一刻的 revision)。
|
|
1267
1420
|
credential: { $ref: '#/components/schemas/CredentialState' }
|
|
1268
1421
|
config: { $ref: '#/components/schemas/ProviderConfig' }
|
|
1422
|
+
NativeAuthHint:
|
|
1423
|
+
type: object
|
|
1424
|
+
required: [env, oauth]
|
|
1425
|
+
properties:
|
|
1426
|
+
env:
|
|
1427
|
+
type: array
|
|
1428
|
+
items: { type: string }
|
|
1429
|
+
description: '适配器原生认证依赖的环境变量。'
|
|
1430
|
+
oauth: { type: boolean, description: '是否支持 OAuth 登录。' }
|
|
1269
1431
|
ProviderSettings:
|
|
1270
1432
|
type: object
|
|
1271
1433
|
required: [writable, revisionByNamespace, customProvider, providers]
|
|
@@ -1289,7 +1451,8 @@ components:
|
|
|
1289
1451
|
thinking: { type: [string, 'null'] }
|
|
1290
1452
|
reasoningEffort: { type: [string, 'null'] }
|
|
1291
1453
|
models:
|
|
1292
|
-
type: array
|
|
1454
|
+
type: [array, 'null']
|
|
1455
|
+
description: '数组=整体替换模型目录(空数组=自定义空目录);`null`=恢复适配器默认目录(unset)。'
|
|
1293
1456
|
items: { $ref: '#/components/schemas/ProviderModel' }
|
|
1294
1457
|
expectedRevision: { type: integer, minimum: 0 }
|
|
1295
1458
|
CustomProviderCapability:
|
|
@@ -1329,3 +1492,21 @@ components:
|
|
|
1329
1492
|
message: { type: string }
|
|
1330
1493
|
requestId: { type: string, format: uuid }
|
|
1331
1494
|
details: { type: object, additionalProperties: true }
|
|
1495
|
+
JobStatus:
|
|
1496
|
+
type: string
|
|
1497
|
+
description: Lifecycle state. `running` and `stopping` are live; the rest are terminal.
|
|
1498
|
+
enum: [running, stopping, completed, killed, failed]
|
|
1499
|
+
JobView:
|
|
1500
|
+
type: object
|
|
1501
|
+
description: Read-only projection of one background job.
|
|
1502
|
+
required: [id, kind, label, status, startedAt]
|
|
1503
|
+
properties:
|
|
1504
|
+
id: { type: string, description: 'Registry-issued id, e.g. `bash-<short-uuid>`.' }
|
|
1505
|
+
kind: { type: string, description: 'Producer kind; `bash` is the only host-registered one today.' }
|
|
1506
|
+
label: { type: string, description: Producer-supplied one-line label. }
|
|
1507
|
+
owner: { type: string, description: Owning session; absent for an unowned job. }
|
|
1508
|
+
status: { $ref: '#/components/schemas/JobStatus' }
|
|
1509
|
+
progress: { type: string, description: 'Live progress line (`3/10`); cleared at settlement.' }
|
|
1510
|
+
detail: { type: string, description: 'Terminal reason (`exit code: 3`); a recorded kill reason is merged in.' }
|
|
1511
|
+
startedAt: { type: integer, format: epoch-ms }
|
|
1512
|
+
finishedAt: { type: integer, format: epoch-ms, description: Absent while live. }
|
|
@@ -206,6 +206,12 @@ data class CredentialState(
|
|
|
206
206
|
val writable: Boolean,
|
|
207
207
|
)
|
|
208
208
|
|
|
209
|
+
@Serializable
|
|
210
|
+
data class NativeAuthHint(
|
|
211
|
+
val env: List<String> = emptyList(),
|
|
212
|
+
val oauth: Boolean = false,
|
|
213
|
+
)
|
|
214
|
+
|
|
209
215
|
@Serializable
|
|
210
216
|
data class ProviderView(
|
|
211
217
|
val id: String,
|
|
@@ -215,6 +221,11 @@ data class ProviderView(
|
|
|
215
221
|
val configurable: Boolean,
|
|
216
222
|
val configured: Boolean,
|
|
217
223
|
val removable: Boolean,
|
|
224
|
+
val defaultBaseURL: String? = null,
|
|
225
|
+
val baseURLEnv: List<String>? = null,
|
|
226
|
+
val nativeAuth: NativeAuthHint? = null,
|
|
227
|
+
/** 目录条目的设置命名空间;客户端用它捕获 expectedRevision(打开卡片那一刻的 revision)。 */
|
|
228
|
+
val settingsNs: String? = null,
|
|
218
229
|
val credential: CredentialState,
|
|
219
230
|
val config: ProviderConfig,
|
|
220
231
|
)
|
|
@@ -242,6 +253,7 @@ data class ProviderPatch(
|
|
|
242
253
|
val apiKey: String? = null,
|
|
243
254
|
val thinking: String? = null,
|
|
244
255
|
val reasoningEffort: String? = null,
|
|
256
|
+
/** 数组=整体替换(空数组=自定义空目录);恢复默认目录用 [DshWorkspaceClient.resetProviderModels](wire `models: null`)。 */
|
|
245
257
|
val models: List<ProviderModel>? = null,
|
|
246
258
|
val expectedRevision: Int? = null,
|
|
247
259
|
)
|
|
@@ -426,6 +438,27 @@ class DshWorkspaceClient(
|
|
|
426
438
|
return executeJson("settings/providers/$providerId", "PATCH", body, ProviderSettings.serializer())
|
|
427
439
|
}
|
|
428
440
|
|
|
441
|
+
/**
|
|
442
|
+
* 恢复供应商的适配器默认模型目录(wire `models: null`)。data class 无法把
|
|
443
|
+
* null 默认值与「字段缺席」区分开,所以这里手工拼 body。
|
|
444
|
+
*/
|
|
445
|
+
fun resetProviderModels(providerId: String, expectedRevision: Int? = null): ProviderSettings {
|
|
446
|
+
val body = buildString {
|
|
447
|
+
append("{\"models\":null")
|
|
448
|
+
if (expectedRevision != null) append(",\"expectedRevision\":").append(expectedRevision)
|
|
449
|
+
append('}')
|
|
450
|
+
}
|
|
451
|
+
return executeJson("settings/providers/$providerId", "PATCH", body, ProviderSettings.serializer())
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fun deleteProvider(providerId: String, expectedRevision: Int? = null) {
|
|
455
|
+
val relative = buildString {
|
|
456
|
+
append("settings/providers/").append(providerId)
|
|
457
|
+
if (expectedRevision != null) append("?expectedRevision=").append(expectedRevision)
|
|
458
|
+
}
|
|
459
|
+
executeUnit(relative, "DELETE", null)
|
|
460
|
+
}
|
|
461
|
+
|
|
429
462
|
fun createCustomProvider(provider: CustomProviderCreate): ProviderSettings {
|
|
430
463
|
val body = json.encodeToString(CustomProviderCreate.serializer(), provider)
|
|
431
464
|
return executeJson("settings/providers", "POST", body, ProviderSettings.serializer())
|
|
@@ -514,6 +547,51 @@ class DshWorkspaceClient(
|
|
|
514
547
|
executeUnit("chat/sessions/$sessionId/archive", "POST", null)
|
|
515
548
|
}
|
|
516
549
|
|
|
550
|
+
/**
|
|
551
|
+
* 这个会话可见的后台任务清单(它自己的 + 所有无主任务)。
|
|
552
|
+
*
|
|
553
|
+
* 服务端只读内核流的第一帧 —— 内核保证首帧就是全量 truth,
|
|
554
|
+
* 所以这里返回的是完整清单,不是一个分页。
|
|
555
|
+
*/
|
|
556
|
+
fun listSessionJobs(sessionId: String): List<JobView> = executeJson(
|
|
557
|
+
"chat/sessions/$sessionId/jobs",
|
|
558
|
+
"GET",
|
|
559
|
+
null,
|
|
560
|
+
JobListEnvelope.serializer(),
|
|
561
|
+
).jobs
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 读一个后台任务已保留的输出。
|
|
565
|
+
*
|
|
566
|
+
* [from] 是**绝对字节偏移**,传上一次返回的 [JobOutput.next] 即可增量续读;
|
|
567
|
+
* 传 null 从最旧的保留字节开始。
|
|
568
|
+
*
|
|
569
|
+
* [JobOutput.lossy] 为 true 时,请求偏移与返回内容之间有字节被留存策略吃掉 ——
|
|
570
|
+
* 这段输出不是完整的,调用方应当提示用户。
|
|
571
|
+
*/
|
|
572
|
+
fun readJobOutput(sessionId: String, jobId: String, from: Long? = null): JobOutput {
|
|
573
|
+
val query = if (from == null) "" else "?from=$from"
|
|
574
|
+
return executeJson(
|
|
575
|
+
"chat/sessions/$sessionId/jobs/$jobId/output$query",
|
|
576
|
+
"GET",
|
|
577
|
+
null,
|
|
578
|
+
JobOutput.serializer(),
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* 终止一个后台任务。
|
|
584
|
+
*
|
|
585
|
+
* 返回 `requested`(已受理)或 `already-finished`(已是终态、无需再杀)——
|
|
586
|
+
* 调用方应据此显示「已终止」或「它已经结束了」,后者不是前者的成功说法。
|
|
587
|
+
*/
|
|
588
|
+
fun killSessionJob(sessionId: String, jobId: String): JobKillResult = executeJson(
|
|
589
|
+
"chat/sessions/$sessionId/jobs/$jobId/kill",
|
|
590
|
+
"POST",
|
|
591
|
+
null,
|
|
592
|
+
JobKillResult.serializer(),
|
|
593
|
+
)
|
|
594
|
+
|
|
517
595
|
fun listPendingApprovals(sessionId: String): List<PendingApproval> = executeJson(
|
|
518
596
|
"chat/sessions/$sessionId/approvals",
|
|
519
597
|
"GET",
|
|
@@ -726,3 +804,59 @@ class EventSubscription internal constructor(
|
|
|
726
804
|
@Serializable private data class SendMessageRequest(val text: String, val mode: String, val clientRequestId: String)
|
|
727
805
|
@Serializable private data class ErrorEnvelope(val error: ApiErrorBody)
|
|
728
806
|
@Serializable private data class ApiErrorBody(val code: String, val message: String, val requestId: String)
|
|
807
|
+
|
|
808
|
+
/** 后台任务的生命周期状态。`running` / `stopping` 存活中,其余为终态。 */
|
|
809
|
+
@Serializable
|
|
810
|
+
enum class JobStatus {
|
|
811
|
+
@SerialName("running") RUNNING,
|
|
812
|
+
@SerialName("stopping") STOPPING,
|
|
813
|
+
@SerialName("completed") COMPLETED,
|
|
814
|
+
@SerialName("killed") KILLED,
|
|
815
|
+
@SerialName("failed") FAILED,
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* 一个后台任务的只读投影。
|
|
820
|
+
*
|
|
821
|
+
* 服务端逐个字段校验过类型,所以这里除 [status] / [startedAt] / [id] / [kind] / [label]
|
|
822
|
+
* 之外的字段都可空 —— 内核没给就是没有,客户端不该看到一个 `owner: undefined`。
|
|
823
|
+
*/
|
|
824
|
+
@Serializable
|
|
825
|
+
data class JobView(
|
|
826
|
+
val id: String,
|
|
827
|
+
val kind: String,
|
|
828
|
+
val label: String,
|
|
829
|
+
val owner: String? = null,
|
|
830
|
+
val status: JobStatus,
|
|
831
|
+
/** 生产者的实时进度行(`3/10`);终态时被清空。 */
|
|
832
|
+
val progress: String? = null,
|
|
833
|
+
/** 终态原因(`exit code: 3`);记录过的人工取消原因会并进来。 */
|
|
834
|
+
val detail: String? = null,
|
|
835
|
+
/** 注册时刻(epoch ms)。 */
|
|
836
|
+
val startedAt: Long,
|
|
837
|
+
/** 落定时刻(epoch ms);存活中为 null。 */
|
|
838
|
+
val finishedAt: Long? = null,
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
@Serializable private data class JobListEnvelope(val jobs: List<JobView>)
|
|
842
|
+
|
|
843
|
+
/** 一个后台任务已保留的输出。 */
|
|
844
|
+
@Serializable
|
|
845
|
+
data class JobOutput(
|
|
846
|
+
val jobId: String,
|
|
847
|
+
val text: String,
|
|
848
|
+
/** 绝对字节偏移;下次增量续读传它。 */
|
|
849
|
+
val next: Long,
|
|
850
|
+
/** true = 请求偏移与返回内容之间有字节被留存策略吃掉,这段输出不完整。 */
|
|
851
|
+
val lossy: Boolean,
|
|
852
|
+
/** 读到终态帧时带上;流被提前切断时为 null。 */
|
|
853
|
+
val status: JobStatus? = null,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
/** 终止结果。 */
|
|
857
|
+
@Serializable
|
|
858
|
+
data class JobKillResult(
|
|
859
|
+
val jobId: String,
|
|
860
|
+
/** `requested` 已受理;`already-finished` 已是终态、无需再杀。 */
|
|
861
|
+
val outcome: String,
|
|
862
|
+
)
|