cartethyia 2.5.3 → 2.5.6

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 (456) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  60. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  61. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  62. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  63. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  64. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
  66. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  70. package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
  74. package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
  76. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
  78. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  79. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  80. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
  82. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
  84. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  85. package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
  86. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  87. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
  88. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  89. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  90. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
  94. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
  96. package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
  98. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
  100. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
  102. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  103. package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
  104. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  105. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  106. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  107. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  108. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  109. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  110. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  111. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
  112. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  113. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  114. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  115. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  116. package/dist/dashboard/index.html +1 -1
  117. package/dist/dashboard/index.html.gz +0 -0
  118. package/dist/dashboard/precompressed-assets.json +57 -57
  119. package/migrations/README.md +52 -52
  120. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  121. package/package.json +96 -95
  122. package/scripts/cli/admin.ts +125 -0
  123. package/scripts/cli/autostart.ts +237 -0
  124. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  125. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
  126. package/scripts/cli/doctor.ts +37 -0
  127. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  128. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  129. package/scripts/cli/instance.ts +224 -0
  130. package/scripts/cli/main.ts +88 -18
  131. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  132. package/scripts/cli/port.ts +100 -0
  133. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  134. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  135. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  136. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  137. package/scripts/cli/tray/index.ts +62 -12
  138. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  139. package/scripts/cli/update.ts +40 -0
  140. package/scripts/cli/version.ts +67 -0
  141. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  142. package/src/boot/app.ts +11 -7
  143. package/src/boot/dependencies.ts +50 -19
  144. package/src/boot/timeout.ts +2 -13
  145. package/src/config/cooldowns.ts +186 -138
  146. package/src/config/network.ts +8 -14
  147. package/src/config/providers.ts +7 -10
  148. package/src/config/runtime.ts +76 -76
  149. package/src/config/security.ts +15 -0
  150. package/src/config/spec.ts +52 -7
  151. package/src/config/telemetry.ts +26 -0
  152. package/src/config/timeouts.ts +0 -6
  153. package/src/console/admin/model-abuse/contracts.ts +28 -6
  154. package/src/console/admin/model-abuse/routes.ts +56 -3
  155. package/src/console/api-keys/bucket-spend.ts +15 -4
  156. package/src/console/api-keys/contracts.ts +410 -414
  157. package/src/console/api-keys/routes.ts +881 -880
  158. package/src/console/auth/credentials.ts +3 -3
  159. package/src/console/auth/setup-service.ts +2 -2
  160. package/src/console/backup/contracts.ts +15 -2
  161. package/src/console/backup/export-endpoint.ts +6 -2
  162. package/src/console/backup/nine-router.ts +2 -5
  163. package/src/console/backup/service.ts +4 -0
  164. package/src/console/backup/store.ts +192 -29
  165. package/src/console/backup/validate.ts +3 -45
  166. package/src/console/cli-tools/fs-ops.ts +487 -487
  167. package/src/console/console-router-guards.ts +1 -1
  168. package/src/console/console-router.ts +8 -0
  169. package/src/console/data-management/contracts.ts +100 -0
  170. package/src/console/data-management/operations.ts +234 -0
  171. package/src/console/data-management/routes.ts +103 -0
  172. package/src/console/domain-registration.ts +45 -1
  173. package/src/console/observability/contracts.ts +524 -485
  174. package/src/console/observability/live.ts +22 -0
  175. package/src/console/observability/store.ts +1138 -1018
  176. package/src/console/observability/usage-dimensions.ts +4 -4
  177. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  178. package/src/console/providers/catalog/contracts.ts +6 -0
  179. package/src/console/providers/catalog/model-operations.ts +5 -2
  180. package/src/console/providers/catalog/provider-operations.ts +24 -4
  181. package/src/console/providers/catalog/public-model-store.ts +8 -11
  182. package/src/console/providers/catalog/routes.ts +7 -0
  183. package/src/console/providers/catalog/store.ts +1553 -1532
  184. package/src/console/providers/oauth/routes.ts +8 -12
  185. package/src/console/quota/account-quota-shared.ts +52 -39
  186. package/src/console/quota/account-quota-tenant.ts +6 -6
  187. package/src/console/quota/quota-view.ts +1 -1
  188. package/src/console/quota/refresh.ts +441 -429
  189. package/src/console/remote-support/contracts.ts +61 -0
  190. package/src/console/remote-support/routes.ts +65 -0
  191. package/src/console/remote-support/service.ts +207 -0
  192. package/src/console/response-compression.ts +0 -3
  193. package/src/console/routing/pools/import-parse.ts +206 -0
  194. package/src/console/routing/pools/operations.ts +543 -501
  195. package/src/console/routing/pools/probe-result.ts +3 -2
  196. package/src/console/routing/pools/probe-verdict.ts +132 -132
  197. package/src/console/routing/pools/routes.ts +181 -164
  198. package/src/console/routing/pools/store.ts +8 -8
  199. package/src/console/settings/contracts.ts +95 -2
  200. package/src/console/settings/store.ts +81 -3
  201. package/src/console/shared/cursor.ts +8 -8
  202. package/src/console/shared/errors.ts +1 -3
  203. package/src/console/updates/routes.ts +16 -0
  204. package/src/main.ts +10 -0
  205. package/src/network/http2-bidi.ts +2 -4
  206. package/src/network/pool/agent.ts +478 -478
  207. package/src/network/pool/selector.ts +5 -4
  208. package/src/network/retry.ts +1 -2
  209. package/src/network/ssrf.ts +5 -4
  210. package/src/observability/event-compaction.ts +317 -0
  211. package/src/observability/event-day-index.ts +440 -0
  212. package/src/observability/event-log.ts +155 -6
  213. package/src/observability/logger.ts +6 -6
  214. package/src/observability/payload-capture.ts +158 -33
  215. package/src/observability/payload-store.ts +82 -61
  216. package/src/observability/redaction.ts +5 -6
  217. package/src/observability/request-notify.ts +43 -0
  218. package/src/observability/telemetry-status.ts +8 -9
  219. package/src/observability/token-speed.ts +5 -4
  220. package/src/persistence/db-mode.ts +7 -7
  221. package/src/persistence/health-event-flush.ts +36 -0
  222. package/src/persistence/readiness.ts +1 -1
  223. package/src/persistence/schema.ts +75 -29
  224. package/src/persistence/shared-cache.ts +1 -2
  225. package/src/persistence/stores/api-key-store.ts +18 -6
  226. package/src/persistence/stores/share-store.ts +4 -3
  227. package/src/persistence/stores/telemetry-store.ts +33 -11
  228. package/src/protocol/canonical-model.ts +11 -3
  229. package/src/protocol/codecs.ts +139 -6
  230. package/src/protocol/gateway-error.ts +6 -4
  231. package/src/protocol/image-clamp.ts +19 -1
  232. package/src/protocol/registry.ts +8 -0
  233. package/src/protocol/request/chat.ts +21 -1
  234. package/src/protocol/request/codex.ts +10 -1
  235. package/src/protocol/request/gemini.ts +9 -2
  236. package/src/protocol/request/messages.ts +18 -7
  237. package/src/protocol/request/responses.ts +9 -1
  238. package/src/protocol/response/chat.ts +34 -4
  239. package/src/protocol/response/codex.ts +7 -4
  240. package/src/protocol/response/dsml.ts +140 -28
  241. package/src/protocol/response/responses.ts +11 -5
  242. package/src/protocol/stream-error-frames.ts +4 -5
  243. package/src/providers/adapters/compatible-adapter.ts +3 -0
  244. package/src/providers/authentication/jwt-validator.ts +9 -10
  245. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  246. package/src/providers/bundled-discovery-providers.ts +122 -119
  247. package/src/providers/configured-openai-providers.ts +4 -4
  248. package/src/providers/default-registry.ts +5 -4
  249. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  250. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  251. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  252. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  253. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  254. package/src/providers/discovery/probe-phases.ts +4 -4
  255. package/src/providers/discovery/probe-wire.ts +6 -13
  256. package/src/providers/discovery/probing-service.ts +1117 -1045
  257. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  258. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  259. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  260. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  261. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  262. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  263. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  264. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  265. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  266. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  267. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  268. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  269. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  270. package/src/providers/integrations/claude/claude.ts +608 -608
  271. package/src/providers/integrations/cline/cline-quota.ts +27 -20
  272. package/src/providers/integrations/cline/cline.ts +537 -539
  273. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  274. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  275. package/src/providers/integrations/codex/codex.ts +2 -29
  276. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  277. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  278. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  279. package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
  280. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  281. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  282. package/src/providers/integrations/cursor/cursor.ts +1333 -960
  283. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  284. package/src/providers/integrations/factory/factory-models.ts +3 -5
  285. package/src/providers/integrations/factory/factory.ts +8 -1
  286. package/src/providers/integrations/gemini.ts +6 -1
  287. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  288. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  289. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  290. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  291. package/src/providers/integrations/grok/grok.ts +6 -7
  292. package/src/providers/integrations/hermes.ts +14 -18
  293. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  294. package/src/providers/integrations/kiro/kiro.ts +1 -1
  295. package/src/providers/integrations/mistral.ts +6 -6
  296. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  297. package/src/providers/integrations/opencode/provider.ts +19 -20
  298. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  299. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  300. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  301. package/src/providers/integrations/qoder/qoder.ts +884 -865
  302. package/src/providers/integrations/shared/connect.ts +6 -6
  303. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  304. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  305. package/src/providers/integrations/trae/trae.ts +2 -2
  306. package/src/providers/integrations/zcode/oauth.ts +6 -1
  307. package/src/providers/operations/account-health-service.ts +75 -27
  308. package/src/providers/operations/account-reset-service.ts +1 -1
  309. package/src/providers/operations/client-versions.ts +6 -6
  310. package/src/providers/operations/custom-cli-headers.ts +3 -4
  311. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  312. package/src/providers/operations/provider-catalog-service.ts +3 -4
  313. package/src/providers/provider-contracts.ts +7 -0
  314. package/src/providers/provider-metadata.ts +10 -8
  315. package/src/providers/provider-registry.ts +6 -7
  316. package/src/providers/provider-usage.ts +1 -1
  317. package/src/providers/quota/quota-support.ts +8 -0
  318. package/src/security/abuse.ts +15 -10
  319. package/src/security/access-control.ts +144 -146
  320. package/src/security/admission/contracts.ts +11 -0
  321. package/src/security/admission/in-memory-store.ts +4 -3
  322. package/src/security/admission/lease-sweep.ts +6 -6
  323. package/src/security/admission/reasons.ts +5 -1
  324. package/src/security/admission/redis-store.ts +211 -210
  325. package/src/security/admission/service.ts +4 -0
  326. package/src/security/api-key-auth.ts +27 -17
  327. package/src/security/ip-boundary.ts +155 -15
  328. package/src/security/model-abuse.ts +366 -94
  329. package/src/security/outbound-headers.ts +3 -4
  330. package/src/shared/sleep.ts +4 -0
  331. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  332. package/src/transport/dispatch/attempt-loop.ts +531 -530
  333. package/src/transport/dispatch/generation.ts +6 -0
  334. package/src/transport/dispatch/proxy-request.ts +24 -1
  335. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  336. package/src/transport/dispatch/stream-bridge.ts +1 -3
  337. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  338. package/src/transport/dispatch/streaming/stream.ts +19 -6
  339. package/src/transport/dispatch/upstream.ts +9 -0
  340. package/src/transport/failure-policy.ts +162 -8
  341. package/src/transport/guards/auth-guard.ts +7 -4
  342. package/src/transport/guards/concurrency-guard.ts +0 -5
  343. package/src/transport/guards/error-lifecycle.ts +13 -6
  344. package/src/transport/guards/pipeline.ts +15 -3
  345. package/src/transport/guards/request-context.ts +8 -2
  346. package/src/transport/request/inflight.ts +5 -6
  347. package/src/transport/request/preparer.ts +9 -8
  348. package/src/transport/request/state.ts +21 -8
  349. package/src/transport/request/tenant-preferences.ts +60 -0
  350. package/src/transport/routing/admission.ts +4 -0
  351. package/src/transport/routing/route-catalog.ts +65 -10
  352. package/src/transport/routing/route-model.ts +46 -14
  353. package/src/transport/routing/router.ts +21 -14
  354. package/src/transport/surface/completion.ts +2 -3
  355. package/src/transport/surface/dialects.ts +5 -6
  356. package/src/transport/surface/responses/encode.ts +38 -0
  357. package/src/transport/surface/responses/parse.ts +4 -0
  358. package/src/transport/translation/capabilities.ts +2 -3
  359. package/src/transport/translation/quirks.ts +3 -3
  360. package/src/workers/buddy-growth-tasks.ts +5 -10
  361. package/src/workers/daily-checkin.ts +17 -23
  362. package/src/workers/quota-refresh-sweep.ts +26 -26
  363. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  364. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
  365. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  366. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  367. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
  368. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  369. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  370. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  371. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
  372. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  373. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  374. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
  375. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  376. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
  377. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  378. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
  379. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  380. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
  381. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  382. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
  383. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  384. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
  385. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  386. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
  387. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  388. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  389. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  390. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  391. package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
  392. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  393. package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
  394. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  395. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  396. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  397. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  398. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  399. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  400. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  401. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  402. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  403. package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
  404. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  405. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  406. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  407. package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
  408. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  409. package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
  410. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  411. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  412. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  413. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  414. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  415. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  416. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  417. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  418. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  419. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  420. package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
  421. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  422. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  423. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  424. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  425. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  426. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  427. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  428. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  429. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  430. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  431. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  432. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  433. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  434. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  435. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  436. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  437. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  438. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  439. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  440. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  441. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  442. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  443. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  444. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  445. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  446. package/scripts/cli/command/admin.ts +0 -65
  447. package/scripts/cli/command/doctor.ts +0 -257
  448. package/scripts/cli/command/update.ts +0 -23
  449. package/scripts/cli/gateway/drain-token.ts +0 -78
  450. package/scripts/cli/gateway/instance-lock.ts +0 -105
  451. package/scripts/cli/gateway/stop.ts +0 -51
  452. package/scripts/cli/support/bun.ts +0 -27
  453. package/scripts/cli/support/version.ts +0 -37
  454. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
  455. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
  456. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -3,8 +3,7 @@
