codex-multi-auth 1.1.11 → 1.2.1

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 (695) hide show
  1. package/README.md +52 -20
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1023 -1661
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/accounts.d.ts +22 -2
  6. package/dist/lib/accounts.d.ts.map +1 -1
  7. package/dist/lib/accounts.js +84 -1
  8. package/dist/lib/accounts.js.map +1 -1
  9. package/dist/lib/auth/browser.d.ts +1 -0
  10. package/dist/lib/auth/browser.d.ts.map +1 -1
  11. package/dist/lib/auth/browser.js +13 -0
  12. package/dist/lib/auth/browser.js.map +1 -1
  13. package/dist/lib/auth/index.d.ts +2 -0
  14. package/dist/lib/auth/index.d.ts.map +1 -0
  15. package/dist/lib/auth/index.js +2 -0
  16. package/dist/lib/auth/index.js.map +1 -0
  17. package/dist/lib/auth/token-utils.d.ts +16 -0
  18. package/dist/lib/auth/token-utils.d.ts.map +1 -1
  19. package/dist/lib/auth/token-utils.js +13 -0
  20. package/dist/lib/auth/token-utils.js.map +1 -1
  21. package/dist/lib/codex-cli/index.d.ts +5 -0
  22. package/dist/lib/codex-cli/index.d.ts.map +1 -0
  23. package/dist/lib/codex-cli/index.js +5 -0
  24. package/dist/lib/codex-cli/index.js.map +1 -0
  25. package/dist/lib/codex-manager/auth-commands.d.ts +72 -0
  26. package/dist/lib/codex-manager/auth-commands.d.ts.map +1 -0
  27. package/dist/lib/codex-manager/auth-commands.js +643 -0
  28. package/dist/lib/codex-manager/auth-commands.js.map +1 -0
  29. package/dist/lib/codex-manager/backend-category-entry.d.ts +103 -0
  30. package/dist/lib/codex-manager/backend-category-entry.d.ts.map +1 -0
  31. package/dist/lib/codex-manager/backend-category-entry.js +23 -0
  32. package/dist/lib/codex-manager/backend-category-entry.js.map +1 -0
  33. package/dist/lib/codex-manager/backend-category-helpers.d.ts +10 -0
  34. package/dist/lib/codex-manager/backend-category-helpers.d.ts.map +1 -0
  35. package/dist/lib/codex-manager/backend-category-helpers.js +29 -0
  36. package/dist/lib/codex-manager/backend-category-helpers.js.map +1 -0
  37. package/dist/lib/codex-manager/backend-category-prompt.d.ts +55 -0
  38. package/dist/lib/codex-manager/backend-category-prompt.d.ts.map +1 -0
  39. package/dist/lib/codex-manager/backend-category-prompt.js +178 -0
  40. package/dist/lib/codex-manager/backend-category-prompt.js.map +1 -0
  41. package/dist/lib/codex-manager/backend-settings-controller.d.ts +11 -0
  42. package/dist/lib/codex-manager/backend-settings-controller.d.ts.map +1 -0
  43. package/dist/lib/codex-manager/backend-settings-controller.js +14 -0
  44. package/dist/lib/codex-manager/backend-settings-controller.js.map +1 -0
  45. package/dist/lib/codex-manager/backend-settings-entry.d.ts +20 -0
  46. package/dist/lib/codex-manager/backend-settings-entry.d.ts.map +1 -0
  47. package/dist/lib/codex-manager/backend-settings-entry.js +12 -0
  48. package/dist/lib/codex-manager/backend-settings-entry.js.map +1 -0
  49. package/dist/lib/codex-manager/backend-settings-helpers.d.ts +16 -0
  50. package/dist/lib/codex-manager/backend-settings-helpers.d.ts.map +1 -0
  51. package/dist/lib/codex-manager/backend-settings-helpers.js +102 -0
  52. package/dist/lib/codex-manager/backend-settings-helpers.js.map +1 -0
  53. package/dist/lib/codex-manager/backend-settings-prompt.d.ts +49 -0
  54. package/dist/lib/codex-manager/backend-settings-prompt.d.ts.map +1 -0
  55. package/dist/lib/codex-manager/backend-settings-prompt.js +121 -0
  56. package/dist/lib/codex-manager/backend-settings-prompt.js.map +1 -0
  57. package/dist/lib/codex-manager/backend-settings-schema.d.ts +55 -0
  58. package/dist/lib/codex-manager/backend-settings-schema.d.ts.map +1 -0
  59. package/dist/lib/codex-manager/backend-settings-schema.js +264 -0
  60. package/dist/lib/codex-manager/backend-settings-schema.js.map +1 -0
  61. package/dist/lib/codex-manager/behavior-settings-panel.d.ts +33 -0
  62. package/dist/lib/codex-manager/behavior-settings-panel.d.ts.map +1 -0
  63. package/dist/lib/codex-manager/behavior-settings-panel.js +180 -0
  64. package/dist/lib/codex-manager/behavior-settings-panel.js.map +1 -0
  65. package/dist/lib/codex-manager/best.d.ts +26 -0
  66. package/dist/lib/codex-manager/best.d.ts.map +1 -0
  67. package/dist/lib/codex-manager/best.js +249 -0
  68. package/dist/lib/codex-manager/best.js.map +1 -0
  69. package/dist/lib/codex-manager/commands/best.d.ts +81 -0
  70. package/dist/lib/codex-manager/commands/best.d.ts.map +1 -0
  71. package/dist/lib/codex-manager/commands/best.js +223 -0
  72. package/dist/lib/codex-manager/commands/best.js.map +1 -0
  73. package/dist/lib/codex-manager/commands/check.d.ts +7 -0
  74. package/dist/lib/codex-manager/commands/check.d.ts.map +1 -0
  75. package/dist/lib/codex-manager/commands/check.js +5 -0
  76. package/dist/lib/codex-manager/commands/check.js.map +1 -0
  77. package/dist/lib/codex-manager/commands/config-explain.d.ts +7 -0
  78. package/dist/lib/codex-manager/commands/config-explain.d.ts.map +1 -0
  79. package/dist/lib/codex-manager/commands/config-explain.js +31 -0
  80. package/dist/lib/codex-manager/commands/config-explain.js.map +1 -0
  81. package/dist/lib/codex-manager/commands/debug-bundle.d.ts +28 -0
  82. package/dist/lib/codex-manager/commands/debug-bundle.d.ts.map +1 -0
  83. package/dist/lib/codex-manager/commands/debug-bundle.js +62 -0
  84. package/dist/lib/codex-manager/commands/debug-bundle.js.map +1 -0
  85. package/dist/lib/codex-manager/commands/doctor.d.ts +85 -0
  86. package/dist/lib/codex-manager/commands/doctor.d.ts.map +1 -0
  87. package/dist/lib/codex-manager/commands/doctor.js +424 -0
  88. package/dist/lib/codex-manager/commands/doctor.js.map +1 -0
  89. package/dist/lib/codex-manager/commands/fix.d.ts +92 -0
  90. package/dist/lib/codex-manager/commands/fix.d.ts.map +1 -0
  91. package/dist/lib/codex-manager/commands/fix.js +347 -0
  92. package/dist/lib/codex-manager/commands/fix.js.map +1 -0
  93. package/dist/lib/codex-manager/commands/forecast.d.ts +70 -0
  94. package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -0
  95. package/dist/lib/codex-manager/commands/forecast.js +281 -0
  96. package/dist/lib/codex-manager/commands/forecast.js.map +1 -0
  97. package/dist/lib/codex-manager/commands/init-config.d.ts +15 -0
  98. package/dist/lib/codex-manager/commands/init-config.d.ts.map +1 -0
  99. package/dist/lib/codex-manager/commands/init-config.js +95 -0
  100. package/dist/lib/codex-manager/commands/init-config.js.map +1 -0
  101. package/dist/lib/codex-manager/commands/report.d.ts +24 -0
  102. package/dist/lib/codex-manager/commands/report.d.ts.map +1 -0
  103. package/dist/lib/codex-manager/commands/report.js +314 -0
  104. package/dist/lib/codex-manager/commands/report.js.map +1 -0
  105. package/dist/lib/codex-manager/commands/status.d.ts +23 -0
  106. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -0
  107. package/dist/lib/codex-manager/commands/status.js +50 -0
  108. package/dist/lib/codex-manager/commands/status.js.map +1 -0
  109. package/dist/lib/codex-manager/commands/switch.d.ts +22 -0
  110. package/dist/lib/codex-manager/commands/switch.d.ts.map +1 -0
  111. package/dist/lib/codex-manager/commands/switch.js +41 -0
  112. package/dist/lib/codex-manager/commands/switch.js.map +1 -0
  113. package/dist/lib/codex-manager/commands/verify-flagged.d.ts +69 -0
  114. package/dist/lib/codex-manager/commands/verify-flagged.d.ts.map +1 -0
  115. package/dist/lib/codex-manager/commands/verify-flagged.js +251 -0
  116. package/dist/lib/codex-manager/commands/verify-flagged.js.map +1 -0
  117. package/dist/lib/codex-manager/config-explain.d.ts +2 -0
  118. package/dist/lib/codex-manager/config-explain.d.ts.map +1 -0
  119. package/dist/lib/codex-manager/config-explain.js +62 -0
  120. package/dist/lib/codex-manager/config-explain.js.map +1 -0
  121. package/dist/lib/codex-manager/config-template.d.ts +2 -0
  122. package/dist/lib/codex-manager/config-template.d.ts.map +1 -0
  123. package/dist/lib/codex-manager/config-template.js +112 -0
  124. package/dist/lib/codex-manager/config-template.js.map +1 -0
  125. package/dist/lib/codex-manager/dashboard-display-panel.d.ts +40 -0
  126. package/dist/lib/codex-manager/dashboard-display-panel.d.ts.map +1 -0
  127. package/dist/lib/codex-manager/dashboard-display-panel.js +187 -0
  128. package/dist/lib/codex-manager/dashboard-display-panel.js.map +1 -0
  129. package/dist/lib/codex-manager/dashboard-formatters.d.ts +6 -0
  130. package/dist/lib/codex-manager/dashboard-formatters.d.ts.map +1 -0
  131. package/dist/lib/codex-manager/dashboard-formatters.js +19 -0
  132. package/dist/lib/codex-manager/dashboard-formatters.js.map +1 -0
  133. package/dist/lib/codex-manager/dashboard-settings-controller.d.ts +12 -0
  134. package/dist/lib/codex-manager/dashboard-settings-controller.d.ts.map +1 -0
  135. package/dist/lib/codex-manager/dashboard-settings-controller.js +17 -0
  136. package/dist/lib/codex-manager/dashboard-settings-controller.js.map +1 -0
  137. package/dist/lib/codex-manager/dashboard-settings-data.d.ts +10 -0
  138. package/dist/lib/codex-manager/dashboard-settings-data.d.ts.map +1 -0
  139. package/dist/lib/codex-manager/dashboard-settings-data.js +95 -0
  140. package/dist/lib/codex-manager/dashboard-settings-data.js.map +1 -0
  141. package/dist/lib/codex-manager/dashboard-settings-entry.d.ts +22 -0
  142. package/dist/lib/codex-manager/dashboard-settings-entry.d.ts.map +1 -0
  143. package/dist/lib/codex-manager/dashboard-settings-entry.js +13 -0
  144. package/dist/lib/codex-manager/dashboard-settings-entry.js.map +1 -0
  145. package/dist/lib/codex-manager/debug-bundle.d.ts +2 -0
  146. package/dist/lib/codex-manager/debug-bundle.d.ts.map +1 -0
  147. package/dist/lib/codex-manager/debug-bundle.js +128 -0
  148. package/dist/lib/codex-manager/debug-bundle.js.map +1 -0
  149. package/dist/lib/codex-manager/doctor.d.ts +20 -0
  150. package/dist/lib/codex-manager/doctor.d.ts.map +1 -0
  151. package/dist/lib/codex-manager/doctor.js +546 -0
  152. package/dist/lib/codex-manager/doctor.js.map +1 -0
  153. package/dist/lib/codex-manager/experimental-settings-entry.d.ts +7 -0
  154. package/dist/lib/codex-manager/experimental-settings-entry.d.ts.map +1 -0
  155. package/dist/lib/codex-manager/experimental-settings-entry.js +30 -0
  156. package/dist/lib/codex-manager/experimental-settings-entry.js.map +1 -0
  157. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +66 -0
  158. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts.map +1 -0
  159. package/dist/lib/codex-manager/experimental-settings-prompt.js +221 -0
  160. package/dist/lib/codex-manager/experimental-settings-prompt.js.map +1 -0
  161. package/dist/lib/codex-manager/experimental-settings-schema.d.ts +23 -0
  162. package/dist/lib/codex-manager/experimental-settings-schema.d.ts.map +1 -0
  163. package/dist/lib/codex-manager/experimental-settings-schema.js +34 -0
  164. package/dist/lib/codex-manager/experimental-settings-schema.js.map +1 -0
  165. package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts +30 -0
  166. package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts.map +1 -0
  167. package/dist/lib/codex-manager/experimental-sync-target-entry.js +16 -0
  168. package/dist/lib/codex-manager/experimental-sync-target-entry.js.map +1 -0
  169. package/dist/lib/codex-manager/experimental-sync-target.d.ts +23 -0
  170. package/dist/lib/codex-manager/experimental-sync-target.d.ts.map +1 -0
  171. package/dist/lib/codex-manager/experimental-sync-target.js +39 -0
  172. package/dist/lib/codex-manager/experimental-sync-target.js.map +1 -0
  173. package/dist/lib/codex-manager/fix.d.ts +26 -0
  174. package/dist/lib/codex-manager/fix.d.ts.map +1 -0
  175. package/dist/lib/codex-manager/fix.js +366 -0
  176. package/dist/lib/codex-manager/fix.js.map +1 -0
  177. package/dist/lib/codex-manager/forecast-report-commands.d.ts +54 -0
  178. package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +1 -0
  179. package/dist/lib/codex-manager/forecast-report-commands.js +443 -0
  180. package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -0
  181. package/dist/lib/codex-manager/forecast.d.ts +26 -0
  182. package/dist/lib/codex-manager/forecast.d.ts.map +1 -0
  183. package/dist/lib/codex-manager/forecast.js +218 -0
  184. package/dist/lib/codex-manager/forecast.js.map +1 -0
  185. package/dist/lib/codex-manager/help.d.ts +36 -0
  186. package/dist/lib/codex-manager/help.d.ts.map +1 -0
  187. package/dist/lib/codex-manager/help.js +125 -0
  188. package/dist/lib/codex-manager/help.js.map +1 -0
  189. package/dist/lib/codex-manager/info.d.ts +11 -0
  190. package/dist/lib/codex-manager/info.d.ts.map +1 -0
  191. package/dist/lib/codex-manager/info.js +91 -0
  192. package/dist/lib/codex-manager/info.js.map +1 -0
  193. package/dist/lib/codex-manager/login.d.ts +40 -0
  194. package/dist/lib/codex-manager/login.d.ts.map +1 -0
  195. package/dist/lib/codex-manager/login.js +1041 -0
  196. package/dist/lib/codex-manager/login.js.map +1 -0
  197. package/dist/lib/codex-manager/repair-commands.d.ts +70 -0
  198. package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -0
  199. package/dist/lib/codex-manager/repair-commands.js +1671 -0
  200. package/dist/lib/codex-manager/repair-commands.js.map +1 -0
  201. package/dist/lib/codex-manager/report.d.ts +39 -0
  202. package/dist/lib/codex-manager/report.d.ts.map +1 -0
  203. package/dist/lib/codex-manager/report.js +243 -0
  204. package/dist/lib/codex-manager/report.js.map +1 -0
  205. package/dist/lib/codex-manager/settings-hub-entry.d.ts +30 -0
  206. package/dist/lib/codex-manager/settings-hub-entry.d.ts.map +1 -0
  207. package/dist/lib/codex-manager/settings-hub-entry.js +11 -0
  208. package/dist/lib/codex-manager/settings-hub-entry.js.map +1 -0
  209. package/dist/lib/codex-manager/settings-hub-menu.d.ts +30 -0
  210. package/dist/lib/codex-manager/settings-hub-menu.d.ts.map +1 -0
  211. package/dist/lib/codex-manager/settings-hub-menu.js +37 -0
  212. package/dist/lib/codex-manager/settings-hub-menu.js.map +1 -0
  213. package/dist/lib/codex-manager/settings-hub-prompt.d.ts +25 -0
  214. package/dist/lib/codex-manager/settings-hub-prompt.d.ts.map +1 -0
  215. package/dist/lib/codex-manager/settings-hub-prompt.js +23 -0
  216. package/dist/lib/codex-manager/settings-hub-prompt.js.map +1 -0
  217. package/dist/lib/codex-manager/settings-hub.d.ts +12 -10
  218. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  219. package/dist/lib/codex-manager/settings-hub.js +262 -2187
  220. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  221. package/dist/lib/codex-manager/settings-panels.d.ts +56 -0
  222. package/dist/lib/codex-manager/settings-panels.d.ts.map +1 -0
  223. package/dist/lib/codex-manager/settings-panels.js +73 -0
  224. package/dist/lib/codex-manager/settings-panels.js.map +1 -0
  225. package/dist/lib/codex-manager/settings-persist-utils.d.ts +9 -0
  226. package/dist/lib/codex-manager/settings-persist-utils.d.ts.map +1 -0
  227. package/dist/lib/codex-manager/settings-persist-utils.js +33 -0
  228. package/dist/lib/codex-manager/settings-persist-utils.js.map +1 -0
  229. package/dist/lib/codex-manager/settings-preview.d.ts +12 -0
  230. package/dist/lib/codex-manager/settings-preview.d.ts.map +1 -0
  231. package/dist/lib/codex-manager/settings-preview.js +118 -0
  232. package/dist/lib/codex-manager/settings-preview.js.map +1 -0
  233. package/dist/lib/codex-manager/settings-write-queue.d.ts +14 -0
  234. package/dist/lib/codex-manager/settings-write-queue.d.ts.map +1 -0
  235. package/dist/lib/codex-manager/settings-write-queue.js +88 -0
  236. package/dist/lib/codex-manager/settings-write-queue.js.map +1 -0
  237. package/dist/lib/codex-manager/statusline-order.d.ts +3 -0
  238. package/dist/lib/codex-manager/statusline-order.d.ts.map +1 -0
  239. package/dist/lib/codex-manager/statusline-order.js +17 -0
  240. package/dist/lib/codex-manager/statusline-order.js.map +1 -0
  241. package/dist/lib/codex-manager/statusline-settings-panel.d.ts +40 -0
  242. package/dist/lib/codex-manager/statusline-settings-panel.d.ts.map +1 -0
  243. package/dist/lib/codex-manager/statusline-settings-panel.js +167 -0
  244. package/dist/lib/codex-manager/statusline-settings-panel.js.map +1 -0
  245. package/dist/lib/codex-manager/switch.d.ts +31 -0
  246. package/dist/lib/codex-manager/switch.d.ts.map +1 -0
  247. package/dist/lib/codex-manager/switch.js +103 -0
  248. package/dist/lib/codex-manager/switch.js.map +1 -0
  249. package/dist/lib/codex-manager/theme-settings-panel.d.ts +26 -0
  250. package/dist/lib/codex-manager/theme-settings-panel.d.ts.map +1 -0
  251. package/dist/lib/codex-manager/theme-settings-panel.js +131 -0
  252. package/dist/lib/codex-manager/theme-settings-panel.js.map +1 -0
  253. package/dist/lib/codex-manager/unified-settings-controller.d.ts +27 -0
  254. package/dist/lib/codex-manager/unified-settings-controller.d.ts.map +1 -0
  255. package/dist/lib/codex-manager/unified-settings-controller.js +50 -0
  256. package/dist/lib/codex-manager/unified-settings-controller.js.map +1 -0
  257. package/dist/lib/codex-manager/unified-settings-entry.d.ts +6 -0
  258. package/dist/lib/codex-manager/unified-settings-entry.d.ts.map +1 -0
  259. package/dist/lib/codex-manager/unified-settings-entry.js +23 -0
  260. package/dist/lib/codex-manager/unified-settings-entry.js.map +1 -0
  261. package/dist/lib/codex-manager/verify-flagged.d.ts +20 -0
  262. package/dist/lib/codex-manager/verify-flagged.d.ts.map +1 -0
  263. package/dist/lib/codex-manager/verify-flagged.js +316 -0
  264. package/dist/lib/codex-manager/verify-flagged.js.map +1 -0
  265. package/dist/lib/codex-manager.d.ts +1 -0
  266. package/dist/lib/codex-manager.d.ts.map +1 -1
  267. package/dist/lib/codex-manager.js +767 -2195
  268. package/dist/lib/codex-manager.js.map +1 -1
  269. package/dist/lib/config.d.ts +38 -0
  270. package/dist/lib/config.d.ts.map +1 -1
  271. package/dist/lib/config.js +371 -9
  272. package/dist/lib/config.js.map +1 -1
  273. package/dist/lib/errors.d.ts +9 -0
  274. package/dist/lib/errors.d.ts.map +1 -1
  275. package/dist/lib/errors.js +13 -0
  276. package/dist/lib/errors.js.map +1 -1
  277. package/dist/lib/forecast.d.ts +17 -0
  278. package/dist/lib/forecast.d.ts.map +1 -1
  279. package/dist/lib/forecast.js +33 -6
  280. package/dist/lib/forecast.js.map +1 -1
  281. package/dist/lib/named-backup-export.js +2 -2
  282. package/dist/lib/named-backup-export.js.map +1 -1
  283. package/dist/lib/prompts/codex.d.ts +8 -3
  284. package/dist/lib/prompts/codex.d.ts.map +1 -1
  285. package/dist/lib/prompts/codex.js +16 -26
  286. package/dist/lib/prompts/codex.js.map +1 -1
  287. package/dist/lib/request/failover-config.d.ts +4 -0
  288. package/dist/lib/request/failover-config.d.ts.map +1 -0
  289. package/dist/lib/request/failover-config.js +15 -0
  290. package/dist/lib/request/failover-config.js.map +1 -0
  291. package/dist/lib/request/fetch-helpers.d.ts +28 -4
  292. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  293. package/dist/lib/request/fetch-helpers.js +170 -8
  294. package/dist/lib/request/fetch-helpers.js.map +1 -1
  295. package/dist/lib/request/helpers/model-map.d.ts +49 -12
  296. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  297. package/dist/lib/request/helpers/model-map.js +303 -108
  298. package/dist/lib/request/helpers/model-map.js.map +1 -1
  299. package/dist/lib/request/helpers/tool-utils.d.ts +2 -15
  300. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
  301. package/dist/lib/request/helpers/tool-utils.js +40 -27
  302. package/dist/lib/request/helpers/tool-utils.js.map +1 -1
  303. package/dist/lib/request/index.d.ts +5 -0
  304. package/dist/lib/request/index.d.ts.map +1 -0
  305. package/dist/lib/request/index.js +5 -0
  306. package/dist/lib/request/index.js.map +1 -0
  307. package/dist/lib/request/request-init.d.ts +3 -0
  308. package/dist/lib/request/request-init.d.ts.map +1 -0
  309. package/dist/lib/request/request-init.js +50 -0
  310. package/dist/lib/request/request-init.js.map +1 -0
  311. package/dist/lib/request/request-transformer.d.ts +19 -6
  312. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  313. package/dist/lib/request/request-transformer.js +210 -193
  314. package/dist/lib/request/request-transformer.js.map +1 -1
  315. package/dist/lib/request/response-compaction.d.ts +20 -0
  316. package/dist/lib/request/response-compaction.d.ts.map +1 -0
  317. package/dist/lib/request/response-compaction.js +124 -0
  318. package/dist/lib/request/response-compaction.js.map +1 -0
  319. package/dist/lib/request/response-handler.d.ts +2 -0
  320. package/dist/lib/request/response-handler.d.ts.map +1 -1
  321. package/dist/lib/request/response-handler.js +567 -11
  322. package/dist/lib/request/response-handler.js.map +1 -1
  323. package/dist/lib/request/response-metadata.d.ts +3 -0
  324. package/dist/lib/request/response-metadata.d.ts.map +1 -0
  325. package/dist/lib/request/response-metadata.js +61 -0
  326. package/dist/lib/request/response-metadata.js.map +1 -0
  327. package/dist/lib/request/trace-mode.d.ts +9 -0
  328. package/dist/lib/request/trace-mode.d.ts.map +1 -0
  329. package/dist/lib/request/trace-mode.js +14 -0
  330. package/dist/lib/request/trace-mode.js.map +1 -0
  331. package/dist/lib/request/wait-utils.d.ts +14 -0
  332. package/dist/lib/request/wait-utils.d.ts.map +1 -0
  333. package/dist/lib/request/wait-utils.js +44 -0
  334. package/dist/lib/request/wait-utils.js.map +1 -0
  335. package/dist/lib/runtime/account-check-helpers.d.ts +8 -0
  336. package/dist/lib/runtime/account-check-helpers.d.ts.map +1 -0
  337. package/dist/lib/runtime/account-check-helpers.js +30 -0
  338. package/dist/lib/runtime/account-check-helpers.js.map +1 -0
  339. package/dist/lib/runtime/account-check-types.d.ts +18 -0
  340. package/dist/lib/runtime/account-check-types.d.ts.map +1 -0
  341. package/dist/lib/runtime/account-check-types.js +12 -0
  342. package/dist/lib/runtime/account-check-types.js.map +1 -0
  343. package/dist/lib/runtime/account-check.d.ts +48 -0
  344. package/dist/lib/runtime/account-check.d.ts.map +1 -0
  345. package/dist/lib/runtime/account-check.js +202 -0
  346. package/dist/lib/runtime/account-check.js.map +1 -0
  347. package/dist/lib/runtime/account-health-check.d.ts +8 -0
  348. package/dist/lib/runtime/account-health-check.d.ts.map +1 -0
  349. package/dist/lib/runtime/account-health-check.js +30 -0
  350. package/dist/lib/runtime/account-health-check.js.map +1 -0
  351. package/dist/lib/runtime/account-manager-cache-entry.d.ts +25 -0
  352. package/dist/lib/runtime/account-manager-cache-entry.d.ts.map +1 -0
  353. package/dist/lib/runtime/account-manager-cache-entry.js +17 -0
  354. package/dist/lib/runtime/account-manager-cache-entry.js.map +1 -0
  355. package/dist/lib/runtime/account-manager-cache.d.ts +25 -0
  356. package/dist/lib/runtime/account-manager-cache.d.ts.map +1 -0
  357. package/dist/lib/runtime/account-manager-cache.js +44 -0
  358. package/dist/lib/runtime/account-manager-cache.js.map +1 -0
  359. package/dist/lib/runtime/account-pool.d.ts +23 -0
  360. package/dist/lib/runtime/account-pool.d.ts.map +1 -0
  361. package/dist/lib/runtime/account-pool.js +138 -0
  362. package/dist/lib/runtime/account-pool.js.map +1 -0
  363. package/dist/lib/runtime/account-scope.d.ts +10 -0
  364. package/dist/lib/runtime/account-scope.d.ts.map +1 -0
  365. package/dist/lib/runtime/account-scope.js +13 -0
  366. package/dist/lib/runtime/account-scope.js.map +1 -0
  367. package/dist/lib/runtime/account-select-event.d.ts +19 -0
  368. package/dist/lib/runtime/account-select-event.d.ts.map +1 -0
  369. package/dist/lib/runtime/account-select-event.js +53 -0
  370. package/dist/lib/runtime/account-select-event.js.map +1 -0
  371. package/dist/lib/runtime/account-selection.d.ts +28 -0
  372. package/dist/lib/runtime/account-selection.d.ts.map +1 -0
  373. package/dist/lib/runtime/account-selection.js +46 -0
  374. package/dist/lib/runtime/account-selection.js.map +1 -0
  375. package/dist/lib/runtime/account-state.d.ts +13 -0
  376. package/dist/lib/runtime/account-state.d.ts.map +1 -0
  377. package/dist/lib/runtime/account-state.js +38 -0
  378. package/dist/lib/runtime/account-state.js.map +1 -0
  379. package/dist/lib/runtime/account-status.d.ts +13 -0
  380. package/dist/lib/runtime/account-status.d.ts.map +1 -0
  381. package/dist/lib/runtime/account-status.js +37 -0
  382. package/dist/lib/runtime/account-status.js.map +1 -0
  383. package/dist/lib/runtime/account-storage-scope-entry.d.ts +26 -0
  384. package/dist/lib/runtime/account-storage-scope-entry.d.ts.map +1 -0
  385. package/dist/lib/runtime/account-storage-scope-entry.js +15 -0
  386. package/dist/lib/runtime/account-storage-scope-entry.js.map +1 -0
  387. package/dist/lib/runtime/auth-facade.d.ts +51 -0
  388. package/dist/lib/runtime/auth-facade.d.ts.map +1 -0
  389. package/dist/lib/runtime/auth-facade.js +48 -0
  390. package/dist/lib/runtime/auth-facade.js.map +1 -0
  391. package/dist/lib/runtime/browser-oauth-flow.d.ts +32 -0
  392. package/dist/lib/runtime/browser-oauth-flow.d.ts.map +1 -0
  393. package/dist/lib/runtime/browser-oauth-flow.js +31 -0
  394. package/dist/lib/runtime/browser-oauth-flow.js.map +1 -0
  395. package/dist/lib/runtime/capability-boost.d.ts +25 -0
  396. package/dist/lib/runtime/capability-boost.d.ts.map +1 -0
  397. package/dist/lib/runtime/capability-boost.js +26 -0
  398. package/dist/lib/runtime/capability-boost.js.map +1 -0
  399. package/dist/lib/runtime/event-handler.d.ts +53 -0
  400. package/dist/lib/runtime/event-handler.d.ts.map +1 -0
  401. package/dist/lib/runtime/event-handler.js +46 -0
  402. package/dist/lib/runtime/event-handler.js.map +1 -0
  403. package/dist/lib/runtime/flagged-verify-types.d.ts +9 -0
  404. package/dist/lib/runtime/flagged-verify-types.d.ts.map +1 -0
  405. package/dist/lib/runtime/flagged-verify-types.js +7 -0
  406. package/dist/lib/runtime/flagged-verify-types.js.map +1 -0
  407. package/dist/lib/runtime/hydrate-emails.d.ts +13 -0
  408. package/dist/lib/runtime/hydrate-emails.d.ts.map +1 -0
  409. package/dist/lib/runtime/hydrate-emails.js +56 -0
  410. package/dist/lib/runtime/hydrate-emails.js.map +1 -0
  411. package/dist/lib/runtime/live-sync-entry.d.ts +36 -0
  412. package/dist/lib/runtime/live-sync-entry.d.ts.map +1 -0
  413. package/dist/lib/runtime/live-sync-entry.js +14 -0
  414. package/dist/lib/runtime/live-sync-entry.js.map +1 -0
  415. package/dist/lib/runtime/live-sync.d.ts +35 -0
  416. package/dist/lib/runtime/live-sync.d.ts.map +1 -0
  417. package/dist/lib/runtime/live-sync.js +65 -0
  418. package/dist/lib/runtime/live-sync.js.map +1 -0
  419. package/dist/lib/runtime/loader-setup.d.ts +9 -0
  420. package/dist/lib/runtime/loader-setup.d.ts.map +1 -0
  421. package/dist/lib/runtime/loader-setup.js +8 -0
  422. package/dist/lib/runtime/loader-setup.js.map +1 -0
  423. package/dist/lib/runtime/login-menu-accounts.d.ts +29 -0
  424. package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -0
  425. package/dist/lib/runtime/login-menu-accounts.js +33 -0
  426. package/dist/lib/runtime/login-menu-accounts.js.map +1 -0
  427. package/dist/lib/runtime/manual-oauth-flow.d.ts +40 -0
  428. package/dist/lib/runtime/manual-oauth-flow.d.ts.map +1 -0
  429. package/dist/lib/runtime/manual-oauth-flow.js +71 -0
  430. package/dist/lib/runtime/manual-oauth-flow.js.map +1 -0
  431. package/dist/lib/runtime/metrics.d.ts +29 -0
  432. package/dist/lib/runtime/metrics.d.ts.map +1 -0
  433. package/dist/lib/runtime/metrics.js +97 -0
  434. package/dist/lib/runtime/metrics.js.map +1 -0
  435. package/dist/lib/runtime/oauth-browser-flow.d.ts +9 -0
  436. package/dist/lib/runtime/oauth-browser-flow.d.ts.map +1 -0
  437. package/dist/lib/runtime/oauth-browser-flow.js +34 -0
  438. package/dist/lib/runtime/oauth-browser-flow.js.map +1 -0
  439. package/dist/lib/runtime/preemptive-quota.d.ts +13 -0
  440. package/dist/lib/runtime/preemptive-quota.d.ts.map +1 -0
  441. package/dist/lib/runtime/preemptive-quota.js +9 -0
  442. package/dist/lib/runtime/preemptive-quota.js.map +1 -0
  443. package/dist/lib/runtime/quota-headers.d.ts +12 -0
  444. package/dist/lib/runtime/quota-headers.d.ts.map +1 -0
  445. package/dist/lib/runtime/quota-headers.js +131 -0
  446. package/dist/lib/runtime/quota-headers.js.map +1 -0
  447. package/dist/lib/runtime/quota-probe.d.ts +18 -0
  448. package/dist/lib/runtime/quota-probe.d.ts.map +1 -0
  449. package/dist/lib/runtime/quota-probe.js +82 -0
  450. package/dist/lib/runtime/quota-probe.js.map +1 -0
  451. package/dist/lib/runtime/quota-settings.d.ts +14 -0
  452. package/dist/lib/runtime/quota-settings.d.ts.map +1 -0
  453. package/dist/lib/runtime/quota-settings.js +12 -0
  454. package/dist/lib/runtime/quota-settings.js.map +1 -0
  455. package/dist/lib/runtime/refresh-guardian-entry.d.ts +32 -0
  456. package/dist/lib/runtime/refresh-guardian-entry.d.ts.map +1 -0
  457. package/dist/lib/runtime/refresh-guardian-entry.js +12 -0
  458. package/dist/lib/runtime/refresh-guardian-entry.js.map +1 -0
  459. package/dist/lib/runtime/refresh-guardian.d.ts +24 -0
  460. package/dist/lib/runtime/refresh-guardian.d.ts.map +1 -0
  461. package/dist/lib/runtime/refresh-guardian.js +32 -0
  462. package/dist/lib/runtime/refresh-guardian.js.map +1 -0
  463. package/dist/lib/runtime/request-init.d.ts +5 -0
  464. package/dist/lib/runtime/request-init.d.ts.map +1 -0
  465. package/dist/lib/runtime/request-init.js +50 -0
  466. package/dist/lib/runtime/request-init.js.map +1 -0
  467. package/dist/lib/runtime/runtime-services.d.ts +58 -0
  468. package/dist/lib/runtime/runtime-services.d.ts.map +1 -0
  469. package/dist/lib/runtime/runtime-services.js +87 -0
  470. package/dist/lib/runtime/runtime-services.js.map +1 -0
  471. package/dist/lib/runtime/session-affinity-entry.d.ts +30 -0
  472. package/dist/lib/runtime/session-affinity-entry.d.ts.map +1 -0
  473. package/dist/lib/runtime/session-affinity-entry.js +11 -0
  474. package/dist/lib/runtime/session-affinity-entry.js.map +1 -0
  475. package/dist/lib/runtime/session-affinity.d.ts +13 -0
  476. package/dist/lib/runtime/session-affinity.d.ts.map +1 -0
  477. package/dist/lib/runtime/session-affinity.js +17 -0
  478. package/dist/lib/runtime/session-affinity.js.map +1 -0
  479. package/dist/lib/runtime/session-recovery.d.ts +8 -0
  480. package/dist/lib/runtime/session-recovery.d.ts.map +1 -0
  481. package/dist/lib/runtime/session-recovery.js +7 -0
  482. package/dist/lib/runtime/session-recovery.js.map +1 -0
  483. package/dist/lib/runtime/status-marker.d.ts +3 -0
  484. package/dist/lib/runtime/status-marker.d.ts.map +1 -0
  485. package/dist/lib/runtime/status-marker.js +15 -0
  486. package/dist/lib/runtime/status-marker.js.map +1 -0
  487. package/dist/lib/runtime/storage-scope.d.ts +13 -0
  488. package/dist/lib/runtime/storage-scope.d.ts.map +1 -0
  489. package/dist/lib/runtime/storage-scope.js +14 -0
  490. package/dist/lib/runtime/storage-scope.js.map +1 -0
  491. package/dist/lib/runtime/toast.d.ts +16 -0
  492. package/dist/lib/runtime/toast.d.ts.map +1 -0
  493. package/dist/lib/runtime/toast.js +16 -0
  494. package/dist/lib/runtime/toast.js.map +1 -0
  495. package/dist/lib/runtime/ui-runtime-entry.d.ts +7 -0
  496. package/dist/lib/runtime/ui-runtime-entry.d.ts.map +1 -0
  497. package/dist/lib/runtime/ui-runtime-entry.js +4 -0
  498. package/dist/lib/runtime/ui-runtime-entry.js.map +1 -0
  499. package/dist/lib/runtime/ui-runtime.d.ts +9 -0
  500. package/dist/lib/runtime/ui-runtime.d.ts.map +1 -0
  501. package/dist/lib/runtime/ui-runtime.js +23 -0
  502. package/dist/lib/runtime/ui-runtime.js.map +1 -0
  503. package/dist/lib/runtime/verify-flagged.d.ts +44 -0
  504. package/dist/lib/runtime/verify-flagged.d.ts.map +1 -0
  505. package/dist/lib/runtime/verify-flagged.js +84 -0
  506. package/dist/lib/runtime/verify-flagged.js.map +1 -0
  507. package/dist/lib/schemas.d.ts +9 -0
  508. package/dist/lib/schemas.d.ts.map +1 -1
  509. package/dist/lib/schemas.js +9 -1
  510. package/dist/lib/schemas.js.map +1 -1
  511. package/dist/lib/session-affinity.d.ts +10 -0
  512. package/dist/lib/session-affinity.d.ts.map +1 -1
  513. package/dist/lib/session-affinity.js +53 -6
  514. package/dist/lib/session-affinity.js.map +1 -1
  515. package/dist/lib/storage/account-clear-entry.d.ts +16 -0
  516. package/dist/lib/storage/account-clear-entry.d.ts.map +1 -0
  517. package/dist/lib/storage/account-clear-entry.js +12 -0
  518. package/dist/lib/storage/account-clear-entry.js.map +1 -0
  519. package/dist/lib/storage/account-clear.d.ts +8 -0
  520. package/dist/lib/storage/account-clear.d.ts.map +1 -0
  521. package/dist/lib/storage/account-clear.js +29 -0
  522. package/dist/lib/storage/account-clear.js.map +1 -0
  523. package/dist/lib/storage/account-match-utils.d.ts +9 -0
  524. package/dist/lib/storage/account-match-utils.d.ts.map +1 -0
  525. package/dist/lib/storage/account-match-utils.js +45 -0
  526. package/dist/lib/storage/account-match-utils.js.map +1 -0
  527. package/dist/lib/storage/account-persistence.d.ts +3 -0
  528. package/dist/lib/storage/account-persistence.d.ts.map +1 -0
  529. package/dist/lib/storage/account-persistence.js +12 -0
  530. package/dist/lib/storage/account-persistence.js.map +1 -0
  531. package/dist/lib/storage/account-port.d.ts +50 -0
  532. package/dist/lib/storage/account-port.d.ts.map +1 -0
  533. package/dist/lib/storage/account-port.js +43 -0
  534. package/dist/lib/storage/account-port.js.map +1 -0
  535. package/dist/lib/storage/account-save-entry.d.ts +7 -0
  536. package/dist/lib/storage/account-save-entry.d.ts.map +1 -0
  537. package/dist/lib/storage/account-save-entry.js +6 -0
  538. package/dist/lib/storage/account-save-entry.js.map +1 -0
  539. package/dist/lib/storage/account-save.d.ts +30 -0
  540. package/dist/lib/storage/account-save.d.ts.map +1 -0
  541. package/dist/lib/storage/account-save.js +49 -0
  542. package/dist/lib/storage/account-save.js.map +1 -0
  543. package/dist/lib/storage/account-snapshot.d.ts +25 -0
  544. package/dist/lib/storage/account-snapshot.d.ts.map +1 -0
  545. package/dist/lib/storage/account-snapshot.js +78 -0
  546. package/dist/lib/storage/account-snapshot.js.map +1 -0
  547. package/dist/lib/storage/backup-metadata-builder.d.ts +32 -0
  548. package/dist/lib/storage/backup-metadata-builder.d.ts.map +1 -0
  549. package/dist/lib/storage/backup-metadata-builder.js +77 -0
  550. package/dist/lib/storage/backup-metadata-builder.js.map +1 -0
  551. package/dist/lib/storage/backup-metadata.d.ts +29 -0
  552. package/dist/lib/storage/backup-metadata.d.ts.map +1 -0
  553. package/dist/lib/storage/backup-metadata.js +16 -0
  554. package/dist/lib/storage/backup-metadata.js.map +1 -0
  555. package/dist/lib/storage/backup-paths.d.ts +10 -0
  556. package/dist/lib/storage/backup-paths.d.ts.map +1 -0
  557. package/dist/lib/storage/backup-paths.js +27 -0
  558. package/dist/lib/storage/backup-paths.js.map +1 -0
  559. package/dist/lib/storage/backup-restore.d.ts +11 -0
  560. package/dist/lib/storage/backup-restore.d.ts.map +1 -0
  561. package/dist/lib/storage/backup-restore.js +52 -0
  562. package/dist/lib/storage/backup-restore.js.map +1 -0
  563. package/dist/lib/storage/cache-artifacts.d.ts +2 -0
  564. package/dist/lib/storage/cache-artifacts.d.ts.map +1 -0
  565. package/dist/lib/storage/cache-artifacts.js +4 -0
  566. package/dist/lib/storage/cache-artifacts.js.map +1 -0
  567. package/dist/lib/storage/error-hints.d.ts +10 -0
  568. package/dist/lib/storage/error-hints.d.ts.map +1 -0
  569. package/dist/lib/storage/error-hints.js +36 -0
  570. package/dist/lib/storage/error-hints.js.map +1 -0
  571. package/dist/lib/storage/file-paths.d.ts +6 -0
  572. package/dist/lib/storage/file-paths.d.ts.map +1 -0
  573. package/dist/lib/storage/file-paths.js +17 -0
  574. package/dist/lib/storage/file-paths.js.map +1 -0
  575. package/dist/lib/storage/fixture-guards.d.ts +4 -0
  576. package/dist/lib/storage/fixture-guards.d.ts.map +1 -0
  577. package/dist/lib/storage/fixture-guards.js +27 -0
  578. package/dist/lib/storage/fixture-guards.js.map +1 -0
  579. package/dist/lib/storage/flagged-entry.d.ts +15 -0
  580. package/dist/lib/storage/flagged-entry.d.ts.map +1 -0
  581. package/dist/lib/storage/flagged-entry.js +12 -0
  582. package/dist/lib/storage/flagged-entry.js.map +1 -0
  583. package/dist/lib/storage/flagged-load-entry.d.ts +20 -0
  584. package/dist/lib/storage/flagged-load-entry.d.ts.map +1 -0
  585. package/dist/lib/storage/flagged-load-entry.js +13 -0
  586. package/dist/lib/storage/flagged-load-entry.js.map +1 -0
  587. package/dist/lib/storage/flagged-save-entry.d.ts +7 -0
  588. package/dist/lib/storage/flagged-save-entry.d.ts.map +1 -0
  589. package/dist/lib/storage/flagged-save-entry.js +6 -0
  590. package/dist/lib/storage/flagged-save-entry.js.map +1 -0
  591. package/dist/lib/storage/flagged-storage-file.d.ts +7 -0
  592. package/dist/lib/storage/flagged-storage-file.d.ts.map +1 -0
  593. package/dist/lib/storage/flagged-storage-file.js +25 -0
  594. package/dist/lib/storage/flagged-storage-file.js.map +1 -0
  595. package/dist/lib/storage/flagged-storage-io.d.ts +28 -0
  596. package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -0
  597. package/dist/lib/storage/flagged-storage-io.js +135 -0
  598. package/dist/lib/storage/flagged-storage-io.js.map +1 -0
  599. package/dist/lib/storage/flagged-storage.d.ts +6 -0
  600. package/dist/lib/storage/flagged-storage.d.ts.map +1 -0
  601. package/dist/lib/storage/flagged-storage.js +90 -0
  602. package/dist/lib/storage/flagged-storage.js.map +1 -0
  603. package/dist/lib/storage/gitignore.d.ts +7 -0
  604. package/dist/lib/storage/gitignore.d.ts.map +1 -0
  605. package/dist/lib/storage/gitignore.js +34 -0
  606. package/dist/lib/storage/gitignore.js.map +1 -0
  607. package/dist/lib/storage/hash.d.ts +2 -0
  608. package/dist/lib/storage/hash.d.ts.map +1 -0
  609. package/dist/lib/storage/hash.js +5 -0
  610. package/dist/lib/storage/hash.js.map +1 -0
  611. package/dist/lib/storage/identity.d.ts +15 -0
  612. package/dist/lib/storage/identity.d.ts.map +1 -0
  613. package/dist/lib/storage/identity.js +48 -0
  614. package/dist/lib/storage/identity.js.map +1 -0
  615. package/dist/lib/storage/import-export.d.ts +24 -0
  616. package/dist/lib/storage/import-export.d.ts.map +1 -0
  617. package/dist/lib/storage/import-export.js +108 -0
  618. package/dist/lib/storage/import-export.js.map +1 -0
  619. package/dist/lib/storage/metadata-section.d.ts +16 -0
  620. package/dist/lib/storage/metadata-section.d.ts.map +1 -0
  621. package/dist/lib/storage/metadata-section.js +16 -0
  622. package/dist/lib/storage/metadata-section.js.map +1 -0
  623. package/dist/lib/storage/migrations.d.ts +5 -2
  624. package/dist/lib/storage/migrations.d.ts.map +1 -1
  625. package/dist/lib/storage/migrations.js.map +1 -1
  626. package/dist/lib/storage/named-backup-entry.d.ts +15 -0
  627. package/dist/lib/storage/named-backup-entry.d.ts.map +1 -0
  628. package/dist/lib/storage/named-backup-entry.js +7 -0
  629. package/dist/lib/storage/named-backup-entry.js.map +1 -0
  630. package/dist/lib/storage/named-backups-entry.d.ts +19 -0
  631. package/dist/lib/storage/named-backups-entry.d.ts.map +1 -0
  632. package/dist/lib/storage/named-backups-entry.js +7 -0
  633. package/dist/lib/storage/named-backups-entry.js.map +1 -0
  634. package/dist/lib/storage/named-backups.d.ts +15 -0
  635. package/dist/lib/storage/named-backups.d.ts.map +1 -0
  636. package/dist/lib/storage/named-backups.js +68 -0
  637. package/dist/lib/storage/named-backups.js.map +1 -0
  638. package/dist/lib/storage/path-state.d.ts +9 -0
  639. package/dist/lib/storage/path-state.d.ts.map +1 -0
  640. package/dist/lib/storage/path-state.js +20 -0
  641. package/dist/lib/storage/path-state.js.map +1 -0
  642. package/dist/lib/storage/project-migration.d.ts +10 -0
  643. package/dist/lib/storage/project-migration.d.ts.map +1 -0
  644. package/dist/lib/storage/project-migration.js +38 -0
  645. package/dist/lib/storage/project-migration.js.map +1 -0
  646. package/dist/lib/storage/record-utils.d.ts +3 -0
  647. package/dist/lib/storage/record-utils.d.ts.map +1 -0
  648. package/dist/lib/storage/record-utils.js +9 -0
  649. package/dist/lib/storage/record-utils.js.map +1 -0
  650. package/dist/lib/storage/restore-assessment.d.ts +7 -0
  651. package/dist/lib/storage/restore-assessment.d.ts.map +1 -0
  652. package/dist/lib/storage/restore-assessment.js +42 -0
  653. package/dist/lib/storage/restore-assessment.js.map +1 -0
  654. package/dist/lib/storage/restore-backup-entry.d.ts +24 -0
  655. package/dist/lib/storage/restore-backup-entry.d.ts.map +1 -0
  656. package/dist/lib/storage/restore-backup-entry.js +10 -0
  657. package/dist/lib/storage/restore-backup-entry.js.map +1 -0
  658. package/dist/lib/storage/restore-metadata.d.ts +10 -0
  659. package/dist/lib/storage/restore-metadata.d.ts.map +1 -0
  660. package/dist/lib/storage/restore-metadata.js +18 -0
  661. package/dist/lib/storage/restore-metadata.js.map +1 -0
  662. package/dist/lib/storage/restore.d.ts +14 -0
  663. package/dist/lib/storage/restore.d.ts.map +1 -0
  664. package/dist/lib/storage/restore.js +53 -0
  665. package/dist/lib/storage/restore.js.map +1 -0
  666. package/dist/lib/storage/snapshot-inspectors.d.ts +22 -0
  667. package/dist/lib/storage/snapshot-inspectors.d.ts.map +1 -0
  668. package/dist/lib/storage/snapshot-inspectors.js +95 -0
  669. package/dist/lib/storage/snapshot-inspectors.js.map +1 -0
  670. package/dist/lib/storage/storage-parser.d.ts +15 -0
  671. package/dist/lib/storage/storage-parser.d.ts.map +1 -0
  672. package/dist/lib/storage/storage-parser.js +16 -0
  673. package/dist/lib/storage/storage-parser.js.map +1 -0
  674. package/dist/lib/storage/transactions.d.ts +23 -0
  675. package/dist/lib/storage/transactions.d.ts.map +1 -0
  676. package/dist/lib/storage/transactions.js +64 -0
  677. package/dist/lib/storage/transactions.js.map +1 -0
  678. package/dist/lib/storage.d.ts +12 -41
  679. package/dist/lib/storage.d.ts.map +1 -1
  680. package/dist/lib/storage.js +527 -938
  681. package/dist/lib/storage.js.map +1 -1
  682. package/dist/lib/types.d.ts +80 -1
  683. package/dist/lib/types.d.ts.map +1 -1
  684. package/dist/lib/ui/copy.d.ts +24 -4
  685. package/dist/lib/ui/copy.d.ts.map +1 -1
  686. package/dist/lib/ui/copy.js +24 -4
  687. package/dist/lib/ui/copy.js.map +1 -1
  688. package/package.json +44 -6
  689. package/scripts/check-pack-budget-lib.js +114 -0
  690. package/scripts/check-pack-budget.mjs +10 -0
  691. package/scripts/codex-multi-auth.js +34 -10
  692. package/scripts/install-codex-auth-utils.js +54 -14
  693. package/scripts/install-codex-auth.js +62 -33
  694. package/scripts/update-vendor-provenance.mjs +96 -0
  695. package/scripts/verify-vendor-provenance.mjs +53 -0
