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.
- package/README.md +49 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +910 -1704
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/index.d.ts +2 -0
- package/dist/lib/auth/index.d.ts.map +1 -0
- package/dist/lib/auth/index.js +2 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/codex-cli/index.d.ts +5 -0
- package/dist/lib/codex-cli/index.d.ts.map +1 -0
- package/dist/lib/codex-cli/index.js +5 -0
- package/dist/lib/codex-cli/index.js.map +1 -0
- package/dist/lib/codex-manager/auth-commands.d.ts +72 -0
- package/dist/lib/codex-manager/auth-commands.d.ts.map +1 -0
- package/dist/lib/codex-manager/auth-commands.js +643 -0
- package/dist/lib/codex-manager/auth-commands.js.map +1 -0
- package/dist/lib/codex-manager/backend-category-entry.d.ts +103 -0
- package/dist/lib/codex-manager/backend-category-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-category-entry.js +23 -0
- package/dist/lib/codex-manager/backend-category-entry.js.map +1 -0
- package/dist/lib/codex-manager/backend-category-helpers.d.ts +10 -0
- package/dist/lib/codex-manager/backend-category-helpers.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-category-helpers.js +29 -0
- package/dist/lib/codex-manager/backend-category-helpers.js.map +1 -0
- package/dist/lib/codex-manager/backend-category-prompt.d.ts +55 -0
- package/dist/lib/codex-manager/backend-category-prompt.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-category-prompt.js +178 -0
- package/dist/lib/codex-manager/backend-category-prompt.js.map +1 -0
- package/dist/lib/codex-manager/backend-settings-controller.d.ts +11 -0
- package/dist/lib/codex-manager/backend-settings-controller.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-settings-controller.js +14 -0
- package/dist/lib/codex-manager/backend-settings-controller.js.map +1 -0
- package/dist/lib/codex-manager/backend-settings-entry.d.ts +20 -0
- package/dist/lib/codex-manager/backend-settings-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-settings-entry.js +12 -0
- package/dist/lib/codex-manager/backend-settings-entry.js.map +1 -0
- package/dist/lib/codex-manager/backend-settings-helpers.d.ts +16 -0
- package/dist/lib/codex-manager/backend-settings-helpers.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-settings-helpers.js +102 -0
- package/dist/lib/codex-manager/backend-settings-helpers.js.map +1 -0
- package/dist/lib/codex-manager/backend-settings-prompt.d.ts +49 -0
- package/dist/lib/codex-manager/backend-settings-prompt.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-settings-prompt.js +121 -0
- package/dist/lib/codex-manager/backend-settings-prompt.js.map +1 -0
- package/dist/lib/codex-manager/backend-settings-schema.d.ts +55 -0
- package/dist/lib/codex-manager/backend-settings-schema.d.ts.map +1 -0
- package/dist/lib/codex-manager/backend-settings-schema.js +264 -0
- package/dist/lib/codex-manager/backend-settings-schema.js.map +1 -0
- package/dist/lib/codex-manager/behavior-settings-panel.d.ts +33 -0
- package/dist/lib/codex-manager/behavior-settings-panel.d.ts.map +1 -0
- package/dist/lib/codex-manager/behavior-settings-panel.js +180 -0
- package/dist/lib/codex-manager/behavior-settings-panel.js.map +1 -0
- package/dist/lib/codex-manager/best.d.ts +26 -0
- package/dist/lib/codex-manager/best.d.ts.map +1 -0
- package/dist/lib/codex-manager/best.js +249 -0
- package/dist/lib/codex-manager/best.js.map +1 -0
- package/dist/lib/codex-manager/commands/best.d.ts +81 -0
- package/dist/lib/codex-manager/commands/best.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/best.js +223 -0
- package/dist/lib/codex-manager/commands/best.js.map +1 -0
- package/dist/lib/codex-manager/commands/check.d.ts +7 -0
- package/dist/lib/codex-manager/commands/check.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/check.js +5 -0
- package/dist/lib/codex-manager/commands/check.js.map +1 -0
- package/dist/lib/codex-manager/commands/config-explain.d.ts +7 -0
- package/dist/lib/codex-manager/commands/config-explain.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/config-explain.js +31 -0
- package/dist/lib/codex-manager/commands/config-explain.js.map +1 -0
- package/dist/lib/codex-manager/commands/debug-bundle.d.ts +28 -0
- package/dist/lib/codex-manager/commands/debug-bundle.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/debug-bundle.js +62 -0
- package/dist/lib/codex-manager/commands/debug-bundle.js.map +1 -0
- package/dist/lib/codex-manager/commands/doctor.d.ts +85 -0
- package/dist/lib/codex-manager/commands/doctor.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/doctor.js +424 -0
- package/dist/lib/codex-manager/commands/doctor.js.map +1 -0
- package/dist/lib/codex-manager/commands/fix.d.ts +92 -0
- package/dist/lib/codex-manager/commands/fix.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/fix.js +347 -0
- package/dist/lib/codex-manager/commands/fix.js.map +1 -0
- package/dist/lib/codex-manager/commands/forecast.d.ts +70 -0
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/forecast.js +281 -0
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -0
- package/dist/lib/codex-manager/commands/init-config.d.ts +15 -0
- package/dist/lib/codex-manager/commands/init-config.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/init-config.js +95 -0
- package/dist/lib/codex-manager/commands/init-config.js.map +1 -0
- package/dist/lib/codex-manager/commands/report.d.ts +24 -0
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/report.js +314 -0
- package/dist/lib/codex-manager/commands/report.js.map +1 -0
- package/dist/lib/codex-manager/commands/status.d.ts +23 -0
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/status.js +50 -0
- package/dist/lib/codex-manager/commands/status.js.map +1 -0
- package/dist/lib/codex-manager/commands/switch.d.ts +22 -0
- package/dist/lib/codex-manager/commands/switch.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/switch.js +41 -0
- package/dist/lib/codex-manager/commands/switch.js.map +1 -0
- package/dist/lib/codex-manager/commands/verify-flagged.d.ts +69 -0
- package/dist/lib/codex-manager/commands/verify-flagged.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/verify-flagged.js +251 -0
- package/dist/lib/codex-manager/commands/verify-flagged.js.map +1 -0
- package/dist/lib/codex-manager/config-explain.d.ts +2 -0
- package/dist/lib/codex-manager/config-explain.d.ts.map +1 -0
- package/dist/lib/codex-manager/config-explain.js +62 -0
- package/dist/lib/codex-manager/config-explain.js.map +1 -0
- package/dist/lib/codex-manager/config-template.d.ts +2 -0
- package/dist/lib/codex-manager/config-template.d.ts.map +1 -0
- package/dist/lib/codex-manager/config-template.js +112 -0
- package/dist/lib/codex-manager/config-template.js.map +1 -0
- package/dist/lib/codex-manager/dashboard-display-panel.d.ts +40 -0
- package/dist/lib/codex-manager/dashboard-display-panel.d.ts.map +1 -0
- package/dist/lib/codex-manager/dashboard-display-panel.js +187 -0
- package/dist/lib/codex-manager/dashboard-display-panel.js.map +1 -0
- package/dist/lib/codex-manager/dashboard-formatters.d.ts +6 -0
- package/dist/lib/codex-manager/dashboard-formatters.d.ts.map +1 -0
- package/dist/lib/codex-manager/dashboard-formatters.js +19 -0
- package/dist/lib/codex-manager/dashboard-formatters.js.map +1 -0
- package/dist/lib/codex-manager/dashboard-settings-controller.d.ts +12 -0
- package/dist/lib/codex-manager/dashboard-settings-controller.d.ts.map +1 -0
- package/dist/lib/codex-manager/dashboard-settings-controller.js +17 -0
- package/dist/lib/codex-manager/dashboard-settings-controller.js.map +1 -0
- package/dist/lib/codex-manager/dashboard-settings-data.d.ts +10 -0
- package/dist/lib/codex-manager/dashboard-settings-data.d.ts.map +1 -0
- package/dist/lib/codex-manager/dashboard-settings-data.js +95 -0
- package/dist/lib/codex-manager/dashboard-settings-data.js.map +1 -0
- package/dist/lib/codex-manager/dashboard-settings-entry.d.ts +22 -0
- package/dist/lib/codex-manager/dashboard-settings-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/dashboard-settings-entry.js +13 -0
- package/dist/lib/codex-manager/dashboard-settings-entry.js.map +1 -0
- package/dist/lib/codex-manager/debug-bundle.d.ts +2 -0
- package/dist/lib/codex-manager/debug-bundle.d.ts.map +1 -0
- package/dist/lib/codex-manager/debug-bundle.js +128 -0
- package/dist/lib/codex-manager/debug-bundle.js.map +1 -0
- package/dist/lib/codex-manager/doctor.d.ts +20 -0
- package/dist/lib/codex-manager/doctor.d.ts.map +1 -0
- package/dist/lib/codex-manager/doctor.js +546 -0
- package/dist/lib/codex-manager/doctor.js.map +1 -0
- package/dist/lib/codex-manager/experimental-settings-entry.d.ts +7 -0
- package/dist/lib/codex-manager/experimental-settings-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/experimental-settings-entry.js +30 -0
- package/dist/lib/codex-manager/experimental-settings-entry.js.map +1 -0
- package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +66 -0
- package/dist/lib/codex-manager/experimental-settings-prompt.d.ts.map +1 -0
- package/dist/lib/codex-manager/experimental-settings-prompt.js +221 -0
- package/dist/lib/codex-manager/experimental-settings-prompt.js.map +1 -0
- package/dist/lib/codex-manager/experimental-settings-schema.d.ts +23 -0
- package/dist/lib/codex-manager/experimental-settings-schema.d.ts.map +1 -0
- package/dist/lib/codex-manager/experimental-settings-schema.js +34 -0
- package/dist/lib/codex-manager/experimental-settings-schema.js.map +1 -0
- package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts +30 -0
- package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/experimental-sync-target-entry.js +16 -0
- package/dist/lib/codex-manager/experimental-sync-target-entry.js.map +1 -0
- package/dist/lib/codex-manager/experimental-sync-target.d.ts +23 -0
- package/dist/lib/codex-manager/experimental-sync-target.d.ts.map +1 -0
- package/dist/lib/codex-manager/experimental-sync-target.js +39 -0
- package/dist/lib/codex-manager/experimental-sync-target.js.map +1 -0
- package/dist/lib/codex-manager/fix.d.ts +26 -0
- package/dist/lib/codex-manager/fix.d.ts.map +1 -0
- package/dist/lib/codex-manager/fix.js +366 -0
- package/dist/lib/codex-manager/fix.js.map +1 -0
- package/dist/lib/codex-manager/forecast-report-commands.d.ts +54 -0
- package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +1 -0
- package/dist/lib/codex-manager/forecast-report-commands.js +443 -0
- package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -0
- package/dist/lib/codex-manager/forecast.d.ts +26 -0
- package/dist/lib/codex-manager/forecast.d.ts.map +1 -0
- package/dist/lib/codex-manager/forecast.js +218 -0
- package/dist/lib/codex-manager/forecast.js.map +1 -0
- package/dist/lib/codex-manager/help.d.ts +36 -0
- package/dist/lib/codex-manager/help.d.ts.map +1 -0
- package/dist/lib/codex-manager/help.js +125 -0
- package/dist/lib/codex-manager/help.js.map +1 -0
- package/dist/lib/codex-manager/info.d.ts +11 -0
- package/dist/lib/codex-manager/info.d.ts.map +1 -0
- package/dist/lib/codex-manager/info.js +91 -0
- package/dist/lib/codex-manager/info.js.map +1 -0
- package/dist/lib/codex-manager/login.d.ts +40 -0
- package/dist/lib/codex-manager/login.d.ts.map +1 -0
- package/dist/lib/codex-manager/login.js +1041 -0
- package/dist/lib/codex-manager/login.js.map +1 -0
- package/dist/lib/codex-manager/repair-commands.d.ts +70 -0
- package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -0
- package/dist/lib/codex-manager/repair-commands.js +1671 -0
- package/dist/lib/codex-manager/repair-commands.js.map +1 -0
- package/dist/lib/codex-manager/report.d.ts +39 -0
- package/dist/lib/codex-manager/report.d.ts.map +1 -0
- package/dist/lib/codex-manager/report.js +243 -0
- package/dist/lib/codex-manager/report.js.map +1 -0
- package/dist/lib/codex-manager/settings-hub-entry.d.ts +30 -0
- package/dist/lib/codex-manager/settings-hub-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-hub-entry.js +11 -0
- package/dist/lib/codex-manager/settings-hub-entry.js.map +1 -0
- package/dist/lib/codex-manager/settings-hub-menu.d.ts +30 -0
- package/dist/lib/codex-manager/settings-hub-menu.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-hub-menu.js +37 -0
- package/dist/lib/codex-manager/settings-hub-menu.js.map +1 -0
- package/dist/lib/codex-manager/settings-hub-prompt.d.ts +25 -0
- package/dist/lib/codex-manager/settings-hub-prompt.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-hub-prompt.js +23 -0
- package/dist/lib/codex-manager/settings-hub-prompt.js.map +1 -0
- package/dist/lib/codex-manager/settings-hub.d.ts +10 -29
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
- package/dist/lib/codex-manager/settings-hub.js +260 -2168
- package/dist/lib/codex-manager/settings-hub.js.map +1 -1
- package/dist/lib/codex-manager/settings-panels.d.ts +56 -0
- package/dist/lib/codex-manager/settings-panels.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-panels.js +73 -0
- package/dist/lib/codex-manager/settings-panels.js.map +1 -0
- package/dist/lib/codex-manager/settings-persist-utils.d.ts +9 -0
- package/dist/lib/codex-manager/settings-persist-utils.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-persist-utils.js +33 -0
- package/dist/lib/codex-manager/settings-persist-utils.js.map +1 -0
- package/dist/lib/codex-manager/settings-preview.d.ts +12 -0
- package/dist/lib/codex-manager/settings-preview.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-preview.js +118 -0
- package/dist/lib/codex-manager/settings-preview.js.map +1 -0
- package/dist/lib/codex-manager/settings-write-queue.d.ts +14 -0
- package/dist/lib/codex-manager/settings-write-queue.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-write-queue.js +88 -0
- package/dist/lib/codex-manager/settings-write-queue.js.map +1 -0
- package/dist/lib/codex-manager/statusline-order.d.ts +3 -0
- package/dist/lib/codex-manager/statusline-order.d.ts.map +1 -0
- package/dist/lib/codex-manager/statusline-order.js +17 -0
- package/dist/lib/codex-manager/statusline-order.js.map +1 -0
- package/dist/lib/codex-manager/statusline-settings-panel.d.ts +40 -0
- package/dist/lib/codex-manager/statusline-settings-panel.d.ts.map +1 -0
- package/dist/lib/codex-manager/statusline-settings-panel.js +167 -0
- package/dist/lib/codex-manager/statusline-settings-panel.js.map +1 -0
- package/dist/lib/codex-manager/switch.d.ts +31 -0
- package/dist/lib/codex-manager/switch.d.ts.map +1 -0
- package/dist/lib/codex-manager/switch.js +103 -0
- package/dist/lib/codex-manager/switch.js.map +1 -0
- package/dist/lib/codex-manager/theme-settings-panel.d.ts +26 -0
- package/dist/lib/codex-manager/theme-settings-panel.d.ts.map +1 -0
- package/dist/lib/codex-manager/theme-settings-panel.js +131 -0
- package/dist/lib/codex-manager/theme-settings-panel.js.map +1 -0
- package/dist/lib/codex-manager/unified-settings-controller.d.ts +27 -0
- package/dist/lib/codex-manager/unified-settings-controller.d.ts.map +1 -0
- package/dist/lib/codex-manager/unified-settings-controller.js +50 -0
- package/dist/lib/codex-manager/unified-settings-controller.js.map +1 -0
- package/dist/lib/codex-manager/unified-settings-entry.d.ts +6 -0
- package/dist/lib/codex-manager/unified-settings-entry.d.ts.map +1 -0
- package/dist/lib/codex-manager/unified-settings-entry.js +23 -0
- package/dist/lib/codex-manager/unified-settings-entry.js.map +1 -0
- package/dist/lib/codex-manager/verify-flagged.d.ts +20 -0
- package/dist/lib/codex-manager/verify-flagged.d.ts.map +1 -0
- package/dist/lib/codex-manager/verify-flagged.js +316 -0
- package/dist/lib/codex-manager/verify-flagged.js.map +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +439 -2199
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts +38 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +371 -9
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +9 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +13 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/forecast.d.ts +17 -0
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +33 -6
- package/dist/lib/forecast.js.map +1 -1
- package/dist/lib/named-backup-export.js +2 -2
- package/dist/lib/named-backup-export.js.map +1 -1
- package/dist/lib/prompts/codex.d.ts +8 -3
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +16 -26
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/request/failover-config.d.ts +4 -0
- package/dist/lib/request/failover-config.d.ts.map +1 -0
- package/dist/lib/request/failover-config.js +15 -0
- package/dist/lib/request/failover-config.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts +10 -4
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +13 -8
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +49 -12
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +303 -108
- package/dist/lib/request/helpers/model-map.js.map +1 -1
- package/dist/lib/request/helpers/tool-utils.d.ts +2 -15
- package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
- package/dist/lib/request/helpers/tool-utils.js +40 -27
- package/dist/lib/request/helpers/tool-utils.js.map +1 -1
- package/dist/lib/request/index.d.ts +5 -0
- package/dist/lib/request/index.d.ts.map +1 -0
- package/dist/lib/request/index.js +5 -0
- package/dist/lib/request/index.js.map +1 -0
- package/dist/lib/request/request-init.d.ts +3 -0
- package/dist/lib/request/request-init.d.ts.map +1 -0
- package/dist/lib/request/request-init.js +50 -0
- package/dist/lib/request/request-init.js.map +1 -0
- package/dist/lib/request/request-transformer.d.ts +19 -6
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +210 -193
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/request/response-compaction.d.ts +20 -0
- package/dist/lib/request/response-compaction.d.ts.map +1 -0
- package/dist/lib/request/response-compaction.js +124 -0
- package/dist/lib/request/response-compaction.js.map +1 -0
- package/dist/lib/request/response-handler.d.ts +2 -0
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +567 -11
- package/dist/lib/request/response-handler.js.map +1 -1
- package/dist/lib/request/response-metadata.d.ts +3 -0
- package/dist/lib/request/response-metadata.d.ts.map +1 -0
- package/dist/lib/request/response-metadata.js +61 -0
- package/dist/lib/request/response-metadata.js.map +1 -0
- package/dist/lib/request/trace-mode.d.ts +9 -0
- package/dist/lib/request/trace-mode.d.ts.map +1 -0
- package/dist/lib/request/trace-mode.js +14 -0
- package/dist/lib/request/trace-mode.js.map +1 -0
- package/dist/lib/request/wait-utils.d.ts +14 -0
- package/dist/lib/request/wait-utils.d.ts.map +1 -0
- package/dist/lib/request/wait-utils.js +44 -0
- package/dist/lib/request/wait-utils.js.map +1 -0
- package/dist/lib/runtime/account-check-helpers.d.ts +8 -0
- package/dist/lib/runtime/account-check-helpers.d.ts.map +1 -0
- package/dist/lib/runtime/account-check-helpers.js +30 -0
- package/dist/lib/runtime/account-check-helpers.js.map +1 -0
- package/dist/lib/runtime/account-check-types.d.ts +18 -0
- package/dist/lib/runtime/account-check-types.d.ts.map +1 -0
- package/dist/lib/runtime/account-check-types.js +12 -0
- package/dist/lib/runtime/account-check-types.js.map +1 -0
- package/dist/lib/runtime/account-check.d.ts +48 -0
- package/dist/lib/runtime/account-check.d.ts.map +1 -0
- package/dist/lib/runtime/account-check.js +202 -0
- package/dist/lib/runtime/account-check.js.map +1 -0
- package/dist/lib/runtime/account-health-check.d.ts +8 -0
- package/dist/lib/runtime/account-health-check.d.ts.map +1 -0
- package/dist/lib/runtime/account-health-check.js +30 -0
- package/dist/lib/runtime/account-health-check.js.map +1 -0
- package/dist/lib/runtime/account-manager-cache-entry.d.ts +25 -0
- package/dist/lib/runtime/account-manager-cache-entry.d.ts.map +1 -0
- package/dist/lib/runtime/account-manager-cache-entry.js +17 -0
- package/dist/lib/runtime/account-manager-cache-entry.js.map +1 -0
- package/dist/lib/runtime/account-manager-cache.d.ts +25 -0
- package/dist/lib/runtime/account-manager-cache.d.ts.map +1 -0
- package/dist/lib/runtime/account-manager-cache.js +44 -0
- package/dist/lib/runtime/account-manager-cache.js.map +1 -0
- package/dist/lib/runtime/account-pool.d.ts +23 -0
- package/dist/lib/runtime/account-pool.d.ts.map +1 -0
- package/dist/lib/runtime/account-pool.js +138 -0
- package/dist/lib/runtime/account-pool.js.map +1 -0
- package/dist/lib/runtime/account-scope.d.ts +10 -0
- package/dist/lib/runtime/account-scope.d.ts.map +1 -0
- package/dist/lib/runtime/account-scope.js +13 -0
- package/dist/lib/runtime/account-scope.js.map +1 -0
- package/dist/lib/runtime/account-select-event.d.ts +19 -0
- package/dist/lib/runtime/account-select-event.d.ts.map +1 -0
- package/dist/lib/runtime/account-select-event.js +53 -0
- package/dist/lib/runtime/account-select-event.js.map +1 -0
- package/dist/lib/runtime/account-selection.d.ts +28 -0
- package/dist/lib/runtime/account-selection.d.ts.map +1 -0
- package/dist/lib/runtime/account-selection.js +46 -0
- package/dist/lib/runtime/account-selection.js.map +1 -0
- package/dist/lib/runtime/account-state.d.ts +13 -0
- package/dist/lib/runtime/account-state.d.ts.map +1 -0
- package/dist/lib/runtime/account-state.js +38 -0
- package/dist/lib/runtime/account-state.js.map +1 -0
- package/dist/lib/runtime/account-status.d.ts +13 -0
- package/dist/lib/runtime/account-status.d.ts.map +1 -0
- package/dist/lib/runtime/account-status.js +37 -0
- package/dist/lib/runtime/account-status.js.map +1 -0
- package/dist/lib/runtime/account-storage-scope-entry.d.ts +26 -0
- package/dist/lib/runtime/account-storage-scope-entry.d.ts.map +1 -0
- package/dist/lib/runtime/account-storage-scope-entry.js +15 -0
- package/dist/lib/runtime/account-storage-scope-entry.js.map +1 -0
- package/dist/lib/runtime/auth-facade.d.ts +51 -0
- package/dist/lib/runtime/auth-facade.d.ts.map +1 -0
- package/dist/lib/runtime/auth-facade.js +48 -0
- package/dist/lib/runtime/auth-facade.js.map +1 -0
- package/dist/lib/runtime/browser-oauth-flow.d.ts +32 -0
- package/dist/lib/runtime/browser-oauth-flow.d.ts.map +1 -0
- package/dist/lib/runtime/browser-oauth-flow.js +31 -0
- package/dist/lib/runtime/browser-oauth-flow.js.map +1 -0
- package/dist/lib/runtime/capability-boost.d.ts +25 -0
- package/dist/lib/runtime/capability-boost.d.ts.map +1 -0
- package/dist/lib/runtime/capability-boost.js +26 -0
- package/dist/lib/runtime/capability-boost.js.map +1 -0
- package/dist/lib/runtime/event-handler.d.ts +53 -0
- package/dist/lib/runtime/event-handler.d.ts.map +1 -0
- package/dist/lib/runtime/event-handler.js +46 -0
- package/dist/lib/runtime/event-handler.js.map +1 -0
- package/dist/lib/runtime/flagged-verify-types.d.ts +9 -0
- package/dist/lib/runtime/flagged-verify-types.d.ts.map +1 -0
- package/dist/lib/runtime/flagged-verify-types.js +7 -0
- package/dist/lib/runtime/flagged-verify-types.js.map +1 -0
- package/dist/lib/runtime/hydrate-emails.d.ts +13 -0
- package/dist/lib/runtime/hydrate-emails.d.ts.map +1 -0
- package/dist/lib/runtime/hydrate-emails.js +56 -0
- package/dist/lib/runtime/hydrate-emails.js.map +1 -0
- package/dist/lib/runtime/live-sync-entry.d.ts +36 -0
- package/dist/lib/runtime/live-sync-entry.d.ts.map +1 -0
- package/dist/lib/runtime/live-sync-entry.js +14 -0
- package/dist/lib/runtime/live-sync-entry.js.map +1 -0
- package/dist/lib/runtime/live-sync.d.ts +35 -0
- package/dist/lib/runtime/live-sync.d.ts.map +1 -0
- package/dist/lib/runtime/live-sync.js +65 -0
- package/dist/lib/runtime/live-sync.js.map +1 -0
- package/dist/lib/runtime/loader-setup.d.ts +9 -0
- package/dist/lib/runtime/loader-setup.d.ts.map +1 -0
- package/dist/lib/runtime/loader-setup.js +8 -0
- package/dist/lib/runtime/loader-setup.js.map +1 -0
- package/dist/lib/runtime/login-menu-accounts.d.ts +29 -0
- package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -0
- package/dist/lib/runtime/login-menu-accounts.js +33 -0
- package/dist/lib/runtime/login-menu-accounts.js.map +1 -0
- package/dist/lib/runtime/manual-oauth-flow.d.ts +40 -0
- package/dist/lib/runtime/manual-oauth-flow.d.ts.map +1 -0
- package/dist/lib/runtime/manual-oauth-flow.js +71 -0
- package/dist/lib/runtime/manual-oauth-flow.js.map +1 -0
- package/dist/lib/runtime/metrics.d.ts +29 -0
- package/dist/lib/runtime/metrics.d.ts.map +1 -0
- package/dist/lib/runtime/metrics.js +97 -0
- package/dist/lib/runtime/metrics.js.map +1 -0
- package/dist/lib/runtime/oauth-browser-flow.d.ts +9 -0
- package/dist/lib/runtime/oauth-browser-flow.d.ts.map +1 -0
- package/dist/lib/runtime/oauth-browser-flow.js +34 -0
- package/dist/lib/runtime/oauth-browser-flow.js.map +1 -0
- package/dist/lib/runtime/preemptive-quota.d.ts +13 -0
- package/dist/lib/runtime/preemptive-quota.d.ts.map +1 -0
- package/dist/lib/runtime/preemptive-quota.js +9 -0
- package/dist/lib/runtime/preemptive-quota.js.map +1 -0
- package/dist/lib/runtime/quota-headers.d.ts +12 -0
- package/dist/lib/runtime/quota-headers.d.ts.map +1 -0
- package/dist/lib/runtime/quota-headers.js +131 -0
- package/dist/lib/runtime/quota-headers.js.map +1 -0
- package/dist/lib/runtime/quota-probe.d.ts +18 -0
- package/dist/lib/runtime/quota-probe.d.ts.map +1 -0
- package/dist/lib/runtime/quota-probe.js +82 -0
- package/dist/lib/runtime/quota-probe.js.map +1 -0
- package/dist/lib/runtime/quota-settings.d.ts +14 -0
- package/dist/lib/runtime/quota-settings.d.ts.map +1 -0
- package/dist/lib/runtime/quota-settings.js +12 -0
- package/dist/lib/runtime/quota-settings.js.map +1 -0
- package/dist/lib/runtime/refresh-guardian-entry.d.ts +32 -0
- package/dist/lib/runtime/refresh-guardian-entry.d.ts.map +1 -0
- package/dist/lib/runtime/refresh-guardian-entry.js +12 -0
- package/dist/lib/runtime/refresh-guardian-entry.js.map +1 -0
- package/dist/lib/runtime/refresh-guardian.d.ts +24 -0
- package/dist/lib/runtime/refresh-guardian.d.ts.map +1 -0
- package/dist/lib/runtime/refresh-guardian.js +32 -0
- package/dist/lib/runtime/refresh-guardian.js.map +1 -0
- package/dist/lib/runtime/request-init.d.ts +5 -0
- package/dist/lib/runtime/request-init.d.ts.map +1 -0
- package/dist/lib/runtime/request-init.js +50 -0
- package/dist/lib/runtime/request-init.js.map +1 -0
- package/dist/lib/runtime/runtime-services.d.ts +58 -0
- package/dist/lib/runtime/runtime-services.d.ts.map +1 -0
- package/dist/lib/runtime/runtime-services.js +87 -0
- package/dist/lib/runtime/runtime-services.js.map +1 -0
- package/dist/lib/runtime/session-affinity-entry.d.ts +30 -0
- package/dist/lib/runtime/session-affinity-entry.d.ts.map +1 -0
- package/dist/lib/runtime/session-affinity-entry.js +11 -0
- package/dist/lib/runtime/session-affinity-entry.js.map +1 -0
- package/dist/lib/runtime/session-affinity.d.ts +13 -0
- package/dist/lib/runtime/session-affinity.d.ts.map +1 -0
- package/dist/lib/runtime/session-affinity.js +17 -0
- package/dist/lib/runtime/session-affinity.js.map +1 -0
- package/dist/lib/runtime/session-recovery.d.ts +8 -0
- package/dist/lib/runtime/session-recovery.d.ts.map +1 -0
- package/dist/lib/runtime/session-recovery.js +7 -0
- package/dist/lib/runtime/session-recovery.js.map +1 -0
- package/dist/lib/runtime/status-marker.d.ts +3 -0
- package/dist/lib/runtime/status-marker.d.ts.map +1 -0
- package/dist/lib/runtime/status-marker.js +15 -0
- package/dist/lib/runtime/status-marker.js.map +1 -0
- package/dist/lib/runtime/storage-scope.d.ts +13 -0
- package/dist/lib/runtime/storage-scope.d.ts.map +1 -0
- package/dist/lib/runtime/storage-scope.js +14 -0
- package/dist/lib/runtime/storage-scope.js.map +1 -0
- package/dist/lib/runtime/toast.d.ts +16 -0
- package/dist/lib/runtime/toast.d.ts.map +1 -0
- package/dist/lib/runtime/toast.js +16 -0
- package/dist/lib/runtime/toast.js.map +1 -0
- package/dist/lib/runtime/ui-runtime-entry.d.ts +7 -0
- package/dist/lib/runtime/ui-runtime-entry.d.ts.map +1 -0
- package/dist/lib/runtime/ui-runtime-entry.js +4 -0
- package/dist/lib/runtime/ui-runtime-entry.js.map +1 -0
- package/dist/lib/runtime/ui-runtime.d.ts +9 -0
- package/dist/lib/runtime/ui-runtime.d.ts.map +1 -0
- package/dist/lib/runtime/ui-runtime.js +23 -0
- package/dist/lib/runtime/ui-runtime.js.map +1 -0
- package/dist/lib/runtime/verify-flagged.d.ts +44 -0
- package/dist/lib/runtime/verify-flagged.d.ts.map +1 -0
- package/dist/lib/runtime/verify-flagged.js +84 -0
- package/dist/lib/runtime/verify-flagged.js.map +1 -0
- package/dist/lib/schemas.d.ts +2 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +2 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/session-affinity.d.ts +10 -0
- package/dist/lib/session-affinity.d.ts.map +1 -1
- package/dist/lib/session-affinity.js +53 -6
- package/dist/lib/session-affinity.js.map +1 -1
- package/dist/lib/storage/account-clear-entry.d.ts +16 -0
- package/dist/lib/storage/account-clear-entry.d.ts.map +1 -0
- package/dist/lib/storage/account-clear-entry.js +12 -0
- package/dist/lib/storage/account-clear-entry.js.map +1 -0
- package/dist/lib/storage/account-clear.d.ts +8 -0
- package/dist/lib/storage/account-clear.d.ts.map +1 -0
- package/dist/lib/storage/account-clear.js +29 -0
- package/dist/lib/storage/account-clear.js.map +1 -0
- package/dist/lib/storage/account-match-utils.d.ts +9 -0
- package/dist/lib/storage/account-match-utils.d.ts.map +1 -0
- package/dist/lib/storage/account-match-utils.js +45 -0
- package/dist/lib/storage/account-match-utils.js.map +1 -0
- package/dist/lib/storage/account-persistence.d.ts +3 -0
- package/dist/lib/storage/account-persistence.d.ts.map +1 -0
- package/dist/lib/storage/account-persistence.js +12 -0
- package/dist/lib/storage/account-persistence.js.map +1 -0
- package/dist/lib/storage/account-port.d.ts +50 -0
- package/dist/lib/storage/account-port.d.ts.map +1 -0
- package/dist/lib/storage/account-port.js +43 -0
- package/dist/lib/storage/account-port.js.map +1 -0
- package/dist/lib/storage/account-save-entry.d.ts +7 -0
- package/dist/lib/storage/account-save-entry.d.ts.map +1 -0
- package/dist/lib/storage/account-save-entry.js +6 -0
- package/dist/lib/storage/account-save-entry.js.map +1 -0
- package/dist/lib/storage/account-save.d.ts +30 -0
- package/dist/lib/storage/account-save.d.ts.map +1 -0
- package/dist/lib/storage/account-save.js +49 -0
- package/dist/lib/storage/account-save.js.map +1 -0
- package/dist/lib/storage/account-snapshot.d.ts +25 -0
- package/dist/lib/storage/account-snapshot.d.ts.map +1 -0
- package/dist/lib/storage/account-snapshot.js +78 -0
- package/dist/lib/storage/account-snapshot.js.map +1 -0
- package/dist/lib/storage/backup-metadata-builder.d.ts +32 -0
- package/dist/lib/storage/backup-metadata-builder.d.ts.map +1 -0
- package/dist/lib/storage/backup-metadata-builder.js +77 -0
- package/dist/lib/storage/backup-metadata-builder.js.map +1 -0
- package/dist/lib/storage/backup-metadata.d.ts +29 -0
- package/dist/lib/storage/backup-metadata.d.ts.map +1 -0
- package/dist/lib/storage/backup-metadata.js +16 -0
- package/dist/lib/storage/backup-metadata.js.map +1 -0
- package/dist/lib/storage/backup-paths.d.ts +10 -0
- package/dist/lib/storage/backup-paths.d.ts.map +1 -0
- package/dist/lib/storage/backup-paths.js +27 -0
- package/dist/lib/storage/backup-paths.js.map +1 -0
- package/dist/lib/storage/backup-restore.d.ts +11 -0
- package/dist/lib/storage/backup-restore.d.ts.map +1 -0
- package/dist/lib/storage/backup-restore.js +52 -0
- package/dist/lib/storage/backup-restore.js.map +1 -0
- package/dist/lib/storage/cache-artifacts.d.ts +2 -0
- package/dist/lib/storage/cache-artifacts.d.ts.map +1 -0
- package/dist/lib/storage/cache-artifacts.js +4 -0
- package/dist/lib/storage/cache-artifacts.js.map +1 -0
- package/dist/lib/storage/error-hints.d.ts +10 -0
- package/dist/lib/storage/error-hints.d.ts.map +1 -0
- package/dist/lib/storage/error-hints.js +36 -0
- package/dist/lib/storage/error-hints.js.map +1 -0
- package/dist/lib/storage/file-paths.d.ts +6 -0
- package/dist/lib/storage/file-paths.d.ts.map +1 -0
- package/dist/lib/storage/file-paths.js +17 -0
- package/dist/lib/storage/file-paths.js.map +1 -0
- package/dist/lib/storage/fixture-guards.d.ts +4 -0
- package/dist/lib/storage/fixture-guards.d.ts.map +1 -0
- package/dist/lib/storage/fixture-guards.js +27 -0
- package/dist/lib/storage/fixture-guards.js.map +1 -0
- package/dist/lib/storage/flagged-entry.d.ts +15 -0
- package/dist/lib/storage/flagged-entry.d.ts.map +1 -0
- package/dist/lib/storage/flagged-entry.js +12 -0
- package/dist/lib/storage/flagged-entry.js.map +1 -0
- package/dist/lib/storage/flagged-load-entry.d.ts +20 -0
- package/dist/lib/storage/flagged-load-entry.d.ts.map +1 -0
- package/dist/lib/storage/flagged-load-entry.js +13 -0
- package/dist/lib/storage/flagged-load-entry.js.map +1 -0
- package/dist/lib/storage/flagged-save-entry.d.ts +7 -0
- package/dist/lib/storage/flagged-save-entry.d.ts.map +1 -0
- package/dist/lib/storage/flagged-save-entry.js +6 -0
- package/dist/lib/storage/flagged-save-entry.js.map +1 -0
- package/dist/lib/storage/flagged-storage-file.d.ts +7 -0
- package/dist/lib/storage/flagged-storage-file.d.ts.map +1 -0
- package/dist/lib/storage/flagged-storage-file.js +25 -0
- package/dist/lib/storage/flagged-storage-file.js.map +1 -0
- package/dist/lib/storage/flagged-storage-io.d.ts +28 -0
- package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -0
- package/dist/lib/storage/flagged-storage-io.js +135 -0
- package/dist/lib/storage/flagged-storage-io.js.map +1 -0
- package/dist/lib/storage/flagged-storage.d.ts +6 -0
- package/dist/lib/storage/flagged-storage.d.ts.map +1 -0
- package/dist/lib/storage/flagged-storage.js +90 -0
- package/dist/lib/storage/flagged-storage.js.map +1 -0
- package/dist/lib/storage/gitignore.d.ts +7 -0
- package/dist/lib/storage/gitignore.d.ts.map +1 -0
- package/dist/lib/storage/gitignore.js +34 -0
- package/dist/lib/storage/gitignore.js.map +1 -0
- package/dist/lib/storage/hash.d.ts +2 -0
- package/dist/lib/storage/hash.d.ts.map +1 -0
- package/dist/lib/storage/hash.js +5 -0
- package/dist/lib/storage/hash.js.map +1 -0
- package/dist/lib/storage/identity.d.ts +15 -0
- package/dist/lib/storage/identity.d.ts.map +1 -0
- package/dist/lib/storage/identity.js +48 -0
- package/dist/lib/storage/identity.js.map +1 -0
- package/dist/lib/storage/import-export.d.ts +24 -0
- package/dist/lib/storage/import-export.d.ts.map +1 -0
- package/dist/lib/storage/import-export.js +108 -0
- package/dist/lib/storage/import-export.js.map +1 -0
- package/dist/lib/storage/metadata-section.d.ts +16 -0
- package/dist/lib/storage/metadata-section.d.ts.map +1 -0
- package/dist/lib/storage/metadata-section.js +16 -0
- package/dist/lib/storage/metadata-section.js.map +1 -0
- package/dist/lib/storage/named-backup-entry.d.ts +15 -0
- package/dist/lib/storage/named-backup-entry.d.ts.map +1 -0
- package/dist/lib/storage/named-backup-entry.js +7 -0
- package/dist/lib/storage/named-backup-entry.js.map +1 -0
- package/dist/lib/storage/named-backups-entry.d.ts +19 -0
- package/dist/lib/storage/named-backups-entry.d.ts.map +1 -0
- package/dist/lib/storage/named-backups-entry.js +7 -0
- package/dist/lib/storage/named-backups-entry.js.map +1 -0
- package/dist/lib/storage/named-backups.d.ts +15 -0
- package/dist/lib/storage/named-backups.d.ts.map +1 -0
- package/dist/lib/storage/named-backups.js +68 -0
- package/dist/lib/storage/named-backups.js.map +1 -0
- package/dist/lib/storage/path-state.d.ts +9 -0
- package/dist/lib/storage/path-state.d.ts.map +1 -0
- package/dist/lib/storage/path-state.js +20 -0
- package/dist/lib/storage/path-state.js.map +1 -0
- package/dist/lib/storage/project-migration.d.ts +10 -0
- package/dist/lib/storage/project-migration.d.ts.map +1 -0
- package/dist/lib/storage/project-migration.js +38 -0
- package/dist/lib/storage/project-migration.js.map +1 -0
- package/dist/lib/storage/record-utils.d.ts +3 -0
- package/dist/lib/storage/record-utils.d.ts.map +1 -0
- package/dist/lib/storage/record-utils.js +9 -0
- package/dist/lib/storage/record-utils.js.map +1 -0
- package/dist/lib/storage/restore-assessment.d.ts +7 -0
- package/dist/lib/storage/restore-assessment.d.ts.map +1 -0
- package/dist/lib/storage/restore-assessment.js +42 -0
- package/dist/lib/storage/restore-assessment.js.map +1 -0
- package/dist/lib/storage/restore-backup-entry.d.ts +24 -0
- package/dist/lib/storage/restore-backup-entry.d.ts.map +1 -0
- package/dist/lib/storage/restore-backup-entry.js +10 -0
- package/dist/lib/storage/restore-backup-entry.js.map +1 -0
- package/dist/lib/storage/restore-metadata.d.ts +10 -0
- package/dist/lib/storage/restore-metadata.d.ts.map +1 -0
- package/dist/lib/storage/restore-metadata.js +18 -0
- package/dist/lib/storage/restore-metadata.js.map +1 -0
- package/dist/lib/storage/restore.d.ts +14 -0
- package/dist/lib/storage/restore.d.ts.map +1 -0
- package/dist/lib/storage/restore.js +53 -0
- package/dist/lib/storage/restore.js.map +1 -0
- package/dist/lib/storage/snapshot-inspectors.d.ts +22 -0
- package/dist/lib/storage/snapshot-inspectors.d.ts.map +1 -0
- package/dist/lib/storage/snapshot-inspectors.js +95 -0
- package/dist/lib/storage/snapshot-inspectors.js.map +1 -0
- package/dist/lib/storage/storage-parser.d.ts +15 -0
- package/dist/lib/storage/storage-parser.d.ts.map +1 -0
- package/dist/lib/storage/storage-parser.js +16 -0
- package/dist/lib/storage/storage-parser.js.map +1 -0
- package/dist/lib/storage/transactions.d.ts +23 -0
- package/dist/lib/storage/transactions.d.ts.map +1 -0
- package/dist/lib/storage/transactions.js +64 -0
- package/dist/lib/storage/transactions.js.map +1 -0
- package/dist/lib/storage.d.ts +8 -47
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +482 -1044
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/types.d.ts +80 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/package.json +41 -4
- package/scripts/check-pack-budget-lib.js +114 -0
- package/scripts/check-pack-budget.mjs +10 -0
- package/scripts/codex-multi-auth.js +34 -10
- package/scripts/install-codex-auth-utils.js +54 -14
- package/scripts/install-codex-auth.js +62 -33
- package/scripts/update-vendor-provenance.mjs +96 -0
- 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 {
|
|
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 {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
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 {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
|
347
|
+
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
|
|
801
348
|
}
|
|
802
349
|
const managerPromise = accountManagerPromise ??
|
|
803
|
-
|
|
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"
|
|
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 =
|
|
879
|
-
|
|
880
|
-
|
|
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
|
|
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 &&
|
|
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
|
|
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 ?? "")
|
|
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
|
|
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 = (
|
|
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 ===
|
|
1044
|
-
|
|
537
|
+
const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot ===
|
|
538
|
+
"function"
|
|
539
|
+
? (accountSnapshotSource.getAccountsSnapshot() ?? [])
|
|
1045
540
|
: [];
|
|
1046
541
|
if (accountSnapshotList.length === 0 &&
|
|
1047
|
-
typeof accountSnapshotSource.getAccountByIndex ===
|
|
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] =
|
|
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 =
|
|
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
|
|
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 &&
|
|
1162
|
-
|
|
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
|
|
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 ??
|
|
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, {
|
|
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
|
|
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
|
|
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 ??
|
|
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 ||
|
|
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 &&
|
|
888
|
+
if (transformedBody &&
|
|
889
|
+
typeof transformedBody === "object") {
|
|
1356
890
|
transformedBody = { ...transformedBody, model };
|
|
1357
891
|
}
|
|
1358
892
|
else {
|
|
1359
|
-
let fallbackBody = {
|
|
1360
|
-
|
|
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
|
|
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 &&
|
|
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
|
|
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 ||
|
|
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 ??
|
|
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 &&
|
|
1433
|
-
nextWorkspace
|
|
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
|
|
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
|
|
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 &&
|
|
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 &&
|
|
1004
|
+
if (recoveryHook &&
|
|
1005
|
+
errorBody &&
|
|
1006
|
+
isRecoverableError(errorBody)) {
|
|
1459
1007
|
const errorType = detectErrorType(errorBody);
|
|
1460
1008
|
const toastContent = getRecoveryToastContent(errorType);
|
|
1461
|
-
await
|
|
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({
|
|
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
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
-
|
|
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 !==
|
|
1321
|
+
if (lastCodexCliActiveSyncIndex !==
|
|
1322
|
+
successAccountForResponse.index) {
|
|
1753
1323
|
void accountManager.syncCodexCliActiveSelectionForIndex(successAccountForResponse.index);
|
|
1754
|
-
lastCodexCliActiveSyncIndex =
|
|
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(
|
|
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
|
-
|
|
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
|
|
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) => ({
|
|
1434
|
+
accounts: loadedStorage.accounts.map((account) => ({
|
|
1435
|
+
...account,
|
|
1436
|
+
})),
|
|
2350
1437
|
activeIndexByFamily: loadedStorage.activeIndexByFamily
|
|
2351
1438
|
? { ...loadedStorage.activeIndexByFamily }
|
|
2352
1439
|
: {},
|
|
2353
1440
|
}
|
|
2354
|
-
: {
|
|
1441
|
+
: {
|
|
1442
|
+
version: 3,
|
|
1443
|
+
accounts: [],
|
|
1444
|
+
activeIndex: 0,
|
|
1445
|
+
activeIndexByFamily: {},
|
|
1446
|
+
};
|
|
2355
1447
|
const flaggedStorage = await loadFlaggedAccounts();
|
|
2356
|
-
if (workingStorage.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
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
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
|
|
1510
|
+
await runRuntimeAccountCheck(false, accountCheckDeps);
|
|
2406
1511
|
continue;
|
|
2407
1512
|
}
|
|
2408
1513
|
if (menuResult.mode === "deep-check") {
|
|
2409
|
-
await
|
|
1514
|
+
await runRuntimeAccountCheck(true, accountCheckDeps);
|
|
2410
1515
|
continue;
|
|
2411
1516
|
}
|
|
2412
1517
|
if (menuResult.mode === "verify-flagged") {
|
|
2413
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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(
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
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" &&
|
|
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
|
|
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 =
|
|
1661
|
+
resolved = resolveTokenSuccessAccount(result);
|
|
2518
1662
|
const email = extractAccountEmail(resolved.access, resolved.idToken);
|
|
2519
|
-
const accountId = resolved.accountIdOverride ??
|
|
2520
|
-
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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", {
|
|
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
|
|
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 =
|
|
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)
|
|
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([
|
|
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)
|
|
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
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
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
|
-
|
|
2174
|
+
lines.push(`Last error: ${runtimeMetrics.lastError}`);
|
|
3013
2175
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
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 =
|
|
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
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
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
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
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
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
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
|
-
|
|
3125
|
-
|
|
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
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
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
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
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,
|
|
3151
|
-
|
|
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
|
-
|
|
2369
|
+
`Removed: ${label}`,
|
|
3163
2370
|
"",
|
|
3164
|
-
formatUiItem(ui, `${getStatusMarker(ui, "ok")} Removed: ${label}`, "success"),
|
|
3165
2371
|
remaining > 0
|
|
3166
|
-
?
|
|
3167
|
-
:
|
|
2372
|
+
? `Remaining accounts: ${remaining}`
|
|
2373
|
+
: "No accounts remaining. Run: codex login",
|
|
3168
2374
|
].join("\n");
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
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
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
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
|
-
|
|
3216
|
-
|
|
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
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
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
|
-
|
|
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, "
|
|
2432
|
+
...formatUiHeader(ui, "Refresh accounts"),
|
|
3269
2433
|
"",
|
|
3270
|
-
|
|
3271
|
-
formatUiKeyValue(ui, "Error", msg, "danger"),
|
|
2434
|
+
...results.map((line) => paintUiText(ui, line, "normal")),
|
|
3272
2435
|
].join("\n");
|
|
3273
2436
|
}
|
|
3274
|
-
return
|
|
3275
|
-
}
|
|
3276
|
-
},
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
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
|
-
|
|
3293
|
-
|
|
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
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
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
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
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
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
}),
|
|
3324
|
-
} : {}),
|
|
2527
|
+
},
|
|
2528
|
+
}),
|
|
2529
|
+
}
|
|
2530
|
+
: {}),
|
|
3325
2531
|
},
|
|
3326
2532
|
};
|
|
3327
2533
|
};
|