dsh-remote-bridge 2.6.0 → 2.7.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 +68 -12
- package/README.md +59 -11
- package/docs/api/asyncapi.yaml +1 -1
- package/docs/api/openapi.yaml +61 -4
- package/kotlin-sdk/src/main/kotlin/ai/deepseek/dsh/workspace/DshWorkspaceClient.kt +15 -1
- package/lib/client.cjs +758 -124
- package/lib/index.d.ts +196 -14
- package/lib/index.js +814 -46
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -26,6 +26,24 @@ interface DeviceView {
|
|
|
26
26
|
createdAt: number;
|
|
27
27
|
lastSeenAt?: number;
|
|
28
28
|
revokedAt?: number;
|
|
29
|
+
/**
|
|
30
|
+
* 设备自称/被推断出的平台与型号(`android` / `harmonyos` / …)。
|
|
31
|
+
*
|
|
32
|
+
* **可选**:老设备从未上报过就是 `undefined`,客户端必须能接受缺省。
|
|
33
|
+
* 这两个字段只用于界面显示与排查,**不参与任何授权判断** —— 它们是客户端
|
|
34
|
+
* 自称的,不是可信身份。
|
|
35
|
+
*/
|
|
36
|
+
platform?: string;
|
|
37
|
+
model?: string;
|
|
38
|
+
/**
|
|
39
|
+
* `name` 是怎么来的,决定后续能不能自动改名。
|
|
40
|
+
*
|
|
41
|
+
* - `default`:配对时的占位名(`'Android 设备'` 之类)。设备第一次带身份
|
|
42
|
+
* 连上来时**允许**被自动改成「平台 · 型号」。
|
|
43
|
+
* - `detected`:由探测到的身份自动生成。后续身份变了仍可再自动更新。
|
|
44
|
+
* - `declared`:用户手工改的。**永不再自动覆盖** —— 用户起的名比机器猜的准。
|
|
45
|
+
*/
|
|
46
|
+
nameSource?: 'default' | 'detected' | 'declared';
|
|
29
47
|
}
|
|
30
48
|
interface FileEntryView {
|
|
31
49
|
name: string;
|
|
@@ -179,6 +197,7 @@ declare class WorkspaceDatabase {
|
|
|
179
197
|
*/
|
|
180
198
|
exchangePairing(code: string, deviceName: string, options?: {
|
|
181
199
|
requestId?: string;
|
|
200
|
+
nameSource?: 'default' | 'declared';
|
|
182
201
|
}): {
|
|
183
202
|
token: string;
|
|
184
203
|
device: DeviceView;
|
|
@@ -186,6 +205,47 @@ declare class WorkspaceDatabase {
|
|
|
186
205
|
authenticate(token: string): AuthenticatedDevice | undefined;
|
|
187
206
|
getActiveDevice(id: string): AuthenticatedDevice | undefined;
|
|
188
207
|
private hydrateDevice;
|
|
208
|
+
/**
|
|
209
|
+
* 记录一次探测到的设备身份,必要时顺手把占位名改成「平台 · 型号」。
|
|
210
|
+
*
|
|
211
|
+
* 时序说明:配对时桥只知道「有个设备要连」,真正的平台与型号要等设备
|
|
212
|
+
* **第一次发请求**才拿得到。所以这个方法由认证路径调用(见 `auth.ts`
|
|
213
|
+
* 的 `principal`),而不是配对路径。
|
|
214
|
+
*
|
|
215
|
+
* ## 什么时候改名
|
|
216
|
+
*
|
|
217
|
+
* 只有 `name_source` 是 `default`(配对时的占位名)或 `detected`(上次自动
|
|
218
|
+
* 生成的)才改名。`declared`(用户手工改的)**永不动** —— 用户起的名比机器
|
|
219
|
+
* 猜的准,而且他能改一次就能改第二次,但机器每次连接都覆盖就再也留不住了。
|
|
220
|
+
*
|
|
221
|
+
* 老库的 `name_source` 是 NULL:那些行里的名字多半是硬编码的「Android 设备」,
|
|
222
|
+
* 但**也可能是用户手工改过的**(此前没有任何改名通道,所以实际上只有占位名;
|
|
223
|
+
* 不过万一有人直接改过库,我们不能替他决定)。保守起见把 NULL 当作
|
|
224
|
+
* `declared` 处理 —— 名字不动,只补 platform/model 两列。
|
|
225
|
+
*
|
|
226
|
+
* ## 为什么不做成「每次请求都写库」
|
|
227
|
+
*
|
|
228
|
+
* 调用方只在**探测结果与库里不同**时才调这里(见 `auth.ts`),所以正常情况
|
|
229
|
+
* 下一台设备一生只写一次。这不是性能考虑,而是避免把一次读请求变成写请求:
|
|
230
|
+
* 写请求会碰 WAL、会影响 `devicesRevisionCounter`,进而触发全体客户端重拉设备表。
|
|
231
|
+
*
|
|
232
|
+
* @returns 是否真的改了库(调用方据此决定要不要递增设备版本号)。
|
|
233
|
+
*/
|
|
234
|
+
recordDeviceIdentity(id: string, identity: {
|
|
235
|
+
platform?: string;
|
|
236
|
+
model?: string;
|
|
237
|
+
suggestedName?: string;
|
|
238
|
+
}): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* 用户手工改名。
|
|
241
|
+
*
|
|
242
|
+
* 一旦走这条路,`name_source` 就变成 `declared`,此后任何自动探测都**不再**
|
|
243
|
+
* 覆盖它。这是「用户意图优先于机器推断」的唯一实现点,所以单独一个方法而不是
|
|
244
|
+
* 塞进 `updateDevice`:权限变更与改名是两件不同的事,审计动作也不该混。
|
|
245
|
+
*/
|
|
246
|
+
renameDevice(id: string, name: string, options?: {
|
|
247
|
+
requestId?: string;
|
|
248
|
+
}): DeviceView;
|
|
189
249
|
listDevices(): DeviceView[];
|
|
190
250
|
isDeviceActive(id: string): boolean;
|
|
191
251
|
/**
|
|
@@ -333,6 +393,9 @@ declare class WorkspaceDatabase {
|
|
|
333
393
|
private dropRootsUniqueConstraint;
|
|
334
394
|
/** `roots` 表是否已有指定列。 */
|
|
335
395
|
private hasRootColumn;
|
|
396
|
+
/** `devices` 表是否已有指定列。 */
|
|
397
|
+
private hasDeviceColumn;
|
|
398
|
+
private hasColumn;
|
|
336
399
|
}
|
|
337
400
|
//#endregion
|
|
338
401
|
//#region src/host/settings-namespace.d.ts
|
|
@@ -553,9 +616,13 @@ declare class DshSettingsAdapter {
|
|
|
553
616
|
/**
|
|
554
617
|
* @param options.writeNamespaces 额外允许手机端写入的设置命名空间(精确 ns 或
|
|
555
618
|
* `前缀*`),与内置策略(`ui-*` / 以 `locale` 结尾)叠加;默认为空。
|
|
619
|
+
* @param options.allowPrivateProviderHosts 允许供应商 `baseURL` 指向的私网 / 回环
|
|
620
|
+
* host 白名单(默认空 = 一律拒绝)。要连本地 LLM 的部署方显式填,理由见
|
|
621
|
+
* `provider-url-policy.ts` 文件头。
|
|
556
622
|
*/
|
|
557
623
|
constructor(api: SettingsApiProxy, options?: {
|
|
558
624
|
writeNamespaces?: readonly string[];
|
|
625
|
+
allowPrivateProviderHosts?: readonly string[];
|
|
559
626
|
});
|
|
560
627
|
/**
|
|
561
628
|
* 列出全部设置命名空间(通用设置面的读侧)。
|
|
@@ -572,6 +639,8 @@ declare class DshSettingsAdapter {
|
|
|
572
639
|
*/
|
|
573
640
|
mutateNamespace(ns: string, ops: unknown, expectedRevision?: number): Promise<SettingsNamespaceEntry>;
|
|
574
641
|
private get writeNamespaces();
|
|
642
|
+
/** 交给地址策略的选项;集中一处,三个入口不会各配各的。 */
|
|
643
|
+
private get baseURLOptions();
|
|
575
644
|
/** 内核 settings 服务缺失时给 503(而不是把 `TypeError` 漏成 500)。 */
|
|
576
645
|
private settingsService;
|
|
577
646
|
listProviders(): Promise<{
|
|
@@ -587,14 +656,38 @@ declare class DshSettingsAdapter {
|
|
|
587
656
|
message: string;
|
|
588
657
|
}[];
|
|
589
658
|
}>;
|
|
590
|
-
|
|
591
|
-
|
|
659
|
+
/**
|
|
660
|
+
* 更新供应商配置。
|
|
661
|
+
*
|
|
662
|
+
* 返回 `baseURLHost`(仅当本次 patch 动了 `baseURL` 时存在)供路由层写审计:
|
|
663
|
+
* 攻击者可以用 discover 把凭据发到任意地址,事后必须能从审计里看出「打了哪」。
|
|
664
|
+
* 只记 host 不记完整 URL —— path/query 可能带凭据,审计表不该成为新的泄露点。
|
|
665
|
+
*/
|
|
666
|
+
updateProvider(providerId: string, patch: ProviderPatch): Promise<{
|
|
667
|
+
baseURLHost?: string;
|
|
668
|
+
}>;
|
|
669
|
+
createProvider(input: CustomProviderCreate): Promise<{
|
|
670
|
+
baseURLHost: string;
|
|
671
|
+
}>;
|
|
592
672
|
removeProvider(providerId: string, expectedRevision?: number): Promise<void>;
|
|
673
|
+
/**
|
|
674
|
+
* 发现供应商可用模型。
|
|
675
|
+
*
|
|
676
|
+
* `draft.baseURL` 是**临时覆盖**(不写配置),但内核会带着该供应商已配置的真实
|
|
677
|
+
* 凭据去请求它 —— 这正是 SEC-H11 的窃取路径:攻击者把自己的服务器填进来即可收到
|
|
678
|
+
* `Authorization: Bearer <真实 key>`。因此临时覆盖与持久化写入过同一道地址策略。
|
|
679
|
+
*
|
|
680
|
+
* 返回 `baseURLHost` 供路由层审计;不传 `baseURL` 时(用配置里的地址)同样返回,
|
|
681
|
+
* 这样「打了哪个地址」在任何情况下都能追溯。
|
|
682
|
+
*/
|
|
593
683
|
discover(providerId: string, draft: {
|
|
594
684
|
baseURL?: string;
|
|
595
685
|
api?: string;
|
|
596
686
|
apiKey?: string;
|
|
597
|
-
}): Promise<
|
|
687
|
+
}): Promise<{
|
|
688
|
+
models: ProviderModelView[];
|
|
689
|
+
baseURLHost?: string;
|
|
690
|
+
}>;
|
|
598
691
|
/**
|
|
599
692
|
* 取一个 provider 的上下文(目录条目 + 设置命名空间 + 当前 profile)。
|
|
600
693
|
*
|
|
@@ -622,6 +715,13 @@ declare class DshSettingsAdapter {
|
|
|
622
715
|
*/
|
|
623
716
|
private invalidateProviderContext;
|
|
624
717
|
private loadProviderContext;
|
|
718
|
+
/**
|
|
719
|
+
* 该供应商当前是否已经存了凭据。
|
|
720
|
+
*
|
|
721
|
+
* 单独一次 `credentials.describe` 只在「要换 baseURL host」时才调 —— 那是罕见路径,
|
|
722
|
+
* 而正常保存(改 models / 显示名 / 协议)不该为此多付一次内核往返。
|
|
723
|
+
*/
|
|
724
|
+
private providerHoldsCredential;
|
|
625
725
|
}
|
|
626
726
|
//#endregion
|
|
627
727
|
//#region src/host/dsh-0.2-bridge.d.ts
|
|
@@ -996,9 +1096,29 @@ interface LlmLike {
|
|
|
996
1096
|
* `this.ctx.llm.resolveModelInfo(...)` 再查 `inputModalities`)。入口要与内核
|
|
997
1097
|
* 得出同一个结论,只能查同一份元数据 —— 名字、前缀、家族表都是启发式,会漂。
|
|
998
1098
|
*/
|
|
999
|
-
resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<
|
|
1000
|
-
|
|
1001
|
-
|
|
1099
|
+
resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfoLike | undefined>;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* `ctx.llm.resolveModelInfo()` 返回值里桥消费的那几个字段。
|
|
1103
|
+
*
|
|
1104
|
+
* 内核的 `LlmResolvedModelInfo` 比这更宽(还有 `reasoning` / `systemPromptUpdate`
|
|
1105
|
+
* / `toolUpdate`),这里只声明真正读的:上下文容量与默认输出上限。
|
|
1106
|
+
*
|
|
1107
|
+
* **为什么必须从 `resolveModelInfo` 拿、而不是从 `modelCatalog()` 拿**:
|
|
1108
|
+
* 0.2.x 的 `ModelCatalogModel` 只有 `{ id, name, description?, reasoning? }` ——
|
|
1109
|
+
* 目录里**没有** `contextWindow` / `maxTokens` / `inputModalities`。这三个只在
|
|
1110
|
+
* `LlmResolvedModelInfo`(= `LlmModelInfo` + `context` + `defaultMaxTokens`)里。
|
|
1111
|
+
* 目录条目自带这两个字段是 1.x 的旧形状,2.x 拆开了。
|
|
1112
|
+
*/
|
|
1113
|
+
interface LlmResolvedModelInfoLike {
|
|
1114
|
+
/** 输入模态;`undefined` = 未知,显式缺 `image` = 负能力。 */
|
|
1115
|
+
readonly inputModalities?: readonly string[];
|
|
1116
|
+
/** 上下文容量(请求 + 响应合计 token 上限);内核不知道时不存在。 */
|
|
1117
|
+
readonly context?: {
|
|
1118
|
+
readonly contextWindow: number;
|
|
1119
|
+
};
|
|
1120
|
+
/** 调用方不显式给上限时,adapter 会用的每请求输出上限。 */
|
|
1121
|
+
readonly defaultMaxTokens?: number;
|
|
1002
1122
|
}
|
|
1003
1123
|
/** Host services the bridge consumes; assembled by `src/index.ts` from `ctx`. */
|
|
1004
1124
|
interface Dsh02HostServices {
|
|
@@ -1551,6 +1671,19 @@ interface ChatSessionView {
|
|
|
1551
1671
|
origin?: 'subagent';
|
|
1552
1672
|
pendingInteraction?: 'approval';
|
|
1553
1673
|
}
|
|
1674
|
+
/**
|
|
1675
|
+
* 建会话的可选参数。
|
|
1676
|
+
*
|
|
1677
|
+
* `reuseBlank` 是**桥**加的语义,不是内核字段:DSH 0.2.1 起桌面端点「新会话」会回到
|
|
1678
|
+
* 那个工作区已有的空白会话,而内核 RPC 不做这层挑选(见 `reusableBlankId`)。
|
|
1679
|
+
* 桥默认照桌面语义走(省略即 `true`),但留一个 `false` 让调用方要「无论如何新建一个」
|
|
1680
|
+
* 时能要得到 —— 老客户端从来不带这个字段,行为因此是**新增**而不是**变更**。
|
|
1681
|
+
*/
|
|
1682
|
+
interface CreateSessionOptions {
|
|
1683
|
+
sessionId?: string;
|
|
1684
|
+
agentPreset?: string;
|
|
1685
|
+
reuseBlank?: boolean;
|
|
1686
|
+
}
|
|
1554
1687
|
interface PendingApprovalView {
|
|
1555
1688
|
id: string;
|
|
1556
1689
|
sessionId: string;
|
|
@@ -1706,20 +1839,49 @@ declare class DshChatAdapter {
|
|
|
1706
1839
|
defaultPreset: string;
|
|
1707
1840
|
}>;
|
|
1708
1841
|
selectAgentPreset(principal: Principal, sessionId: string, agentPreset: string): Promise<string>;
|
|
1709
|
-
createSessionInWorkspace(principal: Principal, workspaceId: string, options: {
|
|
1710
|
-
sessionId?: string;
|
|
1711
|
-
agentPreset?: string;
|
|
1712
|
-
}): Promise<{
|
|
1842
|
+
createSessionInWorkspace(principal: Principal, workspaceId: string, options: CreateSessionOptions): Promise<{
|
|
1713
1843
|
id: string;
|
|
1714
1844
|
agentPreset?: string;
|
|
1715
1845
|
}>;
|
|
1716
|
-
createSession(principal: Principal, rootId: string, relativePath: string, options: {
|
|
1717
|
-
sessionId?: string;
|
|
1718
|
-
agentPreset?: string;
|
|
1719
|
-
}): Promise<{
|
|
1846
|
+
createSession(principal: Principal, rootId: string, relativePath: string, options: CreateSessionOptions): Promise<{
|
|
1720
1847
|
id: string;
|
|
1721
1848
|
agentPreset?: string;
|
|
1722
1849
|
}>;
|
|
1850
|
+
/**
|
|
1851
|
+
* 挑一个可复用的空白会话 —— DSH 0.2.1 起桌面端的「New Session 回到空白会话」语义。
|
|
1852
|
+
*
|
|
1853
|
+
* **内核自己不做这件事。** `sessionController.create` 只有调用方显式传了 `sessionId`
|
|
1854
|
+
* 才会走 `claim` / `reuseLive` 去复用;不传就无条件 `createMinted` 新建一个。
|
|
1855
|
+
* 「跳过 preset 不匹配的空白会话」那层挑选写在**客户端**(`dsh-client-ui-workspace`
|
|
1856
|
+
* 的 `connectWorkspace` → `createInWorkspace`)里,而桥走 REST 直连内核,没有这一层 ——
|
|
1857
|
+
* 于是手机端每点一次「新会话」就多一个空白会话,攒成一串永远没人用的空壳。
|
|
1858
|
+
*
|
|
1859
|
+
* 这里把客户端那条判据原样搬过来(`blank && cwd 匹配 && 未归档 && preset 匹配`),
|
|
1860
|
+
* 取用顺序也照抄:**按内核返回的列表顺序取第一个**,不自己按时间排序 —— 同一份数据
|
|
1861
|
+
* 在桌面与手机上应当落到同一条会话上,桥没有理由发明第三种顺序。
|
|
1862
|
+
*
|
|
1863
|
+
* preset 的口径也与内核一致:空白会话**没声明** preset 时对任何预设都算可用
|
|
1864
|
+
* (只有 `typeof listed === 'string'` 才比较)。「没声明」不等于「声明了别的」——
|
|
1865
|
+
* 把前者当成不匹配会让所有老会话都挑不中。
|
|
1866
|
+
*/
|
|
1867
|
+
private reusableBlankId;
|
|
1868
|
+
/**
|
|
1869
|
+
* 工作区维度上找一个可复用的空白会话。
|
|
1870
|
+
*
|
|
1871
|
+
* 归属判据用**内核自己的两个字段**:`session.cwd === workspace.path` 且
|
|
1872
|
+
* `workspace.sessionIds` 含它 —— 与客户端 `createInWorkspace` 逐字同义。
|
|
1873
|
+
* 这里重新拉一次原始 workspace 列表(而不是复用 `listWorkspaces` 的投影结果),
|
|
1874
|
+
* 因为 `ChatWorkspaceView` 刻意不含 `sessionIds` 与绝对路径。
|
|
1875
|
+
*/
|
|
1876
|
+
private blankSessionInWorkspace;
|
|
1877
|
+
/**
|
|
1878
|
+
* 绝对路径维度上找一个可复用的空白会话(老客户端的 `rootId` + `path` 入口)。
|
|
1879
|
+
*
|
|
1880
|
+
* 这里没有 workspace 摘要可用,判据就只剩「同一 cwd」—— 与客户端按 workspace
|
|
1881
|
+
* 挑选相比少了两道(`sessionIds` 归属、workspace 存在性),但那两道在
|
|
1882
|
+
* `rootId` + `path` 这条路上本来就由授权根解析替代了。
|
|
1883
|
+
*/
|
|
1884
|
+
private blankSessionAtPath;
|
|
1723
1885
|
history(principal: Principal, sessionId: string, options: {
|
|
1724
1886
|
beforeSeq?: number;
|
|
1725
1887
|
maxMessages?: number;
|
|
@@ -1927,6 +2089,14 @@ declare class AuthService {
|
|
|
1927
2089
|
principal(req: IncomingMessage, options?: {
|
|
1928
2090
|
allowAdmin?: boolean;
|
|
1929
2091
|
}): Principal;
|
|
2092
|
+
/**
|
|
2093
|
+
* 把本次请求探测到的身份记进库,并(在允许时)更新设备显示名。
|
|
2094
|
+
*
|
|
2095
|
+
* 吞掉一切异常的理由见调用点注释:设备名是显示信息,不该成为连接的前置条件。
|
|
2096
|
+
*
|
|
2097
|
+
* @returns 改名成功时的新名字;没改名(或探测不到)时 `undefined`。
|
|
2098
|
+
*/
|
|
2099
|
+
private recordIdentity;
|
|
1930
2100
|
/**
|
|
1931
2101
|
* `/manage/**` 的守门人。
|
|
1932
2102
|
*
|
|
@@ -2696,6 +2866,18 @@ interface Config {
|
|
|
2696
2866
|
* 遥控器交出去(理由见 docs/project/SECURITY.md)。
|
|
2697
2867
|
*/
|
|
2698
2868
|
settingsWriteNamespaces?: readonly string[];
|
|
2869
|
+
/**
|
|
2870
|
+
* 允许供应商 `baseURL` 指向的私网 / 回环 host 白名单(默认空 = 一律拒绝)。
|
|
2871
|
+
*
|
|
2872
|
+
* 默认拒绝的理由:手机端改 `baseURL` 就等于把模型请求(含 `Authorization:
|
|
2873
|
+
* Bearer <真实 key>`)引到任意地址,这是 SEC-H11 的窃取路径。默认拒绝把
|
|
2874
|
+
* 「谁能连内网」变成部署方的显式决定。
|
|
2875
|
+
*
|
|
2876
|
+
* 要连本地 LLM(ollama / LM Studio 之类)时填这里,推荐带端口:
|
|
2877
|
+
* `["127.0.0.1:11434", "localhost:1234"]`。带端口的条目只放行该端口,裸
|
|
2878
|
+
* host 条目放行该主机的任意端口(面更大)。规则见 `src/host/provider-url-policy.ts`。
|
|
2879
|
+
*/
|
|
2880
|
+
allowPrivateProviderHosts?: readonly string[];
|
|
2699
2881
|
}
|
|
2700
2882
|
interface HostContext extends Dsh02HostServices {
|
|
2701
2883
|
webServer: Parameters<typeof attachEmbeddedRoutes>[0];
|