cartethyia 2.7.4 → 2.7.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 (595) hide show
  1. package/.env.example +5 -0
  2. package/README.md +18 -6
  3. package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
  7. package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
  8. package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js +899 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
  19. package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
  20. package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-C6WlpU5E.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-DxjvUzSD.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
  37. package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
  38. package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-DRPavCdU.js} +4 -4
  40. package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
  42. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
  43. package/dist/dashboard/assets/UsagePage-B7M7LvMM.js +56 -0
  44. package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
  46. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  47. package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
  48. package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
  49. package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
  50. package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
  51. package/dist/dashboard/assets/assert-CidQVoNo.js +1 -0
  52. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  53. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-BaX2GTrn.js} +1 -1
  54. package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
  55. package/dist/dashboard/assets/{banner-DympBNq4.js → banner-DFH4Bkte.js} +1 -1
  56. package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
  57. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  58. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  59. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  60. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  61. package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CxDw0lK6.js} +1 -1
  62. package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
  63. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  64. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
  66. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
  67. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +1 -0
  68. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  69. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-DfS1ntiq.js} +1 -1
  70. package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
  71. package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
  72. package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
  73. package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-QqPul6sC.js} +1 -1
  74. package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
  75. package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
  76. package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
  78. package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
  79. package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
  80. package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
  81. package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-u7JPB5BX.js} +1 -1
  82. package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
  83. package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
  84. package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
  85. package/dist/dashboard/assets/{index-DBNPOfhV.js → index-Ko4dLRnV.js} +2 -2
  86. package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
  87. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-CHJQwxLa.js} +1 -1
  88. package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
  89. package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BlmlizVV.js} +1 -1
  90. package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
  91. package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-Bz93I-yE.js} +1 -1
  92. package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
  93. package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Ml16KCfu.js} +1 -1
  94. package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
  95. package/dist/dashboard/assets/live-7_QpnYGu.js +1 -0
  96. package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
  97. package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
  98. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  99. package/dist/dashboard/assets/network-CuMDaTzT.js +1 -0
  100. package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
  101. package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
  102. package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
  103. package/dist/dashboard/assets/pagination-B5aOoweZ.js +1 -0
  104. package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
  105. package/dist/dashboard/assets/providers/zai.webp +0 -0
  106. package/dist/dashboard/assets/providers/zcode.webp +0 -0
  107. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
  108. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
  109. package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
  110. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-fnCsxgNO.js +1 -0
  112. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BHqpd-bg.js} +2 -2
  114. package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-BxslsErn.js +1 -0
  116. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  117. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  118. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  119. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-j7cXjgYX.js} +1 -1
  120. package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
  121. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DHmEnLa2.js} +1 -1
  122. package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
  123. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-DmZcyTIC.js} +1 -1
  124. package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
  125. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-CbCP4w1o.js} +1 -1
  126. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  127. package/dist/dashboard/console-backgrounds.jpg +0 -0
  128. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  129. package/dist/dashboard/index.html +2 -2
  130. package/dist/dashboard/index.html.gz +0 -0
  131. package/dist/dashboard/precompressed-assets.json +60 -59
  132. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  133. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  134. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  135. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  136. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  137. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  138. package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
  139. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  140. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  141. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  142. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  143. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  144. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  145. package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
  146. package/package.json +4 -6
  147. package/scripts/cli/update.ts +15 -41
  148. package/src/boot/app.ts +58 -2
  149. package/src/boot/dependencies.ts +794 -732
  150. package/src/boot/lifecycle.ts +11 -12
  151. package/src/config/cooldowns.ts +34 -55
  152. package/src/config/network.ts +1 -1
  153. package/src/config/package-paths.ts +1 -1
  154. package/src/config/providers.ts +19 -14
  155. package/src/config/resources.ts +0 -5
  156. package/src/config/runtime.ts +2 -5
  157. package/src/config/spec.ts +33 -6
  158. package/src/config/usage.ts +27 -0
  159. package/src/console/api-keys/ban-constants.ts +38 -0
  160. package/src/console/api-keys/contracts.ts +71 -62
  161. package/src/console/api-keys/routes.ts +56 -24
  162. package/src/console/auth/access.ts +4 -4
  163. package/src/console/auth/account-reset.ts +1 -1
  164. package/src/console/auth/audit-recorder.ts +1 -1
  165. package/src/console/auth/lockout.ts +7 -3
  166. package/src/console/auth/row-coercion.ts +57 -12
  167. package/src/console/auth/session-resolver.ts +1 -1
  168. package/src/console/auth/session-service.ts +8 -2
  169. package/src/console/auth/session.ts +37 -31
  170. package/src/console/auth/setup-service.ts +4 -3
  171. package/src/console/auth/user-store.ts +1 -1
  172. package/src/console/backup/9router-map.ts +0 -3
  173. package/src/console/backup/contracts.ts +9 -9
  174. package/src/console/backup/export-endpoint.ts +5 -6
  175. package/src/console/backup/nine-router.ts +1 -7
  176. package/src/console/backup/routes.ts +29 -33
  177. package/src/console/backup/service.ts +468 -468
  178. package/src/console/backup/store.ts +47 -29
  179. package/src/console/backup/validate.ts +11 -1
  180. package/src/console/cli-tools/contracts.ts +43 -299
  181. package/src/console/cli-tools/fs-ops.ts +29 -0
  182. package/src/console/cli-tools/injectors/claude.ts +3 -3
  183. package/src/console/cli-tools/injectors/codex.ts +181 -181
  184. package/src/console/cli-tools/injectors/driver.ts +150 -247
  185. package/src/console/cli-tools/injectors/kilo.ts +146 -21
  186. package/src/console/cli-tools/injectors/opencode.ts +176 -153
  187. package/src/console/cli-tools/routes.ts +7 -1
  188. package/src/console/cli-tools/service.ts +8 -7
  189. package/src/console/cli-tools/store.ts +2 -2
  190. package/src/console/console-router.ts +32 -0
  191. package/src/console/dashboard-assets.ts +9 -9
  192. package/src/console/domain-registration.ts +16 -2
  193. package/src/console/inferhub/client.ts +0 -7
  194. package/src/console/inferhub/key-store.ts +1 -1
  195. package/src/console/inferhub/normalize.ts +2 -2
  196. package/src/console/inferhub/routes.ts +1 -1
  197. package/src/console/inferhub/types.ts +1 -1
  198. package/src/console/observability/contracts.ts +7 -1
  199. package/src/console/observability/live.ts +31 -18
  200. package/src/console/observability/logs.ts +1 -1
  201. package/src/console/observability/sse.ts +62 -8
  202. package/src/console/observability/store.ts +23 -4
  203. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  204. package/src/console/providers/catalog/contracts.ts +39 -0
  205. package/src/console/providers/catalog/model-operations.ts +1 -1
  206. package/src/console/providers/catalog/provider-operations.ts +95 -17
  207. package/src/console/providers/catalog/public-model-store.ts +0 -0
  208. package/src/console/providers/catalog/routes.ts +27 -3
  209. package/src/console/providers/catalog/store.ts +147 -8
  210. package/src/console/providers/detail/routes.ts +1 -1
  211. package/src/console/providers/oauth/callback-listener.ts +100 -18
  212. package/src/console/providers/oauth/routes.ts +92 -13
  213. package/src/console/quota/account-quota-global.ts +41 -3
  214. package/src/console/quota/account-quota-shared.ts +10 -3
  215. package/src/console/quota/account-quota-tenant.ts +259 -66
  216. package/src/console/quota/daily-growth-job.ts +308 -0
  217. package/src/console/quota/native-identity.ts +2 -2
  218. package/src/console/quota/quota-view.ts +1 -1
  219. package/src/console/remote-support/routes.ts +1 -1
  220. package/src/console/remote-support/service.ts +2 -2
  221. package/src/console/routing/model/contracts.ts +1 -1
  222. package/src/console/routing/pools/contracts.ts +2 -2
  223. package/src/console/routing/pools/import-parse.ts +3 -3
  224. package/src/console/routing/pools/relay-deploy.ts +3 -3
  225. package/src/console/settings/contracts.ts +29 -2
  226. package/src/console/settings/store.ts +20 -0
  227. package/src/console/share/share-router.ts +100 -32
  228. package/src/console/share/share-stats.ts +11 -13
  229. package/src/console/share/share-usage.ts +1 -1
  230. package/src/console/shared/errors.ts +2 -2
  231. package/src/console/shared/object.ts +18 -0
  232. package/src/console/shared/read-through-cache.ts +4 -4
  233. package/src/console/shared/transaction.ts +14 -0
  234. package/src/console/system-prompt/contracts.ts +89 -12
  235. package/src/console/system-prompt/routes.ts +98 -45
  236. package/src/console/updates/apply-update.ts +244 -0
  237. package/src/console/updates/routes.ts +252 -97
  238. package/src/main.ts +11 -5
  239. package/src/network/http2-bidi.ts +1 -1
  240. package/src/network/ip-utils.ts +298 -0
  241. package/src/network/outbound-fetch.ts +1 -1
  242. package/src/network/pool/agent.ts +7 -6
  243. package/src/network/pool/byte-accounting.ts +1 -1
  244. package/src/network/pool/resolver.ts +16 -12
  245. package/src/network/pool/selector.ts +150 -136
  246. package/src/network/pool-health-machine.ts +1 -1
  247. package/src/network/rate-limit-verdict.ts +1 -1
  248. package/src/network/retry.ts +1 -1
  249. package/src/network/ssrf.ts +20 -71
  250. package/src/observability/event-log.ts +9 -2
  251. package/src/observability/event-query.ts +61 -291
  252. package/src/observability/log-sanitize.ts +1 -1
  253. package/src/observability/metrics.ts +4 -4
  254. package/src/observability/payload-capture.ts +6 -5
  255. package/src/observability/payload-store.ts +1 -1
  256. package/src/observability/runtime-metrics.ts +1 -1
  257. package/src/observability/telemetry-buffer.ts +19 -3
  258. package/src/observability/telemetry-reconciliation.ts +3 -3
  259. package/src/observability/token-speed.ts +1 -1
  260. package/src/persistence/connection-url.ts +1 -1
  261. package/src/persistence/db-handle.ts +1 -1
  262. package/src/persistence/db-pg.ts +2 -2
  263. package/src/persistence/db-sqlite.ts +3 -3
  264. package/src/persistence/dialect.ts +5 -1
  265. package/src/persistence/readiness.ts +1 -1
  266. package/src/persistence/redis.ts +1 -1
  267. package/src/persistence/schema.ts +121 -40
  268. package/src/persistence/shared-cache.ts +2 -2
  269. package/src/persistence/sort-order.ts +1 -1
  270. package/src/persistence/stores/api-key-store.ts +29 -2
  271. package/src/persistence/stores/share-store.ts +20 -8
  272. package/src/persistence/system-prompt-store.ts +63 -18
  273. package/src/persistence/tenant-preferences.ts +1 -1
  274. package/src/persistence/ttl-cache.ts +3 -3
  275. package/src/persistence/usage-flush.ts +7 -2
  276. package/src/protocol/canonical-model.ts +1 -1
  277. package/src/protocol/codecs.ts +6 -10
  278. package/src/protocol/dsml.ts +8 -125
  279. package/src/protocol/image-clamp.ts +2 -2
  280. package/src/protocol/reasoning-text.ts +1 -1
  281. package/src/protocol/reasoning.ts +7 -52
  282. package/src/protocol/registry.ts +50 -15
  283. package/src/protocol/request/chat.ts +46 -19
  284. package/src/protocol/request/codex.ts +1 -1
  285. package/src/protocol/request/messages.ts +46 -14
  286. package/src/protocol/request/responses.ts +19 -10
  287. package/src/protocol/response/chat.ts +25 -10
  288. package/src/protocol/response/codex.ts +1 -1
  289. package/src/protocol/response/messages.ts +1 -1
  290. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  291. package/src/protocol/response/responses.ts +1 -1
  292. package/src/protocol/sse-decoder.ts +50 -1
  293. package/src/protocol/stop-reason.ts +2 -2
  294. package/src/protocol/think-split.ts +347 -0
  295. package/src/protocol/translation/attachment-integrity.ts +3 -3
  296. package/src/protocol/translation/cache-controls.ts +8 -20
  297. package/src/protocol/translation/capabilities.ts +6 -6
  298. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  299. package/src/protocol/translation/dialect/inband.ts +39 -0
  300. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  301. package/src/protocol/translation/dialect/qwen.ts +114 -0
  302. package/src/protocol/translation/dialect/resolver.ts +83 -0
  303. package/src/protocol/translation/dialect/rules.ts +93 -0
  304. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  305. package/src/protocol/translation/dialect/types.ts +58 -0
  306. package/src/protocol/translation/quirks.ts +18 -4
  307. package/src/protocol/translation/thinking.ts +12 -252
  308. package/src/protocol/translation/tool-id.ts +3 -3
  309. package/src/protocol/translation/tool-repair.ts +1 -1
  310. package/src/protocol/upstream/post-json.ts +6 -1
  311. package/src/providers/adapters/compatible-adapter.ts +71 -4
  312. package/src/providers/authentication/jwt-validator.ts +0 -5
  313. package/src/providers/authentication/oauth-client.ts +254 -253
  314. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  315. package/src/providers/bundled-discovery-providers.ts +6 -30
  316. package/src/providers/configured-openai-providers.ts +82 -97
  317. package/src/providers/default-registry.ts +35 -37
  318. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  319. package/src/providers/discovery/discovery-types.ts +0 -1
  320. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  321. package/src/providers/discovery/probe-phases.ts +48 -13
  322. package/src/providers/discovery/probing-service.ts +6 -8
  323. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  324. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  325. package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -28
  326. package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
  327. package/src/providers/integrations/antigravity/antigravity.ts +22 -13
  328. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  329. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  330. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  331. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  332. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  333. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  334. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  335. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  336. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  337. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  338. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  339. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  340. package/src/providers/integrations/cline/cline.ts +1 -1
  341. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  342. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  343. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  344. package/src/providers/integrations/commandcode/quota.ts +131 -51
  345. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  346. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  347. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  348. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  349. package/src/providers/integrations/cursor/cursor.ts +35 -3
  350. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  351. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  352. package/src/providers/integrations/factory/factory.ts +9 -9
  353. package/src/providers/integrations/github/github-discovery.ts +70 -12
  354. package/src/providers/integrations/github/github-oauth.ts +184 -14
  355. package/src/providers/integrations/github/github.ts +134 -26
  356. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  357. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  358. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  359. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  360. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  361. package/src/providers/integrations/grok/grok.ts +86 -36
  362. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  363. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  364. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  365. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  366. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  367. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  368. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  369. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  370. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  371. package/src/providers/integrations/kiro/kiro.ts +80 -68
  372. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  373. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  374. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  375. package/src/providers/integrations/opencode/provider.ts +17 -24
  376. package/src/providers/integrations/qoder/qoder-catalog.ts +94 -11
  377. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  378. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  379. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  380. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  381. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  382. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  383. package/src/providers/integrations/qoder/qoder.ts +422 -71
  384. package/src/providers/integrations/shared/connect.ts +0 -54
  385. package/src/providers/integrations/shared/hash.ts +15 -0
  386. package/src/providers/integrations/shared/session-cache.ts +136 -0
  387. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  388. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  389. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  390. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  391. package/src/providers/integrations/trae/trae.ts +51 -8
  392. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  393. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  394. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  395. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  396. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  397. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  398. package/src/providers/operations/account-error-classification.ts +198 -133
  399. package/src/providers/operations/account-health-service.ts +31 -39
  400. package/src/providers/operations/account-reset-service.ts +58 -1
  401. package/src/providers/operations/byok-wire-profile.ts +19 -15
  402. package/src/providers/operations/cli-platform.ts +31 -5
  403. package/src/providers/operations/client-version-resolver.ts +4 -6
  404. package/src/providers/operations/client-versions.ts +10 -7
  405. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  406. package/src/providers/operations/provider-catalog-service.ts +18 -2
  407. package/src/providers/operations/provider-credential-service.ts +19 -2
  408. package/src/providers/provider-contracts.ts +18 -7
  409. package/src/providers/provider-metadata.ts +17 -2
  410. package/src/providers/provider-model-definition.ts +22 -1
  411. package/src/providers/provider-probe.ts +4 -2
  412. package/src/providers/provider-usage.ts +50 -19
  413. package/src/providers/quota/cache.ts +29 -0
  414. package/src/providers/quota/quota-contracts.ts +4 -0
  415. package/src/providers/quota/refresh.ts +27 -4
  416. package/src/providers/quota/snapshot.ts +94 -0
  417. package/src/providers/search/search-provider.ts +1 -3
  418. package/src/providers/search/search-providers.ts +2 -1
  419. package/src/security/abuse.ts +4 -4
  420. package/src/security/admission/service.ts +13 -6
  421. package/src/security/admission/ttl.ts +61 -61
  422. package/src/security/api-key-auth.ts +17 -3
  423. package/src/security/client-router-fingerprint.ts +4 -4
  424. package/src/security/credential-codec.ts +3 -3
  425. package/src/security/csrf.ts +2 -2
  426. package/src/security/ip-boundary.ts +16 -189
  427. package/src/security/model-access-rule.ts +1 -1
  428. package/src/security/outbound-headers.ts +18 -0
  429. package/src/shared/invoke-guard.ts +47 -0
  430. package/src/transport/contracts.ts +13 -4
  431. package/src/transport/dispatch/attempt-finalize.ts +70 -22
  432. package/src/transport/dispatch/attempt-loop.ts +22 -0
  433. package/src/transport/dispatch/capture-policy.ts +34 -37
  434. package/src/transport/dispatch/empty-stream.ts +23 -20
  435. package/src/transport/dispatch/generation.ts +25 -45
  436. package/src/transport/dispatch/native-shared.ts +127 -0
  437. package/src/transport/dispatch/proxy-request.ts +3 -1
  438. package/src/transport/dispatch/responses-compact.ts +25 -46
  439. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  440. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  441. package/src/transport/dispatch/streaming/stream.ts +7 -3
  442. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  443. package/src/transport/dispatch/systemone.ts +20 -48
  444. package/src/transport/dispatch/token-count.ts +41 -38
  445. package/src/transport/dispatch/websearch.ts +19 -45
  446. package/src/transport/drain-endpoint.ts +1 -1
  447. package/src/transport/failure-policy.ts +21 -1
  448. package/src/transport/guards/auth-guard.ts +0 -6
  449. package/src/transport/guards/body-policy.ts +3 -3
  450. package/src/transport/guards/error-lifecycle.ts +4 -0
  451. package/src/transport/guards/headroom-guard.ts +1 -1
  452. package/src/transport/guards/pipeline.ts +1 -1
  453. package/src/transport/guards/request-context.ts +12 -17
  454. package/src/transport/request/preparer.ts +286 -7
  455. package/src/transport/request/rtk/compress-request.ts +3 -3
  456. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  457. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  458. package/src/transport/request/state.ts +7 -13
  459. package/src/transport/request/system-prompt-injection.ts +74 -11
  460. package/src/transport/request/tenant-preferences.ts +22 -0
  461. package/src/transport/routing/candidate-index.ts +2 -2
  462. package/src/transport/routing/round-robin.ts +1 -1
  463. package/src/transport/routing/route-catalog.ts +17 -18
  464. package/src/transport/routing/route-model.ts +126 -33
  465. package/src/transport/routing/router.ts +159 -37
  466. package/src/transport/shutdown-notice.ts +2 -2
  467. package/src/transport/surface/adapters.ts +3 -3
  468. package/src/transport/surface/chat/encode.ts +7 -7
  469. package/src/transport/surface/chat/parse.ts +7 -7
  470. package/src/transport/surface/completion.ts +2 -2
  471. package/src/transport/surface/content-parts.ts +1 -1
  472. package/src/transport/surface/dialects.ts +2 -2
  473. package/src/transport/surface/messages/parse.ts +3 -3
  474. package/src/transport/surface/reasoning-summary.ts +9 -14
  475. package/src/transport/surface/responses/adapter.ts +1 -1
  476. package/src/transport/surface/responses/errors.ts +0 -9
  477. package/src/transport/vision-handoff-cache.ts +58 -0
  478. package/src/transport/vision-handoff.ts +431 -0
  479. package/src/workers/account-identity-sweep.ts +2 -2
  480. package/src/workers/buddy-growth-tasks.ts +392 -346
  481. package/src/workers/daily-checkin.ts +102 -49
  482. package/src/workers/maintenance-lease.ts +2 -35
  483. package/src/workers/oauth-refresh-sweep.ts +8 -1
  484. package/src/workers/quota-refresh-sweep.ts +120 -82
  485. package/src/workers/scheduler.ts +67 -4
  486. package/src/workers/sweep-runner.ts +24 -5
  487. package/src/workers/zcode-gift-claim.ts +108 -0
  488. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  489. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  490. package/dist/dashboard/assets/BannedPage-ChG5svFb.js +0 -1
  491. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  492. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +0 -1
  493. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  494. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +0 -1
  495. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  496. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  497. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  498. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  499. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  500. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +0 -1
  501. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  502. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  503. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  504. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js +0 -1
  505. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  506. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  507. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  508. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  509. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  510. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  511. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  512. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  513. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  514. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  515. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  516. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  517. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  518. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  519. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  520. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +0 -1
  521. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  522. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  523. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  524. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  525. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
  526. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  527. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  528. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  529. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  530. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  531. package/dist/dashboard/assets/app-BayTTy01.js +0 -9
  532. package/dist/dashboard/assets/app-BayTTy01.js.gz +0 -0
  533. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  534. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  535. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  536. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  537. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  538. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  539. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  540. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  541. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  542. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  543. package/dist/dashboard/assets/dialog-8fiE6tec.js +0 -1
  544. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  545. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  546. package/dist/dashboard/assets/entry-BSAsYsmV.js +0 -1
  547. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  548. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  549. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  550. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  551. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  552. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  553. package/dist/dashboard/assets/github-badge-CPK47xQx.js +0 -1
  554. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  555. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  556. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  557. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  558. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  559. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  560. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  561. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  562. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  563. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  564. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  565. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  566. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  567. package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
  568. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  569. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  570. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  571. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  572. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  573. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  574. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  575. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  576. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  577. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  578. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  579. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  580. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  581. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  582. package/src/console/cli-tools/injectors/cline.ts +0 -110
  583. package/src/console/cli-tools/injectors/copilot.ts +0 -108
  584. package/src/console/cli-tools/injectors/cowork.ts +0 -64
  585. package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
  586. package/src/console/cli-tools/injectors/droid.ts +0 -96
  587. package/src/console/cli-tools/injectors/grok-build.ts +0 -70
  588. package/src/console/cli-tools/injectors/hermes.ts +0 -101
  589. package/src/console/cli-tools/injectors/jcode.ts +0 -115
  590. package/src/console/cli-tools/injectors/openclaw.ts +0 -161
  591. package/src/network/response-headers.ts +0 -39
  592. package/src/protocol/response/dsml.ts +0 -38
  593. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  594. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  595. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Canonical thinking clamp + output-floor helpers, shared by every wire codec.
