@deepseek-ai/dsh-client-connection 0.0.1-rc.2 → 0.0.1-rc.3

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/connection/README.md
5
- README.md: a82bb55ab65df2732ad16248d2cc9aa15b60e94d
6
- README.zh.md: d4c19db8e74e3562ec55b5ef60675aef79b82008
5
+ README.md: 3315552dd0400697d5a96639598c5f5bb2bb4f5b
6
+ README.zh.md: 0dc251f1e5410bd0d74ef450c6d8df3dc9cdab81
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared api client + current-page loopback state + observable generation-scoped `hostDescription` + single-consumer stream-loop starter); the export face carries the wire contract types, the `AbstractApiClient` abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact `host.describe` value before `onConnected`; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for `events.mux` and `events.host`; the in-process carrier satisfies the same two-stream abstraction. The Host half owns the single `/api` route and its Fetch bridge; a registered TypeRT interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the `/api` Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived `ctx.connection.isLoopback` state. The node half's `/api` route pins the privileged method set (`host.pickDirectory`, `host.openPath`, and the whole configuration plane — `settings.describe`/`openDocument`/`update`/`replace`/`mutate` and `credentials.describe`/`set`/`unset`; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, `agentPreset.read`/`copy`/`openDocument`/`remove`, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); `agentPreset.list` and `agentPreset.select` stay out — the roster carries only ids and trust, and choosing a preset grants nothing `session.create`'s own `agentPreset` did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared `trustedHosts` authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the [WebSocket downlink carrier Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md).
5
+ Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared api client + current-page loopback state + observable generation-scoped `hostDescription` + single-consumer stream-loop starter); the export face carries the wire contract types, the `AbstractApiClient` abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact `host.describe` value before `onConnected`; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for `events.mux` and `events.host`; the in-process carrier satisfies the same two-stream abstraction. The Host half owns the single `/api` route and its Fetch bridge; a registered Typert interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the `/api` Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived `ctx.connection.isLoopback` state. The node half's `/api` route pins the privileged method set (`host.pickDirectory`, `host.openPath`, and the whole configuration plane — `settings.describe`/`openDocument`/`update`/`replace`/`mutate` and `credentials.describe`/`set`/`unset`; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, `agentPreset.read`/`copy`/`openDocument`/`remove`, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); `agentPreset.list` and `agentPreset.select` stay out — the roster carries only ids and trust, and choosing a preset grants nothing `session.create`'s own `agentPreset` did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared `trustedHosts` authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the [WebSocket downlink carrier Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md).
6
6
 
7
7
  ## /api browser-trust fence
8
8
 
@@ -23,3 +23,4 @@ None; this package neither assembles nor sends a provider request.
23
23
  ## Known Limitations and Deferred Work
24
24
 
25
25
  - **History resumes an unattached session** — opening history may create the host-side agent and add latency to the first open; there is no persistence-only read path.
26
+ - **The `/api` bridge buffers each request body in memory** — `maxRequestBodyBytes` (default 160 MiB, sized for the default 100 MiB aggregate image limit after base64 expansion plus envelope headroom) is therefore also the per-request resident bound; a streaming body path would be needed to lower it without shrinking the image limits.
package/README.zh.md CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 协议消费层:客户端插件的 apply 会挂载 `ctx.connection`(共享 API 客户端 + 当前页面的 loopback 状态 + 可观察且按 generation 生效的 `hostDescription` + 单消费方流循环启动器);导出表层携带协议约定类型、`AbstractApiClient` 抽象,以及循环的 sink/配置类型。每次就绪握手成功后,都会在 `onConnected` 之前发布完整的 `host.describe` 值;generation 失效或显式 stop 会清空它,因此原生能力消费者不会保留已经断线的判断。浏览器载体以 HTTP POST 发送 unary/respond,并为 `events.mux` 与 `events.host` 各开一条只下行的 WebSocket;进程内载体满足同一双流抽象。Host half 持有唯一 `/api` route 及其 Fetch bridge;已注册的 TypeRT interceptor 会先认领自己的 Remote endpoint,未认领请求再回退 API Proxy。Loopback hostname 判定逻辑留在包内部:`/api` Host fence 与 WebSocket upgrade 会直接使用它,其他客户端插件则消费派生的 `ctx.connection.isLoopback` 状态。node 半侧的 `/api` 路由让特权方法集(`host.pickDirectory`、`host.openPath`,以及整个配置面——`settings.describe`/`openDocument`/`update`/`replace`/`mutate` 与 `credentials.describe`/`set`/`unset`;读取与原生操作也在内,因为 describe 会返回已暴露的配置、打开操作会作用于 Host 桌面,而探测任意引用会报出某条凭据来自何处——以及 agent preset 的创作面 `agentPreset.read`/`copy`/`openDocument`/`remove`,因为组装指明了一个会话所运行的插件,读取它是侦察,而 copy/remove/openDocument 管理名单并驱动宿主桌面(创作只有复制一种写入,因此这些方法都不接收组装文本或路径);`agentPreset.list` 与 `agentPreset.select` 不在其中——名单只携带 id 与信任级别,而选择一个 preset 并不比 `session.create` 自带的 `agentPreset` 多给任何能力,何况默认 preset 本就带着 bash)以空信任表过信任 fence,从而钉在回环——已声明的 `trustedHosts` 授权可达其余全部方法,而这些方法在真正的认证层出现之前仍只限回环本机。平台载体与 ConnectionController 循环属于包内部;apply 负责选择并驱动它们。下行边界见 [WebSocket 下行载体 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md)。
5
+ 协议消费层:客户端插件的 apply 会挂载 `ctx.connection`(共享 API 客户端 + 当前页面的 loopback 状态 + 可观察且按 generation 生效的 `hostDescription` + 单消费方流循环启动器);导出表层携带协议约定类型、`AbstractApiClient` 抽象,以及循环的 sink/配置类型。每次就绪握手成功后,都会在 `onConnected` 之前发布完整的 `host.describe` 值;generation 失效或显式 stop 会清空它,因此原生能力消费者不会保留已经断线的判断。浏览器载体以 HTTP POST 发送 unary/respond,并为 `events.mux` 与 `events.host` 各开一条只下行的 WebSocket;进程内载体满足同一双流抽象。Host half 持有唯一 `/api` route 及其 Fetch bridge;已注册的 Typert interceptor 会先认领自己的 Remote endpoint,未认领请求再回退 API Proxy。Loopback hostname 判定逻辑留在包内部:`/api` Host fence 与 WebSocket upgrade 会直接使用它,其他客户端插件则消费派生的 `ctx.connection.isLoopback` 状态。node 半侧的 `/api` 路由让特权方法集(`host.pickDirectory`、`host.openPath`,以及整个配置面——`settings.describe`/`openDocument`/`update`/`replace`/`mutate` 与 `credentials.describe`/`set`/`unset`;读取与原生操作也在内,因为 describe 会返回已暴露的配置、打开操作会作用于 Host 桌面,而探测任意引用会报出某条凭据来自何处——以及 agent(智能体) preset 的创作面 `agentPreset.read`/`copy`/`openDocument`/`remove`,因为组装指明了一个会话所运行的插件,读取它是侦察,而 copy/remove/openDocument 管理名单并驱动宿主桌面(创作只有复制一种写入,因此这些方法都不接收组装文本或路径);`agentPreset.list` 与 `agentPreset.select` 不在其中——名单只携带 id 与信任级别,而选择一个 preset 并不比 `session.create` 自带的 `agentPreset` 多给任何能力,何况默认 preset 本就带着 bash)以空信任表过信任 fence,从而钉在回环——已声明的 `trustedHosts` 授权可达其余全部方法,而这些方法在真正的认证层出现之前仍只限回环本机。平台载体与 ConnectionController 循环属于包内部;apply 负责选择并驱动它们。下行边界见 [WebSocket 下行载体 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md)。
6
6
 
7
7
  ## /api 浏览器信任栅栏
8
8
 