package/dist/index.js CHANGED
@@ -23,39 +23,68 @@
23
23
 
24
24
  */
25
25
  import { tool } from "@codex-ai/plugin/tool";
26
+ import { AccountManager, extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, getAccountIdCandidates, isCodexCliSyncEnabled, lookupCodexCliTokensByEmail, parseRateLimitReason, resolveRequestAccountId, resolveRuntimeRequestIdentity, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, } from "./lib/accounts.js";
26
27
  import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, redactOAuthUrlForLog, REDIRECT_URI, } from "./lib/auth/auth.js";
27
- import { queuedRefresh } from "./lib/refresh-queue.js";
28
- import { openBrowserUrl } from "./lib/auth/browser.js";
28
+ import { isBrowserLaunchSuppressed, openBrowserUrl } from "./lib/auth/browser.js";
29
29
  import { startLocalOAuthServer } from "./lib/auth/server.js";
30
- import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
31
- import { getCodexMode, getFastSession, getFastSessionStrategy, getFastSessionMaxInputItems, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getFallbackToGpt52OnUnsupportedGpt53, getUnsupportedCodexPolicy, getUnsupportedCodexFallbackChain, getTokenRefreshSkewMs, getSessionRecovery, getAutoResume, getToastDurationMs, getPerProjectAccounts, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getPidOffsetEnabled, getFetchTimeoutMs, getStreamStallTimeoutMs, getCodexTuiV2, getCodexTuiColorProfile, getCodexTuiGlyphMode, getLiveAccountSync, getLiveAccountSyncDebounceMs, getLiveAccountSyncPollMs, getSessionAffinity, getSessionAffinityTtlMs, getSessionAffinityMaxEntries, getProactiveRefreshGuardian, getProactiveRefreshIntervalMs, getProactiveRefreshBufferMs, getNetworkErrorCooldownMs, getServerErrorCooldownMs, getStorageBackupEnabled, getPreemptiveQuotaEnabled, getPreemptiveQuotaRemainingPercent5h, getPreemptiveQuotaRemainingPercent7d, getPreemptiveQuotaMaxDeferralMs, loadPluginConfig, } from "./lib/config.js";
32
- import { AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, ACCOUNT_LIMITS, } from "./lib/constants.js";
33
- import { initLogger, logRequest, logDebug, logInfo, logWarn, logError, setCorrelationId, clearCorrelationId, } from "./lib/logger.js";
34
30
  import { checkAndNotify } from "./lib/auto-update-checker.js";
31
+ import { CapabilityPolicyStore } from "./lib/capability-policy.js";
32
+ import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
33
+ import { getAutoResume, getCodexMode, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getFallbackToGpt52OnUnsupportedGpt53, getFastSession, getFastSessionMaxInputItems, getFastSessionStrategy, getFetchTimeoutMs, getLiveAccountSync, getLiveAccountSyncDebounceMs, getLiveAccountSyncPollMs, getNetworkErrorCooldownMs, getPerProjectAccounts, getPidOffsetEnabled, getPreemptiveQuotaEnabled, getPreemptiveQuotaMaxDeferralMs, getPreemptiveQuotaRemainingPercent5h, getPreemptiveQuotaRemainingPercent7d, getProactiveRefreshBufferMs, getProactiveRefreshGuardian, getProactiveRefreshIntervalMs, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getBackgroundResponses, getResponseContinuation, getServerErrorCooldownMs, getSessionAffinity, getSessionAffinityMaxEntries, getSessionAffinityTtlMs, getSessionRecovery, getStorageBackupEnabled, getStreamStallTimeoutMs, getToastDurationMs, getTokenRefreshSkewMs, getUnsupportedCodexFallbackChain, getUnsupportedCodexPolicy, loadPluginConfig, } from "./lib/config.js";
34
+ import { ACCOUNT_LIMITS, AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, } from "./lib/constants.js";
35
35
  import { handleContextOverflow } from "./lib/context-overflow.js";
