cartethyia 2.7.3 → 2.7.5

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 (625) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +1 -0
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js +9 -0
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-Be5Uzamx.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/{cli-tools-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
  87. package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
  92. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  94. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  95. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  96. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  97. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  98. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  99. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  100. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  101. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  102. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  103. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  104. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
  106. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
  108. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  109. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  110. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  111. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  112. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
  114. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
  116. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  117. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
  118. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  119. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
  120. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  121. package/dist/dashboard/console-backgrounds.jpg +0 -0
  122. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  123. package/dist/dashboard/index.html +2 -2
  124. package/dist/dashboard/index.html.gz +0 -0
  125. package/dist/dashboard/precompressed-assets.json +59 -57
  126. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  127. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  128. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  129. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  130. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  131. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  132. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  133. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  134. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  135. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  136. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  137. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  138. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  139. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  140. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  141. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  142. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  143. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  144. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  145. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  146. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  147. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  148. package/package.json +4 -5
  149. package/scripts/cli/update.ts +15 -41
  150. package/src/boot/app.ts +67 -10
  151. package/src/boot/dependencies.ts +786 -732
  152. package/src/boot/lifecycle.ts +11 -12
  153. package/src/config/cooldowns.ts +34 -55
  154. package/src/config/network.ts +8 -4
  155. package/src/config/package-paths.ts +1 -1
  156. package/src/config/providers.ts +13 -14
  157. package/src/config/resources.ts +0 -5
  158. package/src/config/runtime.ts +2 -5
  159. package/src/config/spec.ts +26 -6
  160. package/src/console/api-keys/ban-constants.ts +38 -0
  161. package/src/console/api-keys/contracts.ts +92 -31
  162. package/src/console/api-keys/routes.ts +131 -6
  163. package/src/console/audit/routes.ts +3 -3
  164. package/src/console/auth/access.ts +4 -4
  165. package/src/console/auth/account-reset.ts +5 -5
  166. package/src/console/auth/audit-recorder.ts +1 -1
  167. package/src/console/auth/lockout.ts +7 -3
  168. package/src/console/auth/row-coercion.ts +57 -12
  169. package/src/console/auth/session-resolver.ts +1 -1
  170. package/src/console/auth/session-service.ts +8 -2
  171. package/src/console/auth/session.ts +40 -34
  172. package/src/console/auth/setup-service.ts +4 -3
  173. package/src/console/auth/user-store.ts +1 -1
  174. package/src/console/backup/9router-map.ts +0 -3
  175. package/src/console/backup/contracts.ts +11 -9
  176. package/src/console/backup/export-endpoint.ts +11 -12
  177. package/src/console/backup/nine-router.ts +1 -7
  178. package/src/console/backup/routes.ts +32 -36
  179. package/src/console/backup/service.ts +468 -468
  180. package/src/console/backup/store.ts +47 -29
  181. package/src/console/backup/validate.ts +11 -1
  182. package/src/console/cli-tools/contracts.ts +7 -7
  183. package/src/console/cli-tools/injectors/cline.ts +110 -110
  184. package/src/console/cli-tools/injectors/codex.ts +181 -181
  185. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  186. package/src/console/cli-tools/injectors/driver.ts +247 -247
  187. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  188. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  189. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  190. package/src/console/cli-tools/routes.ts +4 -4
  191. package/src/console/cli-tools/service.ts +1 -1
  192. package/src/console/cli-tools/store.ts +2 -2
  193. package/src/console/console-router-guards.ts +2 -2
  194. package/src/console/console-router.ts +36 -4
  195. package/src/console/dashboard-assets.ts +9 -9
  196. package/src/console/data-management/routes.ts +3 -3
  197. package/src/console/domain-registration.ts +18 -4
  198. package/src/console/inferhub/client.ts +0 -7
  199. package/src/console/inferhub/key-store.ts +1 -1
  200. package/src/console/inferhub/normalize.ts +2 -2
  201. package/src/console/inferhub/routes.ts +1 -1
  202. package/src/console/inferhub/types.ts +1 -1
  203. package/src/console/observability/contracts.ts +5 -1
  204. package/src/console/observability/live.ts +34 -21
  205. package/src/console/observability/logs.ts +4 -4
  206. package/src/console/observability/performance.ts +3 -3
  207. package/src/console/observability/routes.ts +3 -3
  208. package/src/console/observability/sse.ts +62 -8
  209. package/src/console/observability/store.ts +36 -20
  210. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  211. package/src/console/providers/catalog/contracts.ts +82 -22
  212. package/src/console/providers/catalog/model-operations.ts +1 -1
  213. package/src/console/providers/catalog/provider-operations.ts +110 -17
  214. package/src/console/providers/catalog/public-model-store.ts +0 -0
  215. package/src/console/providers/catalog/routes.ts +30 -6
  216. package/src/console/providers/catalog/store-helpers.ts +349 -326
  217. package/src/console/providers/catalog/store.ts +1662 -1292
  218. package/src/console/providers/detail/routes.ts +4 -4
  219. package/src/console/providers/oauth/callback-listener.ts +100 -18
  220. package/src/console/providers/oauth/routes.ts +95 -16
  221. package/src/console/quota/account-quota-global.ts +41 -3
  222. package/src/console/quota/account-quota-shared.ts +10 -3
  223. package/src/console/quota/account-quota-tenant.ts +285 -67
  224. package/src/console/quota/account-test-egress.ts +4 -4
  225. package/src/console/quota/daily-growth-job.ts +308 -0
  226. package/src/console/quota/native-identity.ts +2 -2
  227. package/src/console/quota/quota-view.ts +1 -1
  228. package/src/console/remote-support/routes.ts +1 -1
  229. package/src/console/remote-support/service.ts +2 -2
  230. package/src/console/routing/model/contracts.ts +1 -1
  231. package/src/console/routing/model/routes.ts +3 -3
  232. package/src/console/routing/pools/contracts.ts +13 -10
  233. package/src/console/routing/pools/import-parse.ts +3 -3
  234. package/src/console/routing/pools/operations.ts +14 -7
  235. package/src/console/routing/pools/probe-verdict.ts +8 -3
  236. package/src/console/routing/pools/relay-deploy.ts +12 -12
  237. package/src/console/routing/pools/routes.ts +3 -3
  238. package/src/console/routing/pools/store.ts +21 -1
  239. package/src/console/settings/contracts.ts +29 -2
  240. package/src/console/settings/routes.ts +3 -3
  241. package/src/console/settings/store.ts +20 -0
  242. package/src/console/share/share-router.ts +112 -44
  243. package/src/console/share/share-stats.ts +109 -39
  244. package/src/console/share/share-usage.ts +1 -1
  245. package/src/console/shared/errors.ts +2 -2
  246. package/src/console/shared/object.ts +18 -0
  247. package/src/console/shared/read-through-cache.ts +42 -6
  248. package/src/console/shared/transaction.ts +14 -0
  249. package/src/console/system-prompt/contracts.ts +89 -12
  250. package/src/console/system-prompt/routes.ts +98 -45
  251. package/src/console/updates/apply-update.ts +244 -0
  252. package/src/console/updates/routes.ts +256 -98
  253. package/src/main.ts +11 -5
  254. package/src/network/http2-bidi.ts +1 -1
  255. package/src/network/ip-utils.ts +298 -0
  256. package/src/network/outbound-fetch.ts +76 -5
  257. package/src/network/pool/agent.ts +7 -6
  258. package/src/network/pool/byte-accounting.ts +1 -1
  259. package/src/network/pool/resolver.ts +55 -51
  260. package/src/network/pool/selector.ts +150 -136
  261. package/src/network/pool-cooldown-store.ts +71 -16
  262. package/src/network/pool-health-machine.ts +22 -2
  263. package/src/network/rate-limit-verdict.ts +68 -0
  264. package/src/network/retry.ts +1 -1
  265. package/src/network/ssrf.ts +24 -75
  266. package/src/observability/event-log.ts +2 -2
  267. package/src/observability/event-query.ts +286 -214
  268. package/src/observability/log-sanitize.ts +1 -1
  269. package/src/observability/metrics.ts +4 -4
  270. package/src/observability/payload-capture.ts +6 -5
  271. package/src/observability/payload-store.ts +1 -1
  272. package/src/observability/runtime-metrics.ts +1 -1
  273. package/src/observability/telemetry-buffer.ts +12 -3
  274. package/src/observability/telemetry-reconciliation.ts +3 -3
  275. package/src/observability/telemetry-status.ts +1 -1
  276. package/src/observability/token-speed.ts +1 -1
  277. package/src/persistence/connection-url.ts +1 -1
  278. package/src/persistence/db-handle.ts +1 -1
  279. package/src/persistence/db-pg.ts +2 -2
  280. package/src/persistence/db-sqlite.ts +10 -5
  281. package/src/persistence/dialect.ts +5 -1
  282. package/src/persistence/readiness.ts +19 -7
  283. package/src/persistence/redis.ts +1 -1
  284. package/src/persistence/schema.ts +177 -45
  285. package/src/persistence/shared-cache.ts +2 -2
  286. package/src/persistence/sort-order.ts +1 -1
  287. package/src/persistence/stores/api-key-store.ts +109 -4
  288. package/src/persistence/stores/share-store.ts +20 -8
  289. package/src/persistence/stores/telemetry-store.ts +1 -1
  290. package/src/persistence/system-prompt-store.ts +63 -18
  291. package/src/persistence/tenant-preferences.ts +1 -1
  292. package/src/persistence/ttl-cache.ts +3 -3
  293. package/src/persistence/usage-flush.ts +7 -2
  294. package/src/protocol/canonical-model.ts +1 -1
  295. package/src/protocol/codecs.ts +12 -16
  296. package/src/protocol/dsml.ts +3 -49
  297. package/src/protocol/gateway-error.ts +6 -3
  298. package/src/protocol/image-clamp.ts +2 -2
  299. package/src/protocol/image-generation.ts +2 -2
  300. package/src/protocol/reasoning-text.ts +1 -1
  301. package/src/protocol/reasoning.ts +7 -52
  302. package/src/protocol/registry.ts +50 -15
  303. package/src/protocol/request/chat.ts +46 -19
  304. package/src/protocol/request/codex.ts +1 -1
  305. package/src/protocol/request/messages.ts +46 -14
  306. package/src/protocol/request/responses.ts +19 -10
  307. package/src/protocol/response/chat.ts +27 -12
  308. package/src/protocol/response/codex.ts +2 -2
  309. package/src/protocol/response/gemini.ts +1 -1
  310. package/src/protocol/response/messages.ts +6 -6
  311. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  312. package/src/protocol/response/responses.ts +3 -3
  313. package/src/protocol/sse-decoder.ts +64 -4
  314. package/src/protocol/stop-reason.ts +2 -2
  315. package/src/protocol/think-split.ts +347 -0
  316. package/src/protocol/translation/attachment-integrity.ts +3 -3
  317. package/src/protocol/translation/cache-controls.ts +8 -20
  318. package/src/protocol/translation/capabilities.ts +8 -8
  319. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  320. package/src/protocol/translation/dialect/inband.ts +39 -0
  321. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  322. package/src/protocol/translation/dialect/qwen.ts +114 -0
  323. package/src/protocol/translation/dialect/resolver.ts +83 -0
  324. package/src/protocol/translation/dialect/rules.ts +93 -0
  325. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  326. package/src/protocol/translation/dialect/types.ts +58 -0
  327. package/src/protocol/translation/quirks.ts +18 -4
  328. package/src/protocol/translation/thinking.ts +12 -252
  329. package/src/protocol/translation/tool-id.ts +3 -3
  330. package/src/protocol/translation/tool-repair.ts +1 -1
  331. package/src/protocol/upstream/post-json.ts +6 -1
  332. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  333. package/src/providers/adapters/base-adapter.ts +1 -1
  334. package/src/providers/adapters/compatible-adapter.ts +44 -5
  335. package/src/providers/authentication/jwt-validator.ts +0 -5
  336. package/src/providers/authentication/oauth-client.ts +254 -253
  337. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  338. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  339. package/src/providers/bundled-discovery-providers.ts +6 -30
  340. package/src/providers/configured-openai-providers.ts +82 -97
  341. package/src/providers/configured-provider.ts +1 -1
  342. package/src/providers/default-registry.ts +23 -35
  343. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  344. package/src/providers/discovery/discovery-types.ts +0 -1
  345. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  346. package/src/providers/discovery/probe-phases.ts +50 -15
  347. package/src/providers/discovery/probing-service.ts +18 -20
  348. package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
  349. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  350. package/src/providers/integrations/anthropic.ts +4 -11
  351. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  352. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  353. package/src/providers/integrations/antigravity/antigravity.ts +15 -26
  354. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
  355. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  356. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  357. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  358. package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
  359. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  360. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  361. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  362. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
  363. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  364. package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
  365. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  366. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  367. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  368. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  369. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  370. package/src/providers/integrations/claude/claude.ts +3 -10
  371. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  372. package/src/providers/integrations/cline/cline.ts +2 -2
  373. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  374. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  375. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  376. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  377. package/src/providers/integrations/codex/codex.ts +14 -15
  378. package/src/providers/integrations/commandcode/commandcode.ts +76 -116
  379. package/src/providers/integrations/commandcode/quota.ts +132 -48
  380. package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
  381. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  382. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  383. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  384. package/src/providers/integrations/cursor/cursor.ts +53 -20
  385. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  386. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  387. package/src/providers/integrations/factory/factory.ts +13 -13
  388. package/src/providers/integrations/gemini.ts +11 -11
  389. package/src/providers/integrations/github/github-discovery.ts +55 -10
  390. package/src/providers/integrations/github/github-oauth.ts +6 -5
  391. package/src/providers/integrations/github/github.ts +47 -20
  392. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  393. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  394. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  395. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  396. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  397. package/src/providers/integrations/grok/grok.ts +88 -42
  398. package/src/providers/integrations/inferhub.ts +4 -11
  399. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  400. package/src/providers/integrations/kimi/kimi.ts +4 -11
  401. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  402. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  403. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  404. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  405. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  406. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  407. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  408. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  409. package/src/providers/integrations/kiro/kiro.ts +91 -78
  410. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  411. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  412. package/src/providers/integrations/muse/muse.ts +6 -9
  413. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  414. package/src/providers/integrations/opencode/provider.ts +17 -24
  415. package/src/providers/integrations/perplexity.ts +2 -2
  416. package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
  417. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  418. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  419. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  420. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  421. package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
  422. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  423. package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
  424. package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
  425. package/src/providers/integrations/qoder/qoder.ts +521 -63
  426. package/src/providers/integrations/shared/connect.ts +0 -54
  427. package/src/providers/integrations/shared/hash.ts +15 -0
  428. package/src/providers/integrations/shared/session-cache.ts +136 -0
  429. package/src/providers/integrations/tokenharbor.ts +2 -2
  430. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  431. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  432. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  433. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  434. package/src/providers/integrations/trae/trae.ts +59 -19
  435. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  436. package/src/providers/integrations/zcode/oauth.ts +1 -1
  437. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  438. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  439. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  440. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  441. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  442. package/src/providers/operations/account-error-classification.ts +131 -116
  443. package/src/providers/operations/account-health-service.ts +31 -39
  444. package/src/providers/operations/account-reset-service.ts +58 -1
  445. package/src/providers/operations/cli-platform.ts +31 -5
  446. package/src/providers/operations/client-version-resolver.ts +4 -6
  447. package/src/providers/operations/client-versions.ts +10 -7
  448. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  449. package/src/providers/operations/provider-catalog-service.ts +22 -6
  450. package/src/providers/operations/provider-credential-service.ts +35 -6
  451. package/src/providers/operations/upstream-deadline.ts +3 -3
  452. package/src/providers/provider-contracts.ts +65 -10
  453. package/src/providers/provider-credential-resolver.ts +1 -1
  454. package/src/providers/provider-metadata.ts +17 -2
  455. package/src/providers/provider-model-definition.ts +22 -1
  456. package/src/providers/provider-probe.ts +4 -2
  457. package/src/providers/provider-registry.ts +4 -4
  458. package/src/providers/provider-usage.ts +81 -16
  459. package/src/providers/quota/cache.ts +29 -0
  460. package/src/providers/quota/quota-contracts.ts +28 -10
  461. package/src/providers/quota/refresh.ts +29 -6
  462. package/src/providers/quota/snapshot.ts +94 -0
  463. package/src/providers/search/search-provider.ts +6 -8
  464. package/src/providers/search/search-providers.ts +2 -1
  465. package/src/security/abuse.ts +8 -8
  466. package/src/security/admission/reasons.ts +17 -17
  467. package/src/security/admission/service.ts +2 -2
  468. package/src/security/admission/ttl.ts +61 -61
  469. package/src/security/api-key-auth.ts +20 -8
  470. package/src/security/client-router-fingerprint.ts +4 -4
  471. package/src/security/credential-codec.ts +3 -3
  472. package/src/security/csrf.ts +2 -2
  473. package/src/security/ip-boundary.ts +16 -189
  474. package/src/security/model-access-rule.ts +1 -1
  475. package/src/security/outbound-headers.ts +18 -0
  476. package/src/shared/coerce.ts +19 -0
  477. package/src/shared/invoke-guard.ts +47 -0
  478. package/src/transport/contracts.ts +13 -4
  479. package/src/transport/dispatch/attempt-finalize.ts +5 -4
  480. package/src/transport/dispatch/attempt-loop.ts +10 -7
  481. package/src/transport/dispatch/capture-policy.ts +34 -37
  482. package/src/transport/dispatch/empty-stream.ts +28 -25
  483. package/src/transport/dispatch/generation.ts +33 -53
  484. package/src/transport/dispatch/leases.ts +8 -8
  485. package/src/transport/dispatch/native-shared.ts +127 -0
  486. package/src/transport/dispatch/proxy-request.ts +10 -8
  487. package/src/transport/dispatch/responses-compact.ts +35 -56
  488. package/src/transport/dispatch/retry-policy.ts +4 -67
  489. package/src/transport/dispatch/stream-bridge.ts +1 -1
  490. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  491. package/src/transport/dispatch/streaming/finalize.ts +11 -10
  492. package/src/transport/dispatch/streaming/stream.ts +7 -3
  493. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  494. package/src/transport/dispatch/systemone.ts +27 -55
  495. package/src/transport/dispatch/token-count.ts +49 -46
  496. package/src/transport/dispatch/websearch.ts +24 -50
  497. package/src/transport/drain-endpoint.ts +3 -3
  498. package/src/transport/failure-policy.ts +19 -6
  499. package/src/transport/guards/auth-guard.ts +6 -11
  500. package/src/transport/guards/body-policy.ts +7 -7
  501. package/src/transport/guards/error-lifecycle.ts +18 -3
  502. package/src/transport/guards/guard-errors.ts +7 -3
  503. package/src/transport/guards/headroom-guard.ts +2 -2
  504. package/src/transport/guards/pipeline.ts +7 -7
  505. package/src/transport/guards/readiness-guard.ts +16 -2
  506. package/src/transport/guards/request-context.ts +15 -20
  507. package/src/transport/request/preparer.ts +306 -27
  508. package/src/transport/request/rtk/compress-request.ts +3 -3
  509. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  510. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  511. package/src/transport/request/state.ts +5 -20
  512. package/src/transport/request/system-prompt-injection.ts +74 -11
  513. package/src/transport/request/tenant-preferences.ts +22 -0
  514. package/src/transport/routing/candidate-index.ts +2 -2
  515. package/src/transport/routing/round-robin.ts +1 -1
  516. package/src/transport/routing/route-catalog.ts +113 -19
  517. package/src/transport/routing/route-model.ts +131 -38
  518. package/src/transport/routing/router.ts +154 -39
  519. package/src/transport/shutdown-notice.ts +21 -9
  520. package/src/transport/surface/adapters.ts +3 -3
  521. package/src/transport/surface/chat/encode.ts +7 -7
  522. package/src/transport/surface/chat/parse.ts +7 -7
  523. package/src/transport/surface/completion.ts +3 -3
  524. package/src/transport/surface/content-parts.ts +1 -1
  525. package/src/transport/surface/dialects.ts +2 -2
  526. package/src/transport/surface/messages/encode.ts +1 -1
  527. package/src/transport/surface/messages/parse.ts +3 -3
  528. package/src/transport/surface/responses/adapter.ts +1 -1
  529. package/src/transport/surface/responses/errors.ts +0 -9
  530. package/src/transport/vision-handoff-cache.ts +58 -0
  531. package/src/transport/vision-handoff.ts +376 -0
  532. package/src/workers/account-identity-sweep.ts +2 -2
  533. package/src/workers/buddy-growth-tasks.ts +392 -346
  534. package/src/workers/daily-checkin.ts +102 -49
  535. package/src/workers/maintenance-lease.ts +2 -35
  536. package/src/workers/oauth-refresh-sweep.ts +8 -1
  537. package/src/workers/quota-refresh-sweep.ts +120 -82
  538. package/src/workers/scheduler.ts +67 -4
  539. package/src/workers/sweep-runner.ts +24 -5
  540. package/src/workers/zcode-gift-claim.ts +108 -0
  541. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  542. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  543. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  544. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  545. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  546. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
  547. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  548. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  549. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  550. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  551. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  552. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  553. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  554. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  555. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  556. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  557. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  558. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  559. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  560. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  561. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  562. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  563. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  564. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  565. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  566. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  567. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  568. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  569. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  570. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  571. package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
  572. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  573. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  574. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  575. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  576. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  577. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  578. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  579. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  580. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  581. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  582. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  583. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  584. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  585. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  586. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  587. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  588. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  589. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  590. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  591. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  592. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  593. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  594. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  595. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  596. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  597. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  598. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  599. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  600. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  601. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  602. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  603. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  604. package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
  605. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  606. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  607. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  608. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  609. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  610. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  611. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  612. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  613. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  614. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  615. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  616. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  617. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  618. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  619. package/src/network/response-headers.ts +0 -39
  620. package/src/protocol/response/dsml.ts +0 -38
  621. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  622. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
  623. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  624. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  625. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -25,8 +25,11 @@
25
25
  import {
26
26
  decodeJwtPayload,
27
27
  devicePollBackoff,
28
+ generateOAuthState,
29
+ generatePkcePair,
28
30
  nonEmptyString,
29
31
  nonEmptyTrimmedString,
32
+ parseDeviceAuthStart,
30
33
  readJsonResponse,
31
34
  record,
32
35
  } from "../../authentication/oauth-flow-store";
@@ -53,6 +56,8 @@ import {
53
56
  getKiroVersion,
54
57
  } from "../../operations/client-versions";
55
58
  import { deriveApiKeyMachineId, deriveOAuthMachineId, normalizeMachineId } from "./kiro-machine-id";
59
+ import { KIRO_MANAGEMENT_HOST_TEMPLATE, AWS_REGION_PATTERN, KIRO_DEFAULT_REGION } from "./kiro-catalog";
60
+ import { fetchKiroQuota } from "./kiro-quota";
56
61
 
57
62
  /** Auth methods this integration supports, as stored on the account. */
58
63
  export type KiroAuthMethod =
@@ -64,35 +69,40 @@ export type KiroAuthMethod =
64
69
  | "external_idp"
65
70
  | "api_key";
66
71
 
67
- /** AWS region shape, checked before a region is interpolated into a URL. */
68
- const AWS_REGION_PATTERN = /^[a-z]{2}-[a-z]+-\d{1,2}$/;
69
-
70
- /** Default region when an account carries none. */
71
- const DEFAULT_REGION = "us-east-1";
72
+ const DEFAULT_REGION: string = KIRO_DEFAULT_REGION;
72
73
 
73
74
  /** The vendor's own sign-in and refresh service for the social families. */
74
75
  export const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev" as const;
75
76
 
77
+ /** The web portal that offers the merged social/OIDC sign-in. */
78
+ export const KIRO_SOCIAL_PORTAL = "https://app.kiro.dev" as const;
79
+
76
80
  /**
77
81
  * Redirect URI the vendor's identity provider allowlists. It is a custom
78
82
  * scheme, not a loopback address: the authorization code is shown to the
79
- * operator rather than delivered to a local listener.
83
+ * operator rather than delivered to a local listener. The 1.4.62 IDE uses
84
+ * `kiro://kiro.oauth/callback` (observed in the agent bundle).
80
85
  */
81
- export const KIRO_SOCIAL_REDIRECT_URI = "kiro://kiro.kiroAgent/authenticate-success" as const;
86
+ export const KIRO_SOCIAL_REDIRECT_URI = "kiro://kiro.oauth/callback" as const;
82
87
 
83
- /** Scopes the OIDC client registration requests. */
88
+ /** Scopes the OIDC client registration requests (observed in the 1.4.62 IDE). */
84
89
  const KIRO_SCOPES = [
85
90
  "codewhisperer:completions",
86
91
  "codewhisperer:analysis",
87
92
  "codewhisperer:conversations",
93
+ "codewhisperer:transformations",
94
+ "codewhisperer:taskassist",
88
95
  ] as const;
89
96
 
90
97
  /** Grant types the OIDC client registration declares. */
91
98
  const KIRO_GRANT_TYPES = [
92
- "urn:ietf:params:oauth:grant-type:device_code",
99
+ "authorization_code",
93
100
  "refresh_token",
94
101
  ] as const;
95
102
 
103
+ /** Loopback redirect the 1.4.62 IDE registers for the IdC/Builder-ID flow. */
104
+ const KIRO_LOOPBACK_REDIRECT_URI = "http://127.0.0.1/oauth/callback" as const;
105
+
96
106
  /**
97
107
  * Client name the OIDC registration presents.
98
108
  *
@@ -101,7 +111,7 @@ const KIRO_GRANT_TYPES = [
101
111
  */
102
112
  const KIRO_OIDC_CLIENT_NAME = "Kiro IDE" as const;
103
113
 
104
- /** Organization entry point the device flow starts from by default. */
114
+ /** Organization entry point the IdC/Builder-ID flow starts from by default. */
105
115
  export const KIRO_DEFAULT_START_URL = "https://view.awsapps.com/start" as const;
106
116
 
107
117
  /** Microsoft login hosts an enterprise token endpoint may point at. */
@@ -125,7 +135,7 @@ export interface KiroAuthState {
125
135
  readonly profileArn?: string;
126
136
  /** Identity Center organization URL, when the account came from one. */
127
137
  readonly startUrl?: string;
128
- /** OIDC client id from the device-flow registration. */
138
+ /** OIDC client id from the IdC/Builder-ID client registration. */
129
139
  readonly clientId?: string;
130
140
  /** Enterprise identity-provider token endpoint. */
131
141
  readonly tokenEndpoint?: string;
@@ -266,90 +276,111 @@ export function kiroAccountLabel(accessToken: string | undefined): string | unde
266
276
  return undefined;
267
277
  }
268
278
 
269
- /** One AWS SSO OIDC token response, in the casing that endpoint uses. */
270
- interface OidcTokenResponse {
271
- readonly accessToken?: unknown;
272
- readonly refreshToken?: unknown;
273
- readonly expiresIn?: unknown;
274
- readonly profileArn?: unknown;
275
- readonly error?: unknown;
276
- readonly error_description?: unknown;
277
- readonly interval?: unknown;
279
+ /**
280
+ * Falls back to an informative per-family label when the token carries no
281
+ * readable identity (the social access token is opaque, not a JWT). The
282
+ * console otherwise labels every Kiro account "kiro".
283
+ */
284
+ export function kiroDefaultLabel(authMethod: KiroAuthMethod): string {
285
+ switch (authMethod) {
286
+ case "google":
287
+ return "Google account";
288
+ case "github":
289
+ return "GitHub account";
290
+ case "builder-id":
291
+ return "AWS Builder ID";
292
+ case "idc":
293
+ return "IAM Identity Center";
294
+ case "external_idp":
295
+ return "Enterprise IdP";
296
+ case "imported":
297
+ return "Imported Kiro session";
298
+ case "api_key":
299
+ return "Kiro API key";
300
+ }
278
301
  }
279
302
 
280
- /** Credentials the device-flow registration minted and refresh must replay. */
303
+ /**
304
+ * Every stock fallback label `kiroDefaultLabel` can produce. The quota refresh
305
+ * sweep may adopt an upstream-reported identity (an email from `GetUsageLimits`)
306
+ * over any label in this set — those are generated defaults, never an operator's
307
+ * rename — but leaves anything else alone.
308
+ */
309
+ export const KIRO_DEFAULT_LABELS: ReadonlySet<string> = new Set(
310
+ (
311
+ ["google", "github", "builder-id", "idc", "external_idp", "imported", "api_key"] as const
312
+ ).map(kiroDefaultLabel),
313
+ );
314
+
315
+ /** Credentials the client registration minted and refresh must replay. */
281
316
  interface DeviceFlowRegistration {
282
317
  readonly clientId: string;
283
318
  readonly clientSecret: string;
284
319
  readonly region: string;
285
320
  readonly startUrl: string;
286
321
  readonly authMethod: "builder-id" | "idc";
287
- }
288
-
289
- function parseRegistration(value: unknown): DeviceFlowRegistration | undefined {
290
- const raw = record(value);
291
- if (raw === undefined) return undefined;
292
- const clientId = nonEmptyString(raw["clientId"]);
293
- const clientSecret = nonEmptyString(raw["clientSecret"]);
294
- const region = normalizeRegion(raw["region"]);
295
- const startUrl = nonEmptyTrimmedString(raw["startUrl"]) ?? KIRO_DEFAULT_START_URL;
296
- const authMethod = raw["authMethod"] === "idc" ? "idc" : "builder-id";
297
- if (clientId === undefined || clientSecret === undefined) return undefined;
298
- return { clientId, clientSecret, region, startUrl, authMethod };
322
+ /**
323
+ * Redirect URI sent on authorize + CreateToken. The registration body itself
324
+ * always declares {@link KIRO_LOOPBACK_REDIRECT_URI} (no port); AWS SSO OIDC
325
+ * accepts a loopback authorize URI that adds a port (RFC 8252).
326
+ */
327
+ readonly redirectUri: string;
299
328
  }
300
329
 
301
330
  /**
302
331
  * Kiro's authentication client.
303
332
  *
304
- * Extends the device-flow base for the AWS families and implements the social
305
- * code exchange, refresh-token import, enterprise import and API-key validation
306
- * directly, because those are not device flows and forcing them into one would
307
- * misdescribe what the operator is being asked to do.
333
+ * The 1.4.62 IDE migrated every AWS family to the OAuth 2.0 authorization-code
334
+ * flow with PKCE: it registers a public client against AWS SSO OIDC, opens a
335
+ * loopback listener on `http://127.0.0.1/oauth/callback`, and exchanges the code
336
+ * with `CreateToken`. The social families (Google/GitHub) use the vendor's own
337
+ * sign-in portal with a loopback redirect. The device-code flow (RFC 8628) is
338
+ * offered against the same AWS SSO OIDC service for Builder-ID / Identity
339
+ * Center. The import, enterprise, and API-key paths are unchanged.
308
340
  */
309
341
  export class KiroOAuthClient extends OAuthDeviceFlow {
310
342
  override readonly supportsDeviceCode = true;
343
+ override readonly supportsBrowserCode = true;
344
+
311
345
  /**
312
- * The vendor's own identity provider, reached with a custom-scheme redirect.
346
+ * Loopback callback the portal and the IdC flow land on.
313
347
  *
314
- * Declared `true` because this client genuinely implements both halves of a
315
- * browser code flow: it builds the authorize URL (selecting Google or GitHub)
316
- * and it exchanges the pasted code. Saying `false` hid a working sign-in path
317
- * from the console, which is the failure mode the flag exists to prevent in
318
- * the opposite direction — the flag suppresses a flow only when the client
319
- * cannot complete one.
348
+ * Host must be `127.0.0.1`, not `localhost`: OIDC client registration uses
349
+ * the portless `http://127.0.0.1/oauth/callback`, and AWS SSO OIDC allows
350
+ * RFC 8252 port variation on the *same* host only — swapping in `localhost`
351
+ * at authorize yields `Invalid redirectUri provided`. The 1.4.62 IDE binds
352
+ * and authorizes on `127.0.0.1:<port>/oauth/callback` the same way. The
353
+ * portal gets the origin only (`http://127.0.0.1:3128`); the callback lands
354
+ * at `/oauth/callback?login_option=…` or `/signin/callback?…`.
320
355
  */
321
- override readonly supportsBrowserCode = true;
356
+ override readonly browserRedirectUri = "http://127.0.0.1:3128/oauth/callback";
322
357
 
323
358
  protected override readonly providerLabel = "kiro";
324
359
  protected override readonly clientId = "";
325
360
  protected override readonly tokenUrl = `${KIRO_AUTH_SERVICE}/refreshToken`;
326
361
  protected override readonly scopes = "";
362
+
363
+ /** IdC/Builder-ID registrations keyed by flow state, for the code exchange. */
364
+ readonly #registrationByState = new Map<string, DeviceFlowRegistration>();
365
+
327
366
  /**
328
- * The authorize URL carries the identity provider, not a client id, so the
329
- * operator chooses which one to start with. The custom-scheme redirect below
330
- * is handed to the installed app rather than delivered to a loopback
331
- * listener, so the console keeps its manual-paste path for it.
367
+ * Fields the browser flow needs before it can start.
368
+ *
369
+ * None: the vendor's sign-in portal (`app.kiro.dev/signin`) is the single
370
+ * entry point for every family — Google, GitHub, AWS Builder ID, "Your
371
+ * organization" (IdC), and enterprise — and the callback's `login_option`
372
+ * names which one approved. Asking for a family/region/start URL up front
373
+ * both duplicated the portal's own choice and forced non-social families
374
+ * onto a different (OIDC-direct) path the 1.4.62 client does not use.
332
375
  */
333
- readonly browserLoginFields: readonly OAuthLoginField[] = [
334
- {
335
- key: "idp",
336
- label: "Identity provider",
337
- required: true,
338
- defaultValue: "google",
339
- options: [
340
- { value: "google", label: "Google" },
341
- { value: "github", label: "GitHub" },
342
- ],
343
- },
344
- ];
376
+ readonly browserLoginFields: readonly OAuthLoginField[] = [];
345
377
 
346
378
  /**
347
- * Fields the device flow needs before it can start.
379
+ * Fields the device-flow (RFC 8628) needs before it can start.
348
380
  *
349
- * The flow registers a client against one AWS SSO OIDC region and one
350
- * organization entry point, and neither can be inferred: the region decides
351
- * every URL the flow touches, and the start URL decides which portal the
352
- * operator approves in.
381
+ * The device flow targets the AWS SSO OIDC service (Builder-ID / Identity
382
+ * Center), so it asks the same region/organization questions the browser
383
+ * flow does and defaults to the same families.
353
384
  */
354
385
  readonly deviceLoginFields: readonly OAuthLoginField[] = [
355
386
  {
@@ -405,181 +436,337 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
405
436
  ];
406
437
 
407
438
  /**
408
- * Registers a client and starts device authorization.
439
+ * Registers an OIDC client for the IdC/Builder-ID flow.
440
+ *
441
+ * The 1.4.62 IDE registers a public client per region/issuer, then opens a
442
+ * browser authorization-code flow with PKCE. The registration is retained for
443
+ * the code exchange and later refreshes via the flow `state`, which both the
444
+ * authorize URL and the callback carry.
445
+ */
446
+ /**
447
+ * Starts the AWS SSO OIDC device authorization.
409
448
  *
410
- * AWS SSO OIDC requires the registration first: the device authorization is
411
- * issued to a registered client, and the resulting client secret is needed
412
- * again at every later refresh. The registration is handed back to the console
413
- * as provider-private state rather than returned to the browser.
449
+ * The same client registration the browser flow uses is reused: register a
450
+ * public client for the chosen family/region, then ask the regional OIDC
451
+ * service for a device code. The registration is carried in `providerState`
452
+ * so `pollDeviceAuth` can replay it at the token endpoint.
414
453
  */
415
454
  override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
416
455
  const parameters = context?.parameters ?? {};
456
+ const authMethod = parameters["authMethod"] === "idc" ? "idc" : "builder-id";
417
457
  const region = normalizeRegion(parameters["region"]);
418
458
  const startUrl = nonEmptyTrimmedString(parameters["startUrl"]) ?? KIRO_DEFAULT_START_URL;
419
- const authMethod = parameters["authMethod"] === "idc" ? "idc" : "builder-id";
420
459
  assertAwsRegion(region);
460
+ const registration = await this.#registerClient(region, startUrl, authMethod);
421
461
 
422
- const registration = await this.#registerClient(region);
423
- const response = await this.fetchFn(`https://oidc.${region}.amazonaws.com/device_authorization`, {
424
- method: "POST",
425
- headers: ssoOidcHeaders(),
426
- body: JSON.stringify({
427
- clientId: registration.clientId,
428
- clientSecret: registration.clientSecret,
429
- startUrl,
430
- }),
431
- });
432
- const payload = await readJsonResponse(response, "kiro device authorization");
433
- const raw = record(payload) ?? {};
434
- const deviceCode = nonEmptyString(raw["deviceCode"]);
435
- const userCode = nonEmptyString(raw["userCode"]);
436
- const verificationUri =
437
- nonEmptyString(raw["verificationUriComplete"]) ?? nonEmptyString(raw["verificationUri"]);
438
- if (deviceCode === undefined || userCode === undefined || verificationUri === undefined) {
439
- throw new Error("Kiro device authorization response omitted required fields");
462
+ const response = await this.fetchFn(
463
+ `https://oidc.${region}.amazonaws.com/device_authorization`,
464
+ {
465
+ method: "POST",
466
+ headers: ssoOidcHeaders(),
467
+ body: JSON.stringify({
468
+ clientId: registration.clientId,
469
+ clientSecret: registration.clientSecret,
470
+ startUrl,
471
+ }),
472
+ signal: AbortSignal.timeout(15_000),
473
+ },
474
+ );
475
+ const payload = record(await readJsonResponse(response, "kiro device authorization")) ?? {};
476
+ const start = parseDeviceAuthStart(payload, { intervalSeconds: 5, expiresInSeconds: 600 });
477
+ if (start === undefined) {
478
+ throw new Error("Kiro device authorization returned an invalid response");
440
479
  }
441
- const interval = Number(raw["interval"]);
442
- const expiresIn = Number(raw["expiresIn"]);
443
480
  return {
444
- verificationUri,
445
- userCode,
446
- deviceAuthId: deviceCode,
447
- intervalSeconds: Number.isFinite(interval) && interval > 0 ? Math.floor(interval) : 5,
448
- expiresInSeconds: Number.isFinite(expiresIn) && expiresIn > 0 ? Math.floor(expiresIn) : 900,
449
- // The console persists this and never returns it to the browser: it holds
450
- // the client secret the refresh will need.
451
- providerState: JSON.stringify({
452
- clientId: registration.clientId,
453
- clientSecret: registration.clientSecret,
454
- region,
455
- startUrl,
456
- authMethod,
457
- }),
481
+ verificationUri: start.verificationUriComplete ?? start.verificationUri,
482
+ userCode: start.userCode,
483
+ deviceAuthId: start.deviceCode,
484
+ intervalSeconds: start.intervalSeconds,
485
+ expiresInSeconds: start.expiresInSeconds,
486
+ providerState: JSON.stringify(registration),
458
487
  };
459
488
  }
460
489
 
461
- /** Polls the AWS SSO OIDC token endpoint for the device flow. */
490
+ /** Polls the AWS SSO OIDC token endpoint for the device grant result. */
462
491
  override async pollDeviceAuth(
463
492
  deviceAuthId: string,
464
493
  context?: OAuthDeviceFlowContext,
465
494
  ): Promise<OAuthDevicePollResult> {
466
- const registration = parseRegistration(
467
- context?.providerState === undefined ? undefined : safeJson(context.providerState),
468
- );
469
- if (registration === undefined) {
470
- return { status: "failed", reason: "Kiro device flow is missing its client registration" };
495
+ let registration: DeviceFlowRegistration;
496
+ try {
497
+ registration = JSON.parse(context?.providerState ?? "") as DeviceFlowRegistration;
498
+ } catch {
499
+ return { status: "failed", reason: "unknown device authorization" };
471
500
  }
472
- const response = await this.fetchFn(`https://oidc.${registration.region}.amazonaws.com/token`, {
473
- method: "POST",
474
- headers: ssoOidcHeaders(),
475
- body: JSON.stringify({
476
- clientId: registration.clientId,
477
- clientSecret: registration.clientSecret,
478
- deviceCode: deviceAuthId,
479
- grantType: "urn:ietf:params:oauth:grant-type:device_code",
480
- }),
481
- });
482
- // The pending verdicts arrive as a non-2xx body, so the payload is read
483
- // without the helper that throws on a non-ok status — otherwise
484
- // `authorization_pending` would be lost and the flow reported as failed,
485
- // stopping the dashboard's poll while the operator is still authorizing.
486
- const payload = (await readJsonBody(response)) as OidcTokenResponse;
487
- const error = nonEmptyString(payload.error);
488
- // AWS reports its pending states in snake_case, matching RFC 8628.
489
- const verdict = devicePollBackoff(error, payload.interval);
490
- if (verdict !== undefined) return verdict;
491
- if (!response.ok) {
492
- return {
493
- status: "failed",
494
- reason: nonEmptyString(payload.error_description) ?? error ?? `token polling failed (${response.status})`,
495
- };
501
+ if (!registration.clientId || !registration.clientSecret || !registration.region) {
502
+ return { status: "failed", reason: "unknown device authorization" };
496
503
  }
497
- const access = nonEmptyString(payload.accessToken);
498
- if (access === undefined) {
504
+ const region = assertAwsRegion(registration.region);
505
+ let response: Response;
506
+ try {
507
+ response = await this.fetchFn(`https://oidc.${region}.amazonaws.com/token`, {
508
+ method: "POST",
509
+ headers: ssoOidcHeaders(),
510
+ body: JSON.stringify({
511
+ clientId: registration.clientId,
512
+ clientSecret: registration.clientSecret,
513
+ deviceCode: deviceAuthId,
514
+ grantType: "urn:ietf:params:oauth:grant-type:device_code",
515
+ }),
516
+ signal: AbortSignal.timeout(15_000),
517
+ });
518
+ } catch (error) {
519
+ return { status: "failed", reason: error instanceof Error ? error.message : "Kiro device polling failed" };
520
+ }
521
+ const payload = record(await response.json().catch(() => undefined)) ?? {};
522
+ if (response.status === 400 || response.status === 428) {
523
+ const err = typeof payload.error === "string" ? payload.error : "";
524
+ const verdict = devicePollBackoff(err, payload.interval);
525
+ if (verdict !== undefined) return verdict;
526
+ return { status: "failed", reason: err || `device polling failed (${response.status})` };
527
+ }
528
+ const access = nonEmptyString(payload["accessToken"]);
529
+ if (!response.ok || access === undefined) {
530
+ const detail = nonEmptyString(payload["error_description"]) ?? nonEmptyString(payload["error"]);
499
531
  return {
500
532
  status: "failed",
501
- reason: nonEmptyString(payload.error_description) ?? "Kiro token response omitted accessToken",
533
+ reason: detail ?? `Kiro device polling failed (${response.status})`,
502
534
  };
503
535
  }
504
- const refresh = nonEmptyString(payload.refreshToken) ?? "";
505
- // AWS SSO OIDC never reports the profile in its token response, and every
506
- // profile-scoped surface refuses a request without the field, so the account
507
- // resolves its own before the credential is persisted.
508
- const reported = nonEmptyString(payload.profileArn);
509
- const profileArn =
510
- reported ?? (await this.resolveProfileArn(access, registration.region, registration.authMethod));
536
+ const refresh = nonEmptyString(payload["refreshToken"]) ?? "";
537
+ const profileArn = nonEmptyString(payload["profileArn"]);
511
538
  const authState: KiroAuthState = {
512
539
  authMethod: registration.authMethod,
513
- region: registration.region,
540
+ region,
514
541
  ...(profileArn === undefined ? {} : { profileArn }),
515
542
  startUrl: registration.startUrl,
516
543
  clientId: registration.clientId,
517
544
  machineId: deriveOAuthMachineId(refresh),
518
545
  };
519
- const label = kiroAccountLabel(access);
520
- const result: OAuthExchangeResult = {
521
- access,
522
- refresh,
523
- expiresAt: this.#expiry(payload.expiresIn),
524
- ...(label === undefined ? {} : { accountLabel: label }),
525
- auth_state: authStateOf(authState),
526
- client_secret: registration.clientSecret,
546
+ let label = kiroAccountLabel(access) ?? kiroDefaultLabel(registration.authMethod);
547
+ // Same best-effort identity enrichment as the browser flows: the device
548
+ // token is opaque, so ask the usage surface for the account email.
549
+ if (label === kiroDefaultLabel(registration.authMethod)) {
550
+ try {
551
+ const quota = await fetchKiroQuota(access, this.fetchFn, { auth_state: authStateOf(authState) });
552
+ if (quota.accountLabel !== undefined && quota.accountLabel.trim().length > 0) {
553
+ label = quota.accountLabel;
554
+ }
555
+ } catch {
556
+ // keep the family fallback
557
+ }
558
+ }
559
+ return {
560
+ status: "complete",
561
+ result: {
562
+ access,
563
+ refresh,
564
+ expiresAt: this.#expiry(payload["expiresIn"]),
565
+ accountLabel: label,
566
+ auth_state: authStateOf(authState),
567
+ client_secret: registration.clientSecret,
568
+ },
527
569
  };
528
- return { status: "complete", result };
529
570
  }
530
571
 
531
- /**
532
- * No-op: the social flows need no preparation. The authorize URL carries the
533
- * identity provider rather than a client id and allowlists a custom-scheme
534
- * redirect, so the operator completes consent in a browser and pastes the
535
- * resulting URL back — there is no loopback listener to register.
536
- */
537
572
  async prepareAuthorize(request: OAuthAuthorizeRequest): Promise<OAuthAuthorizeRequest> {
573
+ // The portal is the single entry point for every family; it chooses the
574
+ // identity provider and reports it back in the callback's `login_option`.
575
+ // No registration happens here.
538
576
  return request;
539
577
  }
540
578
 
541
579
  override buildAuthorizeUrl(request: OAuthAuthorizeRequest): string {
580
+ // Every family goes through the portal. The 1.4.62 IDE has no per-family
581
+ // OIDC-direct URL: its `buildPortalUrl` sends `state`, `code_challenge`,
582
+ // `redirect_uri`, and `redirect_from` to `app.kiro.dev/signin`, and the
583
+ // portal picks the identity provider. The callback's `login_option` then
584
+ // tells the exchange which family approved.
542
585
  return this.#buildSocialAuthorizeUrl(request);
543
586
  }
544
587
 
545
588
  #buildSocialAuthorizeUrl(request: OAuthAuthorizeRequest): string {
546
- const idp = readAuthorizeIdp(request);
589
+ // The portal chooses the identity provider (Google/GitHub/OIDC) itself, so
590
+ // the authorize URL carries no `idp`. The redirect URI is the origin only —
591
+ // exactly what the bundle's `PortalAuthProvider.buildPortalUrl` sends; a
592
+ // path-carrying redirect is rejected by Cognito with `redirect_mismatch`.
547
593
  const params = new URLSearchParams({
548
- idp: idp === "github" ? "Github" : "Google",
549
- redirect_uri: KIRO_SOCIAL_REDIRECT_URI,
594
+ state: request.state,
550
595
  code_challenge: request.codeChallenge,
551
596
  code_challenge_method: "S256",
552
- state: request.state,
553
- prompt: "select_account",
597
+ redirect_uri: "",
598
+ redirect_from: "KiroIDE",
554
599
  });
555
- return `${KIRO_AUTH_SERVICE}/login?${params.toString()}`;
600
+ const redirectOrigin = new URL(this.browserRedirectUri);
601
+ params.set("redirect_uri", redirectOrigin.origin);
602
+ return `${KIRO_SOCIAL_PORTAL}/signin?${params.toString()}`;
556
603
  }
557
604
 
558
605
  /**
559
- * Exchanges the pasted social authorization code.
606
+ * Exchanges the authorization code the operator pastes back.
560
607
  *
561
- * The vendor's endpoint takes a JSON body and compares `redirect_uri` against
562
- * the custom scheme the authorize step advertised.
608
+ * Social families exchange against the vendor's own sign-in service. The
609
+ * OIDC families (Builder-ID / IdC) use a two-phase flow mirrored from the
610
+ * 1.4.62 IDE: the portal's first callback carries no `code`, only
611
+ * `login_option` + `issuer_url` + `idc_region`; this exchange then registers
612
+ * an OIDC client, builds the real authorize URL, and starts the *second*
613
+ * browser leg by throwing {@link KiroOidcPendingError}. When that second
614
+ * callback arrives (with a `code`), `exchangeCode` runs again with the
615
+ * registration now on file and exchanges it via AWS SSO OIDC `CreateToken`.
563
616
  */
564
617
  override async exchangeCode(
565
618
  code: string,
566
619
  codeVerifier: string,
567
- _redirectUri: string,
568
- _state?: string,
569
- _context?: OAuthCodeExchangeContext,
620
+ redirectUri: string,
621
+ state?: string,
622
+ context?: OAuthCodeExchangeContext,
623
+ ): Promise<OAuthExchangeResult> {
624
+ // Phase 2 (or a resumed device/browser OIDC exchange): registration may be
625
+ // in the process Map or restored from the pending flow's providerState.
626
+ const registration =
627
+ (state === undefined ? undefined : this.#registrationByState.get(state)) ??
628
+ parseRegistration(context?.providerState);
629
+ if (registration !== undefined) {
630
+ if (state !== undefined) this.#registrationByState.delete(state);
631
+ return this.#exchangeIdcCode(code, codeVerifier, registration.redirectUri, registration);
632
+ }
633
+ const params = context?.parameters ?? {};
634
+ const loginOption = params["login_option"];
635
+ if (loginOption === "builderid" || loginOption === "awsidc") {
636
+ // Phase 1: the portal callback for an OIDC family. `code` is absent and
637
+ // the issuer/region tell us which regional OIDC service to use. Mirror the
638
+ // 1.4.62 IDE's IdC-provider.authenticate: register with the portless
639
+ // loopback URI, mint a *fresh* PKCE pair, authorize against the full
640
+ // `127.0.0.1:<port>/oauth/callback` (port allowed under RFC 8252; host
641
+ // must stay `127.0.0.1` — `localhost` is rejected as Invalid redirectUri),
642
+ // scopes comma-joined.
643
+ const issuerUrl = params["issuer_url"];
644
+ const idcRegion = params["idc_region"] ?? params["idcRegion"];
645
+ if (issuerUrl === undefined || idcRegion === undefined) {
646
+ throw new Error(`Kiro ${loginOption} callback missing issuer_url or idc_region`);
647
+ }
648
+ const region = normalizeRegion(idcRegion);
649
+ assertAwsRegion(region);
650
+ const authMethod = loginOption === "awsidc" ? "idc" : "builder-id";
651
+ const registered = await this.#registerClient(region, issuerUrl, authMethod);
652
+ const authorizeRedirect = oidcAuthorizeRedirectUri(redirectUri);
653
+ const phase2: DeviceFlowRegistration = { ...registered, redirectUri: authorizeRedirect };
654
+ const pkce = generatePkcePair();
655
+ // The second browser leg uses a *fresh* state, mirroring the 1.4.62
656
+ // IDE (IdCAuthProvider mints a new UUID for the IdC authorize step). The
657
+ // code exchange keys off that new state, not the portal's.
658
+ const continueState = generateOAuthState();
659
+ this.#registrationByState.set(continueState, phase2);
660
+ const authorizeParams = new URLSearchParams({
661
+ response_type: "code",
662
+ client_id: phase2.clientId,
663
+ redirect_uri: authorizeRedirect,
664
+ scopes: KIRO_SCOPES.join(","),
665
+ state: continueState,
666
+ code_challenge: pkce.codeChallenge,
667
+ code_challenge_method: "S256",
668
+ });
669
+ throw new KiroOidcPendingError(
670
+ `https://oidc.${region}.amazonaws.com/authorize?${authorizeParams.toString()}`,
671
+ pkce.codeVerifier,
672
+ JSON.stringify(phase2),
673
+ continueState,
674
+ );
675
+ }
676
+ if (loginOption === "external_idp") {
677
+ // Enterprise IdP is only reachable by import; the portal cannot complete
678
+ // it for us because the refresh needs the operator's pasted JSON.
679
+ throw new Error("Kiro enterprise sign-in must be imported — use Import credential");
680
+ }
681
+ return this.exchangeSocialCode(code, codeVerifier, redirectUri, loginOption);
682
+ }
683
+
684
+ /** Exchanges an IdC/Builder-ID authorization code via AWS SSO OIDC CreateToken. */
685
+ async #exchangeIdcCode(
686
+ code: string,
687
+ codeVerifier: string,
688
+ redirectUri: string,
689
+ registration: DeviceFlowRegistration,
570
690
  ): Promise<OAuthExchangeResult> {
571
- return this.exchangeSocialCode(code, codeVerifier);
691
+ const response = await this.fetchFn(`https://oidc.${registration.region}.amazonaws.com/token`, {
692
+ method: "POST",
693
+ headers: ssoOidcHeaders(),
694
+ body: JSON.stringify({
695
+ clientId: registration.clientId,
696
+ clientSecret: registration.clientSecret,
697
+ grantType: "authorization_code",
698
+ code,
699
+ redirectUri,
700
+ codeVerifier,
701
+ }),
702
+ });
703
+ const payload = record(await readJsonResponse(response, "kiro IdC token exchange")) ?? {};
704
+ const access = nonEmptyString(payload["accessToken"]);
705
+ if (access === undefined) {
706
+ const detail = nonEmptyString(payload["error_description"]) ?? nonEmptyString(payload["error"]);
707
+ throw new Error(`Kiro IdC token exchange omitted accessToken${detail === undefined ? "" : `: ${detail}`}`);
708
+ }
709
+ const refresh = nonEmptyString(payload["refreshToken"]) ?? "";
710
+ const reported = nonEmptyString(payload["profileArn"]);
711
+ const profileArn =
712
+ reported ?? (await this.resolveProfileArn(access, registration.region, registration.authMethod));
713
+ const authState: KiroAuthState = {
714
+ authMethod: registration.authMethod,
715
+ region: registration.region,
716
+ ...(profileArn === undefined ? {} : { profileArn }),
717
+ startUrl: registration.startUrl,
718
+ clientId: registration.clientId,
719
+ machineId: deriveOAuthMachineId(refresh),
720
+ };
721
+ let label = kiroAccountLabel(access) ?? kiroDefaultLabel(registration.authMethod);
722
+ // Builder-ID / IdC access tokens are opaque (not JWTs), so the JWT
723
+ // extraction above misses the identity; ask the usage surface for the
724
+ // account email instead — the same best-effort enrichment social uses.
725
+ if (label === kiroDefaultLabel(registration.authMethod)) {
726
+ try {
727
+ const quota = await fetchKiroQuota(access, this.fetchFn, { auth_state: authStateOf(authState) });
728
+ if (quota.accountLabel !== undefined && quota.accountLabel.trim().length > 0) {
729
+ label = quota.accountLabel;
730
+ }
731
+ } catch {
732
+ // keep the family fallback
733
+ }
734
+ }
735
+ return {
736
+ access,
737
+ refresh,
738
+ expiresAt: this.#expiry(payload["expiresIn"]),
739
+ accountLabel: label,
740
+ auth_state: authStateOf(authState),
741
+ client_secret: registration.clientSecret,
742
+ };
572
743
  }
573
744
 
574
745
  /** Exchanges a social authorization code for tokens. */
575
- async exchangeSocialCode(code: string, codeVerifier: string): Promise<OAuthExchangeResult> {
746
+ async exchangeSocialCode(
747
+ code: string,
748
+ codeVerifier: string,
749
+ redirectUri?: string,
750
+ loginOption?: string,
751
+ ): Promise<OAuthExchangeResult> {
752
+ // The portal callback lands as `?login_option=google&code=…`. The exchange
753
+ // must replay the exact redirect URI the portal saw — the callback origin +
754
+ // path + `?login_option=…` — otherwise the vendor service rejects it (the
755
+ // bundle rebuilds it the same way). We do not have the full callback URL at
756
+ // exchange time, so the caller passes the pieces: `redirectUri` is the
757
+ // loopback callback URL the flow registered and `loginOption` is the query
758
+ // value. Default to the original scheme when the pieces are absent.
759
+ const redirectUriFinal =
760
+ redirectUri !== undefined && loginOption !== undefined
761
+ ? `${redirectUri}?login_option=${loginOption}`
762
+ : redirectUri ?? KIRO_SOCIAL_REDIRECT_URI;
576
763
  const response = await this.fetchFn(`${KIRO_AUTH_SERVICE}/oauth/token`, {
577
764
  method: "POST",
578
765
  headers: socialServiceHeaders(),
579
766
  body: JSON.stringify({
580
767
  code,
581
768
  code_verifier: codeVerifier,
582
- redirect_uri: KIRO_SOCIAL_REDIRECT_URI,
769
+ redirect_uri: redirectUriFinal,
583
770
  }),
584
771
  });
585
772
  const payload = await readJsonResponse(response, "kiro social token exchange");
@@ -591,18 +778,32 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
591
778
  }
592
779
  const refresh = nonEmptyString(raw["refreshToken"]) ?? "";
593
780
  const profileArn = nonEmptyString(raw["profileArn"]);
781
+ const authMethod: KiroAuthMethod = loginOption === "github" ? "github" : "google";
594
782
  const authState: KiroAuthState = {
595
- authMethod: "google",
783
+ authMethod,
596
784
  region: DEFAULT_REGION,
597
785
  ...(profileArn === undefined ? {} : { profileArn }),
598
786
  machineId: deriveOAuthMachineId(refresh),
599
787
  };
600
- const label = kiroAccountLabel(access);
788
+ let label = kiroAccountLabel(access) ?? kiroDefaultLabel(authMethod);
789
+ // Social access tokens are opaque (not JWTs), so the JWT extraction above
790
+ // misses them; ask the usage surface for the account's email instead. The
791
+ // call is best-effort and guarded — a failure keeps the family fallback.
792
+ if (label === kiroDefaultLabel(authMethod)) {
793
+ try {
794
+ const quota = await fetchKiroQuota(access, this.fetchFn, { auth_state: authStateOf(authState) });
795
+ if (quota.accountLabel !== undefined && quota.accountLabel.trim().length > 0) {
796
+ label = quota.accountLabel;
797
+ }
798
+ } catch {
799
+ // keep the family fallback
800
+ }
801
+ }
601
802
  return {
602
803
  access,
603
804
  refresh,
604
805
  expiresAt: this.#expiry(raw["expiresIn"]),
605
- ...(label === undefined ? {} : { accountLabel: label }),
806
+ accountLabel: label,
606
807
  auth_state: authStateOf(authState),
607
808
  };
608
809
  }
@@ -611,7 +812,7 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
611
812
  * Completes a login from pasted credential material.
612
813
  *
613
814
  * One entry point for the four families that cannot be reached by a redirect
614
- * or a device code, because the operator already holds the material:
815
+ * or a browser flow, because the operator already holds the material:
615
816
  *
616
817
  * * a Kiro social refresh token;
617
818
  * * an Identity Center refresh token, which needs the client registration
@@ -695,7 +896,20 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
695
896
  ...(profileArn === undefined ? {} : { profileArn }),
696
897
  machineId: deriveOAuthMachineId(refreshed.refresh),
697
898
  };
698
- const label = kiroAccountLabel(refreshed.access);
899
+ let label = kiroAccountLabel(refreshed.access) ?? kiroDefaultLabel(isIdc ? "idc" : "imported");
900
+ // An imported refresh token's access token is opaque, so extract the identity
901
+ // from the usage surface instead — the same call the quota sweep makes, and
902
+ // the same best-effort guard.
903
+ if (label === kiroDefaultLabel(isIdc ? "idc" : "imported")) {
904
+ try {
905
+ const quota = await fetchKiroQuota(refreshed.access, this.fetchFn, { auth_state: authStateOf(authState) });
906
+ if (quota.accountLabel !== undefined && quota.accountLabel.trim().length > 0) {
907
+ label = quota.accountLabel;
908
+ }
909
+ } catch {
910
+ // keep the family fallback
911
+ }
912
+ }
699
913
  return {
700
914
  access: refreshed.access,
701
915
  refresh: refreshed.refresh,
@@ -748,12 +962,12 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
748
962
  machineId: deriveOAuthMachineId(refresh),
749
963
  };
750
964
  const expiresAt = readExpiry(raw) ?? this.#expiry(undefined);
751
- const label = kiroAccountLabel(access);
965
+ const label = kiroAccountLabel(access) ?? kiroDefaultLabel("external_idp");
752
966
  return {
753
967
  access,
754
968
  refresh,
755
969
  expiresAt,
756
- ...(label === undefined ? {} : { accountLabel: label }),
970
+ accountLabel: label,
757
971
  auth_state: authStateOf(authState),
758
972
  };
759
973
  }
@@ -771,24 +985,22 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
771
985
  const safeRegion = assertAwsRegion(normalizeRegion(region));
772
986
  const version = getKiroVersion();
773
987
  const machineId = deriveApiKeyMachineId(trimmed);
774
- const response = await this.fetchFn(
775
- `https://q.${safeRegion}.amazonaws.com/ListAvailableModels?origin=AI_EDITOR`,
776
- {
777
- headers: {
778
- authorization: `Bearer ${trimmed}`,
779
- tokentype: "API_KEY",
780
- accept: "application/json",
781
- connection: "close",
782
- "amz-sdk-request": "attempt=1; max=1",
783
- "amz-sdk-invocation-id": crypto.randomUUID(),
784
- "x-amzn-kiro-agent-mode": "vibe",
785
- "x-amzn-codewhisperer-optout": "true",
786
- "user-agent": buildKiroUserAgent(version, machineId),
787
- "x-amz-user-agent": buildKiroAmzUserAgent(version, machineId),
788
- },
789
- signal: AbortSignal.timeout(15_000),
988
+ const host = KIRO_MANAGEMENT_HOST_TEMPLATE.replace("{region}", safeRegion);
989
+ const response = await this.fetchFn(`${host}/List-Available-Models?origin=AI_EDITOR`, {
990
+ headers: {
991
+ authorization: `Bearer ${trimmed}`,
992
+ TokenType: "API_KEY",
993
+ accept: "application/json",
994
+ connection: "close",
995
+ "x-amz-target": "KiroControlPlaneBearerService.ListAvailableModels",
996
+ "amz-sdk-request": "attempt=1; max=1",
997
+ "amz-sdk-invocation-id": crypto.randomUUID(),
998
+ "user-agent": buildKiroUserAgent(version, machineId),
999
+ "x-amz-user-agent": buildKiroAmzUserAgent(version, machineId),
1000
+ "x-kiro-machineid": machineId,
790
1001
  },
791
- );
1002
+ signal: AbortSignal.timeout(15_000),
1003
+ });
792
1004
  if (!response.ok) throw new Error(`API key validation failed (${response.status})`);
793
1005
  const payload = record(await response.json().catch(() => undefined));
794
1006
  const models = payload?.["models"];
@@ -816,10 +1028,8 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
816
1028
  * Profile-scoped surfaces refuse a request that omits the field (`400
817
1029
  * profileArn is required for this request.`), and an Identity Center account
818
1030
  * whose token response carried no profile needs its own resolved before it is
819
- * persisted. `ListAvailableProfiles` is that lookup: an awsJson operation on
820
- * the CodeWhisperer service entry, reached by POST with an `x-amz-target`
821
- * header. The generation host carries no equivalent, and an unqualified
822
- * request to it is answered `UnknownOperationException`.
1031
+ * persisted. `GetProfile` on the Kiro control-plane service is that lookup: a
1032
+ * POST with an `x-amz-target` header on `https://management.{region}.kiro.dev`.
823
1033
  *
824
1034
  * A Builder ID account is skipped rather than asked: the operation refuses
825
1035
  * that family outright (`403 AWS Builder ID is not supported for this
@@ -839,36 +1049,39 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
839
1049
  if (authMethod === "builder-id") return undefined;
840
1050
  const safeRegion = assertAwsRegion(normalizeRegion(region));
841
1051
  try {
842
- const response = await this.fetchFn(`https://codewhisperer.${safeRegion}.amazonaws.com/`, {
1052
+ const host = KIRO_MANAGEMENT_HOST_TEMPLATE.replace("{region}", safeRegion);
1053
+ const response = await this.fetchFn(`${host}/Get-Profile`, {
843
1054
  method: "POST",
844
1055
  headers: {
845
1056
  authorization: `Bearer ${accessToken}`,
846
1057
  "content-type": "application/x-amz-json-1.0",
847
- "x-amz-target": "AmazonCodeWhispererService.ListAvailableProfiles",
1058
+ "x-amz-target": "KiroControlPlaneBearerService.GetProfile",
848
1059
  accept: "application/json",
849
1060
  connection: "close",
850
1061
  "amz-sdk-request": "attempt=1; max=1",
851
1062
  "amz-sdk-invocation-id": crypto.randomUUID(),
852
1063
  },
853
- body: JSON.stringify({ maxResults: 10 }),
1064
+ body: JSON.stringify({}),
854
1065
  signal: AbortSignal.timeout(15_000),
855
1066
  });
856
1067
  if (!response.ok) return undefined;
857
1068
  const payload = record(await response.json().catch(() => undefined));
1069
+ const profile = record(payload?.["profile"]);
1070
+ const arn = nonEmptyTrimmedString(profile?.["arn"] ?? profile?.["profileArn"]);
1071
+ if (arn !== undefined) return arn;
858
1072
  const profiles = payload?.["profiles"];
859
- if (!Array.isArray(profiles)) return undefined;
860
- // Prefer the profile minted in the region the account authenticates
861
- // against, and keep the first usable ARN as the fallback so an account
862
- // with several profiles still resolves to one of its own.
863
- let fallback: string | undefined;
864
- for (const entry of profiles) {
865
- const profile = record(entry);
866
- const arn = nonEmptyTrimmedString(profile?.["arn"] ?? profile?.["profileArn"]);
867
- if (arn === undefined) continue;
868
- if (arn.includes(`:${safeRegion}:`)) return arn;
869
- fallback ??= arn;
1073
+ if (Array.isArray(profiles)) {
1074
+ let fallback: string | undefined;
1075
+ for (const entry of profiles) {
1076
+ const p = record(entry);
1077
+ const value = nonEmptyTrimmedString(p?.["arn"] ?? p?.["profileArn"]);
1078
+ if (value === undefined) continue;
1079
+ if (value.includes(`:${safeRegion}:`)) return value;
1080
+ fallback ??= value;
1081
+ }
1082
+ return fallback;
870
1083
  }
871
- return fallback;
1084
+ return undefined;
872
1085
  } catch {
873
1086
  return undefined;
874
1087
  }
@@ -923,7 +1136,7 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
923
1136
  const clientId = authState.clientId;
924
1137
  const clientSecret = context?.client_secret;
925
1138
  if (clientId === undefined || clientSecret === undefined) {
926
- throw new Error("Kiro device-flow refresh is missing its client registration");
1139
+ throw new Error("Kiro IdC refresh is missing its client registration");
927
1140
  }
928
1141
  const refreshed = await this.#refreshOidc(
929
1142
  refreshToken,
@@ -966,19 +1179,23 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
966
1179
  }
967
1180
 
968
1181
  /**
969
- * Registers an OIDC client for the device flow.
1182
+ * Registers an OIDC client for the IdC/Builder-ID authorization-code flow.
970
1183
  *
971
1184
  * `clientName` and `User-Agent` identify this as the vendor's own IDE client.
972
1185
  * The registration is the first request a brand-new account ever makes, so it
973
1186
  * is the one most likely to be looked at: a client name no shipped build uses,
974
1187
  * or a missing User-Agent, is a shape the real client never produces.
975
1188
  *
976
- * `issuerUrl` is deliberately absent. The device flow starts from an AWS
977
- * Builder ID portal, which has no organization issuer; naming one — and
978
- * especially naming an Identity Center instance that belongs to someone else —
979
- * declares an organization the account is not part of.
1189
+ * The 1.4.62 IDE registers a public client with `authorization_code` grant and
1190
+ * the loopback redirect, and declares the issuer (the start URL) so the
1191
+ * authorization server knows which organization/portal this client belongs to.
980
1192
  */
981
- async #registerClient(region: string): Promise<{ readonly clientId: string; readonly clientSecret: string }> {
1193
+ async #registerClient(
1194
+ region: string,
1195
+ startUrl: string,
1196
+ authMethod: "builder-id" | "idc",
1197
+ redirectUri = KIRO_LOOPBACK_REDIRECT_URI,
1198
+ ): Promise<DeviceFlowRegistration> {
982
1199
  const response = await this.fetchFn(`https://oidc.${region}.amazonaws.com/client/register`, {
983
1200
  method: "POST",
984
1201
  headers: {
@@ -994,6 +1211,8 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
994
1211
  clientType: "public",
995
1212
  scopes: [...KIRO_SCOPES],
996
1213
  grantTypes: [...KIRO_GRANT_TYPES],
1214
+ redirectUris: [redirectUri],
1215
+ issuerUrl: startUrl,
997
1216
  }),
998
1217
  });
999
1218
  const payload = record(await readJsonResponse(response, "kiro client registration")) ?? {};
@@ -1002,13 +1221,13 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
1002
1221
  if (clientId === undefined || clientSecret === undefined) {
1003
1222
  throw new Error("Kiro client registration omitted credentials");
1004
1223
  }
1005
- return { clientId, clientSecret };
1224
+ return { clientId, clientSecret, region, startUrl, authMethod, redirectUri };
1006
1225
  }
1007
1226
 
1008
1227
  /** Refreshes through the regional AWS SSO OIDC token endpoint. */
1009
1228
  async #refreshOidc(
1010
1229
  refreshToken: string,
1011
- registration: DeviceFlowRegistration,
1230
+ registration: Omit<DeviceFlowRegistration, "redirectUri">,
1012
1231
  signal?: AbortSignal,
1013
1232
  ): Promise<{ readonly access: string; readonly refresh: string; readonly expiresAt: Date; readonly profileArn?: string }> {
1014
1233
  const region = assertAwsRegion(registration.region);
@@ -1071,6 +1290,81 @@ export class KiroOAuthClient extends OAuthDeviceFlow {
1071
1290
  }
1072
1291
  }
1073
1292
 
1293
+ /**
1294
+ * Signals that an OIDC portal callback needs a second browser leg: the portal
1295
+ * returns `login_option=builderid|awsidc` without a code, and the real
1296
+ * authorize URL for the regional AWS SSO OIDC service is attached so the route
1297
+ * can redirect the operator to it. Carries the fresh PKCE verifier and the
1298
+ * serialized registration so `completeLogin` can rewrite the pending flow
1299
+ * before sending the browser on — memory alone is not enough across gateways.
1300
+ */
1301
+ export class KiroOidcPendingError extends Error {
1302
+ readonly authorizeUrl: string;
1303
+ readonly codeVerifier: string;
1304
+ readonly providerState: string;
1305
+ readonly continueState: string;
1306
+ constructor(
1307
+ authorizeUrl: string,
1308
+ codeVerifier: string,
1309
+ providerState: string,
1310
+ continueState: string,
1311
+ ) {
1312
+ super(`Kiro OIDC requires a second authorization step: ${authorizeUrl}`);
1313
+ this.name = "KiroOidcPendingError";
1314
+ this.authorizeUrl = authorizeUrl;
1315
+ this.codeVerifier = codeVerifier;
1316
+ this.providerState = providerState;
1317
+ this.continueState = continueState;
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * Authorize/token redirect for AWS SSO OIDC after a portless registration.
1323
+ *
1324
+ * Registration always declares `http://127.0.0.1/oauth/callback`. RFC 8252 lets
1325
+ * the authorize step add a port on that same host; swapping the host to
1326
+ * `localhost` (or any other name) is answered `Invalid redirectUri provided`.
1327
+ * Port is taken from the console's loopback callback when present.
1328
+ */
1329
+ function oidcAuthorizeRedirectUri(loopbackCallback: string): string {
1330
+ let port = "3128";
1331
+ if (loopbackCallback.length > 0) {
1332
+ try {
1333
+ const parsed = new URL(loopbackCallback);
1334
+ if (parsed.port.length > 0) port = parsed.port;
1335
+ } catch {
1336
+ // keep the Kiro browser default port
1337
+ }
1338
+ }
1339
+ return `http://127.0.0.1:${port}/oauth/callback`;
1340
+ }
1341
+
1342
+ /** Restores a {@link DeviceFlowRegistration} from pending-flow providerState. */
1343
+ function parseRegistration(providerState: string | undefined): DeviceFlowRegistration | undefined {
1344
+ if (providerState === undefined || providerState.length === 0) return undefined;
1345
+ const parsed = safeJson(providerState);
1346
+ const root = record(parsed);
1347
+ if (root === undefined) return undefined;
1348
+ const clientId = nonEmptyString(root["clientId"]);
1349
+ const clientSecret = nonEmptyString(root["clientSecret"]);
1350
+ const region = nonEmptyString(root["region"]);
1351
+ const startUrl = nonEmptyString(root["startUrl"]);
1352
+ const authMethod = root["authMethod"] === "idc" || root["authMethod"] === "builder-id"
1353
+ ? root["authMethod"]
1354
+ : undefined;
1355
+ const redirectUri = nonEmptyString(root["redirectUri"]) ?? KIRO_LOOPBACK_REDIRECT_URI;
1356
+ if (
1357
+ clientId === undefined ||
1358
+ clientSecret === undefined ||
1359
+ region === undefined ||
1360
+ startUrl === undefined ||
1361
+ authMethod === undefined
1362
+ ) {
1363
+ return undefined;
1364
+ }
1365
+ return { clientId, clientSecret, region, startUrl, authMethod, redirectUri };
1366
+ }
1367
+
1074
1368
  /** Parses JSON without throwing. */
1075
1369
  function safeJson(value: string): unknown {
1076
1370
  try {
@@ -1080,21 +1374,6 @@ function safeJson(value: string): unknown {
1080
1374
  }
1081
1375
  }
1082
1376
 
1083
- /**
1084
- * Reads a JSON body regardless of status.
1085
- *
1086
- * The device-token endpoint reports "still waiting" and "slow down" as non-2xx
1087
- * bodies, so a helper that throws on a non-ok status would discard the verdict
1088
- * the poll needs. An unparseable body yields `{}` rather than throwing, because
1089
- * the caller decides what a missing field means.
1090
- */
1091
- async function readJsonBody(response: Response): Promise<Record<string, unknown>> {
1092
- const text = await response.text().catch(() => "");
1093
- if (text.trim().length === 0) return {};
1094
- const parsed = safeJson(text);
1095
- return record(parsed) ?? {};
1096
- }
1097
-
1098
1377
  /** Normalizes a scope list or space-delimited string. */
1099
1378
  function normalizeScope(value: unknown): string {
1100
1379
  if (Array.isArray(value)) {
@@ -1118,15 +1397,4 @@ function readExpiry(raw: Record<string, unknown>): Date | undefined {
1118
1397
  return undefined;
1119
1398
  }
1120
1399
 
1121
- /**
1122
- * Chooses the identity provider a social sign-in starts with.
1123
- *
1124
- * The vendor selects the provider by name rather than by client id, and the
1125
- * console passes the operator's choice through the start parameters.
1126
- */
1127
- function readAuthorizeIdp(request: OAuthAuthorizeRequest): "google" | "github" {
1128
- const idp = request.parameters?.["idp"] ?? request.parameters?.["provider"];
1129
- return idp === "github" ? "github" : "google";
1130
- }
1131
-
1132
1400
  export const kiroOAuthClient = new KiroOAuthClient();