3
3
  * `src/protocol/`. This is the single home for the generic guards, UTF-16
4
4
  * well-formedness coercion, JSON/argument helpers, hash helpers, endpoint
5
5
  * URL construction, image-source resolution, and custom-header validation
6
- * that the per-wire request/response codecs build on — previously duplicated
7
- * across the Claude/Codex protocol modules and `providers/compatible-adapter`.
6
+ * that the per-wire request/response codecs build on.
8
7
  *
9
8
  * Provider-specific primitives that depend on provider identity (e.g. Codex
10
9
  * identity headers) deliberately live with their provider, not here.
@@ -273,6 +272,46 @@ const SUPPORTED_SCHEMA_KEYS: Readonly<Record<string, true>> = Object.freeze({
273
272
  maxProperties: true,
274
273
  });
275
274
 
275
+ /**
276
+ * True when a schema string carries a backslash before an underscore.
277
+ *
278
+ * Some clients emit a property or pattern key containing a literal `\_` (an
279
+ * escaped underscore). Upstreams reject it, so the escape has to be stripped —
280
+ * and stripping it can collapse two previously distinct keys onto the same
281
+ * name, which is why {@link mergeCollidingKeys} exists below.
282
+ */
283
+ function hasUnderscoreEscape(value: string): boolean {
284
+ return /\\+_/.test(value);
285
+ }
286
+
287
+ /**
288
+ * Merges map entries whose normalized keys collide, rather than overwriting.
289
+ *
290
+ * `allOf` accepts boolean schemas, so neither constraint is lost — a plain
291
+ * overwrite would silently drop one of two rules the caller actually sent.
292
+ */
293
+ function mergeCollidingKeys(entries: ReadonlyArray<readonly [string, unknown]>): Record<string, unknown> {
294
+ const merged: Record<string, unknown> = {};
295
+ for (const [key, value] of entries) {
296
+ const existing = merged[key];
297
+ if (existing === undefined) {
298
+ merged[key] = value;
299
+ continue;
300
+ }
301
+ merged[key] = { allOf: [existing, value] };
302
+ }
303
+ return merged;
304
+ }
305
+
306
+ /** One schema map's keys, with `\_` escapes stripped and collisions merged. */
307
+ function normalizeSchemaMapKeys(map: Record<string, unknown>): Record<string, unknown> {
308
+ const entries = Object.entries(map);
309
+ if (!entries.some(([key]) => hasUnderscoreEscape(key))) return map;
310
+ return mergeCollidingKeys(
311
+ entries.map(([key, value]) => [key.replace(/\\+_/g, "_"), value] as const),
312
+ );
313
+ }
314
+
276
315
  export function sanitizeSchemaForAnthropic(value: unknown): unknown {
277
316
  return sanitizeSchemaWalk(value, ROOT_WALK);
278
317
  }