36
- import { AccountManager, getAccountIdCandidates, extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, resolveRequestAccountId, parseRateLimitReason, lookupCodexCliTokensByEmail, isCodexCliSyncEnabled, } from "./lib/accounts.js";
37
- import { getStoragePath, loadAccounts, saveAccounts, withAccountStorageTransaction, clearAccounts, setStoragePath, exportAccounts, importAccounts, loadFlaggedAccounts, saveFlaggedAccounts, clearFlaggedAccounts, findMatchingAccountIndex, StorageError, formatStorageErrorHint, setStorageBackupEnabled, } from "./lib/storage.js";
38
- import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
39
- import { applyFastSessionDefaults } from "./lib/request/request-transformer.js";
40
- import { getRateLimitBackoff, RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS, resetRateLimitBackoff, } from "./lib/request/rate-limit-backoff.js";
41
- import { isEmptyResponse } from "./lib/request/response-handler.js";
42
- import { addJitter } from "./lib/rotation.js";
43
- import { SessionAffinityStore } from "./lib/session-affinity.js";
36
+ import { EntitlementCache, resolveEntitlementAccountKey, } from "./lib/entitlement-cache.js";
44
37
  import { LiveAccountSync } from "./lib/live-account-sync.js";
38
+ import { clearCorrelationId, initLogger, logDebug, logError, logInfo, logRequest, logWarn, setCorrelationId, } from "./lib/logger.js";
39
+ import { PreemptiveQuotaScheduler, readQuotaSchedulerSnapshot, } from "./lib/preemptive-quota-scheduler.js";
40
+ import { getModelFamily, MODEL_FAMILIES, prewarmCodexInstructions, } from "./lib/prompts/codex.js";
41
+ import { prewarmHostCodexPrompt } from "./lib/prompts/host-codex-prompt.js";
42
+ import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./lib/quota-probe.js";
43
+ import { detectErrorType, getRecoveryToastContent, isRecoverableError, } from "./lib/recovery.js";
45
44
  import { RefreshGuardian } from "./lib/refresh-guardian.js";
45
+ import { queuedRefresh } from "./lib/refresh-queue.js";
46
+ import { parseEnvInt, parseFailoverMode, } from "./lib/request/failover-config.js";
46
47
  import { evaluateFailurePolicy, } from "./lib/request/failure-policy.js";