9
- node 半侧在桥接或 upgrade 前守卫 `/api` 下的每个入口(`src/api-request-trust.ts`)。每个请求——无论是否带浏览器标记——`Host` 都必须是回环地址权威,或与某个 `trustedHosts` 条目匹配:带端口的 `host:port` 条目精确匹配,不带端口的条目匹配任意端口,两侧均经 WHATWG 归一化后比较(DNS rebinding 防御)。刻意不为无浏览器标记的 HTTP 请求开捷径:明文 HTTP 下浏览器的图片与导航读取既不带 `Origin` 也不带 Fetch-Metadata,因此无标记请求仍可能是被重绑页面发起的、响应可被读走的读取,而 Host 是重绑唯一伪造不了的请求头;WebSocket 浏览器握手会带 `Origin` 并通过同一道比较。非浏览器客户端经由回环地址、CLI 推导的 LAN IP 字面量或已声明的权威通过同一道栅栏。当标记存在时,`Origin` 必须与 Host 权威完全一致;显式的 `sec-fetch-site: cross-site` 标记一律拒绝。不是纯的、规范形 `host[:port]` 权威的 `trustedHosts` 条目——即 WHATWG 解析读回后与原文不完全一致的——会让插件加载明确报错:否则解析会悄悄授权 `harness.internal/path` 这类笔误里的 hostname,或把悬空冒号、补零端口放大成任意端口授权。HTTP 失败在任何 RPC 分发之前以纯 403 应答,upgrade 失败在启动任何 event stream 前拒绝握手。因此非回环(`--host 0.0.0.0`)部署需要让自己的服务权威被信任:dsh CLI 会自行推导本机的 LAN IP 字面量,其 `--trusted-host` flag 用于声明具名权威,所以 cordis.yml 中的 `trustedHosts` 面向 CLI 不参与引导的组合。这道栅栏是可达性策略,而不是认证;Web 载体不提供认证层。决策记录:[api 浏览器信任边界 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md)。
9
+ node 半侧在桥接或 upgrade 前守卫 `/api` 下的每个入口(`src/api-request-trust.ts`)。每个请求——无论是否带浏览器标记——`Host` 都必须是回环地址权威,或与某个 `trustedHosts` 条目匹配:带端口的 `host:port` 条目精确匹配,不带端口的条目匹配任意端口,两侧均经 WHATWG 归一化后比较(DNS rebinding 防御)。刻意不为无浏览器标记的 HTTP 请求开捷径:明文 HTTP 下浏览器的图片与导航读取既不带 `Origin` 也不带 Fetch-Metadata,因此无标记请求仍可能是被重绑页面发起的、响应可被读走的读取,而 Host 是重绑唯一伪造不了的请求头;WebSocket 浏览器握手会带 `Origin` 并通过同一道比较。非浏览器客户端经由回环地址、CLI(命令行界面)推导的 LAN IP 字面量或已声明的权威通过同一道栅栏。当标记存在时,如附带 `Origin`,则它必须与 Host 权威完全一致;显式的 `sec-fetch-site: cross-site` 标记一律拒绝。不是纯的、规范形 `host[:port]` 权威的 `trustedHosts` 条目——即 WHATWG 解析读回后与原文不完全一致的——会让插件加载明确报错:否则解析会悄悄授权 `harness.internal/path` 这类笔误里的 hostname,或把悬空冒号、补零端口放大成任意端口授权。HTTP 失败在任何 RPC 分发之前以纯 403 应答,upgrade 失败在启动任何事件流前拒绝握手。因此非回环(`--host 0.0.0.0`)部署需要让自己的服务权威被信任:dsh CLI 会自行推导本机的 LAN IP 字面量,其 `--trusted-host` flag 用于声明具名权威,所以 cordis.yml 中的 `trustedHosts` 面向 CLI 不参与引导的组合。这道栅栏是可达性策略,而不是认证;Web 载体不提供认证层。决策记录:[api 浏览器信任边界 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md)。
10
10
 
11
11
  ## `/api` WebSocket 下行
12
12
 
13
- `/api/events.mux` 与 `/api/events.host` 各接受一条 WebSocket upgrade,并只向浏览器发送对应的 `ServerRequest` text message;客户端不会在这些 socket 上发送业务数据。任一 socket 结束都会使当前 connection generation 失败并重建两条流,连接就绪仍要求两条 socket open `host.describe` HTTP 调用成功。Host teardown 会终止两条 socket、中止各自的 source,并等待 source 清理完成后再返回。普通网络 GET 这些路径会返回 426,不保留 SSE 回退;`toFetchHandler` 的 SSE 编解码只服务进程内同构载体。
13
+ `/api/events.mux` 与 `/api/events.host` 各接受一条 WebSocket upgrade,并只向浏览器发送对应的 `ServerRequest` 文本消息;客户端不会在这些 socket 上发送业务数据。任一 socket 结束都会使当前 connection generation 失败并重建两条流,连接就绪仍要求两条 socket 均已打开且 `host.describe` HTTP 调用成功。Host teardown 会终止两条 socket、中止各自的 source,并等待 source 清理完成后再返回。普通网络 GET 这些路径会返回 426,不保留 SSE(Server-Sent Events)回退;`toFetchHandler` 的 SSE 编解码只服务进程内同构载体。
14
14
 
15
15
  ## 模型体验
16
16
 