3
+ *
4
+ * `clampThinkingLevelForModel` is the single clamp: it steps a requested
5
+ * effort down to the highest supported level ≤ the request, or up to the
6
+ * model's floor when the request is below it. It replaces the old
7
+ * `clampReasoningEffort` with the same semantics, driven by the data-driven
8
+ * `ThinkingConfig` from `thinking-ladder.ts`.
9
+ *
10
+ * `outputFloorFor` is the clamped-effort output floor (previously
11
+ * `clampedEffortOutputFloor` in `protocol/reasoning.ts`): when the applied
12
+ * effort differs from the requested one, the caller sized `max_tokens` for the
13
+ * effort it asked for, and the applied level spends its budget on reasoning
14
+ * before writing the answer — so raise the output budget to the model's
15
+ * catalog ceiling rather than let the answer truncate.
16
+ */
17
+ import {
18
+ REASONING_EFFORT_LADDER,
19
+ type ReasoningEffortLevel,
20
+ } from "../thinking";
21
+ import type { ThinkingConfig } from "./thinking-ladder";
22
+
23
+ /**
24
+ * Clamps a requested reasoning effort against a model/wire's resolved
25
+ * `ThinkingConfig`. Same semantics as the removed `clampReasoningEffort`:
26
+ * synonyms normalize (`ultra` → `max`, `off` → `none`), unknown strings drop
27
+ * (no invented fallback), and a request below the floor uses the floor.
28
+ */
29
+ export function clampThinkingLevelForModel(
30
+ thinking: ThinkingConfig | undefined,
31
+ requested: string | undefined,
32
+ ): ReasoningEffortLevel | undefined {
33
+ if (requested === undefined || typeof requested !== "string") return undefined;
34
+ const raw = requested.toLowerCase().trim();
35
+ const normalized = raw === "ultra" ? "max" : raw === "off" ? "none" : raw;
36
+ if (normalized === "none") return undefined;
37
+ if (thinking === undefined) return normalized as ReasoningEffortLevel;
38
+
39
+ const supported = thinking.efforts;
40
+ if (supported.includes(normalized as ReasoningEffortLevel)) {
41
+ return normalized as ReasoningEffortLevel;
42
+ }
43
+ const requestedIndex = REASONING_EFFORT_LADDER.indexOf(normalized as ReasoningEffortLevel);
44
+ if (requestedIndex === -1) return undefined;
45
+
46
+ let clamped: ReasoningEffortLevel | undefined;
47
+ for (const level of supported) {
48
+ if (REASONING_EFFORT_LADDER.indexOf(level) > requestedIndex) break;
49
+ clamped = level;
50
+ }
51
+ // No smaller supported level exists (caller asked below floor): use floor.
52
+ return clamped ?? supported[0];
53
+ }
54
+
55
+ const CLAMPED_EFFORT_MIN_OUTPUT_TOKENS = 16_384;
56
+
57
+ const EFFORT_LEVEL_RANK: Readonly<Record<string, number>> = {
58
+ off: 0,
59
+ none: 0,
60
+ instant: 1,
61
+ minimal: 2,
62
+ low: 3,
63
+ medium: 4,
64
+ high: 5,
65
+ xhigh: 6,
66
+ max: 7,
67
+ };
68
+
69
+ /**
70
+ * Minimum output budget for a caller whose requested effort the ladder could
71
+ * not serve as asked. See the module doc for why both clamp directions need it.
72
+ * Only ever raises, and only to the model's own catalog limit.
73
+ */
74
+ export function outputFloorFor(
75
+ requested: string | undefined,
76
+ applied: string | undefined,
77
+ modelOutputLimit: number | undefined,
78
+ ): number | undefined {
79
+ if (requested === undefined || applied === undefined) return undefined;
80
+ const from = EFFORT_LEVEL_RANK[requested.trim().toLowerCase()];
81
+ const to = EFFORT_LEVEL_RANK[applied.trim().toLowerCase()];
82
+ if (from === undefined || to === undefined || from === to) return undefined;
83
+ if (modelOutputLimit === undefined || !Number.isFinite(modelOutputLimit) || modelOutputLimit <= 0)
84
+ return undefined;
85
+ // Leave headroom instead of claiming the whole catalog limit: the caller's
86
+ // number was a real preference about answer size, and the goal is to stop
87
+ // truncating, not to spend the model's ceiling on every turn.
88
+ return Math.max(CLAMPED_EFFORT_MIN_OUTPUT_TOKENS, Math.floor(modelOutputLimit / 2));
89
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Qwen chat-template in-band tool calls.
3
+ *
4
+ * Qwen3-family models emit tool calls as JSON blocks inside the text stream
5
+ * when the server's structured tool parser is not in use:
6
+ *
7
+ * <tool_call>
8
+ * {"name": "web_search", "arguments": {"query": "..."}}
9
+ * </tool_call>
10
+ *
11
+ * The scanner recovers complete blocks into canonical tool calls and leaves the
12
+ * rest as text, mirroring the DSML/OMP scanners used for other families.
13
+ */
14
+ import type { InbandScanner, InbandScanResult } from "./inband";
15
+
16
+ const OPEN_TAG = "<tool_call>";
17
+ const CLOSE_TAG = "</tool_call>";
18
+
19
+ export class QwenToolCallScanner implements InbandScanner {
20
+ readonly #allowedNames: ReadonlySet<string>;
21
+ #pending = "";
22
+ #sequence = 0;
23
+
24
+ constructor(allowedNames: ReadonlySet<string>) {
25
+ this.#allowedNames = allowedNames;
26
+ }
27
+
28
+ push(chunk: string): InbandScanResult {
29
+ this.#pending += chunk;
30
+ let cursor = 0;
31
+ let text = "";
32
+ const calls: Array<InbandScanResult["calls"][number]> = [];
33
+ for (;;) {
34
+ const open = this.#pending.indexOf(OPEN_TAG, cursor);
35
+ if (open < 0) {
36
+ // Hold a possible pending opener at the tail; settle everything before it.
37
+ text += this.#pending.slice(cursor, this.#settledEnd(this.#pending.length));
38
+ cursor = this.#settledEnd(this.#pending.length);
39
+ break;
40
+ }
41
+ text += this.#pending.slice(cursor, open);
42
+ const bodyStart = open + OPEN_TAG.length;
43
+ const close = this.#pending.indexOf(CLOSE_TAG, bodyStart);
44
+ if (close < 0) {
45
+ // Unterminated block: hold from the opener on and settle what preceded it.
46
+ cursor = open;
47
+ break;
48
+ }
49
+ const inner = this.#pending.slice(bodyStart, close).trim();
50
+ const parsed = parseToolCall(inner);
51
+ if (parsed !== undefined) {
52
+ if (this.#allowedNames.has(parsed.name)) {
53
+ calls.push({
54
+ id: `call_qwen_${this.#sequence++}`,
55
+ name: parsed.name,
56
+ arguments: parsed.arguments,
57
+ });
58
+ } else {
59
+ // An undeclared tool is model text, not authority to invoke it.
60
+ text += this.#pending.slice(open, close + CLOSE_TAG.length);
61
+ }
62
+ } else {
63
+ text += this.#pending.slice(open, close + CLOSE_TAG.length);
64
+ }
65
+ cursor = close + CLOSE_TAG.length;
66
+ }
67
+ this.#pending = this.#pending.slice(cursor);
68
+ return { text, calls };
69
+ }
70
+
71
+ flush(): InbandScanResult {
72
+ const pending = this.#pending;
73
+ this.#pending = "";
74
+ if (isPossibleOpener(pending)) return { text: "", calls: [] };
75
+ return { text: pending, calls: [] };
76
+ }
77
+
78
+ #settledEnd(end: number): number {
79
+ // Keep a trailing fragment that could be the start of an opener.
80
+ const tail = this.#pending.slice(0, end);
81
+ const maxPrefix = Math.min(OPEN_TAG.length - 1, tail.length);
82
+ for (let len = maxPrefix; len > 0; len -= 1) {
83
+ if (OPEN_TAG.startsWith(tail.slice(-len))) return end - len;
84
+ }
85
+ return end;
86
+ }
87
+ }
88
+
89
+ function parseToolCall(inner: string): { readonly name: string; readonly arguments: string } | undefined {
90
+ if (inner.length === 0) return undefined;
91
+ let value: unknown;
92
+ try {
93
+ value = JSON.parse(inner);
94
+ } catch {
95
+ return undefined;
96
+ }
97
+ if (typeof value !== "object" || value === null) return undefined;
98
+ const record = value as Record<string, unknown>;
99
+ const name = typeof record["name"] === "string" ? record["name"] : undefined;
100
+ if (name === undefined || name.length === 0) return undefined;
101
+ const args = record["arguments"] ?? record["parameters"] ?? {};
102
+ let argsText: string;
103
+ try {
104
+ argsText = typeof args === "string" ? args : JSON.stringify(args);
105
+ } catch {
106
+ argsText = "{}";
107
+ }
108
+ return { name, arguments: argsText };
109
+ }
110
+
111
+ function isPossibleOpener(value: string): boolean {
112
+ if (value.length < OPEN_TAG.length) return OPEN_TAG.startsWith(value);
113
+ return value.startsWith(OPEN_TAG) && !value.includes(CLOSE_TAG);
114
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Dialect resolver — pure, never throws, always returns a policy.
3
+ *
4
+ * Precedence:
5
+ * 1. explicit DB override (`models.dialect`; passed as `override`)
6
+ * 2. explicit `ModelDefinition.dialect` (passed as `declared`)
7
+ * 3. heuristic rules (`DIALECT_RULES`)
8
+ * 4. family default (`anthropic` for messages, `openai` otherwise)
9
+ *
10
+ * The default policies reproduce today's exact behavior: a request with no
11
+ * dialect set encodes byte-identically to before this layer existed.
12
+ */
13
+ import type { WireFamily } from "../../canonical-model";
14
+ import { DEFAULT_DIALECT_POLICIES, DIALECT_RULES } from "./rules";
15
+ import type { DialectId, DialectPolicy } from "./types";
16
+ import { BoundedLruMap } from "../../../transport/routing/bounded-lru";
17
+
18
+ /** Known dialect ids, for validating loose DB/string overrides. */
19
+ const KNOWN_DIALECT_IDS = new Set<string>(Object.keys(DEFAULT_DIALECT_POLICIES));
20
+
21
+ /**
22
+ * Memoization for `resolveDialect` (mirrors THINKING_RESOLVE_CACHE). The key
23
+ * concatenates every argument, all of which are value types, so it is
24
+ * collision-safe. The cache is a {@link BoundedLruMap}: the key space is
25
+ * bounded by the model catalog, but a request can name an arbitrary model
26
+ * string, so an unbounded map would pin an entry for every miss forever.
27
+ */
28
+ const DIALECT_RESOLVE_CACHE_MAX_ENTRIES = 1024;
29
+ const DIALECT_RESOLVE_CACHE = new BoundedLruMap<string, DialectPolicy>(
30
+ DIALECT_RESOLVE_CACHE_MAX_ENTRIES,
31
+ );
32
+
33
+ export function resolveDialect(
34
+ modelId: string,
35
+ providerId: string,
36
+ wireFamily: WireFamily,
37
+ override?: string | null,
38
+ declared?: DialectId,
39
+ ): DialectPolicy {
40
+ const key = `${modelId.toLowerCase()}|${providerId}|${wireFamily}|${override ?? ""}|${declared ?? ""}`;
41
+ const cached = DIALECT_RESOLVE_CACHE.get(key);
42
+ if (cached !== undefined) return cached;
43
+ const policy = resolveDialectUncached(modelId, providerId, wireFamily, override, declared);
44
+ DIALECT_RESOLVE_CACHE.set(key, policy);
45
+ return policy;
46
+ }
47
+
48
+ function resolveDialectUncached(
49
+ modelId: string,
50
+ providerId: string,
51
+ wireFamily: WireFamily,
52
+ override?: string | null,
53
+ declared?: DialectId,
54
+ ): DialectPolicy {
55
+ const id = pickDialect(modelId, providerId, wireFamily, override, declared);
56
+ const policy = DEFAULT_DIALECT_POLICIES[id];
57
+ if (policy !== undefined) return policy;
58
+
59
+ // An unknown dialect id (e.g. from a future DB row) falls back to the
60
+ // family default — never throw, never invent behavior.
61
+ return DEFAULT_DIALECT_POLICIES[familyDefault(wireFamily)];
62
+ }
63
+
64
+ function pickDialect(
65
+ modelId: string,
66
+ providerId: string,
67
+ wireFamily: WireFamily,
68
+ override?: string | null,
69
+ declared?: DialectId,
70
+ ): DialectId {
71
+ if (override !== undefined && override !== null && KNOWN_DIALECT_IDS.has(override)) {
72
+ return override as DialectId;
73
+ }
74
+ if (declared !== undefined) return declared;
75
+ for (const rule of DIALECT_RULES) {
76
+ if (rule.match(modelId, providerId, wireFamily)) return rule.dialect;
77
+ }
78
+ return familyDefault(wireFamily);
79
+ }
80
+
81
+ function familyDefault(wireFamily: WireFamily): DialectId {
82
+ return wireFamily === "messages" ? "anthropic" : "openai";
83
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Dialect policies & heuristic rules.
3
+ *
4
+ * The default policies are frozen to today's behavior — they are the baseline
5
+ * the golden tests assert against. A dialect not listed here falls back to the
6
+ * family default in `resolver.ts`.
7
+ */
8
+ import type { WireFamily } from "../../canonical-model";
9
+ import type { DialectId, DialectPolicy } from "./types";
10
+
11
+ /** The generic OpenAI-compatible default — mirrors current `chat` wire behavior. */
12
+ const OPENAI_POLICY: DialectPolicy = {
13
+ reasoningContentField: "reasoning_content",
14
+ maxTokensField: "max_tokens",
15
+ stripSpecialTokens: false,
16
+ supportsMultipleSystemMessages: true,
17
+ supportsToolChoice: true,
18
+ requiresAssistantContentForToolCalls: false,
19
+ allowsSyntheticReasoningContent: true,
20
+ thinkingFormat: "openai",
21
+ inband: null,
22
+ };
23
+
24
+ /** The generic Anthropic/Messages default. */
25
+ const ANTHROPIC_POLICY: DialectPolicy = {
26
+ maxTokensField: "max_tokens",
27
+ stripSpecialTokens: false,
28
+ supportsMultipleSystemMessages: true,
29
+ supportsToolChoice: true,
30
+ requiresAssistantContentForToolCalls: false,
31
+ allowsSyntheticReasoningContent: true,
32
+ thinkingFormat: "openai",
33
+ inband: null,
34
+ };
35
+
36
+ /** DeepSeek: `reasoning_content`, `max_tokens`, no tool_choice, DSML in-band. */
37
+ const DEEPSEEK_POLICY: DialectPolicy = {
38
+ reasoningContentField: "reasoning_content",
39
+ maxTokensField: "max_tokens",
40
+ stripSpecialTokens: "deepseek",
41
+ supportsMultipleSystemMessages: true,
42
+ supportsToolChoice: false,
43
+ requiresAssistantContentForToolCalls: true,
44
+ allowsSyntheticReasoningContent: false,
45
+ thinkingFormat: "deepseek",
46
+ inband: "deepseek",
47
+ };
48
+
49
+ /** Qwen: `enable_thinking` + `<tool_call>` chat template, strict system cardinality. */
50
+ const QWEN_POLICY: DialectPolicy = {
51
+ reasoningContentField: "reasoning_content",
52
+ maxTokensField: "max_tokens",
53
+ stripSpecialTokens: false,
54
+ supportsMultipleSystemMessages: false,
55
+ supportsToolChoice: true,
56
+ requiresAssistantContentForToolCalls: false,
57
+ allowsSyntheticReasoningContent: true,
58
+ thinkingFormat: "qwen",
59
+ inband: "qwen",
60
+ };
61
+
62
+ /** Resolved policy per dialect id. A missing id falls back in `resolver.ts`. */
63
+ export const DEFAULT_DIALECT_POLICIES: Readonly<Record<DialectId, DialectPolicy>> = {
64
+ openai: OPENAI_POLICY,
65
+ anthropic: ANTHROPIC_POLICY,
66
+ deepseek: DEEPSEEK_POLICY,
67
+ qwen: QWEN_POLICY,
68
+ kimi: OPENAI_POLICY,
69
+ glm: OPENAI_POLICY,
70
+ minimax: OPENAI_POLICY,
71
+ gemini: OPENAI_POLICY,
72
+ };
73
+
74
+ interface DialectRule {
75
+ readonly dialect: DialectId;
76
+ readonly match: (modelId: string, providerId: string, wireFamily: WireFamily) => boolean;
77
+ }
78
+
79
+ /**
80
+ * Heuristic rules (used only when no DB override and no explicit
81
+ * `ModelDefinition.dialect` exists). Declarative and provider/model-id based,
82
+ * matching the precedent of `src/protocol/translation/quirks.ts`.
83
+ */
84
+ export const DIALECT_RULES: readonly DialectRule[] = [
85
+ {
86
+ dialect: "deepseek",
87
+ match: (_modelId, providerId) => providerId.toLowerCase() === "deepseek",
88
+ },
89
+ {
90
+ dialect: "qwen",
91
+ match: (modelId) => /qwen|portal|alibaba|qwq/i.test(modelId),
92
+ },
93
+ ];
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Data-driven thinking/effort ladder for a model + wire family.
3
+ *
4
+ * Replaces the old `resolveSupportedReasoningEfforts` if-chain and
5
+ * `claudeUsesAdaptiveThinking` string matching with a declarative rule table,
6
+ * following the oh-my-pi pattern: a model's reasoning ladder is data (a
7
+ * `ThinkingConfig`), not code. Adding a new model generation is a table row,
8
+ * not another `if (id.includes(...))`.
9
+ *
10
+ * Precedence:
11
+ * 1. explicit declared efforts (catalog `ModelDefinition.reasoningEfforts`)
12
+ * 2. first matching rule in `THINKING_LADDER_RULES`
13
+ * 3. family default (extended ladder, per-family transport mode)
14
+ */
15
+ import type { WireFamily } from "../../canonical-model";
16
+ import {
17
+ REASONING_EFFORT_LADDER,
18
+ type ReasoningEffortLevel,
19
+ } from "../thinking";
20
+ import { BoundedLruMap } from "../../../transport/routing/bounded-lru";
21
+
22
+ /** How a reasoning effort is transported on the wire. */
23
+ export type ThinkingControlMode =
24
+ | "effort" // OpenAI-style `reasoning_effort` / `reasoning.effort`
25
+ | "budget" // Anthropic budget-era: `thinking.enabled` + `budget_tokens` (+ `output_config.effort`)
26
+ | "anthropic-adaptive" // Anthropic adaptive-era: `thinking.adaptive` + `output_config.effort`
27
+ | "google-level"; // Gemini: effort affects the output floor only
28
+
29
+ /**
30
+ * Resolved reasoning config for one model + wire family. The codecs read this
31
+ * instead of re-deriving ladders from model-id heuristics per request.
32
+ */
33
+ export interface ThinkingConfig {
34
+ /** Which wire field/transport the effort rides on. */
35
+ readonly mode: ThinkingControlMode;
36
+ /** Supported efforts, canonical ladder order, never empty. */
37
+ readonly efforts: readonly ReasoningEffortLevel[];
38
+ /** Default effort when the caller expresses none (e.g. DeepSeek forces high). */
39
+ readonly defaultLevel?: ReasoningEffortLevel;
40
+ /** True when the upstream rejects off/omitted efforts → clamp up to the floor. */
41
+ readonly requiresEffort?: boolean;
42
+ }
43
+
44
+ /** Optional model-declared thinking override (compile-time, persisted catalog). */
45
+ export interface ThinkingModelDecl {
46
+ readonly efforts?: readonly string[];
47
+ readonly mode?: ThinkingControlMode;
48
+ }
49
+
50
+ /** DeepSeek's default effort, owned here so rule table and helpers agree. */
51
+ export const DEEPSEEK_DEFAULT_EFFORT = "high" as const;
52
+
53
+ /** Responses-wire default: no `max` tier (OpenAI/OpenCode/Azure). */
54
+ const RESPONSES_WIRE_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
55
+ "minimal",
56
+ "low",
57
+ "medium",
58
+ "high",
59
+ "xhigh",
60
+ ];
61
+
62
+ /** Full 6-tier scale (Claude, MiMo, Kimi, DeepSeek). */
63
+ const EXTENDED_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
64
+ "minimal",
65
+ "low",
66
+ "medium",
67
+ "high",
68
+ "xhigh",
69
+ "max",
70
+ ];
71
+
72
+ /** Standard 4-tier scale (Gemini, Qwen). */
73
+ const BASE_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
74
+ "minimal",
75
+ "low",
76
+ "medium",
77
+ "high",
78
+ ];
79
+
80
+ /** `mimo-v2.6` rejects every effort outside this set (live 500s). */
81
+ const MIMO_V26_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = ["low", "medium", "high"];
82
+
83
+ /** Mercury chat ladder: `{instant, low, medium, high}` (live 400s outside). */
84
+ const INCEPTION_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
85
+ "instant",
86
+ "low",
87
+ "medium",
88
+ "high",
89
+ ];
90
+
91
+ /** OpenAI 5.6/6/daybreak + new-gen Claude adaptive: `low..max`, no `minimal`. */
92
+ const LOW_TO_MAX_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
93
+ "low",
94
+ "medium",
95
+ "high",
96
+ "xhigh",
97
+ "max",
98
+ ];
99
+
100
+ /** `gpt-5.5` rows: neither `minimal` nor `max`. */
101
+ const GPT55_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
102
+ "low",
103
+ "medium",
104
+ "high",
105
+ "xhigh",
106
+ ];
107
+
108
+ /** Claude adaptive 4.6 pair: `low..high`. */
109
+ const CLAUDE_ADAPTIVE_46_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
110
+ "low",
111
+ "medium",
112
+ "high",
113
+ ];
114
+
115
+ interface ThinkingLadderRule {
116
+ readonly id?: RegExp;
117
+ readonly provider?: RegExp;
118
+ readonly wireFamily?: WireFamily;
119
+ readonly efforts: readonly ReasoningEffortLevel[];
120
+ readonly mode?: ThinkingControlMode;
121
+ readonly defaultLevel?: ReasoningEffortLevel;
122
+ readonly requiresEffort?: boolean;
123
+ /** True when `id` expects the dot → dash normalized id (Claude generations). */
124
+ readonly normalizeClaudeId?: boolean;
125
+ }
126
+
127
+ /**
128
+ * Rule table — the declarative replacement for the previous if-chain.
129
+ * First match wins; `wireFamily` rules must precede family-agnostic ones so
130
+ * the Responses wire keeps its cap (`no max`) even over Claude-family model
131
+ * ids, exactly like the old `wireFamily === "responses"` early return.
132
+ *
133
+ * Claude generation rules use `normalizeClaudeId` and match against the
134
+ * dot-tolerant form (`claude-opus-4.6` → `claude-opus-4-6`), mirroring the
135
+ * removed `claudeUsesAdaptiveThinking` behavior.
136
+ */
137
+ export const THINKING_LADDER_RULES: readonly ThinkingLadderRule[] = [
138
+ // --- Responses wire (cap at `xhigh` unless a generation ladder says otherwise) ---
139
+ { wireFamily: "responses", id: /gpt-5\.5/, efforts: GPT55_SUPPORTED_EFFORTS },
140
+ { wireFamily: "responses", id: /gpt-5\.6|gpt-6|daybreak/, efforts: LOW_TO_MAX_SUPPORTED_EFFORTS },
141
+ { wireFamily: "responses", efforts: RESPONSES_WIRE_SUPPORTED_EFFORTS },
142
+ // --- Family heuristics (chat/messages/gemini) ---
143
+ { id: /muse/, efforts: RESPONSES_WIRE_SUPPORTED_EFFORTS },
144
+ { id: /gemini/, efforts: BASE_SUPPORTED_EFFORTS, mode: "google-level" },
145
+ { id: /mimo-v2\.6/, efforts: MIMO_V26_SUPPORTED_EFFORTS },
146
+ {
147
+ id: /mercury/,
148
+ wireFamily: "chat",
149
+ efforts: INCEPTION_SUPPORTED_EFFORTS,
150
+ },
151
+ // Claude adaptive 4.6 pair: `low..high`, no `xhigh`/`max`.
152
+ {
153
+ id: /opus-4-6|sonnet-4-6/,
154
+ normalizeClaudeId: true,
155
+ efforts: CLAUDE_ADAPTIVE_46_SUPPORTED_EFFORTS,
156
+ mode: "anthropic-adaptive",
157
+ },
158
+ // New-gen Claude adaptive: `low..max`.
159
+ {
160
+ id: /fable|mythos|opus-5|sonnet-5|opus-4-7|opus-4-8/,
161
+ normalizeClaudeId: true,
162
+ efforts: LOW_TO_MAX_SUPPORTED_EFFORTS,
163
+ mode: "anthropic-adaptive",
164
+ },
165
+ // Budget-era Claude rows: `minimal..xhigh`, no `max`.
166
+ {
167
+ id: /opus-4-5|sonnet-4-5|haiku-4-5|opus-4-1|opus-4-0|sonnet-4-0/,
168
+ normalizeClaudeId: true,
169
+ efforts: RESPONSES_WIRE_SUPPORTED_EFFORTS,
170
+ },
171
+ // Any other Claude row: extended ladder, budget-era transport.
172
+ { id: /claude/, efforts: EXTENDED_SUPPORTED_EFFORTS },
173
+ ];
174
+
175
+ function familyDefaultMode(wireFamily: WireFamily): ThinkingControlMode {
176
+ if (wireFamily === "messages") return "budget";
177
+ if (wireFamily === "gemini") return "google-level";
178
+ return "effort";
179
+ }
180
+
181
+ /**
182
+ * Resolves the thinking config for a model + wire family. Pure, never throws,
183
+ * always returns a config (the family default is the floor).
184
+ *
185
+ * The rule table is deterministic and the codecs call this once per encode, so
186
+ * the no-declared-efforts result (the common case) is memoized per
187
+ * model/wire/provider key. Declared efforts bypass the cache because the
188
+ * `declared` object identity changes per call.
189
+ *
190
+ * The cache is a {@link BoundedLruMap}: the key space is bounded by the model
191
+ * catalog, but a request can name an arbitrary model string, so an unbounded
192
+ * map would pin an entry for every miss forever. The bound keeps memory flat
193
+ * while hot models stay resident.
194
+ */
195
+ const THINKING_RESOLVE_CACHE_MAX_ENTRIES = 1024;
196
+ const THINKING_RESOLVE_CACHE = new BoundedLruMap<string, ThinkingConfig>(
197
+ THINKING_RESOLVE_CACHE_MAX_ENTRIES,
198
+ );
199
+
200
+ export function resolveThinkingForModel(
201
+ modelId: string,
202
+ providerId: string,
203
+ wireFamily: WireFamily,
204
+ declared?: ThinkingModelDecl,
205
+ ): ThinkingConfig {
206
+ const id = modelId.toLowerCase();
207
+
208
+ // Declared efforts (catalog `reasoningEfforts`) win when present and valid.
209
+ if (declared?.efforts !== undefined && declared.efforts.length > 0) {
210
+ const filtered = declared.efforts.filter((e): e is ReasoningEffortLevel =>
211
+ REASONING_EFFORT_LADDER.includes(e as ReasoningEffortLevel),
212
+ );
213
+ if (filtered.length > 0) {
214
+ return {
215
+ mode: declared.mode ?? familyDefaultMode(wireFamily),
216
+ efforts: filtered,
217
+ };
218
+ }
219
+ }
220
+
221
+ const cacheKey = `${id}|${providerId}|${wireFamily}`;
222
+ const cached = THINKING_RESOLVE_CACHE.get(cacheKey);
223
+ if (cached !== undefined) return cached;
224
+
225
+ for (const rule of THINKING_LADDER_RULES) {
226
+ if (rule.wireFamily !== undefined && rule.wireFamily !== wireFamily) continue;
227
+ if (rule.id !== undefined) {
228
+ const source = rule.normalizeClaudeId === true ? id.replaceAll(".", "-") : id;
229
+ if (!rule.id.test(source)) continue;
230
+ }
231
+ if (rule.provider !== undefined && !rule.provider.test(providerId)) continue;
232
+ const resolved: ThinkingConfig = {
233
+ mode: rule.mode ?? familyDefaultMode(wireFamily),
234
+ efforts: rule.efforts,
235
+ ...(rule.defaultLevel === undefined ? {} : { defaultLevel: rule.defaultLevel }),
236
+ ...(rule.requiresEffort === undefined ? {} : { requiresEffort: rule.requiresEffort }),
237
+ };
238
+ THINKING_RESOLVE_CACHE.set(cacheKey, resolved);
239
+ return resolved;
240
+ }
241
+
242
+ const fallback: ThinkingConfig = { mode: familyDefaultMode(wireFamily), efforts: EXTENDED_SUPPORTED_EFFORTS };
243
+ THINKING_RESOLVE_CACHE.set(cacheKey, fallback);
244
+ return fallback;
245
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Wire dialect layer — the per-provider dial that sits *below* `WireFamily`.
3
+ *
4
+ * `WireFamily` names the transport envelope (chat / responses / messages /
5
+ * gemini). A dialect names the *dial* that family speaks for one provider or
6
+ * model family: DeepSeek's `reasoning_content` + DSML in-band tool calls,
7
+ * Qwen's `enable_thinking` + `<tool_call>` chat template, Anthropic's native
8
+ * Messages vocabulary, and the generic OpenAI-compatible default.
9
+ *
10
+ * The split matters because "canonical forces everything through one general
11
+ * shape" is what made the pipeline fragile: quirks unique to a family
12
+ * (reasoning field names, system-message cardinality, tool_choice support,
13
+ * in-band tool markup) had nowhere to live except ad-hoc `if (providerId ===
14
+ * …)` branches inside generic codecs. Here they are a resolved policy object
15
+ * the codecs read, exactly like oh-my-pi's `OpenAICompatPolicy`.
16
+ */
17
+
18
+ /** A wire dialect. `"openai"`/`"anthropic"` are the generic defaults for each
19
+ * `WireFamily`; the rest are family-specific dialects. */
20
+ export type DialectId =
21
+ | "openai" // OpenAI-compatible generik (default wire chat)
22
+ | "anthropic" // Anthropic-compatible/ Claude-native dialect: signature thinking, cache-control
23
+ | "deepseek" // DSML + reasoning_content + max_tokens + strip tokens
24
+ | "qwen" // enable_thinking + <tool_call> + strict chat template
25
+ | "kimi"
26
+ | "glm"
27
+ | "minimax"
28
+ | "gemini"; // extensibel
29
+
30
+ /** Resolved policy consumed by the wire codecs. Every field has a safe
31
+ * default matching today's behavior; absent values fall back to it. */
32
+ export interface DialectPolicy {
33
+ /** Which wire field carries a previous assistant turn's reasoning. None when
34
+ * the wire has no reasoning side-channel (e.g. strict non-reasoning
35
+ * upstreams that reject the extra field). Default `"reasoning_content"` —
36
+ * the Chat wire's side channel, matching current behavior. */
37
+ readonly reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
38
+ /** Which token-limit field the upstream accepts. Default `"max_tokens"`. */
39
+ readonly maxTokensField: "max_tokens" | "max_completion_tokens";
40
+ /** When truthy, strip the named family's special tokens from the stream. */
41
+ readonly stripSpecialTokens: "deepseek" | false;
42
+ /** False collapses multiple `system` messages into one (strict chat templates,
43
+ * e.g. Qwen Portal / Alibaba / MiniMax). */
44
+ readonly supportsMultipleSystemMessages: boolean;
45
+ /** False drops `tool_choice` entirely (DeepSeek rejects it). */
46
+ readonly supportsToolChoice: boolean;
47
+ /** True requires every tool-call assistant turn to also carry text content
48
+ * (DeepSeek V4 validates the exact content). */
49
+ readonly requiresAssistantContentForToolCalls: boolean;
50
+ /** False rejects synthetic reasoning placeholders on a tool-call turn
51
+ * (DeepSeek V4 validates the exact value). */
52
+ readonly allowsSyntheticReasoningContent: boolean;
53
+ /** How a reasoning effort is encoded on the wire. */
54
+ readonly thinkingFormat: "openai" | "qwen" | "deepseek" | "chat-template";
55
+ /** In-band tool-call codec to use (DSML / `<tool_call>` …), or null for
56
+ * structured-only tool calls. */
57
+ readonly inband: DialectId | null;
58
+ }