codex-multi-auth 1.2.0 → 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 (675) hide show
  1. package/README.md +49 -32
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +910 -1704
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/auth/index.d.ts +2 -0
  6. package/dist/lib/auth/index.d.ts.map +1 -0
  7. package/dist/lib/auth/index.js +2 -0
  8. package/dist/lib/auth/index.js.map +1 -0
  9. package/dist/lib/codex-cli/index.d.ts +5 -0
  10. package/dist/lib/codex-cli/index.d.ts.map +1 -0
  11. package/dist/lib/codex-cli/index.js +5 -0
  12. package/dist/lib/codex-cli/index.js.map +1 -0
  13. package/dist/lib/codex-manager/auth-commands.d.ts +72 -0
  14. package/dist/lib/codex-manager/auth-commands.d.ts.map +1 -0
  15. package/dist/lib/codex-manager/auth-commands.js +643 -0
  16. package/dist/lib/codex-manager/auth-commands.js.map +1 -0
  17. package/dist/lib/codex-manager/backend-category-entry.d.ts +103 -0
  18. package/dist/lib/codex-manager/backend-category-entry.d.ts.map +1 -0
  19. package/dist/lib/codex-manager/backend-category-entry.js +23 -0
  20. package/dist/lib/codex-manager/backend-category-entry.js.map +1 -0
  21. package/dist/lib/codex-manager/backend-category-helpers.d.ts +10 -0
  22. package/dist/lib/codex-manager/backend-category-helpers.d.ts.map +1 -0
  23. package/dist/lib/codex-manager/backend-category-helpers.js +29 -0
  24. package/dist/lib/codex-manager/backend-category-helpers.js.map +1 -0
  25. package/dist/lib/codex-manager/backend-category-prompt.d.ts +55 -0
  26. package/dist/lib/codex-manager/backend-category-prompt.d.ts.map +1 -0
  27. package/dist/lib/codex-manager/backend-category-prompt.js +178 -0
  28. package/dist/lib/codex-manager/backend-category-prompt.js.map +1 -0
  29. package/dist/lib/codex-manager/backend-settings-controller.d.ts +11 -0
  30. package/dist/lib/codex-manager/backend-settings-controller.d.ts.map +1 -0
  31. package/dist/lib/codex-manager/backend-settings-controller.js +14 -0
  32. package/dist/lib/codex-manager/backend-settings-controller.js.map +1 -0
  33. package/dist/lib/codex-manager/backend-settings-entry.d.ts +20 -0
  34. package/dist/lib/codex-manager/backend-settings-entry.d.ts.map +1 -0
  35. package/dist/lib/codex-manager/backend-settings-entry.js +12 -0
  36. package/dist/lib/codex-manager/backend-settings-entry.js.map +1 -0
  37. package/dist/lib/codex-manager/backend-settings-helpers.d.ts +16 -0
  38. package/dist/lib/codex-manager/backend-settings-helpers.d.ts.map +1 -0
  39. package/dist/lib/codex-manager/backend-settings-helpers.js +102 -0
  40. package/dist/lib/codex-manager/backend-settings-helpers.js.map +1 -0
  41. package/dist/lib/codex-manager/backend-settings-prompt.d.ts +49 -0
  42. package/dist/lib/codex-manager/backend-settings-prompt.d.ts.map +1 -0
  43. package/dist/lib/codex-manager/backend-settings-prompt.js +121 -0
  44. package/dist/lib/codex-manager/backend-settings-prompt.js.map +1 -0
  45. package/dist/lib/codex-manager/backend-settings-schema.d.ts +55 -0
  46. package/dist/lib/codex-manager/backend-settings-schema.d.ts.map +1 -0
  47. package/dist/lib/codex-manager/backend-settings-schema.js +264 -0
  48. package/dist/lib/codex-manager/backend-settings-schema.js.map +1 -0
  49. package/dist/lib/codex-manager/behavior-settings-panel.d.ts +33 -0
  50. package/dist/lib/codex-manager/behavior-settings-panel.d.ts.map +1 -0
  51. package/dist/lib/codex-manager/behavior-settings-panel.js +180 -0
  52. package/dist/lib/codex-manager/behavior-settings-panel.js.map +1 -0
  53. package/dist/lib/codex-manager/best.d.ts +26 -0
  54. package/dist/lib/codex-manager/best.d.ts.map +1 -0
  55. package/dist/lib/codex-manager/best.js +249 -0
  56. package/dist/lib/codex-manager/best.js.map +1 -0
  57. package/dist/lib/codex-manager/commands/best.d.ts +81 -0
  58. package/dist/lib/codex-manager/commands/best.d.ts.map +1 -0
  59. package/dist/lib/codex-manager/commands/best.js +223 -0
  60. package/dist/lib/codex-manager/commands/best.js.map +1 -0
  61. package/dist/lib/codex-manager/commands/check.d.ts +7 -0
  62. package/dist/lib/codex-manager/commands/check.d.ts.map +1 -0
  63. package/dist/lib/codex-manager/commands/check.js +5 -0
  64. package/dist/lib/codex-manager/commands/check.js.map +1 -0
  65. package/dist/lib/codex-manager/commands/config-explain.d.ts +7 -0
  66. package/dist/lib/codex-manager/commands/config-explain.d.ts.map +1 -0
  67. package/dist/lib/codex-manager/commands/config-explain.js +31 -0
  68. package/dist/lib/codex-manager/commands/config-explain.js.map +1 -0
  69. package/dist/lib/codex-manager/commands/debug-bundle.d.ts +28 -0
  70. package/dist/lib/codex-manager/commands/debug-bundle.d.ts.map +1 -0
  71. package/dist/lib/codex-manager/commands/debug-bundle.js +62 -0
  72. package/dist/lib/codex-manager/commands/debug-bundle.js.map +1 -0
  73. package/dist/lib/codex-manager/commands/doctor.d.ts +85 -0
  74. package/dist/lib/codex-manager/commands/doctor.d.ts.map +1 -0
  75. package/dist/lib/codex-manager/commands/doctor.js +424 -0
  76. package/dist/lib/codex-manager/commands/doctor.js.map +1 -0
  77. package/dist/lib/codex-manager/commands/fix.d.ts +92 -0
  78. package/dist/lib/codex-manager/commands/fix.d.ts.map +1 -0
  79. package/dist/lib/codex-manager/commands/fix.js +347 -0
  80. package/dist/lib/codex-manager/commands/fix.js.map +1 -0
  81. package/dist/lib/codex-manager/commands/forecast.d.ts +70 -0
  82. package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -0
  83. package/dist/lib/codex-manager/commands/forecast.js +281 -0
  84. package/dist/lib/codex-manager/commands/forecast.js.map +1 -0
  85. package/dist/lib/codex-manager/commands/init-config.d.ts +15 -0
  86. package/dist/lib/codex-manager/commands/init-config.d.ts.map +1 -0
  87. package/dist/lib/codex-manager/commands/init-config.js +95 -0
  88. package/dist/lib/codex-manager/commands/init-config.js.map +1 -0
  89. package/dist/lib/codex-manager/commands/report.d.ts +24 -0
  90. package/dist/lib/codex-manager/commands/report.d.ts.map +1 -0
  91. package/dist/lib/codex-manager/commands/report.js +314 -0
  92. package/dist/lib/codex-manager/commands/report.js.map +1 -0
  93. package/dist/lib/codex-manager/commands/status.d.ts +23 -0
  94. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -0
  95. package/dist/lib/codex-manager/commands/status.js +50 -0
  96. package/dist/lib/codex-manager/commands/status.js.map +1 -0
  97. package/dist/lib/codex-manager/commands/switch.d.ts +22 -0
  98. package/dist/lib/codex-manager/commands/switch.d.ts.map +1 -0
  99. package/dist/lib/codex-manager/commands/switch.js +41 -0
  100. package/dist/lib/codex-manager/commands/switch.js.map +1 -0
  101. package/dist/lib/codex-manager/commands/verify-flagged.d.ts +69 -0
  102. package/dist/lib/codex-manager/commands/verify-flagged.d.ts.map +1 -0
  103. package/dist/lib/codex-manager/commands/verify-flagged.js +251 -0
  104. package/dist/lib/codex-manager/commands/verify-flagged.js.map +1 -0
  105. package/dist/lib/codex-manager/config-explain.d.ts +2 -0
  106. package/dist/lib/codex-manager/config-explain.d.ts.map +1 -0
  107. package/dist/lib/codex-manager/config-explain.js +62 -0
  108. package/dist/lib/codex-manager/config-explain.js.map +1 -0
  109. package/dist/lib/codex-manager/config-template.d.ts +2 -0
  110. package/dist/lib/codex-manager/config-template.d.ts.map +1 -0
  111. package/dist/lib/codex-manager/config-template.js +112 -0
  112. package/dist/lib/codex-manager/config-template.js.map +1 -0
  113. package/dist/lib/codex-manager/dashboard-display-panel.d.ts +40 -0
  114. package/dist/lib/codex-manager/dashboard-display-panel.d.ts.map +1 -0
  115. package/dist/lib/codex-manager/dashboard-display-panel.js +187 -0
  116. package/dist/lib/codex-manager/dashboard-display-panel.js.map +1 -0
  117. package/dist/lib/codex-manager/dashboard-formatters.d.ts +6 -0
  118. package/dist/lib/codex-manager/dashboard-formatters.d.ts.map +1 -0
  119. package/dist/lib/codex-manager/dashboard-formatters.js +19 -0
  120. package/dist/lib/codex-manager/dashboard-formatters.js.map +1 -0
  121. package/dist/lib/codex-manager/dashboard-settings-controller.d.ts +12 -0
  122. package/dist/lib/codex-manager/dashboard-settings-controller.d.ts.map +1 -0
  123. package/dist/lib/codex-manager/dashboard-settings-controller.js +17 -0
  124. package/dist/lib/codex-manager/dashboard-settings-controller.js.map +1 -0
  125. package/dist/lib/codex-manager/dashboard-settings-data.d.ts +10 -0
  126. package/dist/lib/codex-manager/dashboard-settings-data.d.ts.map +1 -0
  127. package/dist/lib/codex-manager/dashboard-settings-data.js +95 -0
  128. package/dist/lib/codex-manager/dashboard-settings-data.js.map +1 -0
  129. package/dist/lib/codex-manager/dashboard-settings-entry.d.ts +22 -0
  130. package/dist/lib/codex-manager/dashboard-settings-entry.d.ts.map +1 -0
  131. package/dist/lib/codex-manager/dashboard-settings-entry.js +13 -0
  132. package/dist/lib/codex-manager/dashboard-settings-entry.js.map +1 -0
  133. package/dist/lib/codex-manager/debug-bundle.d.ts +2 -0
  134. package/dist/lib/codex-manager/debug-bundle.d.ts.map +1 -0
  135. package/dist/lib/codex-manager/debug-bundle.js +128 -0
  136. package/dist/lib/codex-manager/debug-bundle.js.map +1 -0
  137. package/dist/lib/codex-manager/doctor.d.ts +20 -0
  138. package/dist/lib/codex-manager/doctor.d.ts.map +1 -0
  139. package/dist/lib/codex-manager/doctor.js +546 -0
  140. package/dist/lib/codex-manager/doctor.js.map +1 -0
  141. package/dist/lib/codex-manager/experimental-settings-entry.d.ts +7 -0
  142. package/dist/lib/codex-manager/experimental-settings-entry.d.ts.map +1 -0
  143. package/dist/lib/codex-manager/experimental-settings-entry.js +30 -0
  144. package/dist/lib/codex-manager/experimental-settings-entry.js.map +1 -0
  145. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +66 -0
  146. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts.map +1 -0
  147. package/dist/lib/codex-manager/experimental-settings-prompt.js +221 -0
  148. package/dist/lib/codex-manager/experimental-settings-prompt.js.map +1 -0
  149. package/dist/lib/codex-manager/experimental-settings-schema.d.ts +23 -0
  150. package/dist/lib/codex-manager/experimental-settings-schema.d.ts.map +1 -0
  151. package/dist/lib/codex-manager/experimental-settings-schema.js +34 -0
  152. package/dist/lib/codex-manager/experimental-settings-schema.js.map +1 -0
  153. package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts +30 -0
  154. package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts.map +1 -0
  155. package/dist/lib/codex-manager/experimental-sync-target-entry.js +16 -0
  156. package/dist/lib/codex-manager/experimental-sync-target-entry.js.map +1 -0
  157. package/dist/lib/codex-manager/experimental-sync-target.d.ts +23 -0
  158. package/dist/lib/codex-manager/experimental-sync-target.d.ts.map +1 -0
  159. package/dist/lib/codex-manager/experimental-sync-target.js +39 -0
  160. package/dist/lib/codex-manager/experimental-sync-target.js.map +1 -0
  161. package/dist/lib/codex-manager/fix.d.ts +26 -0
  162. package/dist/lib/codex-manager/fix.d.ts.map +1 -0
  163. package/dist/lib/codex-manager/fix.js +366 -0
  164. package/dist/lib/codex-manager/fix.js.map +1 -0
  165. package/dist/lib/codex-manager/forecast-report-commands.d.ts +54 -0
  166. package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +1 -0
  167. package/dist/lib/codex-manager/forecast-report-commands.js +443 -0
  168. package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -0
  169. package/dist/lib/codex-manager/forecast.d.ts +26 -0
  170. package/dist/lib/codex-manager/forecast.d.ts.map +1 -0
  171. package/dist/lib/codex-manager/forecast.js +218 -0
  172. package/dist/lib/codex-manager/forecast.js.map +1 -0
  173. package/dist/lib/codex-manager/help.d.ts +36 -0
  174. package/dist/lib/codex-manager/help.d.ts.map +1 -0
  175. package/dist/lib/codex-manager/help.js +125 -0
  176. package/dist/lib/codex-manager/help.js.map +1 -0
  177. package/dist/lib/codex-manager/info.d.ts +11 -0
  178. package/dist/lib/codex-manager/info.d.ts.map +1 -0
  179. package/dist/lib/codex-manager/info.js +91 -0
  180. package/dist/lib/codex-manager/info.js.map +1 -0
  181. package/dist/lib/codex-manager/login.d.ts +40 -0
  182. package/dist/lib/codex-manager/login.d.ts.map +1 -0
  183. package/dist/lib/codex-manager/login.js +1041 -0
  184. package/dist/lib/codex-manager/login.js.map +1 -0
  185. package/dist/lib/codex-manager/repair-commands.d.ts +70 -0
  186. package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -0
  187. package/dist/lib/codex-manager/repair-commands.js +1671 -0
  188. package/dist/lib/codex-manager/repair-commands.js.map +1 -0
  189. package/dist/lib/codex-manager/report.d.ts +39 -0
  190. package/dist/lib/codex-manager/report.d.ts.map +1 -0
  191. package/dist/lib/codex-manager/report.js +243 -0
  192. package/dist/lib/codex-manager/report.js.map +1 -0
  193. package/dist/lib/codex-manager/settings-hub-entry.d.ts +30 -0
  194. package/dist/lib/codex-manager/settings-hub-entry.d.ts.map +1 -0
  195. package/dist/lib/codex-manager/settings-hub-entry.js +11 -0
  196. package/dist/lib/codex-manager/settings-hub-entry.js.map +1 -0
  197. package/dist/lib/codex-manager/settings-hub-menu.d.ts +30 -0
  198. package/dist/lib/codex-manager/settings-hub-menu.d.ts.map +1 -0
  199. package/dist/lib/codex-manager/settings-hub-menu.js +37 -0
  200. package/dist/lib/codex-manager/settings-hub-menu.js.map +1 -0
  201. package/dist/lib/codex-manager/settings-hub-prompt.d.ts +25 -0
  202. package/dist/lib/codex-manager/settings-hub-prompt.d.ts.map +1 -0
  203. package/dist/lib/codex-manager/settings-hub-prompt.js +23 -0
  204. package/dist/lib/codex-manager/settings-hub-prompt.js.map +1 -0
  205. package/dist/lib/codex-manager/settings-hub.d.ts +10 -29
  206. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  207. package/dist/lib/codex-manager/settings-hub.js +260 -2168
  208. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  209. package/dist/lib/codex-manager/settings-panels.d.ts +56 -0
  210. package/dist/lib/codex-manager/settings-panels.d.ts.map +1 -0
  211. package/dist/lib/codex-manager/settings-panels.js +73 -0
  212. package/dist/lib/codex-manager/settings-panels.js.map +1 -0
  213. package/dist/lib/codex-manager/settings-persist-utils.d.ts +9 -0
  214. package/dist/lib/codex-manager/settings-persist-utils.d.ts.map +1 -0
  215. package/dist/lib/codex-manager/settings-persist-utils.js +33 -0
  216. package/dist/lib/codex-manager/settings-persist-utils.js.map +1 -0
  217. package/dist/lib/codex-manager/settings-preview.d.ts +12 -0
  218. package/dist/lib/codex-manager/settings-preview.d.ts.map +1 -0
  219. package/dist/lib/codex-manager/settings-preview.js +118 -0
  220. package/dist/lib/codex-manager/settings-preview.js.map +1 -0
  221. package/dist/lib/codex-manager/settings-write-queue.d.ts +14 -0
  222. package/dist/lib/codex-manager/settings-write-queue.d.ts.map +1 -0
  223. package/dist/lib/codex-manager/settings-write-queue.js +88 -0
  224. package/dist/lib/codex-manager/settings-write-queue.js.map +1 -0
  225. package/dist/lib/codex-manager/statusline-order.d.ts +3 -0
  226. package/dist/lib/codex-manager/statusline-order.d.ts.map +1 -0
  227. package/dist/lib/codex-manager/statusline-order.js +17 -0
  228. package/dist/lib/codex-manager/statusline-order.js.map +1 -0
  229. package/dist/lib/codex-manager/statusline-settings-panel.d.ts +40 -0
  230. package/dist/lib/codex-manager/statusline-settings-panel.d.ts.map +1 -0
  231. package/dist/lib/codex-manager/statusline-settings-panel.js +167 -0
  232. package/dist/lib/codex-manager/statusline-settings-panel.js.map +1 -0
  233. package/dist/lib/codex-manager/switch.d.ts +31 -0
  234. package/dist/lib/codex-manager/switch.d.ts.map +1 -0
  235. package/dist/lib/codex-manager/switch.js +103 -0
  236. package/dist/lib/codex-manager/switch.js.map +1 -0
  237. package/dist/lib/codex-manager/theme-settings-panel.d.ts +26 -0
  238. package/dist/lib/codex-manager/theme-settings-panel.d.ts.map +1 -0
  239. package/dist/lib/codex-manager/theme-settings-panel.js +131 -0
  240. package/dist/lib/codex-manager/theme-settings-panel.js.map +1 -0
  241. package/dist/lib/codex-manager/unified-settings-controller.d.ts +27 -0
  242. package/dist/lib/codex-manager/unified-settings-controller.d.ts.map +1 -0
  243. package/dist/lib/codex-manager/unified-settings-controller.js +50 -0
  244. package/dist/lib/codex-manager/unified-settings-controller.js.map +1 -0
  245. package/dist/lib/codex-manager/unified-settings-entry.d.ts +6 -0
  246. package/dist/lib/codex-manager/unified-settings-entry.d.ts.map +1 -0
  247. package/dist/lib/codex-manager/unified-settings-entry.js +23 -0
  248. package/dist/lib/codex-manager/unified-settings-entry.js.map +1 -0
  249. package/dist/lib/codex-manager/verify-flagged.d.ts +20 -0
  250. package/dist/lib/codex-manager/verify-flagged.d.ts.map +1 -0
  251. package/dist/lib/codex-manager/verify-flagged.js +316 -0
  252. package/dist/lib/codex-manager/verify-flagged.js.map +1 -0
  253. package/dist/lib/codex-manager.d.ts.map +1 -1
  254. package/dist/lib/codex-manager.js +439 -2199
  255. package/dist/lib/codex-manager.js.map +1 -1
  256. package/dist/lib/config.d.ts +38 -0
  257. package/dist/lib/config.d.ts.map +1 -1
  258. package/dist/lib/config.js +371 -9
  259. package/dist/lib/config.js.map +1 -1
  260. package/dist/lib/errors.d.ts +9 -0
  261. package/dist/lib/errors.d.ts.map +1 -1
  262. package/dist/lib/errors.js +13 -0
  263. package/dist/lib/errors.js.map +1 -1
  264. package/dist/lib/forecast.d.ts +17 -0
  265. package/dist/lib/forecast.d.ts.map +1 -1
  266. package/dist/lib/forecast.js +33 -6
  267. package/dist/lib/forecast.js.map +1 -1
  268. package/dist/lib/named-backup-export.js +2 -2
  269. package/dist/lib/named-backup-export.js.map +1 -1
  270. package/dist/lib/prompts/codex.d.ts +8 -3
  271. package/dist/lib/prompts/codex.d.ts.map +1 -1
  272. package/dist/lib/prompts/codex.js +16 -26
  273. package/dist/lib/prompts/codex.js.map +1 -1
  274. package/dist/lib/request/failover-config.d.ts +4 -0
  275. package/dist/lib/request/failover-config.d.ts.map +1 -0
  276. package/dist/lib/request/failover-config.js +15 -0
  277. package/dist/lib/request/failover-config.js.map +1 -0
  278. package/dist/lib/request/fetch-helpers.d.ts +10 -4
  279. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  280. package/dist/lib/request/fetch-helpers.js +13 -8
  281. package/dist/lib/request/fetch-helpers.js.map +1 -1
  282. package/dist/lib/request/helpers/model-map.d.ts +49 -12
  283. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  284. package/dist/lib/request/helpers/model-map.js +303 -108
  285. package/dist/lib/request/helpers/model-map.js.map +1 -1
  286. package/dist/lib/request/helpers/tool-utils.d.ts +2 -15
  287. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
  288. package/dist/lib/request/helpers/tool-utils.js +40 -27
  289. package/dist/lib/request/helpers/tool-utils.js.map +1 -1
  290. package/dist/lib/request/index.d.ts +5 -0
  291. package/dist/lib/request/index.d.ts.map +1 -0
  292. package/dist/lib/request/index.js +5 -0
  293. package/dist/lib/request/index.js.map +1 -0
  294. package/dist/lib/request/request-init.d.ts +3 -0
  295. package/dist/lib/request/request-init.d.ts.map +1 -0
  296. package/dist/lib/request/request-init.js +50 -0
  297. package/dist/lib/request/request-init.js.map +1 -0
  298. package/dist/lib/request/request-transformer.d.ts +19 -6
  299. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  300. package/dist/lib/request/request-transformer.js +210 -193
  301. package/dist/lib/request/request-transformer.js.map +1 -1
  302. package/dist/lib/request/response-compaction.d.ts +20 -0
  303. package/dist/lib/request/response-compaction.d.ts.map +1 -0
  304. package/dist/lib/request/response-compaction.js +124 -0
  305. package/dist/lib/request/response-compaction.js.map +1 -0
  306. package/dist/lib/request/response-handler.d.ts +2 -0
  307. package/dist/lib/request/response-handler.d.ts.map +1 -1
  308. package/dist/lib/request/response-handler.js +567 -11
  309. package/dist/lib/request/response-handler.js.map +1 -1
  310. package/dist/lib/request/response-metadata.d.ts +3 -0
  311. package/dist/lib/request/response-metadata.d.ts.map +1 -0
  312. package/dist/lib/request/response-metadata.js +61 -0
  313. package/dist/lib/request/response-metadata.js.map +1 -0
  314. package/dist/lib/request/trace-mode.d.ts +9 -0
  315. package/dist/lib/request/trace-mode.d.ts.map +1 -0
  316. package/dist/lib/request/trace-mode.js +14 -0
  317. package/dist/lib/request/trace-mode.js.map +1 -0
  318. package/dist/lib/request/wait-utils.d.ts +14 -0
  319. package/dist/lib/request/wait-utils.d.ts.map +1 -0
  320. package/dist/lib/request/wait-utils.js +44 -0
  321. package/dist/lib/request/wait-utils.js.map +1 -0
  322. package/dist/lib/runtime/account-check-helpers.d.ts +8 -0
  323. package/dist/lib/runtime/account-check-helpers.d.ts.map +1 -0
  324. package/dist/lib/runtime/account-check-helpers.js +30 -0
  325. package/dist/lib/runtime/account-check-helpers.js.map +1 -0
  326. package/dist/lib/runtime/account-check-types.d.ts +18 -0
  327. package/dist/lib/runtime/account-check-types.d.ts.map +1 -0
  328. package/dist/lib/runtime/account-check-types.js +12 -0
  329. package/dist/lib/runtime/account-check-types.js.map +1 -0
  330. package/dist/lib/runtime/account-check.d.ts +48 -0
  331. package/dist/lib/runtime/account-check.d.ts.map +1 -0
  332. package/dist/lib/runtime/account-check.js +202 -0
  333. package/dist/lib/runtime/account-check.js.map +1 -0
  334. package/dist/lib/runtime/account-health-check.d.ts +8 -0
  335. package/dist/lib/runtime/account-health-check.d.ts.map +1 -0
  336. package/dist/lib/runtime/account-health-check.js +30 -0
  337. package/dist/lib/runtime/account-health-check.js.map +1 -0
  338. package/dist/lib/runtime/account-manager-cache-entry.d.ts +25 -0
  339. package/dist/lib/runtime/account-manager-cache-entry.d.ts.map +1 -0
  340. package/dist/lib/runtime/account-manager-cache-entry.js +17 -0
  341. package/dist/lib/runtime/account-manager-cache-entry.js.map +1 -0
  342. package/dist/lib/runtime/account-manager-cache.d.ts +25 -0
  343. package/dist/lib/runtime/account-manager-cache.d.ts.map +1 -0
  344. package/dist/lib/runtime/account-manager-cache.js +44 -0
  345. package/dist/lib/runtime/account-manager-cache.js.map +1 -0
  346. package/dist/lib/runtime/account-pool.d.ts +23 -0
  347. package/dist/lib/runtime/account-pool.d.ts.map +1 -0
  348. package/dist/lib/runtime/account-pool.js +138 -0
  349. package/dist/lib/runtime/account-pool.js.map +1 -0
  350. package/dist/lib/runtime/account-scope.d.ts +10 -0
  351. package/dist/lib/runtime/account-scope.d.ts.map +1 -0
  352. package/dist/lib/runtime/account-scope.js +13 -0
  353. package/dist/lib/runtime/account-scope.js.map +1 -0
  354. package/dist/lib/runtime/account-select-event.d.ts +19 -0
  355. package/dist/lib/runtime/account-select-event.d.ts.map +1 -0
  356. package/dist/lib/runtime/account-select-event.js +53 -0
  357. package/dist/lib/runtime/account-select-event.js.map +1 -0
  358. package/dist/lib/runtime/account-selection.d.ts +28 -0
  359. package/dist/lib/runtime/account-selection.d.ts.map +1 -0
  360. package/dist/lib/runtime/account-selection.js +46 -0
  361. package/dist/lib/runtime/account-selection.js.map +1 -0
  362. package/dist/lib/runtime/account-state.d.ts +13 -0
  363. package/dist/lib/runtime/account-state.d.ts.map +1 -0
  364. package/dist/lib/runtime/account-state.js +38 -0
  365. package/dist/lib/runtime/account-state.js.map +1 -0
  366. package/dist/lib/runtime/account-status.d.ts +13 -0
  367. package/dist/lib/runtime/account-status.d.ts.map +1 -0
  368. package/dist/lib/runtime/account-status.js +37 -0
  369. package/dist/lib/runtime/account-status.js.map +1 -0
  370. package/dist/lib/runtime/account-storage-scope-entry.d.ts +26 -0
  371. package/dist/lib/runtime/account-storage-scope-entry.d.ts.map +1 -0
  372. package/dist/lib/runtime/account-storage-scope-entry.js +15 -0
  373. package/dist/lib/runtime/account-storage-scope-entry.js.map +1 -0
  374. package/dist/lib/runtime/auth-facade.d.ts +51 -0
  375. package/dist/lib/runtime/auth-facade.d.ts.map +1 -0
  376. package/dist/lib/runtime/auth-facade.js +48 -0
  377. package/dist/lib/runtime/auth-facade.js.map +1 -0
  378. package/dist/lib/runtime/browser-oauth-flow.d.ts +32 -0
  379. package/dist/lib/runtime/browser-oauth-flow.d.ts.map +1 -0
  380. package/dist/lib/runtime/browser-oauth-flow.js +31 -0
  381. package/dist/lib/runtime/browser-oauth-flow.js.map +1 -0
  382. package/dist/lib/runtime/capability-boost.d.ts +25 -0
  383. package/dist/lib/runtime/capability-boost.d.ts.map +1 -0
  384. package/dist/lib/runtime/capability-boost.js +26 -0
  385. package/dist/lib/runtime/capability-boost.js.map +1 -0
  386. package/dist/lib/runtime/event-handler.d.ts +53 -0
  387. package/dist/lib/runtime/event-handler.d.ts.map +1 -0
  388. package/dist/lib/runtime/event-handler.js +46 -0
  389. package/dist/lib/runtime/event-handler.js.map +1 -0
  390. package/dist/lib/runtime/flagged-verify-types.d.ts +9 -0
  391. package/dist/lib/runtime/flagged-verify-types.d.ts.map +1 -0
  392. package/dist/lib/runtime/flagged-verify-types.js +7 -0
  393. package/dist/lib/runtime/flagged-verify-types.js.map +1 -0
  394. package/dist/lib/runtime/hydrate-emails.d.ts +13 -0
  395. package/dist/lib/runtime/hydrate-emails.d.ts.map +1 -0
  396. package/dist/lib/runtime/hydrate-emails.js +56 -0
  397. package/dist/lib/runtime/hydrate-emails.js.map +1 -0
  398. package/dist/lib/runtime/live-sync-entry.d.ts +36 -0
  399. package/dist/lib/runtime/live-sync-entry.d.ts.map +1 -0
  400. package/dist/lib/runtime/live-sync-entry.js +14 -0
  401. package/dist/lib/runtime/live-sync-entry.js.map +1 -0
  402. package/dist/lib/runtime/live-sync.d.ts +35 -0
  403. package/dist/lib/runtime/live-sync.d.ts.map +1 -0
  404. package/dist/lib/runtime/live-sync.js +65 -0
  405. package/dist/lib/runtime/live-sync.js.map +1 -0
  406. package/dist/lib/runtime/loader-setup.d.ts +9 -0
  407. package/dist/lib/runtime/loader-setup.d.ts.map +1 -0
  408. package/dist/lib/runtime/loader-setup.js +8 -0
  409. package/dist/lib/runtime/loader-setup.js.map +1 -0
  410. package/dist/lib/runtime/login-menu-accounts.d.ts +29 -0
  411. package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -0
  412. package/dist/lib/runtime/login-menu-accounts.js +33 -0
  413. package/dist/lib/runtime/login-menu-accounts.js.map +1 -0
  414. package/dist/lib/runtime/manual-oauth-flow.d.ts +40 -0
  415. package/dist/lib/runtime/manual-oauth-flow.d.ts.map +1 -0
  416. package/dist/lib/runtime/manual-oauth-flow.js +71 -0
  417. package/dist/lib/runtime/manual-oauth-flow.js.map +1 -0
  418. package/dist/lib/runtime/metrics.d.ts +29 -0
  419. package/dist/lib/runtime/metrics.d.ts.map +1 -0
  420. package/dist/lib/runtime/metrics.js +97 -0
  421. package/dist/lib/runtime/metrics.js.map +1 -0
  422. package/dist/lib/runtime/oauth-browser-flow.d.ts +9 -0
  423. package/dist/lib/runtime/oauth-browser-flow.d.ts.map +1 -0
  424. package/dist/lib/runtime/oauth-browser-flow.js +34 -0
  425. package/dist/lib/runtime/oauth-browser-flow.js.map +1 -0
  426. package/dist/lib/runtime/preemptive-quota.d.ts +13 -0
  427. package/dist/lib/runtime/preemptive-quota.d.ts.map +1 -0
  428. package/dist/lib/runtime/preemptive-quota.js +9 -0
  429. package/dist/lib/runtime/preemptive-quota.js.map +1 -0
  430. package/dist/lib/runtime/quota-headers.d.ts +12 -0
  431. package/dist/lib/runtime/quota-headers.d.ts.map +1 -0
  432. package/dist/lib/runtime/quota-headers.js +131 -0
  433. package/dist/lib/runtime/quota-headers.js.map +1 -0
  434. package/dist/lib/runtime/quota-probe.d.ts +18 -0
  435. package/dist/lib/runtime/quota-probe.d.ts.map +1 -0
  436. package/dist/lib/runtime/quota-probe.js +82 -0
  437. package/dist/lib/runtime/quota-probe.js.map +1 -0
  438. package/dist/lib/runtime/quota-settings.d.ts +14 -0
  439. package/dist/lib/runtime/quota-settings.d.ts.map +1 -0
  440. package/dist/lib/runtime/quota-settings.js +12 -0
  441. package/dist/lib/runtime/quota-settings.js.map +1 -0
  442. package/dist/lib/runtime/refresh-guardian-entry.d.ts +32 -0
  443. package/dist/lib/runtime/refresh-guardian-entry.d.ts.map +1 -0
  444. package/dist/lib/runtime/refresh-guardian-entry.js +12 -0
  445. package/dist/lib/runtime/refresh-guardian-entry.js.map +1 -0
  446. package/dist/lib/runtime/refresh-guardian.d.ts +24 -0
  447. package/dist/lib/runtime/refresh-guardian.d.ts.map +1 -0
  448. package/dist/lib/runtime/refresh-guardian.js +32 -0
  449. package/dist/lib/runtime/refresh-guardian.js.map +1 -0
  450. package/dist/lib/runtime/request-init.d.ts +5 -0
  451. package/dist/lib/runtime/request-init.d.ts.map +1 -0
  452. package/dist/lib/runtime/request-init.js +50 -0
  453. package/dist/lib/runtime/request-init.js.map +1 -0
  454. package/dist/lib/runtime/runtime-services.d.ts +58 -0
  455. package/dist/lib/runtime/runtime-services.d.ts.map +1 -0
  456. package/dist/lib/runtime/runtime-services.js +87 -0
  457. package/dist/lib/runtime/runtime-services.js.map +1 -0
  458. package/dist/lib/runtime/session-affinity-entry.d.ts +30 -0
  459. package/dist/lib/runtime/session-affinity-entry.d.ts.map +1 -0
  460. package/dist/lib/runtime/session-affinity-entry.js +11 -0
  461. package/dist/lib/runtime/session-affinity-entry.js.map +1 -0
  462. package/dist/lib/runtime/session-affinity.d.ts +13 -0
  463. package/dist/lib/runtime/session-affinity.d.ts.map +1 -0
  464. package/dist/lib/runtime/session-affinity.js +17 -0
  465. package/dist/lib/runtime/session-affinity.js.map +1 -0
  466. package/dist/lib/runtime/session-recovery.d.ts +8 -0
  467. package/dist/lib/runtime/session-recovery.d.ts.map +1 -0
  468. package/dist/lib/runtime/session-recovery.js +7 -0
  469. package/dist/lib/runtime/session-recovery.js.map +1 -0
  470. package/dist/lib/runtime/status-marker.d.ts +3 -0
  471. package/dist/lib/runtime/status-marker.d.ts.map +1 -0
  472. package/dist/lib/runtime/status-marker.js +15 -0
  473. package/dist/lib/runtime/status-marker.js.map +1 -0
  474. package/dist/lib/runtime/storage-scope.d.ts +13 -0
  475. package/dist/lib/runtime/storage-scope.d.ts.map +1 -0
  476. package/dist/lib/runtime/storage-scope.js +14 -0
  477. package/dist/lib/runtime/storage-scope.js.map +1 -0
  478. package/dist/lib/runtime/toast.d.ts +16 -0
  479. package/dist/lib/runtime/toast.d.ts.map +1 -0
  480. package/dist/lib/runtime/toast.js +16 -0
  481. package/dist/lib/runtime/toast.js.map +1 -0
  482. package/dist/lib/runtime/ui-runtime-entry.d.ts +7 -0
  483. package/dist/lib/runtime/ui-runtime-entry.d.ts.map +1 -0
  484. package/dist/lib/runtime/ui-runtime-entry.js +4 -0
  485. package/dist/lib/runtime/ui-runtime-entry.js.map +1 -0
  486. package/dist/lib/runtime/ui-runtime.d.ts +9 -0
  487. package/dist/lib/runtime/ui-runtime.d.ts.map +1 -0
  488. package/dist/lib/runtime/ui-runtime.js +23 -0
  489. package/dist/lib/runtime/ui-runtime.js.map +1 -0
  490. package/dist/lib/runtime/verify-flagged.d.ts +44 -0
  491. package/dist/lib/runtime/verify-flagged.d.ts.map +1 -0
  492. package/dist/lib/runtime/verify-flagged.js +84 -0
  493. package/dist/lib/runtime/verify-flagged.js.map +1 -0
  494. package/dist/lib/schemas.d.ts +2 -0
  495. package/dist/lib/schemas.d.ts.map +1 -1
  496. package/dist/lib/schemas.js +2 -0
  497. package/dist/lib/schemas.js.map +1 -1
  498. package/dist/lib/session-affinity.d.ts +10 -0
  499. package/dist/lib/session-affinity.d.ts.map +1 -1
  500. package/dist/lib/session-affinity.js +53 -6
  501. package/dist/lib/session-affinity.js.map +1 -1
  502. package/dist/lib/storage/account-clear-entry.d.ts +16 -0
  503. package/dist/lib/storage/account-clear-entry.d.ts.map +1 -0
  504. package/dist/lib/storage/account-clear-entry.js +12 -0
  505. package/dist/lib/storage/account-clear-entry.js.map +1 -0
  506. package/dist/lib/storage/account-clear.d.ts +8 -0
  507. package/dist/lib/storage/account-clear.d.ts.map +1 -0
  508. package/dist/lib/storage/account-clear.js +29 -0
  509. package/dist/lib/storage/account-clear.js.map +1 -0
  510. package/dist/lib/storage/account-match-utils.d.ts +9 -0
  511. package/dist/lib/storage/account-match-utils.d.ts.map +1 -0
  512. package/dist/lib/storage/account-match-utils.js +45 -0
  513. package/dist/lib/storage/account-match-utils.js.map +1 -0
  514. package/dist/lib/storage/account-persistence.d.ts +3 -0
  515. package/dist/lib/storage/account-persistence.d.ts.map +1 -0
  516. package/dist/lib/storage/account-persistence.js +12 -0
  517. package/dist/lib/storage/account-persistence.js.map +1 -0
  518. package/dist/lib/storage/account-port.d.ts +50 -0
  519. package/dist/lib/storage/account-port.d.ts.map +1 -0
  520. package/dist/lib/storage/account-port.js +43 -0
  521. package/dist/lib/storage/account-port.js.map +1 -0
  522. package/dist/lib/storage/account-save-entry.d.ts +7 -0
  523. package/dist/lib/storage/account-save-entry.d.ts.map +1 -0
  524. package/dist/lib/storage/account-save-entry.js +6 -0
  525. package/dist/lib/storage/account-save-entry.js.map +1 -0
  526. package/dist/lib/storage/account-save.d.ts +30 -0
  527. package/dist/lib/storage/account-save.d.ts.map +1 -0
  528. package/dist/lib/storage/account-save.js +49 -0
  529. package/dist/lib/storage/account-save.js.map +1 -0
  530. package/dist/lib/storage/account-snapshot.d.ts +25 -0
  531. package/dist/lib/storage/account-snapshot.d.ts.map +1 -0
  532. package/dist/lib/storage/account-snapshot.js +78 -0
  533. package/dist/lib/storage/account-snapshot.js.map +1 -0
  534. package/dist/lib/storage/backup-metadata-builder.d.ts +32 -0
  535. package/dist/lib/storage/backup-metadata-builder.d.ts.map +1 -0
  536. package/dist/lib/storage/backup-metadata-builder.js +77 -0
  537. package/dist/lib/storage/backup-metadata-builder.js.map +1 -0
  538. package/dist/lib/storage/backup-metadata.d.ts +29 -0
  539. package/dist/lib/storage/backup-metadata.d.ts.map +1 -0
  540. package/dist/lib/storage/backup-metadata.js +16 -0
  541. package/dist/lib/storage/backup-metadata.js.map +1 -0
  542. package/dist/lib/storage/backup-paths.d.ts +10 -0
  543. package/dist/lib/storage/backup-paths.d.ts.map +1 -0
  544. package/dist/lib/storage/backup-paths.js +27 -0
  545. package/dist/lib/storage/backup-paths.js.map +1 -0
  546. package/dist/lib/storage/backup-restore.d.ts +11 -0
  547. package/dist/lib/storage/backup-restore.d.ts.map +1 -0
  548. package/dist/lib/storage/backup-restore.js +52 -0
  549. package/dist/lib/storage/backup-restore.js.map +1 -0
  550. package/dist/lib/storage/cache-artifacts.d.ts +2 -0
  551. package/dist/lib/storage/cache-artifacts.d.ts.map +1 -0
  552. package/dist/lib/storage/cache-artifacts.js +4 -0
  553. package/dist/lib/storage/cache-artifacts.js.map +1 -0
  554. package/dist/lib/storage/error-hints.d.ts +10 -0
  555. package/dist/lib/storage/error-hints.d.ts.map +1 -0
  556. package/dist/lib/storage/error-hints.js +36 -0
  557. package/dist/lib/storage/error-hints.js.map +1 -0
  558. package/dist/lib/storage/file-paths.d.ts +6 -0
  559. package/dist/lib/storage/file-paths.d.ts.map +1 -0
  560. package/dist/lib/storage/file-paths.js +17 -0
  561. package/dist/lib/storage/file-paths.js.map +1 -0
  562. package/dist/lib/storage/fixture-guards.d.ts +4 -0
  563. package/dist/lib/storage/fixture-guards.d.ts.map +1 -0
  564. package/dist/lib/storage/fixture-guards.js +27 -0
  565. package/dist/lib/storage/fixture-guards.js.map +1 -0
  566. package/dist/lib/storage/flagged-entry.d.ts +15 -0
  567. package/dist/lib/storage/flagged-entry.d.ts.map +1 -0
  568. package/dist/lib/storage/flagged-entry.js +12 -0
  569. package/dist/lib/storage/flagged-entry.js.map +1 -0
  570. package/dist/lib/storage/flagged-load-entry.d.ts +20 -0
  571. package/dist/lib/storage/flagged-load-entry.d.ts.map +1 -0
  572. package/dist/lib/storage/flagged-load-entry.js +13 -0
  573. package/dist/lib/storage/flagged-load-entry.js.map +1 -0
  574. package/dist/lib/storage/flagged-save-entry.d.ts +7 -0
  575. package/dist/lib/storage/flagged-save-entry.d.ts.map +1 -0
  576. package/dist/lib/storage/flagged-save-entry.js +6 -0
  577. package/dist/lib/storage/flagged-save-entry.js.map +1 -0
  578. package/dist/lib/storage/flagged-storage-file.d.ts +7 -0
  579. package/dist/lib/storage/flagged-storage-file.d.ts.map +1 -0
  580. package/dist/lib/storage/flagged-storage-file.js +25 -0
  581. package/dist/lib/storage/flagged-storage-file.js.map +1 -0
  582. package/dist/lib/storage/flagged-storage-io.d.ts +28 -0
  583. package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -0
  584. package/dist/lib/storage/flagged-storage-io.js +135 -0
  585. package/dist/lib/storage/flagged-storage-io.js.map +1 -0
  586. package/dist/lib/storage/flagged-storage.d.ts +6 -0
  587. package/dist/lib/storage/flagged-storage.d.ts.map +1 -0
  588. package/dist/lib/storage/flagged-storage.js +90 -0
  589. package/dist/lib/storage/flagged-storage.js.map +1 -0
  590. package/dist/lib/storage/gitignore.d.ts +7 -0
  591. package/dist/lib/storage/gitignore.d.ts.map +1 -0
  592. package/dist/lib/storage/gitignore.js +34 -0
  593. package/dist/lib/storage/gitignore.js.map +1 -0
  594. package/dist/lib/storage/hash.d.ts +2 -0
  595. package/dist/lib/storage/hash.d.ts.map +1 -0
  596. package/dist/lib/storage/hash.js +5 -0
  597. package/dist/lib/storage/hash.js.map +1 -0
  598. package/dist/lib/storage/identity.d.ts +15 -0
  599. package/dist/lib/storage/identity.d.ts.map +1 -0
  600. package/dist/lib/storage/identity.js +48 -0
  601. package/dist/lib/storage/identity.js.map +1 -0
  602. package/dist/lib/storage/import-export.d.ts +24 -0
  603. package/dist/lib/storage/import-export.d.ts.map +1 -0
  604. package/dist/lib/storage/import-export.js +108 -0
  605. package/dist/lib/storage/import-export.js.map +1 -0
  606. package/dist/lib/storage/metadata-section.d.ts +16 -0
  607. package/dist/lib/storage/metadata-section.d.ts.map +1 -0
  608. package/dist/lib/storage/metadata-section.js +16 -0
  609. package/dist/lib/storage/metadata-section.js.map +1 -0
  610. package/dist/lib/storage/named-backup-entry.d.ts +15 -0
  611. package/dist/lib/storage/named-backup-entry.d.ts.map +1 -0
  612. package/dist/lib/storage/named-backup-entry.js +7 -0
  613. package/dist/lib/storage/named-backup-entry.js.map +1 -0
  614. package/dist/lib/storage/named-backups-entry.d.ts +19 -0
  615. package/dist/lib/storage/named-backups-entry.d.ts.map +1 -0
  616. package/dist/lib/storage/named-backups-entry.js +7 -0
  617. package/dist/lib/storage/named-backups-entry.js.map +1 -0
  618. package/dist/lib/storage/named-backups.d.ts +15 -0
  619. package/dist/lib/storage/named-backups.d.ts.map +1 -0
  620. package/dist/lib/storage/named-backups.js +68 -0
  621. package/dist/lib/storage/named-backups.js.map +1 -0
  622. package/dist/lib/storage/path-state.d.ts +9 -0
  623. package/dist/lib/storage/path-state.d.ts.map +1 -0
  624. package/dist/lib/storage/path-state.js +20 -0
  625. package/dist/lib/storage/path-state.js.map +1 -0
  626. package/dist/lib/storage/project-migration.d.ts +10 -0
  627. package/dist/lib/storage/project-migration.d.ts.map +1 -0
  628. package/dist/lib/storage/project-migration.js +38 -0
  629. package/dist/lib/storage/project-migration.js.map +1 -0
  630. package/dist/lib/storage/record-utils.d.ts +3 -0
  631. package/dist/lib/storage/record-utils.d.ts.map +1 -0
  632. package/dist/lib/storage/record-utils.js +9 -0
  633. package/dist/lib/storage/record-utils.js.map +1 -0
  634. package/dist/lib/storage/restore-assessment.d.ts +7 -0
  635. package/dist/lib/storage/restore-assessment.d.ts.map +1 -0
  636. package/dist/lib/storage/restore-assessment.js +42 -0
  637. package/dist/lib/storage/restore-assessment.js.map +1 -0
  638. package/dist/lib/storage/restore-backup-entry.d.ts +24 -0
  639. package/dist/lib/storage/restore-backup-entry.d.ts.map +1 -0
  640. package/dist/lib/storage/restore-backup-entry.js +10 -0
  641. package/dist/lib/storage/restore-backup-entry.js.map +1 -0
  642. package/dist/lib/storage/restore-metadata.d.ts +10 -0
  643. package/dist/lib/storage/restore-metadata.d.ts.map +1 -0
  644. package/dist/lib/storage/restore-metadata.js +18 -0
  645. package/dist/lib/storage/restore-metadata.js.map +1 -0
  646. package/dist/lib/storage/restore.d.ts +14 -0
  647. package/dist/lib/storage/restore.d.ts.map +1 -0
  648. package/dist/lib/storage/restore.js +53 -0
  649. package/dist/lib/storage/restore.js.map +1 -0
  650. package/dist/lib/storage/snapshot-inspectors.d.ts +22 -0
  651. package/dist/lib/storage/snapshot-inspectors.d.ts.map +1 -0
  652. package/dist/lib/storage/snapshot-inspectors.js +95 -0
  653. package/dist/lib/storage/snapshot-inspectors.js.map +1 -0
  654. package/dist/lib/storage/storage-parser.d.ts +15 -0
  655. package/dist/lib/storage/storage-parser.d.ts.map +1 -0
  656. package/dist/lib/storage/storage-parser.js +16 -0
  657. package/dist/lib/storage/storage-parser.js.map +1 -0
  658. package/dist/lib/storage/transactions.d.ts +23 -0
  659. package/dist/lib/storage/transactions.d.ts.map +1 -0
  660. package/dist/lib/storage/transactions.js +64 -0
  661. package/dist/lib/storage/transactions.js.map +1 -0
  662. package/dist/lib/storage.d.ts +8 -47
  663. package/dist/lib/storage.d.ts.map +1 -1
  664. package/dist/lib/storage.js +482 -1044
  665. package/dist/lib/storage.js.map +1 -1
  666. package/dist/lib/types.d.ts +80 -1
  667. package/dist/lib/types.d.ts.map +1 -1
  668. package/package.json +41 -4
  669. package/scripts/check-pack-budget-lib.js +114 -0
  670. package/scripts/check-pack-budget.mjs +10 -0
  671. package/scripts/codex-multi-auth.js +34 -10
  672. package/scripts/install-codex-auth-utils.js +54 -14
  673. package/scripts/install-codex-auth.js +62 -33
  674. package/scripts/update-vendor-provenance.mjs +96 -0
  675. 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
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, resolveRuntimeRequestIdentity, 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 { applyProxyCompatibleInit, createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, isWorkspaceDisabledError, } 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,557 +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
- // Convert candidates to workspaces
217
- const workspaces = candidates.map((c) => ({
218
- id: c.accountId,
219
- name: c.label,
220
- enabled: true,
221
- isDefault: c.isDefault,
222
- }));
223
- if (candidates.length === 1) {
224
- const [candidate] = candidates;
225
- if (candidate) {
226
- return {
227
- ...tokens,
228
- accountIdOverride: candidate.accountId,
229
- accountIdSource: candidate.source,
230
- accountLabel: candidate.label,
231
- workspaces,
232
- };
233
- }
234
- }
235
- // Auto-select the best workspace candidate without prompting.
236
- // This honors org/default/id-token signals and avoids forcing personal token IDs.
237
- const choice = selectBestAccountCandidate(candidates);
238
- if (!choice)
239
- return tokens;
240
- return {
241
- ...tokens,
242
- accountIdOverride: choice.accountId,
243
- accountIdSource: choice.source ?? "token",
244
- accountLabel: choice.label,
245
- workspaces,
246
- };
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
+ },
247
178
  };
