@ljwei-stak/dsh-model-route-bot 0.66.3 → 0.67.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.
Files changed (272) hide show
  1. package/.dsh-plugin/bot-product.mjs +81 -5
  2. package/.dsh-plugin/client.js +17490 -1791
  3. package/README.i18n.yaml +4 -4
  4. package/README.md +409 -364
  5. package/README.zh.md +409 -364
  6. package/apps/integration-client/README.md +31 -7
  7. package/apps/integration-client/api.mjs +14 -3
  8. package/apps/integration-client/binding-editor.jsx +308 -109
  9. package/apps/integration-client/binding-model.mjs +262 -0
  10. package/apps/integration-client/binding.css +144 -0
  11. package/apps/integration-client/bot-model-status.mjs +18 -0
  12. package/apps/integration-client/bot-profile-identity.jsx +197 -0
  13. package/apps/integration-client/bot-profile.css +57 -0
  14. package/apps/integration-client/bot-profile.mjs +97 -0
  15. package/apps/integration-client/bot-settings-dialog.css +18 -0
  16. package/apps/integration-client/bot-settings-dialog.jsx +111 -0
  17. package/apps/integration-client/bot-thread-header-bridge.css +20 -0
  18. package/apps/integration-client/bot-thread-header.jsx +278 -0
  19. package/apps/integration-client/bot-threads-panel.jsx +378 -0
  20. package/apps/integration-client/bot-threads.css +169 -0
  21. package/apps/integration-client/chat-design.mjs +250 -0
  22. package/apps/integration-client/chat-ui.jsx +175 -0
  23. package/apps/integration-client/chat.css +477 -0
  24. package/apps/integration-client/composer-draft-binding.mjs +50 -0
  25. package/apps/integration-client/composer-draft-bound.jsx +14 -0
  26. package/apps/integration-client/composer-draft-host.jsx +142 -0
  27. package/apps/integration-client/composer-draft-session.jsx +287 -0
  28. package/apps/integration-client/composer-draft-witness.mjs +115 -0
  29. package/apps/integration-client/composer-draft.css +4 -0
  30. package/apps/integration-client/composer-draft.mjs +322 -0
  31. package/apps/integration-client/contact-attention.jsx +54 -0
  32. package/apps/integration-client/data-panel.jsx +439 -0
  33. package/apps/integration-client/data.css +69 -0
  34. package/apps/integration-client/desktop-bridge.mjs +1 -0
  35. package/apps/integration-client/desktop-http-socket.mjs +49 -0
  36. package/apps/integration-client/desktop-keyboard.mjs +32 -0
  37. package/apps/integration-client/desktop-pane.jsx +99 -30
  38. package/apps/integration-client/desktop-stream.mjs +13 -3
  39. package/apps/integration-client/desktop-tools.jsx +19 -0
  40. package/apps/integration-client/desktop-touch.mjs +27 -0
  41. package/apps/integration-client/desktop.css +41 -0
  42. package/apps/integration-client/independent-chat-controller.mjs +224 -0
  43. package/apps/integration-client/independent-chat-entry.jsx +61 -0
  44. package/apps/integration-client/independent-chat-navigation.mjs +21 -0
  45. package/apps/integration-client/independent-chat-page.jsx +148 -0
  46. package/apps/integration-client/independent-chat-rich.jsx +64 -0
  47. package/apps/integration-client/independent-chat-rich.mjs +30 -0
  48. package/apps/integration-client/independent-chat.css +73 -0
  49. package/apps/integration-client/index.jsx +24 -16
  50. package/apps/integration-client/library-panel.jsx +4 -1
  51. package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
  52. package/apps/integration-client/main-route-editor.jsx +94 -0
  53. package/apps/integration-client/main-route-model.mjs +100 -0
  54. package/apps/integration-client/model-picker.css +37 -0
  55. package/apps/integration-client/model-picker.jsx +83 -0
  56. package/apps/integration-client/native-activity.css +141 -0
  57. package/apps/integration-client/native-activity.jsx +276 -0
  58. package/apps/integration-client/native-activity.mjs +384 -0
  59. package/apps/integration-client/notifications-panel.jsx +186 -0
  60. package/apps/integration-client/notifications.css +126 -0
  61. package/apps/integration-client/pairing-qr.jsx +79 -0
  62. package/apps/integration-client/panels.jsx +126 -115
  63. package/apps/integration-client/product-slots.jsx +46 -0
  64. package/apps/integration-client/routine-recorder.mjs +23 -2
  65. package/apps/integration-client/secure-pairing.jsx +84 -0
  66. package/apps/integration-client/tasks-panel.jsx +32 -10
  67. package/apps/integration-client/ui-icons.jsx +17 -0
  68. package/apps/integration-client/usage-explorer.jsx +43 -0
  69. package/apps/integration-client/usage-report.mjs +71 -0
  70. package/apps/integration-client/usage.css +34 -0
  71. package/apps/integration-client/web-panel-model.mjs +140 -0
  72. package/apps/integration-client/web-panel-page.jsx +221 -0
  73. package/apps/integration-client/web-panel-pins-live.jsx +28 -0
  74. package/apps/integration-client/web-panel.css +139 -0
  75. package/apps/integration-client/workspace-model.mjs +67 -0
  76. package/apps/integration-client/workspace-navigation.mjs +56 -0
  77. package/apps/integration-client/workspace-ui.jsx +173 -0
  78. package/apps/integration-client/workspace.css +332 -0
  79. package/modules/bot-runtime/SOURCE.json +176 -50
  80. package/modules/bot-runtime/index.mjs +169 -13
  81. package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
  82. package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
  83. package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
  84. package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
  85. package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
  86. package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
  87. package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
  88. package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
  89. package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
  90. package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
  91. package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
  92. package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
  93. package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
  94. package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
  95. package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
  96. package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
  97. package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
  98. package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
  99. package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
  100. package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
  101. package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
  102. package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
  103. package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
  104. package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
  105. package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
  106. package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
  107. package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
  108. package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
  109. package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
  110. package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
  111. package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
  112. package/modules/bot-runtime/read-model.mjs +19 -4
  113. package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
  114. package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
  115. package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
  116. package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
  117. package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
  118. package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
  119. package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
  120. package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
  121. package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
  122. package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
  123. package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
  124. package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
  125. package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
  126. package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
  127. package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
  128. package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
  129. package/modules/independent-runtime/index.mjs +152 -62
  130. package/modules/independent-runtime/remote-tools.mjs +8 -5
  131. package/modules/openmaus-protocol/NOTICE +10 -0
  132. package/modules/openmaus-protocol/README.md +15 -0
  133. package/modules/openmaus-protocol/SOURCE.json +68 -0
  134. package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
  135. package/modules/openmaus-protocol/index.mjs +1 -0
  136. package/modules/openmaus-protocol/transport.mjs +77 -0
  137. package/modules/openmaus-protocol/vendor/LICENSE +202 -0
  138. package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
  139. package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
  140. package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
  141. package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
  142. package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
  143. package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
  144. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
  145. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
  146. package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
  147. package/package.json +20 -6
  148. package/packages/bot-acp/README.md +156 -0
  149. package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
  150. package/packages/bot-acp/licenses/NOTICE.md +27 -0
  151. package/packages/bot-acp/src/acp-adapter.mjs +795 -0
  152. package/packages/bot-acp/src/acp-host.mjs +148 -0
  153. package/packages/bot-acp/src/native-launch.mjs +121 -0
  154. package/packages/bot-acp/src/permission-policy.mjs +283 -0
  155. package/packages/bot-acp/src/rpc-codec.mjs +265 -0
  156. package/packages/bot-acp/src/session-core.mjs +581 -0
  157. package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
  158. package/packages/bot-engines/README.md +281 -0
  159. package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
  160. package/packages/bot-engines/licenses/NOTICE.md +32 -0
  161. package/packages/bot-engines/src/claude-engine.mjs +772 -0
  162. package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
  163. package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
  164. package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
  165. package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
  166. package/packages/bot-engines/src/claude-protocol.mjs +177 -0
  167. package/packages/bot-engines/src/driver-catalog.mjs +334 -0
  168. package/packages/bot-engines/src/host-loop-request.mjs +95 -0
  169. package/packages/bot-engines/src/host-registrations.mjs +105 -0
  170. package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
  171. package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
  172. package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
  173. package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
  174. package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
  175. package/packages/cli-runtime/src/native-peer.ps1 +51 -3
  176. package/packages/cli-runtime/src/native-process.mjs +77 -14
  177. package/packages/model-router-plugin/README.md +3 -3
  178. package/packages/model-router-plugin/package.json +1 -1
  179. package/packages/orchestration/src/binding-product.mjs +19 -0
  180. package/packages/orchestration/src/bot-data-actions.mjs +45 -0
  181. package/packages/orchestration/src/bot-data-chart.mjs +109 -0
  182. package/packages/orchestration/src/bot-data-context.mjs +44 -0
  183. package/packages/orchestration/src/bot-data-engine.mjs +809 -0
  184. package/packages/orchestration/src/bot-data-io.mjs +186 -0
  185. package/packages/orchestration/src/bot-data-sql.mjs +195 -0
  186. package/packages/orchestration/src/bot-data-surface.mjs +144 -0
  187. package/packages/orchestration/src/bot-data-tools.mjs +68 -0
  188. package/packages/orchestration/src/bot-data-worker.mjs +228 -0
  189. package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
  190. package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
  191. package/packages/orchestration/src/bot-loop.mjs +152 -19
  192. package/packages/orchestration/src/bot-notifications.mjs +159 -0
  193. package/packages/orchestration/src/bot-policy.mjs +43 -4
  194. package/packages/orchestration/src/bot-profile.mjs +171 -0
  195. package/packages/orchestration/src/bot-routine.mjs +4 -4
  196. package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
  197. package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
  198. package/packages/orchestration/src/bot-serving.mjs +164 -0
  199. package/packages/orchestration/src/bot-session-store.mjs +105 -9
  200. package/packages/orchestration/src/bot-settings.mjs +50 -11
  201. package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
  202. package/packages/orchestration/src/bot-thread-host.mjs +187 -0
  203. package/packages/orchestration/src/bot-threads.mjs +790 -0
  204. package/packages/orchestration/src/data-product-access.mjs +37 -0
  205. package/packages/orchestration/src/data-tool-contract.mjs +291 -0
  206. package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
  207. package/packages/orchestration/src/desktop-proxy.mjs +311 -0
  208. package/packages/orchestration/src/desktop-sessions.mjs +43 -9
  209. package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
  210. package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
  211. package/packages/orchestration/src/independent-chat-page.mjs +39 -0
  212. package/packages/orchestration/src/independent-chat-service.mjs +308 -0
  213. package/packages/orchestration/src/independent-context-state.mjs +44 -0
  214. package/packages/orchestration/src/independent-message-wire.mjs +84 -0
  215. package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
  216. package/packages/orchestration/src/independent-tool-families.mjs +73 -0
  217. package/packages/orchestration/src/independent-tool-host.mjs +344 -0
  218. package/packages/orchestration/src/main-bot-grant.mjs +15 -0
  219. package/packages/orchestration/src/main-model-routes.mjs +208 -0
  220. package/packages/orchestration/src/model-accounting.mjs +99 -35
  221. package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
  222. package/packages/orchestration/src/model-loop-frame.mjs +179 -0
  223. package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
  224. package/packages/orchestration/src/model-request-bounds.mjs +111 -0
  225. package/packages/orchestration/src/native-activity-contract.mjs +491 -0
  226. package/packages/orchestration/src/native-activity-service.mjs +102 -0
  227. package/packages/orchestration/src/native-activity-store.mjs +249 -0
  228. package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
  229. package/packages/orchestration/src/native-data-tools.mjs +100 -0
  230. package/packages/orchestration/src/native-remote-actions.mjs +742 -0
  231. package/packages/orchestration/src/native-team-tools.mjs +73 -0
  232. package/packages/orchestration/src/pairing-invite.mjs +37 -0
  233. package/packages/orchestration/src/product-composition.mjs +1559 -1001
  234. package/packages/orchestration/src/remote-machines.mjs +82 -14
  235. package/packages/orchestration/src/routine-execution.mjs +5 -3
  236. package/packages/orchestration/src/routine-session.mjs +19 -13
  237. package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
  238. package/packages/orchestration/src/task-journal.mjs +29 -0
  239. package/packages/orchestration/src/task-product.mjs +17 -0
  240. package/packages/orchestration/src/team-assignment-store.mjs +120 -0
  241. package/packages/orchestration/src/team-bridge.mjs +282 -0
  242. package/packages/orchestration/src/team-scheduler.mjs +83 -0
  243. package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
  244. package/packages/orchestration/src/team-tool-contract.mjs +284 -0
  245. package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
  246. package/packages/orchestration/src/web-panel-actions.mjs +103 -0
  247. package/packages/orchestration/src/web-panel-framing.mjs +227 -0
  248. package/packages/orchestration/src/web-panel-store.mjs +206 -0
  249. package/remote-computer-agent/README.md +39 -29
  250. package/remote-computer-agent/build.mjs +3 -0
  251. package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
  252. package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
  253. package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
  254. package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
  255. package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
  256. package/remote-computer-agent/dist/cli.mjs +1688 -168
  257. package/remote-computer-agent/dist/cli.mjs.map +4 -4
  258. package/remote-computer-agent/dist/index.mjs +1579 -122
  259. package/remote-computer-agent/dist/index.mjs.map +4 -4
  260. package/remote-computer-agent/dist/native-peer.ps1 +51 -3
  261. package/remote-computer-agent/package-lock.json +308 -2
  262. package/remote-computer-agent/package.json +2 -1
  263. package/remote-computer-agent/pair-device.ps1 +20 -0
  264. package/remote-computer-agent/setup.ps1 +38 -16
  265. package/remote-computer-agent/src/cli.ts +24 -3
  266. package/remote-computer-agent/src/desktop-session.ts +11 -3
  267. package/remote-computer-agent/src/e2e-server.ts +81 -0
  268. package/remote-computer-agent/src/pairing-page.ts +35 -0
  269. package/remote-computer-agent/src/protocol.ts +25 -25
  270. package/remote-computer-agent/src/secure-pairing.ts +105 -0
  271. package/remote-computer-agent/src/store.ts +20 -5
  272. package/remote-computer-agent/src/transport.ts +39 -15
