dsh-remote-bridge 2.7.1 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/api/asyncapi.yaml +591 -1
- package/docs/api/openapi.yaml +484 -2
- package/kotlin-sdk/src/main/kotlin/ai/deepseek/dsh/workspace/MobileGatewayClient.kt +437 -0
- package/lib/index.d.ts +316 -3
- package/lib/index.js +6113 -1248
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
2
3
|
//#region src/shared/contracts.d.ts
|
|
3
4
|
declare const DEVICE_SCOPES: readonly ["chat.read", "chat.write", "files.read", "files.write", "files.delete", "settings.read", "settings.write"];
|
|
4
5
|
type DeviceScope = (typeof DEVICE_SCOPES)[number];
|
|
@@ -1411,6 +1412,22 @@ interface DshApiProxy {
|
|
|
1411
1412
|
}): Promise<RpcResponse<{
|
|
1412
1413
|
selected: ModelSelectionView;
|
|
1413
1414
|
}>>;
|
|
1415
|
+
/**
|
|
1416
|
+
* 读会话投影基线(内核 `sessionController.projections`)。
|
|
1417
|
+
*
|
|
1418
|
+
* 供 agent-team / subagents 等只读投影的 WS 帧使用:投影里的 `agentTeam` /
|
|
1419
|
+
* `subagentCatalog` 键给出团队成员、任务与子会话目录。会话不存在时内核返回 null。
|
|
1420
|
+
*/
|
|
1421
|
+
projections(request: {
|
|
1422
|
+
rpcId: string;
|
|
1423
|
+
payload: {
|
|
1424
|
+
sessionId: string;
|
|
1425
|
+
};
|
|
1426
|
+
}): Promise<RpcResponse<{
|
|
1427
|
+
asOfSeq?: number;
|
|
1428
|
+
kind?: string;
|
|
1429
|
+
values: Record<string, unknown>;
|
|
1430
|
+
} | null>>;
|
|
1414
1431
|
};
|
|
1415
1432
|
workspace: {
|
|
1416
1433
|
list(request: {
|
|
@@ -1525,6 +1542,24 @@ interface DshApiProxy {
|
|
|
1525
1542
|
answer: UserQuestionAnswer;
|
|
1526
1543
|
};
|
|
1527
1544
|
}): Promise<RpcResponse<UserQuestionAnswerOutcome>>;
|
|
1545
|
+
/**
|
|
1546
|
+
* 取消本桥接管的前台提问(question-cancel)。内核 userQuestions 服务无 cancel,
|
|
1547
|
+
* 因此只能取消正阻塞 agent 的前台挂起项;带 timeout 的 continued 提问取消不了。
|
|
1548
|
+
*/
|
|
1549
|
+
cancel(request: {
|
|
1550
|
+
rpcId: string;
|
|
1551
|
+
payload: {
|
|
1552
|
+
sessionId: string;
|
|
1553
|
+
callId?: string;
|
|
1554
|
+
rpcId?: string;
|
|
1555
|
+
};
|
|
1556
|
+
}): Promise<RpcResponse<{
|
|
1557
|
+
accepted: true;
|
|
1558
|
+
rpcId: string;
|
|
1559
|
+
} | {
|
|
1560
|
+
accepted: false;
|
|
1561
|
+
reason: 'not-pending';
|
|
1562
|
+
}>>;
|
|
1528
1563
|
};
|
|
1529
1564
|
events: {
|
|
1530
1565
|
mux(request: {
|
|
@@ -1994,8 +2029,30 @@ declare class DshChatAdapter {
|
|
|
1994
2029
|
* 映成 404/409,其余一律 400 —— 表达不了这几种区别。
|
|
1995
2030
|
*/
|
|
1996
2031
|
answerUserQuestion(principal: Principal, sessionId: string, callId: string, answer: UserQuestionAnswer): Promise<void>;
|
|
2032
|
+
/**
|
|
2033
|
+
* 取消一条正挂在前台的用户提问(WS `question-cancel`)。
|
|
2034
|
+
*
|
|
2035
|
+
* 只能取消本桥接管、此刻正阻塞 agent 的前台提问;带 timeout 的 continued 提问
|
|
2036
|
+
* 内核没有取消通道。取消成功后事件总线会推 userQuestions/dismissed(见桥的竞速收尾),
|
|
2037
|
+
* 移动端据此关闭卡片。
|
|
2038
|
+
*/
|
|
2039
|
+
cancelUserQuestion(principal: Principal, sessionId: string, ref: {
|
|
2040
|
+
callId?: string;
|
|
2041
|
+
rpcId?: string;
|
|
2042
|
+
}): Promise<void>;
|
|
1997
2043
|
models(principal: Principal, sessionId: string): Promise<SessionModelsView>;
|
|
1998
2044
|
selectModel(principal: Principal, sessionId: string, selection: ModelSelectionView): Promise<ModelSelectionView>;
|
|
2045
|
+
/**
|
|
2046
|
+
* 读会话投影基线(agent-team / subagents 帧用)。
|
|
2047
|
+
*
|
|
2048
|
+
* 会话不存在(内核返回 null)映射成 404 SESSION_NOT_FOUND;取不到投影不打 500,
|
|
2049
|
+
* 由调用方按 PROTOCOL 回 projectionState:'unavailable'。这里只做鉴权 + 透传。
|
|
2050
|
+
*/
|
|
2051
|
+
sessionProjections(principal: Principal, sessionId: string): Promise<{
|
|
2052
|
+
asOfSeq?: number;
|
|
2053
|
+
kind?: string;
|
|
2054
|
+
values: Record<string, unknown>;
|
|
2055
|
+
}>;
|
|
1999
2056
|
listCommands(principal: Principal, sessionId: string): Promise<CommandDescriptorView[]>;
|
|
2000
2057
|
executeCommand(principal: Principal, sessionId: string, line: string): Promise<CommandExecutionView>;
|
|
2001
2058
|
isSessionAuthorized(principal: Principal, sessionId: string): Promise<boolean>;
|
|
@@ -2110,6 +2167,32 @@ declare class AuthService {
|
|
|
2110
2167
|
requireRoot(principal: Principal, rootId: string): void;
|
|
2111
2168
|
}
|
|
2112
2169
|
//#endregion
|
|
2170
|
+
//#region src/host/capabilities.d.ts
|
|
2171
|
+
/**
|
|
2172
|
+
* 运行时**不可用**的能力位:服务端此刻做不了这些事,就不该声明。
|
|
2173
|
+
*
|
|
2174
|
+
* ## 为什么必须过滤(这是本节最重要的一条)
|
|
2175
|
+
*
|
|
2176
|
+
* 能力位的全部意义是让客户端把「服务端不支持」与「本来就没有」分开
|
|
2177
|
+
* (见文件头的说明)。而 `jobController` 是**可选宿主服务**:0.2.0-rc.2 上没有它,
|
|
2178
|
+
* 三个 `/jobs` 端点在那套内核上根本不会被挂载(`index.ts` 直接不下发,路由返回 404)。
|
|
2179
|
+
* 若 `connection.ready` 仍然宣告 `job-list-v1`,客户端按位探测后就会去打一个必然
|
|
2180
|
+
* 404 的端点 —— 这恰好是这份清单本身想消灭的那种「静默少一个功能」。
|
|
2181
|
+
*
|
|
2182
|
+
* ## 为什么按「能力位 → 依赖的服务」映射,而不是在调用点手写过滤
|
|
2183
|
+
*
|
|
2184
|
+
* 声明与端点必须**同源**。把「哪个位依赖哪个可选服务」写在一处,将来再加一个
|
|
2185
|
+
* 可选服务只需要在这里补一行;散在调用点就会漂。
|
|
2186
|
+
*/
|
|
2187
|
+
/** 运行时能力开关。缺省(不传键)一律当作可用,见 {@link availableCapabilities}。 */
|
|
2188
|
+
interface CapabilityFlags {
|
|
2189
|
+
jobs?: boolean;
|
|
2190
|
+
/** 宿主 cordis 服务 `ctx.schedule` 在不在(`dsh-schedule` 插件未装时为 false)。 */
|
|
2191
|
+
schedule?: boolean;
|
|
2192
|
+
/** 内核 typert Gateway 在不在(`speech` 目录与转写都要经它)。 */
|
|
2193
|
+
speech?: boolean;
|
|
2194
|
+
}
|
|
2195
|
+
//#endregion
|
|
2113
2196
|
//#region src/host/event-bus.d.ts
|
|
2114
2197
|
declare class WorkspaceEventBus {
|
|
2115
2198
|
private readonly listeners;
|
|
@@ -2117,6 +2200,197 @@ declare class WorkspaceEventBus {
|
|
|
2117
2200
|
subscribe(listener: (event: WorkspaceEvent) => void): () => void;
|
|
2118
2201
|
}
|
|
2119
2202
|
//#endregion
|
|
2203
|
+
//#region src/host/ws-host-services.d.ts
|
|
2204
|
+
/** 供 WS 域处理器调用的远程服务句柄。 */
|
|
2205
|
+
interface HostRemoteHandle {
|
|
2206
|
+
invoke(namespace: string, method: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>;
|
|
2207
|
+
stream(namespace: string, method: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<AsyncIterable<unknown>>;
|
|
2208
|
+
}
|
|
2209
|
+
//#endregion
|
|
2210
|
+
//#region src/host/ws-handler.d.ts
|
|
2211
|
+
/**
|
|
2212
|
+
* `/ws/mobile` 的可调参数。
|
|
2213
|
+
*
|
|
2214
|
+
* 三个都有默认值,且**必须可注入**:默认值(32 / 30 s / 512)在单测里既等不起
|
|
2215
|
+
* (30 s 的心跳周期)也压不满(512 深度要发五百多帧)。可注入才能把「第 33 条 429」
|
|
2216
|
+
* 「两个周期踢人」「溢出补 resync」这三条**文档承诺的行为**变成可断言的用例。
|
|
2217
|
+
*/
|
|
2218
|
+
interface MobileGatewayOptions {
|
|
2219
|
+
/** 同时允许的连接数上限,默认 {@link DEFAULT_MOBILE_MAX_CONNECTIONS}。 */
|
|
2220
|
+
maxConnections?: number;
|
|
2221
|
+
/** 心跳周期(毫秒),默认 {@link DEFAULT_MOBILE_HEARTBEAT_INTERVAL_MS}。 */
|
|
2222
|
+
heartbeatIntervalMs?: number;
|
|
2223
|
+
/** 单连接发送队列深度,默认 {@link DEFAULT_MOBILE_QUEUE_LIMIT}。 */
|
|
2224
|
+
queueLimit?: number;
|
|
2225
|
+
}
|
|
2226
|
+
declare class MobileGatewayWsHandler {
|
|
2227
|
+
private readonly database;
|
|
2228
|
+
private readonly chat;
|
|
2229
|
+
private readonly auth;
|
|
2230
|
+
private readonly capabilitiesFlags;
|
|
2231
|
+
private readonly sockets;
|
|
2232
|
+
/**
|
|
2233
|
+
* 当前异步上下文里正在处理的那一帧客户端请求的 `requestId`。
|
|
2234
|
+
*
|
|
2235
|
+
* **为什么必须是 AsyncLocalStorage 而不是实例字段/闭包变量**:`webSocket.on('message')`
|
|
2236
|
+
* 里是 `void this.handleMessage(...)`(不 await),同一条连接可以同时有多个请求在途;
|
|
2237
|
+
* 任何「先存起来、发送时再读」的共享槽位都会把并发请求的 id 串到一起。ALS 把上下文
|
|
2238
|
+
* 绑定在**这条 await 链**上,`await` 穿过它、并发分支各持一份,互不可见。
|
|
2239
|
+
*/
|
|
2240
|
+
private readonly frameRequestId;
|
|
2241
|
+
private settingsService;
|
|
2242
|
+
private filesService;
|
|
2243
|
+
/**
|
|
2244
|
+
* 可选内核远程服务(typertGateway + owner peer)。存在时才启用定时任务与官方语音代理;
|
|
2245
|
+
* 缺失(老内核)时对应帧回结构化 unavailable,引导 REST 兜底。
|
|
2246
|
+
*/
|
|
2247
|
+
private hostRemote;
|
|
2248
|
+
/** 语音管理器:跨连接共享(全局转写并发计数需要进程级单一实例)。 */
|
|
2249
|
+
private speech;
|
|
2250
|
+
/** 文件分块下载/上传管理器:跨连接共享(每连接并发计数 + spool 落盘需要进程级状态)。 */
|
|
2251
|
+
private fileTransfer;
|
|
2252
|
+
/**
|
|
2253
|
+
* 装配全局模型目录与文件浏览依赖。这两个域需要 DshSettingsAdapter / FileService,
|
|
2254
|
+
* 它们在 index.ts 里晚于 RemoteApiServer 构造(router 装配期),故用 setter 注入,
|
|
2255
|
+
* 而不是塞进构造函数(避免 server↔router 的构造顺序环)。
|
|
2256
|
+
*/
|
|
2257
|
+
attachHostServices(services: {
|
|
2258
|
+
settings: MobileGatewayWsHandler['settingsService'];
|
|
2259
|
+
files: MobileGatewayWsHandler['filesService'];
|
|
2260
|
+
}): void;
|
|
2261
|
+
/**
|
|
2262
|
+
* 装配内核可选远程服务(定时任务 / 官方语音)。在 index.ts apply 期取到
|
|
2263
|
+
* typertGateway+connection 后注入;传入 undefined 表示当前内核不提供(优雅降级)。
|
|
2264
|
+
*/
|
|
2265
|
+
attachRemoteServices(remote: HostRemoteHandle | undefined): void;
|
|
2266
|
+
/** 同时允许的连接数上限(鉴权后计数;server.ts 在 Upgrade 前据此回 429)。 */
|
|
2267
|
+
private readonly maxConnections;
|
|
2268
|
+
/** 心跳周期:每周期 ping 一次,连续两个周期没回 pong 就 terminate。 */
|
|
2269
|
+
private readonly heartbeatIntervalMs;
|
|
2270
|
+
/** 单连接发送队列深度(溢出时先丢可重建帧)。 */
|
|
2271
|
+
private readonly queueLimit;
|
|
2272
|
+
/** 心跳定时器(首个连接接入时惰性启动,{@link stop} 时清掉)。 */
|
|
2273
|
+
private heartbeatTimer;
|
|
2274
|
+
constructor(database: WorkspaceDatabase, chat: DshChatAdapter, auth: AuthService, events: WorkspaceEventBus, capabilitiesFlags: CapabilityFlags, options?: MobileGatewayOptions);
|
|
2275
|
+
private readonly eventBridge;
|
|
2276
|
+
/**
|
|
2277
|
+
* 判定这次 upgrade 是否属于 /ws/mobile。server.ts 负责实际的 WebSocket 升级,
|
|
2278
|
+
* 升级完成后再把 WebSocket 交给 onConnection。
|
|
2279
|
+
*/
|
|
2280
|
+
ownsPath(req: IncomingMessage): boolean;
|
|
2281
|
+
/**
|
|
2282
|
+
* 对已经升级好的 WebSocket 做子协议鉴权、状态登记、帧分发。
|
|
2283
|
+
* 返回 false 表示不属于此处理器(由调用方决定后续,通常是 socket.destroy())。
|
|
2284
|
+
*/
|
|
2285
|
+
onConnection(webSocket: WebSocket, req: IncomingMessage): boolean;
|
|
2286
|
+
private handlePairingConnection;
|
|
2287
|
+
private handleTokenConnection;
|
|
2288
|
+
private acceptSocket;
|
|
2289
|
+
/**
|
|
2290
|
+
* 连接建立后补推待处理的审批/提问。逐连接按本连接身份做会话授权过滤(复用
|
|
2291
|
+
* chat.replayPending* 内部的 isSessionAuthorized),绝不因别的设备授权过就串推。
|
|
2292
|
+
* 失败静默:与 server.ts 同名方法一致,重放失败不应让连接崩掉(实时事件仍走桥)。
|
|
2293
|
+
*
|
|
2294
|
+
* @param onlySessionId - 给 subscribe 帧用:只补推该会话的待处理项;缺省补全部。
|
|
2295
|
+
*/
|
|
2296
|
+
private replayPendingInteractions;
|
|
2297
|
+
private sendHello;
|
|
2298
|
+
private stateOf;
|
|
2299
|
+
private principalToDeviceView;
|
|
2300
|
+
/**
|
|
2301
|
+
* 收到一条文本帧的入口:解码 → 校验 → **绑定 requestId 上下文** → 分发。
|
|
2302
|
+
*
|
|
2303
|
+
* 这里同时是唯一的异常兜底点。调用方是 `webSocket.on('message')` 里的
|
|
2304
|
+
* `void this.handleMessage(...)`(不 await),任何从这里冒出去的 rejection 都会成为
|
|
2305
|
+
* 进程级 unhandledRejection —— DSH 宿主装了 `installFailLoud`,直接 `proc.exit(1)`。
|
|
2306
|
+
* 而 `handleHistory` 等基础帧是直接 `await this.chat.*`,一次越权的 `sessionId` 就能抛
|
|
2307
|
+
* `ApiError(404, 'SESSION_NOT_FOUND')`。所以分发必须整体包在 try/catch 里。
|
|
2308
|
+
*/
|
|
2309
|
+
private handleMessage;
|
|
2310
|
+
/** 把一帧已解码、已校验的请求分发给对应的处理器(异常由 {@link handleMessage} 兜底)。 */
|
|
2311
|
+
private routeFrame;
|
|
2312
|
+
private handlePing;
|
|
2313
|
+
private handleSubscribe;
|
|
2314
|
+
private handleUnsubscribe;
|
|
2315
|
+
/**
|
|
2316
|
+
* 处理 `message` 帧:若未带 sessionId,先在用户首个授权根目录下建一个空会话;
|
|
2317
|
+
* 再把内容送进 chat.prompt。响应 `sent`,客户端后续通过 WS 的 `event` 帧
|
|
2318
|
+
* (或 REST /api/v1/events)拿回流式增量。
|
|
2319
|
+
*/
|
|
2320
|
+
private handleMessageFrame;
|
|
2321
|
+
private handleWorkspaces;
|
|
2322
|
+
private handleSessions;
|
|
2323
|
+
private handleHistory;
|
|
2324
|
+
private handleAttachment;
|
|
2325
|
+
/**
|
|
2326
|
+
* 向一条连接发一条帧(带背压队列)。
|
|
2327
|
+
*
|
|
2328
|
+
* 应答帧统一在这里回显 `requestId`:从 {@link frameRequestId} 取**当前异步上下文**的
|
|
2329
|
+
* requestId,交给 {@link withRequestIdEcho} 决定要不要塞进 `data`。处理器无需再各自
|
|
2330
|
+
* 复制一遍(已手写回显的处理器不受影响 —— 注入规则是「`data` 里已有就不覆盖」)。
|
|
2331
|
+
*/
|
|
2332
|
+
private sendFrame;
|
|
2333
|
+
/**
|
|
2334
|
+
* 压入发送队列,队列满时**先牺牲可重建的帧**。
|
|
2335
|
+
*
|
|
2336
|
+
* 与 `/api/v1/events`(server.ts 的 `enqueue`)同一套取舍,只是这里没有「可重建事件
|
|
2337
|
+
* 标记表」可用,改用 kind 判据。为什么不能无脑 `shift()` 丢最旧:最旧的那条可能正是
|
|
2338
|
+
* 某次请求的**应答**,丢掉它客户端就会一直等到超时(而它等的那个东西其实早成功了),
|
|
2339
|
+
* 反过来一条 `chat.message.delta` 丢了只影响一句的流式观感,history 一拉就齐。
|
|
2340
|
+
*
|
|
2341
|
+
* 被丢的帧数累加到 `state.dropped`,由 {@link drain} 在排空后补一条
|
|
2342
|
+
* `connection-resync-required` 告知客户端 —— **静默丢帧比丢帧本身更糟**。
|
|
2343
|
+
*/
|
|
2344
|
+
private enqueue;
|
|
2345
|
+
private sendError;
|
|
2346
|
+
/**
|
|
2347
|
+
* 把分发过程中冒出来的异常变成一条 `error` 帧(范式与各域处理器的 `reportError` 一致)。
|
|
2348
|
+
*
|
|
2349
|
+
* **这里的 try/catch 不是多余的**:`sendFrame` 第一行就是 `this.stateOf(webSocket)`,而
|
|
2350
|
+
* `teardown` 会在 `close`/`error` 时把连接从 `sockets` 摘掉。连接已断时 `stateOf` 抛
|
|
2351
|
+
* `ApiError(4001, 'NOT_AUTHENTICATED')` —— 在 catch 块里再抛一次,就等于没修。
|
|
2352
|
+
*/
|
|
2353
|
+
private reportFrameError;
|
|
2354
|
+
/**
|
|
2355
|
+
* 把队列里的帧逐条发出去,排空后按需补一条「你漏了 N 帧」的告知帧。
|
|
2356
|
+
*
|
|
2357
|
+
* 补发放在**排空之后**而不是溢出当场:溢出那一刻队列正满,插一条进去还得再挤掉别人;
|
|
2358
|
+
* 等排空再补,既不会二次溢出,也能把同一段时间内多次溢出合并成一条(`dropped` 累加)。
|
|
2359
|
+
*/
|
|
2360
|
+
private drain;
|
|
2361
|
+
/** 拒绝未鉴权连接(401 close code)。 */
|
|
2362
|
+
private closeUnauthorized;
|
|
2363
|
+
/** 用自定义 close code 关闭,并附简短理由。 */
|
|
2364
|
+
private closeWithCode;
|
|
2365
|
+
/** 关闭所有连接(server.stop 时调用)。 */
|
|
2366
|
+
stop(timeoutMs?: number): Promise<void>;
|
|
2367
|
+
/**
|
|
2368
|
+
* 心跳循环:每 `heartbeatIntervalMs` ping 一次,**连续错过两个周期**才 terminate。
|
|
2369
|
+
*
|
|
2370
|
+
* 为什么必须有这一步:`/ws/mobile` 是手机上的长连接,切网、熄屏、NAT 表项过期都会留下
|
|
2371
|
+
* **半开连接** —— 内核这边没有任何事件,TCP 要等自己超时才报错(默认以分钟计),期间
|
|
2372
|
+
* 这条连接一直占着 `sockets` 里的额度、每次广播还白查一遍库。API 文档承诺「连续错过
|
|
2373
|
+
* 2 个心跳周期 terminate」,这里就是把它兑现。
|
|
2374
|
+
*
|
|
2375
|
+
* 两个刻意的取舍:
|
|
2376
|
+
* - `terminate()` 而不是 `close()`:对端已经不在,优雅挥手没人收,只会多等一个超时;
|
|
2377
|
+
* `close`/`error` 里注册的 `teardown` 会照常摘表、摘事件桥、中止语音与文件传输;
|
|
2378
|
+
* - 存活标记用底层 `pong` 控制帧:客户端库自动回,不需要业务代码配合(业务层的
|
|
2379
|
+
* `ping` 帧是另一回事,用于测 RTT)。
|
|
2380
|
+
*/
|
|
2381
|
+
private startHeartbeat;
|
|
2382
|
+
private clearHeartbeat;
|
|
2383
|
+
/**
|
|
2384
|
+
* 连接数上限(供 server.ts 在 **Upgrade 之前**判定是否回 429)。
|
|
2385
|
+
*
|
|
2386
|
+
* 为什么要在 upgrade 前判:升级之后就只能发 WS 关闭码,`Retry-After` 根本无法表达,
|
|
2387
|
+
* 客户端也拿不到「稍后再试」这个语义(只会看到一次莫名其妙的断连)。
|
|
2388
|
+
*/
|
|
2389
|
+
connectionLimit(): number;
|
|
2390
|
+
/** 当前连接数(供 metrics / 观测)。 */
|
|
2391
|
+
connectionCount(): number;
|
|
2392
|
+
}
|
|
2393
|
+
//#endregion
|
|
2120
2394
|
//#region src/host/host-service-probe.d.ts
|
|
2121
2395
|
interface ServiceProbe {
|
|
2122
2396
|
name: string;
|
|
@@ -2480,6 +2754,12 @@ declare class FileService {
|
|
|
2480
2754
|
private notify;
|
|
2481
2755
|
}
|
|
2482
2756
|
//#endregion
|
|
2757
|
+
//#region src/host/bff-rest.d.ts
|
|
2758
|
+
/** 宿主 `ctx.schedule` 服务在本层只需要 `create`(远程面没有新建,见 `bff-schedule.ts` 文件头)。 */
|
|
2759
|
+
interface ScheduleCreatePort {
|
|
2760
|
+
create: (sessionId: string, request: Record<string, unknown>) => Promise<unknown>;
|
|
2761
|
+
}
|
|
2762
|
+
//#endregion
|
|
2483
2763
|
//#region src/host/router.d.ts
|
|
2484
2764
|
interface RemoteOperationView {
|
|
2485
2765
|
id: string;
|
|
@@ -2541,10 +2821,33 @@ interface RouterOptions {
|
|
|
2541
2821
|
* 可选:不注入则这些端点返回 404,测试不必每条都补一个字段。
|
|
2542
2822
|
*/
|
|
2543
2823
|
jobs?: JobAdapter;
|
|
2824
|
+
/**
|
|
2825
|
+
* 内核 typert Gateway 句柄:REST 的语音与定时任务读面(`bff-*.ts`)。
|
|
2826
|
+
*
|
|
2827
|
+
* 可选,且**缺席不是错误**:老内核上它本来就不存在,此时语音回
|
|
2828
|
+
* `200 {available:false}`、定时任务读面回 503 —— 与 `/ws/mobile` 帧的
|
|
2829
|
+
* `speech/unavailable`、`schedule/unavailable` 是**同一个**判据。
|
|
2830
|
+
*/
|
|
2831
|
+
hostRemote?: HostRemoteHandle;
|
|
2832
|
+
/**
|
|
2833
|
+
* 宿主 `ctx.schedule` 服务:只有它有 `create`(远程面没注册,见
|
|
2834
|
+
* `bff-schedule.ts` 文件头)。可选,缺席时 `POST /chat/schedules` 回 503。
|
|
2835
|
+
*/
|
|
2836
|
+
schedule?: ScheduleCreatePort;
|
|
2544
2837
|
}
|
|
2545
2838
|
declare class ApiRouter {
|
|
2546
2839
|
private readonly options;
|
|
2840
|
+
/**
|
|
2841
|
+
* REST 对等面(分块上传 / 定时任务 / 语音)的分发体。
|
|
2842
|
+
*
|
|
2843
|
+
* 构造在这里而不是 `index.ts`:它只依赖 `RouterOptions` 已有的东西
|
|
2844
|
+
* (`database.dataDir`、`chat`、`hostRemote`、`schedule`),多一个外部装配点
|
|
2845
|
+
* 只会多一条会漂的接线。
|
|
2846
|
+
*/
|
|
2847
|
+
private readonly bff;
|
|
2547
2848
|
constructor(options: RouterOptions);
|
|
2849
|
+
/** 停机清理(分块 spool)。由 `index.ts` 的生命周期钩子调用。 */
|
|
2850
|
+
dispose(): Promise<void>;
|
|
2548
2851
|
handle(req: IncomingMessage, res: ServerResponse, stripPrefix?: string): Promise<void>;
|
|
2549
2852
|
private dispatch;
|
|
2550
2853
|
private dispatchManagement;
|
|
@@ -2650,9 +2953,7 @@ interface RemoteApiServerOptions {
|
|
|
2650
2953
|
* 为什么在构造参数里而不是全局变量:能力位是**这台服务端此刻**能做什么,
|
|
2651
2954
|
* 同一进程里可能有多个实例(测试、重绑),用实例参数才不会被互相污染。
|
|
2652
2955
|
*/
|
|
2653
|
-
capabilities?:
|
|
2654
|
-
jobs?: boolean;
|
|
2655
|
-
};
|
|
2956
|
+
capabilities?: CapabilityFlags;
|
|
2656
2957
|
}
|
|
2657
2958
|
declare class RemoteApiServer implements RemoteControl {
|
|
2658
2959
|
private readonly options;
|
|
@@ -2665,6 +2966,8 @@ declare class RemoteApiServer implements RemoteControl {
|
|
|
2665
2966
|
private readonly operations;
|
|
2666
2967
|
private readonly sockets;
|
|
2667
2968
|
private readonly webSockets;
|
|
2969
|
+
/** 移动端网关 WebSocket(/ws/mobile):与 /api/v1/events 并存,WS 优先、REST 兜底。 */
|
|
2970
|
+
private readonly wsMobile;
|
|
2668
2971
|
private readonly eventsAbort;
|
|
2669
2972
|
private unsubscribeEvents;
|
|
2670
2973
|
/** 所有连接的 drain 完成信号;`Promise.all` 它们即可等排空。 */
|
|
@@ -2677,6 +2980,16 @@ declare class RemoteApiServer implements RemoteControl {
|
|
|
2677
2980
|
private readonly highWaterMarkBytes;
|
|
2678
2981
|
private readonly heartbeatIntervalMs;
|
|
2679
2982
|
constructor(options: RemoteApiServerOptions);
|
|
2983
|
+
/**
|
|
2984
|
+
* 注入全局模型目录与文件浏览依赖(index.ts 在 router 装配期调用)。
|
|
2985
|
+
* 这两个域依赖晚于本类构造的 DshSettingsAdapter / FileService,故走 setter 而非构造参数。
|
|
2986
|
+
*/
|
|
2987
|
+
attachMobileHostServices(services: Parameters<MobileGatewayWsHandler['attachHostServices']>[0]): void;
|
|
2988
|
+
/**
|
|
2989
|
+
* 注入内核可选远程服务(typertGateway + owner peer),用于定时任务与官方语音 WS 代理。
|
|
2990
|
+
* 传入 undefined(老内核无这两个服务)时相关帧回结构化 unavailable,引导 REST 兜底。
|
|
2991
|
+
*/
|
|
2992
|
+
attachMobileRemoteServices(remote: Parameters<MobileGatewayWsHandler['attachRemoteServices']>[0]): void;
|
|
2680
2993
|
start(): Promise<void>;
|
|
2681
2994
|
stop(): Promise<void>;
|
|
2682
2995
|
status(operationId?: string): {
|