248
- const buildManualOAuthFlow = (pkce, url, expectedState, onSuccess) => ({
249
- url,
250
- method: "code",
251
- instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
252
- validate: (input) => {
253
- const parsed = parseAuthorizationInput(input);
254
- if (!parsed.code) {
255
- return `No authorization code found. Paste the full callback URL (e.g., ${REDIRECT_URI}?code=...)`;
256
- }
257
- if (!parsed.state) {
258
- return "Missing OAuth state. Paste the full callback URL including both code and state parameters.";
259
- }
260
- if (parsed.state !== expectedState) {
261
- return "OAuth state mismatch. Restart login and paste the callback URL generated for this login attempt.";
262
- }
263
- return undefined;
179
+ const makeReloadAccountManagerDeps = (authFallback) => ({
180
+ currentReloadInFlight: accountReloadInFlight,
181
+ loadFromDisk: (fallback) => AccountManager.loadFromDisk(fallback),
182
+ setCachedAccountManager: (value) => {
183
+ cachedAccountManager = value;
264
184
  },
265
- callback: async (input) => {
266
- const parsed = parseAuthorizationInput(input);
267
- if (!parsed.code || !parsed.state) {
268
- return {
269
- type: "failed",
270
- reason: "invalid_response",
271
- message: "Missing authorization code or OAuth state",
272
- };
273
- }
274
- if (parsed.state !== expectedState) {
275
- return {
276
- type: "failed",
277
- reason: "invalid_response",
278
- message: "OAuth state mismatch. Restart login and try again.",
279
- };
280
- }
281
- const tokens = await exchangeAuthorizationCode(parsed.code, pkce.verifier, REDIRECT_URI);
282
- if (tokens?.type === "success") {
283
- const resolved = resolveAccountSelection(tokens);
284
- if (onSuccess) {
285
- await onSuccess(resolved);
286
- }
287
- return resolved;
288
- }
289
- return tokens?.type === "failed"
290
- ? tokens
291
- : { type: "failed" };
185
+ setAccountManagerPromise: (value) => {
186
+ accountManagerPromise = value;
187
+ },
188
+ setReloadInFlight: (value) => {
189
+ accountReloadInFlight = value;
292
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,
293
209
  });
294
- const runOAuthFlow = async (forceNewLogin = false) => {
295
- const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
296
- logInfo(`OAuth URL: ${redactOAuthUrlForLog(url)}`);
297
- let serverInfo = null;
298
- try {
299
- serverInfo = await startLocalOAuthServer({ state });
300
- }
301
- catch (err) {
302
- logDebug(`[${PLUGIN_NAME}] Failed to start OAuth server: ${err?.message ?? String(err)}`);
303
- serverInfo = null;
304
- }
305
- openBrowserUrl(url);
306
- if (!serverInfo || !serverInfo.ready) {
307
- serverInfo?.close();
308
- const message = `\n[${PLUGIN_NAME}] OAuth callback server failed to start. ` +
309
- `Please retry with "${AUTH_LABELS.OAUTH_MANUAL}".\n`;
310
- logWarn(message);
311
- return { type: "failed" };
312
- }
313
- const result = await serverInfo.waitForCode(state);
314
- serverInfo.close();
315
- if (!result) {
316
- return { type: "failed", reason: "unknown", message: "OAuth callback timeout or cancelled" };
317
- }
318
- return await exchangeAuthorizationCode(result.code, pkce.verifier, REDIRECT_URI);
319
- };
320
- const persistAccountPool = async (results, replaceAll = false) => {
321
- if (results.length === 0)
322
- return;
323
- await withAccountStorageTransaction(async (loadedStorage, persist) => {
324
- const now = Date.now();
325
- const stored = replaceAll ? null : loadedStorage;
326
- const accounts = stored?.accounts ? [...stored.accounts] : [];
327
- for (const result of results) {
328
- const accountId = result.accountIdOverride ?? extractAccountId(result.access);
329
- const accountIdSource = accountId
330
- ? result.accountIdSource ??
331
- (result.accountIdOverride ? "manual" : "token")
332
- : undefined;
333
- const accountLabel = result.accountLabel;
334
- const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
335
- const existingIndex = findMatchingAccountIndex(accounts, {
336
- accountId,
337
- email: accountEmail,
338
- refreshToken: result.refresh,
339
- }, {
340
- allowUniqueAccountIdFallbackWithoutEmail: true,
341
- });
342
- if (existingIndex === undefined) {
343
- const initialWorkspaceIndex = result.workspaces && result.workspaces.length > 0
344
- ? (() => {
345
- if (accountId) {
346
- const matchingWorkspaceIndex = result.workspaces.findIndex((workspace) => workspace.id === accountId);
347
- if (matchingWorkspaceIndex >= 0) {
348
- return matchingWorkspaceIndex;
349
- }
350
- }
351
- const firstEnabledWorkspaceIndex = result.workspaces.findIndex((workspace) => workspace.enabled !== false);
352
- return firstEnabledWorkspaceIndex >= 0 ? firstEnabledWorkspaceIndex : 0;
353
- })()
354
- : undefined;
355
- accounts.push({
356
- accountId,
357
- accountIdSource,
358
- accountLabel,
359
- email: accountEmail,
360
- refreshToken: result.refresh,
361
- accessToken: result.access,
362
- expiresAt: result.expires,
363
- addedAt: now,
364
- lastUsed: now,
365
- workspaces: result.workspaces,
366
- currentWorkspaceIndex: initialWorkspaceIndex,
367
- });
368
- continue;
369
- }
370
- const existing = accounts[existingIndex];
371
- if (!existing)
372
- continue;
373
- const nextEmail = accountEmail ?? sanitizeEmail(existing.email);
374
- const nextAccountId = accountId ?? existing.accountId;
375
- const nextAccountIdSource = accountId ? accountIdSource ?? existing.accountIdSource : existing.accountIdSource;
376
- const nextAccountLabel = accountLabel ?? existing.accountLabel;
377
- // Preserve tracked workspace state when auth refreshes do not return workspace metadata.
378
- const mergedWorkspaces = result.workspaces
379
- ? result.workspaces.map((newWs) => {
380
- const existingWs = existing.workspaces?.find((w) => w.id === newWs.id);
381
- return existingWs
382
- ? {
383
- ...newWs,
384
- enabled: existingWs.enabled,
385
- disabledAt: existingWs.disabledAt,
386
- }
387
- : newWs;
388
- })
389
- : existing.workspaces;
390
- const currentWorkspaceId = existing.workspaces?.[typeof existing.currentWorkspaceIndex === "number"
391
- ? existing.currentWorkspaceIndex
392
- : 0]?.id;
393
- const nextCurrentWorkspaceIndex = mergedWorkspaces && mergedWorkspaces.length > 0
394
- ? (() => {
395
- if (currentWorkspaceId) {
396
- const matchingWorkspaceIndex = mergedWorkspaces.findIndex((workspace) => workspace.id === currentWorkspaceId);
397
- if (matchingWorkspaceIndex >= 0) {
398
- return matchingWorkspaceIndex;
399
- }
400
- }
401
- const defaultWorkspaceIndex = mergedWorkspaces.findIndex((workspace) => workspace.isDefault === true);
402
- if (defaultWorkspaceIndex >= 0) {
403
- return defaultWorkspaceIndex;
404
- }
405
- const firstEnabledWorkspaceIndex = mergedWorkspaces.findIndex((workspace) => workspace.enabled !== false);
406
- return firstEnabledWorkspaceIndex >= 0 ? firstEnabledWorkspaceIndex : 0;
407
- })()
408
- : existing.currentWorkspaceIndex;
409
- accounts[existingIndex] = {
410
- ...existing,
411
- accountId: nextAccountId,
412
- accountIdSource: nextAccountIdSource,
413
- accountLabel: nextAccountLabel,
414
- email: nextEmail,
415
- refreshToken: result.refresh,
416
- accessToken: result.access,
417
- expiresAt: result.expires,
418
- lastUsed: now,
419
- workspaces: mergedWorkspaces,
420
- currentWorkspaceIndex: nextCurrentWorkspaceIndex,
421
- };
422
- }
423
- if (accounts.length === 0)
424
- return;
425
- const activeIndex = replaceAll
426
- ? 0
427
- : typeof stored?.activeIndex === "number" && Number.isFinite(stored.activeIndex)
428
- ? stored.activeIndex
429
- : 0;
430
- const clampedActiveIndex = Math.max(0, Math.min(activeIndex, accounts.length - 1));
431
- const activeIndexByFamily = {};
432
- for (const family of MODEL_FAMILIES) {
433
- const storedFamilyIndex = stored?.activeIndexByFamily?.[family];
434
- const rawFamilyIndex = replaceAll
435
- ? 0
436
- : typeof storedFamilyIndex === "number" && Number.isFinite(storedFamilyIndex)
437
- ? storedFamilyIndex
438
- : clampedActiveIndex;
439
- activeIndexByFamily[family] = Math.max(0, Math.min(Math.floor(rawFamilyIndex), accounts.length - 1));
440
- }
441
- await persist({
442
- version: 3,
443
- accounts,
444
- activeIndex: clampedActiveIndex,
445
- activeIndexByFamily,
446
- });
447
- });
448
- };
449
- const showToast = async (message, variant = "success", options) => {
450
- try {
451
- await client.tui.showToast({
452
- body: {
453
- message,
454
- variant,
455
- ...(options?.title && { title: options.title }),
456
- ...(options?.duration && { duration: options.duration }),
457
- },
458
- });
459
- }
460
- catch {
461
- // Ignore when TUI is not available.
462
- }
463
- };
464
- const resolveActiveIndex = (storage, family = "codex") => {
465
- const total = storage.accounts.length;
466
- if (total === 0)
467
- return 0;
468
- const rawCandidate = storage.activeIndexByFamily?.[family] ?? storage.activeIndex;
469
- const raw = Number.isFinite(rawCandidate) ? rawCandidate : 0;
470
- return Math.max(0, Math.min(raw, total - 1));
471
- };
472
- const hydrateEmails = async (storage) => {
473
- if (!storage)
474
- return storage;
475
- const skipHydrate = process.env.VITEST_WORKER_ID !== undefined ||
476
- process.env.NODE_ENV === "test" ||
477
- process.env.CODEX_SKIP_EMAIL_HYDRATE === "1";
478
- if (skipHydrate)
479
- return storage;
480
- const accountsCopy = storage.accounts.map((account) => account ? { ...account } : account);
481
- const accountsToHydrate = accountsCopy.filter((account) => account && !account.email);
482
- if (accountsToHydrate.length === 0)
483
- return storage;
484
- let changed = false;
485
- await Promise.all(accountsToHydrate.map(async (account) => {
486
- try {
487
- const refreshed = await queuedRefresh(account.refreshToken);
488
- if (refreshed.type !== "success")
489
- return;
490
- const id = extractAccountId(refreshed.access);
491
- const email = sanitizeEmail(extractAccountEmail(refreshed.access, refreshed.idToken));
492
- if (id &&
493
- id !== account.accountId &&
494
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId)) {
495
- account.accountId = id;
496
- account.accountIdSource = "token";
497
- changed = true;
498
- }
499
- if (email && email !== account.email) {
500
- account.email = email;
501
- changed = true;
502
- }
503
- if (refreshed.access && refreshed.access !== account.accessToken) {
504
- account.accessToken = refreshed.access;
505
- changed = true;
506
- }
507
- if (typeof refreshed.expires === "number" && refreshed.expires !== account.expiresAt) {
508
- account.expiresAt = refreshed.expires;
509
- changed = true;
510
- }
511
- if (refreshed.refresh && refreshed.refresh !== account.refreshToken) {
512
- account.refreshToken = refreshed.refresh;
513
- changed = true;
514
- }
515
- }
516
- catch {
517
- logWarn(`[${PLUGIN_NAME}] Failed to hydrate email for account`);
518
- }
519
- }));
520
- if (changed) {
521
- storage.accounts = accountsCopy;
522
- await saveAccounts(storage);
523
- }
524
- return storage;
525
- };
526
- const getRateLimitResetTimeForFamily = (account, now, family) => {
527
- const times = account.rateLimitResetTimes;
528
- if (!times)
529
- return null;
530
- let minReset = null;
531
- const prefix = `${family}:`;
532
- for (const [key, value] of Object.entries(times)) {
533
- if (typeof value !== "number")
534
- continue;
535
- if (value <= now)
536
- continue;
537
- if (key !== family && !key.startsWith(prefix))
538
- continue;
539
- if (minReset === null || value < minReset) {
540
- minReset = value;
541
- }
542
- }
543
- return minReset;
544
- };
545
- const formatRateLimitEntry = (account, now, family = "codex") => {
546
- const resetAt = getRateLimitResetTimeForFamily(account, now, family);
547
- if (typeof resetAt !== "number")
548
- return null;
549
- const remaining = resetAt - now;
550
- if (remaining <= 0)
551
- return null;
552
- return `resets in ${formatWaitTime(remaining)}`;
553
- };
554
- const applyUiRuntimeFromConfig = (pluginConfig) => {
555
- return setUiRuntimeOptions({
556
- v2Enabled: getCodexTuiV2(pluginConfig),
557
- colorProfile: getCodexTuiColorProfile(pluginConfig),
558
- glyphMode: getCodexTuiGlyphMode(pluginConfig),
559
- });
560
- };
561
- const resolveUiRuntime = () => {
562
- return applyUiRuntimeFromConfig(loadPluginConfig());
563
- };
564
- const getStatusMarker = (ui, status) => {
565
- if (!ui.v2Enabled) {
566
- if (status === "ok")
567
- return "✓";
568
- if (status === "warning")
569
- return "!";
570
- return "✗";
571
- }
572
- if (status === "ok")
573
- return ui.theme.glyphs.check;
574
- if (status === "warning")
575
- return "!";
576
- return ui.theme.glyphs.cross;
577
- };
578
- const invalidateAccountManagerCache = () => {
579
- cachedAccountManager = null;
580
- accountManagerPromise = null;
581
- };
582
- const reloadAccountManagerFromDisk = async (authFallback) => {
583
- if (accountReloadInFlight) {
584
- return accountReloadInFlight;
585
- }
586
- accountReloadInFlight = (async () => {
587
- const reloaded = await AccountManager.loadFromDisk(authFallback);
588
- cachedAccountManager = reloaded;
589
- accountManagerPromise = Promise.resolve(reloaded);
590
- return reloaded;
591
- })();
592
- try {
593
- return await accountReloadInFlight;
594
- }
595
- finally {
596
- accountReloadInFlight = null;
597
- }
598
- };
599
- const applyAccountStorageScope = (pluginConfig) => {
600
- const perProjectAccounts = getPerProjectAccounts(pluginConfig);
601
- setStorageBackupEnabled(getStorageBackupEnabled(pluginConfig));
602
- if (isCodexCliSyncEnabled()) {
603
- if (perProjectAccounts && !perProjectStorageWarningShown) {
604
- perProjectStorageWarningShown = true;
605
- logWarn(`[${PLUGIN_NAME}] CODEX_AUTH_PER_PROJECT_ACCOUNTS is ignored while Codex CLI sync is enabled. Using global account storage.`);
606
- }
607
- setStoragePath(null);
608
- return;
609
- }
610
- setStoragePath(perProjectAccounts ? process.cwd() : null);
611
- };
612
210
  const ensureLiveAccountSync = async (pluginConfig, authFallback) => {
613
- if (!getLiveAccountSync(pluginConfig)) {
614
- if (liveAccountSync) {
615
- liveAccountSync.stop();
616
- liveAccountSync = null;
617
- liveAccountSyncPath = null;
618
- }
619
- return;
620
- }
621
- const targetPath = getStoragePath();
622
- if (!liveAccountSync) {
623
- liveAccountSync = new LiveAccountSync(async () => {
624
- 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));
625
220
  }, {
626
221
  debounceMs: getLiveAccountSyncDebounceMs(pluginConfig),
627
222
  pollIntervalMs: getLiveAccountSyncPollMs(pluginConfig),
628
- });
629
- registerCleanup(() => {
630
- liveAccountSync?.stop();
631
- });
632
- }
633
- if (liveAccountSyncPath !== targetPath) {
634
- let switched = false;
635
- for (let attempt = 0; attempt < 3; attempt += 1) {
636
- try {
637
- await liveAccountSync.syncToPath(targetPath);
638
- liveAccountSyncPath = targetPath;
639
- switched = true;
640
- break;
641
- }
642
- catch (error) {
643
- const code = error?.code;
644
- if (code !== "EBUSY" && code !== "EPERM") {
645
- throw error;
646
- }
647
- await new Promise((resolve) => setTimeout(resolve, 25 * 2 ** attempt));
648
- }
649
- }
650
- if (!switched) {
651
- logWarn(`[${PLUGIN_NAME}] Live account sync path switch failed due to transient filesystem locks; keeping previous watcher.`);
652
- }
653
- }
223
+ }),
224
+ registerCleanup,
225
+ logWarn,
226
+ pluginName: PLUGIN_NAME,
227
+ ensureLiveAccountSyncState,
228
+ });
229
+ liveAccountSync = next.liveAccountSync;
230
+ liveAccountSyncPath = next.liveAccountSyncPath;
654
231
  };