@@ -0,0 +1,175 @@
1
+ // One prepared model generation, admitted and dispatched as the same generation.
2
+ //
3
+ // The installed SDK's `LlmRuntime.prepareCall(config, signal, configure)` is the only
4
+ // public seam that binds one adapter registration's metadata to its own dispatch: it
5
+ // captures the registration and the adapter's `prepareCall` result at prepare time, and
6
+ // its returned one-shot `stream(options)` dispatches against exactly that capture, failing
7
+ // `INVALID_PREPARED_CALL` on a second dispatch or a call-config change. A Host that reads
8
+ // `resolveModelInfo` at admission and then lets the waterfall's `next()` re-resolve the
9
+ // model at dispatch can price one generation and dispatch another when a registration is
10
+ // replaced in between — the race `model-request-bounds` documents and cannot close, because
11
+ // its admission wraps the waterfall `next()`, not a prepared call.
12
+ //
13
+ // This module moves the independent path's admission onto that fence. A prepared call is
14
+ // made once, its exact metadata and defaults are captured into a Host-owned frame keyed by
15
+ // the request's own signal, and the accounting admits against that frame instead of reading
16
+ // the current registration again. The clamp happens in `configure`, before the config is
17
+ // frozen, so the dispatched config, the receipt hash and the estimate all describe the one
18
+ // generation that actually streams. Nothing here holds money, issues a receipt, or bypasses
19
+ // the policy, the secret rewrite or the `llm/stream` waterfall: it only decides the bound
20
+ // and hands the product's own listener the same generation it admitted.
21
+ //
22
+ // Each dispatch derives its own signal, linked to cancellation but used only to find
23
+ // captured capacity, never as Bot authentication. The product bridge redacts through
24
+ // the Host before preparation and refuses a later request-object replacement: an
25
+ // ordinary llm.stream re-entry would lose the SDK's prepared dispatch. The immutable
26
+ // frame therefore includes both the exact route and its original request identity.
27
+
28
+ const fail = (code, detail) => { throw Object.assign(new Error(code), { code, ...detail }) }
29
+ const positive = value => Number.isSafeInteger(value) && value > 0 ? value : null
30
+
31
+ /** The call-config fields the SDK compares for a prepared dispatch (`callConfigEquals`). */
32
+ const CALL_CONFIG_FIELDS = Object.freeze(['maxTokens', 'reasoningEffort', 'temperature', 'stop'])
33
+
34
+ /** Only the call-config fields — never `signal`, `messages`, `tools` or `system`. */
35
+ function callConfigOf(options) {
36
+ const config = { provider: options.provider, model: options.model }
37
+ for (const field of CALL_CONFIG_FIELDS) if (options[field] !== undefined) config[field] = options[field]
38
+ return config
39
+ }
40
+
41
+ /** The resolved call-config fields to restate onto the dispatched request. */
42
+ function resolvedFields(config) {
43
+ const fields = {}
44
+ for (const field of CALL_CONFIG_FIELDS) if (config[field] !== undefined) fields[field] = config[field]
45
+ return fields
46
+ }
47
+
48
+ /**
49
+ * The output bound one prepared call will dispatch with, decided before the config freezes.
50
+ *
51
+ * A configured allowance is a price, not a capacity; the pinned adapter's `defaultMaxTokens`
52
+ * is the per-request cap the SDK materializes into a request that declares none. The bound
53
+ * the provider sees is the smaller of what the request or the adapter declares and the
54
+ * allowance. Because `configure` works on a detached clone and returns new controls, the
55
+ * clamp always lands — a request frozen above the allowance is lowered to the allowance
56
+ * here, which is the correct recovery (lower the dispatched `maxTokens`), not a refusal and
57
+ * never a lowered allowance.
58
+ *
59
+ * @param requested - the request's own `maxTokens`; `null` when it declares none.
60
+ * @param allowance - the priced output allowance; `null` when the funding is not priced
61
+ * per token (a subscription), in which case nothing is clamped and the adapter default
62
+ * stands.
63
+ * @param adapterDefault - the pinned adapter's declared per-request cap; `null` when none.
64
+ * @returns `{ok:true,maxTokens,clamp}`, or `{ok:false,code}` when the request's own bound is
65
+ * present but invalid.
66
+ */
67
+ export function planConfiguredOutput({ requested = null, allowance = null, adapterDefault = null } = {}) {
68
+ if (requested !== null && !(Number.isSafeInteger(requested) && requested >= 1)) return { ok: false, code: 'MODEL_OUTPUT_BOUNDARY_REQUIRED' }
69
+ if (allowance === null) return { ok: true, maxTokens: requested ?? null, clamp: false }
70
+ const known = requested ?? adapterDefault ?? null
71
+ const maxTokens = known === null ? allowance : Math.min(known, allowance)
72
+ return { ok: true, maxTokens, clamp: known === null || known > allowance }
73
+ }
74
+
75
+ /**
76
+ * The Host-owned frame store. Keyed by the request signal so a rebuilt request (the secret
77
+ * rewrite) keeps its frame, and unforgeable because a request cannot insert into a WeakMap
78
+ * the Host owns. Nothing is cached across calls: the frame lives exactly as long as the one
79
+ * dispatch that set it.
80
+ */
81
+ export function createModelFrameStore() {
82
+ const frames = new WeakMap()
83
+ return Object.freeze({
84
+ set: (signal, frame) => { if (signal) frames.set(signal, frame) },
85
+ read: signal => (signal ? frames.get(signal) ?? null : null),
86
+ delete: signal => { if (signal) frames.delete(signal) },
87
+ })
88
+ }
89
+
90
+ /** Apply the Host's public string redactor before binding an adapter generation. */
91
+ export function redactPreparedModelRequest(options, redact) {
92
+ if (typeof redact !== 'function') return options
93
+ const walk = value => {
94
+ if (typeof value === 'string') return redact(value)
95
+ if (Array.isArray(value)) {
96
+ const next = value.map(walk)
97
+ return next.some((item, index) => item !== value[index]) ? next : value
98
+ }
99
+ if (value && Object.getPrototypeOf(value) === Object.prototype) {
100
+ const next = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, walk(item)]))
101
+ return Object.keys(next).some(key => next[key] !== value[key]) ? next : value
102
+ }
103
+ return value
104
+ }
105
+ const messages = walk(options.messages), system = walk(options.system)
106
+ return messages === options.messages && system === options.system ? options : { ...options, messages, ...(system === undefined ? {} : { system }) }
107
+ }
108
+
109
+ /**
110
+ * The prepared-dispatch seam for the independent path.
111
+ *
112
+ * @param llm - the live `llm` service, or a function returning it.
113
+ * @param frames - the shared {@link createModelFrameStore} the accounting reads.
114
+ * @param fundingFor - `({botId, provider, model}) => funding | null`, the Host's committed
115
+ * funding read. Only its priced output allowance is used, and only as a capacity to clamp
116
+ * against — never as a token count or a cost. A throw or a null is treated as unpriced, so
117
+ * the accounting stays the single authority over money and refuses in its own coded way.
118
+ * @returns `{dispatch}`, where `dispatch(options, {botId})` is an async iterable that prepares
119
+ * the call once, records the frame, and streams the captured generation.
120
+ */
121
+ export function createPreparedModelDispatch({ llm = null, frames = null, fundingFor = null } = {}) {
122
+ if (!frames || typeof frames.set !== 'function') fail('MODEL_FRAME_STORE_REQUIRED')
123
+ const service = () => (typeof llm === 'function' ? llm() : llm)
124
+
125
+ async function* dispatch(options, context = {}) {
126
+ if (options.maxTokens !== undefined && positive(options.maxTokens) === null) fail('MODEL_OUTPUT_BOUNDARY_REQUIRED')
127
+ const runtime = service()
128
+ if (!runtime || typeof runtime.prepareCall !== 'function') fail('MODEL_PREPARED_UNAVAILABLE')
129
+ const { botId } = context
130
+ let funding = null
131
+ if (botId && typeof fundingFor === 'function') {
132
+ try { funding = await fundingFor({ botId, provider: options.provider, model: options.model }) } catch { funding = null }
133
+ }
134
+ const allowance = funding && funding.funding === 'api' ? positive(funding.maxOutputTokens) : null
135
+
136
+ // `configure` is pure and synchronous: it decides the bound from the captured metadata
137
+ // and the already-resolved allowance, and records the capacities the accounting admits on.
138
+ let contextWindow = null, defaultMaxTokens = null
139
+ const configure = (controls, info) => {
140
+ contextWindow = positive(info.context?.contextWindow)
141
+ defaultMaxTokens = positive(info.defaultMaxTokens)
142
+ const plan = planConfiguredOutput({ requested: controls.maxTokens ?? null, allowance, adapterDefault: defaultMaxTokens })
143
+ if (!plan.ok) fail(plan.code)
144
+ // Unpriced (a subscription), or already within the bound: leave the controls untouched,
145
+ // so the SDK materializes the adapter's own default exactly as it would with no Host.
146
+ if (allowance === null || !plan.clamp) return controls
147
+ // Above the allowance, or nothing declared at all: lower the dispatched bound to the
148
+ // allowance. This is the only safe dispatch, and it lands because `configure` returns
149
+ // new controls on a detached clone regardless of how the caller froze its request.
150
+ return { ...controls, maxTokens: plan.maxTokens }
151
+ }
152
+
153
+ const signal = options.signal ? AbortSignal.any([options.signal]) : new AbortController().signal
154
+ const prepared = await runtime.prepareCall(callConfigOf(options), signal, configure)
155
+ // Restate the resolved call-config so the dispatched request equals the prepared one:
156
+ // `prepared.stream` refuses `INVALID_PREPARED_CALL` on any call-config mismatch.
157
+ const dispatchOptions = { ...options, ...resolvedFields(prepared.config), signal }
158
+ const frame = Object.freeze({ provider: options.provider, model: options.model, fundingRevision: funding?.fundingRevision ?? null,
159
+ bounds: Object.freeze({ contextWindow, defaultMaxTokens }), request: dispatchOptions })
160
+ frames.set(signal, frame)
161
+ try {
162
+ // A Host gate between binding the generation and dispatching it. The frame is already
163
+ // recorded, so the accounting that runs inside the stream admits against this capture;
164
+ // anything that swaps the current registration here cannot reach the one-shot stream,
165
+ // which dispatches the generation it was prepared with. The gate may throw to refuse
166
+ // before any dispatch, and it runs with no receipt, reservation or provider call yet.
167
+ if (typeof context.beforeDispatch === 'function') await context.beforeDispatch({ prepared, frame, options: dispatchOptions })
168
+ yield* prepared.stream(dispatchOptions)
169
+ } finally {
170
+ frames.delete(signal)
171
+ }
172
+ }
173
+
174
+ return Object.freeze({ dispatch })
175
+ }
@@ -0,0 +1,111 @@
1
+ // The bounds a Host can prove about one model request, and the ones it cannot.
2
+ //
3
+ // A configured allowance is a price, not a capacity: it says what the Host is
4
+ // willing to pay for, never what the model accepts. This module reads the two
5
+ // capacities that actually decide what a provider request is, from the pinned
6
+ // adapter that owns the exact route, through the installed SDK's own public
7
+ // metadata seam (`LlmRuntime.resolveModelInfo`):
8
+ //
9
+ // * `context.contextWindow` — the whole window the route accepts, input and
10
+ // output together. It is the honest input bound: it already accounts for
11
+ // images, tool declarations and adapter framing, so no tokenizer and no
12
+ // source-length estimate stands in for it.
13
+ // * `defaultMaxTokens` — the per-request output cap the adapter materializes
14
+ // into a request that declares none. The SDK's own call-config resolution
15
+ // does exactly that before dispatch, so this is the bound the provider
16
+ // receives for an unbounded request, not a guess about one.
17
+ //
18
+ // Everything here fails closed with a coded error. A missing context window is
19
+ // a missing bound, never a convenient number.
20
+
21
+ const fail=(code,detail)=>{throw Object.assign(new Error(code),{code,...detail})}
22
+ const positive=value=>Number.isSafeInteger(value)&&value>0?value:null
23
+ const routeName=(value,what)=>{
24
+ if(typeof value!=='string'||value.length===0||value.length>512)fail('MODEL_ROUTE_INVALID',{what})
25
+ return value
26
+ }
27
+
28
+ /**
29
+ * Resolve the pinned adapter's declared capacity for one exact provider/model
30
+ * route. Nothing is cached: an adapter registration can be replaced under a
31
+ * live Host, and one generation's window must never be combined with another
32
+ * generation's endpoint.
33
+ *
34
+ * @param llm - the live `llm` service, or a function returning it.
35
+ * @param provider - provider route; a route name never carries a namespace.
36
+ * @param model - exact model id; a model id may carry one (`vendor/two`).
37
+ * @param signal - cancellation for the adapter-owned lookup.
38
+ * @returns frozen `{provider,model,contextWindow,defaultMaxTokens}`, where a
39
+ * missing `defaultMaxTokens` is `null` rather than an invented cap.
40
+ */
41
+ export async function resolveModelRouteBounds({llm,provider,model,signal=null}){
42
+ const route={provider:routeName(provider,'provider'),model:routeName(model,'model')}
43
+ // The Host splits a saved route at its first slash, so a provider name that
44
+ // carried one would resolve a different route's model and price the wrong one.
45
+ if(route.provider.includes('/'))fail('MODEL_ROUTE_INVALID',{what:'provider',reason:'namespace'})
46
+ if(!llm||typeof llm.resolveModelInfo!=='function')fail('MODEL_BOUND_SOURCE_REQUIRED')
47
+ if(signal?.aborted)fail('MODEL_BOUND_UNAVAILABLE',{reason:'aborted'})
48
+ let info
49
+ try{info=await llm.resolveModelInfo(route.provider,route.model,signal??undefined)}
50
+ catch(error){fail('MODEL_BOUND_UNAVAILABLE',{reason:typeof error?.code==='string'?error.code:'unavailable'})}
51
+ if(!info||typeof info!=='object')fail('MODEL_BOUND_UNAVAILABLE',{reason:'empty'})
52
+ // An adapter that answers about another route would price another model.
53
+ if(info.provider!==route.provider||info.id!==route.model)fail('MODEL_BOUND_MISMATCH',{provider:route.provider,model:route.model})
54
+ const contextWindow=positive(info.context?.contextWindow)
55
+ if(contextWindow===null)fail('MODEL_INPUT_BOUND_REQUIRED',{provider:route.provider,model:route.model})
56
+ return Object.freeze({provider:route.provider,model:route.model,contextWindow,defaultMaxTokens:positive(info.defaultMaxTokens)})
57
+ }
58
+
59
+ /**
60
+ * The reusable Host resolver. `bounds` answers both capacities for one route;
61
+ * `inputBound` answers only the trusted input upper bound, which is the whole
62
+ * declared window.
63
+ */
64
+ export function createModelBoundResolver({llm=null}={}){
65
+ const service=()=>typeof llm==='function'?llm():llm
66
+ const bounds=async({provider,model,signal=null})=>resolveModelRouteBounds({llm:service(),provider,model,signal})
67
+ return Object.freeze({bounds,inputBound:async({provider,model,signal=null})=>(await bounds({provider,model,signal})).contextWindow})
68
+ }
69
+
70
+ /**
71
+ * The output bound one request will actually dispatch with, or the refusal that
72
+ * keeps it out of the ledger.
73
+ *
74
+ * @param requested - the request's own `maxTokens`; `null` when it declares none.
75
+ * @param allowance - the Host-configured priced output allowance.
76
+ * @param adapterDefault - the pinned adapter's declared per-request output cap.
77
+ * @returns `{ok:true,maxTokens,source}` with `source` one of `request`,
78
+ * `adapter-default` or `clamp`, or `{ok:false,code}`. A `clamp` result also
79
+ * carries `refusal`: the code to fail with when this request object cannot
80
+ * carry the clamp.
81
+ */
82
+ export function planOutputBound({requested=null,allowance,adapterDefault=null}){
83
+ const cap=positive(allowance)
84
+ if(cap===null)return{ok:false,code:'MODEL_OUTPUT_ALLOWANCE_REQUIRED'}
85
+ const declared=requested===undefined?null:requested
86
+ if(declared!==null&&positive(declared)===null)return{ok:false,code:'MODEL_OUTPUT_BOUNDARY_REQUIRED'}
87
+ const known=declared!==null?declared:positive(adapterDefault)
88
+ if(known!==null&&known<=cap)return{ok:true,maxTokens:known,source:declared!==null?'request':'adapter-default'}
89
+ // The bound the provider will see is above the allowance, or is the provider's
90
+ // own default that nothing declared. Either way the only safe dispatch is the
91
+ // one this request can be made to carry; whether it can is the caller's
92
+ // question, and the refusal names which of the two it was.
93
+ return{ok:true,maxTokens:cap,source:'clamp',refusal:known===null?'MODEL_OUTPUT_BOUND_UNBOUNDED':'MODEL_OUTPUT_BOUND_EXCEEDED'}
94
+ }
95
+
96
+ /**
97
+ * Whether one field of a request object can carry a value the Host writes.
98
+ *
99
+ * Mutability is per field, and it is independent of every other field's: a
100
+ * request whose signal is read-only may still carry a writable `maxTokens`, and
101
+ * the reverse. Only a plain writable data property — present, or addable on an
102
+ * extensible object — counts as writable; an accessor is never written, because
103
+ * restoring one exactly is not something a defineProperty round-trip promises.
104
+ *
105
+ * @returns frozen `{present,canWrite,descriptor}`; `descriptor` is the own
106
+ * property descriptor to restore, or `null` when the field was absent.
107
+ */
108
+ export function requestFieldAccess(options,field){
109
+ const descriptor=Object.getOwnPropertyDescriptor(options,field)
110
+ return Object.freeze({present:Boolean(descriptor),canWrite:descriptor?descriptor.writable===true:Object.isExtensible(options),descriptor:descriptor??null})
111
+ }