@@ -23,3 +23,4 @@ node 半侧在桥接或 upgrade 前守卫 `/api` 下的每个入口(`src/api-r
23
23
  ## 已知限制与暂缓事项
24
24
 
25
25
  - **History 会恢复未附加的会话**:打开 history 可能创建宿主侧 agent,并增加首次打开的延迟;没有仅从持久化读取的路径。
26
+ - **`/api` 桥把每个请求体整体缓冲在内存里**:`maxRequestBodyBytes`(默认 160 MiB,按默认 100 MiB 图片总量上限经 base64 膨胀加信封余量得出)因此同时是单请求的驻留内存上界;要降低它而不缩小图片限额,需要流式请求体路径。
package/lib/client.js CHANGED
@@ -145,7 +145,18 @@ window.__ModuleLoader__.load({
145
145
  }
146
146
  };
147
147
  //#endregion
148
- //#region ../../llm/llm/src/brand.ts
148
+ //#region ../../llm/llm/lib/types/brand.js
149
+ /**
150
+ * dsh-llm's owned branded ids: tool-call correlation and provider request
151
+ * diagnostics.
152
+ *
153
+ * The `Branded<B>` primitive itself lives in `@deepseek-ai/dsh-brand` (a
154
+ * zero-dependency type-only package) so every owner of a cross-boundary id can
155
+ * brand it without depending on dsh-llm; see that package's README for the
156
+ * nominal-typing policy.
157
+ *
158
+ * @module @deepseek-ai/dsh-llm/brand
159
+ */
149
160
  /**
150
161
  * Brand a message identifier.
151
162
  * @param id - the opaque message identifier.
@@ -163,7 +174,7 @@ window.__ModuleLoader__.load({
163
174
  return id;
164
175
  }
165
176
  //#endregion
166
- //#region ../../llm/llm/src/call-config.ts
177
+ //#region ../../llm/llm/lib/types/call-config.js
167
178
  /**
168
179
  * Deep-freeze a value in place with an iterative traversal, guarding cycles,
169
180
  * so later mutation throws without imposing a JavaScript call-stack depth cap.
@@ -210,7 +221,7 @@ window.__ModuleLoader__.load({
210
221
  return value;
211
222
  }
212
223
  //#endregion
213
- //#region ../../llm/llm/src/message.ts
224
+ //#region ../../llm/llm/lib/types/message.js
214
225
  /** Message value types, identity, and immutable construction helpers. */
215
226
  /**
216
227
  * Detach and deep-freeze a message whose identity already exists.
@@ -276,8 +287,33 @@ window.__ModuleLoader__.load({
276
287
  }]
277
288
  });
278
289
  }
290
+ /**
291
+ * Whether a stream chunk carries visible model output (the first-token
292
+ * boundary shared by client step timing and the whole-log sessionStats
293
+ * projection). Empty deltas (heartbeats, empty tool-call frames) do not count
294
+ * as a first token.
295
+ * @param chunk - the stream chunk to test.
296
+ * @returns true when the chunk contains a non-empty text/reasoning/tool delta.
297
+ */
298
+ function isTokenDelta(chunk) {
299
+ switch (chunk.type) {
300
+ case "text-delta":
301
+ case "reasoning-delta": return chunk.text !== "";
302
+ case "tool-call-delta": return chunk.argumentsDelta !== "" || chunk.name !== void 0;
303
+ default: return false;
304
+ }
305
+ }
279
306
  //#endregion
280
- //#region ../../core/session/src/surface.ts
307
+ //#region ../../core/session/lib/types/surface.js
308
+ /**
309
+ * Surface layer on top of the session event log: an ordered view of events
310
+ * that produce LLM messages. The append-only log remains the source of truth.
311
+ *
312
+ * Browser-safe: web clients consume this subpath export, so it must stay free
313
+ * of `node:` imports (they break the vite bundle).
314
+ *
315
+ * @module @deepseek-ai/dsh-session/surface
316
+ */
281
317
  /** Runtime counterpart of the message-producing event union. */
282
318
  const SURFACE_EVENT_TYPES = new Set([
283
319
  "user/message",
@@ -484,7 +520,13 @@ window.__ModuleLoader__.load({
484
520
  };
485
521
  }
486
522
  //#endregion
487
- //#region ../../host/apiproxy/src/api/rpc.ts
523
+ //#region ../../host/apiproxy/lib/types/api/rpc.js
524
+ /**
525
+ * Four-quadrant RPC message model. Channels and messages are decoupled: HTTP,
526
+ * WebSocket, and in-process SSE are physical carriers, while logical messages
527
+ * are channel-independent and form a four-member discriminated union.
528
+ * api/ contract layer: zero Node dependencies, importable from the browser.
529
+ */
488
530
  /**
489
531
  * Brands a string as RpcId (same precedent as core `SessionId()`). Minted by the initiator:
490
532
  * client-request → client mints; server-request → host mints (answerable frames get a stable
@@ -4837,7 +4879,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4837
4879
  return /* @__PURE__ */ _superRefine(fn, params);
4838
4880
  }
4839
4881
  //#endregion
4840
- //#region ../../host/apiproxy/src/api/rpc.schema.ts
4882
+ //#region ../../host/apiproxy/lib/types/api/rpc.schema.js
4841
4883
  /**
4842
4884
  * Message-layer zod schemas: the four wire full forms + error body +
4843
4885
  * carrier receipt. The payload slot is unknown in the full-form schemas — business payloads
@@ -5127,7 +5169,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5127
5169
  const serverResponseSchema = object({
5128
5170
  type: literal("server-response"),
5129
5171
  rpcId: rpcIdSchema,
5130
- result: rpcResultSchema(unknown())
5172
+ result: rpcResultSchema(unknown().optional())
5131
5173
  });
5132
5174
  /** ServerRequest full form (payload stays wide). */
5133
5175
  const serverRequestSchema = object({
@@ -5143,7 +5185,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5143
5185
  object({
5144
5186
  type: literal("client-response"),
5145
5187
  rpcId: rpcIdSchema,
5146
- result: rpcResultSchema(unknown())
5188
+ result: rpcResultSchema(unknown().optional())
5147
5189
  })
5148
5190
  ]);
5149
5191
  /** Carrier receipt schema. */
@@ -5152,7 +5194,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5152
5194
  reason: union([literal("not-pending"), literal("bad-response")])
5153
5195
  })]);
5154
5196
  //#endregion
5155
- //#region ../../host/apiproxy/src/api/session-search.ts
5197
+ //#region ../../host/apiproxy/lib/types/api/session-search.js
5156
5198
  /**
5157
5199
  * Return the longest prefix containing at most `maximum` Unicode code points.
5158
5200
  * @param value - text to bound.
@@ -5170,7 +5212,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5170
5212
  return value;
5171
5213
  }
5172
5214
  //#endregion
5173
- //#region ../../host/apiproxy/src/api/sessions.schema.ts
5215
+ //#region ../../host/apiproxy/lib/types/api/sessions.schema.js
5174
5216
  /**
5175
5217
  * sessions domain zod schemas (names derived from map keys: sessionListRequestSchema /
5176
5218
  * sessionListValueSchema). SessionEvent passthrough = strict envelope (type/seq/time) + wide
@@ -5314,6 +5356,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5314
5356
  asOfSeq: number().int().min(-1),
5315
5357
  values: record(string(), unknown())
5316
5358
  });
5359
+ object({
5360
+ maxImageBytes: number().int().positive(),
5361
+ maxImagesPerMessage: number().int().positive(),
5362
+ maxMessageImageBytes: number().int().positive(),
5363
+ maxImagePixels: number().int().positive(),
5364
+ mediaTypes: array(string())
5365
+ });
5317
5366
  /** session.history response value (projections rides the tail page only). */
5318
5367
  const sessionHistoryValueSchema = object({
5319
5368
  events: array(historyEntrySchema),
@@ -5407,7 +5456,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5407
5456
  /** session.cancel response value. */
5408
5457
  const sessionCancelValueSchema = object({ accepted: literal(true) });
5409
5458
  //#endregion
5410
- //#region ../../host/apiproxy/src/api/approvals.schema.ts
5459
+ //#region ../../host/apiproxy/lib/types/api/approvals.schema.js
5411
5460
  /**
5412
5461
  * approvals domain zod schemas (respond is a client-response; the payload schema serves
5413
5462
  * the /api/respond endpoint's second parse after routing via the pending table).
@@ -5441,7 +5490,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5441
5490
  finishedAt: number().int().nonnegative().optional()
5442
5491
  });
5443
5492
  //#endregion
5444
- //#region ../../host/apiproxy/src/api/workspace.schema.ts
5493
+ //#region ../../host/apiproxy/lib/types/api/workspace.schema.js
5445
5494
  /**
5446
5495
  * workspace domain zod schemas (names derived from map keys). The
5447
5496
  * WorkspaceId brand cast lives in sessions.schema (see the note there) and
@@ -5477,6 +5526,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5477
5526
  object({ workspaceId: workspaceIdSchema });
5478
5527
  /** workspace.delete response value. */
5479
5528
  const workspaceDeleteValueSchema = object({ deleted: literal(true) });
5529
+ object({
5530
+ workspaceId: workspaceIdSchema,
5531
+ beforeWorkspaceId: workspaceIdSchema.optional()
5532
+ });
5533
+ /** workspace.insertBefore response value: the complete durable display order. */
5534
+ const workspaceInsertBeforeValueSchema = object({ workspaceIds: array(workspaceIdSchema) });
5480
5535
  object({
5481
5536
  workspaceId: workspaceIdSchema,
5482
5537
  sessionId: sessionIdSchema,
@@ -5488,13 +5543,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5488
5543
  /** workspace.archiveSession response value: the full updated archive set. */
5489
5544
  const workspaceArchiveSessionValueSchema = object({ archivedSessionIds: array(sessionIdSchema) });
5490
5545
  //#endregion
5491
- //#region ../../host/apiproxy/src/api/events.schema.ts
5546
+ //#region ../../host/apiproxy/lib/types/api/events.schema.js
5492
5547
  /**
5493
5548
  * events domain zod schemas: MuxFrame / HostFrame unions (discriminatedUnion('type')).
5494
5549
  * A frame is the payload slot of the ServerRequest full form; the SessionEvent inside
5495
5550
  * a session/event frame reuses sessions.schema's strict-envelope + wide-data passthrough branch.
5496
5551
  */
5497
- /** Question fields validated strictly against core dsh-user-interaction. */
5552
+ /** Question fields validated strictly against core dsh-user-questions. */
5498
5553
  const askUserQuestionItemSchema = object({
5499
5554
  id: string(),
5500
5555
  question: string(),
@@ -5578,9 +5633,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5578
5633
  }))
5579
5634
  }),
5580
5635
  object({
5581
- type: literal("session/tasks"),
5636
+ type: literal("session/jobs"),
5582
5637
  sessionId: sessionIdSchema,
5583
- tasks: array(taskViewSchema)
5638
+ jobs: array(taskViewSchema)
5584
5639
  }),
5585
5640
  object({
5586
5641
  type: literal("session/projection"),
@@ -5627,6 +5682,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5627
5682
  type: literal("host/workspace-removed"),
5628
5683
  workspaceId: workspaceIdSchema
5629
5684
  }),
5685
+ object({
5686
+ type: literal("host/workspace-order-changed"),
5687
+ workspaceIds: array(workspaceIdSchema)
5688
+ }),
5630
5689
  object({
5631
5690
  type: literal("host/archived-sessions-changed"),
5632
5691
  archivedSessionIds: array(sessionIdSchema)
@@ -5679,34 +5738,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5679
5738
  /** host.openPath response value. */
5680
5739
  const hostOpenPathValueSchema = object({ opened: literal(true) });
5681
5740
  //#endregion
5682
- //#region ../../host/apiproxy/src/api/commands.schema.ts
5683
- /**
5684
- * commands domain zod schemas (names derived from map keys: commandListRequestSchema /
5685
- * commandListValueSchema / commandExecuteRequestSchema / commandExecuteValueSchema).
5686
- */
5687
- /** CommandDescriptor row of command.list. */
5688
- const commandDescriptorSchema = object({
5689
- name: string().min(1),
5690
- description: string(),
5691
- input: object({ hint: string() }).optional()
5692
- });
5693
- object({ sessionId: sessionIdSchema });
5694
- /** command.list response value. */
5695
- const commandListValueSchema = object({ commands: array(commandDescriptorSchema) });
5696
- object({
5697
- sessionId: sessionIdSchema,
5698
- line: string()
5699
- });
5700
- /** CommandId: one brand cast after schema validation (the only cast point in this domain). */
5701
- const commandIdSchema = string().min(1);
5702
- /** command.execute response value: pure admission — outcomes ride the logged
5703
- * lifecycle events; commandId (present exactly when matched) correlates with them. */
5704
- const commandExecuteValueSchema = object({
5705
- matched: boolean(),
5706
- commandId: commandIdSchema.optional()
5707
- });
5708
- //#endregion
5709
- //#region ../../host/apiproxy/src/api/skills.schema.ts
5741
+ //#region ../../host/apiproxy/lib/types/api/skills.schema.js
5710
5742
  /**
5711
5743
  * skills domain zod schemas (names derived from map keys: skillListRequestSchema /
5712
5744
  * skillListValueSchema).
@@ -5722,7 +5754,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5722
5754
  /** skill.list response value. */
5723
5755
  const skillListValueSchema = object({ skills: array(skillEntrySchema) });
5724
5756
  //#endregion
5725
- //#region ../../host/apiproxy/src/api/agent-presets.schema.ts
5757
+ //#region ../../host/apiproxy/lib/types/api/agent-presets.schema.js
5726
5758
  /**
5727
5759
  * agent-presets domain zod schemas (names derived from map keys:
5728
5760
  * agentPresetListRequestSchema / agentPresetListValueSchema).
@@ -5775,7 +5807,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5775
5807
  /** agentPreset.remove response value. */
5776
5808
  const agentPresetRemoveValueSchema = object({});
5777
5809
  //#endregion
5778
- //#region ../../host/apiproxy/src/api/goals.schema.ts
5810
+ //#region ../../host/apiproxy/lib/types/api/goals.schema.js
5779
5811
  /**
5780
5812
  * goals domain zod schemas. Mutation-only shapes: every value schema is a
5781
5813
  * `{ ref }` acknowledgement (clear: `{ cleared }`) — the current goal state
@@ -5828,7 +5860,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5828
5860
  /** goal.clear response value. */
5829
5861
  const goalClearValueSchema = object({ cleared: literal(true) });
5830
5862
  //#endregion
5831
- //#region ../../host/apiproxy/src/api/settings.schema.ts
5863
+ //#region ../../host/apiproxy/lib/types/api/settings.schema.js
5832
5864
  /**
5833
5865
  * settings domain zod schemas (names derived from map keys: settingsDescribeRequestSchema /
5834
5866
  * settingsDescribeValueSchema / settingsUpdate* / settingsReplace*).
@@ -5890,7 +5922,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5890
5922
  /** settings.replace response value. */
5891
5923
  const settingsReplaceValueSchema = settingsNamespaceViewSchema;
5892
5924
  //#endregion
5893
- //#region ../../host/apiproxy/src/api/credentials.schema.ts
5925
+ //#region ../../host/apiproxy/lib/types/api/credentials.schema.js
5894
5926
  /**
5895
5927
  * credentials domain zod schemas (names derived from map keys:
5896
5928
  * credentialsDescribeRequestSchema / credentialsDescribeValueSchema / …).
@@ -5918,7 +5950,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5918
5950
  /** credentials.unset response value. */
5919
5951
  const credentialsUnsetValueSchema = object({});
5920
5952
  //#endregion
5921
- //#region ../../host/apiproxy/src/api/llm.schema.ts
5953
+ //#region ../../host/apiproxy/lib/types/api/llm.schema.js
5922
5954
  /**
5923
5955
  * llm domain zod schemas (names derived from map keys: llmProvidersRequestSchema /
5924
5956
  * llmProvidersValueSchema / llmModelsRequestSchema / llmModelsValueSchema).
@@ -5958,7 +5990,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5958
5990
  /** llm.discoverModels response value. */
5959
5991
  const llmDiscoverModelsValueSchema = object({ models: array(discoveredModelViewSchema) });
5960
5992
  //#endregion
5961
- //#region ../../host/apiproxy/src/api/subagents.schema.ts
5993
+ //#region ../../host/apiproxy/lib/types/api/subagents.schema.js
5962
5994
  /** Zod schemas for the browser-safe subagent domain. */
5963
5995
  /** Healthy and diagnostic durable catalog rows. */
5964
5996
  const subagentListEntrySchema = union([
@@ -6022,7 +6054,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6022
6054
  /** subagent.interrupt response value. */
6023
6055
  const subagentInterruptValueSchema = object({ accepted: literal(true) });
6024
6056
  //#endregion
6025
- //#region ../../host/apiproxy/src/fetch/client.ts
6057
+ //#region ../../host/apiproxy/lib/types/fetch/client.js
6058
+ /**
6059
+ * Client side of the fetch carrier. AbstractApiClient holds every protocol invariant: rpcId minting,
6060
+ * four-quadrant envelope wrap/unwrap, zod parsing, in-process SSE frame decoding, and the payload-direct
6061
+ * IApiClient domain methods (business code never mints). Platform differences ride two aspects:
6062
+ * abstract doFetch (transport) + overridable onEnvelope (tap). ApiProxy (the impl face) is untouched.
6063
+ */
6026
6064
  /**
6027
6065
  * S→C second-level parse table: value schema by method (the response-path
6028
6066
  * mirror of the handler's request table; key coverage compiler-enforced against RpcMethodMap).
@@ -6053,10 +6091,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6053
6091
  "workspace.create": workspaceCreateValueSchema,
6054
6092
  "workspace.rename": workspaceRenameValueSchema,
6055
6093
  "workspace.delete": workspaceDeleteValueSchema,
6094
+ "workspace.insertBefore": workspaceInsertBeforeValueSchema,
6056
6095
  "workspace.insertSessionBefore": workspaceInsertSessionBeforeValueSchema,
6057
6096
  "workspace.archiveSession": workspaceArchiveSessionValueSchema,
6058
- "command.list": commandListValueSchema,
6059
- "command.execute": commandExecuteValueSchema,
6060
6097
  "skill.list": skillListValueSchema,
6061
6098
  "agentPreset.list": agentPresetListValueSchema,
6062
6099
  "agentPreset.select": agentPresetSelectValueSchema,
@@ -6272,13 +6309,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6272
6309
  create: (payload, signal) => this.callUnary("workspace.create", payload, signal),
6273
6310
  rename: (payload, signal) => this.callUnary("workspace.rename", payload, signal),
6274
6311
  delete: (payload, signal) => this.callUnary("workspace.delete", payload, signal),
6312
+ insertBefore: (payload, signal) => this.callUnary("workspace.insertBefore", payload, signal),
6275
6313
  insertSessionBefore: (payload, signal) => this.callUnary("workspace.insertSessionBefore", payload, signal),
6276
6314
  archiveSession: (payload, signal) => this.callUnary("workspace.archiveSession", payload, signal)
6277
6315
  };
6278
- commands = {
6279
- list: (payload, signal) => this.callUnary("command.list", payload, signal),
6280
- execute: (payload, signal) => this.callUnary("command.execute", payload, signal, "caller-signal-only")
6281
- };
6282
6316
  skills = { list: (payload, signal) => this.callUnary("skill.list", payload, signal) };
6283
6317
  agentPresets = {
6284
6318
  list: (payload, signal) => this.callUnary("agentPreset.list", payload, signal),
@@ -6415,7 +6449,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6415
6449
  * TERMINAL_EXIT_STATUS and deliberately absent from this text: the real bash
6416
6450
  * presenter CONSUMES its `[exit code: N]` marker out of the body, because a
6417
6451
  * terminal card shows the exit as its own pill and leaving the marker in would
6418
- * render it twice (packages/bash/tool-bash/src/render.ts).
6452
+ * render it twice (packages/shell/tool-bash/src/render.ts).
6419
6453
  */
6420
6454
  const TERMINAL_OUTPUT_FIXTURE = [
6421
6455
  sgr(1, "Running 4 checks"),
@@ -6424,7 +6458,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6424
6458
  `${sgr(32, "✓")} duplication 2.10s`,
6425
6459
  `${sgr(31, "✗")} unit 8.41s`,
6426
6460
  "",
6427
- sgr(90, "packages/client/ui-primitives/tests/terminal-block.spec.tsx"),
6461
+ sgr(90, "packages/client/ui-primitives/tests/terminal-block.client.spec.tsx"),
6428
6462
  ` ${sgr(31, "FAIL")} caps output at the configured line budget`,
6429
6463
  " expected 16 lines, received 24",
6430
6464
  "",
@@ -6526,7 +6560,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6526
6560
  "packages/client/ui-primitives/src/SearchBlock.module.css",
6527
6561
  "packages/client/ui-tool/src/client/tool/models/search-card-model.ts",
6528
6562
  "packages/client/ui-tool/src/client/tool/toolviews/search-row.tsx",
6529
- "packages/client/ui-tool/tests/search-card.spec.tsx"
6563
+ "packages/client/ui-tool/tests/search-card.client.spec.tsx"
6530
6564
  ];
6531
6565
  /**
6532
6566
  * The model-facing glob render text — the newline-joined path list plus a
@@ -6688,7 +6722,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6688
6722
  cacheWriteTokens: turn % 10 === 0 ? 4 : 0
6689
6723
  };
6690
6724
  }
6691
- /** fx-alpha history script: 74 turns (~150+ messages -> 4 pages at PAGE_MESSAGES=50),
6725
+ /** fx-alpha history script: 75 turns (~150+ messages -> 4 pages at PAGE_MESSAGES=50),
6692
6726
  * mixing reasoning blocks / tool call+result / context. */
6693
6727
  function buildAlphaLog() {
6694
6728
  const events = [];
@@ -7057,6 +7091,45 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7057
7091
  type: "turn/start",
7058
7092
  data: { turn: 72 }
7059
7093
  });
7094
+ push({
7095
+ type: "user/message",
7096
+ surfaceOp: "append",
7097
+ data: userMessage(text("问题 72:请完整列出全部一百条条目。"))
7098
+ });
7099
+ push({
7100
+ type: "step/start",
7101
+ data: {
7102
+ turn: 72,
7103
+ step: 0
7104
+ }
7105
+ });
7106
+ push({
7107
+ type: "assistant/message",
7108
+ surfaceOp: "append",
7109
+ data: {
7110
+ turn: 72,
7111
+ step: 0,
7112
+ message: assistantMessage(text("条目 1:第一条。条目 2:第二条。条目 3:这一条写到一半被"))
7113
+ }
7114
+ });
7115
+ push({
7116
+ type: "step/end",
7117
+ data: {
7118
+ turn: 72,
7119
+ step: 0
7120
+ }
7121
+ });
7122
+ push({
7123
+ type: "turn/end",
7124
+ data: {
7125
+ turn: 72,
7126
+ reason: { kind: "max-tokens" }
7127
+ }
7128
+ });
7129
+ push({
7130
+ type: "turn/start",
7131
+ data: { turn: 73 }
7132
+ });
7060
7133
  push({
7061
7134
  type: "user/message",
7062
7135
  surfaceOp: "append",
@@ -7068,7 +7141,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7068
7141
  push({
7069
7142
  type: "step/start",
7070
7143
  data: {
7071
- turn: 72,
7144
+ turn: 73,
7072
7145
  step: 0
7073
7146
  }
7074
7147
  });
@@ -7076,7 +7149,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7076
7149
  type: "assistant/message",
7077
7150
  surfaceOp: "append",
7078
7151
  data: {
7079
- turn: 72,
7152
+ turn: 73,
7080
7153
  step: 0,
7081
7154
  message: assistantMessage([...text("结构化模型图片:"), {
7082
7155
  type: "image",
@@ -7087,18 +7160,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7087
7160
  push({
7088
7161
  type: "step/end",
7089
7162
  data: {
7090
- turn: 72,
7163
+ turn: 73,
7091
7164
  step: 0
7092
7165
  }
7093
7166
  });
7094
7167
  push({
7095
7168
  type: "turn/end",
7096
7169
  data: {
7097
- turn: 72,
7170
+ turn: 73,
7098
7171
  reason: { kind: "completed" }
7099
7172
  }
7100
7173
  });
7101
- toolTurn(73, "todo_write", JSON.stringify({ todos: fixtureTodos }), "Updated todo list: 1 pending, 2 in progress, 1 completed.");
7174
+ toolTurn(74, "todo_write", JSON.stringify({ todos: fixtureTodos }), "Updated todo list: 1 pending, 2 in progress, 1 completed.");
7102
7175
  const callIndex = events.length - 4;
7103
7176
  const callTime = events[callIndex]?.time;
7104
7177
  events.splice(callIndex + 1, 0, {
@@ -7319,7 +7392,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7319
7392
  };
7320
7393
  }
7321
7394
  /** Fixture parallel of the host's projection units: whole current values per key over the full log. */
7322
- /** Fixture preset table (the host PermissionService defaults). */
7395
+ /** Fixture preset table (the host PermissionPresetService defaults). */
7323
7396
  const PERMISSION_PRESETS = {
7324
7397
  "workspace-write": {
7325
7398
  sandbox: "workspace-write",
@@ -7405,6 +7478,73 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7405
7478
  }
7406
7479
  return totals;
7407
7480
  }
7481
+ /** Fixture parallel of session-stats' whole-log counting and wall-time fold. */
7482
+ function sessionStatsOf(log) {
7483
+ const value = {
7484
+ turns: 0,
7485
+ steps: 0,
7486
+ llmMs: 0,
7487
+ toolMs: 0,
7488
+ ttftMs: 0,
7489
+ ttftSteps: 0,
7490
+ decodeMs: 0,
7491
+ decodeTokens: 0
7492
+ };
7493
+ let lastTurn = null;
7494
+ let openStep = null;
7495
+ const pendingCalls = /* @__PURE__ */ new Map();
7496
+ for (const event of log) switch (event.type) {
7497
+ case "step/start":
7498
+ openStep = {
7499
+ turn: event.data.turn,
7500
+ step: event.data.step,
7501
+ startTime: event.time,
7502
+ firstTokenTime: null
7503
+ };
7504
+ break;
7505
+ case "assistant/chunk":
7506
+ if (openStep !== null && openStep.turn === event.data.turn && openStep.step === event.data.step && openStep.firstTokenTime === null && isTokenDelta(event.data.chunk)) openStep.firstTokenTime = event.time;
7507
+ break;
7508
+ case "assistant/message":
7509
+ if (openStep === null || openStep.turn !== event.data.turn || openStep.step !== event.data.step) break;
7510
+ value.llmMs += Math.max(0, event.time - openStep.startTime);
7511
+ if (openStep.firstTokenTime !== null) {
7512
+ value.ttftMs += Math.max(0, openStep.firstTokenTime - openStep.startTime);
7513
+ value.ttftSteps += 1;
7514
+ const outputTokens = event.data.usage?.outputTokens;
7515
+ if (typeof outputTokens === "number" && Number.isFinite(outputTokens) && outputTokens >= 0) {
7516
+ value.decodeMs += Math.max(0, event.time - openStep.firstTokenTime);
7517
+ value.decodeTokens += outputTokens;
7518
+ }
7519
+ }
7520
+ openStep = null;
7521
+ break;
7522
+ case "tool/call":
7523
+ pendingCalls.set(event.data.callId, event.time);
7524
+ break;
7525
+ case "tool/result": {
7526
+ const callId = event.data.message.source.callId;
7527
+ const dispatched = pendingCalls.get(callId);
7528
+ if (dispatched === void 0) break;
7529
+ pendingCalls.delete(callId);
7530
+ value.toolMs += Math.max(0, event.time - dispatched);
7531
+ break;
7532
+ }
7533
+ case "step/end":
7534
+ if (event.data.turn !== lastTurn) {
7535
+ value.turns += 1;
7536
+ lastTurn = event.data.turn;
7537
+ }
7538
+ value.steps += 1;
7539
+ openStep = null;
7540
+ break;
7541
+ case "turn/end":
7542
+ pendingCalls.clear();
7543
+ break;
7544
+ default: break;
7545
+ }
7546
+ return value;
7547
+ }
7408
7548
  /** Fixed token-meter heuristic constants mirrored by this client-only fixture. */
7409
7549
  const CHARS_PER_TOKEN = 4;
7410
7550
  const BLOCK_OVERHEAD = 4;
@@ -7474,6 +7614,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7474
7614
  values["tokenUsage"] = tokenUsageOf(log);
7475
7615
  values["contextPressure"] = contextPressureOf(log);
7476
7616
  values["contextBreakdown"] = contextBreakdownOf(log);
7617
+ values["sessionStats"] = sessionStatsOf(log);
7618
+ values["imageLimits"] = {
7619
+ maxImageBytes: 5 * 1024 * 1024,
7620
+ maxImagesPerMessage: 20,
7621
+ maxMessageImageBytes: 100 * 1024 * 1024,
7622
+ maxImagePixels: 4e7,
7623
+ mediaTypes: [
7624
+ "image/png",
7625
+ "image/jpeg",
7626
+ "image/webp",
7627
+ "image/gif"
7628
+ ]
7629
+ };
7477
7630
  return values;
7478
7631
  }
7479
7632
  /** Host push-frame parallel: emit one session/projection frame per key the given event advanced. */
@@ -7507,6 +7660,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7507
7660
  value: contextBreakdownOf(log),
7508
7661
  seq: event.seq
7509
7662
  });
7663
+ if (type === "assistant/message" || type === "tool/result" || type === "step/end") frames.push({
7664
+ type: "session/projection",
7665
+ sessionId: id,
7666
+ key: "sessionStats",
7667
+ value: sessionStatsOf(log),
7668
+ seq: event.seq
7669
+ });
7510
7670
  if (frames.length > 0) return frames;
7511
7671
  if (type === "session/title") {
7512
7672
  const values = projectionValuesOf(log);
@@ -7820,7 +7980,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7820
7980
  }]
7821
7981
  ]);
7822
7982
  let fixtureDefaultPreset = "standard";
7823
- const nextTurn = new Map([[sid("fx-alpha"), 74]]);
7983
+ const nextTurn = new Map([[sid("fx-alpha"), 75]]);
7824
7984
  let nextSession = 1;
7825
7985
  let nextRpc = 1;
7826
7986
  let attachedSessions = options.empty ? 0 : 1;
@@ -8048,6 +8208,200 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8048
8208
  details: { sessionId: id }
8049
8209
  }
8050
8210
  } : void 0;
8211
+ /** Canonical fixture implementation of the generated Commands Remote contract. */
8212
+ const commandRemotes = {
8213
+ list(id) {
8214
+ const missing = requireGoalSession(id);
8215
+ if (missing !== void 0) return missing;
8216
+ return {
8217
+ ok: true,
8218
+ value: [
8219
+ {
8220
+ name: "compact",
8221
+ description: "fixture:压缩当前会话上下文"
8222
+ },
8223
+ {
8224
+ name: "echo",
8225
+ description: "fixture:回显参数",
8226
+ input: { hint: "text to echo" }
8227
+ },
8228
+ {
8229
+ name: "goal",
8230
+ description: "set or view the goal for a long-running task",
8231
+ input: { hint: "<objective>" }
8232
+ },
8233
+ {
8234
+ name: "permission",
8235
+ description: "Switch the permission preset (sandbox mode + approval policy)",
8236
+ input: { hint: "<preset>" }
8237
+ },
8238
+ {
8239
+ name: "plan",
8240
+ description: "Enter or leave plan mode",
8241
+ input: { hint: "[off|message]" }
8242
+ }
8243
+ ]
8244
+ };
8245
+ },
8246
+ execute(id, line) {
8247
+ const missing = requireGoalSession(id);
8248
+ if (missing !== void 0) return missing;
8249
+ const match = /^\/(\S+)((?:\s.*)?)$/.exec(line.trim());
8250
+ const name = match?.[1];
8251
+ const args = match?.[2] ?? "";
8252
+ if (name === "permission") {
8253
+ const preset = args.trim();
8254
+ const commandId = `fx-cmd-${logOf(id).length}`;
8255
+ append(id, {
8256
+ type: "command/run",
8257
+ data: {
8258
+ commandId,
8259
+ name,
8260
+ args,
8261
+ source: { kind: "user" }
8262
+ }
8263
+ });
8264
+ const spec = PERMISSION_PRESETS[preset];
8265
+ let result;
8266
+ if (preset === "") result = {
8267
+ kind: "success",
8268
+ text: `current preset ${permissionSelectOf(logOf(id)).currentValue} (available: ${Object.keys(PERMISSION_PRESETS).join(", ")})`
8269
+ };
8270
+ else if (spec === void 0) result = {
8271
+ kind: "error",
8272
+ text: `unknown preset "${preset}" (available: ${Object.keys(PERMISSION_PRESETS).join(", ")})`
8273
+ };
8274
+ else {
8275
+ if (permissionSelectOf(logOf(id)).currentValue !== preset) append(id, {
8276
+ type: "permission/preset",
8277
+ data: { preset }
8278
+ });
8279
+ append(id, {
8280
+ type: "sandbox/mode",
8281
+ data: { mode: spec.sandbox }
8282
+ });
8283
+ append(id, {
8284
+ type: "approval/policy",
8285
+ data: { policy: spec.approval }
8286
+ });
8287
+ result = {
8288
+ kind: "success",
8289
+ text: `preset ${preset}`
8290
+ };
8291
+ }
8292
+ append(id, {
8293
+ type: "command/done",
8294
+ data: {
8295
+ commandId,
8296
+ ...result
8297
+ }
8298
+ });
8299
+ return {
8300
+ ok: true,
8301
+ value: {
8302
+ commandId,
8303
+ result
8304
+ }
8305
+ };
8306
+ }
8307
+ if (name === "goal") {
8308
+ const commandId = `fx-cmd-${logOf(id).length}`;
8309
+ append(id, {
8310
+ type: "command/run",
8311
+ data: {
8312
+ commandId,
8313
+ name,
8314
+ args,
8315
+ source: { kind: "user" }
8316
+ }
8317
+ });
8318
+ const objective = args.trim();
8319
+ const current = backscanGoal(logOf(id));
8320
+ let text;
8321
+ if (objective === "") text = current === null ? "No goal is set. Usage: /goal <objective>" : `Current goal: ${current.goal.objective}`;
8322
+ else if (current !== null && current.goal.phase !== "complete") text = `A goal already exists (${current.goal.objective}). Clear it first.`;
8323
+ else text = `Goal created: ${appendGoalChange(id, {
8324
+ kind: "goal/change",
8325
+ version: 1,
8326
+ operation: "create",
8327
+ goal: {
8328
+ id: `fx-goal-${logOf(id).length}`,
8329
+ revision: 1,
8330
+ objective,
8331
+ phase: "active",
8332
+ maxGoalRounds: 256
8333
+ },
8334
+ roundsStarted: 0,
8335
+ createdAt: Date.now(),
8336
+ updatedAt: Date.now()
8337
+ }).goal.objective}`;
8338
+ const result = {
8339
+ kind: "success",
8340
+ text
8341
+ };
8342
+ append(id, {
8343
+ type: "command/done",
8344
+ data: {
8345
+ commandId,
8346
+ ...result
8347
+ }
8348
+ });
8349
+ return {
8350
+ ok: true,
8351
+ value: {
8352
+ commandId,
8353
+ result
8354
+ }
8355
+ };
8356
+ }
8357
+ const running = summaryOf(id)?.running === true;
8358
+ const outcomes = {
8359
+ compact: "fixture:已压缩(假动作)",
8360
+ echo: args.trim(),
8361
+ plan: args.trim() === "off" ? running ? "Leaving plan mode (applies from the next step)." : "Plan mode off." : running ? "Entering plan mode (applies from the next step). Use /plan off to leave." : "Plan mode on. Use /plan off to leave."
8362
+ };
8363
+ const text = name === void 0 ? void 0 : outcomes[name];
8364
+ if (name === void 0 || text === void 0) return {
8365
+ ok: true,
8366
+ value: void 0
8367
+ };
8368
+ const commandId = `fx-cmd-${logOf(id).length}`;
8369
+ append(id, {
8370
+ type: "command/run",
8371
+ data: {
8372
+ commandId,
8373
+ name,
8374
+ args,
8375
+ source: { kind: "user" }
8376
+ }
8377
+ });
8378
+ if (name === "plan" && !running) {
8379
+ const plan = foldPlan(logOf(id));
8380
+ if (plan.wanted !== null && plan.wanted !== plan.active) append(id, {
8381
+ type: "plan/mode",
8382
+ data: { active: plan.wanted }
8383
+ });
8384
+ }
8385
+ const result = {
8386
+ kind: "success",
8387
+ ...text === "" ? {} : { text }
8388
+ };
8389
+ append(id, {
8390
+ type: "command/done",
8391
+ data: {
8392
+ commandId,
8393
+ ...result
8394
+ }
8395
+ });
8396
+ return {
8397
+ ok: true,
8398
+ value: {
8399
+ commandId,
8400
+ result
8401
+ }
8402
+ };
8403
+ }
8404
+ };
8051
8405
  const goalView = (projection) => ({
8052
8406
  ...projection.goal,
8053
8407
  roundsStarted: projection.roundsStarted,
@@ -9079,6 +9433,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9079
9433
  });
9080
9434
  return ok(request, { deleted: true });
9081
9435
  },
9436
+ insertBefore: (request) => {
9437
+ const { workspaceId, beforeWorkspaceId } = request.payload;
9438
+ const source = workspaces.findIndex((workspace) => workspace.workspaceId === workspaceId);
9439
+ const anchor = beforeWorkspaceId === void 0 ? workspaces.length : workspaces.findIndex((workspace) => workspace.workspaceId === beforeWorkspaceId);
9440
+ const missing = source === -1 ? workspaceId : anchor === -1 ? beforeWorkspaceId : void 0;
9441
+ if (missing !== void 0) return err(request, {
9442
+ code: "workspace-not-found",
9443
+ message: `no workspace ${missing}`,
9444
+ details: { workspaceId: missing }
9445
+ });
9446
+ if (beforeWorkspaceId !== workspaceId) {
9447
+ const previousOrder = workspaces.map((candidate) => candidate.workspaceId);
9448
+ const [workspace] = workspaces.splice(source, 1);
9449
+ /* v8 ignore next -- source was resolved from the same array immediately above. */
9450
+ if (workspace === void 0) throw new Error(`fixture lost workspace ${workspaceId}`);
9451
+ const at = beforeWorkspaceId === void 0 ? workspaces.length : workspaces.findIndex((candidate) => candidate.workspaceId === beforeWorkspaceId);
9452
+ workspaces.splice(at, 0, workspace);
9453
+ if (workspaces.some((candidate, index) => candidate.workspaceId !== previousOrder[index])) emitHost({
9454
+ type: "host/workspace-order-changed",
9455
+ workspaceIds: workspaces.map((candidate) => candidate.workspaceId)
9456
+ });
9457
+ }
9458
+ return ok(request, { workspaceIds: workspaces.map((candidate) => candidate.workspaceId) });
9459
+ },
9082
9460
  insertSessionBefore: (request) => {
9083
9461
  const { workspaceId, sessionId, beforeSessionId } = request.payload;
9084
9462
  const workspace = workspaces.find((w) => w.workspaceId === workspaceId);
@@ -9127,185 +9505,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9127
9505
  return ok(request, { archivedSessionIds: [...archivedSessionIds] });
9128
9506
  }
9129
9507
  },
9130
- commands: {
9131
- list: (request) => {
9132
- const missing = requireSession(request);
9133
- if (missing !== void 0) return missing;
9134
- return ok(request, { commands: [
9135
- {
9136
- name: "compact",
9137
- description: "fixture:压缩当前会话上下文"
9138
- },
9139
- {
9140
- name: "echo",
9141
- description: "fixture:回显参数",
9142
- input: { hint: "text to echo" }
9143
- },
9144
- {
9145
- name: "goal",
9146
- description: "set or view the goal for a long-running task",
9147
- input: { hint: "<objective>" }
9148
- },
9149
- {
9150
- name: "permission",
9151
- description: "Switch the permission preset (sandbox mode + approval policy)",
9152
- input: { hint: "<preset>" }
9153
- },
9154
- {
9155
- name: "plan",
9156
- description: "Enter or leave plan mode",
9157
- input: { hint: "[off|message]" }
9158
- }
9159
- ] });
9160
- },
9161
- execute: (request) => {
9162
- const missing = requireSession(request);
9163
- if (missing !== void 0) return missing;
9164
- const id = request.payload.sessionId;
9165
- const match = /^\/(\S+)((?:\s.*)?)$/.exec(request.payload.line.trim());
9166
- const name = match?.[1];
9167
- const args = match?.[2] ?? "";
9168
- if (name === "permission") {
9169
- const preset = args.trim();
9170
- const commandId = `fx-cmd-${logOf(id).length}`;
9171
- append(id, {
9172
- type: "command/run",
9173
- data: {
9174
- commandId,
9175
- name,
9176
- args,
9177
- source: { kind: "user" }
9178
- }
9179
- });
9180
- const spec = PERMISSION_PRESETS[preset];
9181
- if (preset === "") {
9182
- const current = permissionSelectOf(logOf(id)).currentValue;
9183
- append(id, {
9184
- type: "command/done",
9185
- data: {
9186
- commandId,
9187
- kind: "success",
9188
- text: `current preset ${current} (available: ${Object.keys(PERMISSION_PRESETS).join(", ")})`
9189
- }
9190
- });
9191
- } else if (spec === void 0) append(id, {
9192
- type: "command/done",
9193
- data: {
9194
- commandId,
9195
- kind: "error",
9196
- text: `unknown preset "${preset}" (available: ${Object.keys(PERMISSION_PRESETS).join(", ")})`
9197
- }
9198
- });
9199
- else {
9200
- if (permissionSelectOf(logOf(id)).currentValue !== preset) append(id, {
9201
- type: "permission/preset",
9202
- data: { preset }
9203
- });
9204
- append(id, {
9205
- type: "sandbox/mode",
9206
- data: { mode: spec.sandbox }
9207
- });
9208
- append(id, {
9209
- type: "approval/policy",
9210
- data: { policy: spec.approval }
9211
- });
9212
- append(id, {
9213
- type: "command/done",
9214
- data: {
9215
- commandId,
9216
- kind: "success",
9217
- text: `preset ${preset}`
9218
- }
9219
- });
9220
- }
9221
- return ok(request, {
9222
- matched: true,
9223
- commandId
9224
- });
9225
- }
9226
- if (name === "goal") {
9227
- const commandId = `fx-cmd-${logOf(id).length}`;
9228
- append(id, {
9229
- type: "command/run",
9230
- data: {
9231
- commandId,
9232
- name,
9233
- args,
9234
- source: { kind: "user" }
9235
- }
9236
- });
9237
- const objective = args.trim();
9238
- const current = backscanGoal(logOf(id));
9239
- let text;
9240
- if (objective === "") text = current === null ? "No goal is set. Usage: /goal <objective>" : `Current goal: ${current.goal.objective}`;
9241
- else if (current !== null && current.goal.phase !== "complete") text = `A goal already exists (${current.goal.objective}). Clear it first.`;
9242
- else text = `Goal created: ${appendGoalChange(id, {
9243
- kind: "goal/change",
9244
- version: 1,
9245
- operation: "create",
9246
- goal: {
9247
- id: `fx-goal-${logOf(id).length}`,
9248
- revision: 1,
9249
- objective,
9250
- phase: "active",
9251
- maxGoalRounds: 256
9252
- },
9253
- roundsStarted: 0,
9254
- createdAt: Date.now(),
9255
- updatedAt: Date.now()
9256
- }).goal.objective}`;
9257
- append(id, {
9258
- type: "command/done",
9259
- data: {
9260
- commandId,
9261
- kind: "success",
9262
- text
9263
- }
9264
- });
9265
- return ok(request, {
9266
- matched: true,
9267
- commandId
9268
- });
9269
- }
9270
- const running = summaryOf(id)?.running === true;
9271
- const outcomes = {
9272
- compact: "fixture:已压缩(假动作)",
9273
- echo: args.trim(),
9274
- plan: args.trim() === "off" ? running ? "Leaving plan mode (applies from the next step)." : "Plan mode off." : running ? "Entering plan mode (applies from the next step). Use /plan off to leave." : "Plan mode on. Use /plan off to leave."
9275
- };
9276
- const text = name === void 0 ? void 0 : outcomes[name];
9277
- if (name === void 0 || text === void 0) return ok(request, { matched: false });
9278
- const commandId = `fx-cmd-${logOf(id).length}`;
9279
- append(id, {
9280
- type: "command/run",
9281
- data: {
9282
- commandId,
9283
- name,
9284
- args,
9285
- source: { kind: "user" }
9286
- }
9287
- });
9288
- if (name === "plan" && !running) {
9289
- const plan = foldPlan(logOf(id));
9290
- if (plan.wanted !== null && plan.wanted !== plan.active) append(id, {
9291
- type: "plan/mode",
9292
- data: { active: plan.wanted }
9293
- });
9294
- }
9295
- append(id, {
9296
- type: "command/done",
9297
- data: {
9298
- commandId,
9299
- kind: "success",
9300
- ...text === "" ? {} : { text }
9301
- }
9302
- });
9303
- return ok(request, {
9304
- matched: true,
9305
- commandId
9306
- });
9307
- }
9308
- },
9309
9508
  agentPresets: {
9310
9509
  list: (request) => ok(request, {
9311
9510
  presets: [...fixturePresets].map(([id, preset]) => ({
@@ -9632,6 +9831,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9632
9831
  const args = payload.args;
9633
9832
  const sessionId = args.agentId;
9634
9833
  switch (endpoint) {
9834
+ case "commands/list": return Promise.resolve(commandRemotes.list(sessionId));
9835
+ case "commands/execute": return Promise.resolve(commandRemotes.execute(sessionId, args.line));
9635
9836
  case "goals/create": return Promise.resolve(goalRemotes.create(sessionId, {
9636
9837
  objective: args.request?.objective,
9637
9838
  ...args.request?.maxGoalRounds === void 0 ? {} : { maxGoalRounds: args.request.maxGoalRounds }
@@ -9712,10 +9913,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9712
9913
  case "workspace.create": return this.api.workspace.create(request);
9713
9914
  case "workspace.rename": return this.api.workspace.rename(request);
9714
9915
  case "workspace.delete": return this.api.workspace.delete(request);
9916
+ case "workspace.insertBefore": return this.api.workspace.insertBefore(request);
9715
9917
  case "workspace.insertSessionBefore": return this.api.workspace.insertSessionBefore(request);
9716
9918
  case "workspace.archiveSession": return this.api.workspace.archiveSession(request);
9717
- case "command.list": return this.api.commands.list(request);
9718
- case "command.execute": return this.api.commands.execute(request, signal);
9719
9919
  case "skill.list": return this.api.skills.list(request);
9720
9920
  case "agentPreset.list": return this.api.agentPresets.list(request);
9721
9921
  case "agentPreset.select": return this.api.agentPresets.select(request);
package/lib/index.js CHANGED
@@ -22,6 +22,11 @@ const HOST_EVENTS_PATH = `${API_PATH}/events.host`;
22
22
  * node:http ↔ WHATWG fetch bridge for the /api transport (host side of the
23
23
  * web carrier; the fetch-shaped handler itself is transport-agnostic).
24
24
  */
25
+ /** Default carrier cap for all HTTP RPC bodies: sized for the default
26
+ * aggregate image limit (100 MiB) after base64 expansion plus envelope
27
+ * headroom (~134.3 MiB required), rounded up for slack. The bridge buffers
28
+ * each body in memory, so this cap is also the per-request resident bound. */
29
+ const DEFAULT_MAX_REQUEST_BODY_BYTES = 160 * 1024 * 1024;
25
30
  /**
26
31
  * Bridge one node:http request to the fetch-shaped handler (client close
27
32
  * aborts; SSE bodies stream out chunk by chunk).
@@ -30,7 +35,7 @@ const HOST_EVENTS_PATH = `${API_PATH}/events.host`;
30
35
  * @param apiHandler - fetch-shaped API carrier the request is dispatched to.
31
36
  * @param maxRequestBodyBytes - maximum body bytes buffered before dispatch.
32
37
  */
33
- async function bridge(req, res, apiHandler, maxRequestBodyBytes = 32 * 1024 * 1024) {
38
+ async function bridge(req, res, apiHandler, maxRequestBodyBytes = DEFAULT_MAX_REQUEST_BODY_BYTES) {
34
39
  const abort = new AbortController();
35
40
  res.on("close", () => {
36
41
  if (!res.writableEnded) abort.abort();
@@ -249,7 +254,7 @@ var HostConnectionService = class extends Service {
249
254
  await bridge(req, res, fetchHandler);
250
255
  }
251
256
  };
252
- return owner.effect(() => owner.httpServer.register(route), `client-connection: ${channel} rpc channel`);
257
+ return owner.effect(() => owner.webServer.register(route), `client-connection: ${channel} rpc channel`);
253
258
  }
254
259
  registerInterceptor(owner, channel, matches, handler, options) {
255
260
  if (channel !== "/api") throw new Error(`connection: invalid shared RPC channel ${JSON.stringify(channel)}`);
@@ -470,10 +475,8 @@ function assertImageBodyCapacity(ctx, maxRequestBodyBytes) {
470
475
  const requiredImageBodyBytes = Math.ceil(attachments.imageLimits.maxMessageImageBytes * 4 / 3) + REQUEST_ENVELOPE_HEADROOM_BYTES;
471
476
  if (maxRequestBodyBytes < requiredImageBodyBytes) throw new Error(`client-connection maxRequestBodyBytes (${String(maxRequestBodyBytes)}) must be at least ${String(requiredImageBodyBytes)} for the configured aggregate image limit`);
472
477
  }
473
- /** Default carrier cap for all HTTP RPC bodies. */
474
- const DEFAULT_MAX_REQUEST_BODY_BYTES = 32 * 1024 * 1024;
475
478
  /** Services required before providing Connection; API Proxy is an optional `/api` fallback. */
476
- const inject = ["httpServer"];
479
+ const inject = ["webServer"];
477
480
  const Config = z.object({
478
481
  trustedHosts: z.array(String).default([]),
479
482
  maxRequestBodyBytes: z.natural().min(1).default(DEFAULT_MAX_REQUEST_BODY_BYTES)
@@ -526,7 +529,7 @@ const PRIVILEGED_METHODS = new Set([
526
529
  */
527
530
  function apply(ctx, config) {
528
531
  const trustedHosts = config?.trustedHosts ?? [];
529
- const maxRequestBodyBytes = config?.maxRequestBodyBytes ?? DEFAULT_MAX_REQUEST_BODY_BYTES;
532
+ const maxRequestBodyBytes = config?.maxRequestBodyBytes ?? 167772160;
530
533
  for (const entry of trustedHosts) assertTrustedAuthority(entry);
531
534
  if (ctx.get("apiProxy") !== void 0) assertImageBodyCapacity(ctx, maxRequestBodyBytes);
532
535
  const fetchHandler = new HostConnectionService(ctx, trustedHosts).createSharedFetchHandler(API_PATH, { async fetch(request) {
@@ -556,12 +559,12 @@ function apply(ctx, config) {
556
559
  await bridge(req, res, fetchHandler, maxRequestBodyBytes);
557
560
  }
558
561
  };
559
- ctx.effect(() => ctx.httpServer.register(route), "client-connection: /api route");
562
+ ctx.effect(() => ctx.webServer.register(route), "client-connection: /api route");
560
563
  ctx.inject(["apiProxy"], (apiCtx) => {
561
564
  assertImageBodyCapacity(apiCtx, maxRequestBodyBytes);
562
565
  const downlinks = new WebSocketDownlinks(apiCtx.apiProxy);
563
566
  const registerDownlink = (path, handle) => {
564
- apiCtx.effect(() => apiCtx.httpServer.registerUpgrade({
567
+ apiCtx.effect(() => apiCtx.webServer.registerUpgrade({
565
568
  path,
566
569
  handler: (req, socket, head) => {
567
570
  if (!isTrustedApiRequest(req, trustedHosts)) {
@@ -1,4 +1,4 @@
1
- export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ResponseValue, WorkspaceApi, WorkspaceId, WorkspaceView, CommandsApi, CommandDescriptor, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, TaskView, } from '@deepseek-ai/dsh-host-apiproxy/api';
1
+ export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ResponseValue, WorkspaceApi, WorkspaceId, WorkspaceView, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, JobView, } from '@deepseek-ai/dsh-host-apiproxy/api';
2
2
  export type { ToolCallView, ToolResultView } from '@deepseek-ai/dsh-tools/presentation';
3
3
  export type { RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt, } from '@deepseek-ai/dsh-host-apiproxy/api';
4
4
  export { RpcId, SESSION_SEARCH_RESULT_LIMIT, transportError, } from '@deepseek-ai/dsh-host-apiproxy/api';
@@ -22,6 +22,20 @@ export interface FixtureOptions {
22
22
  * @returns an ApiProxy backed entirely by in-memory state — no host process, no network.
23
23
  */
24
24
  export declare function createFixtureApi(options?: FixtureOptions): ApiProxy;
25
+ /** Both fixture faces over one state graph. */
26
+ export interface FixtureWorld {
27
+ /** Legacy unary/stream API the fixture still answers. */
28
+ readonly api: ApiProxy;
29
+ /** Generic Remote caller for the endpoints business services own. */
30
+ readonly rpc: ClientConnectionRpc;
31
+ }
32
+ /**
33
+ * Build both fixture faces so a caller can drive the Remote endpoints and the
34
+ * legacy API against one in-memory state graph.
35
+ * @param options - fixture branches for empty state and failure timing.
36
+ * @returns the legacy API face and the Remote RPC face.
37
+ */
38
+ export declare function createFixtureFaces(options?: FixtureOptions): FixtureWorld;
25
39
  /**
26
40
  * Fixture platform subclass: there is no HTTP at all, so instead of a doFetch transport it
27
41
  * overrides the protocol-level virtuals (callUnary/openMux/openHost/respond) to dispatch
@@ -7,7 +7,7 @@ import type { Context } from '@deepseek-ai/cordis';
7
7
  import type { HostDescription, IApiClient } from './api.ts';
8
8
  import { type ConnectionConfig, type ConnectionSinks, type ConnectionState } from './connection.ts';
9
9
  import type { ClientConnectionRpc } from '../rpc.ts';
10
- export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ToolCallView, ToolResultView, WorkspaceApi, WorkspaceId, WorkspaceView, CommandsApi, CommandDescriptor, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, MessageId, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, TaskView, RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt, HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, } from './api.ts';
10
+ export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ToolCallView, ToolResultView, WorkspaceApi, WorkspaceId, WorkspaceView, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, MessageId, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, JobView, RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt, HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, } from './api.ts';
11
11
  export { RpcId, AbstractApiClient, transportError, } from './api.ts';
12
12
  export type { ConnectionConfig, ConnectionSinks, ConnectionState };
13
13
  export type { ClientConnectionRpc } from '../rpc.ts';
@@ -3,6 +3,11 @@
3
3
  * web carrier; the fetch-shaped handler itself is transport-agnostic).
4
4
  */
5
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
6
+ /** Default carrier cap for all HTTP RPC bodies: sized for the default
7
+ * aggregate image limit (100 MiB) after base64 expansion plus envelope
8
+ * headroom (~134.3 MiB required), rounded up for slack. The bridge buffers
9
+ * each body in memory, so this cap is also the per-request resident bound. */
10
+ export declare const DEFAULT_MAX_REQUEST_BODY_BYTES: number;
6
11
  /** Transport-independent request handler consumed by the Host HTTP bridge. */
7
12
  export interface FetchHandler {
8
13
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-connection",
3
3
  "description": "Wire consumer layer: HTTP-up/WebSocket-down client, ConnectionController dual streams with reconnect, and fixture api",
4
- "version": "0.0.1-rc.2",
4
+ "version": "0.0.1-rc.3",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },
@@ -39,13 +39,13 @@
39
39
  "license": "BSD-3-Clause",
40
40
  "dependencies": {
41
41
  "ws": "^8.21.0",
42
- "@deepseek-ai/dsh-commands": "^0.0.1-rc.2",
43
- "@deepseek-ai/dsh-attachment": "^0.0.1-rc.2",
44
- "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.2",
45
- "@deepseek-ai/dsh-session": "^0.0.1-rc.2",
46
- "@deepseek-ai/dsh-tools": "^0.0.1-rc.2",
47
- "@deepseek-ai/schemastery": "^3.18.1-rc.1",
48
- "@deepseek-ai/dsh-llm": "^0.0.1-rc.2"
42
+ "@deepseek-ai/dsh-attachment": "^0.0.1-rc.3",
43
+ "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.3",
44
+ "@deepseek-ai/dsh-commands": "^0.0.1-rc.3",
45
+ "@deepseek-ai/dsh-llm": "^0.0.1-rc.3",
46
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.3",
47
+ "@deepseek-ai/dsh-tools": "^0.0.1-rc.3",
48
+ "@deepseek-ai/schemastery": "^3.18.1-rc.1"
49
49
  },
50
50
  "files": [
51
51
  "lib/index.js",
@@ -54,14 +54,14 @@
54
54
  "lib/types/**/*.d.ts"
55
55
  ],
56
56
  "peerDependencies": {
57
- "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.2",
58
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.2",
57
+ "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.3",
58
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
59
59
  "@deepseek-ai/cordis": "^4.0.1-rc.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/ws": "^8.18.1",
63
- "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.2",
64
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
65
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.2"
63
+ "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.3",
64
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
65
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
66
66
  }
67
67
  }