655
232
  const ensureRefreshGuardian = (pluginConfig) => {
656
- if (!getProactiveRefreshGuardian(pluginConfig)) {
657
- if (refreshGuardian) {
658
- refreshGuardian.stop();
659
- refreshGuardian = null;
660
- refreshGuardianConfigKey = null;
661
- }
662
- return;
663
- }
664
- const intervalMs = getProactiveRefreshIntervalMs(pluginConfig);
665
- const bufferMs = getProactiveRefreshBufferMs(pluginConfig);
666
- const configKey = `${intervalMs}:${bufferMs}`;
667
- if (refreshGuardian && refreshGuardianConfigKey === configKey)
668
- return;
669
- if (refreshGuardian) {
670
- refreshGuardian.stop();
671
- }
672
- refreshGuardian = new RefreshGuardian(() => cachedAccountManager, { intervalMs, bufferMs });
673
- refreshGuardianConfigKey = configKey;
674
- refreshGuardian.start();
675
- registerCleanup(() => {
676
- 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,
677
246
  });
247
+ refreshGuardian = next.refreshGuardian;
248
+ refreshGuardianConfigKey = next.refreshGuardianConfigKey;
249
+ refreshGuardianCleanupRegistered =
250
+ next.refreshGuardianCleanupRegistered;
678
251
  };
