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/lib/index.d.ts
CHANGED
|
@@ -78,7 +78,9 @@ declare class WorkspaceDatabase {
|
|
|
78
78
|
readonly dataDir: string;
|
|
79
79
|
readonly trashDir: string;
|
|
80
80
|
private readonly db;
|
|
81
|
+
private closed;
|
|
81
82
|
constructor(dataDir: string);
|
|
83
|
+
/** 关闭数据库。**幂等** —— 重复调用不抛错。 */
|
|
82
84
|
close(): void;
|
|
83
85
|
/**
|
|
84
86
|
* 导出 `roots` 的建表语句(只读,诊断与迁移测试用)。
|
|
@@ -218,6 +220,12 @@ interface ConfigurableProvider {
|
|
|
218
220
|
settingsPath: string[];
|
|
219
221
|
active: boolean;
|
|
220
222
|
declared?: boolean;
|
|
223
|
+
defaultBaseURL?: string;
|
|
224
|
+
baseURLEnv?: string[];
|
|
225
|
+
nativeAuth?: {
|
|
226
|
+
env?: string[];
|
|
227
|
+
oauth?: boolean;
|
|
228
|
+
};
|
|
221
229
|
}
|
|
222
230
|
interface SettingsApiProxy extends DshApiProxy {
|
|
223
231
|
settings: {
|
|
@@ -326,6 +334,14 @@ interface ProviderView {
|
|
|
326
334
|
configurable: boolean;
|
|
327
335
|
configured: boolean;
|
|
328
336
|
removable: boolean;
|
|
337
|
+
defaultBaseURL?: string;
|
|
338
|
+
baseURLEnv?: string[];
|
|
339
|
+
nativeAuth?: {
|
|
340
|
+
env?: string[];
|
|
341
|
+
oauth?: boolean;
|
|
342
|
+
};
|
|
343
|
+
/** 目录条目的设置命名空间;客户端用它捕获 expectedRevision(打开卡片那一刻的 revision)。 */
|
|
344
|
+
settingsNs?: string;
|
|
329
345
|
credential: {
|
|
330
346
|
ref: string;
|
|
331
347
|
configured: boolean;
|
|
@@ -341,7 +357,8 @@ interface ProviderPatch {
|
|
|
341
357
|
apiKey?: string | null;
|
|
342
358
|
thinking?: string | null;
|
|
343
359
|
reasoningEffort?: string | null;
|
|
344
|
-
|
|
360
|
+
/** `null` 恢复适配器默认模型目录(unset),`[]` 写入自定义空目录。 */
|
|
361
|
+
models?: ProviderModelView[] | null;
|
|
345
362
|
expectedRevision?: number;
|
|
346
363
|
}
|
|
347
364
|
interface CustomProviderCreate {
|
|
@@ -671,6 +688,57 @@ interface AgentPresetsLike {
|
|
|
671
688
|
interface PermissionPresetsLike {
|
|
672
689
|
catalog(): Promise<unknown>;
|
|
673
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* 内核 `dsh-api-job-controller`(cordis 服务名 `jobController`)。
|
|
693
|
+
*
|
|
694
|
+
* 后台任务(目前只有 bash 一种 producer kind)的只读观察 + 人类取消。
|
|
695
|
+
*
|
|
696
|
+
* **`list` 与 `follow` 是流,不是一次性查询** —— 内核类型上就是
|
|
697
|
+
* `AsyncIterable<JobListFrame>` / `AsyncIterable<JobFollowFrame>`。但 list 的首帧
|
|
698
|
+
* 就是全量 truth(内核注释原文:「Whole-set replacement, so a reconnect's first
|
|
699
|
+
* frame is already the truth」),所以 REST 侧读**第一帧**即可得到完整清单,
|
|
700
|
+
* 不必把 HTTP 变成长连接(手机端耗电、断线重连都更难)。
|
|
701
|
+
*
|
|
702
|
+
* 只有 `kill` 是同步的,且它的返回只有两种:`requested` / `already-finished`
|
|
703
|
+
* —— 没有「不存在」这一档,未知 job 走 `job/not-found` 远程错误。
|
|
704
|
+
*
|
|
705
|
+
* 结构类型照 `dsh-api-job-controller/lib/types/{index,types}.d.ts` 与
|
|
706
|
+
* `dsh-jobs/lib/types/view.d.ts` 抄,只留本插件真用到的那部分。
|
|
707
|
+
*/
|
|
708
|
+
interface JobControllerLike {
|
|
709
|
+
/** 流:首帧全量,随后每次生命周期提交后一帧。 */
|
|
710
|
+
list(request: {
|
|
711
|
+
sessionId: string;
|
|
712
|
+
}, signal: AbortSignal): AsyncIterable<{
|
|
713
|
+
type?: string;
|
|
714
|
+
jobs?: readonly unknown[];
|
|
715
|
+
}>;
|
|
716
|
+
/** 流:opened 锚点 → 若干 output 批次 → 终态 status,之后正常关闭。 */
|
|
717
|
+
follow(request: {
|
|
718
|
+
sessionId?: string;
|
|
719
|
+
jobId: string;
|
|
720
|
+
from?: number;
|
|
721
|
+
}, signal: AbortSignal): AsyncIterable<{
|
|
722
|
+
type?: string;
|
|
723
|
+
job?: unknown;
|
|
724
|
+
from?: number;
|
|
725
|
+
chunks?: readonly unknown[];
|
|
726
|
+
next?: number;
|
|
727
|
+
lossy?: true;
|
|
728
|
+
}>;
|
|
729
|
+
/**
|
|
730
|
+
* 人类取消。返回 `requested`(已受理)或 `already-finished`(已终态,无需再杀)。
|
|
731
|
+
*
|
|
732
|
+
* 注意它**不是** Promise:内核类型上就是同步返回。调用方 `await` 它也合法
|
|
733
|
+
* (await 非 Promise 返回原值),所以这里按内核原样声明成同步。
|
|
734
|
+
*/
|
|
735
|
+
kill(request: {
|
|
736
|
+
sessionId: string;
|
|
737
|
+
jobId: string;
|
|
738
|
+
}): {
|
|
739
|
+
outcome: 'requested' | 'already-finished';
|
|
740
|
+
};
|
|
741
|
+
}
|
|
674
742
|
interface LlmLike {
|
|
675
743
|
/** Live routes, as `{ id, name }` rows. */
|
|
676
744
|
listProviders(): readonly {
|
|
@@ -687,6 +755,14 @@ interface LlmLike {
|
|
|
687
755
|
settingsNs: string;
|
|
688
756
|
settingsPath: readonly string[];
|
|
689
757
|
declared?: boolean;
|
|
758
|
+
defaultBaseURL?: string;
|
|
759
|
+
/** Environment variables that may override the default base URL, in priority order. */
|
|
760
|
+
baseURLEnv?: readonly string[];
|
|
761
|
+
/** Native authentication the adapter can fall back to when no key is stored. */
|
|
762
|
+
nativeAuth?: {
|
|
763
|
+
env?: readonly string[];
|
|
764
|
+
oauth?: boolean;
|
|
765
|
+
};
|
|
690
766
|
error?: string;
|
|
691
767
|
}[];
|
|
692
768
|
discoverModels(settingsNs: string, request: unknown, signal?: AbortSignal): Promise<readonly unknown[]>;
|
|
@@ -710,7 +786,31 @@ interface Dsh02HostServices {
|
|
|
710
786
|
settingsController: SettingsControllerLike;
|
|
711
787
|
credentialsController: CredentialsControllerLike;
|
|
712
788
|
agentPresets: AgentPresetsLike;
|
|
713
|
-
|
|
789
|
+
/**
|
|
790
|
+
* 权限档位目录(`ctx.permissionPresets`)——**可选**。
|
|
791
|
+
*
|
|
792
|
+
* 0.2.1 的 web profile 上有;0.2.0-rc.2 上没有。inject 是硬依赖,
|
|
793
|
+
* 所以它不能进 inject,只能声明成可选,由 `optionalService` 运行时读;
|
|
794
|
+
* 读不到时权限档位走 `FALLBACK_PERMISSION_CATALOG` + BFF。
|
|
795
|
+
*/
|
|
796
|
+
permissionPresets?: PermissionPresetsLike;
|
|
797
|
+
/**
|
|
798
|
+
* 后台任务控制器(`ctx.jobController`,`@deepseek-ai/dsh-api-job-controller`)。
|
|
799
|
+
*
|
|
800
|
+
* 为什么要它:远程场景下「任务跑飞了停不掉」是真焦虑 —— 人不在电脑前,
|
|
801
|
+
* 一个卡住的 bash 任务只能干等。手机端需要能看到清单并终止它。
|
|
802
|
+
*
|
|
803
|
+
* 与 `sessionController` 不同,这个是 `TypertRemoteService`(不是 `@Remote`
|
|
804
|
+
* 控制器集合),但结构类型消费方式完全一样:只声明用到的成员,缺了就明确报错。
|
|
805
|
+
*/
|
|
806
|
+
/**
|
|
807
|
+
* 后台任务控制器(`ctx.jobController`)——**可选**。
|
|
808
|
+
*
|
|
809
|
+
* 只有 DSH 0.2.1 的 web profile 挂了这个服务;0.2.0-rc.2 上没有,
|
|
810
|
+
* 而 inject 是硬依赖(缺一个整个插件不激活),所以它**不能**出现在 inject 里,
|
|
811
|
+
* 只能在这里声明成可选,由 `optionalService` 运行时读。
|
|
812
|
+
*/
|
|
813
|
+
jobController?: JobControllerLike;
|
|
714
814
|
llm: LlmLike;
|
|
715
815
|
/**
|
|
716
816
|
* 内核附件存储(`ctx.attachments`);会话历史里的图片要经它取字节。
|
|
@@ -721,14 +821,23 @@ interface Dsh02HostServices {
|
|
|
721
821
|
* `cannot get property "attachments" without inject` —— 请求链路本身没问题,
|
|
722
822
|
* 挂在服务端、而且只在真的去取图时才炸,所以很难在本地测出来。
|
|
723
823
|
*/
|
|
724
|
-
attachments: AttachmentStoreLike;
|
|
725
824
|
/**
|
|
726
|
-
*
|
|
825
|
+
* 内核附件存储(`ctx.attachments`)——**可选**。
|
|
826
|
+
*
|
|
827
|
+
* 0.2.0-rc.2 的 web profile 上没有这个服务(诊断日志实测 MISSING),
|
|
828
|
+
* 而 inject 是硬依赖,所以它只能声明成可选,由 `optionalService` 运行时读;
|
|
829
|
+
* 读不到时附件读取走内核 `sessionController.attachment()`。
|
|
830
|
+
*/
|
|
831
|
+
attachments?: AttachmentStoreLike;
|
|
832
|
+
/**
|
|
833
|
+
* Kernel file-upload service (`ctx.fileUploads`)——**可选**。
|
|
727
834
|
*
|
|
728
835
|
* 为什么用它而不是内核那条 `/api/session/uploadFileBinary` HTTP 路由:那条路由
|
|
729
836
|
* 只绑 loopback(手机根本到不了),而这个 cordis 服务就在同一个进程里,插件可直接调。
|
|
837
|
+
*
|
|
838
|
+
* 可选的原因同 [attachments]:0.2.0-rc.2 的 web profile 上没有它。
|
|
730
839
|
*/
|
|
731
|
-
fileUploads
|
|
840
|
+
fileUploads?: FileUploadsLike;
|
|
732
841
|
/**
|
|
733
842
|
* Cordis event bus of the host context. Used for the two facts the old
|
|
734
843
|
* `apiProxy` event streams carried that no controller returns: session
|
|
@@ -1533,6 +1642,136 @@ declare class WorkspaceEventBus {
|
|
|
1533
1642
|
subscribe(listener: (event: WorkspaceEvent) => void): () => void;
|
|
1534
1643
|
}
|
|
1535
1644
|
//#endregion
|
|
1645
|
+
//#region src/host/host-service-probe.d.ts
|
|
1646
|
+
interface ServiceProbe {
|
|
1647
|
+
name: string;
|
|
1648
|
+
/** 服务是否可解析。`false` = cordis 认为没有插件提供它。 */
|
|
1649
|
+
available: boolean;
|
|
1650
|
+
/** 解析失败时的错误信息(成功时为 undefined)。 */
|
|
1651
|
+
error?: string;
|
|
1652
|
+
/** 解析到的值是否像一个可用的服务对象(非 null/非 undefined)。 */
|
|
1653
|
+
resolvable: boolean;
|
|
1654
|
+
}
|
|
1655
|
+
interface HostServiceReport {
|
|
1656
|
+
/** 探针执行时的内核运行时版本,供比对「哪个版本注入面是这样」。 */
|
|
1657
|
+
pluginVersion: string;
|
|
1658
|
+
injected: ServiceProbe[];
|
|
1659
|
+
observed: ServiceProbe[];
|
|
1660
|
+
/** 注入面摘要:注入了几个、缺几个。缺的那几个就是「功能可能静默不工作」的清单。 */
|
|
1661
|
+
summary: {
|
|
1662
|
+
injectedTotal: number;
|
|
1663
|
+
injectedAvailable: number;
|
|
1664
|
+
injectedMissing: readonly string[];
|
|
1665
|
+
observedAvailable: readonly string[];
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
//#endregion
|
|
1669
|
+
//#region src/host/job-adapter.d.ts
|
|
1670
|
+
/**
|
|
1671
|
+
* 后台任务(background jobs)。
|
|
1672
|
+
*
|
|
1673
|
+
* ## 为什么需要它
|
|
1674
|
+
*
|
|
1675
|
+
* 远程场景下有个真焦虑:用户在手机上让 agent 跑个长任务(bash 构建、批量处理),
|
|
1676
|
+
* 然后切到别的会话或者放下手机 —— **此时没有任何地方能看到「它还在跑吗」,
|
|
1677
|
+
* 更没有地方能「让它停下」**。任务跑飞了只能干等,或者回到电脑前。
|
|
1678
|
+
*
|
|
1679
|
+
* 而内核一直有这套能力:`dsh-api-job-controller` 在 web profile 里挂着,
|
|
1680
|
+
* 提供 roster 流(`list`)、单任务输出观察流(`follow`)与人类取消(`kill`)。
|
|
1681
|
+
* 官方 WebUI 有 Jobs 面板(`client-ui-jobs` + `IconJobs`),本桥此前 26 个能力位里
|
|
1682
|
+
* **一个 job 相关的都没有**。
|
|
1683
|
+
*
|
|
1684
|
+
* ## 为什么 REST 只读第一帧,不做长连接
|
|
1685
|
+
*
|
|
1686
|
+
* 内核的 `list` 是 `AsyncIterable<JobListFrame>`,但内核注释写得很清楚:
|
|
1687
|
+
* 「Whole-set replacement, so a reconnect's first frame is already the truth」——
|
|
1688
|
+
* **首帧就是完整真相**。所以:
|
|
1689
|
+
*
|
|
1690
|
+
* - REST `GET /jobs` 读第一帧就够,返回完整清单;
|
|
1691
|
+
* - 不把 HTTP 变成长连接:手机端长连接耗电、断线重连复杂、而且清单本来就该「拉一次就是真的」;
|
|
1692
|
+
* - 实时推送留给后续(若要,走 WebSocket 事件帧,与 chat.* 同一条通道)。
|
|
1693
|
+
*
|
|
1694
|
+
* ## 为什么端点挂在会话维度下
|
|
1695
|
+
*
|
|
1696
|
+
* 内核的 `list(request)` 与 `kill(request)` **都要 `sessionId`** —— 返回的是
|
|
1697
|
+
* 「该会话自己的任务 + 所有无主任务」,owner fence 是唯一的访问规则。
|
|
1698
|
+
* 所以本模块的每个方法都要调用方给出 `sessionId`,**不能替客户端猜** ——
|
|
1699
|
+
* 猜错会让一个会话看到另一个会话的任务。`kill` 同理:请求里的 session 就是
|
|
1700
|
+
* 那次围栏读写的调用者。
|
|
1701
|
+
*
|
|
1702
|
+
* 所以 REST 也必须是会话维度的:`/chat/sessions/{sessionId}/jobs`。
|
|
1703
|
+
* 做成顶层 `/jobs` 就得让客户端额外猜一个 session,那是把内核的围栏语义
|
|
1704
|
+
* 搬到桥外面,早晚出「一个会话看到另一个会话的任务」这种事。
|
|
1705
|
+
*
|
|
1706
|
+
* 可见性由内核裁决,本模块不二次判断:内核抛 `job/not-found` 就转成 404。
|
|
1707
|
+
*/
|
|
1708
|
+
/** 一个后台任务的对外视图(内核 `JobView` 的结构投影)。 */
|
|
1709
|
+
interface JobView {
|
|
1710
|
+
id: string;
|
|
1711
|
+
kind: string;
|
|
1712
|
+
label: string;
|
|
1713
|
+
/** 属主会话;无主任务没有这个字段(任何调用方都可见)。 */
|
|
1714
|
+
owner?: string;
|
|
1715
|
+
status: 'running' | 'stopping' | 'completed' | 'killed' | 'failed';
|
|
1716
|
+
/** 生产者的实时进度行(`3/10`、当前阶段);终态时被清空。 */
|
|
1717
|
+
progress?: string;
|
|
1718
|
+
/** 终态原因(`exit code: 3`);记录过的 kill 原因会并进来。 */
|
|
1719
|
+
detail?: string;
|
|
1720
|
+
/** 注册时刻(epoch ms)。 */
|
|
1721
|
+
startedAt: number;
|
|
1722
|
+
/** 落定时刻(epoch ms);存活中无此字段。 */
|
|
1723
|
+
finishedAt?: number;
|
|
1724
|
+
}
|
|
1725
|
+
/** `GET /jobs` 的响应。 */
|
|
1726
|
+
interface JobListResult {
|
|
1727
|
+
jobs: JobView[];
|
|
1728
|
+
}
|
|
1729
|
+
/** `POST /jobs/{jobId}/kill` 的响应。 */
|
|
1730
|
+
interface JobKillResult {
|
|
1731
|
+
jobId: string;
|
|
1732
|
+
/** `requested` = 已受理;`already-finished` = 已是终态,无需再杀。 */
|
|
1733
|
+
outcome: 'requested' | 'already-finished';
|
|
1734
|
+
}
|
|
1735
|
+
declare class JobAdapter {
|
|
1736
|
+
private readonly host;
|
|
1737
|
+
constructor(host: {
|
|
1738
|
+
jobController: JobControllerLike;
|
|
1739
|
+
});
|
|
1740
|
+
/**
|
|
1741
|
+
* 列出某个会话可见的后台任务。
|
|
1742
|
+
*
|
|
1743
|
+
* 读流的**第一帧**(内核保证它是全量 truth)。若流首帧就不是 `rows`
|
|
1744
|
+
* (内核升级改了帧型),明确报错而不是返回空清单 ——
|
|
1745
|
+
* 空清单会被客户端理解成「没有任务」,那是**假的**。
|
|
1746
|
+
*/
|
|
1747
|
+
list(sessionId: string, signal: AbortSignal): Promise<JobListResult>;
|
|
1748
|
+
/**
|
|
1749
|
+
* 读一个任务已保留的输出(从指定字节偏移继续)。
|
|
1750
|
+
*
|
|
1751
|
+
* 内核的 `follow` 是流:`opened` 锚点 → 若干 `output` 批次 → 终态 `status`。
|
|
1752
|
+
* 这里**只读到第一个终态帧或流结束为止**,把批次摊平成一个文本块 ——
|
|
1753
|
+
* 手机端要看的是「这任务打印了什么」,不是逐帧的字节偏移记账。
|
|
1754
|
+
*
|
|
1755
|
+
* 但 `next` 偏移会一并返回:客户端想增量续读时,拿它当 `from` 再调一次即可,
|
|
1756
|
+
* 不必从头拉。`lossy` 也透出去,让客户端知道「有字节被留存策略吃掉了」,
|
|
1757
|
+
* 而不是把一段对不上的输出当成完整的。
|
|
1758
|
+
*/
|
|
1759
|
+
follow(sessionId: string, jobId: string, from: number | undefined, signal: AbortSignal): Promise<{
|
|
1760
|
+
jobId: string;
|
|
1761
|
+
text: string;
|
|
1762
|
+
next: number;
|
|
1763
|
+
lossy: boolean;
|
|
1764
|
+
status?: JobView['status'];
|
|
1765
|
+
}>;
|
|
1766
|
+
/**
|
|
1767
|
+
* 终止一个后台任务。
|
|
1768
|
+
*
|
|
1769
|
+
* 内核的 `kill` 是**同步**的,且只有 `requested` / `already-finished` 两种返回;
|
|
1770
|
+
* 「这个任务不存在或该会话看不到它」走 `job/not-found` 远程错误,在这里转成 404。
|
|
1771
|
+
*/
|
|
1772
|
+
kill(sessionId: string, jobId: string): JobKillResult;
|
|
1773
|
+
}
|
|
1774
|
+
//#endregion
|
|
1536
1775
|
//#region src/host/cloudflared.d.ts
|
|
1537
1776
|
/**
|
|
1538
1777
|
* Cloudflare 隧道(cloudflared)管理。
|
|
@@ -1725,6 +1964,20 @@ interface RouterOptions {
|
|
|
1725
1964
|
* 生产路径由 `index.ts` 传入与交接实际使用**同一个**目录,避免「界面显示 A、落盘在 B」。
|
|
1726
1965
|
*/
|
|
1727
1966
|
imageFallbackDir?: string;
|
|
1967
|
+
/**
|
|
1968
|
+
* 宿主服务注入面探针报告(见 `host-service-probe.ts`)。
|
|
1969
|
+
*
|
|
1970
|
+
* 可选:不注入则该端点返回 404,测试因此不必每条都补一个字段。
|
|
1971
|
+
* 生产路径由 `index.ts` 在 apply 期间采集后传入 —— 必须是**快照**,
|
|
1972
|
+
* 因为 cordis 的注入属性只在 apply 执行期间可读(见探针文件头注释)。
|
|
1973
|
+
*/
|
|
1974
|
+
hostServiceReport?: HostServiceReport;
|
|
1975
|
+
/**
|
|
1976
|
+
* 后台任务(`/chat/sessions/{sessionId}/jobs`)。
|
|
1977
|
+
*
|
|
1978
|
+
* 可选:不注入则这些端点返回 404,测试不必每条都补一个字段。
|
|
1979
|
+
*/
|
|
1980
|
+
jobs?: JobAdapter;
|
|
1728
1981
|
}
|
|
1729
1982
|
declare class ApiRouter {
|
|
1730
1983
|
private readonly options;
|