dsh-remote-bridge 0.0.0-stage → 2.2.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/lib/index.d.ts ADDED
@@ -0,0 +1,1261 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+ //#region src/shared/contracts.d.ts
3
+ declare const DEVICE_SCOPES: readonly ["chat.read", "chat.write", "files.read", "files.write", "files.delete", "settings.read", "settings.write"];
4
+ type DeviceScope = (typeof DEVICE_SCOPES)[number];
5
+ interface RootView {
6
+ id: string;
7
+ label: string;
8
+ createdAt: number;
9
+ }
10
+ interface DeviceView {
11
+ id: string;
12
+ name: string;
13
+ scopes: DeviceScope[];
14
+ rootIds: string[];
15
+ createdAt: number;
16
+ lastSeenAt?: number;
17
+ revokedAt?: number;
18
+ }
19
+ interface FileEntryView {
20
+ name: string;
21
+ path: string;
22
+ kind: 'file' | 'directory' | 'symlink' | 'other';
23
+ size: number;
24
+ modifiedAt: number;
25
+ writable: boolean;
26
+ }
27
+ interface WorkspaceEvent {
28
+ id: string;
29
+ type: string;
30
+ time: number;
31
+ data: Record<string, unknown>;
32
+ }
33
+ interface AdminPrincipal {
34
+ kind: 'admin';
35
+ id: 'loopback-admin';
36
+ scopes: ReadonlySet<DeviceScope>;
37
+ rootIds: 'all';
38
+ }
39
+ interface DevicePrincipal {
40
+ kind: 'device';
41
+ id: string;
42
+ name: string;
43
+ scopes: ReadonlySet<DeviceScope>;
44
+ rootIds: ReadonlySet<string>;
45
+ }
46
+ type Principal = AdminPrincipal | DevicePrincipal;
47
+ //#endregion
48
+ //#region src/host/database.d.ts
49
+ interface StoredRoot extends RootView {
50
+ realPath: string;
51
+ }
52
+ interface AuthenticatedDevice extends DeviceView {
53
+ tokenHash: string;
54
+ }
55
+ interface TrashRecord {
56
+ id: string;
57
+ rootId: string;
58
+ relativePath: string;
59
+ payloadPath: string;
60
+ kind: string;
61
+ size: number;
62
+ digest: string;
63
+ createdAt: number;
64
+ status: string;
65
+ }
66
+ declare class WorkspaceDatabase {
67
+ readonly dataDir: string;
68
+ readonly trashDir: string;
69
+ private readonly db;
70
+ constructor(dataDir: string);
71
+ close(): void;
72
+ getSetting(key: string): string | undefined;
73
+ setSetting(key: string, value: string): void;
74
+ addRoot(inputPath: string, label?: string): Promise<StoredRoot>;
75
+ listRoots(): StoredRoot[];
76
+ getRoot(id: string): StoredRoot | undefined;
77
+ removeRoot(id: string): void;
78
+ createPairing(rootIds: string[], scopes: DeviceScope[], ttlMs?: number): {
79
+ code: string;
80
+ expiresAt: number;
81
+ };
82
+ exchangePairing(code: string, deviceName: string): {
83
+ token: string;
84
+ device: DeviceView;
85
+ };
86
+ authenticate(token: string): AuthenticatedDevice | undefined;
87
+ getActiveDevice(id: string): AuthenticatedDevice | undefined;
88
+ private hydrateDevice;
89
+ listDevices(): DeviceView[];
90
+ isDeviceActive(id: string): boolean;
91
+ updateDevice(id: string, scopes: DeviceScope[], rootIds: string[]): DeviceView;
92
+ revokeDevice(id: string): void;
93
+ insertTrash(record: TrashRecord): void;
94
+ updateTrashStatus(id: string, status: string): void;
95
+ getTrash(id: string): TrashRecord | undefined;
96
+ listTrash(rootIds?: ReadonlySet<string>): TrashRecord[];
97
+ deleteTrashRecord(id: string): void;
98
+ audit(actorType: string, actorId: string, action: string, rootId?: string, relativePath?: string, details?: Record<string, unknown>, requestId?: string): void;
99
+ listAudit(limit?: number): Record<string, unknown>[];
100
+ readIdempotent(deviceId: string, key: string): {
101
+ status: number;
102
+ body: unknown;
103
+ } | undefined;
104
+ saveIdempotent(deviceId: string, key: string, status: number, body: unknown): void;
105
+ private migrate;
106
+ }
107
+ //#endregion
108
+ //#region src/host/chat-adapter.d.ts
109
+ interface RpcResponse<T> {
110
+ result: {
111
+ ok: true;
112
+ value: T;
113
+ } | {
114
+ ok: false;
115
+ error: {
116
+ code: string;
117
+ message: string;
118
+ details?: unknown;
119
+ };
120
+ };
121
+ }
122
+ /**
123
+ * 一条 prompt 的内容片段 —— 与 DSH 0.2.x 的 `PromptContentPart` 一一对应。
124
+ *
125
+ * 三种形态的代价差别很大,值得写清楚:
126
+ * - `text` 直接发送;
127
+ * - `image` 字节**内联**在 payload 里(base64),一次调用完成,但请求体膨胀约 33%,
128
+ * 所以客户端必须先限制原图大小,别等 413;
129
+ * - `file` 的 `receiptId` 必须来自**先前的**上传调用,并在同一次 prompt 里绑定,
130
+ * 否则凭证会被回收。
131
+ */
132
+ type PromptContentPart = {
133
+ type: 'text';
134
+ text: string;
135
+ } | {
136
+ type: 'image';
137
+ mediaType: string;
138
+ data: string;
139
+ name?: string;
140
+ } | {
141
+ type: 'file';
142
+ receiptId: string;
143
+ };
144
+ interface DshApiProxy {
145
+ respond(message: {
146
+ type: 'client-response';
147
+ rpcId: string;
148
+ result: {
149
+ ok: true;
150
+ value: unknown;
151
+ };
152
+ }): Promise<{
153
+ accepted: true;
154
+ } | {
155
+ accepted: false;
156
+ reason: 'not-pending' | 'bad-response';
157
+ }>;
158
+ sessions: {
159
+ list(request: {
160
+ rpcId: string;
161
+ payload: {
162
+ cursor?: string;
163
+ };
164
+ }): Promise<RpcResponse<{
165
+ items: DshSessionSummary[];
166
+ }>>;
167
+ create(request: {
168
+ rpcId: string;
169
+ payload: {
170
+ cwd?: string;
171
+ workspaceId?: string;
172
+ sessionId?: string;
173
+ agentPreset?: string;
174
+ };
175
+ }): Promise<RpcResponse<{
176
+ sessionId: string;
177
+ agentPreset?: string;
178
+ }>>;
179
+ history(request: {
180
+ rpcId: string;
181
+ payload: {
182
+ sessionId: string;
183
+ beforeSeq?: number;
184
+ maxMessages?: number;
185
+ };
186
+ }): Promise<RpcResponse<{
187
+ events: unknown[];
188
+ hasMore: boolean;
189
+ projections?: unknown;
190
+ }>>;
191
+ prompt(request: {
192
+ rpcId: string;
193
+ payload: {
194
+ sessionId: string;
195
+ mode: 'queue' | 'steer';
196
+ content: PromptContentPart[];
197
+ clientTimeZone?: string;
198
+ };
199
+ }): Promise<RpcResponse<{
200
+ accepted: true;
201
+ command?: unknown;
202
+ }>>;
203
+ /**
204
+ * 上传一个文件,换取 prompt 可引用的凭证(`receiptId`)。
205
+ * `data` 是文件字节的**标准 base64**。
206
+ */
207
+ uploadAttachment(request: {
208
+ rpcId: string;
209
+ payload: {
210
+ sessionId: string;
211
+ data: string;
212
+ name?: string;
213
+ };
214
+ }): Promise<RpcResponse<{
215
+ receiptId: string;
216
+ name: string | null;
217
+ }>>;
218
+ /**
219
+ * 按 id 取回附件字节。
220
+ *
221
+ * `attachmentId` 形如 `sha256:<hex>`(**含冒号**,客户端必须 URL 编码)。
222
+ * 返回 `Uint8Array` 而不是 base64:图片动辄上百 KB,多一次编解码纯属浪费。
223
+ */
224
+ readAttachment(request: {
225
+ rpcId: string;
226
+ payload: {
227
+ attachmentId: string;
228
+ };
229
+ }): Promise<RpcResponse<{
230
+ bytes: Uint8Array;
231
+ mediaType: string;
232
+ }>>;
233
+ cancel(request: {
234
+ rpcId: string;
235
+ payload: {
236
+ sessionId: string;
237
+ };
238
+ }): Promise<RpcResponse<{
239
+ accepted: true;
240
+ }>>;
241
+ rename(request: {
242
+ rpcId: string;
243
+ payload: {
244
+ sessionId: string;
245
+ title: string;
246
+ };
247
+ }): Promise<RpcResponse<{
248
+ title: string;
249
+ seq: number;
250
+ }>>;
251
+ fork(request: {
252
+ rpcId: string;
253
+ payload: {
254
+ sessionId: string;
255
+ atSeq?: number;
256
+ };
257
+ }): Promise<RpcResponse<{
258
+ sessionId: string;
259
+ }>>;
260
+ models(request: {
261
+ rpcId: string;
262
+ payload: {
263
+ sessionId: string;
264
+ };
265
+ }): Promise<RpcResponse<SessionModelsView>>;
266
+ selectModel(request: {
267
+ rpcId: string;
268
+ payload: {
269
+ sessionId: string;
270
+ provider: string;
271
+ model: string;
272
+ reasoningEffort?: string;
273
+ };
274
+ }): Promise<RpcResponse<{
275
+ selected: ModelSelectionView;
276
+ }>>;
277
+ };
278
+ workspace: {
279
+ list(request: {
280
+ rpcId: string;
281
+ payload: Record<string, never>;
282
+ }): Promise<RpcResponse<{
283
+ items: DshWorkspaceSummary[];
284
+ archivedSessionIds: string[];
285
+ }>>;
286
+ create(request: {
287
+ rpcId: string;
288
+ payload: {
289
+ path: string;
290
+ };
291
+ }): Promise<RpcResponse<{
292
+ workspace: DshWorkspaceSummary;
293
+ created: boolean;
294
+ }>>;
295
+ rename(request: {
296
+ rpcId: string;
297
+ payload: {
298
+ workspaceId: string;
299
+ title: string;
300
+ };
301
+ }): Promise<RpcResponse<{
302
+ workspace: DshWorkspaceSummary;
303
+ }>>;
304
+ delete(request: {
305
+ rpcId: string;
306
+ payload: {
307
+ workspaceId: string;
308
+ };
309
+ }): Promise<RpcResponse<{
310
+ deleted: true;
311
+ }>>;
312
+ archiveSession(request: {
313
+ rpcId: string;
314
+ payload: {
315
+ sessionId: string;
316
+ };
317
+ }): Promise<RpcResponse<{
318
+ archivedSessionIds: string[];
319
+ }>>;
320
+ };
321
+ agentPresets: {
322
+ list(request: {
323
+ rpcId: string;
324
+ payload: Record<string, never>;
325
+ }): Promise<RpcResponse<{
326
+ presets: DshAgentPresetSummary[];
327
+ authorable: boolean;
328
+ hasDocument: boolean;
329
+ }>>;
330
+ select(request: {
331
+ rpcId: string;
332
+ payload: {
333
+ sessionId: string;
334
+ agentPreset: string;
335
+ };
336
+ }): Promise<RpcResponse<{
337
+ agentPreset: string;
338
+ }>>;
339
+ };
340
+ events: {
341
+ mux(request: {
342
+ rpcId: string;
343
+ payload: Record<string, never>;
344
+ }, signal: AbortSignal): AsyncIterable<{
345
+ rpcId: string;
346
+ payload: Record<string, unknown>;
347
+ }>;
348
+ host(request: {
349
+ rpcId: string;
350
+ payload: Record<string, never>;
351
+ }, signal: AbortSignal): AsyncIterable<{
352
+ rpcId: string;
353
+ payload: Record<string, unknown>;
354
+ }>;
355
+ };
356
+ }
357
+ interface ModelSelectionView {
358
+ provider: string;
359
+ model: string;
360
+ reasoningEffort?: string;
361
+ }
362
+ interface ModelReasoningView {
363
+ efforts: {
364
+ id: string;
365
+ name: string;
366
+ description?: string;
367
+ }[];
368
+ defaultEffort?: string;
369
+ }
370
+ interface ModelProviderGroupView {
371
+ id: string;
372
+ name: string;
373
+ models: {
374
+ id: string;
375
+ name: string;
376
+ description?: string;
377
+ contextWindow?: number;
378
+ maxTokens?: number;
379
+ reasoning?: ModelReasoningView;
380
+ }[];
381
+ }
382
+ interface SessionModelsView {
383
+ current: ModelSelectionView;
384
+ routable: boolean;
385
+ groups: ModelProviderGroupView[];
386
+ failures: {
387
+ provider: string;
388
+ message: string;
389
+ }[];
390
+ }
391
+ interface CommandDescriptorView {
392
+ name: string;
393
+ description: string;
394
+ input?: {
395
+ hint: string;
396
+ };
397
+ }
398
+ interface CommandExecutionView {
399
+ commandId: string;
400
+ result: {
401
+ kind: 'success' | 'error';
402
+ text?: string;
403
+ sourceEventSeq?: number;
404
+ };
405
+ }
406
+ interface DshCommandRuntime {
407
+ list(agent: unknown): readonly CommandDescriptorView[];
408
+ execute(agent: unknown, line: string, signal: AbortSignal): Promise<CommandExecutionView | undefined>;
409
+ }
410
+ interface DshAgentRegistry {
411
+ get(sessionId: string): unknown | undefined;
412
+ }
413
+ interface DshCommandServices {
414
+ commands: DshCommandRuntime;
415
+ agents: DshAgentRegistry;
416
+ }
417
+ interface DshSessionSummary {
418
+ sessionId: string;
419
+ updatedAt: number;
420
+ running: boolean;
421
+ blank: boolean;
422
+ cwd?: string;
423
+ agentPreset?: string;
424
+ parentSessionId?: string;
425
+ origin?: 'subagent';
426
+ projections?: {
427
+ asOfSeq: number;
428
+ values: Record<string, unknown>;
429
+ };
430
+ }
431
+ interface DshWorkspaceSummary {
432
+ workspaceId: string;
433
+ path: string;
434
+ title: string;
435
+ sessionIds: string[];
436
+ createdAt: string;
437
+ updatedAt: string;
438
+ }
439
+ interface DshAgentPresetSummary {
440
+ id: string;
441
+ trust: 'system' | 'user';
442
+ isDefault: boolean;
443
+ name?: string;
444
+ description?: string;
445
+ broken?: string;
446
+ }
447
+ interface ChatSessionView {
448
+ id: string;
449
+ rootId: string;
450
+ cwd: string;
451
+ updatedAt: number;
452
+ running: boolean;
453
+ blank: boolean;
454
+ title?: string;
455
+ workspaceId?: string;
456
+ workspaceTitle?: string;
457
+ agentPreset?: string;
458
+ parentSessionId?: string;
459
+ origin?: 'subagent';
460
+ pendingInteraction?: 'approval';
461
+ }
462
+ interface PendingApprovalView {
463
+ id: string;
464
+ sessionId: string;
465
+ toolName: string;
466
+ reason?: string;
467
+ detail?: string;
468
+ risk: 'standard' | 'full-access';
469
+ requestedAt: number;
470
+ }
471
+ interface ChatWorkspaceView {
472
+ id: string;
473
+ title: string;
474
+ rootId: string;
475
+ path: string;
476
+ createdAt: string;
477
+ updatedAt: string;
478
+ }
479
+ interface AgentPresetView {
480
+ id: string;
481
+ name: string;
482
+ description?: string;
483
+ trust: 'system' | 'user';
484
+ isDefault: boolean;
485
+ available: boolean;
486
+ }
487
+ declare class DshChatAdapter {
488
+ private readonly api;
489
+ private readonly database;
490
+ private readonly commandServices?;
491
+ private readonly sessionRootIds;
492
+ private readonly sessionRootLookups;
493
+ private readonly pendingApprovals;
494
+ constructor(api: DshApiProxy, database: WorkspaceDatabase, commandServices?: DshCommandServices | undefined);
495
+ listSessions(principal: Principal): Promise<ChatSessionView[]>;
496
+ listWorkspaces(principal: Principal): Promise<ChatWorkspaceView[]>;
497
+ createWorkspace(principal: Principal, rootId: string, relativePath: string): Promise<ChatWorkspaceView>;
498
+ renameWorkspace(principal: Principal, workspaceId: string, title: string): Promise<ChatWorkspaceView>;
499
+ deleteWorkspace(principal: Principal, workspaceId: string): Promise<void>;
500
+ listAgentPresets(): Promise<AgentPresetView[]>;
501
+ selectAgentPreset(principal: Principal, sessionId: string, agentPreset: string): Promise<string>;
502
+ createSessionInWorkspace(principal: Principal, workspaceId: string, options: {
503
+ sessionId?: string;
504
+ agentPreset?: string;
505
+ }): Promise<{
506
+ id: string;
507
+ agentPreset?: string;
508
+ }>;
509
+ createSession(principal: Principal, rootId: string, relativePath: string, options: {
510
+ sessionId?: string;
511
+ agentPreset?: string;
512
+ }): Promise<{
513
+ id: string;
514
+ agentPreset?: string;
515
+ }>;
516
+ history(principal: Principal, sessionId: string, options: {
517
+ beforeSeq?: number;
518
+ maxMessages?: number;
519
+ }): Promise<{
520
+ events: unknown[];
521
+ hasMore: boolean;
522
+ projections?: unknown;
523
+ }>;
524
+ prompt(principal: Principal, sessionId: string, parts: PromptContentPart[], mode: 'queue' | 'steer', clientTimeZone?: string): Promise<{
525
+ accepted: true;
526
+ command?: unknown;
527
+ }>;
528
+ /**
529
+ * 上传一个附件,返回 prompt 可以引用的凭证。
530
+ *
531
+ * 为什么需要这一步而不是让客户端直接把字节塞进消息:内核对**文件**类型要求的是
532
+ * `receiptId`(由上传换取),只有**图片**才允许内联 base64。
533
+ */
534
+ uploadAttachment(principal: Principal, sessionId: string, data: string, name?: string): Promise<{
535
+ receiptId: string;
536
+ name: string | null;
537
+ }>;
538
+ /**
539
+ * 取回会话历史里某个图片附件的字节。
540
+ *
541
+ * 这里不做会话归属校验:附件 id 是内容哈希,拿到 id 就等于拿到内容,
542
+ * 而且调用方必须已经通过 `files.read` 鉴权并主动持有该 id(它只出现在其有权读取的
543
+ * 会话事件里)。多一层会话校验并不能提升安全性,却会让「在别的会话里引用同一张图」
544
+ * 这种正常情况莫名失败。
545
+ */
546
+ readAttachment(principal: Principal, attachmentId: string): Promise<{
547
+ bytes: Uint8Array;
548
+ mediaType: string;
549
+ }>;
550
+ cancel(principal: Principal, sessionId: string): Promise<void>;
551
+ renameSession(principal: Principal, sessionId: string, title: string): Promise<{
552
+ title: string;
553
+ seq: number;
554
+ }>;
555
+ forkSession(principal: Principal, sessionId: string, atSeq?: number): Promise<string>;
556
+ archiveSession(principal: Principal, sessionId: string): Promise<string[]>;
557
+ listPendingApprovals(principal: Principal, sessionId: string): Promise<PendingApprovalView[]>;
558
+ decideApproval(principal: Principal, sessionId: string, approvalId: string, outcome: 'allowed-once' | 'rejected'): Promise<PendingApprovalView>;
559
+ models(principal: Principal, sessionId: string): Promise<SessionModelsView>;
560
+ selectModel(principal: Principal, sessionId: string, selection: ModelSelectionView): Promise<ModelSelectionView>;
561
+ listCommands(principal: Principal, sessionId: string): Promise<CommandDescriptorView[]>;
562
+ executeCommand(principal: Principal, sessionId: string, line: string): Promise<CommandExecutionView>;
563
+ isSessionAuthorized(principal: Principal, sessionId: string): Promise<boolean>;
564
+ startEvents(signal: AbortSignal, emit: (event: WorkspaceEvent) => void): void;
565
+ private captureApproval;
566
+ private hasPendingApproval;
567
+ /**
568
+ * Resolve the Agent backing a session for command work.
569
+ *
570
+ * Returns `undefined` when the session exists but cannot be activated. 0.2.x
571
+ * rejects activation for a Session owned by a subagent, so that outcome is a
572
+ * property of the session rather than a failure of this call: the caller
573
+ * decides whether it means "no commands" (listing) or an error (execution).
574
+ */
575
+ private commandAgent;
576
+ private requireSession;
577
+ private requireWorkspace;
578
+ private visibleWorkspaces;
579
+ private requireAuthorizedRoot;
580
+ private authorizedRoots;
581
+ private sessionRootId;
582
+ }
583
+ //#endregion
584
+ //#region src/host/settings-adapter.d.ts
585
+ interface SettingsNamespaceView {
586
+ ns: string;
587
+ schema?: unknown;
588
+ value: unknown;
589
+ base?: unknown;
590
+ user?: unknown;
591
+ revision: number;
592
+ }
593
+ interface ConfigurableProvider {
594
+ provider: string;
595
+ displayName: string;
596
+ settingsNs: string;
597
+ settingsPath: string[];
598
+ active: boolean;
599
+ declared?: boolean;
600
+ }
601
+ interface SettingsApiProxy extends DshApiProxy {
602
+ settings: {
603
+ describe(request: {
604
+ rpcId: string;
605
+ payload: Record<string, never>;
606
+ }): Promise<RpcResponse<{
607
+ writable: boolean;
608
+ hasDocument: boolean;
609
+ namespaces: SettingsNamespaceView[];
610
+ }>>;
611
+ mutate(request: {
612
+ rpcId: string;
613
+ payload: {
614
+ ns: string;
615
+ ops: Array<{
616
+ op: 'set';
617
+ path: string[];
618
+ value: unknown;
619
+ } | {
620
+ op: 'unset';
621
+ path: string[];
622
+ }>;
623
+ expectedRevision?: number;
624
+ };
625
+ }): Promise<RpcResponse<SettingsNamespaceView>>;
626
+ };
627
+ credentials: {
628
+ describe(request: {
629
+ rpcId: string;
630
+ payload: {
631
+ refs: string[];
632
+ };
633
+ }): Promise<RpcResponse<{
634
+ credentials: Record<string, {
635
+ configured: boolean;
636
+ source?: string;
637
+ writable: boolean;
638
+ }>;
639
+ }>>;
640
+ set(request: {
641
+ rpcId: string;
642
+ payload: {
643
+ ref: string;
644
+ value: string;
645
+ };
646
+ }): Promise<RpcResponse<Record<string, never>>>;
647
+ unset(request: {
648
+ rpcId: string;
649
+ payload: {
650
+ ref: string;
651
+ };
652
+ }): Promise<RpcResponse<Record<string, never>>>;
653
+ };
654
+ llm: {
655
+ providers(request: {
656
+ rpcId: string;
657
+ payload: Record<string, never>;
658
+ }): Promise<RpcResponse<{
659
+ providers: ConfigurableProvider[];
660
+ }>>;
661
+ models(request: {
662
+ rpcId: string;
663
+ payload: Record<string, never>;
664
+ }): Promise<RpcResponse<{
665
+ groups: ModelProviderGroupView[];
666
+ failures: {
667
+ provider: string;
668
+ message: string;
669
+ }[];
670
+ }>>;
671
+ discoverModels(request: {
672
+ rpcId: string;
673
+ payload: {
674
+ settingsNs: string;
675
+ provider?: string;
676
+ baseURL?: string;
677
+ api?: string;
678
+ apiKey?: string;
679
+ };
680
+ }): Promise<RpcResponse<{
681
+ models: ProviderModelView[];
682
+ }>>;
683
+ };
684
+ }
685
+ interface ProviderModelView {
686
+ id: string;
687
+ name?: string;
688
+ contextWindow?: number;
689
+ maxTokens?: number;
690
+ }
691
+ interface ProviderConfigView {
692
+ baseURL?: string;
693
+ api?: string;
694
+ displayName?: string;
695
+ thinking?: string;
696
+ reasoningEffort?: string;
697
+ models: ProviderModelView[];
698
+ modelsInherited?: boolean;
699
+ }
700
+ interface ProviderView {
701
+ id: string;
702
+ displayName: string;
703
+ active: boolean;
704
+ declared?: boolean;
705
+ configurable: boolean;
706
+ configured: boolean;
707
+ removable: boolean;
708
+ credential: {
709
+ ref: string;
710
+ configured: boolean;
711
+ source?: string;
712
+ writable: boolean;
713
+ };
714
+ config: ProviderConfigView;
715
+ }
716
+ interface ProviderPatch {
717
+ displayName?: string | null;
718
+ baseURL?: string | null;
719
+ api?: string | null;
720
+ apiKey?: string | null;
721
+ thinking?: string | null;
722
+ reasoningEffort?: string | null;
723
+ models?: ProviderModelView[];
724
+ expectedRevision?: number;
725
+ }
726
+ interface CustomProviderCreate {
727
+ id: string;
728
+ displayName?: string;
729
+ baseURL: string;
730
+ api: string;
731
+ apiKey?: string;
732
+ models: ProviderModelView[];
733
+ expectedRevision?: number;
734
+ }
735
+ interface CustomProviderCapability {
736
+ available: boolean;
737
+ protocols: string[];
738
+ revision?: number;
739
+ }
740
+ declare class DshSettingsAdapter {
741
+ private readonly api;
742
+ constructor(api: SettingsApiProxy);
743
+ listProviders(): Promise<{
744
+ writable: boolean;
745
+ revisionByNamespace: Record<string, number>;
746
+ customProvider: CustomProviderCapability;
747
+ providers: ProviderView[];
748
+ }>;
749
+ catalog(): Promise<{
750
+ groups: ModelProviderGroupView[];
751
+ failures: {
752
+ provider: string;
753
+ message: string;
754
+ }[];
755
+ }>;
756
+ updateProvider(providerId: string, patch: ProviderPatch): Promise<void>;
757
+ createProvider(input: CustomProviderCreate): Promise<void>;
758
+ removeProvider(providerId: string, expectedRevision?: number): Promise<void>;
759
+ discover(providerId: string, draft: {
760
+ baseURL?: string;
761
+ api?: string;
762
+ apiKey?: string;
763
+ }): Promise<ProviderModelView[]>;
764
+ private providerContext;
765
+ }
766
+ //#endregion
767
+ //#region src/host/dsh-0.2-bridge.d.ts
768
+ /**
769
+ * Prompt content a 0.2.x Host accepts — mirrors its `PromptContentPart` union.
770
+ *
771
+ * 权威定义在 `chat-adapter.ts`(协议层),这里只是复用它,避免两处各写一遍
772
+ * 然后悄悄漂移。
773
+ */
774
+ type PromptContentPartLike = PromptContentPart;
775
+ /**
776
+ * `ctx.fileUploads`:内核的文件上传服务。
777
+ *
778
+ * 由内核的 `@deepseek-ai/dsh-client-file-upload` 以 cordis 服务的形式暴露,
779
+ * 因此插件**不需要**把字节转发到内核的 HTTP 路由(那条路由只绑 loopback,手机也到不了)。
780
+ */
781
+ interface FileUploadsLike {
782
+ upload(agent: unknown, request: {
783
+ data: string;
784
+ name?: string;
785
+ }, signal: AbortSignal): Promise<{
786
+ receiptId: string;
787
+ }>;
788
+ /**
789
+ * 把凭证绑定到某次 prompt。返回值不 commit 的话凭证会被回收 ——
790
+ * 所以调用方必须在 prompt **成功之后** commit()。
791
+ */
792
+ bindPrompt(agent: unknown, receiptIds: readonly string[], requestId: string): {
793
+ commit(): void;
794
+ };
795
+ }
796
+ /**
797
+ * `ctx.attachments`:内核的附件存储。
798
+ *
799
+ * 为什么会话里的图片必须经由它:消息**发送时**图片是 base64 内联的,但**存进历史后**
800
+ * 会变成 `{type:'image', attachment:{attachmentId:'sha256:…'}}` 这样的引用 ——
801
+ * 字节在附件存储里,不在事件里。客户端只拿到 id,所以必须有一步「按 id 取字节」,
802
+ * 否则历史里的图片一律显示不出来。
803
+ */
804
+ interface AttachmentStoreLike {
805
+ readImage(ref: {
806
+ attachmentId: string;
807
+ }, signal?: AbortSignal): Promise<{
808
+ ref: {
809
+ mediaType: string;
810
+ };
811
+ data: Uint8Array;
812
+ }>;
813
+ }
814
+ interface SessionControllerLike {
815
+ list(request: {
816
+ cursor?: string;
817
+ }, signal: AbortSignal): Promise<{
818
+ items: readonly unknown[];
819
+ }>;
820
+ create(request: {
821
+ workspaceId?: string;
822
+ cwd?: string;
823
+ sessionId?: string;
824
+ agentPreset?: string;
825
+ }): Promise<{
826
+ sessionId: string;
827
+ agentPreset?: string;
828
+ }>;
829
+ /**
830
+ * Live follow stream. Its first frame is always a `snapshot` carrying the
831
+ * opening window (`cursor`, `records`, `hasMore`), and that is the only
832
+ * reliable way to read history: `page()` demands a `throughSeq` "obtained from
833
+ * the corresponding follow opening frame", so guessing one (e.g. `-1`) returns
834
+ * a well-formed but *empty* page — a silent failure, not an error.
835
+ */
836
+ follow(request: {
837
+ address: {
838
+ kind: 'session';
839
+ sessionId: string;
840
+ };
841
+ maxMessages?: number;
842
+ /**
843
+ * Assistant presentation frames are opt-in: without `assistantStream: true`
844
+ * the host emits durable events only, so a live stream would carry no
845
+ * incremental text at all (the transcript then appears only after commit).
846
+ */
847
+ assistantStream?: true;
848
+ }, signal: AbortSignal): AsyncIterable<{
849
+ type?: string;
850
+ cursor?: number;
851
+ records?: readonly unknown[];
852
+ hasMore?: boolean;
853
+ }>;
854
+ prompt(request: {
855
+ requestId: string;
856
+ sessionId: string;
857
+ mode: 'queue' | 'steer';
858
+ content: readonly PromptContentPartLike[];
859
+ clientTimeZone?: string;
860
+ }, signal: AbortSignal): Promise<{
861
+ accepted: true;
862
+ }>;
863
+ cancel(request: {
864
+ sessionId: string;
865
+ }): {
866
+ accepted: true;
867
+ };
868
+ rename(request: {
869
+ sessionId: string;
870
+ title: string;
871
+ }): Promise<{
872
+ title: string;
873
+ seq: number;
874
+ }>;
875
+ fork(request: {
876
+ sessionId: string;
877
+ atSeq?: number;
878
+ }): Promise<{
879
+ sessionId: string;
880
+ }>;
881
+ selectModel(request: {
882
+ sessionId: string;
883
+ provider: string;
884
+ model: string;
885
+ reasoningEffort?: string;
886
+ }): Promise<{
887
+ selected: unknown;
888
+ }>;
889
+ /**
890
+ * Host model catalog. 0.2.x reports `default`, the routable route list, and
891
+ * per-route failures; the plugin's `SessionModelsView` instead wants a boolean
892
+ * `routable` plus a per-session `current`, so the bridge folds them (see the
893
+ * `models` method) rather than passing this through.
894
+ */
895
+ modelCatalog(): Promise<{
896
+ default?: unknown;
897
+ routableProviders?: readonly string[];
898
+ groups?: readonly unknown[];
899
+ failures?: readonly {
900
+ id?: unknown;
901
+ name?: unknown;
902
+ message?: unknown;
903
+ }[];
904
+ }>;
905
+ /**
906
+ * Projection baseline for one session. Unlike the catalog this is per-session,
907
+ * which is what makes it the source of the session's *current* model selection
908
+ * (`values.modelSelection`).
909
+ */
910
+ projections(request: {
911
+ sessionId: string;
912
+ }, signal: AbortSignal): Promise<{
913
+ asOfSeq?: number;
914
+ values?: Record<string, unknown>;
915
+ } | null>;
916
+ resolveAgent(sessionId: string): Promise<unknown>;
917
+ }
918
+ interface WorkspaceControllerLike {
919
+ create(request: {
920
+ path: string;
921
+ }): Promise<{
922
+ workspace: unknown;
923
+ created: boolean;
924
+ }>;
925
+ rename(request: {
926
+ workspaceId: string;
927
+ title: string;
928
+ }): Promise<unknown>;
929
+ delete(request: {
930
+ workspaceId: string;
931
+ }): Promise<{
932
+ deleted: boolean;
933
+ }>;
934
+ archiveSession(request: {
935
+ sessionId: string;
936
+ }): Promise<{
937
+ archivedSessionIds: readonly string[];
938
+ }>;
939
+ }
940
+ interface WorkspaceRegistryLike {
941
+ list(): readonly {
942
+ id: string;
943
+ path: string;
944
+ title: string;
945
+ createdAt: string;
946
+ updatedAt: string;
947
+ sessionIds?: readonly string[];
948
+ }[];
949
+ }
950
+ interface SettingsControllerLike {
951
+ describe(): {
952
+ writable: boolean;
953
+ hasDocument: boolean;
954
+ namespaces: readonly unknown[];
955
+ };
956
+ mutate(ns: string, ops: readonly unknown[], expectedRevision: number | undefined): Promise<unknown>;
957
+ }
958
+ interface CredentialsControllerLike {
959
+ describe(refs: readonly string[]): Promise<Record<string, unknown>>;
960
+ set(ref: string, value: string): Promise<void>;
961
+ unset(ref: string): Promise<void>;
962
+ }
963
+ interface AgentPresetsLike {
964
+ list(): Promise<readonly unknown[]>;
965
+ remoteExportList(): Promise<unknown>;
966
+ resolveAgent?: never;
967
+ select(agent: unknown, agentPreset: string): Promise<string>;
968
+ }
969
+ interface LlmLike {
970
+ /** Live routes, as `{ id, name }` rows. */
971
+ listProviders(): readonly {
972
+ id: string;
973
+ }[];
974
+ /**
975
+ * Every route an adapter plugin could activate through configuration, live or
976
+ * dormant, carrying the settings namespace and path that configures it.
977
+ * Configuration surfaces merge this with `listProviders()` for liveness.
978
+ */
979
+ listConfigurableProviders(): readonly {
980
+ provider: string;
981
+ displayName: string;
982
+ settingsNs: string;
983
+ settingsPath: readonly string[];
984
+ declared?: boolean;
985
+ error?: string;
986
+ }[];
987
+ discoverModels(settingsNs: string, request: unknown, signal?: AbortSignal): Promise<readonly unknown[]>;
988
+ }
989
+ /** Host services the bridge consumes; assembled by `src/index.ts` from `ctx`. */
990
+ interface Dsh02HostServices {
991
+ sessionController: SessionControllerLike;
992
+ workspaceController: WorkspaceControllerLike;
993
+ workspaceRegistry: WorkspaceRegistryLike;
994
+ settingsController: SettingsControllerLike;
995
+ credentialsController: CredentialsControllerLike;
996
+ agentPresets: AgentPresetsLike;
997
+ llm: LlmLike;
998
+ /**
999
+ * 内核附件存储(`ctx.attachments`);会话历史里的图片要经它取字节。
1000
+ *
1001
+ * 必须是**已经取到的服务对象**,不能是 `ctx` 本身:cordis 的注入属性是有生命周期的
1002
+ * 惰性访问器,只在 `apply` 执行期间可读。把它存进 `host` 里、等到 HTTP 请求回调
1003
+ * (`readAttachment`)里再读,就会撞上
1004
+ * `cannot get property "attachments" without inject` —— 请求链路本身没问题,
1005
+ * 挂在服务端、而且只在真的去取图时才炸,所以很难在本地测出来。
1006
+ */
1007
+ attachments: AttachmentStoreLike;
1008
+ /**
1009
+ * Kernel file-upload service (`ctx.fileUploads`).
1010
+ *
1011
+ * 为什么用它而不是内核那条 `/api/session/uploadFileBinary` HTTP 路由:那条路由
1012
+ * 只绑 loopback(手机根本到不了),而这个 cordis 服务就在同一个进程里,插件可直接调。
1013
+ */
1014
+ fileUploads: FileUploadsLike;
1015
+ /**
1016
+ * Cordis event bus of the host context. Used for the two facts the old
1017
+ * `apiProxy` event streams carried that no controller returns: session
1018
+ * registry changes and the approval answerer waterfall.
1019
+ */
1020
+ on(event: string, listener: (...args: any[]) => unknown): () => void;
1021
+ }
1022
+ //#endregion
1023
+ //#region src/host/rate-limit.d.ts
1024
+ declare class FixedWindowLimiter {
1025
+ private readonly limit;
1026
+ private readonly windowMs;
1027
+ private readonly buckets;
1028
+ constructor(limit: number, windowMs: number);
1029
+ consume(key: string): void;
1030
+ private sweep;
1031
+ }
1032
+ //#endregion
1033
+ //#region src/host/auth.d.ts
1034
+ declare class AuthService {
1035
+ private readonly database;
1036
+ private readonly authFailures;
1037
+ readonly pairingAttempts: FixedWindowLimiter;
1038
+ readonly requests: FixedWindowLimiter;
1039
+ constructor(database: WorkspaceDatabase);
1040
+ principal(req: IncomingMessage, options?: {
1041
+ allowAdmin?: boolean;
1042
+ }): Principal;
1043
+ requireAdmin(req: IncomingMessage): Principal;
1044
+ requireScope(principal: Principal, scope: DeviceScope): void;
1045
+ requireRoot(principal: Principal, rootId: string): void;
1046
+ }
1047
+ //#endregion
1048
+ //#region src/host/event-bus.d.ts
1049
+ declare class WorkspaceEventBus {
1050
+ private readonly listeners;
1051
+ emit(event: WorkspaceEvent): void;
1052
+ subscribe(listener: (event: WorkspaceEvent) => void): () => void;
1053
+ }
1054
+ //#endregion
1055
+ //#region src/host/file-service.d.ts
1056
+ interface DirectoryPage {
1057
+ path: string;
1058
+ entries: FileEntryView[];
1059
+ nextCursor?: string;
1060
+ }
1061
+ interface ContentDescriptor {
1062
+ absolutePath: string;
1063
+ size: number;
1064
+ modifiedAt: number;
1065
+ etag: string;
1066
+ contentType: string;
1067
+ }
1068
+ interface TrashView {
1069
+ id: string;
1070
+ rootId: string;
1071
+ path: string;
1072
+ kind: string;
1073
+ size: number;
1074
+ createdAt: number;
1075
+ status: string;
1076
+ }
1077
+ type EventSink = (event: WorkspaceEvent) => void;
1078
+ declare class FileService {
1079
+ private readonly database;
1080
+ private readonly emit;
1081
+ constructor(database: WorkspaceDatabase, emit: EventSink);
1082
+ list(root: StoredRoot, relativePath: string, limit?: number, cursor?: string): Promise<DirectoryPage>;
1083
+ inspectContent(root: StoredRoot, relativePath: string): Promise<ContentDescriptor>;
1084
+ streamContent(descriptor: ContentDescriptor, range?: {
1085
+ start: number;
1086
+ end: number;
1087
+ }): import("fs").ReadStream;
1088
+ writeContent(root: StoredRoot, relativePath: string, content: Uint8Array, conditions: {
1089
+ ifMatch?: string;
1090
+ ifNoneMatch?: string;
1091
+ }): Promise<{
1092
+ etag: string;
1093
+ size: number;
1094
+ modifiedAt: number;
1095
+ }>;
1096
+ createEntry(root: StoredRoot, relativePath: string, kind: 'file' | 'directory'): Promise<FileEntryView>;
1097
+ moveEntry(root: StoredRoot, sourcePath: string, destinationPath: string): Promise<void>;
1098
+ trashEntry(root: StoredRoot, relativePath: string): Promise<TrashView>;
1099
+ listTrash(rootIds?: ReadonlySet<string>): TrashView[];
1100
+ restoreTrash(root: StoredRoot, trashId: string): Promise<void>;
1101
+ purgeTrash(trashId: string): Promise<void>;
1102
+ private notify;
1103
+ }
1104
+ //#endregion
1105
+ //#region src/host/router.d.ts
1106
+ interface RemoteOperationView {
1107
+ id: string;
1108
+ state: 'pending' | 'succeeded' | 'failed';
1109
+ targetHost: string;
1110
+ targetPort: number;
1111
+ code?: string;
1112
+ message?: string;
1113
+ pairingCode?: string;
1114
+ pairingExpiresAt?: number;
1115
+ }
1116
+ interface RemoteControl {
1117
+ status(operationId?: string): {
1118
+ host: string;
1119
+ port: number;
1120
+ configuredHost: string;
1121
+ configuredPort: number;
1122
+ remoteEnabled: boolean;
1123
+ operation?: RemoteOperationView;
1124
+ };
1125
+ enable(rootIds: string[], scopes: DeviceScope[]): RemoteOperationView;
1126
+ disable(): RemoteOperationView;
1127
+ configure(host: string, port: number): RemoteOperationView;
1128
+ }
1129
+ interface RouterOptions {
1130
+ database: WorkspaceDatabase;
1131
+ auth: AuthService;
1132
+ files: FileService;
1133
+ chat: DshChatAdapter;
1134
+ settings: DshSettingsAdapter;
1135
+ remote: RemoteControl;
1136
+ maxUploadBytes: number;
1137
+ }
1138
+ declare class ApiRouter {
1139
+ private readonly options;
1140
+ constructor(options: RouterOptions);
1141
+ handle(req: IncomingMessage, res: ServerResponse, stripPrefix?: string): Promise<void>;
1142
+ private dispatch;
1143
+ private dispatchManagement;
1144
+ private dispatchEntries;
1145
+ private dispatchContent;
1146
+ private requireRoot;
1147
+ private authorizedRoots;
1148
+ private audit;
1149
+ private readIdempotent;
1150
+ private saveIdempotent;
1151
+ }
1152
+ //#endregion
1153
+ //#region src/host/server.d.ts
1154
+ interface RemoteApiServerOptions {
1155
+ port: number;
1156
+ remoteHost?: string;
1157
+ database: WorkspaceDatabase;
1158
+ auth: AuthService;
1159
+ chat: DshChatAdapter;
1160
+ events: WorkspaceEventBus;
1161
+ handle: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
1162
+ }
1163
+ declare class RemoteApiServer implements RemoteControl {
1164
+ private readonly options;
1165
+ private server;
1166
+ private host;
1167
+ private actualPort;
1168
+ private configuredHost;
1169
+ private configuredPort;
1170
+ private remoteEnabled;
1171
+ private readonly operations;
1172
+ private readonly sockets;
1173
+ private readonly webSockets;
1174
+ private readonly eventsAbort;
1175
+ private unsubscribeEvents;
1176
+ private broadcastQueue;
1177
+ private changing;
1178
+ constructor(options: RemoteApiServerOptions);
1179
+ start(): Promise<void>;
1180
+ stop(): Promise<void>;
1181
+ status(operationId?: string): {
1182
+ operation?: RemoteOperationView;
1183
+ host: string;
1184
+ port: number;
1185
+ configuredHost: string;
1186
+ configuredPort: number;
1187
+ remoteEnabled: boolean;
1188
+ };
1189
+ enable(rootIds: string[], scopes: DeviceScope[]): RemoteOperationView;
1190
+ disable(): RemoteOperationView;
1191
+ configure(host: string, port: number): RemoteOperationView;
1192
+ private scheduleChange;
1193
+ private performChange;
1194
+ private rebind;
1195
+ private bind;
1196
+ private createBoundServer;
1197
+ private closeCurrent;
1198
+ private handleUpgrade;
1199
+ private broadcast;
1200
+ private eventAllowed;
1201
+ private trimOperations;
1202
+ }
1203
+ declare function attachEmbeddedRoutes(webServer: {
1204
+ register(route: {
1205
+ kind: 'exact' | 'prefix';
1206
+ path: string;
1207
+ handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
1208
+ }): () => void;
1209
+ }, router: ApiRouter, remote: RemoteApiServer): () => void;
1210
+ //#endregion
1211
+ //#region src/index.d.ts
1212
+ /**
1213
+ * 插件身份。
1214
+ *
1215
+ * 2.0.4 起由 `dsh-workspace` 改名为 `dsh-remote-bridge`:插件实际承担的是
1216
+ * 「把 DSH 内核的私有 remote 协议翻译成版本化 REST,供手机端远程访问这台机器」,
1217
+ * 外加一个设置中心(根目录 / 远程访问 / 设备 / 回收站 / 审计)。原先的
1218
+ * 「workspace」只是其中一页,名字比内容宽。
1219
+ *
1220
+ * **以下一处刻意不改**,它是已发布客户端依赖的对外契约,改了等于破坏兼容:
1221
+ * - REST 前缀 `/dsh-workspace-api`(`src/host/server.ts`)
1222
+ *
1223
+ * 已发布的 App 把它写死了,改名只动「装的时候叫什么、bundle 叫什么」。
1224
+ *
1225
+ * 另外两项**已随 2.2.0 删除独立页一并退役**,不再是契约:独立页路径 `/dsh-workspace`
1226
+ * (页面已删)、浏览器存储键 `dsh-workspace-device-token`(只被那个页面读写过,现已无人使用)。
1227
+ * 另有一项契约不在此处:状态目录 `dshHomePath('dsh-workspace')`(在 `cordis.patch.yml`),
1228
+ * 换目录等于清空用户的授权根与回收站。
1229
+ */
1230
+ declare const name = "dsh-remote-bridge";
1231
+ /**
1232
+ * Host services this plugin needs, for DSH 0.2.x.
1233
+ *
1234
+ * DSH ≤ 0.1.1 published the client-shaped `apiProxy` facade from
1235
+ * `@deepseek-ai/dsh-host-apiproxy`. DSH 0.1.2-alpha.3 replaced that package with
1236
+ * `@deepseek-ai/dsh-api-gateway` (service `typertGateway`), which publishes no
1237
+ * `apiProxy` — the controllers instead register directly as host services.
1238
+ * Injecting those keeps the plugin loadable on 0.2.x, and
1239
+ * `./host/dsh-0.2-bridge.ts` rebuilds the old facade on top of them so the
1240
+ * chat/settings adapters keep their original contract.
1241
+ */
1242
+ declare const inject: string[];
1243
+ interface Config {
1244
+ dataDir?: string;
1245
+ port?: number;
1246
+ remoteHost?: string;
1247
+ maxUploadBytes?: number;
1248
+ }
1249
+ interface HostContext extends Dsh02HostServices {
1250
+ webServer: Parameters<typeof attachEmbeddedRoutes>[0];
1251
+ agents: NonNullable<ConstructorParameters<typeof DshChatAdapter>[2]>['agents'];
1252
+ commands: NonNullable<ConstructorParameters<typeof DshChatAdapter>[2]>['commands'];
1253
+ logger: {
1254
+ info(message: string): void;
1255
+ warn(error: Error): void;
1256
+ };
1257
+ effect(register: () => (() => void | Promise<void>), label?: string): void;
1258
+ }
1259
+ declare function apply(ctx: HostContext, config?: Config): Promise<void>;
1260
+ //#endregion
1261
+ export { Config, apply, apply as default, inject, name };