679
252
  const ensureSessionAffinity = (pluginConfig) => {
680
- if (!getSessionAffinity(pluginConfig)) {
681
- sessionAffinityStore = null;
682
- sessionAffinityConfigKey = null;
683
- return;
684
- }
685
- const ttlMs = getSessionAffinityTtlMs(pluginConfig);
686
- const maxEntries = getSessionAffinityMaxEntries(pluginConfig);
687
- const configKey = `${ttlMs}:${maxEntries}`;
688
- if (sessionAffinityStore && sessionAffinityConfigKey === configKey)
689
- return;
690
- sessionAffinityStore = new SessionAffinityStore({ ttlMs, maxEntries });
691
- sessionAffinityConfigKey = configKey;
692
- };
693
- const applyPreemptiveQuotaSettings = (pluginConfig) => {
694
- preemptiveQuotaScheduler.configure({
695
- enabled: getPreemptiveQuotaEnabled(pluginConfig),
696
- remainingPercentThresholdPrimary: getPreemptiveQuotaRemainingPercent5h(pluginConfig),
697
- remainingPercentThresholdSecondary: getPreemptiveQuotaRemainingPercent7d(pluginConfig),
698
- 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 }),
699
261
  });
262
+ sessionAffinityStore = next.sessionAffinityStore;
263
+ sessionAffinityConfigKey = next.sessionAffinityConfigKey;
700
264
  };
265
+ const applyPreemptiveQuotaSettings = (pluginConfig) => applyPreemptiveQuotaSettingsFromConfig(pluginConfig, {
266
+ configure: (options) => preemptiveQuotaScheduler.configure(options),
267
+ getPreemptiveQuotaEnabled,
268
+ getPreemptiveQuotaRemainingPercent5h,
269
+ getPreemptiveQuotaRemainingPercent7d,
270
+ getPreemptiveQuotaMaxDeferralMs,
271
+ });
701
272
  // Event handler for session recovery and account selection
702
- const eventHandler = async (input) => {
703
- try {
704
- const { event } = input;
705
- // Handle TUI account selection events
706
- // Accepts generic selection events with an index property
707
- if (event.type === "account.select" ||
708
- event.type === "openai.account.select") {
709
- const props = event.properties;
710
- // Filter by provider if specified
711
- if (props.provider && props.provider !== "openai" && props.provider !== PROVIDER_ID) {
712
- return;
713
- }
714
- const index = props.index ?? props.accountIndex;
715
- if (typeof index === "number") {
716
- const storage = await loadAccounts();
717
- if (!storage || index < 0 || index >= storage.accounts.length) {
718
- return;
719
- }
720
- const now = Date.now();
721
- const account = storage.accounts[index];
722
- if (account) {
723
- account.lastUsed = now;
724
- account.lastSwitchReason = "rotation";
725
- }
726
- storage.activeIndex = index;
727
- storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
728
- for (const family of MODEL_FAMILIES) {
729
- storage.activeIndexByFamily[family] = index;
730
- }
731
- await saveAccounts(storage);
732
- if (cachedAccountManager) {
733
- await cachedAccountManager.syncCodexCliActiveSelectionForIndex(index);
734
- }
735
- lastCodexCliActiveSyncIndex = index;
736
- // Reload manager from disk so we don't overwrite newer rotated
737
- // refresh tokens with stale in-memory state.
738
- if (cachedAccountManager) {
739
- await reloadAccountManagerFromDisk();
740
- }
741
- await showToast(`Switched to account ${index + 1}`, "info");
742
- }
743
- }
744
- }
745
- catch (error) {
746
- logDebug(`[${PLUGIN_NAME}] Event handler error: ${error instanceof Error ? error.message : String(error)}`);
747
- }
748
- };
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
+ });
749
293
  // Initialize runtime UI settings once on plugin load; auth/tools refresh this dynamically.