47
- import { EntitlementCache, resolveEntitlementAccountKey, } from "./lib/entitlement-cache.js";
48
- import { PreemptiveQuotaScheduler, readQuotaSchedulerSnapshot, } from "./lib/preemptive-quota-scheduler.js";
49
- import { CapabilityPolicyStore } from "./lib/capability-policy.js";
48
+ import { applyProxyCompatibleInit, createCodexHeaders, extractRequestUrl, getUnsupportedCodexModelInfo, handleErrorResponse, handleSuccessResponse, isWorkspaceDisabledError, refreshAndUpdateToken, resolveUnsupportedCodexFallbackModel, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
49
+ import { getRateLimitBackoff, RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS, resetRateLimitBackoff, } from "./lib/request/rate-limit-backoff.js";
50
+ import { normalizeRequestInit, parseRequestBodyFromInit, } from "./lib/request/request-init.js";
51
+ import { applyFastSessionDefaults } from "./lib/request/request-transformer.js";
52
+ import { applyResponseCompaction } from "./lib/request/response-compaction.js";
53
+ import { isEmptyResponse } from "./lib/request/response-handler.js";
54
+ import { parseRetryAfterHintMs, sanitizeResponseHeadersForLog, } from "./lib/request/response-metadata.js";
50
55
  import { withStreamingFailover } from "./lib/request/stream-failover.js";
51
- import { buildTableHeader, buildTableRow } from "./lib/table-formatter.js";
52
- import { setUiRuntimeOptions } from "./lib/ui/runtime.js";
53
- import { paintUiText, formatUiBadge, formatUiHeader, formatUiItem, formatUiKeyValue, formatUiSection } from "./lib/ui/format.js";
54
- import { getModelFamily, getCodexInstructions, MODEL_FAMILIES, prewarmCodexInstructions, } from "./lib/prompts/codex.js";
55
- import { prewarmHostCodexPrompt } from "./lib/prompts/host-codex-prompt.js";
56
- import { createSessionRecoveryHook, isRecoverableError, detectErrorType, getRecoveryToastContent, } from "./lib/recovery.js";
57
- import { createHashlineEditTool, createHashlineReadTool, } from "./lib/tools/hashline-tools.js";
56
+ import { createAbortableSleep, sleepWithCountdown, } from "./lib/request/wait-utils.js";
57
+ import { addJitter } from "./lib/rotation.js";
58
+ import { clampActiveIndices, isFlaggableFailure, } from "./lib/runtime/account-check-helpers.js";
59
+ import { runRuntimeAccountCheck } from "./lib/runtime/account-check.js";
60
+ import { createAccountCheckWorkingState } from "./lib/runtime/account-check-types.js";
61
+ import { invalidateRuntimeAccountManagerCache, reloadRuntimeAccountManager, } from "./lib/runtime/account-manager-cache.js";
62
+ import { persistAccountPoolResults, } from "./lib/runtime/account-pool.js";
63
+ import { resolveAccountSelection } from "./lib/runtime/account-selection.js";
64
+ import { formatRateLimitEntry, getRateLimitResetTimeForFamily, resolveActiveIndex, } from "./lib/runtime/account-status.js";
65
+ import { createPersistAccounts, runRuntimeOAuthFlow, } from "./lib/runtime/auth-facade.js";
66
+ import { applyAccountStorageScopeEntry } from "./lib/runtime/account-storage-scope-entry.js";
67
+ import { runBrowserOAuthFlow } from "./lib/runtime/browser-oauth-flow.js";
68
+ import { handleRuntimeEvent } from "./lib/runtime/event-handler.js";
69
+ import { hydrateRuntimeEmails } from "./lib/runtime/hydrate-emails.js";
70
+ import { buildLoginMenuAccounts } from "./lib/runtime/login-menu-accounts.js";
71
+ import { ensureLiveAccountSyncEntry } from "./lib/runtime/live-sync-entry.js";
72
+ import { applyLoaderRuntimeSetup } from "./lib/runtime/loader-setup.js";
73
+ import { buildManualOAuthFlow } from "./lib/runtime/manual-oauth-flow.js";
74
+ import { applyPreemptiveQuotaSettingsFromConfig, } from "./lib/runtime/quota-settings.js";
75
+ import { ensureLiveAccountSyncState, ensureRefreshGuardianState, ensureSessionAffinityState, } from "./lib/runtime/runtime-services.js";
76
+ import { applyAccountStorageScopeFromConfig } from "./lib/runtime/storage-scope.js";
77
+ import { showRuntimeToast } from "./lib/runtime/toast.js";
78
+ import { createRuntimeSessionRecoveryHook } from "./lib/runtime/session-recovery.js";
79
+ import { applyUiRuntimeFromConfig, getStatusMarker, } from "./lib/runtime/ui-runtime.js";
80
+ import { verifyRuntimeFlaggedAccounts } from "./lib/runtime/verify-flagged.js";
81
+ import { SessionAffinityStore } from "./lib/session-affinity.js";
58
82
  import { registerCleanup } from "./lib/shutdown.js";
83
+ import { clearAccounts, clearFlaggedAccounts, exportAccounts, findMatchingAccountIndex, formatStorageErrorHint, getStoragePath, importAccounts, loadAccounts, loadFlaggedAccounts, StorageError, saveAccounts, saveFlaggedAccounts, setStorageBackupEnabled, setStoragePath, withAccountStorageTransaction, } from "./lib/storage.js";
84
+ import { buildTableHeader, buildTableRow, } from "./lib/table-formatter.js";
85
+ import { createHashlineEditTool, createHashlineReadTool, } from "./lib/tools/hashline-tools.js";
86
+ import { formatUiBadge, formatUiHeader, formatUiItem, formatUiKeyValue, formatUiSection, paintUiText, } from "./lib/ui/format.js";
87
+ import { setUiRuntimeOptions, } from "./lib/ui/runtime.js";
59
88
  /**
60
89
  * OpenAI Codex OAuth authentication plugin for Codex CLI host runtime
61
90
  *
@@ -85,6 +114,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
85
114
  let liveAccountSyncPath = null;
86
115
  let refreshGuardian = null;
87
116
  let refreshGuardianConfigKey = null;
117
+ let refreshGuardianCleanupRegistered = false;
88
118
  let sessionAffinityStore = new SessionAffinityStore();
89
119
  let sessionAffinityConfigKey = null;
90
120
  const entitlementCache = new EntitlementCache();
@@ -103,80 +133,6 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
103
133
  balanced: 15_000,
104
134
  conservative: 20_000,
105
135
  };
106
- const parseFailoverMode = (value) => {
107
- const normalized = (value ?? "").trim().toLowerCase();
108
- if (normalized === "aggressive")
109
- return "aggressive";
110
- if (normalized === "conservative")
111
- return "conservative";
112
- return "balanced";
113
- };
114
- const parseEnvInt = (value) => {
115
- if (value === undefined)
116
- return undefined;
117
- const parsed = Number.parseInt(value, 10);
118
- return Number.isFinite(parsed) ? parsed : undefined;
119
- };
120
- const MAX_RETRY_HINT_MS = 5 * 60 * 1000;
121
- const clampRetryHintMs = (value) => {
122
- if (!Number.isFinite(value))
123
- return null;
124
- const normalized = Math.floor(value);
125
- if (normalized <= 0)
126
- return null;
127
- return Math.min(normalized, MAX_RETRY_HINT_MS);
128
- };
129
- const parseRetryAfterHintMs = (headers) => {
130
- const retryAfterMsHeader = headers.get("retry-after-ms")?.trim();
131
- if (retryAfterMsHeader && /^\d+$/.test(retryAfterMsHeader)) {
132
- return clampRetryHintMs(Number.parseInt(retryAfterMsHeader, 10));
133
- }
134
- const retryAfterHeader = headers.get("retry-after")?.trim();
135
- if (retryAfterHeader && /^\d+$/.test(retryAfterHeader)) {
136
- return clampRetryHintMs(Number.parseInt(retryAfterHeader, 10) * 1000);
137
- }
138
- if (retryAfterHeader) {
139
- const retryAtMs = Date.parse(retryAfterHeader);
140
- if (Number.isFinite(retryAtMs)) {
141
- return clampRetryHintMs(retryAtMs - Date.now());
142
- }
143
- }
144
- const resetAtHeader = headers.get("x-ratelimit-reset")?.trim();
145
- if (resetAtHeader && /^\d+$/.test(resetAtHeader)) {
146
- const resetRaw = Number.parseInt(resetAtHeader, 10);
147
- const resetAtMs = resetRaw < 10_000_000_000 ? resetRaw * 1000 : resetRaw;
148
- return clampRetryHintMs(resetAtMs - Date.now());
149
- }
150
- return null;
151
- };
152
- const sanitizeResponseHeadersForLog = (headers) => {
153
- const allowed = new Set([
154
- "content-type",
155
- "x-request-id",
156
- "x-openai-request-id",
157
- "x-codex-plan-type",
158
- "x-codex-active-limit",
159
- "x-codex-primary-used-percent",
160
- "x-codex-primary-window-minutes",
161
- "x-codex-primary-reset-at",
162
- "x-codex-primary-reset-after-seconds",
163
- "x-codex-secondary-used-percent",
164
- "x-codex-secondary-window-minutes",
165
- "x-codex-secondary-reset-at",
166
- "x-codex-secondary-reset-after-seconds",
167
- "retry-after",
168
- "x-ratelimit-reset",
169
- "x-ratelimit-reset-requests",
170
- ]);
171
- const sanitized = {};
172
- for (const [rawName, rawValue] of headers.entries()) {
173
- const name = rawName.toLowerCase();
174
- if (!allowed.has(name))
175
- continue;
176
- sanitized[name] = rawValue;
177
- }
178
- return sanitized;
179
- };
180
136
  const runtimeMetrics = {
181
137
  startedAt: Date.now(),
182
138
  totalRequests: 0,
@@ -197,500 +153,145 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
197
153
  lastRequestAt: null,
198
154
  lastError: null,
199
155
  };
200
- const resolveAccountSelection = (tokens) => {
201
- const override = (process.env.CODEX_AUTH_ACCOUNT_ID ?? "").trim();
202
- if (override) {
203
- const suffix = override.length > 6 ? override.slice(-6) : override;
204
- logInfo(`Using account override from CODEX_AUTH_ACCOUNT_ID (id:${suffix}).`);
205
- return {
206
- ...tokens,
207
- accountIdOverride: override,
208
- accountIdSource: "manual",
209
- accountLabel: `Override [id:${suffix}]`,
210
- };
211
- }
212
- const candidates = getAccountIdCandidates(tokens.access, tokens.idToken);
213
- if (candidates.length === 0) {
214
- return tokens;
215
- }
216
- if (candidates.length === 1) {
217
- const [candidate] = candidates;
218
- if (candidate) {
219
- return {
220
- ...tokens,
221
- accountIdOverride: candidate.accountId,
222
- accountIdSource: candidate.source,
223
- accountLabel: candidate.label,
224
- };
225
- }
226
- }
227
- // Auto-select the best workspace candidate without prompting.
228
- // This honors org/default/id-token signals and avoids forcing personal token IDs.
229
- const choice = selectBestAccountCandidate(candidates);
230
- if (!choice)
231
- return tokens;
232
- return {
233
- ...tokens,
234
- accountIdOverride: choice.accountId,
235
- accountIdSource: choice.source ?? "token",
236
- accountLabel: choice.label,
237
- };
156
+ const resolveTokenSuccessAccount = (tokens) => resolveAccountSelection(tokens, {
157
+ envAccountId: process.env.CODEX_AUTH_ACCOUNT_ID,
158
+ logInfo,
159
+ getAccountIdCandidates,
160
+ selectBestAccountCandidate,
161
+ });
162
+ const persistAccountPool = createPersistAccounts({
163
+ persistAccountPoolResults,
164
+ withAccountStorageTransaction,
165
+ extractAccountId,
166
+ extractAccountEmail,
167
+ sanitizeEmail,
168
+ findMatchingAccountIndex,
169
+ modelFamilies: MODEL_FAMILIES,
170
+ });
171
+ const accountManagerCacheInvalidationDeps = {
172
+ setCachedAccountManager: (value) => {
173
+ cachedAccountManager = value;
174
+ },
175
+ setAccountManagerPromise: (value) => {
176
+ accountManagerPromise = value;
177
+ },
238
178
  };
239
- const buildManualOAuthFlow = (pkce, url, expectedState, onSuccess) => ({
240
- url,
241
- method: "code",
242
- instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
243
- validate: (input) => {
244
- const parsed = parseAuthorizationInput(input);
245
- if (!parsed.code) {
246
- return `No authorization code found. Paste the full callback URL (e.g., ${REDIRECT_URI}?code=...)`;
247
- }
248
- if (!parsed.state) {
249
- return "Missing OAuth state. Paste the full callback URL including both code and state parameters.";
250
- }
251
- if (parsed.state !== expectedState) {
252
- return "OAuth state mismatch. Restart login and paste the callback URL generated for this login attempt.";
253
- }
254
- return undefined;
179
+ const makeReloadAccountManagerDeps = (authFallback) => ({
180
+ currentReloadInFlight: accountReloadInFlight,
181
+ loadFromDisk: (fallback) => AccountManager.loadFromDisk(fallback),
182
+ setCachedAccountManager: (value) => {
183
+ cachedAccountManager = value;
255
184
  },
256
- callback: async (input) => {
257
- const parsed = parseAuthorizationInput(input);
258
- if (!parsed.code || !parsed.state) {
259
- return {
260
- type: "failed",
261
- reason: "invalid_response",
262
- message: "Missing authorization code or OAuth state",
263
- };
264
- }
265
- if (parsed.state !== expectedState) {
266
- return {
267
- type: "failed",
268
- reason: "invalid_response",
269
- message: "OAuth state mismatch. Restart login and try again.",
270
- };
271
- }
272
- const tokens = await exchangeAuthorizationCode(parsed.code, pkce.verifier, REDIRECT_URI);
273
- if (tokens?.type === "success") {
274
- const resolved = resolveAccountSelection(tokens);
275
- if (onSuccess) {
276
- await onSuccess(resolved);
277
- }
278
- return resolved;
279
- }
280
- return tokens?.type === "failed"
281
- ? tokens
282
- : { type: "failed" };
185
+ setAccountManagerPromise: (value) => {
186
+ accountManagerPromise = value;
283
187
  },
188
+ setReloadInFlight: (value) => {
189
+ accountReloadInFlight = value;
190
+ },
191
+ authFallback,
192
+ });
193
+ const reloadAccountManagerFromDisk = async (authFallback) => reloadRuntimeAccountManager(makeReloadAccountManagerDeps(authFallback));
194
+ const applyAccountStorageScope = (pluginConfig) => applyAccountStorageScopeEntry({
195
+ pluginConfig,
196
+ getPerProjectAccounts,
197
+ getStorageBackupEnabled,
198
+ setStorageBackupEnabled,
199
+ isCodexCliSyncEnabled,
200
+ getWarningShown: () => perProjectStorageWarningShown,
201
+ setWarningShown: (shown) => {
202
+ perProjectStorageWarningShown = shown;
203
+ },
204
+ logWarn,
205
+ pluginName: PLUGIN_NAME,
206
+ setStoragePath,
207
+ cwd: () => process.cwd(),
208
+ applyAccountStorageScopeFromConfig,
284
209
  });
285
- const runOAuthFlow = async (forceNewLogin = false) => {
286
- const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
287
- logInfo(`OAuth URL: ${redactOAuthUrlForLog(url)}`);
288
- let serverInfo = null;
289
- try {
290
- serverInfo = await startLocalOAuthServer({ state });
291
- }
292
- catch (err) {
293
- logDebug(`[${PLUGIN_NAME}] Failed to start OAuth server: ${err?.message ?? String(err)}`);
294
- serverInfo = null;
295
- }
296
- openBrowserUrl(url);
297
- if (!serverInfo || !serverInfo.ready) {
298
- serverInfo?.close();
299
- const message = `\n[${PLUGIN_NAME}] OAuth callback server failed to start. ` +
300
- `Please retry with "${AUTH_LABELS.OAUTH_MANUAL}".\n`;
301
- logWarn(message);
302
- return { type: "failed" };
303
- }
304
- const result = await serverInfo.waitForCode(state);
305
- serverInfo.close();
306
- if (!result) {
307
- return { type: "failed", reason: "unknown", message: "OAuth callback timeout or cancelled" };
308
- }
309
- return await exchangeAuthorizationCode(result.code, pkce.verifier, REDIRECT_URI);
310
- };
311
- const persistAccountPool = async (results, replaceAll = false) => {
312
- if (results.length === 0)
313
- return;
314
- await withAccountStorageTransaction(async (loadedStorage, persist) => {
315
- const now = Date.now();
316
- const stored = replaceAll ? null : loadedStorage;
317
- const accounts = stored?.accounts ? [...stored.accounts] : [];
318
- for (const result of results) {
319
- const accountId = result.accountIdOverride ?? extractAccountId(result.access);
320
- const accountIdSource = accountId
321
- ? result.accountIdSource ??
322
- (result.accountIdOverride ? "manual" : "token")
323
- : undefined;
324
- const accountLabel = result.accountLabel;
325
- const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
326
- const existingIndex = findMatchingAccountIndex(accounts, {
327
- accountId,
328
- email: accountEmail,
329
- refreshToken: result.refresh,
330
- }, {
331
- allowUniqueAccountIdFallbackWithoutEmail: true,
332
- });
333
- if (existingIndex === undefined) {
334
- accounts.push({
335
- accountId,
336
- accountIdSource,
337
- accountLabel,
338
- email: accountEmail,
339
- refreshToken: result.refresh,
340
- accessToken: result.access,
341
- expiresAt: result.expires,
342
- addedAt: now,
343
- lastUsed: now,
344
- });
345
- continue;
346
- }
347
- const existing = accounts[existingIndex];
348
- if (!existing)
349
- continue;
350
- const nextEmail = accountEmail ?? sanitizeEmail(existing.email);
351
- const nextAccountId = accountId ?? existing.accountId;
352
- const nextAccountIdSource = accountId ? accountIdSource ?? existing.accountIdSource : existing.accountIdSource;
353
- const nextAccountLabel = accountLabel ?? existing.accountLabel;
354
- accounts[existingIndex] = {
355
- ...existing,
356
- accountId: nextAccountId,
357
- accountIdSource: nextAccountIdSource,
358
- accountLabel: nextAccountLabel,
359
- email: nextEmail,
360
- refreshToken: result.refresh,
361
- accessToken: result.access,
362
- expiresAt: result.expires,
363
- lastUsed: now,
364
- };
365
- }
366
- if (accounts.length === 0)
367
- return;
368
- const activeIndex = replaceAll
369
- ? 0
370
- : typeof stored?.activeIndex === "number" && Number.isFinite(stored.activeIndex)
371
- ? stored.activeIndex
372
- : 0;
373
- const clampedActiveIndex = Math.max(0, Math.min(activeIndex, accounts.length - 1));
374
- const activeIndexByFamily = {};
375
- for (const family of MODEL_FAMILIES) {
376
- const storedFamilyIndex = stored?.activeIndexByFamily?.[family];
377
- const rawFamilyIndex = replaceAll
378
- ? 0
379
- : typeof storedFamilyIndex === "number" && Number.isFinite(storedFamilyIndex)
380
- ? storedFamilyIndex
381
- : clampedActiveIndex;
382
- activeIndexByFamily[family] = Math.max(0, Math.min(Math.floor(rawFamilyIndex), accounts.length - 1));
383
- }
384
- await persist({
385
- version: 3,
386
- accounts,
387
- activeIndex: clampedActiveIndex,
388
- activeIndexByFamily,
389
- });
390
- });
391
- };
392
- const showToast = async (message, variant = "success", options) => {
393
- try {
394
- await client.tui.showToast({
395
- body: {
396
- message,
397
- variant,
398
- ...(options?.title && { title: options.title }),
399
- ...(options?.duration && { duration: options.duration }),
400
- },
401
- });
402
- }
403
- catch {
404
- // Ignore when TUI is not available.
405
- }
406
- };
407
- const resolveActiveIndex = (storage, family = "codex") => {
408
- const total = storage.accounts.length;
409
- if (total === 0)
410
- return 0;
411
- const rawCandidate = storage.activeIndexByFamily?.[family] ?? storage.activeIndex;
412
- const raw = Number.isFinite(rawCandidate) ? rawCandidate : 0;
413
- return Math.max(0, Math.min(raw, total - 1));
414
- };
415
- const hydrateEmails = async (storage) => {
416
- if (!storage)
417
- return storage;
418
- const skipHydrate = process.env.VITEST_WORKER_ID !== undefined ||
419
- process.env.NODE_ENV === "test" ||
420
- process.env.CODEX_SKIP_EMAIL_HYDRATE === "1";
421
- if (skipHydrate)
422
- return storage;
423
- const accountsCopy = storage.accounts.map((account) => account ? { ...account } : account);
424
- const accountsToHydrate = accountsCopy.filter((account) => account && !account.email);
425
- if (accountsToHydrate.length === 0)
426
- return storage;
427
- let changed = false;
428
- await Promise.all(accountsToHydrate.map(async (account) => {
429
- try {
430
- const refreshed = await queuedRefresh(account.refreshToken);
431
- if (refreshed.type !== "success")
432
- return;
433
- const id = extractAccountId(refreshed.access);
434
- const email = sanitizeEmail(extractAccountEmail(refreshed.access, refreshed.idToken));
435
- if (id &&
436
- id !== account.accountId &&
437
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId)) {
438
- account.accountId = id;
439
- account.accountIdSource = "token";
440
- changed = true;
441
- }
442
- if (email && email !== account.email) {
443
- account.email = email;
444
- changed = true;
445
- }
446
- if (refreshed.access && refreshed.access !== account.accessToken) {
447
- account.accessToken = refreshed.access;
448
- changed = true;
449
- }
450
- if (typeof refreshed.expires === "number" && refreshed.expires !== account.expiresAt) {
451
- account.expiresAt = refreshed.expires;
452
- changed = true;
453
- }
454
- if (refreshed.refresh && refreshed.refresh !== account.refreshToken) {
455
- account.refreshToken = refreshed.refresh;
456
- changed = true;
457
- }
458
- }
459
- catch {
460
- logWarn(`[${PLUGIN_NAME}] Failed to hydrate email for account`);
461
- }
462
- }));
463
- if (changed) {
464
- storage.accounts = accountsCopy;
465
- await saveAccounts(storage);
466
- }
467
- return storage;
468
- };
469
- const getRateLimitResetTimeForFamily = (account, now, family) => {
470
- const times = account.rateLimitResetTimes;
471
- if (!times)
472
- return null;
473
- let minReset = null;
474
- const prefix = `${family}:`;
475
- for (const [key, value] of Object.entries(times)) {
476
- if (typeof value !== "number")
477
- continue;
478
- if (value <= now)
479
- continue;
480
- if (key !== family && !key.startsWith(prefix))
481
- continue;
482
- if (minReset === null || value < minReset) {
483
- minReset = value;
484
- }
485
- }
486
- return minReset;
487
- };
488
- const formatRateLimitEntry = (account, now, family = "codex") => {
489
- const resetAt = getRateLimitResetTimeForFamily(account, now, family);
490
- if (typeof resetAt !== "number")
491
- return null;
492
- const remaining = resetAt - now;
493
- if (remaining <= 0)
494
- return null;
495
- return `resets in ${formatWaitTime(remaining)}`;
496
- };
497
- const applyUiRuntimeFromConfig = (pluginConfig) => {
498
- return setUiRuntimeOptions({
499
- v2Enabled: getCodexTuiV2(pluginConfig),
500
- colorProfile: getCodexTuiColorProfile(pluginConfig),
501
- glyphMode: getCodexTuiGlyphMode(pluginConfig),
502
- });
503
- };
504
- const resolveUiRuntime = () => {
505
- return applyUiRuntimeFromConfig(loadPluginConfig());
506
- };
507
- const getStatusMarker = (ui, status) => {
508
- if (!ui.v2Enabled) {
509
- if (status === "ok")
510
- return "✓";
511
- if (status === "warning")
512
- return "!";
513
- return "✗";
514
- }
515
- if (status === "ok")
516
- return ui.theme.glyphs.check;
517
- if (status === "warning")
518
- return "!";
519
- return ui.theme.glyphs.cross;
520
- };
521
- const invalidateAccountManagerCache = () => {
522
- cachedAccountManager = null;
523
- accountManagerPromise = null;
524
- };
525
- const reloadAccountManagerFromDisk = async (authFallback) => {
526
- if (accountReloadInFlight) {
527
- return accountReloadInFlight;
528
- }
529
- accountReloadInFlight = (async () => {
530
- const reloaded = await AccountManager.loadFromDisk(authFallback);
531
- cachedAccountManager = reloaded;
532
- accountManagerPromise = Promise.resolve(reloaded);
533
- return reloaded;
534
- })();
535
- try {
536
- return await accountReloadInFlight;
537
- }
538
- finally {
539
- accountReloadInFlight = null;
540
- }
541
- };
542
- const applyAccountStorageScope = (pluginConfig) => {
543
- const perProjectAccounts = getPerProjectAccounts(pluginConfig);
544
- setStorageBackupEnabled(getStorageBackupEnabled(pluginConfig));
545
- if (isCodexCliSyncEnabled()) {
546
- if (perProjectAccounts && !perProjectStorageWarningShown) {
547
- perProjectStorageWarningShown = true;
548
- logWarn(`[${PLUGIN_NAME}] CODEX_AUTH_PER_PROJECT_ACCOUNTS is ignored while Codex CLI sync is enabled. Using global account storage.`);
549
- }
550
- setStoragePath(null);
551
- return;
552
- }
553
- setStoragePath(perProjectAccounts ? process.cwd() : null);
554
- };
555
210
  const ensureLiveAccountSync = async (pluginConfig, authFallback) => {
556
- if (!getLiveAccountSync(pluginConfig)) {
557
- if (liveAccountSync) {
558
- liveAccountSync.stop();
559
- liveAccountSync = null;
560
- liveAccountSyncPath = null;
561
- }
562
- return;
563
- }
564
- const targetPath = getStoragePath();
565
- if (!liveAccountSync) {
566
- liveAccountSync = new LiveAccountSync(async () => {
567
- await reloadAccountManagerFromDisk(authFallback);
211
+ const next = await ensureLiveAccountSyncEntry({
212
+ pluginConfig,
213
+ authFallback,
214
+ currentSync: liveAccountSync,
215
+ currentPath: liveAccountSyncPath,
216
+ getLiveAccountSync,
217
+ getStoragePath,
218
+ createSync: (oauthFallback) => new LiveAccountSync(async () => {
219
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(oauthFallback));
568
220
  }, {
569
221
  debounceMs: getLiveAccountSyncDebounceMs(pluginConfig),
570
222
  pollIntervalMs: getLiveAccountSyncPollMs(pluginConfig),
571
- });
572
- registerCleanup(() => {
573
- liveAccountSync?.stop();
574
- });
575
- }
576
- if (liveAccountSyncPath !== targetPath) {
577
- let switched = false;
578
- for (let attempt = 0; attempt < 3; attempt += 1) {
579
- try {
580
- await liveAccountSync.syncToPath(targetPath);
581
- liveAccountSyncPath = targetPath;
582
- switched = true;
583
- break;
584
- }
585
- catch (error) {
586
- const code = error?.code;
587
- if (code !== "EBUSY" && code !== "EPERM") {
588
- throw error;
589
- }
590
- await new Promise((resolve) => setTimeout(resolve, 25 * 2 ** attempt));
591
- }
592
- }
593
- if (!switched) {
594
- logWarn(`[${PLUGIN_NAME}] Live account sync path switch failed due to transient filesystem locks; keeping previous watcher.`);
595
- }
596
- }
223
+ }),
224
+ registerCleanup,
225
+ logWarn,
226
+ pluginName: PLUGIN_NAME,
227
+ ensureLiveAccountSyncState,
228
+ });
229
+ liveAccountSync = next.liveAccountSync;
230
+ liveAccountSyncPath = next.liveAccountSyncPath;
597
231
  };
598
232
  const ensureRefreshGuardian = (pluginConfig) => {
599
- if (!getProactiveRefreshGuardian(pluginConfig)) {
600
- if (refreshGuardian) {
601
- refreshGuardian.stop();
602
- refreshGuardian = null;
603
- refreshGuardianConfigKey = null;
604
- }
605
- return;
606
- }
607
- const intervalMs = getProactiveRefreshIntervalMs(pluginConfig);
608
- const bufferMs = getProactiveRefreshBufferMs(pluginConfig);
609
- const configKey = `${intervalMs}:${bufferMs}`;
610
- if (refreshGuardian && refreshGuardianConfigKey === configKey)
611
- return;
612
- if (refreshGuardian) {
613
- refreshGuardian.stop();
614
- }
615
- refreshGuardian = new RefreshGuardian(() => cachedAccountManager, { intervalMs, bufferMs });
616
- refreshGuardianConfigKey = configKey;
617
- refreshGuardian.start();
618
- registerCleanup(() => {
619
- refreshGuardian?.stop();
233
+ const next = ensureRefreshGuardianState({
234
+ enabled: getProactiveRefreshGuardian(pluginConfig),
235
+ intervalMs: getProactiveRefreshIntervalMs(pluginConfig),
236
+ bufferMs: getProactiveRefreshBufferMs(pluginConfig),
237
+ currentGuardian: refreshGuardian,
238
+ currentConfigKey: refreshGuardianConfigKey,
239
+ currentCleanupRegistered: refreshGuardianCleanupRegistered,
240
+ getCurrentGuardian: () => refreshGuardian,
241
+ createGuardian: ({ intervalMs, bufferMs }) => new RefreshGuardian(() => cachedAccountManager, {
242
+ intervalMs,
243
+ bufferMs,
244
+ }),
245
+ registerCleanup,
620
246
  });
247
+ refreshGuardian = next.refreshGuardian;
248
+ refreshGuardianConfigKey = next.refreshGuardianConfigKey;
249
+ refreshGuardianCleanupRegistered =
250
+ next.refreshGuardianCleanupRegistered;
621
251
  };
622
252
  const ensureSessionAffinity = (pluginConfig) => {
623
- if (!getSessionAffinity(pluginConfig)) {
624
- sessionAffinityStore = null;
625
- sessionAffinityConfigKey = null;
626
- return;
627
- }
628
- const ttlMs = getSessionAffinityTtlMs(pluginConfig);
629
- const maxEntries = getSessionAffinityMaxEntries(pluginConfig);
630
- const configKey = `${ttlMs}:${maxEntries}`;
631
- if (sessionAffinityStore && sessionAffinityConfigKey === configKey)
632
- return;
633
- sessionAffinityStore = new SessionAffinityStore({ ttlMs, maxEntries });
634
- sessionAffinityConfigKey = configKey;
635
- };
636
- const applyPreemptiveQuotaSettings = (pluginConfig) => {
637
- preemptiveQuotaScheduler.configure({
638
- enabled: getPreemptiveQuotaEnabled(pluginConfig),
639
- remainingPercentThresholdPrimary: getPreemptiveQuotaRemainingPercent5h(pluginConfig),
640
- remainingPercentThresholdSecondary: getPreemptiveQuotaRemainingPercent7d(pluginConfig),
641
- maxDeferralMs: getPreemptiveQuotaMaxDeferralMs(pluginConfig),
253
+ const next = ensureSessionAffinityState({
254
+ enabled: getSessionAffinity(pluginConfig) ||
255
+ getResponseContinuation(pluginConfig),
256
+ ttlMs: getSessionAffinityTtlMs(pluginConfig),
257
+ maxEntries: getSessionAffinityMaxEntries(pluginConfig),
258
+ currentStore: sessionAffinityStore,
259
+ currentConfigKey: sessionAffinityConfigKey,
260
+ createStore: ({ ttlMs, maxEntries }) => new SessionAffinityStore({ ttlMs, maxEntries }),
642
261
  });
262
+ sessionAffinityStore = next.sessionAffinityStore;
263
+ sessionAffinityConfigKey = next.sessionAffinityConfigKey;
643
264
  };
265
+ const applyPreemptiveQuotaSettings = (pluginConfig) => applyPreemptiveQuotaSettingsFromConfig(pluginConfig, {
266
+ configure: (options) => preemptiveQuotaScheduler.configure(options),
267
+ getPreemptiveQuotaEnabled,
268
+ getPreemptiveQuotaRemainingPercent5h,
269
+ getPreemptiveQuotaRemainingPercent7d,
270
+ getPreemptiveQuotaMaxDeferralMs,
271
+ });
644
272
  // Event handler for session recovery and account selection
645
- const eventHandler = async (input) => {
646
- try {
647
- const { event } = input;
648
- // Handle TUI account selection events
649
- // Accepts generic selection events with an index property
650
- if (event.type === "account.select" ||
651
- event.type === "openai.account.select") {
652
- const props = event.properties;
653
- // Filter by provider if specified
654
- if (props.provider && props.provider !== "openai" && props.provider !== PROVIDER_ID) {
655
- return;
656
- }
657
- const index = props.index ?? props.accountIndex;
658
- if (typeof index === "number") {
659
- const storage = await loadAccounts();
660
- if (!storage || index < 0 || index >= storage.accounts.length) {
661
- return;
662
- }
663
- const now = Date.now();
664
- const account = storage.accounts[index];
665
- if (account) {
666
- account.lastUsed = now;
667
- account.lastSwitchReason = "rotation";
668
- }
669
- storage.activeIndex = index;
670
- storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
671
- for (const family of MODEL_FAMILIES) {
672
- storage.activeIndexByFamily[family] = index;
673
- }
674
- await saveAccounts(storage);
675
- if (cachedAccountManager) {
676
- await cachedAccountManager.syncCodexCliActiveSelectionForIndex(index);
677
- }
678
- lastCodexCliActiveSyncIndex = index;
679
- // Reload manager from disk so we don't overwrite newer rotated
680
- // refresh tokens with stale in-memory state.
681
- if (cachedAccountManager) {
682
- await reloadAccountManagerFromDisk();
683
- }
684
- await showToast(`Switched to account ${index + 1}`, "info");
685
- }
686
- }
687
- }
688
- catch (error) {
689
- logDebug(`[${PLUGIN_NAME}] Event handler error: ${error instanceof Error ? error.message : String(error)}`);
690
- }
691
- };
273
+ const eventHandler = async (input) => handleRuntimeEvent({
274
+ input,
275
+ providerId: PROVIDER_ID,
276
+ modelFamilies: MODEL_FAMILIES,
277
+ loadAccounts,
278
+ saveAccounts,
279
+ hasCachedAccountManager: () => !!cachedAccountManager,
280
+ syncCodexCliActiveSelectionForIndex: async (index) => {
281
+ if (!cachedAccountManager)
282
+ return;
283
+ await cachedAccountManager.syncCodexCliActiveSelectionForIndex(index);
284
+ },
285
+ setLastCodexCliActiveSyncIndex: (index) => {
286
+ lastCodexCliActiveSyncIndex = index;
287
+ },
288
+ reloadAccountManagerFromDisk: () => reloadAccountManagerFromDisk(),
289
+ showToast: (message, variant) => showRuntimeToast(client, message, variant),
290
+ logDebug,
291
+ pluginName: PLUGIN_NAME,
292
+ });
692
293
  // Initialize runtime UI settings once on plugin load; auth/tools refresh this dynamically.
693
- resolveUiRuntime();
294
+ applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
694
295
  return {
695
296
  event: eventHandler,
696
297
  auth: {
@@ -699,11 +300,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
699
300
  * Loader function that configures OAuth authentication and request handling
700
301
  *
701
302
  * This function:
702
- * 1. Validates OAuth authentication
703
- * 2. Loads multi-account pool from disk (fallback to current auth)
704
- * 3. Loads user configuration from runtime model config
705
- * 4. Fetches Codex system instructions from GitHub (cached)
706
- * 5. Returns SDK configuration with custom fetch implementation
303
+ * 1. Validates OAuth authentication
304
+ * 2. Loads multi-account pool from disk (fallback to current auth)
305
+ * 3. Loads user configuration from runtime model config
306
+ * 4. Fetches Codex system instructions from GitHub (cached)
307
+ * 5. Returns SDK configuration with custom fetch implementation
707
308
  *
708
309
  * @param getAuth - Function to retrieve current auth state
709
310
  * @param provider - Provider configuration from runtime model config
@@ -712,11 +313,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
712
313
  async loader(getAuth, provider) {
713
314
  const auth = await getAuth();
714
315
  const pluginConfig = loadPluginConfig();
715
- applyUiRuntimeFromConfig(pluginConfig);
716
- applyAccountStorageScope(pluginConfig);
717
- ensureSessionAffinity(pluginConfig);
718
- ensureRefreshGuardian(pluginConfig);
719
- applyPreemptiveQuotaSettings(pluginConfig);
316
+ applyLoaderRuntimeSetup({
317
+ pluginConfig,
318
+ applyUiRuntimeFromConfig: (config) => applyUiRuntimeFromConfig(config, setUiRuntimeOptions),
319
+ applyAccountStorageScope,
320
+ ensureSessionAffinity,
321
+ ensureRefreshGuardian,
322
+ applyPreemptiveQuotaSettings,
323
+ });
720
324
  // Only handle OAuth auth type, skip API key auth
721
325
  if (auth.type !== "oauth") {
722
326
  return {};
@@ -740,15 +344,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
740
344
  try {
741
345
  await ensureLiveAccountSync(pluginConfig, auth);
742
346
  if (!accountManagerPromise) {
743
- await reloadAccountManagerFromDisk(auth);
347
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
744
348
  }
745
349
  const managerPromise = accountManagerPromise ??
746
- reloadAccountManagerFromDisk(auth);
350
+ reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
747
351
  let accountManager = await managerPromise;
748
352
  cachedAccountManager = accountManager;
749
353
  const refreshToken = auth.type === "oauth" ? auth.refresh : "";
750
- const needsPersist = refreshToken &&
751
- !accountManager.hasRefreshToken(refreshToken);
354
+ const needsPersist = refreshToken && !accountManager.hasRefreshToken(refreshToken);
752
355
  if (needsPersist) {
753
356
  await accountManager.saveToDisk();
754
357
  }
@@ -789,7 +392,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
789
392
  STREAM_FAILOVER_SOFT_TIMEOUT_BY_MODE[failoverMode]);
790
393
  const streamFailoverHardTimeoutMs = Math.max(streamFailoverSoftTimeoutMs, parseEnvInt(process.env.CODEX_AUTH_STREAM_STALL_HARD_TIMEOUT_MS) ??
791
394
  streamStallTimeoutMs);
792
- const maxSameAccountRetries = failoverMode === "conservative" ? 2 : failoverMode === "balanced" ? 1 : 0;
395
+ const maxSameAccountRetries = failoverMode === "conservative"
396
+ ? 2
397
+ : failoverMode === "balanced"
398
+ ? 1
399
+ : 0;
793
400
  const sessionRecoveryEnabled = getSessionRecovery(pluginConfig);
794
401
  const autoResumeEnabled = getAutoResume(pluginConfig);
795
402
  const emptyResponseMaxRetries = getEmptyResponseMaxRetries(pluginConfig);
@@ -818,11 +425,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
818
425
  prewarmHostCodexPrompt();
819
426
  }
820
427
  }
821
- const recoveryHook = sessionRecoveryEnabled
822
- ? createSessionRecoveryHook({ client, directory: process.cwd() }, { sessionRecovery: true, autoResume: autoResumeEnabled })
823
- : null;
428
+ const recoveryHook = createRuntimeSessionRecoveryHook({
429
+ enabled: sessionRecoveryEnabled,
430
+ client,
431
+ directory: process.cwd(),
432
+ autoResume: autoResumeEnabled,
433
+ });
824
434
  checkAndNotify(async (message, variant) => {
825
- await showToast(message, variant);
435
+ await showRuntimeToast(client, message, variant);
826
436
  }).catch((err) => {
827
437
  logDebug(`Update check failed: ${err instanceof Error ? err.message : String(err)}`);
828
438
  });
@@ -847,7 +457,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
847
457
  */
848
458
  async fetch(input, init) {
849
459
  try {
850
- if (cachedAccountManager && cachedAccountManager !== accountManager) {
460
+ if (cachedAccountManager &&
461
+ cachedAccountManager !== accountManager) {
851
462
  accountManager = cachedAccountManager;
852
463
  }
853
464
  // Step 1: Extract and rewrite URL for Codex backend
@@ -857,118 +468,58 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
857
468
  // Instructions are fetched per model family (codex-max, codex, gpt-5.1)
858
469
  // Capture original stream value before transformation
859
470
  // generateText() sends no stream field, streamText() sends stream=true
860
- const normalizeRequestInit = async (requestInput, requestInit) => {
861
- if (requestInit)
862
- return requestInit;
863
- if (!(requestInput instanceof Request))
864
- return requestInit;
865
- const method = requestInput.method || "GET";
866
- const normalized = {
867
- method,
868
- headers: new Headers(requestInput.headers),
869
- };
870
- if (method !== "GET" && method !== "HEAD") {
871
- try {
872
- const bodyText = await requestInput.clone().text();
873
- if (bodyText) {
874
- normalized.body = bodyText;
875
- }
876
- }
877
- catch {
878
- // Body may be unreadable; proceed without it.
879
- }
880
- }
881
- return normalized;
882
- };
883
- const parseRequestBodyFromInit = async (body) => {
884
- if (!body)
885
- return {};
886
- try {
887
- if (typeof body === "string") {
888
- return JSON.parse(body);
889
- }
890
- if (body instanceof Uint8Array) {
891
- return JSON.parse(new TextDecoder().decode(body));
892
- }
893
- if (body instanceof ArrayBuffer) {
894
- return JSON.parse(new TextDecoder().decode(new Uint8Array(body)));
895
- }
896
- if (ArrayBuffer.isView(body)) {
897
- const view = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
898
- return JSON.parse(new TextDecoder().decode(view));
899
- }
900
- if (typeof Blob !== "undefined" && body instanceof Blob) {
901
- return JSON.parse(await body.text());
902
- }
903
- }
904
- catch {
905
- logWarn("Failed to parse request body, using empty object");
906
- }
907
- return {};
908
- };
909
471
  const baseInit = await normalizeRequestInit(input, init);
910
- const originalBody = await parseRequestBodyFromInit(baseInit?.body);
472
+ const originalBody = await parseRequestBodyFromInit(baseInit?.body, logWarn);
911
473
  const isStreaming = originalBody.stream === true;
912
- const parsedBody = Object.keys(originalBody).length > 0 ? originalBody : undefined;
474
+ const parsedBody = Object.keys(originalBody).length > 0
475
+ ? { ...originalBody }
476
+ : undefined;
913
477
  const transformation = await transformRequestForCodex(baseInit, url, effectiveUserConfig, codexMode, parsedBody, {
914
478
  fastSession: fastSessionEnabled,
915
479
  fastSessionStrategy,
916
480
  fastSessionMaxInputItems,
481
+ deferFastSessionInputTrimming: fastSessionEnabled,
482
+ allowBackgroundResponses: getBackgroundResponses(pluginConfig),
917
483
  });
918
484
  let requestInit = transformation?.updatedInit ?? baseInit;
919
485
  let transformedBody = transformation?.body;
486
+ let pendingFastSessionInputTrim = transformation?.deferredFastSessionInputTrim;
920
487
  const promptCacheKey = transformedBody?.prompt_cache_key;
921
488
  let model = transformedBody?.model;
922
489
  let modelFamily = model ? getModelFamily(model) : "gpt-5.1";
923
490
  let quotaKey = model ? `${modelFamily}:${model}` : modelFamily;
491
+ const responseContinuationEnabled = getResponseContinuation(pluginConfig);
924
492
  const threadIdCandidate = (process.env.CODEX_THREAD_ID ?? promptCacheKey ?? "")
925
493
  .toString()
926
494
  .trim() || undefined;
927
495
  const sessionAffinityKey = threadIdCandidate ?? promptCacheKey ?? null;
928
- const effectivePromptCacheKey = (sessionAffinityKey ?? promptCacheKey ?? "").toString().trim() || undefined;
496
+ const effectivePromptCacheKey = (sessionAffinityKey ?? promptCacheKey ?? "")
497
+ .toString()
498
+ .trim() || undefined;
499
+ const shouldUseResponseContinuation = Boolean(transformedBody) &&
500
+ responseContinuationEnabled &&
501
+ !transformedBody?.previous_response_id;
502
+ if (shouldUseResponseContinuation && transformedBody) {
503
+ const lastResponseId = sessionAffinityStore?.getLastResponseId(sessionAffinityKey);
504
+ if (lastResponseId) {
505
+ transformedBody = {
506
+ ...transformedBody,
507
+ previous_response_id: lastResponseId,
508
+ };
509
+ requestInit = {
510
+ ...requestInit,
511
+ body: JSON.stringify(transformedBody),
512
+ };
513
+ }
514
+ }
929
515
  const preferredSessionAccountIndex = sessionAffinityStore?.getPreferredAccountIndex(sessionAffinityKey);
930
516
  sessionAffinityStore?.prune();
931
- const requestCorrelationId = setCorrelationId(threadIdCandidate ? `${threadIdCandidate}:${Date.now()}` : undefined);
517
+ const requestCorrelationId = setCorrelationId(threadIdCandidate
518
+ ? `${threadIdCandidate}:${Date.now()}`
519
+ : undefined);
932
520
  runtimeMetrics.lastRequestAt = Date.now();
933
521
  const abortSignal = requestInit?.signal ?? init?.signal ?? null;
934
- const sleep = (ms) => new Promise((resolve, reject) => {
935
- if (abortSignal?.aborted) {
936
- reject(new Error("Aborted"));
937
- return;
938
- }
939
- const timeout = setTimeout(() => {
940
- cleanup();
941
- resolve();
942
- }, ms);
943
- const onAbort = () => {
944
- cleanup();
945
- reject(new Error("Aborted"));
946
- };
947
- const cleanup = () => {
948
- clearTimeout(timeout);
949
- abortSignal?.removeEventListener("abort", onAbort);
950
- };
951
- abortSignal?.addEventListener("abort", onAbort, { once: true });
952
- });
953
- const sleepWithCountdown = async (totalMs, message, intervalMs = 5000) => {
954
- const startTime = Date.now();
955
- const endTime = startTime + totalMs;
956
- while (Date.now() < endTime) {
957
- if (abortSignal?.aborted) {
958
- throw new Error("Aborted");
959
- }
960
- const remaining = Math.max(0, endTime - Date.now());
961
- const waitLabel = formatWaitTime(remaining);
962
- await showToast(`${message} (${waitLabel} remaining)`, "warning", { duration: Math.min(intervalMs + 1000, toastDurationMs) });
963
- const sleepTime = Math.min(intervalMs, remaining);
964
- if (sleepTime > 0) {
965
- await sleep(sleepTime);
966
- }
967
- else {
968
- break;
969
- }
970
- }
971
- };
522
+ const sleep = createAbortableSleep(abortSignal);
972
523
  let allRateLimitedRetries = 0;
973
524
  let emptyResponseRetries = 0;
974
525
  const attemptedUnsupportedFallbackModels = new Set();
@@ -983,11 +534,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
983
534
  let usedPreferredSessionAccount = false;
984
535
  const capabilityBoostByAccount = {};
985
536
  const accountSnapshotSource = accountManager;
986
- const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot === "function"
987
- ? accountSnapshotSource.getAccountsSnapshot() ?? []
537
+ const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot ===
538
+ "function"
539
+ ? (accountSnapshotSource.getAccountsSnapshot() ?? [])
988
540
  : [];
989
541
  if (accountSnapshotList.length === 0 &&
990
- typeof accountSnapshotSource.getAccountByIndex === "function") {
542
+ typeof accountSnapshotSource.getAccountByIndex ===
543
+ "function") {
991
544
  for (let accountSnapshotIndex = 0; accountSnapshotIndex < accountCount; accountSnapshotIndex += 1) {
992
545
  const candidate = accountSnapshotSource.getAccountByIndex(accountSnapshotIndex);
993
546
  if (candidate) {
@@ -997,9 +550,10 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
997
550
  }
998
551
  for (const candidate of accountSnapshotList) {
999
552
  const accountKey = resolveEntitlementAccountKey(candidate);
1000
- capabilityBoostByAccount[candidate.index] = capabilityPolicyStore.getBoost(accountKey, model ?? modelFamily);
553
+ capabilityBoostByAccount[candidate.index] =
554
+ capabilityPolicyStore.getBoost(accountKey, model ?? modelFamily);
1001
555
  }
1002
- while (attempted.size < Math.max(1, accountCount)) {
556
+ accountAttemptLoop: while (attempted.size < Math.max(1, accountCount)) {
1003
557
  let account = null;
1004
558
  if (!usedPreferredSessionAccount &&
1005
559
  typeof preferredSessionAccountIndex === "number") {
@@ -1041,7 +595,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1041
595
  runtimeMetrics.authRefreshFailures++;
1042
596
  runtimeMetrics.failedRequests++;
1043
597
  runtimeMetrics.accountRotations++;
1044
- runtimeMetrics.lastError = err?.message ?? String(err);
598
+ runtimeMetrics.lastError =
599
+ err?.message ?? String(err);
1045
600
  const failures = accountManager.incrementAuthFailures(account);
1046
601
  const accountLabel = formatAccountLabel(account, account.index);
1047
602
  const authFailurePolicy = evaluateFailurePolicy({
@@ -1055,7 +610,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1055
610
  accountManager.removeAccount(account);
1056
611
  sessionAffinityStore?.reindexAfterRemoval(removedIndex);
1057
612
  accountManager.saveToDiskDebounced();
1058
- await showToast(`Removed ${accountLabel} after ${failures} consecutive auth failures. Run 'codex login' to re-add.`, "error", { duration: toastDurationMs * 2 });
613
+ await showRuntimeToast(client, `Removed ${accountLabel} after ${failures} consecutive auth failures. Run 'codex login' to re-add.`, "error", { duration: toastDurationMs * 2 });
1059
614
  continue;
1060
615
  }
1061
616
  if (typeof authFailurePolicy.cooldownMs === "number" &&
@@ -1065,26 +620,34 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1065
620
  accountManager.saveToDiskDebounced();
1066
621
  continue;
1067
622
  }
1068
- const hadAccountId = !!account.accountId;
1069
- const tokenAccountId = extractAccountId(accountAuth.access);
1070
- const accountId = resolveRequestAccountId(account.accountId, account.accountIdSource, tokenAccountId);
623
+ const currentWorkspace = accountManager.getCurrentWorkspace(account);
624
+ const storedAccountId = currentWorkspace?.id ?? account.accountId;
625
+ const storedAccountIdSource = currentWorkspace
626
+ ? "manual"
627
+ : account.accountIdSource;
628
+ const storedEmail = account.email;
629
+ const hadAccountId = !!storedAccountId;
630
+ const runtimeIdentity = resolveRuntimeRequestIdentity({
631
+ storedAccountId,
632
+ source: storedAccountIdSource,
633
+ storedEmail,
634
+ accessToken: accountAuth.access,
635
+ idToken: accountAuth.idToken,
636
+ });
637
+ const tokenAccountId = runtimeIdentity.tokenAccountId;
638
+ const accountId = runtimeIdentity.accountId;
1071
639
  if (!accountId) {
1072
640
  accountManager.markAccountCoolingDown(account, ACCOUNT_LIMITS.AUTH_FAILURE_COOLDOWN_MS, "auth-failure");
1073
641
  accountManager.saveToDiskDebounced();
1074
642
  continue;
1075
643
  }
1076
- const resolvedEmail = extractAccountEmail(accountAuth.access) ?? account.email;
644
+ const resolvedEmail = runtimeIdentity.email;
1077
645
  const entitlementAccountKey = resolveEntitlementAccountKey({
1078
- accountId: account.accountId ?? accountId,
646
+ accountId: storedAccountId ?? accountId,
1079
647
  email: resolvedEmail,
1080
648
  refreshToken: account.refreshToken,
1081
649
  index: account.index,
1082
650
  });
1083
- account.accountId = accountId;
1084
- if (!hadAccountId && tokenAccountId && accountId === tokenAccountId) {
1085
- account.accountIdSource = account.accountIdSource ?? "token";
1086
- }
1087
- account.email = resolvedEmail;
1088
651
  const entitlementBlock = entitlementCache.isBlocked(entitlementAccountKey, model ?? modelFamily);
1089
652
  if (entitlementBlock.blocked) {
1090
653
  runtimeMetrics.accountRotations++;
@@ -1092,16 +655,51 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1092
655
  logWarn(`Skipping account ${account.index + 1} due to cached entitlement block (${formatWaitTime(entitlementBlock.waitMs)} remaining).`);
1093
656
  continue;
1094
657
  }
658
+ account.accountId = accountId;
659
+ if (!hadAccountId &&
660
+ tokenAccountId &&
661
+ accountId === tokenAccountId) {
662
+ account.accountIdSource =
663
+ storedAccountIdSource ?? "token";
664
+ }
665
+ if (resolvedEmail) {
666
+ account.email = resolvedEmail;
667
+ }
1095
668
  if (accountCount > 1 &&
1096
669
  accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1097
670
  const accountLabel = formatAccountLabel(account, account.index);
1098
- await showToast(`Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
671
+ await showRuntimeToast(client, `Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
1099
672
  accountManager.markToastShown(account.index);
1100
673
  }
1101
674
  const headers = createCodexHeaders(requestInit, accountId, accountAuth.access, {
1102
675
  model,
1103
676
  promptCacheKey: effectivePromptCacheKey,
1104
677
  });
678
+ if (transformedBody && pendingFastSessionInputTrim) {
679
+ const activeFastSessionInputTrim = pendingFastSessionInputTrim;
680
+ pendingFastSessionInputTrim = undefined;
681
+ const compactionResult = await applyResponseCompaction({
682
+ body: transformedBody,
683
+ requestUrl: url,
684
+ headers,
685
+ trim: activeFastSessionInputTrim,
686
+ fetchImpl: async (requestUrl, requestInit) => {
687
+ const normalizedCompactionUrl = typeof requestUrl === "string"
688
+ ? requestUrl
689
+ : String(requestUrl);
690
+ return fetch(normalizedCompactionUrl, applyProxyCompatibleInit(normalizedCompactionUrl, requestInit));
691
+ },
692
+ signal: abortSignal,
693
+ timeoutMs: Math.min(fetchTimeoutMs, 4_000),
694
+ });
695
+ if (compactionResult.mode !== "unchanged") {
696
+ transformedBody = compactionResult.body;
697
+ requestInit = {
698
+ ...(requestInit ?? {}),
699
+ body: JSON.stringify(transformedBody),
700
+ };
701
+ }
702
+ }
1105
703
  const quotaScheduleKey = `${entitlementAccountKey}:${model ?? modelFamily}`;
1106
704
  const capabilityModelKey = model ?? modelFamily;
1107
705
  const quotaDeferral = preemptiveQuotaScheduler.getDeferral(quotaScheduleKey);
@@ -1118,13 +716,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1118
716
  if (!tokenConsumed) {
1119
717
  accountManager.recordRateLimit(account, modelFamily, model);
1120
718
  runtimeMetrics.accountRotations++;
1121
- runtimeMetrics.lastError =
1122
- `Local token bucket depleted for account ${account.index + 1} (${modelFamily}${model ? `:${model}` : ""})`;
719
+ runtimeMetrics.lastError = `Local token bucket depleted for account ${account.index + 1} (${modelFamily}${model ? `:${model}` : ""})`;
1123
720
  logWarn(`Skipping account ${account.index + 1}: local token bucket depleted for ${modelFamily}${model ? `:${model}` : ""}`);
1124
721
  continue;
1125
722
  }
1126
723
  let sameAccountRetryCount = 0;
1127
724
  let successAccountForResponse = account;
725
+ let successEntitlementAccountKey = entitlementAccountKey;
1128
726
  while (true) {
1129
727
  let response;
1130
728
  const fetchStart = performance.now();
@@ -1138,22 +736,25 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1138
736
  fetchController.abort(timeoutReason);
1139
737
  }, requestTimeoutMs);
1140
738
  const onUserAbort = abortSignal
1141
- ? () => fetchController.abort(abortSignal.reason ?? new Error("Aborted by user"))
739
+ ? () => fetchController.abort(abortSignal.reason ??
740
+ new Error("Aborted by user"))
1142
741
  : null;
1143
742
  if (abortSignal?.aborted) {
1144
743
  clearTimeout(fetchTimeoutId);
1145
744
  fetchController.abort(abortSignal.reason ?? new Error("Aborted by user"));
1146
745
  }
1147
746
  else if (abortSignal && onUserAbort) {
1148
- abortSignal.addEventListener("abort", onUserAbort, { once: true });
747
+ abortSignal.addEventListener("abort", onUserAbort, {
748
+ once: true,
749
+ });
1149
750
  }
1150
751
  try {
1151
752
  runtimeMetrics.totalRequests++;
1152
- response = await fetch(url, {
753
+ response = await fetch(url, applyProxyCompatibleInit(url, {
1153
754
  ...requestInit,
1154
755
  headers,
1155
756
  signal: fetchController.signal,
1156
- });
757
+ }));
1157
758
  }
1158
759
  catch (networkError) {
1159
760
  const fetchAbortReason = fetchController.signal.reason;
@@ -1166,11 +767,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1166
767
  runtimeMetrics.userAborts++;
1167
768
  runtimeMetrics.lastError = "request aborted by user";
1168
769
  sessionAffinityStore?.forgetSession(sessionAffinityKey);
1169
- throw (fetchAbortReason instanceof Error
770
+ throw fetchAbortReason instanceof Error
1170
771
  ? fetchAbortReason
1171
- : new Error("Aborted by user"));
772
+ : new Error("Aborted by user");
1172
773
  }
1173
- const errorMsg = networkError instanceof Error ? networkError.message : String(networkError);
774
+ const errorMsg = networkError instanceof Error
775
+ ? networkError.message
776
+ : String(networkError);
1174
777
  logWarn(`Network error for account ${account.index + 1}: ${errorMsg}`);
1175
778
  runtimeMetrics.failedRequests++;
1176
779
  runtimeMetrics.networkErrors++;
@@ -1223,18 +826,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1223
826
  if (contextOverflowResult.handled) {
1224
827
  return contextOverflowResult.response;
1225
828
  }
1226
- const { response: errorResponse, rateLimit, errorBody } = await handleErrorResponse(response, {
829
+ const { response: errorResponse, rateLimit, errorBody, } = await handleErrorResponse(response, {
1227
830
  requestCorrelationId,
1228
831
  threadId: threadIdCandidate,
1229
832
  });
1230
833
  const unsupportedModelInfo = getUnsupportedCodexModelInfo(errorBody);
1231
834
  const hasRemainingAccounts = attempted.size < Math.max(1, accountCount);
1232
- const blockedModel = unsupportedModelInfo.unsupportedModel ?? model ?? "requested model";
835
+ const blockedModel = unsupportedModelInfo.unsupportedModel ??
836
+ model ??
837
+ "requested model";
1233
838
  const blockedModelNormalized = blockedModel.toLowerCase();
1234
839
  const shouldForceSparkFallback = unsupportedModelInfo.isUnsupported &&
1235
840
  (blockedModelNormalized === "gpt-5.3-codex-spark" ||
1236
841
  blockedModelNormalized.includes("gpt-5.3-codex-spark"));
1237
- const allowUnsupportedFallback = fallbackOnUnsupportedCodexModel || shouldForceSparkFallback;
842
+ const allowUnsupportedFallback = fallbackOnUnsupportedCodexModel ||
843
+ shouldForceSparkFallback;
1238
844
  // Entitlements can differ by account/workspace, so try remaining
1239
845
  // accounts before degrading the model via fallback.
1240
846
  // Spark entitlement is commonly unavailable on non-Pro/Business workspaces;
@@ -1279,12 +885,16 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1279
885
  model = fallbackModel;
1280
886
  modelFamily = getModelFamily(model);
1281
887
  quotaKey = `${modelFamily}:${model}`;
1282
- if (transformedBody && typeof transformedBody === "object") {
888
+ if (transformedBody &&
889
+ typeof transformedBody === "object") {
1283
890
  transformedBody = { ...transformedBody, model };
1284
891
  }
1285
892
  else {
1286
- let fallbackBody = { model };
1287
- if (requestInit?.body && typeof requestInit.body === "string") {
893
+ let fallbackBody = {
894
+ model,
895
+ };
896
+ if (requestInit?.body &&
897
+ typeof requestInit.body === "string") {
1288
898
  try {
1289
899
  const parsed = JSON.parse(requestInit.body);
1290
900
  fallbackBody = { ...parsed, model };
@@ -1307,11 +917,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1307
917
  fallbackApplied: true,
1308
918
  fallbackReason: "unsupported-model-entitlement",
1309
919
  });
1310
- await showToast(`Model ${previousModel} is not available for this account. Retrying with ${model}.`, "warning", { duration: toastDurationMs });
920
+ await showRuntimeToast(client, `Model ${previousModel} is not available for this account. Retrying with ${model}.`, "warning", { duration: toastDurationMs });
1311
921
  restartAccountTraversalWithFallback = true;
1312
922
  break;
1313
923
  }
1314
- if (unsupportedModelInfo.isUnsupported && !allowUnsupportedFallback) {
924
+ if (unsupportedModelInfo.isUnsupported &&
925
+ !allowUnsupportedFallback) {
1315
926
  entitlementCache.markBlocked(entitlementAccountKey, blockedModel, "unsupported-model");
1316
927
  capabilityPolicyStore.recordUnsupported(entitlementAccountKey, blockedModel);
1317
928
  runtimeMetrics.lastError = `Unsupported model (strict): ${blockedModel}`;
@@ -1322,7 +933,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1322
933
  fallbackApplied: false,
1323
934
  fallbackReason: "unsupported-model-entitlement",
1324
935
  });
1325
- await showToast(`Model ${blockedModel} is not available for this account. Strict policy blocked automatic fallback.`, "warning", { duration: toastDurationMs });
936
+ await showRuntimeToast(client, `Model ${blockedModel} is not available for this account. Strict policy blocked automatic fallback.`, "warning", { duration: toastDurationMs });
1326
937
  }
1327
938
  if (unsupportedModelInfo.isUnsupported &&
1328
939
  allowUnsupportedFallback &&
@@ -1331,14 +942,71 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1331
942
  entitlementCache.markBlocked(entitlementAccountKey, blockedModel, "unsupported-model");
1332
943
  capabilityPolicyStore.recordUnsupported(entitlementAccountKey, blockedModel);
1333
944
  }
1334
- if (errorResponse.status === 403 && !unsupportedModelInfo.isUnsupported) {
945
+ const workspaceErrorCode = errorBody?.error?.code ?? "";
946
+ const workspaceErrorMessage = errorBody?.error?.message ?? "";
947
+ const isDisabledWorkspaceError = isWorkspaceDisabledError(errorResponse.status, workspaceErrorCode, workspaceErrorMessage);
948
+ // Handle workspace disabled/expired errors by rotating to the next workspace
949
+ // within the same account before falling back to another account.
950
+ if (isDisabledWorkspaceError) {
951
+ runtimeMetrics.failedRequests++;
952
+ runtimeMetrics.lastError = `Workspace disabled for account ${account.index + 1}`;
953
+ if (!account.workspaces ||
954
+ account.workspaces.length === 0) {
955
+ logWarn(`Workspace disabled/expired for account ${account.index + 1} without tracked workspaces. Leaving account enabled.`, { errorCode: workspaceErrorCode });
956
+ if (hasRemainingAccounts) {
957
+ continue accountAttemptLoop;
958
+ }
959
+ return errorResponse;
960
+ }
961
+ else {
962
+ const currentWorkspace = accountManager.getCurrentWorkspace(account);
963
+ const workspaceName = currentWorkspace?.name ??
964
+ currentWorkspace?.id ??
965
+ "unknown";
966
+ logWarn(`Workspace disabled/expired for account ${account.index + 1} - workspace: ${workspaceName}. Rotating to next workspace.`, { errorCode: workspaceErrorCode });
967
+ const disabledWorkspace = currentWorkspace
968
+ ? accountManager.disableCurrentWorkspace(account, currentWorkspace.id)
969
+ : false;
970
+ let nextWorkspace = disabledWorkspace
971
+ ? accountManager.rotateToNextWorkspace(account)
972
+ : accountManager.getCurrentWorkspace(account);
973
+ if (!disabledWorkspace &&
974
+ (!nextWorkspace ||
975
+ nextWorkspace.enabled === false)) {
976
+ nextWorkspace =
977
+ accountManager.rotateToNextWorkspace(account);
978
+ }
979
+ if (nextWorkspace) {
980
+ accountManager.saveToDiskDebounced();
981
+ const newWorkspaceName = nextWorkspace.name ?? nextWorkspace.id;
982
+ await showRuntimeToast(client, `Workspace ${workspaceName} disabled. Switched to ${newWorkspaceName}.`, "warning", { duration: toastDurationMs });
983
+ logInfo(`Rotated to workspace ${newWorkspaceName} for account ${account.index + 1}`);
984
+ // Allow the same account to be selected again with fresh request state.
985
+ attempted.delete(account.index);
986
+ continue accountAttemptLoop;
987
+ }
988
+ logWarn(`All workspaces disabled for account ${account.index + 1}. Disabling account.`);
989
+ accountManager.setAccountEnabled(account.index, false);
990
+ accountManager.saveToDiskDebounced();
991
+ await showRuntimeToast(client, `All workspaces disabled for account ${account.index + 1}. Switching to another account.`, "warning", { duration: toastDurationMs });
992
+ // Forget session affinity and continue the outer loop so another
993
+ // enabled account can service the request.
994
+ sessionAffinityStore?.forgetSession(sessionAffinityKey);
995
+ continue accountAttemptLoop;
996
+ }
997
+ }
998
+ if (errorResponse.status === 403 &&
999
+ !unsupportedModelInfo.isUnsupported &&
1000
+ !isDisabledWorkspaceError) {
1335
1001
  entitlementCache.markBlocked(entitlementAccountKey, model ?? modelFamily, "plan-entitlement");
1336
1002
  capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
1337
1003
  }
1338
- if (recoveryHook && errorBody && isRecoverableError(errorBody)) {
1004
+ if (recoveryHook &&
1005
+ errorBody &&
1006
+ isRecoverableError(errorBody)) {
1339
1007
  const errorType = detectErrorType(errorBody);
1340
1008
  const toastContent = getRecoveryToastContent(errorType);
1341
- await showToast(`${toastContent.title}: ${toastContent.message}`, "warning", { duration: toastDurationMs });
1009
+ await showRuntimeToast(client, `${toastContent.title}: ${toastContent.message}`, "warning", { duration: toastDurationMs });
1342
1010
  logDebug(`[${PLUGIN_NAME}] Recoverable error detected: ${errorType}`);
1343
1011
  }
1344
1012
  // Handle 5xx server errors by rotating to another account
@@ -1349,7 +1017,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1349
1017
  runtimeMetrics.accountRotations++;
1350
1018
  runtimeMetrics.lastError = `HTTP ${response.status}`;
1351
1019
  const serverRetryAfterMs = parseRetryAfterHintMs(response.headers);
1352
- const policy = evaluateFailurePolicy({ kind: "server", failoverMode, serverRetryAfterMs: serverRetryAfterMs ?? undefined }, { serverCooldownMs: serverErrorCooldownMs });
1020
+ const policy = evaluateFailurePolicy({
1021
+ kind: "server",
1022
+ failoverMode,
1023
+ serverRetryAfterMs: serverRetryAfterMs ?? undefined,
1024
+ }, { serverCooldownMs: serverErrorCooldownMs });
1353
1025
  if (policy.refundToken) {
1354
1026
  accountManager.refundToken(account, modelFamily, model);
1355
1027
  }
@@ -1380,7 +1052,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1380
1052
  const waitLabel = formatWaitTime(delayMs);
1381
1053
  if (delayMs <= RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS) {
1382
1054
  if (accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1383
- await showToast(`Rate limited. Retrying in ${waitLabel} (attempt ${attempt})...`, "warning", { duration: toastDurationMs });
1055
+ await showRuntimeToast(client, `Rate limited. Retrying in ${waitLabel} (attempt ${attempt})...`, "warning", { duration: toastDurationMs });
1384
1056
  accountManager.markToastShown(account.index);
1385
1057
  }
1386
1058
  await sleep(addJitter(Math.max(MIN_BACKOFF_MS, delayMs), 0.2));
@@ -1395,7 +1067,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1395
1067
  logWarn(`Rate limited. Rotating account ${account.index + 1} (${account.email ?? "unknown"}).`);
1396
1068
  if (accountManager.getAccountCount() > 1 &&
1397
1069
  accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1398
- await showToast(`Rate limited. Switching accounts (retry in ${waitLabel}).`, "warning", { duration: toastDurationMs });
1070
+ await showRuntimeToast(client, `Rate limited. Switching accounts (retry in ${waitLabel}).`, "warning", { duration: toastDurationMs });
1399
1071
  accountManager.markToastShown(account.index);
1400
1072
  }
1401
1073
  break;
@@ -1452,14 +1124,50 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1452
1124
  });
1453
1125
  continue;
1454
1126
  }
1455
- const fallbackTokenAccountId = extractAccountId(fallbackAuth.access);
1456
- const fallbackAccountId = resolveRequestAccountId(fallbackAccount.accountId, fallbackAccount.accountIdSource, fallbackTokenAccountId);
1127
+ const fallbackStoredAccountId = fallbackAccount.accountId;
1128
+ const fallbackStoredAccountIdSource = fallbackAccount.accountIdSource;
1129
+ const fallbackStoredEmail = fallbackAccount.email;
1130
+ const hadFallbackAccountId = !!fallbackStoredAccountId;
1131
+ const fallbackRuntimeIdentity = resolveRuntimeRequestIdentity({
1132
+ storedAccountId: fallbackStoredAccountId,
1133
+ source: fallbackStoredAccountIdSource,
1134
+ storedEmail: fallbackStoredEmail,
1135
+ accessToken: fallbackAuth.access,
1136
+ idToken: fallbackAuth.idToken,
1137
+ });
1138
+ const fallbackTokenAccountId = fallbackRuntimeIdentity.tokenAccountId;
1139
+ const fallbackAccountId = fallbackRuntimeIdentity.accountId;
1457
1140
  if (!fallbackAccountId) {
1458
1141
  continue;
1459
1142
  }
1143
+ const fallbackResolvedEmail = fallbackRuntimeIdentity.email;
1144
+ const fallbackEntitlementAccountKey = resolveEntitlementAccountKey({
1145
+ accountId: fallbackStoredAccountId ??
1146
+ fallbackAccountId,
1147
+ email: fallbackResolvedEmail,
1148
+ refreshToken: fallbackAccount.refreshToken,
1149
+ index: fallbackAccount.index,
1150
+ });
1151
+ const fallbackEntitlementBlock = entitlementCache.isBlocked(fallbackEntitlementAccountKey, model ?? modelFamily);
1152
+ if (fallbackEntitlementBlock.blocked) {
1153
+ runtimeMetrics.accountRotations++;
1154
+ runtimeMetrics.lastError = `Entitlement cached block for account ${fallbackAccount.index + 1}`;
1155
+ logWarn(`Skipping account ${fallbackAccount.index + 1} due to cached entitlement block (${formatWaitTime(fallbackEntitlementBlock.waitMs)} remaining).`);
1156
+ continue;
1157
+ }
1460
1158
  if (!accountManager.consumeToken(fallbackAccount, modelFamily, model)) {
1461
1159
  continue;
1462
1160
  }
1161
+ fallbackAccount.accountId = fallbackAccountId;
1162
+ if (!hadFallbackAccountId &&
1163
+ fallbackTokenAccountId &&
1164
+ fallbackAccountId === fallbackTokenAccountId) {
1165
+ fallbackAccount.accountIdSource =
1166
+ fallbackStoredAccountIdSource ?? "token";
1167
+ }
1168
+ if (fallbackResolvedEmail) {
1169
+ fallbackAccount.email = fallbackResolvedEmail;
1170
+ }
1463
1171
  const fallbackHeaders = createCodexHeaders(requestInit, fallbackAccountId, fallbackAuth.access, {
1464
1172
  model,
1465
1173
  promptCacheKey: effectivePromptCacheKey,
@@ -1467,7 +1175,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1467
1175
  const fallbackController = new AbortController();
1468
1176
  const fallbackTimeoutId = setTimeout(() => fallbackController.abort(new Error("Request timeout")), fetchTimeoutMs);
1469
1177
  const onFallbackAbort = abortSignal
1470
- ? () => fallbackController.abort(abortSignal.reason ?? new Error("Aborted by user"))
1178
+ ? () => fallbackController.abort(abortSignal.reason ??
1179
+ new Error("Aborted by user"))
1471
1180
  : null;
1472
1181
  if (abortSignal && onFallbackAbort) {
1473
1182
  abortSignal.addEventListener("abort", onFallbackAbort, {
@@ -1476,14 +1185,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1476
1185
  }
1477
1186
  try {
1478
1187
  runtimeMetrics.totalRequests++;
1479
- const fallbackResponse = await fetch(url, {
1188
+ const fallbackResponse = await fetch(url, applyProxyCompatibleInit(url, {
1480
1189
  ...requestInit,
1481
1190
  headers: fallbackHeaders,
1482
1191
  signal: fallbackController.signal,
1483
- });
1192
+ }));
1484
1193
  const fallbackSnapshot = readQuotaSchedulerSnapshot(fallbackResponse.headers, fallbackResponse.status);
1485
1194
  if (fallbackSnapshot) {
1486
- preemptiveQuotaScheduler.update(`${resolveEntitlementAccountKey(fallbackAccount)}:${model ?? modelFamily}`, fallbackSnapshot);
1195
+ preemptiveQuotaScheduler.update(`${fallbackEntitlementAccountKey}:${model ?? modelFamily}`, fallbackSnapshot);
1487
1196
  }
1488
1197
  if (!fallbackResponse.ok) {
1489
1198
  try {
@@ -1500,15 +1209,19 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1500
1209
  else {
1501
1210
  accountManager.recordFailure(fallbackAccount, modelFamily, model);
1502
1211
  }
1503
- capabilityPolicyStore.recordFailure(resolveEntitlementAccountKey(fallbackAccount), capabilityModelKey);
1212
+ capabilityPolicyStore.recordFailure(fallbackEntitlementAccountKey, capabilityModelKey);
1504
1213
  continue;
1505
1214
  }
1506
1215
  successAccountForResponse = fallbackAccount;
1216
+ successEntitlementAccountKey =
1217
+ fallbackEntitlementAccountKey;
1507
1218
  runtimeMetrics.streamFailoverRecoveries += 1;
1508
1219
  if (fallbackAccount.index !== account.index) {
1509
1220
  runtimeMetrics.streamFailoverCrossAccountRecoveries += 1;
1510
1221
  runtimeMetrics.accountRotations += 1;
1511
- sessionAffinityStore?.remember(sessionAffinityKey, fallbackAccount.index);
1222
+ if (!responseContinuationEnabled) {
1223
+ sessionAffinityStore?.remember(sessionAffinityKey, fallbackAccount.index);
1224
+ }
1512
1225
  }
1513
1226
  logInfo(`Recovered stream via failover attempt ${failoverAttempt} using account ${fallbackAccount.index + 1}.`, { emittedBytes });
1514
1227
  return fallbackResponse;
@@ -1516,14 +1229,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1516
1229
  catch (streamFailoverError) {
1517
1230
  accountManager.refundToken(fallbackAccount, modelFamily, model);
1518
1231
  accountManager.recordFailure(fallbackAccount, modelFamily, model);
1519
- capabilityPolicyStore.recordFailure(resolveEntitlementAccountKey(fallbackAccount), capabilityModelKey);
1232
+ capabilityPolicyStore.recordFailure(fallbackEntitlementAccountKey, capabilityModelKey);
1520
1233
  logWarn(`Stream failover attempt ${failoverAttempt} failed for account ${fallbackAccount.index + 1}.`, {
1521
1234
  emittedBytes,
1522
1235
  error: streamFailoverError instanceof Error
1523
1236
  ? streamFailoverError.message
1524
1237
  : String(streamFailoverError),
1525
1238
  });
1526
- continue;
1527
1239
  }
1528
1240
  finally {
1529
1241
  clearTimeout(fallbackTimeoutId);
@@ -1540,20 +1252,30 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1540
1252
  requestInstanceId: requestCorrelationId ?? undefined,
1541
1253
  });
1542
1254
  }
1255
+ let storedResponseIdForSuccess = false;
1543
1256
  const successResponse = await handleSuccessResponse(responseForSuccess, isStreaming, {
1257
+ onResponseId: (responseId) => {
1258
+ if (!responseContinuationEnabled)
1259
+ return;
1260
+ sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
1261
+ sessionAffinityStore?.updateLastResponseId(sessionAffinityKey, responseId);
1262
+ storedResponseIdForSuccess = true;
1263
+ },
1544
1264
  streamStallTimeoutMs,
1545
1265
  });
1546
1266
  if (!isStreaming && emptyResponseMaxRetries > 0) {
1547
1267
  const clonedResponse = successResponse.clone();
1548
1268
  try {
1549
1269
  const bodyText = await clonedResponse.text();
1550
- const parsedBody = bodyText ? JSON.parse(bodyText) : null;
1270
+ const parsedBody = bodyText
1271
+ ? JSON.parse(bodyText)
1272
+ : null;
1551
1273
  if (isEmptyResponse(parsedBody)) {
1552
1274
  if (emptyResponseRetries < emptyResponseMaxRetries) {
1553
1275
  emptyResponseRetries++;
1554
1276
  runtimeMetrics.emptyResponseRetries++;
1555
1277
  logWarn(`Empty response received (attempt ${emptyResponseRetries}/${emptyResponseMaxRetries}). Retrying...`);
1556
- await showToast(`Empty response. Retrying (${emptyResponseRetries}/${emptyResponseMaxRetries})...`, "warning", { duration: toastDurationMs });
1278
+ await showRuntimeToast(client, `Empty response. Retrying (${emptyResponseRetries}/${emptyResponseMaxRetries})...`, "warning", { duration: toastDurationMs });
1557
1279
  accountManager.refundToken(account, modelFamily, model);
1558
1280
  accountManager.recordFailure(account, modelFamily, model);
1559
1281
  capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
@@ -1565,7 +1287,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1565
1287
  sameAccountRetryCount < maxSameAccountRetries) {
1566
1288
  sameAccountRetryCount += 1;
1567
1289
  runtimeMetrics.sameAccountRetries += 1;
1568
- const retryDelayMs = Math.max(0, Math.floor(emptyPolicy.retryDelayMs ?? emptyResponseRetryDelayMs));
1290
+ const retryDelayMs = Math.max(0, Math.floor(emptyPolicy.retryDelayMs ??
1291
+ emptyResponseRetryDelayMs));
1569
1292
  if (retryDelayMs > 0) {
1570
1293
  await sleep(addJitter(retryDelayMs, 0.2));
1571
1294
  }
@@ -1585,18 +1308,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1585
1308
  if (successAccountForResponse.index !== account.index) {
1586
1309
  accountManager.markSwitched(successAccountForResponse, "rotation", modelFamily);
1587
1310
  }
1588
- const successAccountKey = successAccountForResponse.index === account.index
1589
- ? entitlementAccountKey
1590
- : resolveEntitlementAccountKey(successAccountForResponse);
1311
+ const successAccountKey = successEntitlementAccountKey;
1591
1312
  accountManager.recordSuccess(successAccountForResponse, modelFamily, model);
1592
1313
  capabilityPolicyStore.recordSuccess(successAccountKey, capabilityModelKey);
1593
1314
  entitlementCache.clear(successAccountKey, capabilityModelKey);
1594
- sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
1315
+ if (!responseContinuationEnabled ||
1316
+ (!isStreaming && !storedResponseIdForSuccess)) {
1317
+ sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
1318
+ }
1595
1319
  runtimeMetrics.successfulRequests++;
1596
1320
  runtimeMetrics.lastError = null;
1597
- if (lastCodexCliActiveSyncIndex !== successAccountForResponse.index) {
1321
+ if (lastCodexCliActiveSyncIndex !==
1322
+ successAccountForResponse.index) {
1598
1323
  void accountManager.syncCodexCliActiveSelectionForIndex(successAccountForResponse.index);
1599
- lastCodexCliActiveSyncIndex = successAccountForResponse.index;
1324
+ lastCodexCliActiveSyncIndex =
1325
+ successAccountForResponse.index;
1600
1326
  }
1601
1327
  return successResponse;
1602
1328
  }
@@ -1620,7 +1346,15 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1620
1346
  waitMs <= retryAllAccountsMaxWaitMs) &&
1621
1347
  allRateLimitedRetries < retryAllAccountsMaxRetries) {
1622
1348
  const countdownMessage = `All ${count} account(s) rate-limited. Waiting`;
1623
- await sleepWithCountdown(addJitter(waitMs, 0.2), countdownMessage);
1349
+ await sleepWithCountdown({
1350
+ totalMs: addJitter(waitMs, 0.2),
1351
+ message: countdownMessage,
1352
+ sleep,
1353
+ showToast: (message, variant, options) => showRuntimeToast(client, message, variant, options),
1354
+ formatWaitTime,
1355
+ toastDurationMs,
1356
+ abortSignal,
1357
+ });
1624
1358
  allRateLimitedRetries++;
1625
1359
  continue;
1626
1360
  }
@@ -1657,580 +1391,70 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1657
1391
  type: "oauth",
1658
1392
  authorize: async (inputs) => {
1659
1393
  const authPluginConfig = loadPluginConfig();
1660
- applyUiRuntimeFromConfig(authPluginConfig);
1661
- applyAccountStorageScope(authPluginConfig);
1394
+ applyUiRuntimeFromConfig(authPluginConfig, setUiRuntimeOptions);
1395
+ applyAccountStorageScopeFromConfig(authPluginConfig, {
1396
+ getPerProjectAccounts,
1397
+ getStorageBackupEnabled,
1398
+ setStorageBackupEnabled,
1399
+ isCodexCliSyncEnabled,
1400
+ getWarningShown: () => perProjectStorageWarningShown,
1401
+ setWarningShown: (shown) => {
1402
+ perProjectStorageWarningShown = shown;
1403
+ },
1404
+ logWarn,
1405
+ pluginName: PLUGIN_NAME,
1406
+ setStoragePath,
1407
+ cwd: () => process.cwd(),
1408
+ });
1662
1409
  const accounts = [];
1663
- const noBrowser = inputs?.noBrowser === "true" ||
1410
+ const noBrowser = inputs?.manual === "true" ||
1411
+ inputs?.noBrowser === "true" ||
1664
1412
  inputs?.["no-browser"] === "true";
1665
- const useManualMode = noBrowser;
1413
+ const useManualMode = noBrowser || isBrowserLaunchSuppressed();
1666
1414
  const explicitLoginMode = inputs?.loginMode === "fresh" || inputs?.loginMode === "add"
1667
1415
  ? inputs.loginMode
1668
1416
  : null;
1669
1417
  let startFresh = explicitLoginMode === "fresh";
1670
1418
  let refreshAccountIndex;
1671
- const clampActiveIndices = (storage) => {
1672
- const count = storage.accounts.length;
1673
- if (count === 0) {
1674
- storage.activeIndex = 0;
1675
- storage.activeIndexByFamily = {};
1676
- return;
1677
- }
1678
- storage.activeIndex = Math.max(0, Math.min(storage.activeIndex, count - 1));
1679
- storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
1680
- for (const family of MODEL_FAMILIES) {
1681
- const raw = storage.activeIndexByFamily[family];
1682
- const candidate = typeof raw === "number" && Number.isFinite(raw) ? raw : storage.activeIndex;
1683
- storage.activeIndexByFamily[family] = Math.max(0, Math.min(candidate, count - 1));
1684
- }
1685
- };
1686
- const isFlaggableFailure = (failure) => {
1687
- if (failure.reason === "missing_refresh")
1688
- return true;
1689
- if (failure.statusCode === 401)
1690
- return true;
1691
- if (failure.statusCode !== 400)
1692
- return false;
1693
- const message = (failure.message ?? "").toLowerCase();
1694
- return (message.includes("invalid_grant") ||
1695
- message.includes("invalid refresh") ||
1696
- message.includes("token has been revoked"));
1697
- };
1698
- const parseFiniteNumberHeader = (headers, name) => {
1699
- const raw = headers.get(name);
1700
- if (!raw)
1701
- return undefined;
1702
- const parsed = Number(raw);
1703
- return Number.isFinite(parsed) ? parsed : undefined;
1704
- };
1705
- const parseFiniteIntHeader = (headers, name) => {
1706
- const raw = headers.get(name);
1707
- if (!raw)
1708
- return undefined;
1709
- const parsed = Number.parseInt(raw, 10);
1710
- return Number.isFinite(parsed) ? parsed : undefined;
1711
- };
1712
- const parseResetAtMs = (headers, prefix) => {
1713
- const resetAfterSeconds = parseFiniteIntHeader(headers, `${prefix}-reset-after-seconds`);
1714
- if (typeof resetAfterSeconds === "number" &&
1715
- Number.isFinite(resetAfterSeconds) &&
1716
- resetAfterSeconds > 0) {
1717
- return Date.now() + resetAfterSeconds * 1000;
1718
- }
1719
- const resetAtRaw = headers.get(`${prefix}-reset-at`);
1720
- if (!resetAtRaw)
1721
- return undefined;
1722
- const trimmed = resetAtRaw.trim();
1723
- if (/^\d+$/.test(trimmed)) {
1724
- const parsedNumber = Number.parseInt(trimmed, 10);
1725
- if (Number.isFinite(parsedNumber) && parsedNumber > 0) {
1726
- // Upstream sometimes returns seconds since epoch.
1727
- return parsedNumber < 10_000_000_000 ? parsedNumber * 1000 : parsedNumber;
1728
- }
1729
- }
1730
- const parsedDate = Date.parse(trimmed);
1731
- return Number.isFinite(parsedDate) ? parsedDate : undefined;
1732
- };
1733
- const hasCodexQuotaHeaders = (headers) => {
1734
- const keys = [
1735
- "x-codex-primary-used-percent",
1736
- "x-codex-primary-window-minutes",
1737
- "x-codex-primary-reset-at",
1738
- "x-codex-primary-reset-after-seconds",
1739
- "x-codex-secondary-used-percent",
1740
- "x-codex-secondary-window-minutes",
1741
- "x-codex-secondary-reset-at",
1742
- "x-codex-secondary-reset-after-seconds",
1743
- ];
1744
- return keys.some((key) => headers.get(key) !== null);
1745
- };
1746
- const parseCodexQuotaSnapshot = (headers, status) => {
1747
- if (!hasCodexQuotaHeaders(headers))
1748
- return null;
1749
- const primaryPrefix = "x-codex-primary";
1750
- const secondaryPrefix = "x-codex-secondary";
1751
- const primary = {
1752
- usedPercent: parseFiniteNumberHeader(headers, `${primaryPrefix}-used-percent`),
1753
- windowMinutes: parseFiniteIntHeader(headers, `${primaryPrefix}-window-minutes`),
1754
- resetAtMs: parseResetAtMs(headers, primaryPrefix),
1755
- };
1756
- const secondary = {
1757
- usedPercent: parseFiniteNumberHeader(headers, `${secondaryPrefix}-used-percent`),
1758
- windowMinutes: parseFiniteIntHeader(headers, `${secondaryPrefix}-window-minutes`),
1759
- resetAtMs: parseResetAtMs(headers, secondaryPrefix),
1760
- };
1761
- const planTypeRaw = headers.get("x-codex-plan-type");
1762
- const planType = planTypeRaw && planTypeRaw.trim() ? planTypeRaw.trim() : undefined;
1763
- const activeLimit = parseFiniteIntHeader(headers, "x-codex-active-limit");
1764
- return { status, planType, activeLimit, primary, secondary };
1765
- };
1766
- const formatQuotaWindowLabel = (windowMinutes) => {
1767
- if (!windowMinutes || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
1768
- return "quota";
1769
- }
1770
- if (windowMinutes % 1440 === 0)
1771
- return `${windowMinutes / 1440}d`;
1772
- if (windowMinutes % 60 === 0)
1773
- return `${windowMinutes / 60}h`;
1774
- return `${windowMinutes}m`;
1775
- };
1776
- const formatResetAt = (resetAtMs) => {
1777
- if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0)
1778
- return undefined;
1779
- const date = new Date(resetAtMs);
1780
- if (!Number.isFinite(date.getTime()))
1781
- return undefined;
1782
- const now = new Date();
1783
- const sameDay = now.getFullYear() === date.getFullYear() &&
1784
- now.getMonth() === date.getMonth() &&
1785
- now.getDate() === date.getDate();
1786
- const time = date.toLocaleTimeString(undefined, {
1787
- hour: "2-digit",
1788
- minute: "2-digit",
1789
- hour12: false,
1790
- });
1791
- if (sameDay)
1792
- return time;
1793
- const day = date.toLocaleDateString(undefined, { month: "short", day: "2-digit" });
1794
- return `${time} on ${day}`;
1795
- };
1796
- const formatCodexQuotaLine = (snapshot) => {
1797
- const summarizeWindow = (label, window) => {
1798
- const used = window.usedPercent;
1799
- const left = typeof used === "number" && Number.isFinite(used)
1800
- ? Math.max(0, Math.min(100, Math.round(100 - used)))
1801
- : undefined;
1802
- const reset = formatResetAt(window.resetAtMs);
1803
- let summary = label;
1804
- if (left !== undefined)
1805
- summary = `${summary} ${left}% left`;
1806
- if (reset)
1807
- summary = `${summary} (resets ${reset})`;
1808
- return summary;
1809
- };
1810
- const primaryLabel = formatQuotaWindowLabel(snapshot.primary.windowMinutes);
1811
- const secondaryLabel = formatQuotaWindowLabel(snapshot.secondary.windowMinutes);
1812
- const parts = [
1813
- summarizeWindow(primaryLabel, snapshot.primary),
1814
- summarizeWindow(secondaryLabel, snapshot.secondary),
1815
- ];
1816
- if (snapshot.planType)
1817
- parts.push(`plan:${snapshot.planType}`);
1818
- if (typeof snapshot.activeLimit === "number" && Number.isFinite(snapshot.activeLimit)) {
1819
- parts.push(`active:${snapshot.activeLimit}`);
1820
- }
1821
- if (snapshot.status === 429)
1822
- parts.push("rate-limited");
1823
- return parts.join(", ");
1824
- };
1825
- const fetchCodexQuotaSnapshot = async (params) => {
1826
- const QUOTA_PROBE_MODELS = ["gpt-5-codex", "gpt-5.3-codex", "gpt-5.2-codex"];
1827
- let lastError = null;
1828
- for (const model of QUOTA_PROBE_MODELS) {
1829
- try {
1830
- const instructions = await getCodexInstructions(model);
1831
- const probeBody = {
1832
- model,
1833
- stream: true,
1834
- store: false,
1835
- include: ["reasoning.encrypted_content"],
1836
- instructions,
1837
- input: [
1838
- {
1839
- type: "message",
1840
- role: "user",
1841
- content: [{ type: "input_text", text: "quota ping" }],
1842
- },
1843
- ],
1844
- reasoning: { effort: "none", summary: "auto" },
1845
- text: { verbosity: "low" },
1846
- };
1847
- const headers = createCodexHeaders(undefined, params.accountId, params.accessToken, {
1848
- model,
1849
- });
1850
- headers.set("content-type", "application/json; charset=utf-8");
1851
- const controller = new AbortController();
1852
- const timeout = setTimeout(() => controller.abort(), 15_000);
1853
- let response;
1854
- try {
1855
- response = await fetch(`${CODEX_BASE_URL}/codex/responses`, {
1856
- method: "POST",
1857
- headers,
1858
- body: JSON.stringify(probeBody),
1859
- signal: controller.signal,
1860
- });
1861
- }
1862
- finally {
1863
- clearTimeout(timeout);
1864
- }
1865
- const snapshot = parseCodexQuotaSnapshot(response.headers, response.status);
1866
- if (snapshot) {
1867
- // We only need headers; cancel the SSE stream immediately.
1868
- try {
1869
- await response.body?.cancel();
1870
- }
1871
- catch {
1872
- // Ignore cancellation failures.
1873
- }
1874
- return snapshot;
1875
- }
1876
- if (!response.ok) {
1877
- const bodyText = await response.text().catch(() => "");
1878
- let errorBody = undefined;
1879
- try {
1880
- errorBody = bodyText ? JSON.parse(bodyText) : undefined;
1881
- }
1882
- catch {
1883
- errorBody = { error: { message: bodyText } };
1884
- }
1885
- const unsupportedInfo = getUnsupportedCodexModelInfo(errorBody);
1886
- if (unsupportedInfo.isUnsupported) {
1887
- lastError = new Error(unsupportedInfo.message ?? `Model '${model}' unsupported for this account`);
1888
- continue;
1889
- }
1890
- const message = (typeof errorBody?.error?.message === "string"
1891
- ? errorBody.error?.message
1892
- : bodyText) || `HTTP ${response.status}`;
1893
- throw new Error(message);
1894
- }
1895
- lastError = new Error("Codex response did not include quota headers");
1896
- }
1897
- catch (error) {
1898
- lastError = error instanceof Error ? error : new Error(String(error));
1899
- }
1900
- }
1901
- throw lastError ?? new Error("Failed to fetch quotas");
1902
- };
1903
- const runAccountCheck = async (deepProbe) => {
1904
- const loadedStorage = await hydrateEmails(await loadAccounts());
1905
- const workingStorage = loadedStorage
1906
- ? {
1907
- ...loadedStorage,
1908
- accounts: loadedStorage.accounts.map((account) => ({ ...account })),
1909
- activeIndexByFamily: loadedStorage.activeIndexByFamily
1910
- ? { ...loadedStorage.activeIndexByFamily }
1911
- : {},
1912
- }
1913
- : { version: 3, accounts: [], activeIndex: 0, activeIndexByFamily: {} };
1914
- if (workingStorage.accounts.length === 0) {
1915
- console.log("\nNo accounts to check.\n");
1916
- return;
1917
- }
1918
- const flaggedStorage = await loadFlaggedAccounts();
1919
- let storageChanged = false;
1920
- let flaggedChanged = false;
1921
- const removeFromActive = new Set();
1922
- const total = workingStorage.accounts.length;
1923
- let ok = 0;
1924
- let disabled = 0;
1925
- let errors = 0;
1926
- console.log(`\nChecking ${deepProbe ? "full account health" : "quotas"} for all accounts...\n`);
1927
- for (let i = 0; i < total; i += 1) {
1928
- const account = workingStorage.accounts[i];
1929
- if (!account)
1930
- continue;
1931
- const label = account.email ?? account.accountLabel ?? `Account ${i + 1}`;
1932
- if (account.enabled === false) {
1933
- disabled += 1;
1934
- console.log(`[${i + 1}/${total}] ${label}: DISABLED`);
1935
- continue;
1936
- }
1937
- try {
1938
- // If we already have a valid cached access token, don't force-refresh.
1939
- // This avoids flagging accounts where the refresh token has been burned
1940
- // but the access token is still valid (same behavior as Codex CLI).
1941
- const nowMs = Date.now();
1942
- let accessToken = null;
1943
- let tokenAccountId = undefined;
1944
- let authDetail = "OK";
1945
- if (account.accessToken &&
1946
- (typeof account.expiresAt !== "number" ||
1947
- !Number.isFinite(account.expiresAt) ||
1948
- account.expiresAt > nowMs)) {
1949
- accessToken = account.accessToken;
1950
- authDetail = "OK (cached access)";
1951
- tokenAccountId = extractAccountId(account.accessToken);
1952
- if (tokenAccountId &&
1953
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId) &&
1954
- tokenAccountId !== account.accountId) {
1955
- account.accountId = tokenAccountId;
1956
- account.accountIdSource = "token";
1957
- storageChanged = true;
1958
- }
1959
- }
1960
- // If Codex CLI has a valid cached access token for this email, use it
1961
- // instead of forcing a refresh.
1962
- if (!accessToken) {
1963
- const cached = await lookupCodexCliTokensByEmail(account.email);
1964
- if (cached &&
1965
- (typeof cached.expiresAt !== "number" ||
1966
- !Number.isFinite(cached.expiresAt) ||
1967
- cached.expiresAt > nowMs)) {
1968
- accessToken = cached.accessToken;
1969
- authDetail = "OK (Codex CLI cache)";
1970
- if (cached.refreshToken && cached.refreshToken !== account.refreshToken) {
1971
- account.refreshToken = cached.refreshToken;
1972
- storageChanged = true;
1973
- }
1974
- if (cached.accessToken && cached.accessToken !== account.accessToken) {
1975
- account.accessToken = cached.accessToken;
1976
- storageChanged = true;
1977
- }
1978
- if (cached.expiresAt !== account.expiresAt) {
1979
- account.expiresAt = cached.expiresAt;
1980
- storageChanged = true;
1981
- }
1982
- const hydratedEmail = sanitizeEmail(extractAccountEmail(cached.accessToken));
1983
- if (hydratedEmail && hydratedEmail !== account.email) {
1984
- account.email = hydratedEmail;
1985
- storageChanged = true;
1986
- }
1987
- tokenAccountId = extractAccountId(cached.accessToken);
1988
- if (tokenAccountId &&
1989
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId) &&
1990
- tokenAccountId !== account.accountId) {
1991
- account.accountId = tokenAccountId;
1992
- account.accountIdSource = "token";
1993
- storageChanged = true;
1994
- }
1995
- }
1996
- }
1997
- if (!accessToken) {
1998
- const refreshResult = await queuedRefresh(account.refreshToken);
1999
- if (refreshResult.type !== "success") {
2000
- errors += 1;
2001
- const message = refreshResult.message ?? refreshResult.reason ?? "refresh failed";
2002
- console.log(`[${i + 1}/${total}] ${label}: ERROR (${message})`);
2003
- if (deepProbe && isFlaggableFailure(refreshResult)) {
2004
- const existingIndex = flaggedStorage.accounts.findIndex((flagged) => flagged.refreshToken === account.refreshToken);
2005
- const flaggedRecord = {
2006
- ...account,
2007
- flaggedAt: Date.now(),
2008
- flaggedReason: "token-invalid",
2009
- lastError: message,
2010
- };
2011
- if (existingIndex >= 0) {
2012
- flaggedStorage.accounts[existingIndex] = flaggedRecord;
2013
- }
2014
- else {
2015
- flaggedStorage.accounts.push(flaggedRecord);
2016
- }
2017
- removeFromActive.add(account.refreshToken);
2018
- flaggedChanged = true;
2019
- }
2020
- continue;
2021
- }
2022
- accessToken = refreshResult.access;
2023
- authDetail = "OK";
2024
- if (refreshResult.refresh !== account.refreshToken) {
2025
- account.refreshToken = refreshResult.refresh;
2026
- storageChanged = true;
2027
- }
2028
- if (refreshResult.access && refreshResult.access !== account.accessToken) {
2029
- account.accessToken = refreshResult.access;
2030
- storageChanged = true;
2031
- }
2032
- if (typeof refreshResult.expires === "number" &&
2033
- refreshResult.expires !== account.expiresAt) {
2034
- account.expiresAt = refreshResult.expires;
2035
- storageChanged = true;
2036
- }
2037
- const hydratedEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken));
2038
- if (hydratedEmail && hydratedEmail !== account.email) {
2039
- account.email = hydratedEmail;
2040
- storageChanged = true;
2041
- }
2042
- tokenAccountId = extractAccountId(refreshResult.access);
2043
- if (tokenAccountId &&
2044
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId) &&
2045
- tokenAccountId !== account.accountId) {
2046
- account.accountId = tokenAccountId;
2047
- account.accountIdSource = "token";
2048
- storageChanged = true;
2049
- }
2050
- }
2051
- if (!accessToken) {
2052
- throw new Error("Missing access token after refresh");
2053
- }
2054
- if (deepProbe) {
2055
- ok += 1;
2056
- const detail = tokenAccountId
2057
- ? `${authDetail} (id:${tokenAccountId.slice(-6)})`
2058
- : authDetail;
2059
- console.log(`[${i + 1}/${total}] ${label}: ${detail}`);
2060
- continue;
2061
- }
2062
- try {
2063
- const requestAccountId = resolveRequestAccountId(account.accountId, account.accountIdSource, tokenAccountId) ??
2064
- tokenAccountId ??
2065
- account.accountId;
2066
- if (!requestAccountId) {
2067
- throw new Error("Missing accountId for quota probe");
2068
- }
2069
- const snapshot = await fetchCodexQuotaSnapshot({
2070
- accountId: requestAccountId,
2071
- accessToken,
2072
- });
2073
- ok += 1;
2074
- console.log(`[${i + 1}/${total}] ${label}: ${formatCodexQuotaLine(snapshot)}`);
2075
- }
2076
- catch (error) {
2077
- errors += 1;
2078
- const message = error instanceof Error ? error.message : String(error);
2079
- console.log(`[${i + 1}/${total}] ${label}: ERROR (${message.slice(0, 160)})`);
2080
- }
2081
- }
2082
- catch (error) {
2083
- errors += 1;
2084
- const message = error instanceof Error ? error.message : String(error);
2085
- console.log(`[${i + 1}/${total}] ${label}: ERROR (${message.slice(0, 120)})`);
2086
- }
2087
- }
2088
- if (removeFromActive.size > 0) {
2089
- workingStorage.accounts = workingStorage.accounts.filter((account) => !removeFromActive.has(account.refreshToken));
2090
- clampActiveIndices(workingStorage);
2091
- storageChanged = true;
2092
- }
2093
- if (storageChanged) {
2094
- await saveAccounts(workingStorage);
2095
- invalidateAccountManagerCache();
2096
- }
2097
- if (flaggedChanged) {
2098
- await saveFlaggedAccounts(flaggedStorage);
2099
- }
2100
- console.log("");
2101
- console.log(`Results: ${ok} ok, ${errors} error, ${disabled} disabled`);
2102
- if (removeFromActive.size > 0) {
2103
- console.log(`Moved ${removeFromActive.size} account(s) to flagged pool (invalid refresh token).`);
2104
- }
2105
- console.log("");
2106
- };
2107
- const verifyFlaggedAccounts = async () => {
2108
- const flaggedStorage = await loadFlaggedAccounts();
2109
- if (flaggedStorage.accounts.length === 0) {
2110
- console.log("\nNo flagged accounts to verify.\n");
2111
- return;
2112
- }
2113
- console.log("\nVerifying flagged accounts...\n");
2114
- const remaining = [];
2115
- const restored = [];
2116
- for (let i = 0; i < flaggedStorage.accounts.length; i += 1) {
2117
- const flagged = flaggedStorage.accounts[i];
2118
- if (!flagged)
2119
- continue;
2120
- const label = flagged.email ?? flagged.accountLabel ?? `Flagged ${i + 1}`;
2121
- try {
2122
- const cached = await lookupCodexCliTokensByEmail(flagged.email);
2123
- const now = Date.now();
2124
- if (cached &&
2125
- typeof cached.expiresAt === "number" &&
2126
- Number.isFinite(cached.expiresAt) &&
2127
- cached.expiresAt > now) {
2128
- const refreshToken = typeof cached.refreshToken === "string" && cached.refreshToken.trim()
2129
- ? cached.refreshToken.trim()
2130
- : flagged.refreshToken;
2131
- const resolved = resolveAccountSelection({
2132
- type: "success",
2133
- access: cached.accessToken,
2134
- refresh: refreshToken,
2135
- expires: cached.expiresAt,
2136
- multiAccount: true,
2137
- });
2138
- if (!resolved.accountIdOverride && flagged.accountId) {
2139
- resolved.accountIdOverride = flagged.accountId;
2140
- resolved.accountIdSource = flagged.accountIdSource ?? "manual";
2141
- }
2142
- if (!resolved.accountLabel && flagged.accountLabel) {
2143
- resolved.accountLabel = flagged.accountLabel;
2144
- }
2145
- restored.push(resolved);
2146
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: RESTORED (Codex CLI cache)`);
2147
- continue;
2148
- }
2149
- const refreshResult = await queuedRefresh(flagged.refreshToken);
2150
- if (refreshResult.type !== "success") {
2151
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: STILL FLAGGED (${refreshResult.message ?? refreshResult.reason ?? "refresh failed"})`);
2152
- remaining.push(flagged);
2153
- continue;
2154
- }
2155
- const resolved = resolveAccountSelection(refreshResult);
2156
- if (!resolved.accountIdOverride && flagged.accountId) {
2157
- resolved.accountIdOverride = flagged.accountId;
2158
- resolved.accountIdSource = flagged.accountIdSource ?? "manual";
2159
- }
2160
- if (!resolved.accountLabel && flagged.accountLabel) {
2161
- resolved.accountLabel = flagged.accountLabel;
2162
- }
2163
- restored.push(resolved);
2164
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: RESTORED`);
2165
- }
2166
- catch (error) {
2167
- const message = error instanceof Error ? error.message : String(error);
2168
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: ERROR (${message.slice(0, 120)})`);
2169
- remaining.push({
2170
- ...flagged,
2171
- lastError: message,
2172
- });
2173
- }
2174
- }
2175
- if (restored.length > 0) {
2176
- await persistAccountPool(restored, false);
2177
- invalidateAccountManagerCache();
2178
- }
2179
- await saveFlaggedAccounts({
2180
- version: 1,
2181
- accounts: remaining,
2182
- });
2183
- console.log("");
2184
- console.log(`Results: ${restored.length} restored, ${remaining.length} still flagged`);
2185
- console.log("");
2186
- };
2187
1419
  if (!explicitLoginMode) {
2188
1420
  while (true) {
2189
- const loadedStorage = await hydrateEmails(await loadAccounts());
1421
+ const loadedStorage = await hydrateRuntimeEmails(await loadAccounts(), {
1422
+ queuedRefresh,
1423
+ extractAccountId,
1424
+ sanitizeEmail,
1425
+ extractAccountEmail,
1426
+ shouldUpdateAccountIdFromToken,
1427
+ saveAccounts,
1428
+ logWarn,
1429
+ pluginName: PLUGIN_NAME,
1430
+ });
2190
1431
  const workingStorage = loadedStorage
2191
1432
  ? {
2192
1433
  ...loadedStorage,
2193
- accounts: loadedStorage.accounts.map((account) => ({ ...account })),
1434
+ accounts: loadedStorage.accounts.map((account) => ({
1435
+ ...account,
1436
+ })),
2194
1437
  activeIndexByFamily: loadedStorage.activeIndexByFamily
2195
1438
  ? { ...loadedStorage.activeIndexByFamily }
2196
1439
  : {},
2197
1440
  }
2198
- : { version: 3, accounts: [], activeIndex: 0, activeIndexByFamily: {} };
1441
+ : {
1442
+ version: 3,
1443
+ accounts: [],
1444
+ activeIndex: 0,
1445
+ activeIndexByFamily: {},
1446
+ };
2199
1447
  const flaggedStorage = await loadFlaggedAccounts();
2200
- if (workingStorage.accounts.length === 0 && flaggedStorage.accounts.length === 0) {
1448
+ if (workingStorage.accounts.length === 0 &&
1449
+ flaggedStorage.accounts.length === 0) {
2201
1450
  break;
2202
1451
  }
2203
1452
  const now = Date.now();
2204
1453
  const activeIndex = resolveActiveIndex(workingStorage, "codex");
2205
- const existingAccounts = workingStorage.accounts.map((account, index) => {
2206
- let status;
2207
- if (account.enabled === false) {
2208
- status = "disabled";
2209
- }
2210
- else if (typeof account.coolingDownUntil === "number" &&
2211
- account.coolingDownUntil > now) {
2212
- status = "cooldown";
2213
- }
2214
- else if (formatRateLimitEntry(account, now)) {
2215
- status = "rate-limited";
2216
- }
2217
- else if (index === activeIndex) {
2218
- status = "active";
2219
- }
2220
- else {
2221
- status = "ok";
2222
- }
2223
- return {
2224
- accountId: account.accountId,
2225
- accountLabel: account.accountLabel,
2226
- email: account.email,
2227
- index,
2228
- addedAt: account.addedAt,
2229
- lastUsed: account.lastUsed,
2230
- status,
2231
- isCurrentAccount: index === activeIndex,
2232
- enabled: account.enabled !== false,
2233
- };
1454
+ const existingAccounts = buildLoginMenuAccounts(workingStorage.accounts, {
1455
+ now,
1456
+ activeIndex,
1457
+ formatRateLimitEntry: (account, currentNow) => formatRateLimitEntry(account, currentNow, formatWaitTime),
2234
1458
  });
2235
1459
  const menuResult = await promptLoginMode(existingAccounts, {
2236
1460
  flaggedCount: flaggedStorage.accounts.length,
@@ -2245,16 +1469,62 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2245
1469
  }),
2246
1470
  };
2247
1471
  }
1472
+ const accountCheckDeps = {
1473
+ hydrateEmails: (storage) => hydrateRuntimeEmails(storage, {
1474
+ queuedRefresh,
1475
+ extractAccountId,
1476
+ sanitizeEmail,
1477
+ extractAccountEmail,
1478
+ shouldUpdateAccountIdFromToken,
1479
+ saveAccounts,
1480
+ logWarn,
1481
+ pluginName: PLUGIN_NAME,
1482
+ }),
1483
+ loadAccounts,
1484
+ createEmptyStorage: () => ({
1485
+ version: 3,
1486
+ accounts: [],
1487
+ activeIndex: 0,
1488
+ activeIndexByFamily: {},
1489
+ }),
1490
+ loadFlaggedAccounts,
1491
+ createAccountCheckWorkingState,
1492
+ lookupCodexCliTokensByEmail,
1493
+ extractAccountId,
1494
+ shouldUpdateAccountIdFromToken,
1495
+ sanitizeEmail,
1496
+ extractAccountEmail,
1497
+ queuedRefresh,
1498
+ isRuntimeFlaggableFailure: isFlaggableFailure,
1499
+ fetchCodexQuotaSnapshot,
1500
+ resolveRequestAccountId,
1501
+ formatCodexQuotaLine: formatQuotaSnapshotLine,
1502
+ clampRuntimeActiveIndices: clampActiveIndices,
1503
+ MODEL_FAMILIES,
1504
+ saveAccounts,
1505
+ invalidateAccountManagerCache: () => invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps),
1506
+ saveFlaggedAccounts,
1507
+ showLine: (message) => console.log(message),
1508
+ };
2248
1509
  if (menuResult.mode === "check") {
2249
- await runAccountCheck(false);
1510
+ await runRuntimeAccountCheck(false, accountCheckDeps);
2250
1511
  continue;
2251
1512
  }
2252
1513
  if (menuResult.mode === "deep-check") {
2253
- await runAccountCheck(true);
1514
+ await runRuntimeAccountCheck(true, accountCheckDeps);
2254
1515
  continue;
2255
1516
  }
2256
1517
  if (menuResult.mode === "verify-flagged") {
2257
- await verifyFlaggedAccounts();
1518
+ await verifyRuntimeFlaggedAccounts({
1519
+ loadFlaggedAccounts,
1520
+ lookupCodexCliTokensByEmail,
1521
+ queuedRefresh,
1522
+ resolveTokenSuccessAccount,
1523
+ persistAccounts: persistAccountPool,
1524
+ invalidateAccountManagerCache: () => invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps),
1525
+ saveFlaggedAccounts,
1526
+ showLine: (message) => console.log(message),
1527
+ });
2258
1528
  continue;
2259
1529
  }
2260
1530
  if (menuResult.mode === "manage") {
@@ -2262,13 +1532,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2262
1532
  const target = workingStorage.accounts[menuResult.deleteAccountIndex];
2263
1533
  if (target) {
2264
1534
  workingStorage.accounts.splice(menuResult.deleteAccountIndex, 1);
2265
- clampActiveIndices(workingStorage);
1535
+ clampActiveIndices(workingStorage, MODEL_FAMILIES);
2266
1536
  await saveAccounts(workingStorage);
2267
1537
  await saveFlaggedAccounts({
2268
1538
  version: 1,
2269
1539
  accounts: flaggedStorage.accounts.filter((flagged) => flagged.refreshToken !== target.refreshToken),
2270
1540
  });
2271
- invalidateAccountManagerCache();
1541
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2272
1542
  console.log(`\nDeleted ${target.email ?? `Account ${menuResult.deleteAccountIndex + 1}`}.\n`);
2273
1543
  }
2274
1544
  continue;
@@ -2278,7 +1548,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2278
1548
  if (target) {
2279
1549
  target.enabled = target.enabled === false ? true : false;
2280
1550
  await saveAccounts(workingStorage);
2281
- invalidateAccountManagerCache();
1551
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2282
1552
  console.log(`\n${target.email ?? `Account ${menuResult.toggleAccountIndex + 1}`} ${target.enabled === false ? "disabled" : "enabled"}.\n`);
2283
1553
  }
2284
1554
  continue;
@@ -2295,7 +1565,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2295
1565
  if (menuResult.deleteAll) {
2296
1566
  await clearAccounts();
2297
1567
  await clearFlaggedAccounts();
2298
- invalidateAccountManagerCache();
1568
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2299
1569
  console.log("\nCleared saved accounts from active storage. Recovery snapshots remain available. Starting fresh.\n");
2300
1570
  }
2301
1571
  break;
@@ -2307,7 +1577,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2307
1577
  const latestStorage = await loadAccounts();
2308
1578
  const existingCount = latestStorage?.accounts.length ?? 0;
2309
1579
  const requestedCount = Number.parseInt(inputs?.accountCount ?? "1", 10);
2310
- const normalizedRequested = Number.isFinite(requestedCount) ? requestedCount : 1;
1580
+ const normalizedRequested = Number.isFinite(requestedCount)
1581
+ ? requestedCount
1582
+ : 1;
2311
1583
  const availableSlots = refreshAccountIndex !== undefined
2312
1584
  ? 1
2313
1585
  : startFresh
@@ -2332,39 +1604,72 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2332
1604
  }
2333
1605
  if (useManualMode) {
2334
1606
  const { pkce, state, url } = await createAuthorizationFlow();
2335
- return buildManualOAuthFlow(pkce, url, state, async (tokens) => {
2336
- try {
2337
- await persistAccountPool([tokens], startFresh);
2338
- invalidateAccountManagerCache();
2339
- }
2340
- catch (err) {
2341
- const storagePath = getStoragePath();
2342
- const errorCode = err?.code || "UNKNOWN";
2343
- const hint = err instanceof StorageError
2344
- ? err.hint
2345
- : formatStorageErrorHint(err, storagePath);
2346
- logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
2347
- await showToast(hint, "error", {
2348
- title: "Account Persistence Failed",
2349
- duration: 10000,
2350
- });
2351
- }
1607
+ return buildManualOAuthFlow({
1608
+ pkce,
1609
+ url,
1610
+ expectedState: state,
1611
+ redirectUri: REDIRECT_URI,
1612
+ parseAuthorizationInput,
1613
+ exchangeAuthorizationCode,
1614
+ resolveTokenSuccess: resolveTokenSuccessAccount,
1615
+ instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
1616
+ onSuccess: async (tokens) => {
1617
+ try {
1618
+ await persistAccountPool([tokens], startFresh);
1619
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
1620
+ }
1621
+ catch (err) {
1622
+ const storagePath = getStoragePath();
1623
+ const errorCode = err?.code || "UNKNOWN";
1624
+ const hint = err instanceof StorageError
1625
+ ? err.hint
1626
+ : formatStorageErrorHint(err, storagePath);
1627
+ logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
1628
+ await showRuntimeToast(client, hint, "error", {
1629
+ title: "Account Persistence Failed",
1630
+ duration: 10000,
1631
+ });
1632
+ }
1633
+ },
2352
1634
  });
2353
1635
  }
2354
- const explicitCountProvided = typeof inputs?.accountCount === "string" && inputs.accountCount.trim().length > 0;
1636
+ const explicitCountProvided = typeof inputs?.accountCount === "string" &&
1637
+ inputs.accountCount.trim().length > 0;
2355
1638
  while (accounts.length < targetCount) {
2356
1639
  logInfo(`=== OpenAI OAuth (Account ${accounts.length + 1}) ===`);
2357
1640
  const forceNewLogin = accounts.length > 0 || refreshAccountIndex !== undefined;
2358
- const result = await runOAuthFlow(forceNewLogin);
1641
+ const result = await runRuntimeOAuthFlow(forceNewLogin, {
1642
+ runBrowserOAuthFlow: (input) => runBrowserOAuthFlow({
1643
+ ...input,
1644
+ createAuthorizationFlow,
1645
+ redactOAuthUrlForLog,
1646
+ startLocalOAuthServer,
1647
+ openBrowserUrl,
1648
+ pluginName: PLUGIN_NAME,
1649
+ authManualLabel: AUTH_LABELS.OAUTH_MANUAL,
1650
+ exchangeAuthorizationCode,
1651
+ redirectUri: REDIRECT_URI,
1652
+ }),
1653
+ manualModeLabel: AUTH_LABELS.OAUTH_MANUAL,
1654
+ logInfo,
1655
+ logDebug,
1656
+ logWarn,
1657
+ pluginName: PLUGIN_NAME,
1658
+ });
2359
1659
  let resolved = null;
2360
1660
  if (result.type === "success") {
2361
- resolved = resolveAccountSelection(result);
1661
+ resolved = resolveTokenSuccessAccount(result);
2362
1662
  const email = extractAccountEmail(resolved.access, resolved.idToken);
2363
- const accountId = resolved.accountIdOverride ?? extractAccountId(resolved.access);
2364
- const label = resolved.accountLabel ?? email ?? accountId ?? "Unknown account";
1663
+ const accountId = resolved.accountIdOverride ??
1664
+ extractAccountId(resolved.access);
1665
+ const label = resolved.accountLabel ??
1666
+ email ??
1667
+ accountId ??
1668
+ "Unknown account";
2365
1669
  logInfo(`Authenticated as: ${label}`);
2366
1670
  const isDuplicate = findMatchingAccountIndex(accounts.map((account) => ({
2367
- accountId: account.accountIdOverride ?? extractAccountId(account.access),
1671
+ accountId: account.accountIdOverride ??
1672
+ extractAccountId(account.access),
2368
1673
  email: sanitizeEmail(extractAccountEmail(account.access, account.idToken)),
2369
1674
  refreshToken: account.refresh,
2370
1675
  })), {
@@ -2394,11 +1699,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2394
1699
  continue;
2395
1700
  }
2396
1701
  accounts.push(resolved);
2397
- await showToast(`Account ${accounts.length} authenticated`, "success");
1702
+ await showRuntimeToast(client, `Account ${accounts.length} authenticated`, "success");
2398
1703
  try {
2399
1704
  const isFirstAccount = accounts.length === 1;
2400
1705
  await persistAccountPool([resolved], isFirstAccount && startFresh);
2401
- invalidateAccountManagerCache();
1706
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2402
1707
  }
2403
1708
  catch (err) {
2404
1709
  const storagePath = getStoragePath();
@@ -2407,7 +1712,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2407
1712
  ? err.hint
2408
1713
  : formatStorageErrorHint(err, storagePath);
2409
1714
  logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
2410
- await showToast(hint, "error", {
1715
+ await showRuntimeToast(client, hint, "error", {
2411
1716
  title: "Account Persistence Failed",
2412
1717
  duration: 10000,
2413
1718
  });
@@ -2463,20 +1768,48 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2463
1768
  // Initialize storage path for manual OAuth flow
2464
1769
  // Must happen BEFORE persistAccountPool to ensure correct storage location
2465
1770
  const manualPluginConfig = loadPluginConfig();
2466
- applyUiRuntimeFromConfig(manualPluginConfig);
2467
- applyAccountStorageScope(manualPluginConfig);
1771
+ applyUiRuntimeFromConfig(manualPluginConfig, setUiRuntimeOptions);
1772
+ applyAccountStorageScopeFromConfig(manualPluginConfig, {
1773
+ getPerProjectAccounts,
1774
+ getStorageBackupEnabled,
1775
+ setStorageBackupEnabled,
1776
+ isCodexCliSyncEnabled,
1777
+ getWarningShown: () => perProjectStorageWarningShown,
1778
+ setWarningShown: (shown) => {
1779
+ perProjectStorageWarningShown = shown;
1780
+ },
1781
+ logWarn,
1782
+ pluginName: PLUGIN_NAME,
1783
+ setStoragePath,
1784
+ cwd: () => process.cwd(),
1785
+ });
2468
1786
  const { pkce, state, url } = await createAuthorizationFlow();
2469
- return buildManualOAuthFlow(pkce, url, state, async (tokens) => {
2470
- try {
2471
- await persistAccountPool([tokens], false);
2472
- }
2473
- catch (err) {
2474
- const storagePath = getStoragePath();
2475
- const errorCode = err?.code || "UNKNOWN";
2476
- const hint = err instanceof StorageError ? err.hint : formatStorageErrorHint(err, storagePath);
2477
- logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
2478
- await showToast(hint, "error", { title: "Account Persistence Failed", duration: 10000 });
2479
- }
1787
+ return buildManualOAuthFlow({
1788
+ pkce,
1789
+ url,
1790
+ expectedState: state,
1791
+ redirectUri: REDIRECT_URI,
1792
+ parseAuthorizationInput,
1793
+ exchangeAuthorizationCode,
1794
+ resolveTokenSuccess: resolveTokenSuccessAccount,
1795
+ instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
1796
+ onSuccess: async (tokens) => {
1797
+ try {
1798
+ await persistAccountPool([tokens], false);
1799
+ }
1800
+ catch (err) {
1801
+ const storagePath = getStoragePath();
1802
+ const errorCode = err?.code || "UNKNOWN";
1803
+ const hint = err instanceof StorageError
1804
+ ? err.hint
1805
+ : formatStorageErrorHint(err, storagePath);
1806
+ logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
1807
+ await showRuntimeToast(client, hint, "error", {
1808
+ title: "Account Persistence Failed",
1809
+ duration: 10000,
1810
+ });
1811
+ }
1812
+ },
2480
1813
  });
2481
1814
  },
2482
1815
  },
@@ -2492,7 +1825,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2492
1825
  description: "List all Codex OAuth accounts and the current active index.",
2493
1826
  args: {},
2494
1827
  async execute() {
2495
- const ui = resolveUiRuntime();
1828
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2496
1829
  const storage = await loadAccounts();
2497
1830
  const storePath = getStoragePath();
2498
1831
  if (!storage || storage.accounts.length === 0) {
@@ -2531,7 +1864,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2531
1864
  badges.push(formatUiBadge(ui, "current", "accent"));
2532
1865
  if (account.enabled === false)
2533
1866
  badges.push(formatUiBadge(ui, "disabled", "danger"));
2534
- const rateLimit = formatRateLimitEntry(account, now);
1867
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime);
2535
1868
  if (rateLimit)
2536
1869
  badges.push(formatUiBadge(ui, "rate-limited", "warning"));
2537
1870
  if (typeof account.coolingDownUntil === "number" &&
@@ -2569,13 +1902,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2569
1902
  storage.accounts.forEach((account, index) => {
2570
1903
  const label = formatAccountLabel(account, index);
2571
1904
  const statuses = [];
2572
- const rateLimit = formatRateLimitEntry(account, now);
1905
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime);
2573
1906
  if (index === activeIndex)
2574
1907
  statuses.push("active");
2575
1908
  if (rateLimit)
2576
1909
  statuses.push("rate-limited");
2577
- if (typeof account.coolingDownUntil ===
2578
- "number" &&
1910
+ if (typeof account.coolingDownUntil === "number" &&
2579
1911
  account.coolingDownUntil > now) {
2580
1912
  statuses.push("cooldown");
2581
1913
  }
@@ -2596,10 +1928,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2596
1928
  "codex-switch": tool({
2597
1929
  description: "Switch active Codex account by index (1-based).",
2598
1930
  args: {
2599
- index: tool.schema.number().describe("Account number to switch to (1-based, e.g., 1 for first account)"),
1931
+ index: tool.schema
1932
+ .number()
1933
+ .describe("Account number to switch to (1-based, e.g., 1 for first account)"),
2600
1934
  },
2601
1935
  async execute({ index }) {
2602
- const ui = resolveUiRuntime();
1936
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2603
1937
  const storage = await loadAccounts();
2604
1938
  if (!storage || storage.accounts.length === 0) {
2605
1939
  if (ui.v2Enabled) {
@@ -2641,7 +1975,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2641
1975
  await saveAccounts(storage);
2642
1976
  }
2643
1977
  catch (saveError) {
2644
- logWarn("Failed to save account switch", { error: String(saveError) });
1978
+ logWarn("Failed to save account switch", {
1979
+ error: String(saveError),
1980
+ });
2645
1981
  if (ui.v2Enabled) {
2646
1982
  return [
2647
1983
  ...formatUiHeader(ui, "Switch account"),
@@ -2653,7 +1989,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2653
1989
  return `Switched to ${formatAccountLabel(account, targetIndex)} but failed to persist. Changes may be lost on restart.`;
2654
1990
  }
2655
1991
  if (cachedAccountManager) {
2656
- await reloadAccountManagerFromDisk();
1992
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
2657
1993
  }
2658
1994
  const label = formatAccountLabel(account, targetIndex);
2659
1995
  if (ui.v2Enabled) {
@@ -2670,7 +2006,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2670
2006
  description: "Show detailed status of Codex accounts and rate limits.",
2671
2007
  args: {},
2672
2008
  async execute() {
2673
- const ui = resolveUiRuntime();
2009
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2674
2010
  const storage = await loadAccounts();
2675
2011
  if (!storage || storage.accounts.length === 0) {
2676
2012
  if (ui.v2Enabled) {
@@ -2699,7 +2035,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2699
2035
  badges.push(formatUiBadge(ui, "active", "accent"));
2700
2036
  if (account.enabled === false)
2701
2037
  badges.push(formatUiBadge(ui, "disabled", "danger"));
2702
- const rateLimit = formatRateLimitEntry(account, now) ?? "none";
2038
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime) ?? "none";
2703
2039
  const cooldown = formatCooldown(account, now) ?? "none";
2704
2040
  if (rateLimit !== "none")
2705
2041
  badges.push(formatUiBadge(ui, "rate-limited", "warning"));
@@ -2715,7 +2051,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2715
2051
  lines.push(...formatUiSection(ui, "Active index by model family"));
2716
2052
  for (const family of MODEL_FAMILIES) {
2717
2053
  const idx = storage.activeIndexByFamily?.[family];
2718
- const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx) ? String(idx + 1) : "-";
2054
+ const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx)
2055
+ ? String(idx + 1)
2056
+ : "-";
2719
2057
  lines.push(formatUiItem(ui, `${family}: ${familyIndexLabel}`));
2720
2058
  }
2721
2059
  lines.push("");
@@ -2749,18 +2087,27 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2749
2087
  storage.accounts.forEach((account, index) => {
2750
2088
  const label = formatAccountLabel(account, index);
2751
2089
  const active = index === activeIndex ? "Yes" : "No";
2752
- const rateLimit = formatRateLimitEntry(account, now) ?? "None";
2090
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime) ?? "None";
2753
2091
  const cooldown = formatCooldown(account, now) ?? "No";
2754
2092
  const lastUsed = typeof account.lastUsed === "number" && account.lastUsed > 0
2755
2093
  ? `${formatWaitTime(now - account.lastUsed)} ago`
2756
2094
  : "-";
2757
- lines.push(buildTableRow([String(index + 1), label, active, rateLimit, cooldown, lastUsed], statusTableOptions));
2095
+ lines.push(buildTableRow([
2096
+ String(index + 1),
2097
+ label,
2098
+ active,
2099
+ rateLimit,
2100
+ cooldown,
2101
+ lastUsed,
2102
+ ], statusTableOptions));
2758
2103
  });
2759
2104
  lines.push("");
2760
2105
  lines.push("Active index by model family:");
2761
2106
  for (const family of MODEL_FAMILIES) {
2762
2107
  const idx = storage.activeIndexByFamily?.[family];
2763
- const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx) ? String(idx + 1) : "-";
2108
+ const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx)
2109
+ ? String(idx + 1)
2110
+ : "-";
2764
2111
  lines.push(` ${family}: ${familyIndexLabel}`);
2765
2112
  }
2766
2113
  lines.push("");
@@ -2777,95 +2124,97 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2777
2124
  return lines.join("\n");
2778
2125
  },
2779
2126
  }),
2780
- ...(exposeAdvancedCodexTools ? {
2781
- "codex-metrics": tool({
2782
- description: "Show runtime request metrics for this plugin process.",
2783
- args: {},
2784
- execute() {
2785
- const ui = resolveUiRuntime();
2786
- const now = Date.now();
2787
- const uptimeMs = Math.max(0, now - runtimeMetrics.startedAt);
2788
- const total = runtimeMetrics.totalRequests;
2789
- const successful = runtimeMetrics.successfulRequests;
2790
- const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) : "0.0";
2791
- const avgLatencyMs = successful > 0
2792
- ? Math.round(runtimeMetrics.cumulativeLatencyMs / successful)
2793
- : 0;
2794
- const liveSyncSnapshot = liveAccountSync?.getSnapshot();
2795
- const guardianStats = refreshGuardian?.getStats();
2796
- const sessionAffinityEntries = sessionAffinityStore?.size() ?? 0;
2797
- const lastRequest = runtimeMetrics.lastRequestAt !== null
2798
- ? `${formatWaitTime(now - runtimeMetrics.lastRequestAt)} ago`
2799
- : "never";
2800
- const lines = [
2801
- "Codex Plugin Metrics:",
2802
- "",
2803
- `Uptime: ${formatWaitTime(uptimeMs)}`,
2804
- `Total upstream requests: ${total}`,
2805
- `Successful responses: ${successful}`,
2806
- `Failed responses: ${runtimeMetrics.failedRequests}`,
2807
- `Success rate: ${successRate}%`,
2808
- `Average successful latency: ${avgLatencyMs}ms`,
2809
- `Rate-limited responses: ${runtimeMetrics.rateLimitedResponses}`,
2810
- `Server errors (5xx): ${runtimeMetrics.serverErrors}`,
2811
- `Network errors: ${runtimeMetrics.networkErrors}`,
2812
- `User aborts: ${runtimeMetrics.userAborts}`,
2813
- `Auth refresh failures: ${runtimeMetrics.authRefreshFailures}`,
2814
- `Account rotations: ${runtimeMetrics.accountRotations}`,
2815
- `Same-account retries: ${runtimeMetrics.sameAccountRetries}`,
2816
- `Stream failover attempts: ${runtimeMetrics.streamFailoverAttempts}`,
2817
- `Stream failover recoveries: ${runtimeMetrics.streamFailoverRecoveries}`,
2818
- `Stream failover cross-account recoveries: ${runtimeMetrics.streamFailoverCrossAccountRecoveries}`,
2819
- `Empty-response retries: ${runtimeMetrics.emptyResponseRetries}`,
2820
- `Session affinity entries: ${sessionAffinityEntries}`,
2821
- `Live sync: ${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`,
2822
- `Refresh guardian: ${guardianStats ? "on" : "off"} (${guardianStats?.refreshed ?? 0} refreshed, ${guardianStats?.failed ?? 0} failed)`,
2823
- `Last upstream request: ${lastRequest}`,
2824
- ];
2825
- if (runtimeMetrics.lastError) {
2826
- lines.push(`Last error: ${runtimeMetrics.lastError}`);
2827
- }
2828
- if (ui.v2Enabled) {
2829
- const styled = [
2830
- ...formatUiHeader(ui, "Codex plugin metrics"),
2831
- formatUiKeyValue(ui, "Uptime", formatWaitTime(uptimeMs)),
2832
- formatUiKeyValue(ui, "Total upstream requests", String(total)),
2833
- formatUiKeyValue(ui, "Successful responses", String(successful), "success"),
2834
- formatUiKeyValue(ui, "Failed responses", String(runtimeMetrics.failedRequests), "danger"),
2835
- formatUiKeyValue(ui, "Success rate", `${successRate}%`, "accent"),
2836
- formatUiKeyValue(ui, "Average successful latency", `${avgLatencyMs}ms`),
2837
- formatUiKeyValue(ui, "Rate-limited responses", String(runtimeMetrics.rateLimitedResponses), "warning"),
2838
- formatUiKeyValue(ui, "Server errors (5xx)", String(runtimeMetrics.serverErrors), "danger"),
2839
- formatUiKeyValue(ui, "Network errors", String(runtimeMetrics.networkErrors), "danger"),
2840
- formatUiKeyValue(ui, "User aborts", String(runtimeMetrics.userAborts), "muted"),
2841
- formatUiKeyValue(ui, "Auth refresh failures", String(runtimeMetrics.authRefreshFailures), "warning"),
2842
- formatUiKeyValue(ui, "Account rotations", String(runtimeMetrics.accountRotations), "accent"),
2843
- formatUiKeyValue(ui, "Same-account retries", String(runtimeMetrics.sameAccountRetries), "warning"),
2844
- formatUiKeyValue(ui, "Stream failover attempts", String(runtimeMetrics.streamFailoverAttempts), "muted"),
2845
- formatUiKeyValue(ui, "Stream failover recoveries", String(runtimeMetrics.streamFailoverRecoveries), "success"),
2846
- formatUiKeyValue(ui, "Stream failover cross-account recoveries", String(runtimeMetrics.streamFailoverCrossAccountRecoveries), "accent"),
2847
- formatUiKeyValue(ui, "Empty-response retries", String(runtimeMetrics.emptyResponseRetries), "warning"),
2848
- formatUiKeyValue(ui, "Session affinity entries", String(sessionAffinityEntries), "muted"),
2849
- formatUiKeyValue(ui, "Live sync", `${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`, liveSyncSnapshot?.running ? "success" : "muted"),
2850
- formatUiKeyValue(ui, "Refresh guardian", guardianStats
2851
- ? `on (${guardianStats.refreshed} refreshed, ${guardianStats.failed} failed)`
2852
- : "off", guardianStats ? "success" : "muted"),
2853
- formatUiKeyValue(ui, "Last upstream request", lastRequest, "muted"),
2127
+ ...(exposeAdvancedCodexTools
2128
+ ? {
2129
+ "codex-metrics": tool({
2130
+ description: "Show runtime request metrics for this plugin process.",
2131
+ args: {},
2132
+ execute() {
2133
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2134
+ const now = Date.now();
2135
+ const uptimeMs = Math.max(0, now - runtimeMetrics.startedAt);
2136
+ const total = runtimeMetrics.totalRequests;
2137
+ const successful = runtimeMetrics.successfulRequests;
2138
+ const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) : "0.0";
2139
+ const avgLatencyMs = successful > 0
2140
+ ? Math.round(runtimeMetrics.cumulativeLatencyMs / successful)
2141
+ : 0;
2142
+ const liveSyncSnapshot = liveAccountSync?.getSnapshot();
2143
+ const guardianStats = refreshGuardian?.getStats();
2144
+ const sessionAffinityEntries = sessionAffinityStore?.size() ?? 0;
2145
+ const lastRequest = runtimeMetrics.lastRequestAt !== null
2146
+ ? `${formatWaitTime(now - runtimeMetrics.lastRequestAt)} ago`
2147
+ : "never";
2148
+ const lines = [
2149
+ "Codex Plugin Metrics:",
2150
+ "",
2151
+ `Uptime: ${formatWaitTime(uptimeMs)}`,
2152
+ `Total upstream requests: ${total}`,
2153
+ `Successful responses: ${successful}`,
2154
+ `Failed responses: ${runtimeMetrics.failedRequests}`,
2155
+ `Success rate: ${successRate}%`,
2156
+ `Average successful latency: ${avgLatencyMs}ms`,
2157
+ `Rate-limited responses: ${runtimeMetrics.rateLimitedResponses}`,
2158
+ `Server errors (5xx): ${runtimeMetrics.serverErrors}`,
2159
+ `Network errors: ${runtimeMetrics.networkErrors}`,
2160
+ `User aborts: ${runtimeMetrics.userAborts}`,
2161
+ `Auth refresh failures: ${runtimeMetrics.authRefreshFailures}`,
2162
+ `Account rotations: ${runtimeMetrics.accountRotations}`,
2163
+ `Same-account retries: ${runtimeMetrics.sameAccountRetries}`,
2164
+ `Stream failover attempts: ${runtimeMetrics.streamFailoverAttempts}`,
2165
+ `Stream failover recoveries: ${runtimeMetrics.streamFailoverRecoveries}`,
2166
+ `Stream failover cross-account recoveries: ${runtimeMetrics.streamFailoverCrossAccountRecoveries}`,
2167
+ `Empty-response retries: ${runtimeMetrics.emptyResponseRetries}`,
2168
+ `Session affinity entries: ${sessionAffinityEntries}`,
2169
+ `Live sync: ${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`,
2170
+ `Refresh guardian: ${guardianStats ? "on" : "off"} (${guardianStats?.refreshed ?? 0} refreshed, ${guardianStats?.failed ?? 0} failed)`,
2171
+ `Last upstream request: ${lastRequest}`,
2854
2172
  ];
2855
2173
  if (runtimeMetrics.lastError) {
2856
- styled.push(formatUiKeyValue(ui, "Last error", runtimeMetrics.lastError, "danger"));
2174
+ lines.push(`Last error: ${runtimeMetrics.lastError}`);
2857
2175
  }
2858
- return Promise.resolve(styled.join("\n"));
2859
- }
2860
- return Promise.resolve(lines.join("\n"));
2861
- },
2862
- }),
2863
- } : {}),
2176
+ if (ui.v2Enabled) {
2177
+ const styled = [
2178
+ ...formatUiHeader(ui, "Codex plugin metrics"),
2179
+ formatUiKeyValue(ui, "Uptime", formatWaitTime(uptimeMs)),
2180
+ formatUiKeyValue(ui, "Total upstream requests", String(total)),
2181
+ formatUiKeyValue(ui, "Successful responses", String(successful), "success"),
2182
+ formatUiKeyValue(ui, "Failed responses", String(runtimeMetrics.failedRequests), "danger"),
2183
+ formatUiKeyValue(ui, "Success rate", `${successRate}%`, "accent"),
2184
+ formatUiKeyValue(ui, "Average successful latency", `${avgLatencyMs}ms`),
2185
+ formatUiKeyValue(ui, "Rate-limited responses", String(runtimeMetrics.rateLimitedResponses), "warning"),
2186
+ formatUiKeyValue(ui, "Server errors (5xx)", String(runtimeMetrics.serverErrors), "danger"),
2187
+ formatUiKeyValue(ui, "Network errors", String(runtimeMetrics.networkErrors), "danger"),
2188
+ formatUiKeyValue(ui, "User aborts", String(runtimeMetrics.userAborts), "muted"),
2189
+ formatUiKeyValue(ui, "Auth refresh failures", String(runtimeMetrics.authRefreshFailures), "warning"),
2190
+ formatUiKeyValue(ui, "Account rotations", String(runtimeMetrics.accountRotations), "accent"),
2191
+ formatUiKeyValue(ui, "Same-account retries", String(runtimeMetrics.sameAccountRetries), "warning"),
2192
+ formatUiKeyValue(ui, "Stream failover attempts", String(runtimeMetrics.streamFailoverAttempts), "muted"),
2193
+ formatUiKeyValue(ui, "Stream failover recoveries", String(runtimeMetrics.streamFailoverRecoveries), "success"),
2194
+ formatUiKeyValue(ui, "Stream failover cross-account recoveries", String(runtimeMetrics.streamFailoverCrossAccountRecoveries), "accent"),
2195
+ formatUiKeyValue(ui, "Empty-response retries", String(runtimeMetrics.emptyResponseRetries), "warning"),
2196
+ formatUiKeyValue(ui, "Session affinity entries", String(sessionAffinityEntries), "muted"),
2197
+ formatUiKeyValue(ui, "Live sync", `${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`, liveSyncSnapshot?.running ? "success" : "muted"),
2198
+ formatUiKeyValue(ui, "Refresh guardian", guardianStats
2199
+ ? `on (${guardianStats.refreshed} refreshed, ${guardianStats.failed} failed)`
2200
+ : "off", guardianStats ? "success" : "muted"),
2201
+ formatUiKeyValue(ui, "Last upstream request", lastRequest, "muted"),
2202
+ ];
2203
+ if (runtimeMetrics.lastError) {
2204
+ styled.push(formatUiKeyValue(ui, "Last error", runtimeMetrics.lastError, "danger"));
2205
+ }
2206
+ return Promise.resolve(styled.join("\n"));
2207
+ }
2208
+ return Promise.resolve(lines.join("\n"));
2209
+ },
2210
+ }),
2211
+ }
2212
+ : {}),
2864
2213
  "codex-health": tool({
2865
2214
  description: "Check health of all Codex accounts by validating refresh tokens.",
2866
2215
  args: {},
2867
2216
  async execute() {
2868
- const ui = resolveUiRuntime();
2217
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2869
2218
  const storage = await loadAccounts();
2870
2219
  if (!storage || storage.accounts.length === 0) {
2871
2220
  if (ui.v2Enabled) {
@@ -2917,255 +2266,268 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2917
2266
  return results.join("\n");
2918
2267
  },
2919
2268
  }),
2920
- ...(exposeAdvancedCodexTools ? {
2921
- "codex-remove": tool({
2922
- description: "Remove a Codex account by index (1-based). Use codex-list to list accounts first.",
2923
- args: {
2924
- index: tool.schema.number().describe("Account number to remove (1-based, e.g., 1 for first account)"),
2925
- },
2926
- async execute({ index }) {
2927
- const ui = resolveUiRuntime();
2928
- const storage = await loadAccounts();
2929
- if (!storage || storage.accounts.length === 0) {
2930
- if (ui.v2Enabled) {
2931
- return [
2932
- ...formatUiHeader(ui, "Remove account"),
2933
- "",
2934
- formatUiItem(ui, "No accounts configured.", "warning"),
2935
- ].join("\n");
2269
+ ...(exposeAdvancedCodexTools
2270
+ ? {
2271
+ "codex-remove": tool({
2272
+ description: "Remove a Codex account by index (1-based). Use codex-list to list accounts first.",
2273
+ args: {
2274
+ index: tool.schema
2275
+ .number()
2276
+ .describe("Account number to remove (1-based, e.g., 1 for first account)"),
2277
+ },
2278
+ async execute({ index }) {
2279
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2280
+ const storage = await loadAccounts();
2281
+ if (!storage || storage.accounts.length === 0) {
2282
+ if (ui.v2Enabled) {
2283
+ return [
2284
+ ...formatUiHeader(ui, "Remove account"),
2285
+ "",
2286
+ formatUiItem(ui, "No accounts configured.", "warning"),
2287
+ ].join("\n");
2288
+ }
2289
+ return "No Codex accounts configured. Nothing to remove.";
2936
2290
  }
2937
- return "No Codex accounts configured. Nothing to remove.";
2938
- }
2939
- const targetIndex = Math.floor((index ?? 0) - 1);
2940
- if (!Number.isFinite(targetIndex) ||
2941
- targetIndex < 0 ||
2942
- targetIndex >= storage.accounts.length) {
2943
- if (ui.v2Enabled) {
2944
- return [
2945
- ...formatUiHeader(ui, "Remove account"),
2946
- "",
2947
- formatUiItem(ui, `Invalid account number: ${index}`, "danger"),
2948
- formatUiKeyValue(ui, "Valid range", `1-${storage.accounts.length}`, "muted"),
2949
- formatUiItem(ui, "Use codex-list to list all accounts.", "accent"),
2950
- ].join("\n");
2291
+ const targetIndex = Math.floor((index ?? 0) - 1);
2292
+ if (!Number.isFinite(targetIndex) ||
2293
+ targetIndex < 0 ||
2294
+ targetIndex >= storage.accounts.length) {
2295
+ if (ui.v2Enabled) {
2296
+ return [
2297
+ ...formatUiHeader(ui, "Remove account"),
2298
+ "",
2299
+ formatUiItem(ui, `Invalid account number: ${index}`, "danger"),
2300
+ formatUiKeyValue(ui, "Valid range", `1-${storage.accounts.length}`, "muted"),
2301
+ formatUiItem(ui, "Use codex-list to list all accounts.", "accent"),
2302
+ ].join("\n");
2303
+ }
2304
+ return `Invalid account number: ${index}\n\nValid range: 1-${storage.accounts.length}\n\nUse codex-list to list all accounts.`;
2951
2305
  }
2952
- return `Invalid account number: ${index}\n\nValid range: 1-${storage.accounts.length}\n\nUse codex-list to list all accounts.`;
2953
- }
2954
- const account = storage.accounts[targetIndex];
2955
- if (!account) {
2956
- return `Account ${index} not found.`;
2957
- }
2958
- const label = formatAccountLabel(account, targetIndex);
2959
- storage.accounts.splice(targetIndex, 1);
2960
- if (storage.accounts.length === 0) {
2961
- storage.activeIndex = 0;
2962
- storage.activeIndexByFamily = {};
2963
- }
2964
- else {
2965
- if (storage.activeIndex >= storage.accounts.length) {
2966
- storage.activeIndex = 0;
2306
+ const account = storage.accounts[targetIndex];
2307
+ if (!account) {
2308
+ return `Account ${index} not found.`;
2967
2309
  }
2968
- else if (storage.activeIndex > targetIndex) {
2969
- storage.activeIndex -= 1;
2310
+ const label = formatAccountLabel(account, targetIndex);
2311
+ storage.accounts.splice(targetIndex, 1);
2312
+ if (storage.accounts.length === 0) {
2313
+ storage.activeIndex = 0;
2314
+ storage.activeIndexByFamily = {};
2970
2315
  }
2971
- if (storage.activeIndexByFamily) {
2972
- for (const family of MODEL_FAMILIES) {
2973
- const idx = storage.activeIndexByFamily[family];
2974
- if (typeof idx === "number") {
2975
- if (idx >= storage.accounts.length) {
2976
- storage.activeIndexByFamily[family] = 0;
2977
- }
2978
- else if (idx > targetIndex) {
2979
- storage.activeIndexByFamily[family] = idx - 1;
2316
+ else {
2317
+ if (storage.activeIndex >= storage.accounts.length) {
2318
+ storage.activeIndex = 0;
2319
+ }
2320
+ else if (storage.activeIndex > targetIndex) {
2321
+ storage.activeIndex -= 1;
2322
+ }
2323
+ if (storage.activeIndexByFamily) {
2324
+ for (const family of MODEL_FAMILIES) {
2325
+ const idx = storage.activeIndexByFamily[family];
2326
+ if (typeof idx === "number") {
2327
+ if (idx >= storage.accounts.length) {
2328
+ storage.activeIndexByFamily[family] = 0;
2329
+ }
2330
+ else if (idx > targetIndex) {
2331
+ storage.activeIndexByFamily[family] = idx - 1;
2332
+ }
2980
2333
  }
2981
2334
  }
2982
2335
  }
2983
2336
  }
2984
- }
2985
- try {
2986
- await saveAccounts(storage);
2987
- }
2988
- catch (saveError) {
2989
- logWarn("Failed to save account removal", { error: String(saveError) });
2337
+ try {
2338
+ await saveAccounts(storage);
2339
+ }
2340
+ catch (saveError) {
2341
+ logWarn("Failed to save account removal", {
2342
+ error: String(saveError),
2343
+ });
2344
+ if (ui.v2Enabled) {
2345
+ return [
2346
+ ...formatUiHeader(ui, "Remove account"),
2347
+ "",
2348
+ formatUiItem(ui, `Removed ${formatAccountLabel(account, targetIndex)} from memory`, "warning"),
2349
+ formatUiItem(ui, "Failed to persist. Change may be lost on restart.", "danger"),
2350
+ ].join("\n");
2351
+ }
2352
+ return `Removed ${formatAccountLabel(account, targetIndex)} from memory but failed to persist. Changes may be lost on restart.`;
2353
+ }
2354
+ if (cachedAccountManager) {
2355
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
2356
+ }
2357
+ const remaining = storage.accounts.length;
2990
2358
  if (ui.v2Enabled) {
2991
2359
  return [
2992
2360
  ...formatUiHeader(ui, "Remove account"),
2993
2361
  "",
2994
- formatUiItem(ui, `Removed ${formatAccountLabel(account, targetIndex)} from memory`, "warning"),
2995
- formatUiItem(ui, "Failed to persist. Change may be lost on restart.", "danger"),
2362
+ formatUiItem(ui, `${getStatusMarker(ui, "ok")} Removed: ${label}`, "success"),
2363
+ remaining > 0
2364
+ ? formatUiKeyValue(ui, "Remaining accounts", String(remaining))
2365
+ : formatUiItem(ui, "No accounts remaining. Run: codex login", "warning"),
2996
2366
  ].join("\n");
2997
2367
  }
2998
- return `Removed ${formatAccountLabel(account, targetIndex)} from memory but failed to persist. Changes may be lost on restart.`;
2999
- }
3000
- if (cachedAccountManager) {
3001
- await reloadAccountManagerFromDisk();
3002
- }
3003
- const remaining = storage.accounts.length;
3004
- if (ui.v2Enabled) {
3005
2368
  return [
3006
- ...formatUiHeader(ui, "Remove account"),
2369
+ `Removed: ${label}`,
3007
2370
  "",
3008
- formatUiItem(ui, `${getStatusMarker(ui, "ok")} Removed: ${label}`, "success"),
3009
2371
  remaining > 0
3010
- ? formatUiKeyValue(ui, "Remaining accounts", String(remaining))
3011
- : formatUiItem(ui, "No accounts remaining. Run: codex login", "warning"),
2372
+ ? `Remaining accounts: ${remaining}`
2373
+ : "No accounts remaining. Run: codex login",
3012
2374
  ].join("\n");
3013
- }
3014
- return [
3015
- `Removed: ${label}`,
3016
- "",
3017
- remaining > 0
3018
- ? `Remaining accounts: ${remaining}`
3019
- : "No accounts remaining. Run: codex login",
3020
- ].join("\n");
3021
- },
3022
- }),
3023
- "codex-refresh": tool({
3024
- description: "Manually refresh OAuth tokens for all accounts to verify they're still valid.",
3025
- args: {},
3026
- async execute() {
3027
- const ui = resolveUiRuntime();
3028
- const storage = await loadAccounts();
3029
- if (!storage || storage.accounts.length === 0) {
3030
- if (ui.v2Enabled) {
3031
- return [
3032
- ...formatUiHeader(ui, "Refresh accounts"),
3033
- "",
3034
- formatUiItem(ui, "No accounts configured.", "warning"),
3035
- formatUiItem(ui, "Run: codex login", "accent"),
3036
- ].join("\n");
2375
+ },
2376
+ }),
2377
+ "codex-refresh": tool({
2378
+ description: "Manually refresh OAuth tokens for all accounts to verify they're still valid.",
2379
+ args: {},
2380
+ async execute() {
2381
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2382
+ const storage = await loadAccounts();
2383
+ if (!storage || storage.accounts.length === 0) {
2384
+ if (ui.v2Enabled) {
2385
+ return [
2386
+ ...formatUiHeader(ui, "Refresh accounts"),
2387
+ "",
2388
+ formatUiItem(ui, "No accounts configured.", "warning"),
2389
+ formatUiItem(ui, "Run: codex login", "accent"),
2390
+ ].join("\n");
2391
+ }
2392
+ return "No Codex accounts configured. Run: codex login";
3037
2393
  }
3038
- return "No Codex accounts configured. Run: codex login";
3039
- }
3040
- const results = ui.v2Enabled
3041
- ? []
3042
- : [`Refreshing ${storage.accounts.length} account(s):`, ""];
3043
- let refreshedCount = 0;
3044
- let failedCount = 0;
3045
- for (let i = 0; i < storage.accounts.length; i++) {
3046
- const account = storage.accounts[i];
3047
- if (!account)
3048
- continue;
3049
- const label = formatAccountLabel(account, i);
3050
- try {
3051
- const refreshResult = await queuedRefresh(account.refreshToken);
3052
- if (refreshResult.type === "success") {
3053
- account.refreshToken = refreshResult.refresh;
3054
- account.accessToken = refreshResult.access;
3055
- account.expiresAt = refreshResult.expires;
3056
- results.push(` ${getStatusMarker(ui, "ok")} ${label}: Refreshed`);
3057
- refreshedCount++;
2394
+ const results = ui.v2Enabled
2395
+ ? []
2396
+ : [`Refreshing ${storage.accounts.length} account(s):`, ""];
2397
+ let refreshedCount = 0;
2398
+ let failedCount = 0;
2399
+ for (let i = 0; i < storage.accounts.length; i++) {
2400
+ const account = storage.accounts[i];
2401
+ if (!account)
2402
+ continue;
2403
+ const label = formatAccountLabel(account, i);
2404
+ try {
2405
+ const refreshResult = await queuedRefresh(account.refreshToken);
2406
+ if (refreshResult.type === "success") {
2407
+ account.refreshToken = refreshResult.refresh;
2408
+ account.accessToken = refreshResult.access;
2409
+ account.expiresAt = refreshResult.expires;
2410
+ results.push(` ${getStatusMarker(ui, "ok")} ${label}: Refreshed`);
2411
+ refreshedCount++;
2412
+ }
2413
+ else {
2414
+ results.push(` ${getStatusMarker(ui, "error")} ${label}: Failed - ${refreshResult.message ?? refreshResult.reason}`);
2415
+ failedCount++;
2416
+ }
3058
2417
  }
3059
- else {
3060
- results.push(` ${getStatusMarker(ui, "error")} ${label}: Failed - ${refreshResult.message ?? refreshResult.reason}`);
2418
+ catch (error) {
2419
+ const errorMsg = error instanceof Error ? error.message : String(error);
2420
+ results.push(` ${getStatusMarker(ui, "error")} ${label}: Error - ${errorMsg.slice(0, 120)}`);
3061
2421
  failedCount++;
3062
2422
  }
3063
2423
  }
3064
- catch (error) {
3065
- const errorMsg = error instanceof Error ? error.message : String(error);
3066
- results.push(` ${getStatusMarker(ui, "error")} ${label}: Error - ${errorMsg.slice(0, 120)}`);
3067
- failedCount++;
3068
- }
3069
- }
3070
- await saveAccounts(storage);
3071
- if (cachedAccountManager) {
3072
- await reloadAccountManagerFromDisk();
3073
- }
3074
- results.push("");
3075
- results.push(`Summary: ${refreshedCount} refreshed, ${failedCount} failed`);
3076
- if (ui.v2Enabled) {
3077
- return [
3078
- ...formatUiHeader(ui, "Refresh accounts"),
3079
- "",
3080
- ...results.map((line) => paintUiText(ui, line, "normal")),
3081
- ].join("\n");
3082
- }
3083
- return results.join("\n");
3084
- },
3085
- }),
3086
- "codex-export": tool({
3087
- description: "Export accounts to a JSON file for backup or migration to another machine.",
3088
- args: {
3089
- path: tool.schema.string().describe("File path to export to (e.g., ~/codex-backup.json)"),
3090
- force: tool.schema.boolean().optional().describe("Overwrite existing file (default: true)"),
3091
- },
3092
- async execute({ path: filePath, force }) {
3093
- const ui = resolveUiRuntime();
3094
- try {
3095
- await exportAccounts(filePath, force ?? true);
3096
- const storage = await loadAccounts();
3097
- const count = storage?.accounts.length ?? 0;
3098
- if (ui.v2Enabled) {
3099
- return [
3100
- ...formatUiHeader(ui, "Export accounts"),
3101
- "",
3102
- formatUiItem(ui, `${getStatusMarker(ui, "ok")} Exported ${count} account(s)`, "success"),
3103
- formatUiKeyValue(ui, "Path", filePath, "muted"),
3104
- ].join("\n");
2424
+ await saveAccounts(storage);
2425
+ if (cachedAccountManager) {
2426
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
3105
2427
  }
3106
- return `Exported ${count} account(s) to: ${filePath}`;
3107
- }
3108
- catch (error) {
3109
- const msg = error instanceof Error ? error.message : String(error);
2428
+ results.push("");
2429
+ results.push(`Summary: ${refreshedCount} refreshed, ${failedCount} failed`);
3110
2430
  if (ui.v2Enabled) {
3111
2431
  return [
3112
- ...formatUiHeader(ui, "Export accounts"),
2432
+ ...formatUiHeader(ui, "Refresh accounts"),
3113
2433
  "",
3114
- formatUiItem(ui, `${getStatusMarker(ui, "error")} Export failed`, "danger"),
3115
- formatUiKeyValue(ui, "Error", msg, "danger"),
2434
+ ...results.map((line) => paintUiText(ui, line, "normal")),
3116
2435
  ].join("\n");
3117
2436
  }
3118
- return `Export failed: ${msg}`;
3119
- }
3120
- },
3121
- }),
3122
- "codex-import": tool({
3123
- description: "Import accounts from a JSON file, merging with existing accounts.",
3124
- args: {
3125
- path: tool.schema.string().describe("File path to import from (e.g., ~/codex-backup.json)"),
3126
- },
3127
- async execute({ path: filePath }) {
3128
- const ui = resolveUiRuntime();
3129
- try {
3130
- const result = await importAccounts(filePath);
3131
- invalidateAccountManagerCache();
3132
- const lines = [`Import complete.`, ``];
3133
- if (result.imported > 0) {
3134
- lines.push(`New accounts: ${result.imported}`);
2437
+ return results.join("\n");
2438
+ },
2439
+ }),
2440
+ "codex-export": tool({
2441
+ description: "Export accounts to a JSON file for backup or migration to another machine.",
2442
+ args: {
2443
+ path: tool.schema
2444
+ .string()
2445
+ .describe("File path to export to (e.g., ~/codex-backup.json)"),
2446
+ force: tool.schema
2447
+ .boolean()
2448
+ .optional()
2449
+ .describe("Overwrite existing file (default: true)"),
2450
+ },
2451
+ async execute({ path: filePath, force }) {
2452
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2453
+ try {
2454
+ await exportAccounts(filePath, force ?? true);
2455
+ const storage = await loadAccounts();
2456
+ const count = storage?.accounts.length ?? 0;
2457
+ if (ui.v2Enabled) {
2458
+ return [
2459
+ ...formatUiHeader(ui, "Export accounts"),
2460
+ "",
2461
+ formatUiItem(ui, `${getStatusMarker(ui, "ok")} Exported ${count} account(s)`, "success"),
2462
+ formatUiKeyValue(ui, "Path", filePath, "muted"),
2463
+ ].join("\n");
2464
+ }
2465
+ return `Exported ${count} account(s) to: ${filePath}`;
3135
2466
  }
3136
- if (result.skipped > 0) {
3137
- lines.push(`Duplicates skipped: ${result.skipped}`);
2467
+ catch (error) {
2468
+ const msg = error instanceof Error ? error.message : String(error);
2469
+ if (ui.v2Enabled) {
2470
+ return [
2471
+ ...formatUiHeader(ui, "Export accounts"),
2472
+ "",
2473
+ formatUiItem(ui, `${getStatusMarker(ui, "error")} Export failed`, "danger"),
2474
+ formatUiKeyValue(ui, "Error", msg, "danger"),
2475
+ ].join("\n");
2476
+ }
2477
+ return `Export failed: ${msg}`;
3138
2478
  }
3139
- lines.push(`Total accounts: ${result.total}`);
3140
- if (ui.v2Enabled) {
3141
- const styled = [
3142
- ...formatUiHeader(ui, "Import accounts"),
3143
- "",
3144
- formatUiItem(ui, `${getStatusMarker(ui, "ok")} Import complete`, "success"),
3145
- formatUiKeyValue(ui, "Path", filePath, "muted"),
3146
- formatUiKeyValue(ui, "New accounts", String(result.imported), result.imported > 0 ? "success" : "muted"),
3147
- formatUiKeyValue(ui, "Duplicates skipped", String(result.skipped), result.skipped > 0 ? "warning" : "muted"),
3148
- formatUiKeyValue(ui, "Total accounts", String(result.total), "accent"),
3149
- ];
3150
- return styled.join("\n");
2479
+ },
2480
+ }),
2481
+ "codex-import": tool({
2482
+ description: "Import accounts from a JSON file, merging with existing accounts.",
2483
+ args: {
2484
+ path: tool.schema
2485
+ .string()
2486
+ .describe("File path to import from (e.g., ~/codex-backup.json)"),
2487
+ },
2488
+ async execute({ path: filePath }) {
2489
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2490
+ try {
2491
+ const result = await importAccounts(filePath);
2492
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2493
+ const lines = [`Import complete.`, ``];
2494
+ if (result.imported > 0) {
2495
+ lines.push(`New accounts: ${result.imported}`);
2496
+ }
2497
+ if (result.skipped > 0) {
2498
+ lines.push(`Duplicates skipped: ${result.skipped}`);
2499
+ }
2500
+ lines.push(`Total accounts: ${result.total}`);
2501
+ if (ui.v2Enabled) {
2502
+ const styled = [
2503
+ ...formatUiHeader(ui, "Import accounts"),
2504
+ "",
2505
+ formatUiItem(ui, `${getStatusMarker(ui, "ok")} Import complete`, "success"),
2506
+ formatUiKeyValue(ui, "Path", filePath, "muted"),
2507
+ formatUiKeyValue(ui, "New accounts", String(result.imported), result.imported > 0 ? "success" : "muted"),
2508
+ formatUiKeyValue(ui, "Duplicates skipped", String(result.skipped), result.skipped > 0 ? "warning" : "muted"),
2509
+ formatUiKeyValue(ui, "Total accounts", String(result.total), "accent"),
2510
+ ];
2511
+ return styled.join("\n");
2512
+ }
2513
+ return lines.join("\n");
3151
2514
  }
3152
- return lines.join("\n");
3153
- }
3154
- catch (error) {
3155
- const msg = error instanceof Error ? error.message : String(error);
3156
- if (ui.v2Enabled) {
3157
- return [
3158
- ...formatUiHeader(ui, "Import accounts"),
3159
- "",
3160
- formatUiItem(ui, `${getStatusMarker(ui, "error")} Import failed`, "danger"),
3161
- formatUiKeyValue(ui, "Error", msg, "danger"),
3162
- ].join("\n");
2515
+ catch (error) {
2516
+ const msg = error instanceof Error ? error.message : String(error);
2517
+ if (ui.v2Enabled) {
2518
+ return [
2519
+ ...formatUiHeader(ui, "Import accounts"),
2520
+ "",
2521
+ formatUiItem(ui, `${getStatusMarker(ui, "error")} Import failed`, "danger"),
2522
+ formatUiKeyValue(ui, "Error", msg, "danger"),
2523
+ ].join("\n");
2524
+ }
2525
+ return `Import failed: ${msg}`;
3163
2526
  }
3164
- return `Import failed: ${msg}`;
3165
- }
3166
- },
3167
- }),
3168
- } : {}),
2527
+ },
2528
+ }),
2529
+ }
2530
+ : {}),
3169
2531
  },
3170
2532
  };
3171
2533
  };