@@ -292,10 +331,21 @@ function sanitizeSchemaWalk(value: unknown, walk: RewriteWalk): unknown {
292
331
  // preserve them while sanitizing each property's schema recursively.
293
332
  if (key === "properties" && isRecord(child)) {
294
333
  out.properties = Object.fromEntries(
295
- Object.entries(child).map(([name, schema]) => [name, sanitizeSchemaWalk(schema, inner)]),
334
+ // Escaped-underscore keys are normalized first: two names differing only
335
+ // by an escape would otherwise survive as two properties and be rebuilt
336
+ // into a `required` list the upstream rejects.
337
+ Object.entries(normalizeSchemaMapKeys(child)).map(([name, schema]) => [
338
+ name,
339
+ sanitizeSchemaWalk(schema, inner),
340
+ ]),
296
341
  );
297
342
  continue;
298
343
  }
344
+ // `patternProperties` is deliberately NOT re-emitted: it is absent from
345
+ // `SUPPORTED_SCHEMA_KEYS` because the Anthropic wire does not accept it, and
346
+ // writing it here would be undone by the keyword filter a few lines down
347
+ // anyway. Its keys are still walked below, so nested schemas are sanitized
348
+ // and an escaped-underscore key inside one is still normalized.
299
349
  // Drop wire-unsupported keywords so a strict upstream never 400s on
300
350
  // them — but never invent caller text to say so. The dropped names are
301
351
  // debug-logged by the caller-facing builder when that matters.
@@ -485,13 +535,12 @@ export function endpointUrl(
485
535
  return url;
486
536
  }
487
537
 
488
- /** Session-scoped state (turn-state / models-etag / session identity). */
538
+ /** Session-scoped state (turn-state / session identity). */
489
539
  export interface CodexSessionState {
490
540
  sessionId: string;
491
541
  threadId: string;
492
542
  windowId: string;
493
543
  turnState?: string;
494
- modelsEtag?: string;
495
544
  turnStartedAtUnixMs?: number;
496
545
  }
497
546
 
@@ -677,12 +726,96 @@ export function canonicalTerminal(input: {
677
726
  };
678
727
  }
679
728
 
729
+ /**
730
+ * JSON Schema keywords. A parameter root containing any of these is already a
731
+ * schema object, not a legacy property-name → schema map.
732
+ */
733
+ const SCHEMA_KEYWORDS: ReadonlySet<string> = new Set([
734
+ "$schema", "$id", "$ref", "$anchor", "$dynamicRef", "$dynamicAnchor", "$defs",
735
+ "$vocabulary", "$comment", "definitions", "type", "properties", "patternProperties",
736
+ "additionalProperties", "unevaluatedProperties", "propertyNames", "required",
737
+ "dependentRequired", "dependentSchemas", "dependencies", "minProperties", "maxProperties",
738
+ "allOf", "anyOf", "oneOf", "not", "if", "then", "else", "items", "prefixItems",
739
+ "additionalItems", "contains", "unevaluatedItems", "minItems", "maxItems", "uniqueItems",
740
+ "minContains", "maxContains", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum",
741
+ "multipleOf", "minLength", "maxLength", "pattern", "format", "contentEncoding",
742
+ "contentMediaType", "contentSchema", "enum", "const", "title", "description", "default",
743
+ "examples", "deprecated", "readOnly", "writeOnly",
744
+ ]);
745
+
746
+ /** The JSON Schema primitive type names. */
747
+ const SCHEMA_TYPES: ReadonlySet<string> = new Set([
748
+ "string", "number", "integer", "boolean", "object", "array", "null",
749
+ ]);
750
+
751
+ /**
752
+ * Rewrites one legacy tool parameter root into an explicit object schema.
753
+ *
754
+ * Two shapes reach here and are both rejected upstream as-is:
755
+ *
756
+ * - **An empty root.** `{}` carries no `type`, and a schema with no type is not
757
+ * a schema the upstream will accept — it answers `invalid_function_parameters`
758
+ * (CodeBuddy `11129`).
759
+ * - **A legacy property-name → schema map.** Every field names a parameter and
760
+ * carries its own boolean `required` flag, with no `{"type":"object",
761
+ * "properties":…}` wrapper around them. Old clients in the wild still emit
762
+ * this; the upstream wants the wrapper and the `required` array.
763
+ *
764
+ * Anything ambiguous is returned **untouched**. A root that already holds schema
765
+ * keywords, or one whose fields do not all look like `{type: <primitive>}`
766
+ * entries, is left alone: guessing there would rewrite a valid schema into a
767
+ * wrong one, which is worse than the refusal it was trying to avoid.
768
+ */
769
+ export function normalizeToolParameterRoot(value: unknown): unknown {
770
+ if (!isRecord(value)) return value;
771
+ if (Object.keys(value).length === 0) return { type: "object", properties: {} };
772
+ // Schema keywords (or an extension/annotation key) mean this is a real
773
+ // schema object already, not a property map.
774
+ if (SCHEMA_KEYWORDS.has(Object.keys(value)[0] ?? "")) return value;
775
+ if (Object.keys(value).some((name) => SCHEMA_KEYWORDS.has(name))) return value;
776
+ if (Object.keys(value).some((name) => name.startsWith("$") || name.startsWith("x-")))
777
+ return value;
778
+ const entries = Object.entries(value);
779
+ // A legacy map is identified by its per-field boolean `required`. Without at
780
+ // least one, this is not recognisably the legacy shape.
781
+ if (!entries.some(([, field]) => isRecord(field) && typeof field["required"] === "boolean"))
782
+ return value;
783
+ if (
784
+ !entries.every(
785
+ ([, field]) =>
786
+ isRecord(field) &&
787
+ typeof field["type"] === "string" &&
788
+ SCHEMA_TYPES.has(field["type"]) &&
789
+ (!("required" in field) || typeof field["required"] === "boolean"),
790
+ )
791
+ )
792
+ return value;
793
+
794
+ const properties: Record<string, unknown> = {};
795
+ const required: string[] = [];
796
+ for (const [name, field] of entries) {
797
+ const copy = { ...(field as Record<string, unknown>) };
798
+ if (copy["required"] === true) required.push(name);
799
+ delete copy["required"];
800
+ properties[name] = copy;
801
+ }
802
+ return {
803
+ type: "object",
804
+ properties,
805
+ ...(required.length > 0 ? { required } : {}),
806
+ };
807
+ }
808
+
680
809
  /**
681
810
  * Defaults a tool schema's `required` list to all declared properties while
682
811
  * preserving an explicit list, including optional properties.
812
+ *
813
+ * Also normalizes a legacy parameter root first (see
814
+ * {@link normalizeToolParameterRoot}), so a caller sending an empty root or a
815
+ * property-name → schema map still ships a schema the upstream accepts.
683
816
  */
684
817
  export function completeRequiredSchema(value: unknown): unknown {
685
- return completeRequiredWalk(value, ROOT_WALK);
818
+ return completeRequiredWalk(normalizeToolParameterRoot(value), ROOT_WALK);
686
819
  }
687
820
 
688
821
  function completeRequiredWalk(value: unknown, walk: RewriteWalk): unknown {
@@ -12,6 +12,8 @@ export type GatewayErrorCode =
12
12
  | "accounts_unavailable"
13
13
  | "accounts_rate_limited"
14
14
  | "capacity_exhausted"
15
+ | "provider_capacity_exhausted"
16
+ | "upstream_queued"
15
17
  | "proxy_pool_capacity_exceeded"
16
18
  | "proxy_pool_provider_capacity_exceeded"
17
19
  | "proxy_pool_blocked"
@@ -110,11 +112,11 @@ function stripLegacyOriginLabel(message: string): string {
110
112
  * stamped into it.
111
113
  *
112
114
  * The code is not lost — it travels in the envelope's `error.code` field, and
113
- * the HTTP status carries the class. It used to be prefixed into the text
115
+ * the HTTP status carries the class. Stamping it into the text
114
116
  * (`accounts_unavailable: Model 'x' has no available account (52 candidate(s)
115
- * unusable: credit_floor_reached, model_cooldown)`), which put internal enum
116
- * names in front of the operator's sentence and made the message unreadable
117
- * while adding nothing the field did not already carry.
117
+ * unusable: credit_floor_reached, model_cooldown)`) would put internal enum
118
+ * names in front of the operator's sentence and make the message unreadable
119
+ * while adding nothing the field does not already carry.
118
120
  *
119
121
  * Stripping is idempotent, and that is the point: a message that already
120
122
  * carries a `code:` prefix — stamped by an older builder, or by a code path
@@ -56,6 +56,17 @@ export interface ImageClampInput {
56
56
  readonly height?: number;
57
57
  /** Images in the whole request, including this one. Defaults to this one alone. */
58
58
  readonly imageCount?: number;
59
+ /**
60
+ * Per-tenant byte ceiling override, in bytes.
61
+ *
62
+ * Absent means the deployment default (`IMAGE_CLAMP_MAX_BYTES`). A tenant
63
+ * raising this asserts that its upstreams accept larger attachments: the
64
+ * clamp exists so an oversized block cannot poison every retry and every
65
+ * replayed turn with a request the upstream rejects whole. An override
66
+ * promising more than the provider accepts turns that protection back off,
67
+ * which is why it is a deliberate per-tenant choice and not a global knob.
68
+ */
69
+ readonly maxBytesOverride?: number;
59
70
  }
60
71
 
61
72
  export interface ImageClampDecision {
@@ -108,7 +119,14 @@ export function imageClampMaxDimensionPx(imageCount: number): number {
108
119
  export function clampImage(input: ImageClampInput): ImageClampDecision {
109
120
  const imageCount = input.imageCount ?? 1;
110
121
  const maxDimensionPx = imageClampMaxDimensionPx(imageCount);
111
- const maxBytes = IMAGE_CLAMP_MAX_BYTES;
122
+ // A non-finite or non-positive override is not a ceiling, it is a mistake:
123
+ // falling back to the default keeps a malformed preference from silently
124
+ // admitting (or rejecting) every image.
125
+ const override = input.maxBytesOverride;
126
+ const maxBytes =
127
+ typeof override === "number" && Number.isFinite(override) && override > 0
128
+ ? Math.floor(override)
129
+ : IMAGE_CLAMP_MAX_BYTES;
112
130
  const longestEdge = Math.max(input.width ?? 0, input.height ?? 0);
113
131
  if (longestEdge > maxDimensionPx) {
114
132
  return {
@@ -8,6 +8,11 @@ export interface CodecContext {
8
8
  readonly maxOutputTokens?: number;
9
9
  readonly promptCacheKey?: string;
10
10
  readonly signal?: AbortSignal;
11
+ /**
12
+ * Per-tenant ceiling for one inline attachment, in bytes. Absent means the
13
+ * deployment default; only wire encoders that clamp images read it.
14
+ */
15
+ readonly maxBytesOverride?: number;
11
16
  }
12
17
 
13
18
  /**
@@ -138,6 +143,9 @@ export async function encodeWireRequest(
138
143
  if (codec.family === "messages") {
139
144
  return codec.encodeWireRequest(request, {
140
145
  ...(context?.isOAuth === undefined ? {} : { isOAuth: context.isOAuth }),
146
+ ...(context?.maxBytesOverride === undefined
147
+ ? {}
148
+ : { maxBytesOverride: context.maxBytesOverride }),
141
149
  });
142
150
  }
143
151
  if (codec.family === "chat") {
@@ -353,7 +353,24 @@ export function canonicalToChatPayload(
353
353
  // `display: "omitted"`, and dropping it stripped the replay the
354
354
  // provider demands on the next request. `reasoning_content` is an
355
355
  // assistant-only field, so it is emitted here and nowhere else.
356
- if (m.role === "assistant" && hasReasoning) {
356
+ //
357
+ // Load-bearing requires *content* in the replay: a non-empty summary
358
+ // or a non-empty serialised payload. Pushing `{content:"",
359
+ // reasoning_content:""}` is the opposite of load-bearing — the
360
+ // upstream reads it as "thinking mode with the reasoning stripped"
361
+ // and rejects the whole request (`WorkBuddy/CodeBuddy 11151 "history
362
+ // not found"`). The canonical form that reaches this branch with no
363
+ // trace is an input `reasoning` item whose `summary` was absent and
364
+ // whose `payload` was `null` — a signature-less thinking block
365
+ // emitted under `display: "omitted"` and already stripped by an
366
+ // earlier hop. There is nothing to replay, so the turn is dropped
367
+ // instead of being forwarded as an invalid stub.
368
+ // This non-empty echo is load-bearing for non-buddy
369
+ // OpenAI-compatible providers. Buddy providers deliberately drop
370
+ // the resulting bare stub later in `dropEmptyBuddyMessages`
371
+ // (`providers/integrations/buddy/buddy-chat-shared.ts`) because
372
+ // their upstream rejects that shape with 11151.
373
+ if (m.role === "assistant" && hasReasoning && reasoningContent.length > 0) {
357
374
  messages.push({
358
375
  role: "assistant",
359
376
  content: "",
@@ -361,6 +378,9 @@ export function canonicalToChatPayload(
361
378
  });
362
379
  continue;
363
380
  }
381
+ // No trace to carry: skip the turn entirely rather than emit an
382
+ // empty assistant message the upstream will reject.
383
+ if (m.role === "assistant" && hasReasoning) continue;
364
384
  if (m.role !== "assistant" && hasReasoning && reasoningContent.length > 0) {
365
385
  const last = messages[messages.length - 1];
366
386
  if (last !== undefined && last["role"] === "tool") {
@@ -374,7 +374,16 @@ export function canonicalToCodexResponsesPayload(
374
374
  summary: summaryArray,
375
375
  encrypted_content: r.encrypted_content,
376
376
  };
377
- if (r.summary !== undefined || r.encrypted_content !== undefined) {
377
+ // The upstream binds encrypted reasoning to the item it was issued
378
+ // for and rejects a replay whose id does not match, so the id that
379
+ // came back with the payload is authoritative. A synthesized
380
+ // `rs_<hash>` never matched the real item and failed the whole
381
+ // request with "Encrypted content item_id did not match the target
382
+ // item id"; only fall back to it when no id survived, where a
383
+ // mismatch is unavoidable anyway.
384
+ if (typeof r.item_id === "string" && r.item_id.length > 0) {
385
+ item["id"] = r.item_id;
386
+ } else if (r.summary !== undefined || r.encrypted_content !== undefined) {
378
387
  const idSource = `${r.summary ?? ""}|${r.encrypted_content ?? ""}`;
379
388
  if (idSource.length > 1) {
380
389
  item["id"] = `rs_${hashToBase36(idSource)}`;
@@ -147,6 +147,7 @@ function geminiMediaPart(part: {
147
147
  function toGeminiPart(
148
148
  part: CanonicalRequest["messages"][number]["content"][number],
149
149
  toolNames: ReadonlyMap<string, string>,
150
+ maxBytesOverride?: number,
150
151
  ): Record<string, unknown>[] {
151
152
  if (part.kind === "text") return [{ text: part.text }];
152
153
  if (part.kind === "reasoning") {
@@ -173,6 +174,7 @@ function toGeminiPart(
173
174
  // whose attachment silently vanished.
174
175
  const decision = clampImage({
175
176
  bytes: base64DecodedByteLength(split.data),
177
+ ...(maxBytesOverride === undefined ? {} : { maxBytesOverride }),
176
178
  });
177
179
  if (decision.action === "reject") {
178
180
  // Only the byte ceiling refuses a block. A `downscale` decision is
@@ -255,7 +257,10 @@ function parseJsonObject(value: string): Record<string, unknown> {
255
257
  }
256
258
  }
257
259
 
258
- export function buildGeminiPayload(request: CanonicalRequest): Record<string, unknown> {
260
+ export function buildGeminiPayload(
261
+ request: CanonicalRequest,
262
+ options: { maxBytesOverride?: number } = {},
263
+ ): Record<string, unknown> {
259
264
  const systemParts: Array<{ text: string }> = [];
260
265
  const pushSystemText = (text: string): void => {
261
266
  if (!isClaudeBillingHeaderText(text)) systemParts.push({ text });
@@ -275,7 +280,9 @@ export function buildGeminiPayload(request: CanonicalRequest): Record<string, un
275
280
  .map((message) => {
276
281
  // Gemini roles: user or model (assistant -> model)
277
282
  const role = message.role === "assistant" ? "model" : message.role === "tool" ? "user" : "user";
278
- const parts = message.content.flatMap((block) => toGeminiPart(block, toolNames));
283
+ const parts = message.content.flatMap((block) =>
284
+ toGeminiPart(block, toolNames, options.maxBytesOverride),
285
+ );
279
286
  return { role, parts: parts.length ? parts : [{ text: "" }] };
280
287
  });
281
288
  const payload: Record<string, unknown> = { contents };
@@ -121,6 +121,7 @@ function applyImageClamp(
121
121
  block: ClaudeWireObject,
122
122
  imageCount: number,
123
123
  payload: unknown,
124
+ maxBytesOverride?: number,
124
125
  ): ClaudeWireObject {
125
126
  const source = block["source"];
126
127
  const data = isRecord(source) ? source["data"] : undefined;
@@ -131,6 +132,7 @@ function applyImageClamp(
131
132
  ...(width === undefined ? {} : { width }),
132
133
  ...(height === undefined ? {} : { height }),
133
134
  imageCount,
135
+ ...(maxBytesOverride === undefined ? {} : { maxBytesOverride }),
134
136
  });
135
137
  if (decision.action === "pass") return block;
136
138
  log.warn(
@@ -155,12 +157,11 @@ function applyImageClamp(
155
157
  * the upstream rejects anything else. A canonical image part is an opaque
156
158
  * origin payload, so it can arrive in a vocabulary that is *not* Anthropic's:
157
159
  * a Responses part carries `type: "input_image"` with a top-level `image_url`
158
- * string, and a Chat part carries a nested `image_url` object. Both used to be
159
- * forwarded with their own `type` intact (or with no `source` at all), so a
160
- * Responses- or Chat-origin image sent to a Claude model produced
161
- * `source.type: "input_image"` — an invalid block the provider refused.
160
+ * string, and a Chat part carries a nested `image_url` object. Forwarding either
161
+ * with its own `type` intact (or with no `source` at all) sends a Claude model
162
+ * `source.type: "input_image"` — an invalid block the provider refuses.
162
163
  *
163
- * Every shape now collapses to a valid source, preferring the cheapest
164
+ * Every shape collapses to a valid source, preferring the cheapest
164
165
  * transport: a Files API reference, then a URL, then base64 bytes (a `data:`
165
166
  * URL is split rather than embedded, because Anthropic has no data-URL field).
166
167
  */
@@ -281,6 +282,7 @@ function partToClaudeBlock(
281
282
  idMap: Map<string, string>;
282
283
  isOAuth: boolean;
283
284
  imageCount: number;
285
+ maxBytesOverride?: number;
284
286
  },
285
287
  ): ClaudeWireObject | undefined {
286
288
  switch (part.kind) {
@@ -291,6 +293,7 @@ function partToClaudeBlock(
291
293
  normalizeImageBlock(part.payload),
292
294
  ctx?.imageCount ?? 1,
293
295
  part.payload,
296
+ ctx?.maxBytesOverride,
294
297
  );
295
298
  case "file":
296
299
  return {
@@ -553,14 +556,22 @@ export function claudeFallbacksChain(
553
556
 
554
557
  export function canonicalToClaudeMessagesPayload(
555
558
  request: CanonicalRequest,
556
- options: { isOAuth?: boolean } = {},
559
+ options: { isOAuth?: boolean; maxBytesOverride?: number } = {},
557
560
  ): ClaudeWireObject {
558
561
  const seen = new Map<string, number>();
559
562
  const idMap = new Map<string, string>();
560
563
  // The clamp's per-image ceiling depends on how many images the whole request
561
564
  // carries, so it is counted once over every part rather than per message.
562
565
  const imageCount = allParts(request).filter((part) => part.kind === "image").length;
563
- const ctx = { seen, idMap, isOAuth: options.isOAuth ?? false, imageCount };
566
+ const ctx = {
567
+ seen,
568
+ idMap,
569
+ isOAuth: options.isOAuth ?? false,
570
+ imageCount,
571
+ ...(options.maxBytesOverride === undefined
572
+ ? {}
573
+ : { maxBytesOverride: options.maxBytesOverride }),
574
+ };
564
575
  const messages: ClaudeWireObject[] = [];
565
576
  // Anthropic accepts system content only at the top level. Chat `developer`
566
577
  // turns and Responses `instructions` have no `developer` role upstream, and
@@ -163,7 +163,15 @@ export function canonicalToResponsesPayload(
163
163
  flushMessage();
164
164
  // Replays prior encrypted reasoning so the model retains continuity
165
165
  // across turns; never decrypted or reconstructed from summary text.
166
- input.push({ type: "reasoning", encrypted_content: p.encrypted_content });
166
+ // The upstream binds encrypted reasoning to the item it was issued
167
+ // for and rejects a replay whose id does not match ("Encrypted content
168
+ // item_id did not match the target item id"), so the item carries the
169
+ // id it was parsed with. Without one there is nothing to match and the
170
+ // payload would be rejected anyway — it is dropped rather than sent
171
+ // under an id the upstream cannot verify.
172
+ if (p.item_id !== undefined) {
173
+ input.push({ type: "reasoning", id: p.item_id, encrypted_content: p.encrypted_content });
174
+ }
167
175
  } else if (p.kind === "reasoning") {
168
176
  // Plain reasoning replayed from another surface (a Messages `thinking`
169
177
  // block, or Chat `reasoning_content`). It has no encrypted artifact, so
@@ -10,7 +10,7 @@ import { mergeResponsesUsage } from "./responses";
10
10
  import { usageFromProvider, readReasoningText } from "../../providers/provider-usage";
11
11
  import { createToolEmitLedger } from "../tool-identity";
12
12
  import { MimoThinkSplitter, stripThinkingTags } from "../../providers/integrations/xiaomi-mimo/think-stream";
13
- import { parseDsmlToolCalls } from "./dsml";
13
+ import { DsmlToolCallParser, parseDsmlToolCalls } from "./dsml";
14
14
  import {
15
15
  OmpToolCallMarkupParser,
16
16
  parseOmpToolCallMarkup,
@@ -83,7 +83,7 @@ export function parseChatResponseToEvents(
83
83
  const contentWithoutMarkup = recoveredMarkup?.text ?? content;
84
84
  const recovered =
85
85
  typeof contentWithoutMarkup === "string"
86
- ? parseDsmlToolCalls(contentWithoutMarkup)
86
+ ? parseDsmlToolCalls(contentWithoutMarkup, declaredToolNames)
87
87
  : undefined;
88
88
  const visible = recovered === undefined ? contentWithoutMarkup : recovered.text;
89
89
  if (typeof visible === "string" && visible.includes("<think")) {
@@ -144,6 +144,13 @@ export function parseChatResponseToEvents(
144
144
  : undefined;
145
145
  return callId && name !== undefined && name.length > 0;
146
146
  });
147
+ // A request that declared no tools carries no allowlist to enforce, so a
148
+ // recovered block is honoured as-is exactly as before; a request that did
149
+ // declare them must never execute a name it did not offer.
150
+ const recoveredCalls =
151
+ declaredToolNames.size > 0
152
+ ? (recovered?.calls ?? []).filter((call) => callableToolNames.has(call.name))
153
+ : (recovered?.calls ?? []);
147
154
  const toolCalls = hasStructuredCalls
148
155
  ? structuredCalls
149
156
  : recoveredMarkup?.calls.some((call) => callableToolNames.has(call.name))
@@ -153,7 +160,7 @@ export function parseChatResponseToEvents(
153
160
  id: call.id,
154
161
  function: { name: call.name, arguments: call.arguments },
155
162
  }))
156
- : recovered?.calls ?? [];
163
+ : recoveredCalls;
157
164
  if (!hasStructuredCalls && recoveredMarkup !== undefined) {
158
165
  recoveredToolCalls = recoveredMarkup.calls.filter((call) =>
159
166
  callableToolNames.has(call.name),
@@ -257,6 +264,8 @@ export async function* decodeChatSseStream(
257
264
  (request.tools?.length ?? 0) > 0 ? new OmpToolCallMarkupParser(declaredToolNames) : undefined;
258
265
  const reasoningMarkup =
259
266
  (request.tools?.length ?? 0) > 0 ? new OmpToolCallMarkupParser(declaredToolNames) : undefined;
267
+ const dsmlMarkup =
268
+ (request.tools?.length ?? 0) > 0 ? new DsmlToolCallParser(declaredToolNames) : undefined;
260
269
  yield {
261
270
  type: "response_start",
262
271
  sequence_number: seq++,
@@ -302,7 +311,7 @@ export async function* decodeChatSseStream(
302
311
  const raw = delta["content"] as string;
303
312
  const markup = contentMarkup?.push(raw);
304
313
  if (markup !== undefined) recoveredMarkupCalls.push(...markup.calls);
305
- const recovered = parseDsmlToolCalls(markup?.text ?? raw);
314
+ const recovered = dsmlMarkup?.push(markup?.text ?? raw);
306
315
  const visible = recovered?.text ?? markup?.text ?? raw;
307
316
  const split = think.push(visible);
308
317
  if (split.reasoning.length > 0) {
@@ -478,6 +487,27 @@ export async function* decodeChatSseStream(
478
487
  }
479
488
  }
480
489
  }
490
+ const dsmlMarkupTail = dsmlMarkup?.flush();
491
+ if (dsmlMarkupTail !== undefined) {
492
+ if (dsmlMarkupTail.text.length > 0) {
493
+ const split = think.push(dsmlMarkupTail.text);
494
+ if (split.reasoning.length > 0) {
495
+ yield {
496
+ type: "content_delta",
497
+ sequence_number: seq++,
498
+ content: { kind: "reasoning", payload: null, summary: split.reasoning },
499
+ } as CanonicalEvent;
500
+ }
501
+ if (split.text.length > 0) {
502
+ yield {
503
+ type: "content_delta",
504
+ sequence_number: seq++,
505
+ content: { kind: "text", text: split.text },
506
+ } as CanonicalEvent;
507
+ }
508
+ }
509
+ for (const call of dsmlMarkupTail.calls) recoveredMarkupCalls.push(call);
510
+ }
481
511
  const reasoningMarkupTail = reasoningMarkup?.flush();
482
512
  if (reasoningMarkupTail?.text) {
483
513
  const reasoningText = stripThinkingTags(reasoningMarkupTail.text);
@@ -84,7 +84,10 @@ export function reasoningEvent(
84
84
  payload: null,
85
85
  ...(summary === undefined ? {} : { summary }),
86
86
  ...(typeof encrypted === "string"
87
- ? { encrypted_content: encrypted }
87
+ ? {
88
+ encrypted_content: encrypted,
89
+ ...(typeof item["id"] === "string" ? { item_id: item["id"] } : {}),
90
+ }
88
91
  : {}),
89
92
  ...(summary === undefined && typeof encrypted === "string"
90
93
  ? { opaque: true as const }
@@ -235,9 +238,9 @@ export function parseCodexResponsesJsonToEvents(
235
238
  | undefined);
236
239
  // An incomplete response that produced a tool call is always a tool-use
237
240
  // stop; every other incomplete response is a length stop. There is no
238
- // separate executability gate — the branch that used to scan tool arguments
239
- // for parseable JSON returned `tool_use` on both outcomes, so the scan and
240
- // the `incomplete_details.reason` read could not change the result.
241
+ // separate executability gate — scanning tool arguments for parseable JSON
242
+ // returns `tool_use` on both outcomes, so the scan and the
243
+ // `incomplete_details.reason` read could not change the result.
241
244
  const stopReason = mapCodexStopReason(status, hasToolCall, endTurn);
242
245
  let finalStopReason = stopReason;
243
246
  if (endTurn === false && stopReason === "stop") {