750
- resolveUiRuntime();
294
+ applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
751
295
  return {
752
296
  event: eventHandler,
753
297
  auth: {
@@ -756,11 +300,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
756
300
  * Loader function that configures OAuth authentication and request handling
757
301
  *
758
302
  * This function:
759
- * 1. Validates OAuth authentication
760
- * 2. Loads multi-account pool from disk (fallback to current auth)
761
- * 3. Loads user configuration from runtime model config
762
- * 4. Fetches Codex system instructions from GitHub (cached)
763
- * 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
764
308
  *
765
309
  * @param getAuth - Function to retrieve current auth state
766
310
  * @param provider - Provider configuration from runtime model config
@@ -769,11 +313,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
769
313
  async loader(getAuth, provider) {
770
314
  const auth = await getAuth();
771
315
  const pluginConfig = loadPluginConfig();
772
- applyUiRuntimeFromConfig(pluginConfig);
773
- applyAccountStorageScope(pluginConfig);
774
- ensureSessionAffinity(pluginConfig);
775
- ensureRefreshGuardian(pluginConfig);
776
- applyPreemptiveQuotaSettings(pluginConfig);
316
+ applyLoaderRuntimeSetup({
317
+ pluginConfig,
318
+ applyUiRuntimeFromConfig: (config) => applyUiRuntimeFromConfig(config, setUiRuntimeOptions),
319
+ applyAccountStorageScope,
320
+ ensureSessionAffinity,
321
+ ensureRefreshGuardian,
322
+ applyPreemptiveQuotaSettings,
323
+ });
777
324
  // Only handle OAuth auth type, skip API key auth
778
325
  if (auth.type !== "oauth") {
779
326
  return {};
@@ -797,15 +344,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
797
344
  try {
798
345
  await ensureLiveAccountSync(pluginConfig, auth);
799
346
  if (!accountManagerPromise) {
800
- await reloadAccountManagerFromDisk(auth);
347
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
801
348
  }
802
349
  const managerPromise = accountManagerPromise ??
803
- reloadAccountManagerFromDisk(auth);
350
+ reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
804
351
  let accountManager = await managerPromise;
805
352
  cachedAccountManager = accountManager;
806
353
  const refreshToken = auth.type === "oauth" ? auth.refresh : "";
807
- const needsPersist = refreshToken &&
808
- !accountManager.hasRefreshToken(refreshToken);
354
+ const needsPersist = refreshToken && !accountManager.hasRefreshToken(refreshToken);
809
355
  if (needsPersist) {
810
356
  await accountManager.saveToDisk();
811
357
  }
@@ -846,7 +392,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
846
392
  STREAM_FAILOVER_SOFT_TIMEOUT_BY_MODE[failoverMode]);
847
393
  const streamFailoverHardTimeoutMs = Math.max(streamFailoverSoftTimeoutMs, parseEnvInt(process.env.CODEX_AUTH_STREAM_STALL_HARD_TIMEOUT_MS) ??
848
394
  streamStallTimeoutMs);
849
- const maxSameAccountRetries = failoverMode === "conservative" ? 2 : failoverMode === "balanced" ? 1 : 0;
395
+ const maxSameAccountRetries = failoverMode === "conservative"
396
+ ? 2
397
+ : failoverMode === "balanced"
398
+ ? 1
399
+ : 0;
850
400
  const sessionRecoveryEnabled = getSessionRecovery(pluginConfig);
851
401
  const autoResumeEnabled = getAutoResume(pluginConfig);
852
402
  const emptyResponseMaxRetries = getEmptyResponseMaxRetries(pluginConfig);
@@ -875,11 +425,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
875
425
  prewarmHostCodexPrompt();
876
426
  }
877
427
  }
878
- const recoveryHook = sessionRecoveryEnabled
879
- ? createSessionRecoveryHook({ client, directory: process.cwd() }, { sessionRecovery: true, autoResume: autoResumeEnabled })
880
- : null;
428
+ const recoveryHook = createRuntimeSessionRecoveryHook({
429
+ enabled: sessionRecoveryEnabled,
430
+ client,
431
+ directory: process.cwd(),
432
+ autoResume: autoResumeEnabled,
433
+ });
881
434
  checkAndNotify(async (message, variant) => {
882
- await showToast(message, variant);
435
+ await showRuntimeToast(client, message, variant);
883
436
  }).catch((err) => {
884
437
  logDebug(`Update check failed: ${err instanceof Error ? err.message : String(err)}`);
885
438
  });
@@ -904,7 +457,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
904
457
  */
905
458
  async fetch(input, init) {
906
459
  try {
907
- if (cachedAccountManager && cachedAccountManager !== accountManager) {
460
+ if (cachedAccountManager &&
461
+ cachedAccountManager !== accountManager) {
908
462
  accountManager = cachedAccountManager;
909
463
  }
910
464
  // Step 1: Extract and rewrite URL for Codex backend
@@ -914,118 +468,58 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
914
468
  // Instructions are fetched per model family (codex-max, codex, gpt-5.1)
915
469
  // Capture original stream value before transformation
916
470
  // generateText() sends no stream field, streamText() sends stream=true
917
- const normalizeRequestInit = async (requestInput, requestInit) => {
918
- if (requestInit)
919
- return requestInit;
920
- if (!(requestInput instanceof Request))
921
- return requestInit;
922
- const method = requestInput.method || "GET";
923
- const normalized = {
924
- method,
925
- headers: new Headers(requestInput.headers),
926
- };
927
- if (method !== "GET" && method !== "HEAD") {
928
- try {
929
- const bodyText = await requestInput.clone().text();
930
- if (bodyText) {
931
- normalized.body = bodyText;
932
- }
933
- }
934
- catch {
935
- // Body may be unreadable; proceed without it.
936
- }
937
- }
938
- return normalized;
939
- };
940
- const parseRequestBodyFromInit = async (body) => {
941
- if (!body)
942
- return {};
943
- try {
944
- if (typeof body === "string") {
945
- return JSON.parse(body);
946
- }
947
- if (body instanceof Uint8Array) {
948
- return JSON.parse(new TextDecoder().decode(body));
949
- }
950
- if (body instanceof ArrayBuffer) {
951
- return JSON.parse(new TextDecoder().decode(new Uint8Array(body)));
952
- }
953
- if (ArrayBuffer.isView(body)) {
954
- const view = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
955
- return JSON.parse(new TextDecoder().decode(view));
956
- }
957
- if (typeof Blob !== "undefined" && body instanceof Blob) {
958
- return JSON.parse(await body.text());
959
- }
960
- }
961
- catch {
962
- logWarn("Failed to parse request body, using empty object");
963
- }
964
- return {};
965
- };
966
471
  const baseInit = await normalizeRequestInit(input, init);
967
- const originalBody = await parseRequestBodyFromInit(baseInit?.body);
472
+ const originalBody = await parseRequestBodyFromInit(baseInit?.body, logWarn);
968
473
  const isStreaming = originalBody.stream === true;
969
- const parsedBody = Object.keys(originalBody).length > 0 ? originalBody : undefined;
474
+ const parsedBody = Object.keys(originalBody).length > 0
475
+ ? { ...originalBody }
476
+ : undefined;
970
477
  const transformation = await transformRequestForCodex(baseInit, url, effectiveUserConfig, codexMode, parsedBody, {
971
478
  fastSession: fastSessionEnabled,
972
479
  fastSessionStrategy,
973
480
  fastSessionMaxInputItems,
481
+ deferFastSessionInputTrimming: fastSessionEnabled,
482
+ allowBackgroundResponses: getBackgroundResponses(pluginConfig),
974
483
  });
975
484
  let requestInit = transformation?.updatedInit ?? baseInit;
976
485
  let transformedBody = transformation?.body;
486
+ let pendingFastSessionInputTrim = transformation?.deferredFastSessionInputTrim;
977
487
  const promptCacheKey = transformedBody?.prompt_cache_key;
978
488
  let model = transformedBody?.model;
979
489
  let modelFamily = model ? getModelFamily(model) : "gpt-5.1";
980
490
  let quotaKey = model ? `${modelFamily}:${model}` : modelFamily;
491
+ const responseContinuationEnabled = getResponseContinuation(pluginConfig);
981
492
  const threadIdCandidate = (process.env.CODEX_THREAD_ID ?? promptCacheKey ?? "")
982
493
  .toString()
983
494
  .trim() || undefined;
984
495
  const sessionAffinityKey = threadIdCandidate ?? promptCacheKey ?? null;
985
- 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
+ }
986
515
  const preferredSessionAccountIndex = sessionAffinityStore?.getPreferredAccountIndex(sessionAffinityKey);
987
516
  sessionAffinityStore?.prune();
988
- const requestCorrelationId = setCorrelationId(threadIdCandidate ? `${threadIdCandidate}:${Date.now()}` : undefined);
517
+ const requestCorrelationId = setCorrelationId(threadIdCandidate
518
+ ? `${threadIdCandidate}:${Date.now()}`
519
+ : undefined);
989
520
  runtimeMetrics.lastRequestAt = Date.now();
990
521
  const abortSignal = requestInit?.signal ?? init?.signal ?? null;
991
- const sleep = (ms) => new Promise((resolve, reject) => {
992
- if (abortSignal?.aborted) {
993
- reject(new Error("Aborted"));
994
- return;
995
- }
996
- const timeout = setTimeout(() => {
997
- cleanup();
998
- resolve();
999
- }, ms);
1000
- const onAbort = () => {
1001
- cleanup();
1002
- reject(new Error("Aborted"));
1003
- };
1004
- const cleanup = () => {
1005
- clearTimeout(timeout);
1006
- abortSignal?.removeEventListener("abort", onAbort);
1007
- };
1008
- abortSignal?.addEventListener("abort", onAbort, { once: true });
1009
- });
1010
- const sleepWithCountdown = async (totalMs, message, intervalMs = 5000) => {
1011
- const startTime = Date.now();
1012
- const endTime = startTime + totalMs;
1013
- while (Date.now() < endTime) {
1014
- if (abortSignal?.aborted) {
1015
- throw new Error("Aborted");
1016
- }
1017
- const remaining = Math.max(0, endTime - Date.now());
1018
- const waitLabel = formatWaitTime(remaining);
1019
- await showToast(`${message} (${waitLabel} remaining)`, "warning", { duration: Math.min(intervalMs + 1000, toastDurationMs) });
1020
- const sleepTime = Math.min(intervalMs, remaining);
1021
- if (sleepTime > 0) {
1022
- await sleep(sleepTime);
1023
- }
1024
- else {
1025
- break;
1026
- }
1027
- }
1028
- };
522
+ const sleep = createAbortableSleep(abortSignal);
1029
523
  let allRateLimitedRetries = 0;
1030
524
  let emptyResponseRetries = 0;
1031
525
  const attemptedUnsupportedFallbackModels = new Set();
@@ -1040,11 +534,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1040
534
  let usedPreferredSessionAccount = false;
1041
535
  const capabilityBoostByAccount = {};
1042
536
  const accountSnapshotSource = accountManager;
1043
- const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot === "function"
1044
- ? accountSnapshotSource.getAccountsSnapshot() ?? []
537
+ const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot ===
538
+ "function"
539
+ ? (accountSnapshotSource.getAccountsSnapshot() ?? [])
1045
540
  : [];
1046
541
  if (accountSnapshotList.length === 0 &&
1047
- typeof accountSnapshotSource.getAccountByIndex === "function") {
542
+ typeof accountSnapshotSource.getAccountByIndex ===
543
+ "function") {
1048
544
  for (let accountSnapshotIndex = 0; accountSnapshotIndex < accountCount; accountSnapshotIndex += 1) {
1049
545
  const candidate = accountSnapshotSource.getAccountByIndex(accountSnapshotIndex);
1050
546
  if (candidate) {
@@ -1054,7 +550,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1054
550
  }
1055
551
  for (const candidate of accountSnapshotList) {
1056
552
  const accountKey = resolveEntitlementAccountKey(candidate);
1057
- capabilityBoostByAccount[candidate.index] = capabilityPolicyStore.getBoost(accountKey, model ?? modelFamily);
553
+ capabilityBoostByAccount[candidate.index] =
554
+ capabilityPolicyStore.getBoost(accountKey, model ?? modelFamily);
1058
555
  }
1059
556
  accountAttemptLoop: while (attempted.size < Math.max(1, accountCount)) {
1060
557
  let account = null;
@@ -1098,7 +595,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1098
595
  runtimeMetrics.authRefreshFailures++;
1099
596
  runtimeMetrics.failedRequests++;
1100
597
  runtimeMetrics.accountRotations++;
1101
- runtimeMetrics.lastError = err?.message ?? String(err);
598
+ runtimeMetrics.lastError =
599
+ err?.message ?? String(err);
1102
600
  const failures = accountManager.incrementAuthFailures(account);
1103
601
  const accountLabel = formatAccountLabel(account, account.index);
1104
602
  const authFailurePolicy = evaluateFailurePolicy({
@@ -1112,7 +610,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1112
610
  accountManager.removeAccount(account);
1113
611
  sessionAffinityStore?.reindexAfterRemoval(removedIndex);
1114
612
  accountManager.saveToDiskDebounced();
1115
- 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 });
1116
614
  continue;
1117
615
  }
1118
616
  if (typeof authFailurePolicy.cooldownMs === "number" &&
@@ -1158,8 +656,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1158
656
  continue;
1159
657
  }
1160
658
  account.accountId = accountId;
1161
- if (!hadAccountId && tokenAccountId && accountId === tokenAccountId) {
1162
- account.accountIdSource = storedAccountIdSource ?? "token";
659
+ if (!hadAccountId &&
660
+ tokenAccountId &&
661
+ accountId === tokenAccountId) {
662
+ account.accountIdSource =
663
+ storedAccountIdSource ?? "token";
1163
664
  }
1164
665
  if (resolvedEmail) {
1165
666
  account.email = resolvedEmail;
@@ -1167,13 +668,38 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1167
668
  if (accountCount > 1 &&
1168
669
  accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1169
670
  const accountLabel = formatAccountLabel(account, account.index);
1170
- await showToast(`Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
671
+ await showRuntimeToast(client, `Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
1171
672
  accountManager.markToastShown(account.index);
1172
673
  }
1173
674
  const headers = createCodexHeaders(requestInit, accountId, accountAuth.access, {
1174
675
  model,
1175
676
  promptCacheKey: effectivePromptCacheKey,
1176
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
+ }
1177
703
  const quotaScheduleKey = `${entitlementAccountKey}:${model ?? modelFamily}`;
1178
704
  const capabilityModelKey = model ?? modelFamily;
1179
705
  const quotaDeferral = preemptiveQuotaScheduler.getDeferral(quotaScheduleKey);
@@ -1190,8 +716,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1190
716
  if (!tokenConsumed) {
1191
717
  accountManager.recordRateLimit(account, modelFamily, model);
1192
718
  runtimeMetrics.accountRotations++;
1193
- runtimeMetrics.lastError =
1194
- `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}` : ""})`;
1195
720
  logWarn(`Skipping account ${account.index + 1}: local token bucket depleted for ${modelFamily}${model ? `:${model}` : ""}`);
1196
721
  continue;
1197
722
  }
@@ -1211,14 +736,17 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1211
736
  fetchController.abort(timeoutReason);
1212
737
  }, requestTimeoutMs);
1213
738
  const onUserAbort = abortSignal
1214
- ? () => fetchController.abort(abortSignal.reason ?? new Error("Aborted by user"))
739
+ ? () => fetchController.abort(abortSignal.reason ??
740
+ new Error("Aborted by user"))
1215
741
  : null;
1216
742
  if (abortSignal?.aborted) {
1217
743
  clearTimeout(fetchTimeoutId);
1218
744
  fetchController.abort(abortSignal.reason ?? new Error("Aborted by user"));
1219
745
  }
1220
746
  else if (abortSignal && onUserAbort) {
1221
- abortSignal.addEventListener("abort", onUserAbort, { once: true });
747
+ abortSignal.addEventListener("abort", onUserAbort, {
748
+ once: true,
749
+ });
1222
750
  }
1223
751
  try {
1224
752
  runtimeMetrics.totalRequests++;
@@ -1239,11 +767,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1239
767
  runtimeMetrics.userAborts++;
1240
768
  runtimeMetrics.lastError = "request aborted by user";
1241
769
  sessionAffinityStore?.forgetSession(sessionAffinityKey);
1242
- throw (fetchAbortReason instanceof Error
770
+ throw fetchAbortReason instanceof Error
1243
771
  ? fetchAbortReason
1244
- : new Error("Aborted by user"));
772
+ : new Error("Aborted by user");
1245
773
  }
1246
- const errorMsg = networkError instanceof Error ? networkError.message : String(networkError);
774
+ const errorMsg = networkError instanceof Error
775
+ ? networkError.message
776
+ : String(networkError);
1247
777
  logWarn(`Network error for account ${account.index + 1}: ${errorMsg}`);
1248
778
  runtimeMetrics.failedRequests++;
1249
779
  runtimeMetrics.networkErrors++;
@@ -1296,18 +826,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1296
826
  if (contextOverflowResult.handled) {
1297
827
  return contextOverflowResult.response;
1298
828
  }
1299
- const { response: errorResponse, rateLimit, errorBody } = await handleErrorResponse(response, {
829
+ const { response: errorResponse, rateLimit, errorBody, } = await handleErrorResponse(response, {
1300
830
  requestCorrelationId,
1301
831
  threadId: threadIdCandidate,
1302
832
  });
1303
833
  const unsupportedModelInfo = getUnsupportedCodexModelInfo(errorBody);
1304
834
  const hasRemainingAccounts = attempted.size < Math.max(1, accountCount);
1305
- const blockedModel = unsupportedModelInfo.unsupportedModel ?? model ?? "requested model";
835
+ const blockedModel = unsupportedModelInfo.unsupportedModel ??
836
+ model ??
837
+ "requested model";
1306
838
  const blockedModelNormalized = blockedModel.toLowerCase();
1307
839
  const shouldForceSparkFallback = unsupportedModelInfo.isUnsupported &&
1308
840
  (blockedModelNormalized === "gpt-5.3-codex-spark" ||
1309
841
  blockedModelNormalized.includes("gpt-5.3-codex-spark"));
1310
- const allowUnsupportedFallback = fallbackOnUnsupportedCodexModel || shouldForceSparkFallback;
842
+ const allowUnsupportedFallback = fallbackOnUnsupportedCodexModel ||
843
+ shouldForceSparkFallback;
1311
844
  // Entitlements can differ by account/workspace, so try remaining
1312
845
  // accounts before degrading the model via fallback.
1313
846
  // Spark entitlement is commonly unavailable on non-Pro/Business workspaces;
@@ -1352,12 +885,16 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1352
885
  model = fallbackModel;
1353
886
  modelFamily = getModelFamily(model);
1354
887
  quotaKey = `${modelFamily}:${model}`;
1355
- if (transformedBody && typeof transformedBody === "object") {
888
+ if (transformedBody &&
889
+ typeof transformedBody === "object") {
1356
890
  transformedBody = { ...transformedBody, model };
1357
891
  }
1358
892
  else {
1359
- let fallbackBody = { model };
1360
- if (requestInit?.body && typeof requestInit.body === "string") {
893
+ let fallbackBody = {
894
+ model,
895
+ };
896
+ if (requestInit?.body &&
897
+ typeof requestInit.body === "string") {
1361
898
  try {
1362
899
  const parsed = JSON.parse(requestInit.body);
1363
900
  fallbackBody = { ...parsed, model };
@@ -1380,11 +917,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1380
917
  fallbackApplied: true,
1381
918
  fallbackReason: "unsupported-model-entitlement",
1382
919
  });
1383
- 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 });
1384
921
  restartAccountTraversalWithFallback = true;
1385
922
  break;
1386
923
  }
1387
- if (unsupportedModelInfo.isUnsupported && !allowUnsupportedFallback) {
924
+ if (unsupportedModelInfo.isUnsupported &&
925
+ !allowUnsupportedFallback) {
1388
926
  entitlementCache.markBlocked(entitlementAccountKey, blockedModel, "unsupported-model");
1389
927
  capabilityPolicyStore.recordUnsupported(entitlementAccountKey, blockedModel);
1390
928
  runtimeMetrics.lastError = `Unsupported model (strict): ${blockedModel}`;
@@ -1395,7 +933,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1395
933
  fallbackApplied: false,
1396
934
  fallbackReason: "unsupported-model-entitlement",
1397
935
  });
1398
- 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 });
1399
937
  }
1400
938
  if (unsupportedModelInfo.isUnsupported &&
1401
939
  allowUnsupportedFallback &&
@@ -1412,7 +950,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1412
950
  if (isDisabledWorkspaceError) {
1413
951
  runtimeMetrics.failedRequests++;
1414
952
  runtimeMetrics.lastError = `Workspace disabled for account ${account.index + 1}`;
1415
- if (!account.workspaces || account.workspaces.length === 0) {
953
+ if (!account.workspaces ||
954
+ account.workspaces.length === 0) {
1416
955
  logWarn(`Workspace disabled/expired for account ${account.index + 1} without tracked workspaces. Leaving account enabled.`, { errorCode: workspaceErrorCode });
1417
956
  if (hasRemainingAccounts) {
1418
957
  continue accountAttemptLoop;
@@ -1421,7 +960,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1421
960
  }
1422
961
  else {
1423
962
  const currentWorkspace = accountManager.getCurrentWorkspace(account);
1424
- const workspaceName = currentWorkspace?.name ?? currentWorkspace?.id ?? "unknown";
963
+ const workspaceName = currentWorkspace?.name ??
964
+ currentWorkspace?.id ??
965
+ "unknown";
1425
966
  logWarn(`Workspace disabled/expired for account ${account.index + 1} - workspace: ${workspaceName}. Rotating to next workspace.`, { errorCode: workspaceErrorCode });
1426
967
  const disabledWorkspace = currentWorkspace
1427
968
  ? accountManager.disableCurrentWorkspace(account, currentWorkspace.id)
@@ -1429,13 +970,16 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1429
970
  let nextWorkspace = disabledWorkspace
1430
971
  ? accountManager.rotateToNextWorkspace(account)
1431
972
  : accountManager.getCurrentWorkspace(account);
1432
- if (!disabledWorkspace && (!nextWorkspace || nextWorkspace.enabled === false)) {
1433
- nextWorkspace = accountManager.rotateToNextWorkspace(account);
973
+ if (!disabledWorkspace &&
974
+ (!nextWorkspace ||
975
+ nextWorkspace.enabled === false)) {
976
+ nextWorkspace =
977
+ accountManager.rotateToNextWorkspace(account);
1434
978
  }
1435
979
  if (nextWorkspace) {
1436
980
  accountManager.saveToDiskDebounced();
1437
981
  const newWorkspaceName = nextWorkspace.name ?? nextWorkspace.id;
1438
- await showToast(`Workspace ${workspaceName} disabled. Switched to ${newWorkspaceName}.`, "warning", { duration: toastDurationMs });
982
+ await showRuntimeToast(client, `Workspace ${workspaceName} disabled. Switched to ${newWorkspaceName}.`, "warning", { duration: toastDurationMs });
1439
983
  logInfo(`Rotated to workspace ${newWorkspaceName} for account ${account.index + 1}`);
1440
984
  // Allow the same account to be selected again with fresh request state.
1441
985
  attempted.delete(account.index);
@@ -1444,21 +988,25 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1444
988
  logWarn(`All workspaces disabled for account ${account.index + 1}. Disabling account.`);
1445
989
  accountManager.setAccountEnabled(account.index, false);
1446
990
  accountManager.saveToDiskDebounced();
1447
- await showToast(`All workspaces disabled for account ${account.index + 1}. Switching to another account.`, "warning", { duration: toastDurationMs });
991
+ await showRuntimeToast(client, `All workspaces disabled for account ${account.index + 1}. Switching to another account.`, "warning", { duration: toastDurationMs });
1448
992
  // Forget session affinity and continue the outer loop so another
1449
993
  // enabled account can service the request.
1450
994
  sessionAffinityStore?.forgetSession(sessionAffinityKey);
1451
995
  continue accountAttemptLoop;
1452
996
  }
1453
997
  }
1454
- if (errorResponse.status === 403 && !unsupportedModelInfo.isUnsupported && !isDisabledWorkspaceError) {
998
+ if (errorResponse.status === 403 &&
999
+ !unsupportedModelInfo.isUnsupported &&
1000
+ !isDisabledWorkspaceError) {
1455
1001
  entitlementCache.markBlocked(entitlementAccountKey, model ?? modelFamily, "plan-entitlement");
1456
1002
  capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
1457
1003
  }
1458
- if (recoveryHook && errorBody && isRecoverableError(errorBody)) {
1004
+ if (recoveryHook &&
1005
+ errorBody &&
1006
+ isRecoverableError(errorBody)) {
1459
1007
  const errorType = detectErrorType(errorBody);
1460
1008
  const toastContent = getRecoveryToastContent(errorType);
1461
- await showToast(`${toastContent.title}: ${toastContent.message}`, "warning", { duration: toastDurationMs });
1009
+ await showRuntimeToast(client, `${toastContent.title}: ${toastContent.message}`, "warning", { duration: toastDurationMs });
1462
1010
  logDebug(`[${PLUGIN_NAME}] Recoverable error detected: ${errorType}`);
1463
1011
  }
1464
1012
  // Handle 5xx server errors by rotating to another account
@@ -1469,7 +1017,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1469
1017
  runtimeMetrics.accountRotations++;
1470
1018
  runtimeMetrics.lastError = `HTTP ${response.status}`;
1471
1019
  const serverRetryAfterMs = parseRetryAfterHintMs(response.headers);
1472
- 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 });
1473
1025
  if (policy.refundToken) {
1474
1026
  accountManager.refundToken(account, modelFamily, model);
1475
1027
  }
@@ -1500,7 +1052,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1500
1052
  const waitLabel = formatWaitTime(delayMs);
1501
1053
  if (delayMs <= RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS) {
1502
1054
  if (accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1503
- 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 });
1504
1056
  accountManager.markToastShown(account.index);
1505
1057
  }
1506
1058
  await sleep(addJitter(Math.max(MIN_BACKOFF_MS, delayMs), 0.2));
@@ -1515,7 +1067,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1515
1067
  logWarn(`Rate limited. Rotating account ${account.index + 1} (${account.email ?? "unknown"}).`);
1516
1068
  if (accountManager.getAccountCount() > 1 &&
1517
1069
  accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1518
- 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 });
1519
1071
  accountManager.markToastShown(account.index);
1520
1072
  }
1521
1073
  break;
@@ -1590,7 +1142,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1590
1142
  }
1591
1143
  const fallbackResolvedEmail = fallbackRuntimeIdentity.email;
1592
1144
  const fallbackEntitlementAccountKey = resolveEntitlementAccountKey({
1593
- accountId: fallbackStoredAccountId ?? fallbackAccountId,
1145
+ accountId: fallbackStoredAccountId ??
1146
+ fallbackAccountId,
1594
1147
  email: fallbackResolvedEmail,
1595
1148
  refreshToken: fallbackAccount.refreshToken,
1596
1149
  index: fallbackAccount.index,
@@ -1598,8 +1151,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1598
1151
  const fallbackEntitlementBlock = entitlementCache.isBlocked(fallbackEntitlementAccountKey, model ?? modelFamily);
1599
1152
  if (fallbackEntitlementBlock.blocked) {
1600
1153
  runtimeMetrics.accountRotations++;
1601
- runtimeMetrics.lastError =
1602
- `Entitlement cached block for account ${fallbackAccount.index + 1}`;
1154
+ runtimeMetrics.lastError = `Entitlement cached block for account ${fallbackAccount.index + 1}`;
1603
1155
  logWarn(`Skipping account ${fallbackAccount.index + 1} due to cached entitlement block (${formatWaitTime(fallbackEntitlementBlock.waitMs)} remaining).`);
1604
1156
  continue;
1605
1157
  }
@@ -1623,7 +1175,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1623
1175
  const fallbackController = new AbortController();
1624
1176
  const fallbackTimeoutId = setTimeout(() => fallbackController.abort(new Error("Request timeout")), fetchTimeoutMs);
1625
1177
  const onFallbackAbort = abortSignal
1626
- ? () => fallbackController.abort(abortSignal.reason ?? new Error("Aborted by user"))
1178
+ ? () => fallbackController.abort(abortSignal.reason ??
1179
+ new Error("Aborted by user"))
1627
1180
  : null;
1628
1181
  if (abortSignal && onFallbackAbort) {
1629
1182
  abortSignal.addEventListener("abort", onFallbackAbort, {
@@ -1660,12 +1213,15 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1660
1213
  continue;
1661
1214
  }
1662
1215
  successAccountForResponse = fallbackAccount;
1663
- successEntitlementAccountKey = fallbackEntitlementAccountKey;
1216
+ successEntitlementAccountKey =
1217
+ fallbackEntitlementAccountKey;
1664
1218
  runtimeMetrics.streamFailoverRecoveries += 1;
1665
1219
  if (fallbackAccount.index !== account.index) {
1666
1220
  runtimeMetrics.streamFailoverCrossAccountRecoveries += 1;
1667
1221
  runtimeMetrics.accountRotations += 1;
1668
- sessionAffinityStore?.remember(sessionAffinityKey, fallbackAccount.index);
1222
+ if (!responseContinuationEnabled) {
1223
+ sessionAffinityStore?.remember(sessionAffinityKey, fallbackAccount.index);
1224
+ }
1669
1225
  }
1670
1226
  logInfo(`Recovered stream via failover attempt ${failoverAttempt} using account ${fallbackAccount.index + 1}.`, { emittedBytes });
1671
1227
  return fallbackResponse;
@@ -1680,7 +1236,6 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1680
1236
  ? streamFailoverError.message
1681
1237
  : String(streamFailoverError),
1682
1238
  });
1683
- continue;
1684
1239
  }
1685
1240
  finally {
1686
1241
  clearTimeout(fallbackTimeoutId);
@@ -1697,20 +1252,30 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1697
1252
  requestInstanceId: requestCorrelationId ?? undefined,
1698
1253
  });
1699
1254
  }
1255
+ let storedResponseIdForSuccess = false;
1700
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
+ },
1701
1264
  streamStallTimeoutMs,
1702
1265
  });
1703
1266
  if (!isStreaming && emptyResponseMaxRetries > 0) {
1704
1267
  const clonedResponse = successResponse.clone();
1705
1268
  try {
1706
1269
  const bodyText = await clonedResponse.text();
1707
- const parsedBody = bodyText ? JSON.parse(bodyText) : null;
1270
+ const parsedBody = bodyText
1271
+ ? JSON.parse(bodyText)
1272
+ : null;
1708
1273
  if (isEmptyResponse(parsedBody)) {
1709
1274
  if (emptyResponseRetries < emptyResponseMaxRetries) {
1710
1275
  emptyResponseRetries++;
1711
1276
  runtimeMetrics.emptyResponseRetries++;
1712
1277
  logWarn(`Empty response received (attempt ${emptyResponseRetries}/${emptyResponseMaxRetries}). Retrying...`);
1713
- await showToast(`Empty response. Retrying (${emptyResponseRetries}/${emptyResponseMaxRetries})...`, "warning", { duration: toastDurationMs });
1278
+ await showRuntimeToast(client, `Empty response. Retrying (${emptyResponseRetries}/${emptyResponseMaxRetries})...`, "warning", { duration: toastDurationMs });
1714
1279
  accountManager.refundToken(account, modelFamily, model);
1715
1280
  accountManager.recordFailure(account, modelFamily, model);
1716
1281
  capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
@@ -1722,7 +1287,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1722
1287
  sameAccountRetryCount < maxSameAccountRetries) {
1723
1288
  sameAccountRetryCount += 1;
1724
1289
  runtimeMetrics.sameAccountRetries += 1;
1725
- const retryDelayMs = Math.max(0, Math.floor(emptyPolicy.retryDelayMs ?? emptyResponseRetryDelayMs));
1290
+ const retryDelayMs = Math.max(0, Math.floor(emptyPolicy.retryDelayMs ??
1291
+ emptyResponseRetryDelayMs));
1726
1292
  if (retryDelayMs > 0) {
1727
1293
  await sleep(addJitter(retryDelayMs, 0.2));
1728
1294
  }
@@ -1746,12 +1312,17 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1746
1312
  accountManager.recordSuccess(successAccountForResponse, modelFamily, model);
1747
1313
  capabilityPolicyStore.recordSuccess(successAccountKey, capabilityModelKey);
1748
1314
  entitlementCache.clear(successAccountKey, capabilityModelKey);
1749
- sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
1315
+ if (!responseContinuationEnabled ||
1316
+ (!isStreaming && !storedResponseIdForSuccess)) {
1317
+ sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
1318
+ }
1750
1319
  runtimeMetrics.successfulRequests++;
1751
1320
  runtimeMetrics.lastError = null;
1752
- if (lastCodexCliActiveSyncIndex !== successAccountForResponse.index) {
1321
+ if (lastCodexCliActiveSyncIndex !==
1322
+ successAccountForResponse.index) {
1753
1323
  void accountManager.syncCodexCliActiveSelectionForIndex(successAccountForResponse.index);
1754
- lastCodexCliActiveSyncIndex = successAccountForResponse.index;
1324
+ lastCodexCliActiveSyncIndex =
1325
+ successAccountForResponse.index;
1755
1326
  }
1756
1327
  return successResponse;
1757
1328
  }
@@ -1775,7 +1346,15 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1775
1346
  waitMs <= retryAllAccountsMaxWaitMs) &&
1776
1347
  allRateLimitedRetries < retryAllAccountsMaxRetries) {
1777
1348
  const countdownMessage = `All ${count} account(s) rate-limited. Waiting`;
1778
- 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
+ });
1779
1358
  allRateLimitedRetries++;
1780
1359
  continue;
1781
1360
  }
@@ -1812,8 +1391,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1812
1391
  type: "oauth",
1813
1392
  authorize: async (inputs) => {
1814
1393
  const authPluginConfig = loadPluginConfig();
1815
- applyUiRuntimeFromConfig(authPluginConfig);
1816
- 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
+ });
1817
1409
  const accounts = [];
1818
1410
  const noBrowser = inputs?.manual === "true" ||
1819
1411
  inputs?.noBrowser === "true" ||
@@ -1824,569 +1416,45 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1824
1416
  : null;
1825
1417
  let startFresh = explicitLoginMode === "fresh";
1826
1418
  let refreshAccountIndex;
1827
- const clampActiveIndices = (storage) => {
1828
- const count = storage.accounts.length;
1829
- if (count === 0) {
1830
- storage.activeIndex = 0;
1831
- storage.activeIndexByFamily = {};
1832
- return;
1833
- }
1834
- storage.activeIndex = Math.max(0, Math.min(storage.activeIndex, count - 1));
1835
- storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
1836
- for (const family of MODEL_FAMILIES) {
1837
- const raw = storage.activeIndexByFamily[family];
1838
- const candidate = typeof raw === "number" && Number.isFinite(raw) ? raw : storage.activeIndex;
1839
- storage.activeIndexByFamily[family] = Math.max(0, Math.min(candidate, count - 1));
1840
- }
1841
- };
1842
- const isFlaggableFailure = (failure) => {
1843
- if (failure.reason === "missing_refresh")
1844
- return true;
1845
- if (failure.statusCode === 401)
1846
- return true;
1847
- if (failure.statusCode !== 400)
1848
- return false;
1849
- const message = (failure.message ?? "").toLowerCase();
1850
- return (message.includes("invalid_grant") ||
1851
- message.includes("invalid refresh") ||
1852
- message.includes("token has been revoked"));
1853
- };
1854
- const parseFiniteNumberHeader = (headers, name) => {
1855
- const raw = headers.get(name);
1856
- if (!raw)
1857
- return undefined;
1858
- const parsed = Number(raw);
1859
- return Number.isFinite(parsed) ? parsed : undefined;
1860
- };
1861
- const parseFiniteIntHeader = (headers, name) => {
1862
- const raw = headers.get(name);
1863
- if (!raw)
1864
- return undefined;
1865
- const parsed = Number.parseInt(raw, 10);
1866
- return Number.isFinite(parsed) ? parsed : undefined;
1867
- };
1868
- const parseResetAtMs = (headers, prefix) => {
1869
- const resetAfterSeconds = parseFiniteIntHeader(headers, `${prefix}-reset-after-seconds`);
1870
- if (typeof resetAfterSeconds === "number" &&
1871
- Number.isFinite(resetAfterSeconds) &&
1872
- resetAfterSeconds > 0) {
1873
- return Date.now() + resetAfterSeconds * 1000;
1874
- }
1875
- const resetAtRaw = headers.get(`${prefix}-reset-at`);
1876
- if (!resetAtRaw)
1877
- return undefined;
1878
- const trimmed = resetAtRaw.trim();
1879
- if (/^\d+$/.test(trimmed)) {
1880
- const parsedNumber = Number.parseInt(trimmed, 10);
1881
- if (Number.isFinite(parsedNumber) && parsedNumber > 0) {
1882
- // Upstream sometimes returns seconds since epoch.
1883
- return parsedNumber < 10_000_000_000 ? parsedNumber * 1000 : parsedNumber;
1884
- }
1885
- }
1886
- const parsedDate = Date.parse(trimmed);
1887
- return Number.isFinite(parsedDate) ? parsedDate : undefined;
1888
- };
1889
- const hasCodexQuotaHeaders = (headers) => {
1890
- const keys = [
1891
- "x-codex-primary-used-percent",
1892
- "x-codex-primary-window-minutes",
1893
- "x-codex-primary-reset-at",
1894
- "x-codex-primary-reset-after-seconds",
1895
- "x-codex-secondary-used-percent",
1896
- "x-codex-secondary-window-minutes",
1897
- "x-codex-secondary-reset-at",
1898
- "x-codex-secondary-reset-after-seconds",
1899
- ];
1900
- return keys.some((key) => headers.get(key) !== null);
1901
- };
1902
- const parseCodexQuotaSnapshot = (headers, status) => {
1903
- if (!hasCodexQuotaHeaders(headers))
1904
- return null;
1905
- const primaryPrefix = "x-codex-primary";
1906
- const secondaryPrefix = "x-codex-secondary";
1907
- const primary = {
1908
- usedPercent: parseFiniteNumberHeader(headers, `${primaryPrefix}-used-percent`),
1909
- windowMinutes: parseFiniteIntHeader(headers, `${primaryPrefix}-window-minutes`),
1910
- resetAtMs: parseResetAtMs(headers, primaryPrefix),
1911
- };
1912
- const secondary = {
1913
- usedPercent: parseFiniteNumberHeader(headers, `${secondaryPrefix}-used-percent`),
1914
- windowMinutes: parseFiniteIntHeader(headers, `${secondaryPrefix}-window-minutes`),
1915
- resetAtMs: parseResetAtMs(headers, secondaryPrefix),
1916
- };
1917
- const planTypeRaw = headers.get("x-codex-plan-type");
1918
- const planType = planTypeRaw && planTypeRaw.trim() ? planTypeRaw.trim() : undefined;
1919
- const activeLimit = parseFiniteIntHeader(headers, "x-codex-active-limit");
1920
- return { status, planType, activeLimit, primary, secondary };
1921
- };
1922
- const formatQuotaWindowLabel = (windowMinutes) => {
1923
- if (!windowMinutes || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
1924
- return "quota";
1925
- }
1926
- if (windowMinutes % 1440 === 0)
1927
- return `${windowMinutes / 1440}d`;
1928
- if (windowMinutes % 60 === 0)
1929
- return `${windowMinutes / 60}h`;
1930
- return `${windowMinutes}m`;
1931
- };
1932
- const formatResetAt = (resetAtMs) => {
1933
- if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0)
1934
- return undefined;
1935
- const date = new Date(resetAtMs);
1936
- if (!Number.isFinite(date.getTime()))
1937
- return undefined;
1938
- const now = new Date();
1939
- const sameDay = now.getFullYear() === date.getFullYear() &&
1940
- now.getMonth() === date.getMonth() &&
1941
- now.getDate() === date.getDate();
1942
- const time = date.toLocaleTimeString(undefined, {
1943
- hour: "2-digit",
1944
- minute: "2-digit",
1945
- hour12: false,
1946
- });
1947
- if (sameDay)
1948
- return time;
1949
- const day = date.toLocaleDateString(undefined, { month: "short", day: "2-digit" });
1950
- return `${time} on ${day}`;
1951
- };
1952
- const formatCodexQuotaLine = (snapshot) => {
1953
- const summarizeWindow = (label, window) => {
1954
- const used = window.usedPercent;
1955
- const left = typeof used === "number" && Number.isFinite(used)
1956
- ? Math.max(0, Math.min(100, Math.round(100 - used)))
1957
- : undefined;
1958
- const reset = formatResetAt(window.resetAtMs);
1959
- let summary = label;
1960
- if (left !== undefined)
1961
- summary = `${summary} ${left}% left`;
1962
- if (reset)
1963
- summary = `${summary} (resets ${reset})`;
1964
- return summary;
1965
- };
1966
- const primaryLabel = formatQuotaWindowLabel(snapshot.primary.windowMinutes);
1967
- const secondaryLabel = formatQuotaWindowLabel(snapshot.secondary.windowMinutes);
1968
- const parts = [
1969
- summarizeWindow(primaryLabel, snapshot.primary),
1970
- summarizeWindow(secondaryLabel, snapshot.secondary),
1971
- ];
1972
- if (snapshot.planType)
1973
- parts.push(`plan:${snapshot.planType}`);
1974
- if (typeof snapshot.activeLimit === "number" && Number.isFinite(snapshot.activeLimit)) {
1975
- parts.push(`active:${snapshot.activeLimit}`);
1976
- }
1977
- if (snapshot.status === 429)
1978
- parts.push("rate-limited");
1979
- return parts.join(", ");
1980
- };
1981
- const fetchCodexQuotaSnapshot = async (params) => {
1982
- const QUOTA_PROBE_MODELS = ["gpt-5-codex", "gpt-5.3-codex", "gpt-5.2-codex"];
1983
- let lastError = null;
1984
- for (const model of QUOTA_PROBE_MODELS) {
1985
- try {
1986
- const instructions = await getCodexInstructions(model);
1987
- const probeBody = {
1988
- model,
1989
- stream: true,
1990
- store: false,
1991
- include: ["reasoning.encrypted_content"],
1992
- instructions,
1993
- input: [
1994
- {
1995
- type: "message",
1996
- role: "user",
1997
- content: [{ type: "input_text", text: "quota ping" }],
1998
- },
1999
- ],
2000
- reasoning: { effort: "none", summary: "auto" },
2001
- text: { verbosity: "low" },
2002
- };
2003
- const headers = createCodexHeaders(undefined, params.accountId, params.accessToken, {
2004
- model,
2005
- });
2006
- headers.set("content-type", "application/json; charset=utf-8");
2007
- const controller = new AbortController();
2008
- const timeout = setTimeout(() => controller.abort(), 15_000);
2009
- let response;
2010
- try {
2011
- response = await fetch(`${CODEX_BASE_URL}/codex/responses`, {
2012
- method: "POST",
2013
- headers,
2014
- body: JSON.stringify(probeBody),
2015
- signal: controller.signal,
2016
- });
2017
- }
2018
- finally {
2019
- clearTimeout(timeout);
2020
- }
2021
- const snapshot = parseCodexQuotaSnapshot(response.headers, response.status);
2022
- if (snapshot) {
2023
- // We only need headers; cancel the SSE stream immediately.
2024
- try {
2025
- await response.body?.cancel();
2026
- }
2027
- catch {
2028
- // Ignore cancellation failures.
2029
- }
2030
- return snapshot;
2031
- }
2032
- if (!response.ok) {
2033
- const bodyText = await response.text().catch(() => "");
2034
- let errorBody = undefined;
2035
- try {
2036
- errorBody = bodyText ? JSON.parse(bodyText) : undefined;
2037
- }
2038
- catch {
2039
- errorBody = { error: { message: bodyText } };
2040
- }
2041
- const unsupportedInfo = getUnsupportedCodexModelInfo(errorBody);
2042
- if (unsupportedInfo.isUnsupported) {
2043
- lastError = new Error(unsupportedInfo.message ?? `Model '${model}' unsupported for this account`);
2044
- continue;
2045
- }
2046
- const message = (typeof errorBody?.error?.message === "string"
2047
- ? errorBody.error?.message
2048
- : bodyText) || `HTTP ${response.status}`;
2049
- throw new Error(message);
2050
- }
2051
- lastError = new Error("Codex response did not include quota headers");
2052
- }
2053
- catch (error) {
2054
- lastError = error instanceof Error ? error : new Error(String(error));
2055
- }
2056
- }
2057
- throw lastError ?? new Error("Failed to fetch quotas");
2058
- };
2059
- const runAccountCheck = async (deepProbe) => {
2060
- const loadedStorage = await hydrateEmails(await loadAccounts());
2061
- const workingStorage = loadedStorage
2062
- ? {
2063
- ...loadedStorage,
2064
- accounts: loadedStorage.accounts.map((account) => ({ ...account })),
2065
- activeIndexByFamily: loadedStorage.activeIndexByFamily
2066
- ? { ...loadedStorage.activeIndexByFamily }
2067
- : {},
2068
- }
2069
- : { version: 3, accounts: [], activeIndex: 0, activeIndexByFamily: {} };
2070
- if (workingStorage.accounts.length === 0) {
2071
- console.log("\nNo accounts to check.\n");
2072
- return;
2073
- }
2074
- const flaggedStorage = await loadFlaggedAccounts();
2075
- let storageChanged = false;
2076
- let flaggedChanged = false;
2077
- const removeFromActive = new Set();
2078
- const total = workingStorage.accounts.length;
2079
- let ok = 0;
2080
- let disabled = 0;
2081
- let errors = 0;
2082
- console.log(`\nChecking ${deepProbe ? "full account health" : "quotas"} for all accounts...\n`);
2083
- for (let i = 0; i < total; i += 1) {
2084
- const account = workingStorage.accounts[i];
2085
- if (!account)
2086
- continue;
2087
- const label = account.email ?? account.accountLabel ?? `Account ${i + 1}`;
2088
- if (account.enabled === false) {
2089
- disabled += 1;
2090
- console.log(`[${i + 1}/${total}] ${label}: DISABLED`);
2091
- continue;
2092
- }
2093
- try {
2094
- // If we already have a valid cached access token, don't force-refresh.
2095
- // This avoids flagging accounts where the refresh token has been burned
2096
- // but the access token is still valid (same behavior as Codex CLI).
2097
- const nowMs = Date.now();
2098
- let accessToken = null;
2099
- let tokenAccountId = undefined;
2100
- let authDetail = "OK";
2101
- if (account.accessToken &&
2102
- (typeof account.expiresAt !== "number" ||
2103
- !Number.isFinite(account.expiresAt) ||
2104
- account.expiresAt > nowMs)) {
2105
- accessToken = account.accessToken;
2106
- authDetail = "OK (cached access)";
2107
- tokenAccountId = extractAccountId(account.accessToken);
2108
- if (tokenAccountId &&
2109
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId) &&
2110
- tokenAccountId !== account.accountId) {
2111
- account.accountId = tokenAccountId;
2112
- account.accountIdSource = "token";
2113
- storageChanged = true;
2114
- }
2115
- }
2116
- // If Codex CLI has a valid cached access token for this email, use it
2117
- // instead of forcing a refresh.
2118
- if (!accessToken) {
2119
- const cached = await lookupCodexCliTokensByEmail(account.email);
2120
- if (cached &&
2121
- (typeof cached.expiresAt !== "number" ||
2122
- !Number.isFinite(cached.expiresAt) ||
2123
- cached.expiresAt > nowMs)) {
2124
- accessToken = cached.accessToken;
2125
- authDetail = "OK (Codex CLI cache)";
2126
- if (cached.refreshToken && cached.refreshToken !== account.refreshToken) {
2127
- account.refreshToken = cached.refreshToken;
2128
- storageChanged = true;
2129
- }
2130
- if (cached.accessToken && cached.accessToken !== account.accessToken) {
2131
- account.accessToken = cached.accessToken;
2132
- storageChanged = true;
2133
- }
2134
- if (cached.expiresAt !== account.expiresAt) {
2135
- account.expiresAt = cached.expiresAt;
2136
- storageChanged = true;
2137
- }
2138
- const hydratedEmail = sanitizeEmail(extractAccountEmail(cached.accessToken));
2139
- if (hydratedEmail && hydratedEmail !== account.email) {
2140
- account.email = hydratedEmail;
2141
- storageChanged = true;
2142
- }
2143
- tokenAccountId = extractAccountId(cached.accessToken);
2144
- if (tokenAccountId &&
2145
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId) &&
2146
- tokenAccountId !== account.accountId) {
2147
- account.accountId = tokenAccountId;
2148
- account.accountIdSource = "token";
2149
- storageChanged = true;
2150
- }
2151
- }
2152
- }
2153
- if (!accessToken) {
2154
- const refreshResult = await queuedRefresh(account.refreshToken);
2155
- if (refreshResult.type !== "success") {
2156
- errors += 1;
2157
- const message = refreshResult.message ?? refreshResult.reason ?? "refresh failed";
2158
- console.log(`[${i + 1}/${total}] ${label}: ERROR (${message})`);
2159
- if (deepProbe && isFlaggableFailure(refreshResult)) {
2160
- const existingIndex = flaggedStorage.accounts.findIndex((flagged) => flagged.refreshToken === account.refreshToken);
2161
- const flaggedRecord = {
2162
- ...account,
2163
- flaggedAt: Date.now(),
2164
- flaggedReason: "token-invalid",
2165
- lastError: message,
2166
- };
2167
- if (existingIndex >= 0) {
2168
- flaggedStorage.accounts[existingIndex] = flaggedRecord;
2169
- }
2170
- else {
2171
- flaggedStorage.accounts.push(flaggedRecord);
2172
- }
2173
- removeFromActive.add(account.refreshToken);
2174
- flaggedChanged = true;
2175
- }
2176
- continue;
2177
- }
2178
- accessToken = refreshResult.access;
2179
- authDetail = "OK";
2180
- if (refreshResult.refresh !== account.refreshToken) {
2181
- account.refreshToken = refreshResult.refresh;
2182
- storageChanged = true;
2183
- }
2184
- if (refreshResult.access && refreshResult.access !== account.accessToken) {
2185
- account.accessToken = refreshResult.access;
2186
- storageChanged = true;
2187
- }
2188
- if (typeof refreshResult.expires === "number" &&
2189
- refreshResult.expires !== account.expiresAt) {
2190
- account.expiresAt = refreshResult.expires;
2191
- storageChanged = true;
2192
- }
2193
- const hydratedEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken));
2194
- if (hydratedEmail && hydratedEmail !== account.email) {
2195
- account.email = hydratedEmail;
2196
- storageChanged = true;
2197
- }
2198
- tokenAccountId = extractAccountId(refreshResult.access);
2199
- if (tokenAccountId &&
2200
- shouldUpdateAccountIdFromToken(account.accountIdSource, account.accountId) &&
2201
- tokenAccountId !== account.accountId) {
2202
- account.accountId = tokenAccountId;
2203
- account.accountIdSource = "token";
2204
- storageChanged = true;
2205
- }
2206
- }
2207
- if (!accessToken) {
2208
- throw new Error("Missing access token after refresh");
2209
- }
2210
- if (deepProbe) {
2211
- ok += 1;
2212
- const detail = tokenAccountId
2213
- ? `${authDetail} (id:${tokenAccountId.slice(-6)})`
2214
- : authDetail;
2215
- console.log(`[${i + 1}/${total}] ${label}: ${detail}`);
2216
- continue;
2217
- }
2218
- try {
2219
- const requestAccountId = resolveRequestAccountId(account.accountId, account.accountIdSource, tokenAccountId) ??
2220
- tokenAccountId ??
2221
- account.accountId;
2222
- if (!requestAccountId) {
2223
- throw new Error("Missing accountId for quota probe");
2224
- }
2225
- const snapshot = await fetchCodexQuotaSnapshot({
2226
- accountId: requestAccountId,
2227
- accessToken,
2228
- });
2229
- ok += 1;
2230
- console.log(`[${i + 1}/${total}] ${label}: ${formatCodexQuotaLine(snapshot)}`);
2231
- }
2232
- catch (error) {
2233
- errors += 1;
2234
- const message = error instanceof Error ? error.message : String(error);
2235
- console.log(`[${i + 1}/${total}] ${label}: ERROR (${message.slice(0, 160)})`);
2236
- }
2237
- }
2238
- catch (error) {
2239
- errors += 1;
2240
- const message = error instanceof Error ? error.message : String(error);
2241
- console.log(`[${i + 1}/${total}] ${label}: ERROR (${message.slice(0, 120)})`);
2242
- }
2243
- }
2244
- if (removeFromActive.size > 0) {
2245
- workingStorage.accounts = workingStorage.accounts.filter((account) => !removeFromActive.has(account.refreshToken));
2246
- clampActiveIndices(workingStorage);
2247
- storageChanged = true;
2248
- }
2249
- if (storageChanged) {
2250
- await saveAccounts(workingStorage);
2251
- invalidateAccountManagerCache();
2252
- }
2253
- if (flaggedChanged) {
2254
- await saveFlaggedAccounts(flaggedStorage);
2255
- }
2256
- console.log("");
2257
- console.log(`Results: ${ok} ok, ${errors} error, ${disabled} disabled`);
2258
- if (removeFromActive.size > 0) {
2259
- console.log(`Moved ${removeFromActive.size} account(s) to flagged pool (invalid refresh token).`);
2260
- }
2261
- console.log("");
2262
- };
2263
- const verifyFlaggedAccounts = async () => {
2264
- const flaggedStorage = await loadFlaggedAccounts();
2265
- if (flaggedStorage.accounts.length === 0) {
2266
- console.log("\nNo flagged accounts to verify.\n");
2267
- return;
2268
- }
2269
- console.log("\nVerifying flagged accounts...\n");
2270
- const remaining = [];
2271
- const restored = [];
2272
- for (let i = 0; i < flaggedStorage.accounts.length; i += 1) {
2273
- const flagged = flaggedStorage.accounts[i];
2274
- if (!flagged)
2275
- continue;
2276
- const label = flagged.email ?? flagged.accountLabel ?? `Flagged ${i + 1}`;
2277
- try {
2278
- const cached = await lookupCodexCliTokensByEmail(flagged.email);
2279
- const now = Date.now();
2280
- if (cached &&
2281
- typeof cached.expiresAt === "number" &&
2282
- Number.isFinite(cached.expiresAt) &&
2283
- cached.expiresAt > now) {
2284
- const refreshToken = typeof cached.refreshToken === "string" && cached.refreshToken.trim()
2285
- ? cached.refreshToken.trim()
2286
- : flagged.refreshToken;
2287
- const resolved = resolveAccountSelection({
2288
- type: "success",
2289
- access: cached.accessToken,
2290
- refresh: refreshToken,
2291
- expires: cached.expiresAt,
2292
- multiAccount: true,
2293
- });
2294
- if (!resolved.accountIdOverride && flagged.accountId) {
2295
- resolved.accountIdOverride = flagged.accountId;
2296
- resolved.accountIdSource = flagged.accountIdSource ?? "manual";
2297
- }
2298
- if (!resolved.accountLabel && flagged.accountLabel) {
2299
- resolved.accountLabel = flagged.accountLabel;
2300
- }
2301
- restored.push(resolved);
2302
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: RESTORED (Codex CLI cache)`);
2303
- continue;
2304
- }
2305
- const refreshResult = await queuedRefresh(flagged.refreshToken);
2306
- if (refreshResult.type !== "success") {
2307
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: STILL FLAGGED (${refreshResult.message ?? refreshResult.reason ?? "refresh failed"})`);
2308
- remaining.push(flagged);
2309
- continue;
2310
- }
2311
- const resolved = resolveAccountSelection(refreshResult);
2312
- if (!resolved.accountIdOverride && flagged.accountId) {
2313
- resolved.accountIdOverride = flagged.accountId;
2314
- resolved.accountIdSource = flagged.accountIdSource ?? "manual";
2315
- }
2316
- if (!resolved.accountLabel && flagged.accountLabel) {
2317
- resolved.accountLabel = flagged.accountLabel;
2318
- }
2319
- restored.push(resolved);
2320
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: RESTORED`);
2321
- }
2322
- catch (error) {
2323
- const message = error instanceof Error ? error.message : String(error);
2324
- console.log(`[${i + 1}/${flaggedStorage.accounts.length}] ${label}: ERROR (${message.slice(0, 120)})`);
2325
- remaining.push({
2326
- ...flagged,
2327
- lastError: message,
2328
- });
2329
- }
2330
- }
2331
- if (restored.length > 0) {
2332
- await persistAccountPool(restored, false);
2333
- invalidateAccountManagerCache();
2334
- }
2335
- await saveFlaggedAccounts({
2336
- version: 1,
2337
- accounts: remaining,
2338
- });
2339
- console.log("");
2340
- console.log(`Results: ${restored.length} restored, ${remaining.length} still flagged`);
2341
- console.log("");
2342
- };
2343
1419
  if (!explicitLoginMode) {
2344
1420
  while (true) {
2345
- 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
+ });
2346
1431
  const workingStorage = loadedStorage
2347
1432
  ? {
2348
1433
  ...loadedStorage,
2349
- accounts: loadedStorage.accounts.map((account) => ({ ...account })),
1434
+ accounts: loadedStorage.accounts.map((account) => ({
1435
+ ...account,
1436
+ })),
2350
1437
  activeIndexByFamily: loadedStorage.activeIndexByFamily
2351
1438
  ? { ...loadedStorage.activeIndexByFamily }
2352
1439
  : {},
2353
1440
  }
2354
- : { version: 3, accounts: [], activeIndex: 0, activeIndexByFamily: {} };
1441
+ : {
1442
+ version: 3,
1443
+ accounts: [],
1444
+ activeIndex: 0,
1445
+ activeIndexByFamily: {},
1446
+ };
2355
1447
  const flaggedStorage = await loadFlaggedAccounts();
2356
- if (workingStorage.accounts.length === 0 && flaggedStorage.accounts.length === 0) {
1448
+ if (workingStorage.accounts.length === 0 &&
1449
+ flaggedStorage.accounts.length === 0) {
2357
1450
  break;
2358
1451
  }
2359
1452
  const now = Date.now();
2360
1453
  const activeIndex = resolveActiveIndex(workingStorage, "codex");
2361
- const existingAccounts = workingStorage.accounts.map((account, index) => {
2362
- let status;
2363
- if (account.enabled === false) {
2364
- status = "disabled";
2365
- }
2366
- else if (typeof account.coolingDownUntil === "number" &&
2367
- account.coolingDownUntil > now) {
2368
- status = "cooldown";
2369
- }
2370
- else if (formatRateLimitEntry(account, now)) {
2371
- status = "rate-limited";
2372
- }
2373
- else if (index === activeIndex) {
2374
- status = "active";
2375
- }
2376
- else {
2377
- status = "ok";
2378
- }
2379
- return {
2380
- accountId: account.accountId,
2381
- accountLabel: account.accountLabel,
2382
- email: account.email,
2383
- index,
2384
- addedAt: account.addedAt,
2385
- lastUsed: account.lastUsed,
2386
- status,
2387
- isCurrentAccount: index === activeIndex,
2388
- enabled: account.enabled !== false,
2389
- };
1454
+ const existingAccounts = buildLoginMenuAccounts(workingStorage.accounts, {
1455
+ now,
1456
+ activeIndex,
1457
+ formatRateLimitEntry: (account, currentNow) => formatRateLimitEntry(account, currentNow, formatWaitTime),
2390
1458
  });
2391
1459
  const menuResult = await promptLoginMode(existingAccounts, {
2392
1460
  flaggedCount: flaggedStorage.accounts.length,
@@ -2401,16 +1469,62 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2401
1469
  }),
2402
1470
  };
2403
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
+ };
2404
1509
  if (menuResult.mode === "check") {
2405
- await runAccountCheck(false);
1510
+ await runRuntimeAccountCheck(false, accountCheckDeps);
2406
1511
  continue;
2407
1512
  }
2408
1513
  if (menuResult.mode === "deep-check") {
2409
- await runAccountCheck(true);
1514
+ await runRuntimeAccountCheck(true, accountCheckDeps);
2410
1515
  continue;
2411
1516
  }
2412
1517
  if (menuResult.mode === "verify-flagged") {
2413
- 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
+ });
2414
1528
  continue;
2415
1529
  }
2416
1530
  if (menuResult.mode === "manage") {
@@ -2418,13 +1532,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2418
1532
  const target = workingStorage.accounts[menuResult.deleteAccountIndex];
2419
1533
  if (target) {
2420
1534
  workingStorage.accounts.splice(menuResult.deleteAccountIndex, 1);
2421
- clampActiveIndices(workingStorage);
1535
+ clampActiveIndices(workingStorage, MODEL_FAMILIES);
2422
1536
  await saveAccounts(workingStorage);
2423
1537
  await saveFlaggedAccounts({
2424
1538
  version: 1,
2425
1539
  accounts: flaggedStorage.accounts.filter((flagged) => flagged.refreshToken !== target.refreshToken),
2426
1540
  });
2427
- invalidateAccountManagerCache();
1541
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2428
1542
  console.log(`\nDeleted ${target.email ?? `Account ${menuResult.deleteAccountIndex + 1}`}.\n`);
2429
1543
  }
2430
1544
  continue;
@@ -2434,7 +1548,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2434
1548
  if (target) {
2435
1549
  target.enabled = target.enabled === false ? true : false;
2436
1550
  await saveAccounts(workingStorage);
2437
- invalidateAccountManagerCache();
1551
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2438
1552
  console.log(`\n${target.email ?? `Account ${menuResult.toggleAccountIndex + 1}`} ${target.enabled === false ? "disabled" : "enabled"}.\n`);
2439
1553
  }
2440
1554
  continue;
@@ -2451,7 +1565,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2451
1565
  if (menuResult.deleteAll) {
2452
1566
  await clearAccounts();
2453
1567
  await clearFlaggedAccounts();
2454
- invalidateAccountManagerCache();
1568
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2455
1569
  console.log("\nCleared saved accounts from active storage. Recovery snapshots remain available. Starting fresh.\n");
2456
1570
  }
2457
1571
  break;
@@ -2463,7 +1577,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2463
1577
  const latestStorage = await loadAccounts();
2464
1578
  const existingCount = latestStorage?.accounts.length ?? 0;
2465
1579
  const requestedCount = Number.parseInt(inputs?.accountCount ?? "1", 10);
2466
- const normalizedRequested = Number.isFinite(requestedCount) ? requestedCount : 1;
1580
+ const normalizedRequested = Number.isFinite(requestedCount)
1581
+ ? requestedCount
1582
+ : 1;
2467
1583
  const availableSlots = refreshAccountIndex !== undefined
2468
1584
  ? 1
2469
1585
  : startFresh
@@ -2488,39 +1604,72 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2488
1604
  }
2489
1605
  if (useManualMode) {
2490
1606
  const { pkce, state, url } = await createAuthorizationFlow();
2491
- return buildManualOAuthFlow(pkce, url, state, async (tokens) => {
2492
- try {
2493
- await persistAccountPool([tokens], startFresh);
2494
- invalidateAccountManagerCache();
2495
- }
2496
- catch (err) {
2497
- const storagePath = getStoragePath();
2498
- const errorCode = err?.code || "UNKNOWN";
2499
- const hint = err instanceof StorageError
2500
- ? err.hint
2501
- : formatStorageErrorHint(err, storagePath);
2502
- logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
2503
- await showToast(hint, "error", {
2504
- title: "Account Persistence Failed",
2505
- duration: 10000,
2506
- });
2507
- }
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
+ },
2508
1634
  });
2509
1635
  }
2510
- const explicitCountProvided = typeof inputs?.accountCount === "string" && inputs.accountCount.trim().length > 0;
1636
+ const explicitCountProvided = typeof inputs?.accountCount === "string" &&
1637
+ inputs.accountCount.trim().length > 0;
2511
1638
  while (accounts.length < targetCount) {
2512
1639
  logInfo(`=== OpenAI OAuth (Account ${accounts.length + 1}) ===`);
2513
1640
  const forceNewLogin = accounts.length > 0 || refreshAccountIndex !== undefined;
2514
- 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
+ });
2515
1659
  let resolved = null;
2516
1660
  if (result.type === "success") {
2517
- resolved = resolveAccountSelection(result);
1661
+ resolved = resolveTokenSuccessAccount(result);
2518
1662
  const email = extractAccountEmail(resolved.access, resolved.idToken);
2519
- const accountId = resolved.accountIdOverride ?? extractAccountId(resolved.access);
2520
- 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";
2521
1669
  logInfo(`Authenticated as: ${label}`);
2522
1670
  const isDuplicate = findMatchingAccountIndex(accounts.map((account) => ({
2523
- accountId: account.accountIdOverride ?? extractAccountId(account.access),
1671
+ accountId: account.accountIdOverride ??
1672
+ extractAccountId(account.access),
2524
1673
  email: sanitizeEmail(extractAccountEmail(account.access, account.idToken)),
2525
1674
  refreshToken: account.refresh,
2526
1675
  })), {
@@ -2550,11 +1699,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2550
1699
  continue;
2551
1700
  }
2552
1701
  accounts.push(resolved);
2553
- await showToast(`Account ${accounts.length} authenticated`, "success");
1702
+ await showRuntimeToast(client, `Account ${accounts.length} authenticated`, "success");
2554
1703
  try {
2555
1704
  const isFirstAccount = accounts.length === 1;
2556
1705
  await persistAccountPool([resolved], isFirstAccount && startFresh);
2557
- invalidateAccountManagerCache();
1706
+ invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
2558
1707
  }
2559
1708
  catch (err) {
2560
1709
  const storagePath = getStoragePath();
@@ -2563,7 +1712,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2563
1712
  ? err.hint
2564
1713
  : formatStorageErrorHint(err, storagePath);
2565
1714
  logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
2566
- await showToast(hint, "error", {
1715
+ await showRuntimeToast(client, hint, "error", {
2567
1716
  title: "Account Persistence Failed",
2568
1717
  duration: 10000,
2569
1718
  });
@@ -2619,20 +1768,48 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2619
1768
  // Initialize storage path for manual OAuth flow
2620
1769
  // Must happen BEFORE persistAccountPool to ensure correct storage location
2621
1770
  const manualPluginConfig = loadPluginConfig();
2622
- applyUiRuntimeFromConfig(manualPluginConfig);
2623
- 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
+ });
2624
1786
  const { pkce, state, url } = await createAuthorizationFlow();
2625
- return buildManualOAuthFlow(pkce, url, state, async (tokens) => {
2626
- try {
2627
- await persistAccountPool([tokens], false);
2628
- }
2629
- catch (err) {
2630
- const storagePath = getStoragePath();
2631
- const errorCode = err?.code || "UNKNOWN";
2632
- const hint = err instanceof StorageError ? err.hint : formatStorageErrorHint(err, storagePath);
2633
- logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
2634
- await showToast(hint, "error", { title: "Account Persistence Failed", duration: 10000 });
2635
- }
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
+ },
2636
1813
  });
2637
1814
  },
2638
1815
  },
@@ -2648,7 +1825,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2648
1825
  description: "List all Codex OAuth accounts and the current active index.",
2649
1826
  args: {},
2650
1827
  async execute() {
2651
- const ui = resolveUiRuntime();
1828
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2652
1829
  const storage = await loadAccounts();
2653
1830
  const storePath = getStoragePath();
2654
1831
  if (!storage || storage.accounts.length === 0) {
@@ -2687,7 +1864,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2687
1864
  badges.push(formatUiBadge(ui, "current", "accent"));
2688
1865
  if (account.enabled === false)
2689
1866
  badges.push(formatUiBadge(ui, "disabled", "danger"));
2690
- const rateLimit = formatRateLimitEntry(account, now);
1867
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime);
2691
1868
  if (rateLimit)
2692
1869
  badges.push(formatUiBadge(ui, "rate-limited", "warning"));
2693
1870
  if (typeof account.coolingDownUntil === "number" &&
@@ -2725,13 +1902,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2725
1902
  storage.accounts.forEach((account, index) => {
2726
1903
  const label = formatAccountLabel(account, index);
2727
1904
  const statuses = [];
2728
- const rateLimit = formatRateLimitEntry(account, now);
1905
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime);
2729
1906
  if (index === activeIndex)
2730
1907
  statuses.push("active");
2731
1908
  if (rateLimit)
2732
1909
  statuses.push("rate-limited");
2733
- if (typeof account.coolingDownUntil ===
2734
- "number" &&
1910
+ if (typeof account.coolingDownUntil === "number" &&
2735
1911
  account.coolingDownUntil > now) {
2736
1912
  statuses.push("cooldown");
2737
1913
  }
@@ -2752,10 +1928,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2752
1928
  "codex-switch": tool({
2753
1929
  description: "Switch active Codex account by index (1-based).",
2754
1930
  args: {
2755
- 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)"),
2756
1934
  },
2757
1935
  async execute({ index }) {
2758
- const ui = resolveUiRuntime();
1936
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2759
1937
  const storage = await loadAccounts();
2760
1938
  if (!storage || storage.accounts.length === 0) {
2761
1939
  if (ui.v2Enabled) {
@@ -2797,7 +1975,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2797
1975
  await saveAccounts(storage);
2798
1976
  }
2799
1977
  catch (saveError) {
2800
- logWarn("Failed to save account switch", { error: String(saveError) });
1978
+ logWarn("Failed to save account switch", {
1979
+ error: String(saveError),
1980
+ });
2801
1981
  if (ui.v2Enabled) {
2802
1982
  return [
2803
1983
  ...formatUiHeader(ui, "Switch account"),
@@ -2809,7 +1989,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2809
1989
  return `Switched to ${formatAccountLabel(account, targetIndex)} but failed to persist. Changes may be lost on restart.`;
2810
1990
  }
2811
1991
  if (cachedAccountManager) {
2812
- await reloadAccountManagerFromDisk();
1992
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
2813
1993
  }
2814
1994
  const label = formatAccountLabel(account, targetIndex);
2815
1995
  if (ui.v2Enabled) {
@@ -2826,7 +2006,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2826
2006
  description: "Show detailed status of Codex accounts and rate limits.",
2827
2007
  args: {},
2828
2008
  async execute() {
2829
- const ui = resolveUiRuntime();
2009
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
2830
2010
  const storage = await loadAccounts();
2831
2011
  if (!storage || storage.accounts.length === 0) {
2832
2012
  if (ui.v2Enabled) {
@@ -2855,7 +2035,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2855
2035
  badges.push(formatUiBadge(ui, "active", "accent"));
2856
2036
  if (account.enabled === false)
2857
2037
  badges.push(formatUiBadge(ui, "disabled", "danger"));
2858
- const rateLimit = formatRateLimitEntry(account, now) ?? "none";
2038
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime) ?? "none";
2859
2039
  const cooldown = formatCooldown(account, now) ?? "none";
2860
2040
  if (rateLimit !== "none")
2861
2041
  badges.push(formatUiBadge(ui, "rate-limited", "warning"));
@@ -2871,7 +2051,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2871
2051
  lines.push(...formatUiSection(ui, "Active index by model family"));
2872
2052
  for (const family of MODEL_FAMILIES) {
2873
2053
  const idx = storage.activeIndexByFamily?.[family];
2874
- 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
+ : "-";
2875
2057
  lines.push(formatUiItem(ui, `${family}: ${familyIndexLabel}`));
2876
2058
  }
2877
2059
  lines.push("");
@@ -2905,18 +2087,27 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2905
2087
  storage.accounts.forEach((account, index) => {
2906
2088
  const label = formatAccountLabel(account, index);
2907
2089
  const active = index === activeIndex ? "Yes" : "No";
2908
- const rateLimit = formatRateLimitEntry(account, now) ?? "None";
2090
+ const rateLimit = formatRateLimitEntry(account, now, formatWaitTime) ?? "None";
2909
2091
  const cooldown = formatCooldown(account, now) ?? "No";
2910
2092
  const lastUsed = typeof account.lastUsed === "number" && account.lastUsed > 0
2911
2093
  ? `${formatWaitTime(now - account.lastUsed)} ago`
2912
2094
  : "-";
2913
- 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));
2914
2103
  });
2915
2104
  lines.push("");
2916
2105
  lines.push("Active index by model family:");
2917
2106
  for (const family of MODEL_FAMILIES) {
2918
2107
  const idx = storage.activeIndexByFamily?.[family];
2919
- 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
+ : "-";
2920
2111
  lines.push(` ${family}: ${familyIndexLabel}`);
2921
2112
  }
2922
2113
  lines.push("");
@@ -2933,95 +2124,97 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2933
2124
  return lines.join("\n");
2934
2125
  },
2935
2126
  }),
2936
- ...(exposeAdvancedCodexTools ? {
2937
- "codex-metrics": tool({
2938
- description: "Show runtime request metrics for this plugin process.",
2939
- args: {},
2940
- execute() {
2941
- const ui = resolveUiRuntime();
2942
- const now = Date.now();
2943
- const uptimeMs = Math.max(0, now - runtimeMetrics.startedAt);
2944
- const total = runtimeMetrics.totalRequests;
2945
- const successful = runtimeMetrics.successfulRequests;
2946
- const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) : "0.0";
2947
- const avgLatencyMs = successful > 0
2948
- ? Math.round(runtimeMetrics.cumulativeLatencyMs / successful)
2949
- : 0;
2950
- const liveSyncSnapshot = liveAccountSync?.getSnapshot();
2951
- const guardianStats = refreshGuardian?.getStats();
2952
- const sessionAffinityEntries = sessionAffinityStore?.size() ?? 0;
2953
- const lastRequest = runtimeMetrics.lastRequestAt !== null
2954
- ? `${formatWaitTime(now - runtimeMetrics.lastRequestAt)} ago`
2955
- : "never";
2956
- const lines = [
2957
- "Codex Plugin Metrics:",
2958
- "",
2959
- `Uptime: ${formatWaitTime(uptimeMs)}`,
2960
- `Total upstream requests: ${total}`,
2961
- `Successful responses: ${successful}`,
2962
- `Failed responses: ${runtimeMetrics.failedRequests}`,
2963
- `Success rate: ${successRate}%`,
2964
- `Average successful latency: ${avgLatencyMs}ms`,
2965
- `Rate-limited responses: ${runtimeMetrics.rateLimitedResponses}`,
2966
- `Server errors (5xx): ${runtimeMetrics.serverErrors}`,
2967
- `Network errors: ${runtimeMetrics.networkErrors}`,
2968
- `User aborts: ${runtimeMetrics.userAborts}`,
2969
- `Auth refresh failures: ${runtimeMetrics.authRefreshFailures}`,
2970
- `Account rotations: ${runtimeMetrics.accountRotations}`,
2971
- `Same-account retries: ${runtimeMetrics.sameAccountRetries}`,
2972
- `Stream failover attempts: ${runtimeMetrics.streamFailoverAttempts}`,
2973
- `Stream failover recoveries: ${runtimeMetrics.streamFailoverRecoveries}`,
2974
- `Stream failover cross-account recoveries: ${runtimeMetrics.streamFailoverCrossAccountRecoveries}`,
2975
- `Empty-response retries: ${runtimeMetrics.emptyResponseRetries}`,
2976
- `Session affinity entries: ${sessionAffinityEntries}`,
2977
- `Live sync: ${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`,
2978
- `Refresh guardian: ${guardianStats ? "on" : "off"} (${guardianStats?.refreshed ?? 0} refreshed, ${guardianStats?.failed ?? 0} failed)`,
2979
- `Last upstream request: ${lastRequest}`,
2980
- ];
2981
- if (runtimeMetrics.lastError) {
2982
- lines.push(`Last error: ${runtimeMetrics.lastError}`);
2983
- }
2984
- if (ui.v2Enabled) {
2985
- const styled = [
2986
- ...formatUiHeader(ui, "Codex plugin metrics"),
2987
- formatUiKeyValue(ui, "Uptime", formatWaitTime(uptimeMs)),
2988
- formatUiKeyValue(ui, "Total upstream requests", String(total)),
2989
- formatUiKeyValue(ui, "Successful responses", String(successful), "success"),
2990
- formatUiKeyValue(ui, "Failed responses", String(runtimeMetrics.failedRequests), "danger"),
2991
- formatUiKeyValue(ui, "Success rate", `${successRate}%`, "accent"),
2992
- formatUiKeyValue(ui, "Average successful latency", `${avgLatencyMs}ms`),
2993
- formatUiKeyValue(ui, "Rate-limited responses", String(runtimeMetrics.rateLimitedResponses), "warning"),
2994
- formatUiKeyValue(ui, "Server errors (5xx)", String(runtimeMetrics.serverErrors), "danger"),
2995
- formatUiKeyValue(ui, "Network errors", String(runtimeMetrics.networkErrors), "danger"),
2996
- formatUiKeyValue(ui, "User aborts", String(runtimeMetrics.userAborts), "muted"),
2997
- formatUiKeyValue(ui, "Auth refresh failures", String(runtimeMetrics.authRefreshFailures), "warning"),
2998
- formatUiKeyValue(ui, "Account rotations", String(runtimeMetrics.accountRotations), "accent"),
2999
- formatUiKeyValue(ui, "Same-account retries", String(runtimeMetrics.sameAccountRetries), "warning"),
3000
- formatUiKeyValue(ui, "Stream failover attempts", String(runtimeMetrics.streamFailoverAttempts), "muted"),
3001
- formatUiKeyValue(ui, "Stream failover recoveries", String(runtimeMetrics.streamFailoverRecoveries), "success"),
3002
- formatUiKeyValue(ui, "Stream failover cross-account recoveries", String(runtimeMetrics.streamFailoverCrossAccountRecoveries), "accent"),
3003
- formatUiKeyValue(ui, "Empty-response retries", String(runtimeMetrics.emptyResponseRetries), "warning"),
3004
- formatUiKeyValue(ui, "Session affinity entries", String(sessionAffinityEntries), "muted"),
3005
- formatUiKeyValue(ui, "Live sync", `${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`, liveSyncSnapshot?.running ? "success" : "muted"),
3006
- formatUiKeyValue(ui, "Refresh guardian", guardianStats
3007
- ? `on (${guardianStats.refreshed} refreshed, ${guardianStats.failed} failed)`
3008
- : "off", guardianStats ? "success" : "muted"),
3009
- 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}`,
3010
2172
  ];
3011
2173
  if (runtimeMetrics.lastError) {
3012
- styled.push(formatUiKeyValue(ui, "Last error", runtimeMetrics.lastError, "danger"));
2174
+ lines.push(`Last error: ${runtimeMetrics.lastError}`);
3013
2175
  }
3014
- return Promise.resolve(styled.join("\n"));
3015
- }
3016
- return Promise.resolve(lines.join("\n"));
3017
- },
3018
- }),
3019
- } : {}),
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
+ : {}),
3020
2213
  "codex-health": tool({
3021
2214
  description: "Check health of all Codex accounts by validating refresh tokens.",
3022
2215
  args: {},
3023
2216
  async execute() {
3024
- const ui = resolveUiRuntime();
2217
+ const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
3025
2218
  const storage = await loadAccounts();
3026
2219
  if (!storage || storage.accounts.length === 0) {
3027
2220
  if (ui.v2Enabled) {
@@ -3073,255 +2266,268 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
3073
2266
  return results.join("\n");
3074
2267
  },
3075
2268
  }),
3076
- ...(exposeAdvancedCodexTools ? {
3077
- "codex-remove": tool({
3078
- description: "Remove a Codex account by index (1-based). Use codex-list to list accounts first.",
3079
- args: {
3080
- index: tool.schema.number().describe("Account number to remove (1-based, e.g., 1 for first account)"),
3081
- },
3082
- async execute({ index }) {
3083
- const ui = resolveUiRuntime();
3084
- const storage = await loadAccounts();
3085
- if (!storage || storage.accounts.length === 0) {
3086
- if (ui.v2Enabled) {
3087
- return [
3088
- ...formatUiHeader(ui, "Remove account"),
3089
- "",
3090
- formatUiItem(ui, "No accounts configured.", "warning"),
3091
- ].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.";
3092
2290
  }
3093
- return "No Codex accounts configured. Nothing to remove.";
3094
- }
3095
- const targetIndex = Math.floor((index ?? 0) - 1);
3096
- if (!Number.isFinite(targetIndex) ||
3097
- targetIndex < 0 ||
3098
- targetIndex >= storage.accounts.length) {
3099
- if (ui.v2Enabled) {
3100
- return [
3101
- ...formatUiHeader(ui, "Remove account"),
3102
- "",
3103
- formatUiItem(ui, `Invalid account number: ${index}`, "danger"),
3104
- formatUiKeyValue(ui, "Valid range", `1-${storage.accounts.length}`, "muted"),
3105
- formatUiItem(ui, "Use codex-list to list all accounts.", "accent"),
3106
- ].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.`;
3107
2305
  }
3108
- return `Invalid account number: ${index}\n\nValid range: 1-${storage.accounts.length}\n\nUse codex-list to list all accounts.`;
3109
- }
3110
- const account = storage.accounts[targetIndex];
3111
- if (!account) {
3112
- return `Account ${index} not found.`;
3113
- }
3114
- const label = formatAccountLabel(account, targetIndex);
3115
- storage.accounts.splice(targetIndex, 1);
3116
- if (storage.accounts.length === 0) {
3117
- storage.activeIndex = 0;
3118
- storage.activeIndexByFamily = {};
3119
- }
3120
- else {
3121
- if (storage.activeIndex >= storage.accounts.length) {
3122
- storage.activeIndex = 0;
2306
+ const account = storage.accounts[targetIndex];
2307
+ if (!account) {
2308
+ return `Account ${index} not found.`;
3123
2309
  }
3124
- else if (storage.activeIndex > targetIndex) {
3125
- 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 = {};
3126
2315
  }
3127
- if (storage.activeIndexByFamily) {
3128
- for (const family of MODEL_FAMILIES) {
3129
- const idx = storage.activeIndexByFamily[family];
3130
- if (typeof idx === "number") {
3131
- if (idx >= storage.accounts.length) {
3132
- storage.activeIndexByFamily[family] = 0;
3133
- }
3134
- else if (idx > targetIndex) {
3135
- 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
+ }
3136
2333
  }
3137
2334
  }
3138
2335
  }
3139
2336
  }
3140
- }
3141
- try {
3142
- await saveAccounts(storage);
3143
- }
3144
- catch (saveError) {
3145
- 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;
3146
2358
  if (ui.v2Enabled) {
3147
2359
  return [
3148
2360
  ...formatUiHeader(ui, "Remove account"),
3149
2361
  "",
3150
- formatUiItem(ui, `Removed ${formatAccountLabel(account, targetIndex)} from memory`, "warning"),
3151
- 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"),
3152
2366
  ].join("\n");
3153
2367
  }
3154
- return `Removed ${formatAccountLabel(account, targetIndex)} from memory but failed to persist. Changes may be lost on restart.`;
3155
- }
3156
- if (cachedAccountManager) {
3157
- await reloadAccountManagerFromDisk();
3158
- }
3159
- const remaining = storage.accounts.length;
3160
- if (ui.v2Enabled) {
3161
2368
  return [
3162
- ...formatUiHeader(ui, "Remove account"),
2369
+ `Removed: ${label}`,
3163
2370
  "",
3164
- formatUiItem(ui, `${getStatusMarker(ui, "ok")} Removed: ${label}`, "success"),
3165
2371
  remaining > 0
3166
- ? formatUiKeyValue(ui, "Remaining accounts", String(remaining))
3167
- : formatUiItem(ui, "No accounts remaining. Run: codex login", "warning"),
2372
+ ? `Remaining accounts: ${remaining}`
2373
+ : "No accounts remaining. Run: codex login",
3168
2374
  ].join("\n");
3169
- }
3170
- return [
3171
- `Removed: ${label}`,
3172
- "",
3173
- remaining > 0
3174
- ? `Remaining accounts: ${remaining}`
3175
- : "No accounts remaining. Run: codex login",
3176
- ].join("\n");
3177
- },
3178
- }),
3179
- "codex-refresh": tool({
3180
- description: "Manually refresh OAuth tokens for all accounts to verify they're still valid.",
3181
- args: {},
3182
- async execute() {
3183
- const ui = resolveUiRuntime();
3184
- const storage = await loadAccounts();
3185
- if (!storage || storage.accounts.length === 0) {
3186
- if (ui.v2Enabled) {
3187
- return [
3188
- ...formatUiHeader(ui, "Refresh accounts"),
3189
- "",
3190
- formatUiItem(ui, "No accounts configured.", "warning"),
3191
- formatUiItem(ui, "Run: codex login", "accent"),
3192
- ].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";
3193
2393
  }
3194
- return "No Codex accounts configured. Run: codex login";
3195
- }
3196
- const results = ui.v2Enabled
3197
- ? []
3198
- : [`Refreshing ${storage.accounts.length} account(s):`, ""];
3199
- let refreshedCount = 0;
3200
- let failedCount = 0;
3201
- for (let i = 0; i < storage.accounts.length; i++) {
3202
- const account = storage.accounts[i];
3203
- if (!account)
3204
- continue;
3205
- const label = formatAccountLabel(account, i);
3206
- try {
3207
- const refreshResult = await queuedRefresh(account.refreshToken);
3208
- if (refreshResult.type === "success") {
3209
- account.refreshToken = refreshResult.refresh;
3210
- account.accessToken = refreshResult.access;
3211
- account.expiresAt = refreshResult.expires;
3212
- results.push(` ${getStatusMarker(ui, "ok")} ${label}: Refreshed`);
3213
- 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
+ }
3214
2417
  }
3215
- else {
3216
- 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)}`);
3217
2421
  failedCount++;
3218
2422
  }
3219
2423
  }
3220
- catch (error) {
3221
- const errorMsg = error instanceof Error ? error.message : String(error);
3222
- results.push(` ${getStatusMarker(ui, "error")} ${label}: Error - ${errorMsg.slice(0, 120)}`);
3223
- failedCount++;
3224
- }
3225
- }
3226
- await saveAccounts(storage);
3227
- if (cachedAccountManager) {
3228
- await reloadAccountManagerFromDisk();
3229
- }
3230
- results.push("");
3231
- results.push(`Summary: ${refreshedCount} refreshed, ${failedCount} failed`);
3232
- if (ui.v2Enabled) {
3233
- return [
3234
- ...formatUiHeader(ui, "Refresh accounts"),
3235
- "",
3236
- ...results.map((line) => paintUiText(ui, line, "normal")),
3237
- ].join("\n");
3238
- }
3239
- return results.join("\n");
3240
- },
3241
- }),
3242
- "codex-export": tool({
3243
- description: "Export accounts to a JSON file for backup or migration to another machine.",
3244
- args: {
3245
- path: tool.schema.string().describe("File path to export to (e.g., ~/codex-backup.json)"),
3246
- force: tool.schema.boolean().optional().describe("Overwrite existing file (default: true)"),
3247
- },
3248
- async execute({ path: filePath, force }) {
3249
- const ui = resolveUiRuntime();
3250
- try {
3251
- await exportAccounts(filePath, force ?? true);
3252
- const storage = await loadAccounts();
3253
- const count = storage?.accounts.length ?? 0;
3254
- if (ui.v2Enabled) {
3255
- return [
3256
- ...formatUiHeader(ui, "Export accounts"),
3257
- "",
3258
- formatUiItem(ui, `${getStatusMarker(ui, "ok")} Exported ${count} account(s)`, "success"),
3259
- formatUiKeyValue(ui, "Path", filePath, "muted"),
3260
- ].join("\n");
2424
+ await saveAccounts(storage);
2425
+ if (cachedAccountManager) {
2426
+ await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
3261
2427
  }
3262
- return `Exported ${count} account(s) to: ${filePath}`;
3263
- }
3264
- catch (error) {
3265
- const msg = error instanceof Error ? error.message : String(error);
2428
+ results.push("");
2429
+ results.push(`Summary: ${refreshedCount} refreshed, ${failedCount} failed`);
3266
2430
  if (ui.v2Enabled) {
3267
2431
  return [
3268
- ...formatUiHeader(ui, "Export accounts"),
2432
+ ...formatUiHeader(ui, "Refresh accounts"),
3269
2433
  "",
3270
- formatUiItem(ui, `${getStatusMarker(ui, "error")} Export failed`, "danger"),
3271
- formatUiKeyValue(ui, "Error", msg, "danger"),
2434
+ ...results.map((line) => paintUiText(ui, line, "normal")),
3272
2435
  ].join("\n");
3273
2436
  }
3274
- return `Export failed: ${msg}`;
3275
- }
3276
- },
3277
- }),
3278
- "codex-import": tool({
3279
- description: "Import accounts from a JSON file, merging with existing accounts.",
3280
- args: {
3281
- path: tool.schema.string().describe("File path to import from (e.g., ~/codex-backup.json)"),
3282
- },
3283
- async execute({ path: filePath }) {
3284
- const ui = resolveUiRuntime();
3285
- try {
3286
- const result = await importAccounts(filePath);
3287
- invalidateAccountManagerCache();
3288
- const lines = [`Import complete.`, ``];
3289
- if (result.imported > 0) {
3290
- 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}`;
3291
2466
  }
3292
- if (result.skipped > 0) {
3293
- 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}`;
3294
2478
  }
3295
- lines.push(`Total accounts: ${result.total}`);
3296
- if (ui.v2Enabled) {
3297
- const styled = [
3298
- ...formatUiHeader(ui, "Import accounts"),
3299
- "",
3300
- formatUiItem(ui, `${getStatusMarker(ui, "ok")} Import complete`, "success"),
3301
- formatUiKeyValue(ui, "Path", filePath, "muted"),
3302
- formatUiKeyValue(ui, "New accounts", String(result.imported), result.imported > 0 ? "success" : "muted"),
3303
- formatUiKeyValue(ui, "Duplicates skipped", String(result.skipped), result.skipped > 0 ? "warning" : "muted"),
3304
- formatUiKeyValue(ui, "Total accounts", String(result.total), "accent"),
3305
- ];
3306
- 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");
3307
2514
  }
3308
- return lines.join("\n");
3309
- }
3310
- catch (error) {
3311
- const msg = error instanceof Error ? error.message : String(error);
3312
- if (ui.v2Enabled) {
3313
- return [
3314
- ...formatUiHeader(ui, "Import accounts"),
3315
- "",
3316
- formatUiItem(ui, `${getStatusMarker(ui, "error")} Import failed`, "danger"),
3317
- formatUiKeyValue(ui, "Error", msg, "danger"),
3318
- ].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}`;
3319
2526
  }
3320
- return `Import failed: ${msg}`;
3321
- }
3322
- },
3323
- }),
3324
- } : {}),
2527
+ },
2528
+ }),
2529
+ }
2530
+ : {}),
3325
2531
  },
3326
2532
  };
3327
2533
  };