codex-multi-auth 1.2.0 → 1.2.2
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 +929 -1712
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +10 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +312 -69
- package/dist/lib/accounts.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/circuit-breaker.d.ts +4 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +33 -1
- package/dist/lib/circuit-breaker.js.map +1 -1
- 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/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 +71 -0
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/forecast.js +371 -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 +25 -0
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/report.js +399 -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/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/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/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/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/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/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/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.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +441 -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/health.d.ts +1 -0
- package/dist/lib/health.d.ts.map +1 -1
- package/dist/lib/health.js +6 -2
- package/dist/lib/health.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/parallel-probe.d.ts +1 -1
- package/dist/lib/parallel-probe.d.ts.map +1 -1
- package/dist/lib/parallel-probe.js +5 -2
- package/dist/lib/parallel-probe.js.map +1 -1
- package/dist/lib/proactive-refresh.d.ts +1 -1
- package/dist/lib/proactive-refresh.d.ts.map +1 -1
- package/dist/lib/proactive-refresh.js +14 -5
- package/dist/lib/proactive-refresh.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/refresh-guardian.d.ts +3 -0
- package/dist/lib/refresh-guardian.d.ts.map +1 -1
- package/dist/lib/refresh-guardian.js +142 -52
- package/dist/lib/refresh-guardian.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 +77 -21
- 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/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/rotation.d.ts +14 -11
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +36 -30
- package/dist/lib/rotation.js.map +1 -1
- 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 +21 -0
- package/dist/lib/storage/identity.d.ts.map +1 -0
- package/dist/lib/storage/identity.js +59 -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,69 @@
|
|
|
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 { CodexAuthError } from "./lib/errors.js";
|
|
55
|
+
import { parseRetryAfterHintMs, sanitizeResponseHeadersForLog, } from "./lib/request/response-metadata.js";
|
|
50
56
|
import { withStreamingFailover } from "./lib/request/stream-failover.js";
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
57
|
+
import { createAbortableSleep, sleepWithCountdown, } from "./lib/request/wait-utils.js";
|
|
58
|
+
import { addJitter } from "./lib/rotation.js";
|
|
59
|
+
import { clampActiveIndices, isFlaggableFailure, } from "./lib/runtime/account-check-helpers.js";
|
|
60
|
+
import { runRuntimeAccountCheck } from "./lib/runtime/account-check.js";
|
|
61
|
+
import { createAccountCheckWorkingState } from "./lib/runtime/account-check-types.js";
|
|
62
|
+
import { invalidateRuntimeAccountManagerCache, reloadRuntimeAccountManager, } from "./lib/runtime/account-manager-cache.js";
|
|
63
|
+
import { persistAccountPoolResults, } from "./lib/runtime/account-pool.js";
|
|
64
|
+
import { resolveAccountSelection } from "./lib/runtime/account-selection.js";
|
|
65
|
+
import { formatRateLimitEntry, getRateLimitResetTimeForFamily, resolveActiveIndex, } from "./lib/runtime/account-status.js";
|
|
66
|
+
import { createPersistAccounts, runRuntimeOAuthFlow, } from "./lib/runtime/auth-facade.js";
|
|
67
|
+
import { applyAccountStorageScopeEntry } from "./lib/runtime/account-storage-scope-entry.js";
|
|
68
|
+
import { runBrowserOAuthFlow } from "./lib/runtime/browser-oauth-flow.js";
|
|
69
|
+
import { handleRuntimeEvent } from "./lib/runtime/event-handler.js";
|
|
70
|
+
import { hydrateRuntimeEmails } from "./lib/runtime/hydrate-emails.js";
|
|
71
|
+
import { buildLoginMenuAccounts } from "./lib/runtime/login-menu-accounts.js";
|
|
72
|
+
import { ensureLiveAccountSyncEntry } from "./lib/runtime/live-sync-entry.js";
|
|
73
|
+
import { applyLoaderRuntimeSetup } from "./lib/runtime/loader-setup.js";
|
|
74
|
+
import { buildManualOAuthFlow } from "./lib/runtime/manual-oauth-flow.js";
|
|
75
|
+
import { applyPreemptiveQuotaSettingsFromConfig, } from "./lib/runtime/quota-settings.js";
|
|
76
|
+
import { ensureLiveAccountSyncState, ensureRefreshGuardianState, ensureSessionAffinityState, } from "./lib/runtime/runtime-services.js";
|
|
77
|
+
import { applyAccountStorageScopeFromConfig } from "./lib/runtime/storage-scope.js";
|
|
78
|
+
import { showRuntimeToast } from "./lib/runtime/toast.js";
|
|
79
|
+
import { createRuntimeSessionRecoveryHook } from "./lib/runtime/session-recovery.js";
|
|
80
|
+
import { applyUiRuntimeFromConfig, getStatusMarker, } from "./lib/runtime/ui-runtime.js";
|
|
81
|
+
import { verifyRuntimeFlaggedAccounts } from "./lib/runtime/verify-flagged.js";
|
|
82
|
+
import { SessionAffinityStore } from "./lib/session-affinity.js";
|
|
58
83
|
import { registerCleanup } from "./lib/shutdown.js";
|
|
84
|
+
import { clearAccounts, clearFlaggedAccounts, exportAccounts, findMatchingAccountIndex, formatStorageErrorHint, getStoragePath, importAccounts, loadAccounts, loadFlaggedAccounts, StorageError, saveAccounts, saveFlaggedAccounts, setStorageBackupEnabled, setStoragePath, withAccountStorageTransaction, } from "./lib/storage.js";
|
|
85
|
+
import { buildTableHeader, buildTableRow, } from "./lib/table-formatter.js";
|
|
86
|
+
import { createHashlineEditTool, createHashlineReadTool, } from "./lib/tools/hashline-tools.js";
|
|
87
|
+
import { formatUiBadge, formatUiHeader, formatUiItem, formatUiKeyValue, formatUiSection, paintUiText, } from "./lib/ui/format.js";
|
|
88
|
+
import { setUiRuntimeOptions, } from "./lib/ui/runtime.js";
|
|
59
89
|
/**
|
|
60
90
|
* OpenAI Codex OAuth authentication plugin for Codex CLI host runtime
|
|
61
91
|
*
|
|
@@ -85,6 +115,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
85
115
|
let liveAccountSyncPath = null;
|
|
86
116
|
let refreshGuardian = null;
|
|
87
117
|
let refreshGuardianConfigKey = null;
|
|
118
|
+
let refreshGuardianCleanupRegistered = false;
|
|
88
119
|
let sessionAffinityStore = new SessionAffinityStore();
|
|
89
120
|
let sessionAffinityConfigKey = null;
|
|
90
121
|
const entitlementCache = new EntitlementCache();
|
|
@@ -103,80 +134,6 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
103
134
|
balanced: 15_000,
|
|
104
135
|
conservative: 20_000,
|
|
105
136
|
};
|
|
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
137
|
const runtimeMetrics = {
|
|
181
138
|
startedAt: Date.now(),
|
|
182
139
|
totalRequests: 0,
|
|
@@ -197,557 +154,145 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
197
154
|
lastRequestAt: null,
|
|
198
155
|
lastError: null,
|
|
199
156
|
};
|
|
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
|
-
};
|
|
157
|
+
const resolveTokenSuccessAccount = (tokens) => resolveAccountSelection(tokens, {
|
|
158
|
+
envAccountId: process.env.CODEX_AUTH_ACCOUNT_ID,
|
|
159
|
+
logInfo,
|
|
160
|
+
getAccountIdCandidates,
|
|
161
|
+
selectBestAccountCandidate,
|
|
162
|
+
});
|
|
163
|
+
const persistAccountPool = createPersistAccounts({
|
|
164
|
+
persistAccountPoolResults,
|
|
165
|
+
withAccountStorageTransaction,
|
|
166
|
+
extractAccountId,
|
|
167
|
+
extractAccountEmail,
|
|
168
|
+
sanitizeEmail,
|
|
169
|
+
findMatchingAccountIndex,
|
|
170
|
+
modelFamilies: MODEL_FAMILIES,
|
|
171
|
+
});
|
|
172
|
+
const accountManagerCacheInvalidationDeps = {
|
|
173
|
+
setCachedAccountManager: (value) => {
|
|
174
|
+
cachedAccountManager = value;
|
|
175
|
+
},
|
|
176
|
+
setAccountManagerPromise: (value) => {
|
|
177
|
+
accountManagerPromise = value;
|
|
178
|
+
},
|
|
247
179
|
};
|
|
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;
|
|
180
|
+
const makeReloadAccountManagerDeps = (authFallback) => ({
|
|
181
|
+
currentReloadInFlight: accountReloadInFlight,
|
|
182
|
+
loadFromDisk: (fallback) => AccountManager.loadFromDisk(fallback),
|
|
183
|
+
setCachedAccountManager: (value) => {
|
|
184
|
+
cachedAccountManager = value;
|
|
264
185
|
},
|
|
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" };
|
|
186
|
+
setAccountManagerPromise: (value) => {
|
|
187
|
+
accountManagerPromise = value;
|
|
188
|
+
},
|
|
189
|
+
setReloadInFlight: (value) => {
|
|
190
|
+
accountReloadInFlight = value;
|
|
292
191
|
},
|
|
192
|
+
authFallback,
|
|
193
|
+
});
|
|
194
|
+
const reloadAccountManagerFromDisk = async (authFallback) => reloadRuntimeAccountManager(makeReloadAccountManagerDeps(authFallback));
|
|
195
|
+
const applyAccountStorageScope = (pluginConfig) => applyAccountStorageScopeEntry({
|
|
196
|
+
pluginConfig,
|
|
197
|
+
getPerProjectAccounts,
|
|
198
|
+
getStorageBackupEnabled,
|
|
199
|
+
setStorageBackupEnabled,
|
|
200
|
+
isCodexCliSyncEnabled,
|
|
201
|
+
getWarningShown: () => perProjectStorageWarningShown,
|
|
202
|
+
setWarningShown: (shown) => {
|
|
203
|
+
perProjectStorageWarningShown = shown;
|
|
204
|
+
},
|
|
205
|
+
logWarn,
|
|
206
|
+
pluginName: PLUGIN_NAME,
|
|
207
|
+
setStoragePath,
|
|
208
|
+
cwd: () => process.cwd(),
|
|
209
|
+
applyAccountStorageScopeFromConfig,
|
|
293
210
|
});
|
|
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
211
|
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);
|
|
212
|
+
const next = await ensureLiveAccountSyncEntry({
|
|
213
|
+
pluginConfig,
|
|
214
|
+
authFallback,
|
|
215
|
+
currentSync: liveAccountSync,
|
|
216
|
+
currentPath: liveAccountSyncPath,
|
|
217
|
+
getLiveAccountSync,
|
|
218
|
+
getStoragePath,
|
|
219
|
+
createSync: (oauthFallback) => new LiveAccountSync(async () => {
|
|
220
|
+
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(oauthFallback));
|
|
625
221
|
}, {
|
|
626
222
|
debounceMs: getLiveAccountSyncDebounceMs(pluginConfig),
|
|
627
223
|
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
|
-
}
|
|
224
|
+
}),
|
|
225
|
+
registerCleanup,
|
|
226
|
+
logWarn,
|
|
227
|
+
pluginName: PLUGIN_NAME,
|
|
228
|
+
ensureLiveAccountSyncState,
|
|
229
|
+
});
|
|
230
|
+
liveAccountSync = next.liveAccountSync;
|
|
231
|
+
liveAccountSyncPath = next.liveAccountSyncPath;
|
|
654
232
|
};
|
|
655
233
|
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();
|
|
234
|
+
const next = ensureRefreshGuardianState({
|
|
235
|
+
enabled: getProactiveRefreshGuardian(pluginConfig),
|
|
236
|
+
intervalMs: getProactiveRefreshIntervalMs(pluginConfig),
|
|
237
|
+
bufferMs: getProactiveRefreshBufferMs(pluginConfig),
|
|
238
|
+
currentGuardian: refreshGuardian,
|
|
239
|
+
currentConfigKey: refreshGuardianConfigKey,
|
|
240
|
+
currentCleanupRegistered: refreshGuardianCleanupRegistered,
|
|
241
|
+
getCurrentGuardian: () => refreshGuardian,
|
|
242
|
+
createGuardian: ({ intervalMs, bufferMs }) => new RefreshGuardian(() => cachedAccountManager, {
|
|
243
|
+
intervalMs,
|
|
244
|
+
bufferMs,
|
|
245
|
+
}),
|
|
246
|
+
registerCleanup,
|
|
677
247
|
});
|
|
248
|
+
refreshGuardian = next.refreshGuardian;
|
|
249
|
+
refreshGuardianConfigKey = next.refreshGuardianConfigKey;
|
|
250
|
+
refreshGuardianCleanupRegistered =
|
|
251
|
+
next.refreshGuardianCleanupRegistered;
|
|
678
252
|
};
|
|
679
253
|
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),
|
|
254
|
+
const next = ensureSessionAffinityState({
|
|
255
|
+
enabled: getSessionAffinity(pluginConfig) ||
|
|
256
|
+
getResponseContinuation(pluginConfig),
|
|
257
|
+
ttlMs: getSessionAffinityTtlMs(pluginConfig),
|
|
258
|
+
maxEntries: getSessionAffinityMaxEntries(pluginConfig),
|
|
259
|
+
currentStore: sessionAffinityStore,
|
|
260
|
+
currentConfigKey: sessionAffinityConfigKey,
|
|
261
|
+
createStore: ({ ttlMs, maxEntries }) => new SessionAffinityStore({ ttlMs, maxEntries }),
|
|
699
262
|
});
|
|
263
|
+
sessionAffinityStore = next.sessionAffinityStore;
|
|
264
|
+
sessionAffinityConfigKey = next.sessionAffinityConfigKey;
|
|
700
265
|
};
|
|
266
|
+
const applyPreemptiveQuotaSettings = (pluginConfig) => applyPreemptiveQuotaSettingsFromConfig(pluginConfig, {
|
|
267
|
+
configure: (options) => preemptiveQuotaScheduler.configure(options),
|
|
268
|
+
getPreemptiveQuotaEnabled,
|
|
269
|
+
getPreemptiveQuotaRemainingPercent5h,
|
|
270
|
+
getPreemptiveQuotaRemainingPercent7d,
|
|
271
|
+
getPreemptiveQuotaMaxDeferralMs,
|
|
272
|
+
});
|
|
701
273
|
// 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
|
-
};
|
|
274
|
+
const eventHandler = async (input) => handleRuntimeEvent({
|
|
275
|
+
input,
|
|
276
|
+
providerId: PROVIDER_ID,
|
|
277
|
+
modelFamilies: MODEL_FAMILIES,
|
|
278
|
+
loadAccounts,
|
|
279
|
+
saveAccounts,
|
|
280
|
+
hasCachedAccountManager: () => !!cachedAccountManager,
|
|
281
|
+
syncCodexCliActiveSelectionForIndex: async (index) => {
|
|
282
|
+
if (!cachedAccountManager)
|
|
283
|
+
return;
|
|
284
|
+
await cachedAccountManager.syncCodexCliActiveSelectionForIndex(index);
|
|
285
|
+
},
|
|
286
|
+
setLastCodexCliActiveSyncIndex: (index) => {
|
|
287
|
+
lastCodexCliActiveSyncIndex = index;
|
|
288
|
+
},
|
|
289
|
+
reloadAccountManagerFromDisk: () => reloadAccountManagerFromDisk(),
|
|
290
|
+
showToast: (message, variant) => showRuntimeToast(client, message, variant),
|
|
291
|
+
logDebug,
|
|
292
|
+
pluginName: PLUGIN_NAME,
|
|
293
|
+
});
|
|
749
294
|
// Initialize runtime UI settings once on plugin load; auth/tools refresh this dynamically.
|
|
750
|
-
|
|
295
|
+
applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
751
296
|
return {
|
|
752
297
|
event: eventHandler,
|
|
753
298
|
auth: {
|
|
@@ -756,11 +301,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
756
301
|
* Loader function that configures OAuth authentication and request handling
|
|
757
302
|
*
|
|
758
303
|
* This function:
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
304
|
+
* 1. Validates OAuth authentication
|
|
305
|
+
* 2. Loads multi-account pool from disk (fallback to current auth)
|
|
306
|
+
* 3. Loads user configuration from runtime model config
|
|
307
|
+
* 4. Fetches Codex system instructions from GitHub (cached)
|
|
308
|
+
* 5. Returns SDK configuration with custom fetch implementation
|
|
764
309
|
*
|
|
765
310
|
* @param getAuth - Function to retrieve current auth state
|
|
766
311
|
* @param provider - Provider configuration from runtime model config
|
|
@@ -769,11 +314,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
769
314
|
async loader(getAuth, provider) {
|
|
770
315
|
const auth = await getAuth();
|
|
771
316
|
const pluginConfig = loadPluginConfig();
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
317
|
+
applyLoaderRuntimeSetup({
|
|
318
|
+
pluginConfig,
|
|
319
|
+
applyUiRuntimeFromConfig: (config) => applyUiRuntimeFromConfig(config, setUiRuntimeOptions),
|
|
320
|
+
applyAccountStorageScope,
|
|
321
|
+
ensureSessionAffinity,
|
|
322
|
+
ensureRefreshGuardian,
|
|
323
|
+
applyPreemptiveQuotaSettings,
|
|
324
|
+
});
|
|
777
325
|
// Only handle OAuth auth type, skip API key auth
|
|
778
326
|
if (auth.type !== "oauth") {
|
|
779
327
|
return {};
|
|
@@ -797,15 +345,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
797
345
|
try {
|
|
798
346
|
await ensureLiveAccountSync(pluginConfig, auth);
|
|
799
347
|
if (!accountManagerPromise) {
|
|
800
|
-
await
|
|
348
|
+
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
|
|
801
349
|
}
|
|
802
350
|
const managerPromise = accountManagerPromise ??
|
|
803
|
-
|
|
351
|
+
reloadRuntimeAccountManager(makeReloadAccountManagerDeps(auth));
|
|
804
352
|
let accountManager = await managerPromise;
|
|
805
353
|
cachedAccountManager = accountManager;
|
|
806
354
|
const refreshToken = auth.type === "oauth" ? auth.refresh : "";
|
|
807
|
-
const needsPersist = refreshToken &&
|
|
808
|
-
!accountManager.hasRefreshToken(refreshToken);
|
|
355
|
+
const needsPersist = refreshToken && !accountManager.hasRefreshToken(refreshToken);
|
|
809
356
|
if (needsPersist) {
|
|
810
357
|
await accountManager.saveToDisk();
|
|
811
358
|
}
|
|
@@ -846,7 +393,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
846
393
|
STREAM_FAILOVER_SOFT_TIMEOUT_BY_MODE[failoverMode]);
|
|
847
394
|
const streamFailoverHardTimeoutMs = Math.max(streamFailoverSoftTimeoutMs, parseEnvInt(process.env.CODEX_AUTH_STREAM_STALL_HARD_TIMEOUT_MS) ??
|
|
848
395
|
streamStallTimeoutMs);
|
|
849
|
-
const maxSameAccountRetries = failoverMode === "conservative"
|
|
396
|
+
const maxSameAccountRetries = failoverMode === "conservative"
|
|
397
|
+
? 2
|
|
398
|
+
: failoverMode === "balanced"
|
|
399
|
+
? 1
|
|
400
|
+
: 0;
|
|
850
401
|
const sessionRecoveryEnabled = getSessionRecovery(pluginConfig);
|
|
851
402
|
const autoResumeEnabled = getAutoResume(pluginConfig);
|
|
852
403
|
const emptyResponseMaxRetries = getEmptyResponseMaxRetries(pluginConfig);
|
|
@@ -875,11 +426,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
875
426
|
prewarmHostCodexPrompt();
|
|
876
427
|
}
|
|
877
428
|
}
|
|
878
|
-
const recoveryHook =
|
|
879
|
-
|
|
880
|
-
|
|
429
|
+
const recoveryHook = createRuntimeSessionRecoveryHook({
|
|
430
|
+
enabled: sessionRecoveryEnabled,
|
|
431
|
+
client,
|
|
432
|
+
directory: process.cwd(),
|
|
433
|
+
autoResume: autoResumeEnabled,
|
|
434
|
+
});
|
|
881
435
|
checkAndNotify(async (message, variant) => {
|
|
882
|
-
await
|
|
436
|
+
await showRuntimeToast(client, message, variant);
|
|
883
437
|
}).catch((err) => {
|
|
884
438
|
logDebug(`Update check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
885
439
|
});
|
|
@@ -904,7 +458,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
904
458
|
*/
|
|
905
459
|
async fetch(input, init) {
|
|
906
460
|
try {
|
|
907
|
-
if (cachedAccountManager &&
|
|
461
|
+
if (cachedAccountManager &&
|
|
462
|
+
cachedAccountManager !== accountManager) {
|
|
908
463
|
accountManager = cachedAccountManager;
|
|
909
464
|
}
|
|
910
465
|
// Step 1: Extract and rewrite URL for Codex backend
|
|
@@ -914,118 +469,58 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
914
469
|
// Instructions are fetched per model family (codex-max, codex, gpt-5.1)
|
|
915
470
|
// Capture original stream value before transformation
|
|
916
471
|
// 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
472
|
const baseInit = await normalizeRequestInit(input, init);
|
|
967
|
-
const originalBody = await parseRequestBodyFromInit(baseInit?.body);
|
|
473
|
+
const originalBody = await parseRequestBodyFromInit(baseInit?.body, logWarn);
|
|
968
474
|
const isStreaming = originalBody.stream === true;
|
|
969
|
-
const parsedBody = Object.keys(originalBody).length > 0
|
|
475
|
+
const parsedBody = Object.keys(originalBody).length > 0
|
|
476
|
+
? { ...originalBody }
|
|
477
|
+
: undefined;
|
|
970
478
|
const transformation = await transformRequestForCodex(baseInit, url, effectiveUserConfig, codexMode, parsedBody, {
|
|
971
479
|
fastSession: fastSessionEnabled,
|
|
972
480
|
fastSessionStrategy,
|
|
973
481
|
fastSessionMaxInputItems,
|
|
482
|
+
deferFastSessionInputTrimming: fastSessionEnabled,
|
|
483
|
+
allowBackgroundResponses: getBackgroundResponses(pluginConfig),
|
|
974
484
|
});
|
|
975
485
|
let requestInit = transformation?.updatedInit ?? baseInit;
|
|
976
486
|
let transformedBody = transformation?.body;
|
|
487
|
+
let pendingFastSessionInputTrim = transformation?.deferredFastSessionInputTrim;
|
|
977
488
|
const promptCacheKey = transformedBody?.prompt_cache_key;
|
|
978
489
|
let model = transformedBody?.model;
|
|
979
490
|
let modelFamily = model ? getModelFamily(model) : "gpt-5.1";
|
|
980
491
|
let quotaKey = model ? `${modelFamily}:${model}` : modelFamily;
|
|
492
|
+
const responseContinuationEnabled = getResponseContinuation(pluginConfig);
|
|
981
493
|
const threadIdCandidate = (process.env.CODEX_THREAD_ID ?? promptCacheKey ?? "")
|
|
982
494
|
.toString()
|
|
983
495
|
.trim() || undefined;
|
|
984
496
|
const sessionAffinityKey = threadIdCandidate ?? promptCacheKey ?? null;
|
|
985
|
-
const effectivePromptCacheKey = (sessionAffinityKey ?? promptCacheKey ?? "")
|
|
497
|
+
const effectivePromptCacheKey = (sessionAffinityKey ?? promptCacheKey ?? "")
|
|
498
|
+
.toString()
|
|
499
|
+
.trim() || undefined;
|
|
500
|
+
const shouldUseResponseContinuation = Boolean(transformedBody) &&
|
|
501
|
+
responseContinuationEnabled &&
|
|
502
|
+
!transformedBody?.previous_response_id;
|
|
503
|
+
if (shouldUseResponseContinuation && transformedBody) {
|
|
504
|
+
const lastResponseId = sessionAffinityStore?.getLastResponseId(sessionAffinityKey);
|
|
505
|
+
if (lastResponseId) {
|
|
506
|
+
transformedBody = {
|
|
507
|
+
...transformedBody,
|
|
508
|
+
previous_response_id: lastResponseId,
|
|
509
|
+
};
|
|
510
|
+
requestInit = {
|
|
511
|
+
...requestInit,
|
|
512
|
+
body: JSON.stringify(transformedBody),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
986
516
|
const preferredSessionAccountIndex = sessionAffinityStore?.getPreferredAccountIndex(sessionAffinityKey);
|
|
987
517
|
sessionAffinityStore?.prune();
|
|
988
|
-
const requestCorrelationId = setCorrelationId(threadIdCandidate
|
|
518
|
+
const requestCorrelationId = setCorrelationId(threadIdCandidate
|
|
519
|
+
? `${threadIdCandidate}:${Date.now()}`
|
|
520
|
+
: undefined);
|
|
989
521
|
runtimeMetrics.lastRequestAt = Date.now();
|
|
990
522
|
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
|
-
};
|
|
523
|
+
const sleep = createAbortableSleep(abortSignal);
|
|
1029
524
|
let allRateLimitedRetries = 0;
|
|
1030
525
|
let emptyResponseRetries = 0;
|
|
1031
526
|
const attemptedUnsupportedFallbackModels = new Set();
|
|
@@ -1040,11 +535,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1040
535
|
let usedPreferredSessionAccount = false;
|
|
1041
536
|
const capabilityBoostByAccount = {};
|
|
1042
537
|
const accountSnapshotSource = accountManager;
|
|
1043
|
-
const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot ===
|
|
1044
|
-
|
|
538
|
+
const accountSnapshotList = typeof accountSnapshotSource.getAccountsSnapshot ===
|
|
539
|
+
"function"
|
|
540
|
+
? (accountSnapshotSource.getAccountsSnapshot() ?? [])
|
|
1045
541
|
: [];
|
|
1046
542
|
if (accountSnapshotList.length === 0 &&
|
|
1047
|
-
typeof accountSnapshotSource.getAccountByIndex ===
|
|
543
|
+
typeof accountSnapshotSource.getAccountByIndex ===
|
|
544
|
+
"function") {
|
|
1048
545
|
for (let accountSnapshotIndex = 0; accountSnapshotIndex < accountCount; accountSnapshotIndex += 1) {
|
|
1049
546
|
const candidate = accountSnapshotSource.getAccountByIndex(accountSnapshotIndex);
|
|
1050
547
|
if (candidate) {
|
|
@@ -1054,7 +551,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1054
551
|
}
|
|
1055
552
|
for (const candidate of accountSnapshotList) {
|
|
1056
553
|
const accountKey = resolveEntitlementAccountKey(candidate);
|
|
1057
|
-
capabilityBoostByAccount[candidate.index] =
|
|
554
|
+
capabilityBoostByAccount[candidate.index] =
|
|
555
|
+
capabilityPolicyStore.getBoost(accountKey, model ?? modelFamily);
|
|
1058
556
|
}
|
|
1059
557
|
accountAttemptLoop: while (attempted.size < Math.max(1, accountCount)) {
|
|
1060
558
|
let account = null;
|
|
@@ -1088,9 +586,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1088
586
|
try {
|
|
1089
587
|
if (shouldRefreshToken(accountAuth, tokenRefreshSkewMs)) {
|
|
1090
588
|
accountAuth = (await refreshAndUpdateToken(accountAuth, client));
|
|
1091
|
-
accountManager.
|
|
1092
|
-
accountManager.clearAuthFailures(account);
|
|
1093
|
-
accountManager.saveToDiskDebounced();
|
|
589
|
+
await accountManager.commitRefreshedAuth(account, accountAuth);
|
|
1094
590
|
}
|
|
1095
591
|
}
|
|
1096
592
|
catch (err) {
|
|
@@ -1098,21 +594,36 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1098
594
|
runtimeMetrics.authRefreshFailures++;
|
|
1099
595
|
runtimeMetrics.failedRequests++;
|
|
1100
596
|
runtimeMetrics.accountRotations++;
|
|
1101
|
-
runtimeMetrics.lastError =
|
|
1102
|
-
|
|
597
|
+
runtimeMetrics.lastError =
|
|
598
|
+
err?.message ?? String(err);
|
|
599
|
+
const isRetryableRefreshError = err instanceof CodexAuthError && err.retryable;
|
|
1103
600
|
const accountLabel = formatAccountLabel(account, account.index);
|
|
601
|
+
sessionAffinityStore?.forgetSession(sessionAffinityKey);
|
|
602
|
+
if (isRetryableRefreshError) {
|
|
603
|
+
runtimeMetrics.networkErrors++;
|
|
604
|
+
const retryableRefreshPolicy = evaluateFailurePolicy({ kind: "network", failoverMode }, { networkCooldownMs: networkErrorCooldownMs });
|
|
605
|
+
if (retryableRefreshPolicy.recordFailure) {
|
|
606
|
+
accountManager.recordFailure(account, modelFamily, model);
|
|
607
|
+
}
|
|
608
|
+
if (typeof retryableRefreshPolicy.cooldownMs === "number" &&
|
|
609
|
+
retryableRefreshPolicy.cooldownReason) {
|
|
610
|
+
accountManager.markAccountCoolingDown(account, retryableRefreshPolicy.cooldownMs, retryableRefreshPolicy.cooldownReason);
|
|
611
|
+
accountManager.saveToDiskDebounced();
|
|
612
|
+
}
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
const failures = accountManager.incrementAuthFailures(account);
|
|
1104
616
|
const authFailurePolicy = evaluateFailurePolicy({
|
|
1105
617
|
kind: "auth-refresh",
|
|
1106
618
|
consecutiveAuthFailures: failures,
|
|
1107
619
|
});
|
|
1108
|
-
sessionAffinityStore?.forgetSession(sessionAffinityKey);
|
|
1109
620
|
if (authFailurePolicy.removeAccount) {
|
|
1110
621
|
const removedIndex = account.index;
|
|
1111
622
|
sessionAffinityStore?.forgetAccount(removedIndex);
|
|
1112
623
|
accountManager.removeAccount(account);
|
|
1113
624
|
sessionAffinityStore?.reindexAfterRemoval(removedIndex);
|
|
1114
625
|
accountManager.saveToDiskDebounced();
|
|
1115
|
-
await
|
|
626
|
+
await showRuntimeToast(client, `Removed ${accountLabel} after ${failures} consecutive auth failures. Run 'codex login' to re-add.`, "error", { duration: toastDurationMs * 2 });
|
|
1116
627
|
continue;
|
|
1117
628
|
}
|
|
1118
629
|
if (typeof authFailurePolicy.cooldownMs === "number" &&
|
|
@@ -1158,8 +669,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1158
669
|
continue;
|
|
1159
670
|
}
|
|
1160
671
|
account.accountId = accountId;
|
|
1161
|
-
if (!hadAccountId &&
|
|
1162
|
-
|
|
672
|
+
if (!hadAccountId &&
|
|
673
|
+
tokenAccountId &&
|
|
674
|
+
accountId === tokenAccountId) {
|
|
675
|
+
account.accountIdSource =
|
|
676
|
+
storedAccountIdSource ?? "token";
|
|
1163
677
|
}
|
|
1164
678
|
if (resolvedEmail) {
|
|
1165
679
|
account.email = resolvedEmail;
|
|
@@ -1167,13 +681,38 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1167
681
|
if (accountCount > 1 &&
|
|
1168
682
|
accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
|
|
1169
683
|
const accountLabel = formatAccountLabel(account, account.index);
|
|
1170
|
-
await
|
|
684
|
+
await showRuntimeToast(client, `Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
|
|
1171
685
|
accountManager.markToastShown(account.index);
|
|
1172
686
|
}
|
|
1173
687
|
const headers = createCodexHeaders(requestInit, accountId, accountAuth.access, {
|
|
1174
688
|
model,
|
|
1175
689
|
promptCacheKey: effectivePromptCacheKey,
|
|
1176
690
|
});
|
|
691
|
+
if (transformedBody && pendingFastSessionInputTrim) {
|
|
692
|
+
const activeFastSessionInputTrim = pendingFastSessionInputTrim;
|
|
693
|
+
pendingFastSessionInputTrim = undefined;
|
|
694
|
+
const compactionResult = await applyResponseCompaction({
|
|
695
|
+
body: transformedBody,
|
|
696
|
+
requestUrl: url,
|
|
697
|
+
headers,
|
|
698
|
+
trim: activeFastSessionInputTrim,
|
|
699
|
+
fetchImpl: async (requestUrl, requestInit) => {
|
|
700
|
+
const normalizedCompactionUrl = typeof requestUrl === "string"
|
|
701
|
+
? requestUrl
|
|
702
|
+
: String(requestUrl);
|
|
703
|
+
return fetch(normalizedCompactionUrl, applyProxyCompatibleInit(normalizedCompactionUrl, requestInit));
|
|
704
|
+
},
|
|
705
|
+
signal: abortSignal,
|
|
706
|
+
timeoutMs: Math.min(fetchTimeoutMs, 4_000),
|
|
707
|
+
});
|
|
708
|
+
if (compactionResult.mode !== "unchanged") {
|
|
709
|
+
transformedBody = compactionResult.body;
|
|
710
|
+
requestInit = {
|
|
711
|
+
...(requestInit ?? {}),
|
|
712
|
+
body: JSON.stringify(transformedBody),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
1177
716
|
const quotaScheduleKey = `${entitlementAccountKey}:${model ?? modelFamily}`;
|
|
1178
717
|
const capabilityModelKey = model ?? modelFamily;
|
|
1179
718
|
const quotaDeferral = preemptiveQuotaScheduler.getDeferral(quotaScheduleKey);
|
|
@@ -1190,8 +729,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1190
729
|
if (!tokenConsumed) {
|
|
1191
730
|
accountManager.recordRateLimit(account, modelFamily, model);
|
|
1192
731
|
runtimeMetrics.accountRotations++;
|
|
1193
|
-
runtimeMetrics.lastError =
|
|
1194
|
-
`Local token bucket depleted for account ${account.index + 1} (${modelFamily}${model ? `:${model}` : ""})`;
|
|
732
|
+
runtimeMetrics.lastError = `Local token bucket depleted for account ${account.index + 1} (${modelFamily}${model ? `:${model}` : ""})`;
|
|
1195
733
|
logWarn(`Skipping account ${account.index + 1}: local token bucket depleted for ${modelFamily}${model ? `:${model}` : ""}`);
|
|
1196
734
|
continue;
|
|
1197
735
|
}
|
|
@@ -1211,14 +749,17 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1211
749
|
fetchController.abort(timeoutReason);
|
|
1212
750
|
}, requestTimeoutMs);
|
|
1213
751
|
const onUserAbort = abortSignal
|
|
1214
|
-
? () => fetchController.abort(abortSignal.reason ??
|
|
752
|
+
? () => fetchController.abort(abortSignal.reason ??
|
|
753
|
+
new Error("Aborted by user"))
|
|
1215
754
|
: null;
|
|
1216
755
|
if (abortSignal?.aborted) {
|
|
1217
756
|
clearTimeout(fetchTimeoutId);
|
|
1218
757
|
fetchController.abort(abortSignal.reason ?? new Error("Aborted by user"));
|
|
1219
758
|
}
|
|
1220
759
|
else if (abortSignal && onUserAbort) {
|
|
1221
|
-
abortSignal.addEventListener("abort", onUserAbort, {
|
|
760
|
+
abortSignal.addEventListener("abort", onUserAbort, {
|
|
761
|
+
once: true,
|
|
762
|
+
});
|
|
1222
763
|
}
|
|
1223
764
|
try {
|
|
1224
765
|
runtimeMetrics.totalRequests++;
|
|
@@ -1239,11 +780,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1239
780
|
runtimeMetrics.userAborts++;
|
|
1240
781
|
runtimeMetrics.lastError = "request aborted by user";
|
|
1241
782
|
sessionAffinityStore?.forgetSession(sessionAffinityKey);
|
|
1242
|
-
throw
|
|
783
|
+
throw fetchAbortReason instanceof Error
|
|
1243
784
|
? fetchAbortReason
|
|
1244
|
-
: new Error("Aborted by user")
|
|
785
|
+
: new Error("Aborted by user");
|
|
1245
786
|
}
|
|
1246
|
-
const errorMsg = networkError instanceof Error
|
|
787
|
+
const errorMsg = networkError instanceof Error
|
|
788
|
+
? networkError.message
|
|
789
|
+
: String(networkError);
|
|
1247
790
|
logWarn(`Network error for account ${account.index + 1}: ${errorMsg}`);
|
|
1248
791
|
runtimeMetrics.failedRequests++;
|
|
1249
792
|
runtimeMetrics.networkErrors++;
|
|
@@ -1296,18 +839,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1296
839
|
if (contextOverflowResult.handled) {
|
|
1297
840
|
return contextOverflowResult.response;
|
|
1298
841
|
}
|
|
1299
|
-
const { response: errorResponse, rateLimit, errorBody } = await handleErrorResponse(response, {
|
|
842
|
+
const { response: errorResponse, rateLimit, errorBody, } = await handleErrorResponse(response, {
|
|
1300
843
|
requestCorrelationId,
|
|
1301
844
|
threadId: threadIdCandidate,
|
|
1302
845
|
});
|
|
1303
846
|
const unsupportedModelInfo = getUnsupportedCodexModelInfo(errorBody);
|
|
1304
847
|
const hasRemainingAccounts = attempted.size < Math.max(1, accountCount);
|
|
1305
|
-
const blockedModel = unsupportedModelInfo.unsupportedModel ??
|
|
848
|
+
const blockedModel = unsupportedModelInfo.unsupportedModel ??
|
|
849
|
+
model ??
|
|
850
|
+
"requested model";
|
|
1306
851
|
const blockedModelNormalized = blockedModel.toLowerCase();
|
|
1307
852
|
const shouldForceSparkFallback = unsupportedModelInfo.isUnsupported &&
|
|
1308
853
|
(blockedModelNormalized === "gpt-5.3-codex-spark" ||
|
|
1309
854
|
blockedModelNormalized.includes("gpt-5.3-codex-spark"));
|
|
1310
|
-
const allowUnsupportedFallback = fallbackOnUnsupportedCodexModel ||
|
|
855
|
+
const allowUnsupportedFallback = fallbackOnUnsupportedCodexModel ||
|
|
856
|
+
shouldForceSparkFallback;
|
|
1311
857
|
// Entitlements can differ by account/workspace, so try remaining
|
|
1312
858
|
// accounts before degrading the model via fallback.
|
|
1313
859
|
// Spark entitlement is commonly unavailable on non-Pro/Business workspaces;
|
|
@@ -1352,12 +898,16 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1352
898
|
model = fallbackModel;
|
|
1353
899
|
modelFamily = getModelFamily(model);
|
|
1354
900
|
quotaKey = `${modelFamily}:${model}`;
|
|
1355
|
-
if (transformedBody &&
|
|
901
|
+
if (transformedBody &&
|
|
902
|
+
typeof transformedBody === "object") {
|
|
1356
903
|
transformedBody = { ...transformedBody, model };
|
|
1357
904
|
}
|
|
1358
905
|
else {
|
|
1359
|
-
let fallbackBody = {
|
|
1360
|
-
|
|
906
|
+
let fallbackBody = {
|
|
907
|
+
model,
|
|
908
|
+
};
|
|
909
|
+
if (requestInit?.body &&
|
|
910
|
+
typeof requestInit.body === "string") {
|
|
1361
911
|
try {
|
|
1362
912
|
const parsed = JSON.parse(requestInit.body);
|
|
1363
913
|
fallbackBody = { ...parsed, model };
|
|
@@ -1380,11 +930,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1380
930
|
fallbackApplied: true,
|
|
1381
931
|
fallbackReason: "unsupported-model-entitlement",
|
|
1382
932
|
});
|
|
1383
|
-
await
|
|
933
|
+
await showRuntimeToast(client, `Model ${previousModel} is not available for this account. Retrying with ${model}.`, "warning", { duration: toastDurationMs });
|
|
1384
934
|
restartAccountTraversalWithFallback = true;
|
|
1385
935
|
break;
|
|
1386
936
|
}
|
|
1387
|
-
if (unsupportedModelInfo.isUnsupported &&
|
|
937
|
+
if (unsupportedModelInfo.isUnsupported &&
|
|
938
|
+
!allowUnsupportedFallback) {
|
|
1388
939
|
entitlementCache.markBlocked(entitlementAccountKey, blockedModel, "unsupported-model");
|
|
1389
940
|
capabilityPolicyStore.recordUnsupported(entitlementAccountKey, blockedModel);
|
|
1390
941
|
runtimeMetrics.lastError = `Unsupported model (strict): ${blockedModel}`;
|
|
@@ -1395,7 +946,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1395
946
|
fallbackApplied: false,
|
|
1396
947
|
fallbackReason: "unsupported-model-entitlement",
|
|
1397
948
|
});
|
|
1398
|
-
await
|
|
949
|
+
await showRuntimeToast(client, `Model ${blockedModel} is not available for this account. Strict policy blocked automatic fallback.`, "warning", { duration: toastDurationMs });
|
|
1399
950
|
}
|
|
1400
951
|
if (unsupportedModelInfo.isUnsupported &&
|
|
1401
952
|
allowUnsupportedFallback &&
|
|
@@ -1412,7 +963,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1412
963
|
if (isDisabledWorkspaceError) {
|
|
1413
964
|
runtimeMetrics.failedRequests++;
|
|
1414
965
|
runtimeMetrics.lastError = `Workspace disabled for account ${account.index + 1}`;
|
|
1415
|
-
if (!account.workspaces ||
|
|
966
|
+
if (!account.workspaces ||
|
|
967
|
+
account.workspaces.length === 0) {
|
|
1416
968
|
logWarn(`Workspace disabled/expired for account ${account.index + 1} without tracked workspaces. Leaving account enabled.`, { errorCode: workspaceErrorCode });
|
|
1417
969
|
if (hasRemainingAccounts) {
|
|
1418
970
|
continue accountAttemptLoop;
|
|
@@ -1421,7 +973,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1421
973
|
}
|
|
1422
974
|
else {
|
|
1423
975
|
const currentWorkspace = accountManager.getCurrentWorkspace(account);
|
|
1424
|
-
const workspaceName = currentWorkspace?.name ??
|
|
976
|
+
const workspaceName = currentWorkspace?.name ??
|
|
977
|
+
currentWorkspace?.id ??
|
|
978
|
+
"unknown";
|
|
1425
979
|
logWarn(`Workspace disabled/expired for account ${account.index + 1} - workspace: ${workspaceName}. Rotating to next workspace.`, { errorCode: workspaceErrorCode });
|
|
1426
980
|
const disabledWorkspace = currentWorkspace
|
|
1427
981
|
? accountManager.disableCurrentWorkspace(account, currentWorkspace.id)
|
|
@@ -1429,13 +983,16 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1429
983
|
let nextWorkspace = disabledWorkspace
|
|
1430
984
|
? accountManager.rotateToNextWorkspace(account)
|
|
1431
985
|
: accountManager.getCurrentWorkspace(account);
|
|
1432
|
-
if (!disabledWorkspace &&
|
|
1433
|
-
nextWorkspace
|
|
986
|
+
if (!disabledWorkspace &&
|
|
987
|
+
(!nextWorkspace ||
|
|
988
|
+
nextWorkspace.enabled === false)) {
|
|
989
|
+
nextWorkspace =
|
|
990
|
+
accountManager.rotateToNextWorkspace(account);
|
|
1434
991
|
}
|
|
1435
992
|
if (nextWorkspace) {
|
|
1436
993
|
accountManager.saveToDiskDebounced();
|
|
1437
994
|
const newWorkspaceName = nextWorkspace.name ?? nextWorkspace.id;
|
|
1438
|
-
await
|
|
995
|
+
await showRuntimeToast(client, `Workspace ${workspaceName} disabled. Switched to ${newWorkspaceName}.`, "warning", { duration: toastDurationMs });
|
|
1439
996
|
logInfo(`Rotated to workspace ${newWorkspaceName} for account ${account.index + 1}`);
|
|
1440
997
|
// Allow the same account to be selected again with fresh request state.
|
|
1441
998
|
attempted.delete(account.index);
|
|
@@ -1444,21 +1001,25 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1444
1001
|
logWarn(`All workspaces disabled for account ${account.index + 1}. Disabling account.`);
|
|
1445
1002
|
accountManager.setAccountEnabled(account.index, false);
|
|
1446
1003
|
accountManager.saveToDiskDebounced();
|
|
1447
|
-
await
|
|
1004
|
+
await showRuntimeToast(client, `All workspaces disabled for account ${account.index + 1}. Switching to another account.`, "warning", { duration: toastDurationMs });
|
|
1448
1005
|
// Forget session affinity and continue the outer loop so another
|
|
1449
1006
|
// enabled account can service the request.
|
|
1450
1007
|
sessionAffinityStore?.forgetSession(sessionAffinityKey);
|
|
1451
1008
|
continue accountAttemptLoop;
|
|
1452
1009
|
}
|
|
1453
1010
|
}
|
|
1454
|
-
if (errorResponse.status === 403 &&
|
|
1011
|
+
if (errorResponse.status === 403 &&
|
|
1012
|
+
!unsupportedModelInfo.isUnsupported &&
|
|
1013
|
+
!isDisabledWorkspaceError) {
|
|
1455
1014
|
entitlementCache.markBlocked(entitlementAccountKey, model ?? modelFamily, "plan-entitlement");
|
|
1456
1015
|
capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
|
|
1457
1016
|
}
|
|
1458
|
-
if (recoveryHook &&
|
|
1017
|
+
if (recoveryHook &&
|
|
1018
|
+
errorBody &&
|
|
1019
|
+
isRecoverableError(errorBody)) {
|
|
1459
1020
|
const errorType = detectErrorType(errorBody);
|
|
1460
1021
|
const toastContent = getRecoveryToastContent(errorType);
|
|
1461
|
-
await
|
|
1022
|
+
await showRuntimeToast(client, `${toastContent.title}: ${toastContent.message}`, "warning", { duration: toastDurationMs });
|
|
1462
1023
|
logDebug(`[${PLUGIN_NAME}] Recoverable error detected: ${errorType}`);
|
|
1463
1024
|
}
|
|
1464
1025
|
// Handle 5xx server errors by rotating to another account
|
|
@@ -1469,7 +1030,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1469
1030
|
runtimeMetrics.accountRotations++;
|
|
1470
1031
|
runtimeMetrics.lastError = `HTTP ${response.status}`;
|
|
1471
1032
|
const serverRetryAfterMs = parseRetryAfterHintMs(response.headers);
|
|
1472
|
-
const policy = evaluateFailurePolicy({
|
|
1033
|
+
const policy = evaluateFailurePolicy({
|
|
1034
|
+
kind: "server",
|
|
1035
|
+
failoverMode,
|
|
1036
|
+
serverRetryAfterMs: serverRetryAfterMs ?? undefined,
|
|
1037
|
+
}, { serverCooldownMs: serverErrorCooldownMs });
|
|
1473
1038
|
if (policy.refundToken) {
|
|
1474
1039
|
accountManager.refundToken(account, modelFamily, model);
|
|
1475
1040
|
}
|
|
@@ -1500,7 +1065,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1500
1065
|
const waitLabel = formatWaitTime(delayMs);
|
|
1501
1066
|
if (delayMs <= RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS) {
|
|
1502
1067
|
if (accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
|
|
1503
|
-
await
|
|
1068
|
+
await showRuntimeToast(client, `Rate limited. Retrying in ${waitLabel} (attempt ${attempt})...`, "warning", { duration: toastDurationMs });
|
|
1504
1069
|
accountManager.markToastShown(account.index);
|
|
1505
1070
|
}
|
|
1506
1071
|
await sleep(addJitter(Math.max(MIN_BACKOFF_MS, delayMs), 0.2));
|
|
@@ -1515,7 +1080,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1515
1080
|
logWarn(`Rate limited. Rotating account ${account.index + 1} (${account.email ?? "unknown"}).`);
|
|
1516
1081
|
if (accountManager.getAccountCount() > 1 &&
|
|
1517
1082
|
accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
|
|
1518
|
-
await
|
|
1083
|
+
await showRuntimeToast(client, `Rate limited. Switching accounts (retry in ${waitLabel}).`, "warning", { duration: toastDurationMs });
|
|
1519
1084
|
accountManager.markToastShown(account.index);
|
|
1520
1085
|
}
|
|
1521
1086
|
break;
|
|
@@ -1559,9 +1124,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1559
1124
|
try {
|
|
1560
1125
|
if (shouldRefreshToken(fallbackAuth, tokenRefreshSkewMs)) {
|
|
1561
1126
|
fallbackAuth = (await refreshAndUpdateToken(fallbackAuth, client));
|
|
1562
|
-
accountManager.
|
|
1563
|
-
accountManager.clearAuthFailures(fallbackAccount);
|
|
1564
|
-
accountManager.saveToDiskDebounced();
|
|
1127
|
+
await accountManager.commitRefreshedAuth(fallbackAccount, fallbackAuth);
|
|
1565
1128
|
}
|
|
1566
1129
|
}
|
|
1567
1130
|
catch (refreshError) {
|
|
@@ -1590,7 +1153,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1590
1153
|
}
|
|
1591
1154
|
const fallbackResolvedEmail = fallbackRuntimeIdentity.email;
|
|
1592
1155
|
const fallbackEntitlementAccountKey = resolveEntitlementAccountKey({
|
|
1593
|
-
accountId: fallbackStoredAccountId ??
|
|
1156
|
+
accountId: fallbackStoredAccountId ??
|
|
1157
|
+
fallbackAccountId,
|
|
1594
1158
|
email: fallbackResolvedEmail,
|
|
1595
1159
|
refreshToken: fallbackAccount.refreshToken,
|
|
1596
1160
|
index: fallbackAccount.index,
|
|
@@ -1598,8 +1162,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1598
1162
|
const fallbackEntitlementBlock = entitlementCache.isBlocked(fallbackEntitlementAccountKey, model ?? modelFamily);
|
|
1599
1163
|
if (fallbackEntitlementBlock.blocked) {
|
|
1600
1164
|
runtimeMetrics.accountRotations++;
|
|
1601
|
-
runtimeMetrics.lastError =
|
|
1602
|
-
`Entitlement cached block for account ${fallbackAccount.index + 1}`;
|
|
1165
|
+
runtimeMetrics.lastError = `Entitlement cached block for account ${fallbackAccount.index + 1}`;
|
|
1603
1166
|
logWarn(`Skipping account ${fallbackAccount.index + 1} due to cached entitlement block (${formatWaitTime(fallbackEntitlementBlock.waitMs)} remaining).`);
|
|
1604
1167
|
continue;
|
|
1605
1168
|
}
|
|
@@ -1623,7 +1186,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1623
1186
|
const fallbackController = new AbortController();
|
|
1624
1187
|
const fallbackTimeoutId = setTimeout(() => fallbackController.abort(new Error("Request timeout")), fetchTimeoutMs);
|
|
1625
1188
|
const onFallbackAbort = abortSignal
|
|
1626
|
-
? () => fallbackController.abort(abortSignal.reason ??
|
|
1189
|
+
? () => fallbackController.abort(abortSignal.reason ??
|
|
1190
|
+
new Error("Aborted by user"))
|
|
1627
1191
|
: null;
|
|
1628
1192
|
if (abortSignal && onFallbackAbort) {
|
|
1629
1193
|
abortSignal.addEventListener("abort", onFallbackAbort, {
|
|
@@ -1660,12 +1224,15 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1660
1224
|
continue;
|
|
1661
1225
|
}
|
|
1662
1226
|
successAccountForResponse = fallbackAccount;
|
|
1663
|
-
successEntitlementAccountKey =
|
|
1227
|
+
successEntitlementAccountKey =
|
|
1228
|
+
fallbackEntitlementAccountKey;
|
|
1664
1229
|
runtimeMetrics.streamFailoverRecoveries += 1;
|
|
1665
1230
|
if (fallbackAccount.index !== account.index) {
|
|
1666
1231
|
runtimeMetrics.streamFailoverCrossAccountRecoveries += 1;
|
|
1667
1232
|
runtimeMetrics.accountRotations += 1;
|
|
1668
|
-
|
|
1233
|
+
if (!responseContinuationEnabled) {
|
|
1234
|
+
sessionAffinityStore?.remember(sessionAffinityKey, fallbackAccount.index);
|
|
1235
|
+
}
|
|
1669
1236
|
}
|
|
1670
1237
|
logInfo(`Recovered stream via failover attempt ${failoverAttempt} using account ${fallbackAccount.index + 1}.`, { emittedBytes });
|
|
1671
1238
|
return fallbackResponse;
|
|
@@ -1680,7 +1247,6 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1680
1247
|
? streamFailoverError.message
|
|
1681
1248
|
: String(streamFailoverError),
|
|
1682
1249
|
});
|
|
1683
|
-
continue;
|
|
1684
1250
|
}
|
|
1685
1251
|
finally {
|
|
1686
1252
|
clearTimeout(fallbackTimeoutId);
|
|
@@ -1697,20 +1263,30 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1697
1263
|
requestInstanceId: requestCorrelationId ?? undefined,
|
|
1698
1264
|
});
|
|
1699
1265
|
}
|
|
1266
|
+
let storedResponseIdForSuccess = false;
|
|
1700
1267
|
const successResponse = await handleSuccessResponse(responseForSuccess, isStreaming, {
|
|
1268
|
+
onResponseId: (responseId) => {
|
|
1269
|
+
if (!responseContinuationEnabled)
|
|
1270
|
+
return;
|
|
1271
|
+
sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
|
|
1272
|
+
sessionAffinityStore?.updateLastResponseId(sessionAffinityKey, responseId);
|
|
1273
|
+
storedResponseIdForSuccess = true;
|
|
1274
|
+
},
|
|
1701
1275
|
streamStallTimeoutMs,
|
|
1702
1276
|
});
|
|
1703
1277
|
if (!isStreaming && emptyResponseMaxRetries > 0) {
|
|
1704
1278
|
const clonedResponse = successResponse.clone();
|
|
1705
1279
|
try {
|
|
1706
1280
|
const bodyText = await clonedResponse.text();
|
|
1707
|
-
const parsedBody = bodyText
|
|
1281
|
+
const parsedBody = bodyText
|
|
1282
|
+
? JSON.parse(bodyText)
|
|
1283
|
+
: null;
|
|
1708
1284
|
if (isEmptyResponse(parsedBody)) {
|
|
1709
1285
|
if (emptyResponseRetries < emptyResponseMaxRetries) {
|
|
1710
1286
|
emptyResponseRetries++;
|
|
1711
1287
|
runtimeMetrics.emptyResponseRetries++;
|
|
1712
1288
|
logWarn(`Empty response received (attempt ${emptyResponseRetries}/${emptyResponseMaxRetries}). Retrying...`);
|
|
1713
|
-
await
|
|
1289
|
+
await showRuntimeToast(client, `Empty response. Retrying (${emptyResponseRetries}/${emptyResponseMaxRetries})...`, "warning", { duration: toastDurationMs });
|
|
1714
1290
|
accountManager.refundToken(account, modelFamily, model);
|
|
1715
1291
|
accountManager.recordFailure(account, modelFamily, model);
|
|
1716
1292
|
capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
|
|
@@ -1722,7 +1298,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1722
1298
|
sameAccountRetryCount < maxSameAccountRetries) {
|
|
1723
1299
|
sameAccountRetryCount += 1;
|
|
1724
1300
|
runtimeMetrics.sameAccountRetries += 1;
|
|
1725
|
-
const retryDelayMs = Math.max(0, Math.floor(emptyPolicy.retryDelayMs ??
|
|
1301
|
+
const retryDelayMs = Math.max(0, Math.floor(emptyPolicy.retryDelayMs ??
|
|
1302
|
+
emptyResponseRetryDelayMs));
|
|
1726
1303
|
if (retryDelayMs > 0) {
|
|
1727
1304
|
await sleep(addJitter(retryDelayMs, 0.2));
|
|
1728
1305
|
}
|
|
@@ -1746,12 +1323,17 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1746
1323
|
accountManager.recordSuccess(successAccountForResponse, modelFamily, model);
|
|
1747
1324
|
capabilityPolicyStore.recordSuccess(successAccountKey, capabilityModelKey);
|
|
1748
1325
|
entitlementCache.clear(successAccountKey, capabilityModelKey);
|
|
1749
|
-
|
|
1326
|
+
if (!responseContinuationEnabled ||
|
|
1327
|
+
(!isStreaming && !storedResponseIdForSuccess)) {
|
|
1328
|
+
sessionAffinityStore?.remember(sessionAffinityKey, successAccountForResponse.index);
|
|
1329
|
+
}
|
|
1750
1330
|
runtimeMetrics.successfulRequests++;
|
|
1751
1331
|
runtimeMetrics.lastError = null;
|
|
1752
|
-
if (lastCodexCliActiveSyncIndex !==
|
|
1332
|
+
if (lastCodexCliActiveSyncIndex !==
|
|
1333
|
+
successAccountForResponse.index) {
|
|
1753
1334
|
void accountManager.syncCodexCliActiveSelectionForIndex(successAccountForResponse.index);
|
|
1754
|
-
lastCodexCliActiveSyncIndex =
|
|
1335
|
+
lastCodexCliActiveSyncIndex =
|
|
1336
|
+
successAccountForResponse.index;
|
|
1755
1337
|
}
|
|
1756
1338
|
return successResponse;
|
|
1757
1339
|
}
|
|
@@ -1775,7 +1357,15 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1775
1357
|
waitMs <= retryAllAccountsMaxWaitMs) &&
|
|
1776
1358
|
allRateLimitedRetries < retryAllAccountsMaxRetries) {
|
|
1777
1359
|
const countdownMessage = `All ${count} account(s) rate-limited. Waiting`;
|
|
1778
|
-
await sleepWithCountdown(
|
|
1360
|
+
await sleepWithCountdown({
|
|
1361
|
+
totalMs: addJitter(waitMs, 0.2),
|
|
1362
|
+
message: countdownMessage,
|
|
1363
|
+
sleep,
|
|
1364
|
+
showToast: (message, variant, options) => showRuntimeToast(client, message, variant, options),
|
|
1365
|
+
formatWaitTime,
|
|
1366
|
+
toastDurationMs,
|
|
1367
|
+
abortSignal,
|
|
1368
|
+
});
|
|
1779
1369
|
allRateLimitedRetries++;
|
|
1780
1370
|
continue;
|
|
1781
1371
|
}
|
|
@@ -1812,8 +1402,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1812
1402
|
type: "oauth",
|
|
1813
1403
|
authorize: async (inputs) => {
|
|
1814
1404
|
const authPluginConfig = loadPluginConfig();
|
|
1815
|
-
applyUiRuntimeFromConfig(authPluginConfig);
|
|
1816
|
-
|
|
1405
|
+
applyUiRuntimeFromConfig(authPluginConfig, setUiRuntimeOptions);
|
|
1406
|
+
applyAccountStorageScopeFromConfig(authPluginConfig, {
|
|
1407
|
+
getPerProjectAccounts,
|
|
1408
|
+
getStorageBackupEnabled,
|
|
1409
|
+
setStorageBackupEnabled,
|
|
1410
|
+
isCodexCliSyncEnabled,
|
|
1411
|
+
getWarningShown: () => perProjectStorageWarningShown,
|
|
1412
|
+
setWarningShown: (shown) => {
|
|
1413
|
+
perProjectStorageWarningShown = shown;
|
|
1414
|
+
},
|
|
1415
|
+
logWarn,
|
|
1416
|
+
pluginName: PLUGIN_NAME,
|
|
1417
|
+
setStoragePath,
|
|
1418
|
+
cwd: () => process.cwd(),
|
|
1419
|
+
});
|
|
1817
1420
|
const accounts = [];
|
|
1818
1421
|
const noBrowser = inputs?.manual === "true" ||
|
|
1819
1422
|
inputs?.noBrowser === "true" ||
|
|
@@ -1824,569 +1427,45 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1824
1427
|
: null;
|
|
1825
1428
|
let startFresh = explicitLoginMode === "fresh";
|
|
1826
1429
|
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
1430
|
if (!explicitLoginMode) {
|
|
2344
1431
|
while (true) {
|
|
2345
|
-
const loadedStorage = await
|
|
1432
|
+
const loadedStorage = await hydrateRuntimeEmails(await loadAccounts(), {
|
|
1433
|
+
queuedRefresh,
|
|
1434
|
+
extractAccountId,
|
|
1435
|
+
sanitizeEmail,
|
|
1436
|
+
extractAccountEmail,
|
|
1437
|
+
shouldUpdateAccountIdFromToken,
|
|
1438
|
+
saveAccounts,
|
|
1439
|
+
logWarn,
|
|
1440
|
+
pluginName: PLUGIN_NAME,
|
|
1441
|
+
});
|
|
2346
1442
|
const workingStorage = loadedStorage
|
|
2347
1443
|
? {
|
|
2348
1444
|
...loadedStorage,
|
|
2349
|
-
accounts: loadedStorage.accounts.map((account) => ({
|
|
1445
|
+
accounts: loadedStorage.accounts.map((account) => ({
|
|
1446
|
+
...account,
|
|
1447
|
+
})),
|
|
2350
1448
|
activeIndexByFamily: loadedStorage.activeIndexByFamily
|
|
2351
1449
|
? { ...loadedStorage.activeIndexByFamily }
|
|
2352
1450
|
: {},
|
|
2353
1451
|
}
|
|
2354
|
-
: {
|
|
1452
|
+
: {
|
|
1453
|
+
version: 3,
|
|
1454
|
+
accounts: [],
|
|
1455
|
+
activeIndex: 0,
|
|
1456
|
+
activeIndexByFamily: {},
|
|
1457
|
+
};
|
|
2355
1458
|
const flaggedStorage = await loadFlaggedAccounts();
|
|
2356
|
-
if (workingStorage.accounts.length === 0 &&
|
|
1459
|
+
if (workingStorage.accounts.length === 0 &&
|
|
1460
|
+
flaggedStorage.accounts.length === 0) {
|
|
2357
1461
|
break;
|
|
2358
1462
|
}
|
|
2359
1463
|
const now = Date.now();
|
|
2360
1464
|
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
|
-
};
|
|
1465
|
+
const existingAccounts = buildLoginMenuAccounts(workingStorage.accounts, {
|
|
1466
|
+
now,
|
|
1467
|
+
activeIndex,
|
|
1468
|
+
formatRateLimitEntry: (account, currentNow) => formatRateLimitEntry(account, currentNow, formatWaitTime),
|
|
2390
1469
|
});
|
|
2391
1470
|
const menuResult = await promptLoginMode(existingAccounts, {
|
|
2392
1471
|
flaggedCount: flaggedStorage.accounts.length,
|
|
@@ -2401,16 +1480,62 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2401
1480
|
}),
|
|
2402
1481
|
};
|
|
2403
1482
|
}
|
|
1483
|
+
const accountCheckDeps = {
|
|
1484
|
+
hydrateEmails: (storage) => hydrateRuntimeEmails(storage, {
|
|
1485
|
+
queuedRefresh,
|
|
1486
|
+
extractAccountId,
|
|
1487
|
+
sanitizeEmail,
|
|
1488
|
+
extractAccountEmail,
|
|
1489
|
+
shouldUpdateAccountIdFromToken,
|
|
1490
|
+
saveAccounts,
|
|
1491
|
+
logWarn,
|
|
1492
|
+
pluginName: PLUGIN_NAME,
|
|
1493
|
+
}),
|
|
1494
|
+
loadAccounts,
|
|
1495
|
+
createEmptyStorage: () => ({
|
|
1496
|
+
version: 3,
|
|
1497
|
+
accounts: [],
|
|
1498
|
+
activeIndex: 0,
|
|
1499
|
+
activeIndexByFamily: {},
|
|
1500
|
+
}),
|
|
1501
|
+
loadFlaggedAccounts,
|
|
1502
|
+
createAccountCheckWorkingState,
|
|
1503
|
+
lookupCodexCliTokensByEmail,
|
|
1504
|
+
extractAccountId,
|
|
1505
|
+
shouldUpdateAccountIdFromToken,
|
|
1506
|
+
sanitizeEmail,
|
|
1507
|
+
extractAccountEmail,
|
|
1508
|
+
queuedRefresh,
|
|
1509
|
+
isRuntimeFlaggableFailure: isFlaggableFailure,
|
|
1510
|
+
fetchCodexQuotaSnapshot,
|
|
1511
|
+
resolveRequestAccountId,
|
|
1512
|
+
formatCodexQuotaLine: formatQuotaSnapshotLine,
|
|
1513
|
+
clampRuntimeActiveIndices: clampActiveIndices,
|
|
1514
|
+
MODEL_FAMILIES,
|
|
1515
|
+
saveAccounts,
|
|
1516
|
+
invalidateAccountManagerCache: () => invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps),
|
|
1517
|
+
saveFlaggedAccounts,
|
|
1518
|
+
showLine: (message) => console.log(message),
|
|
1519
|
+
};
|
|
2404
1520
|
if (menuResult.mode === "check") {
|
|
2405
|
-
await
|
|
1521
|
+
await runRuntimeAccountCheck(false, accountCheckDeps);
|
|
2406
1522
|
continue;
|
|
2407
1523
|
}
|
|
2408
1524
|
if (menuResult.mode === "deep-check") {
|
|
2409
|
-
await
|
|
1525
|
+
await runRuntimeAccountCheck(true, accountCheckDeps);
|
|
2410
1526
|
continue;
|
|
2411
1527
|
}
|
|
2412
1528
|
if (menuResult.mode === "verify-flagged") {
|
|
2413
|
-
await
|
|
1529
|
+
await verifyRuntimeFlaggedAccounts({
|
|
1530
|
+
loadFlaggedAccounts,
|
|
1531
|
+
lookupCodexCliTokensByEmail,
|
|
1532
|
+
queuedRefresh,
|
|
1533
|
+
resolveTokenSuccessAccount,
|
|
1534
|
+
persistAccounts: persistAccountPool,
|
|
1535
|
+
invalidateAccountManagerCache: () => invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps),
|
|
1536
|
+
saveFlaggedAccounts,
|
|
1537
|
+
showLine: (message) => console.log(message),
|
|
1538
|
+
});
|
|
2414
1539
|
continue;
|
|
2415
1540
|
}
|
|
2416
1541
|
if (menuResult.mode === "manage") {
|
|
@@ -2418,13 +1543,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2418
1543
|
const target = workingStorage.accounts[menuResult.deleteAccountIndex];
|
|
2419
1544
|
if (target) {
|
|
2420
1545
|
workingStorage.accounts.splice(menuResult.deleteAccountIndex, 1);
|
|
2421
|
-
clampActiveIndices(workingStorage);
|
|
1546
|
+
clampActiveIndices(workingStorage, MODEL_FAMILIES);
|
|
2422
1547
|
await saveAccounts(workingStorage);
|
|
2423
1548
|
await saveFlaggedAccounts({
|
|
2424
1549
|
version: 1,
|
|
2425
1550
|
accounts: flaggedStorage.accounts.filter((flagged) => flagged.refreshToken !== target.refreshToken),
|
|
2426
1551
|
});
|
|
2427
|
-
|
|
1552
|
+
invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
|
|
2428
1553
|
console.log(`\nDeleted ${target.email ?? `Account ${menuResult.deleteAccountIndex + 1}`}.\n`);
|
|
2429
1554
|
}
|
|
2430
1555
|
continue;
|
|
@@ -2434,7 +1559,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2434
1559
|
if (target) {
|
|
2435
1560
|
target.enabled = target.enabled === false ? true : false;
|
|
2436
1561
|
await saveAccounts(workingStorage);
|
|
2437
|
-
|
|
1562
|
+
invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
|
|
2438
1563
|
console.log(`\n${target.email ?? `Account ${menuResult.toggleAccountIndex + 1}`} ${target.enabled === false ? "disabled" : "enabled"}.\n`);
|
|
2439
1564
|
}
|
|
2440
1565
|
continue;
|
|
@@ -2451,7 +1576,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2451
1576
|
if (menuResult.deleteAll) {
|
|
2452
1577
|
await clearAccounts();
|
|
2453
1578
|
await clearFlaggedAccounts();
|
|
2454
|
-
|
|
1579
|
+
invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
|
|
2455
1580
|
console.log("\nCleared saved accounts from active storage. Recovery snapshots remain available. Starting fresh.\n");
|
|
2456
1581
|
}
|
|
2457
1582
|
break;
|
|
@@ -2463,7 +1588,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2463
1588
|
const latestStorage = await loadAccounts();
|
|
2464
1589
|
const existingCount = latestStorage?.accounts.length ?? 0;
|
|
2465
1590
|
const requestedCount = Number.parseInt(inputs?.accountCount ?? "1", 10);
|
|
2466
|
-
const normalizedRequested = Number.isFinite(requestedCount)
|
|
1591
|
+
const normalizedRequested = Number.isFinite(requestedCount)
|
|
1592
|
+
? requestedCount
|
|
1593
|
+
: 1;
|
|
2467
1594
|
const availableSlots = refreshAccountIndex !== undefined
|
|
2468
1595
|
? 1
|
|
2469
1596
|
: startFresh
|
|
@@ -2488,39 +1615,72 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2488
1615
|
}
|
|
2489
1616
|
if (useManualMode) {
|
|
2490
1617
|
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
|
-
|
|
1618
|
+
return buildManualOAuthFlow({
|
|
1619
|
+
pkce,
|
|
1620
|
+
url,
|
|
1621
|
+
expectedState: state,
|
|
1622
|
+
redirectUri: REDIRECT_URI,
|
|
1623
|
+
parseAuthorizationInput,
|
|
1624
|
+
exchangeAuthorizationCode,
|
|
1625
|
+
resolveTokenSuccess: resolveTokenSuccessAccount,
|
|
1626
|
+
instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
|
|
1627
|
+
onSuccess: async (tokens) => {
|
|
1628
|
+
try {
|
|
1629
|
+
await persistAccountPool([tokens], startFresh);
|
|
1630
|
+
invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
|
|
1631
|
+
}
|
|
1632
|
+
catch (err) {
|
|
1633
|
+
const storagePath = getStoragePath();
|
|
1634
|
+
const errorCode = err?.code || "UNKNOWN";
|
|
1635
|
+
const hint = err instanceof StorageError
|
|
1636
|
+
? err.hint
|
|
1637
|
+
: formatStorageErrorHint(err, storagePath);
|
|
1638
|
+
logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
|
|
1639
|
+
await showRuntimeToast(client, hint, "error", {
|
|
1640
|
+
title: "Account Persistence Failed",
|
|
1641
|
+
duration: 10000,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
},
|
|
2508
1645
|
});
|
|
2509
1646
|
}
|
|
2510
|
-
const explicitCountProvided = typeof inputs?.accountCount === "string" &&
|
|
1647
|
+
const explicitCountProvided = typeof inputs?.accountCount === "string" &&
|
|
1648
|
+
inputs.accountCount.trim().length > 0;
|
|
2511
1649
|
while (accounts.length < targetCount) {
|
|
2512
1650
|
logInfo(`=== OpenAI OAuth (Account ${accounts.length + 1}) ===`);
|
|
2513
1651
|
const forceNewLogin = accounts.length > 0 || refreshAccountIndex !== undefined;
|
|
2514
|
-
const result = await
|
|
1652
|
+
const result = await runRuntimeOAuthFlow(forceNewLogin, {
|
|
1653
|
+
runBrowserOAuthFlow: (input) => runBrowserOAuthFlow({
|
|
1654
|
+
...input,
|
|
1655
|
+
createAuthorizationFlow,
|
|
1656
|
+
redactOAuthUrlForLog,
|
|
1657
|
+
startLocalOAuthServer,
|
|
1658
|
+
openBrowserUrl,
|
|
1659
|
+
pluginName: PLUGIN_NAME,
|
|
1660
|
+
authManualLabel: AUTH_LABELS.OAUTH_MANUAL,
|
|
1661
|
+
exchangeAuthorizationCode,
|
|
1662
|
+
redirectUri: REDIRECT_URI,
|
|
1663
|
+
}),
|
|
1664
|
+
manualModeLabel: AUTH_LABELS.OAUTH_MANUAL,
|
|
1665
|
+
logInfo,
|
|
1666
|
+
logDebug,
|
|
1667
|
+
logWarn,
|
|
1668
|
+
pluginName: PLUGIN_NAME,
|
|
1669
|
+
});
|
|
2515
1670
|
let resolved = null;
|
|
2516
1671
|
if (result.type === "success") {
|
|
2517
|
-
resolved =
|
|
1672
|
+
resolved = resolveTokenSuccessAccount(result);
|
|
2518
1673
|
const email = extractAccountEmail(resolved.access, resolved.idToken);
|
|
2519
|
-
const accountId = resolved.accountIdOverride ??
|
|
2520
|
-
|
|
1674
|
+
const accountId = resolved.accountIdOverride ??
|
|
1675
|
+
extractAccountId(resolved.access);
|
|
1676
|
+
const label = resolved.accountLabel ??
|
|
1677
|
+
email ??
|
|
1678
|
+
accountId ??
|
|
1679
|
+
"Unknown account";
|
|
2521
1680
|
logInfo(`Authenticated as: ${label}`);
|
|
2522
1681
|
const isDuplicate = findMatchingAccountIndex(accounts.map((account) => ({
|
|
2523
|
-
accountId: account.accountIdOverride ??
|
|
1682
|
+
accountId: account.accountIdOverride ??
|
|
1683
|
+
extractAccountId(account.access),
|
|
2524
1684
|
email: sanitizeEmail(extractAccountEmail(account.access, account.idToken)),
|
|
2525
1685
|
refreshToken: account.refresh,
|
|
2526
1686
|
})), {
|
|
@@ -2550,11 +1710,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2550
1710
|
continue;
|
|
2551
1711
|
}
|
|
2552
1712
|
accounts.push(resolved);
|
|
2553
|
-
await
|
|
1713
|
+
await showRuntimeToast(client, `Account ${accounts.length} authenticated`, "success");
|
|
2554
1714
|
try {
|
|
2555
1715
|
const isFirstAccount = accounts.length === 1;
|
|
2556
1716
|
await persistAccountPool([resolved], isFirstAccount && startFresh);
|
|
2557
|
-
|
|
1717
|
+
invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
|
|
2558
1718
|
}
|
|
2559
1719
|
catch (err) {
|
|
2560
1720
|
const storagePath = getStoragePath();
|
|
@@ -2563,7 +1723,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2563
1723
|
? err.hint
|
|
2564
1724
|
: formatStorageErrorHint(err, storagePath);
|
|
2565
1725
|
logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
|
|
2566
|
-
await
|
|
1726
|
+
await showRuntimeToast(client, hint, "error", {
|
|
2567
1727
|
title: "Account Persistence Failed",
|
|
2568
1728
|
duration: 10000,
|
|
2569
1729
|
});
|
|
@@ -2619,20 +1779,48 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2619
1779
|
// Initialize storage path for manual OAuth flow
|
|
2620
1780
|
// Must happen BEFORE persistAccountPool to ensure correct storage location
|
|
2621
1781
|
const manualPluginConfig = loadPluginConfig();
|
|
2622
|
-
applyUiRuntimeFromConfig(manualPluginConfig);
|
|
2623
|
-
|
|
1782
|
+
applyUiRuntimeFromConfig(manualPluginConfig, setUiRuntimeOptions);
|
|
1783
|
+
applyAccountStorageScopeFromConfig(manualPluginConfig, {
|
|
1784
|
+
getPerProjectAccounts,
|
|
1785
|
+
getStorageBackupEnabled,
|
|
1786
|
+
setStorageBackupEnabled,
|
|
1787
|
+
isCodexCliSyncEnabled,
|
|
1788
|
+
getWarningShown: () => perProjectStorageWarningShown,
|
|
1789
|
+
setWarningShown: (shown) => {
|
|
1790
|
+
perProjectStorageWarningShown = shown;
|
|
1791
|
+
},
|
|
1792
|
+
logWarn,
|
|
1793
|
+
pluginName: PLUGIN_NAME,
|
|
1794
|
+
setStoragePath,
|
|
1795
|
+
cwd: () => process.cwd(),
|
|
1796
|
+
});
|
|
2624
1797
|
const { pkce, state, url } = await createAuthorizationFlow();
|
|
2625
|
-
return buildManualOAuthFlow(
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
1798
|
+
return buildManualOAuthFlow({
|
|
1799
|
+
pkce,
|
|
1800
|
+
url,
|
|
1801
|
+
expectedState: state,
|
|
1802
|
+
redirectUri: REDIRECT_URI,
|
|
1803
|
+
parseAuthorizationInput,
|
|
1804
|
+
exchangeAuthorizationCode,
|
|
1805
|
+
resolveTokenSuccess: resolveTokenSuccessAccount,
|
|
1806
|
+
instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
|
|
1807
|
+
onSuccess: async (tokens) => {
|
|
1808
|
+
try {
|
|
1809
|
+
await persistAccountPool([tokens], false);
|
|
1810
|
+
}
|
|
1811
|
+
catch (err) {
|
|
1812
|
+
const storagePath = getStoragePath();
|
|
1813
|
+
const errorCode = err?.code || "UNKNOWN";
|
|
1814
|
+
const hint = err instanceof StorageError
|
|
1815
|
+
? err.hint
|
|
1816
|
+
: formatStorageErrorHint(err, storagePath);
|
|
1817
|
+
logError(`[${PLUGIN_NAME}] Failed to persist account: [${errorCode}] ${err?.message ?? String(err)}`);
|
|
1818
|
+
await showRuntimeToast(client, hint, "error", {
|
|
1819
|
+
title: "Account Persistence Failed",
|
|
1820
|
+
duration: 10000,
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
2636
1824
|
});
|
|
2637
1825
|
},
|
|
2638
1826
|
},
|
|
@@ -2648,7 +1836,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2648
1836
|
description: "List all Codex OAuth accounts and the current active index.",
|
|
2649
1837
|
args: {},
|
|
2650
1838
|
async execute() {
|
|
2651
|
-
const ui =
|
|
1839
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2652
1840
|
const storage = await loadAccounts();
|
|
2653
1841
|
const storePath = getStoragePath();
|
|
2654
1842
|
if (!storage || storage.accounts.length === 0) {
|
|
@@ -2687,7 +1875,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2687
1875
|
badges.push(formatUiBadge(ui, "current", "accent"));
|
|
2688
1876
|
if (account.enabled === false)
|
|
2689
1877
|
badges.push(formatUiBadge(ui, "disabled", "danger"));
|
|
2690
|
-
const rateLimit = formatRateLimitEntry(account, now);
|
|
1878
|
+
const rateLimit = formatRateLimitEntry(account, now, formatWaitTime);
|
|
2691
1879
|
if (rateLimit)
|
|
2692
1880
|
badges.push(formatUiBadge(ui, "rate-limited", "warning"));
|
|
2693
1881
|
if (typeof account.coolingDownUntil === "number" &&
|
|
@@ -2725,13 +1913,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2725
1913
|
storage.accounts.forEach((account, index) => {
|
|
2726
1914
|
const label = formatAccountLabel(account, index);
|
|
2727
1915
|
const statuses = [];
|
|
2728
|
-
const rateLimit = formatRateLimitEntry(account, now);
|
|
1916
|
+
const rateLimit = formatRateLimitEntry(account, now, formatWaitTime);
|
|
2729
1917
|
if (index === activeIndex)
|
|
2730
1918
|
statuses.push("active");
|
|
2731
1919
|
if (rateLimit)
|
|
2732
1920
|
statuses.push("rate-limited");
|
|
2733
|
-
if (typeof account.coolingDownUntil ===
|
|
2734
|
-
"number" &&
|
|
1921
|
+
if (typeof account.coolingDownUntil === "number" &&
|
|
2735
1922
|
account.coolingDownUntil > now) {
|
|
2736
1923
|
statuses.push("cooldown");
|
|
2737
1924
|
}
|
|
@@ -2752,10 +1939,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2752
1939
|
"codex-switch": tool({
|
|
2753
1940
|
description: "Switch active Codex account by index (1-based).",
|
|
2754
1941
|
args: {
|
|
2755
|
-
index: tool.schema
|
|
1942
|
+
index: tool.schema
|
|
1943
|
+
.number()
|
|
1944
|
+
.describe("Account number to switch to (1-based, e.g., 1 for first account)"),
|
|
2756
1945
|
},
|
|
2757
1946
|
async execute({ index }) {
|
|
2758
|
-
const ui =
|
|
1947
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2759
1948
|
const storage = await loadAccounts();
|
|
2760
1949
|
if (!storage || storage.accounts.length === 0) {
|
|
2761
1950
|
if (ui.v2Enabled) {
|
|
@@ -2797,7 +1986,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2797
1986
|
await saveAccounts(storage);
|
|
2798
1987
|
}
|
|
2799
1988
|
catch (saveError) {
|
|
2800
|
-
logWarn("Failed to save account switch", {
|
|
1989
|
+
logWarn("Failed to save account switch", {
|
|
1990
|
+
error: String(saveError),
|
|
1991
|
+
});
|
|
2801
1992
|
if (ui.v2Enabled) {
|
|
2802
1993
|
return [
|
|
2803
1994
|
...formatUiHeader(ui, "Switch account"),
|
|
@@ -2809,7 +2000,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2809
2000
|
return `Switched to ${formatAccountLabel(account, targetIndex)} but failed to persist. Changes may be lost on restart.`;
|
|
2810
2001
|
}
|
|
2811
2002
|
if (cachedAccountManager) {
|
|
2812
|
-
await
|
|
2003
|
+
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
|
|
2813
2004
|
}
|
|
2814
2005
|
const label = formatAccountLabel(account, targetIndex);
|
|
2815
2006
|
if (ui.v2Enabled) {
|
|
@@ -2826,7 +2017,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2826
2017
|
description: "Show detailed status of Codex accounts and rate limits.",
|
|
2827
2018
|
args: {},
|
|
2828
2019
|
async execute() {
|
|
2829
|
-
const ui =
|
|
2020
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2830
2021
|
const storage = await loadAccounts();
|
|
2831
2022
|
if (!storage || storage.accounts.length === 0) {
|
|
2832
2023
|
if (ui.v2Enabled) {
|
|
@@ -2855,7 +2046,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2855
2046
|
badges.push(formatUiBadge(ui, "active", "accent"));
|
|
2856
2047
|
if (account.enabled === false)
|
|
2857
2048
|
badges.push(formatUiBadge(ui, "disabled", "danger"));
|
|
2858
|
-
const rateLimit = formatRateLimitEntry(account, now) ?? "none";
|
|
2049
|
+
const rateLimit = formatRateLimitEntry(account, now, formatWaitTime) ?? "none";
|
|
2859
2050
|
const cooldown = formatCooldown(account, now) ?? "none";
|
|
2860
2051
|
if (rateLimit !== "none")
|
|
2861
2052
|
badges.push(formatUiBadge(ui, "rate-limited", "warning"));
|
|
@@ -2871,7 +2062,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2871
2062
|
lines.push(...formatUiSection(ui, "Active index by model family"));
|
|
2872
2063
|
for (const family of MODEL_FAMILIES) {
|
|
2873
2064
|
const idx = storage.activeIndexByFamily?.[family];
|
|
2874
|
-
const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx)
|
|
2065
|
+
const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx)
|
|
2066
|
+
? String(idx + 1)
|
|
2067
|
+
: "-";
|
|
2875
2068
|
lines.push(formatUiItem(ui, `${family}: ${familyIndexLabel}`));
|
|
2876
2069
|
}
|
|
2877
2070
|
lines.push("");
|
|
@@ -2905,18 +2098,27 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2905
2098
|
storage.accounts.forEach((account, index) => {
|
|
2906
2099
|
const label = formatAccountLabel(account, index);
|
|
2907
2100
|
const active = index === activeIndex ? "Yes" : "No";
|
|
2908
|
-
const rateLimit = formatRateLimitEntry(account, now) ?? "None";
|
|
2101
|
+
const rateLimit = formatRateLimitEntry(account, now, formatWaitTime) ?? "None";
|
|
2909
2102
|
const cooldown = formatCooldown(account, now) ?? "No";
|
|
2910
2103
|
const lastUsed = typeof account.lastUsed === "number" && account.lastUsed > 0
|
|
2911
2104
|
? `${formatWaitTime(now - account.lastUsed)} ago`
|
|
2912
2105
|
: "-";
|
|
2913
|
-
lines.push(buildTableRow([
|
|
2106
|
+
lines.push(buildTableRow([
|
|
2107
|
+
String(index + 1),
|
|
2108
|
+
label,
|
|
2109
|
+
active,
|
|
2110
|
+
rateLimit,
|
|
2111
|
+
cooldown,
|
|
2112
|
+
lastUsed,
|
|
2113
|
+
], statusTableOptions));
|
|
2914
2114
|
});
|
|
2915
2115
|
lines.push("");
|
|
2916
2116
|
lines.push("Active index by model family:");
|
|
2917
2117
|
for (const family of MODEL_FAMILIES) {
|
|
2918
2118
|
const idx = storage.activeIndexByFamily?.[family];
|
|
2919
|
-
const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx)
|
|
2119
|
+
const familyIndexLabel = typeof idx === "number" && Number.isFinite(idx)
|
|
2120
|
+
? String(idx + 1)
|
|
2121
|
+
: "-";
|
|
2920
2122
|
lines.push(` ${family}: ${familyIndexLabel}`);
|
|
2921
2123
|
}
|
|
2922
2124
|
lines.push("");
|
|
@@ -2933,95 +2135,97 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2933
2135
|
return lines.join("\n");
|
|
2934
2136
|
},
|
|
2935
2137
|
}),
|
|
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"),
|
|
2138
|
+
...(exposeAdvancedCodexTools
|
|
2139
|
+
? {
|
|
2140
|
+
"codex-metrics": tool({
|
|
2141
|
+
description: "Show runtime request metrics for this plugin process.",
|
|
2142
|
+
args: {},
|
|
2143
|
+
execute() {
|
|
2144
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2145
|
+
const now = Date.now();
|
|
2146
|
+
const uptimeMs = Math.max(0, now - runtimeMetrics.startedAt);
|
|
2147
|
+
const total = runtimeMetrics.totalRequests;
|
|
2148
|
+
const successful = runtimeMetrics.successfulRequests;
|
|
2149
|
+
const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) : "0.0";
|
|
2150
|
+
const avgLatencyMs = successful > 0
|
|
2151
|
+
? Math.round(runtimeMetrics.cumulativeLatencyMs / successful)
|
|
2152
|
+
: 0;
|
|
2153
|
+
const liveSyncSnapshot = liveAccountSync?.getSnapshot();
|
|
2154
|
+
const guardianStats = refreshGuardian?.getStats();
|
|
2155
|
+
const sessionAffinityEntries = sessionAffinityStore?.size() ?? 0;
|
|
2156
|
+
const lastRequest = runtimeMetrics.lastRequestAt !== null
|
|
2157
|
+
? `${formatWaitTime(now - runtimeMetrics.lastRequestAt)} ago`
|
|
2158
|
+
: "never";
|
|
2159
|
+
const lines = [
|
|
2160
|
+
"Codex Plugin Metrics:",
|
|
2161
|
+
"",
|
|
2162
|
+
`Uptime: ${formatWaitTime(uptimeMs)}`,
|
|
2163
|
+
`Total upstream requests: ${total}`,
|
|
2164
|
+
`Successful responses: ${successful}`,
|
|
2165
|
+
`Failed responses: ${runtimeMetrics.failedRequests}`,
|
|
2166
|
+
`Success rate: ${successRate}%`,
|
|
2167
|
+
`Average successful latency: ${avgLatencyMs}ms`,
|
|
2168
|
+
`Rate-limited responses: ${runtimeMetrics.rateLimitedResponses}`,
|
|
2169
|
+
`Server errors (5xx): ${runtimeMetrics.serverErrors}`,
|
|
2170
|
+
`Network errors: ${runtimeMetrics.networkErrors}`,
|
|
2171
|
+
`User aborts: ${runtimeMetrics.userAborts}`,
|
|
2172
|
+
`Auth refresh failures: ${runtimeMetrics.authRefreshFailures}`,
|
|
2173
|
+
`Account rotations: ${runtimeMetrics.accountRotations}`,
|
|
2174
|
+
`Same-account retries: ${runtimeMetrics.sameAccountRetries}`,
|
|
2175
|
+
`Stream failover attempts: ${runtimeMetrics.streamFailoverAttempts}`,
|
|
2176
|
+
`Stream failover recoveries: ${runtimeMetrics.streamFailoverRecoveries}`,
|
|
2177
|
+
`Stream failover cross-account recoveries: ${runtimeMetrics.streamFailoverCrossAccountRecoveries}`,
|
|
2178
|
+
`Empty-response retries: ${runtimeMetrics.emptyResponseRetries}`,
|
|
2179
|
+
`Session affinity entries: ${sessionAffinityEntries}`,
|
|
2180
|
+
`Live sync: ${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`,
|
|
2181
|
+
`Refresh guardian: ${guardianStats ? "on" : "off"} (${guardianStats?.refreshed ?? 0} refreshed, ${guardianStats?.failed ?? 0} failed)`,
|
|
2182
|
+
`Last upstream request: ${lastRequest}`,
|
|
3010
2183
|
];
|
|
3011
2184
|
if (runtimeMetrics.lastError) {
|
|
3012
|
-
|
|
2185
|
+
lines.push(`Last error: ${runtimeMetrics.lastError}`);
|
|
3013
2186
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
2187
|
+
if (ui.v2Enabled) {
|
|
2188
|
+
const styled = [
|
|
2189
|
+
...formatUiHeader(ui, "Codex plugin metrics"),
|
|
2190
|
+
formatUiKeyValue(ui, "Uptime", formatWaitTime(uptimeMs)),
|
|
2191
|
+
formatUiKeyValue(ui, "Total upstream requests", String(total)),
|
|
2192
|
+
formatUiKeyValue(ui, "Successful responses", String(successful), "success"),
|
|
2193
|
+
formatUiKeyValue(ui, "Failed responses", String(runtimeMetrics.failedRequests), "danger"),
|
|
2194
|
+
formatUiKeyValue(ui, "Success rate", `${successRate}%`, "accent"),
|
|
2195
|
+
formatUiKeyValue(ui, "Average successful latency", `${avgLatencyMs}ms`),
|
|
2196
|
+
formatUiKeyValue(ui, "Rate-limited responses", String(runtimeMetrics.rateLimitedResponses), "warning"),
|
|
2197
|
+
formatUiKeyValue(ui, "Server errors (5xx)", String(runtimeMetrics.serverErrors), "danger"),
|
|
2198
|
+
formatUiKeyValue(ui, "Network errors", String(runtimeMetrics.networkErrors), "danger"),
|
|
2199
|
+
formatUiKeyValue(ui, "User aborts", String(runtimeMetrics.userAborts), "muted"),
|
|
2200
|
+
formatUiKeyValue(ui, "Auth refresh failures", String(runtimeMetrics.authRefreshFailures), "warning"),
|
|
2201
|
+
formatUiKeyValue(ui, "Account rotations", String(runtimeMetrics.accountRotations), "accent"),
|
|
2202
|
+
formatUiKeyValue(ui, "Same-account retries", String(runtimeMetrics.sameAccountRetries), "warning"),
|
|
2203
|
+
formatUiKeyValue(ui, "Stream failover attempts", String(runtimeMetrics.streamFailoverAttempts), "muted"),
|
|
2204
|
+
formatUiKeyValue(ui, "Stream failover recoveries", String(runtimeMetrics.streamFailoverRecoveries), "success"),
|
|
2205
|
+
formatUiKeyValue(ui, "Stream failover cross-account recoveries", String(runtimeMetrics.streamFailoverCrossAccountRecoveries), "accent"),
|
|
2206
|
+
formatUiKeyValue(ui, "Empty-response retries", String(runtimeMetrics.emptyResponseRetries), "warning"),
|
|
2207
|
+
formatUiKeyValue(ui, "Session affinity entries", String(sessionAffinityEntries), "muted"),
|
|
2208
|
+
formatUiKeyValue(ui, "Live sync", `${liveSyncSnapshot?.running ? "on" : "off"} (${liveSyncSnapshot?.reloadCount ?? 0} reloads)`, liveSyncSnapshot?.running ? "success" : "muted"),
|
|
2209
|
+
formatUiKeyValue(ui, "Refresh guardian", guardianStats
|
|
2210
|
+
? `on (${guardianStats.refreshed} refreshed, ${guardianStats.failed} failed)`
|
|
2211
|
+
: "off", guardianStats ? "success" : "muted"),
|
|
2212
|
+
formatUiKeyValue(ui, "Last upstream request", lastRequest, "muted"),
|
|
2213
|
+
];
|
|
2214
|
+
if (runtimeMetrics.lastError) {
|
|
2215
|
+
styled.push(formatUiKeyValue(ui, "Last error", runtimeMetrics.lastError, "danger"));
|
|
2216
|
+
}
|
|
2217
|
+
return Promise.resolve(styled.join("\n"));
|
|
2218
|
+
}
|
|
2219
|
+
return Promise.resolve(lines.join("\n"));
|
|
2220
|
+
},
|
|
2221
|
+
}),
|
|
2222
|
+
}
|
|
2223
|
+
: {}),
|
|
3020
2224
|
"codex-health": tool({
|
|
3021
2225
|
description: "Check health of all Codex accounts by validating refresh tokens.",
|
|
3022
2226
|
args: {},
|
|
3023
2227
|
async execute() {
|
|
3024
|
-
const ui =
|
|
2228
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
3025
2229
|
const storage = await loadAccounts();
|
|
3026
2230
|
if (!storage || storage.accounts.length === 0) {
|
|
3027
2231
|
if (ui.v2Enabled) {
|
|
@@ -3073,255 +2277,268 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
3073
2277
|
return results.join("\n");
|
|
3074
2278
|
},
|
|
3075
2279
|
}),
|
|
3076
|
-
...(exposeAdvancedCodexTools
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
2280
|
+
...(exposeAdvancedCodexTools
|
|
2281
|
+
? {
|
|
2282
|
+
"codex-remove": tool({
|
|
2283
|
+
description: "Remove a Codex account by index (1-based). Use codex-list to list accounts first.",
|
|
2284
|
+
args: {
|
|
2285
|
+
index: tool.schema
|
|
2286
|
+
.number()
|
|
2287
|
+
.describe("Account number to remove (1-based, e.g., 1 for first account)"),
|
|
2288
|
+
},
|
|
2289
|
+
async execute({ index }) {
|
|
2290
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2291
|
+
const storage = await loadAccounts();
|
|
2292
|
+
if (!storage || storage.accounts.length === 0) {
|
|
2293
|
+
if (ui.v2Enabled) {
|
|
2294
|
+
return [
|
|
2295
|
+
...formatUiHeader(ui, "Remove account"),
|
|
2296
|
+
"",
|
|
2297
|
+
formatUiItem(ui, "No accounts configured.", "warning"),
|
|
2298
|
+
].join("\n");
|
|
2299
|
+
}
|
|
2300
|
+
return "No Codex accounts configured. Nothing to remove.";
|
|
3092
2301
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
2302
|
+
const targetIndex = Math.floor((index ?? 0) - 1);
|
|
2303
|
+
if (!Number.isFinite(targetIndex) ||
|
|
2304
|
+
targetIndex < 0 ||
|
|
2305
|
+
targetIndex >= storage.accounts.length) {
|
|
2306
|
+
if (ui.v2Enabled) {
|
|
2307
|
+
return [
|
|
2308
|
+
...formatUiHeader(ui, "Remove account"),
|
|
2309
|
+
"",
|
|
2310
|
+
formatUiItem(ui, `Invalid account number: ${index}`, "danger"),
|
|
2311
|
+
formatUiKeyValue(ui, "Valid range", `1-${storage.accounts.length}`, "muted"),
|
|
2312
|
+
formatUiItem(ui, "Use codex-list to list all accounts.", "accent"),
|
|
2313
|
+
].join("\n");
|
|
2314
|
+
}
|
|
2315
|
+
return `Invalid account number: ${index}\n\nValid range: 1-${storage.accounts.length}\n\nUse codex-list to list all accounts.`;
|
|
3107
2316
|
}
|
|
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;
|
|
2317
|
+
const account = storage.accounts[targetIndex];
|
|
2318
|
+
if (!account) {
|
|
2319
|
+
return `Account ${index} not found.`;
|
|
3123
2320
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
2321
|
+
const label = formatAccountLabel(account, targetIndex);
|
|
2322
|
+
storage.accounts.splice(targetIndex, 1);
|
|
2323
|
+
if (storage.accounts.length === 0) {
|
|
2324
|
+
storage.activeIndex = 0;
|
|
2325
|
+
storage.activeIndexByFamily = {};
|
|
3126
2326
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
2327
|
+
else {
|
|
2328
|
+
if (storage.activeIndex >= storage.accounts.length) {
|
|
2329
|
+
storage.activeIndex = 0;
|
|
2330
|
+
}
|
|
2331
|
+
else if (storage.activeIndex > targetIndex) {
|
|
2332
|
+
storage.activeIndex -= 1;
|
|
2333
|
+
}
|
|
2334
|
+
if (storage.activeIndexByFamily) {
|
|
2335
|
+
for (const family of MODEL_FAMILIES) {
|
|
2336
|
+
const idx = storage.activeIndexByFamily[family];
|
|
2337
|
+
if (typeof idx === "number") {
|
|
2338
|
+
if (idx >= storage.accounts.length) {
|
|
2339
|
+
storage.activeIndexByFamily[family] = 0;
|
|
2340
|
+
}
|
|
2341
|
+
else if (idx > targetIndex) {
|
|
2342
|
+
storage.activeIndexByFamily[family] = idx - 1;
|
|
2343
|
+
}
|
|
3136
2344
|
}
|
|
3137
2345
|
}
|
|
3138
2346
|
}
|
|
3139
2347
|
}
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
2348
|
+
try {
|
|
2349
|
+
await saveAccounts(storage);
|
|
2350
|
+
}
|
|
2351
|
+
catch (saveError) {
|
|
2352
|
+
logWarn("Failed to save account removal", {
|
|
2353
|
+
error: String(saveError),
|
|
2354
|
+
});
|
|
2355
|
+
if (ui.v2Enabled) {
|
|
2356
|
+
return [
|
|
2357
|
+
...formatUiHeader(ui, "Remove account"),
|
|
2358
|
+
"",
|
|
2359
|
+
formatUiItem(ui, `Removed ${formatAccountLabel(account, targetIndex)} from memory`, "warning"),
|
|
2360
|
+
formatUiItem(ui, "Failed to persist. Change may be lost on restart.", "danger"),
|
|
2361
|
+
].join("\n");
|
|
2362
|
+
}
|
|
2363
|
+
return `Removed ${formatAccountLabel(account, targetIndex)} from memory but failed to persist. Changes may be lost on restart.`;
|
|
2364
|
+
}
|
|
2365
|
+
if (cachedAccountManager) {
|
|
2366
|
+
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
|
|
2367
|
+
}
|
|
2368
|
+
const remaining = storage.accounts.length;
|
|
3146
2369
|
if (ui.v2Enabled) {
|
|
3147
2370
|
return [
|
|
3148
2371
|
...formatUiHeader(ui, "Remove account"),
|
|
3149
2372
|
"",
|
|
3150
|
-
formatUiItem(ui,
|
|
3151
|
-
|
|
2373
|
+
formatUiItem(ui, `${getStatusMarker(ui, "ok")} Removed: ${label}`, "success"),
|
|
2374
|
+
remaining > 0
|
|
2375
|
+
? formatUiKeyValue(ui, "Remaining accounts", String(remaining))
|
|
2376
|
+
: formatUiItem(ui, "No accounts remaining. Run: codex login", "warning"),
|
|
3152
2377
|
].join("\n");
|
|
3153
2378
|
}
|
|
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
2379
|
return [
|
|
3162
|
-
|
|
2380
|
+
`Removed: ${label}`,
|
|
3163
2381
|
"",
|
|
3164
|
-
formatUiItem(ui, `${getStatusMarker(ui, "ok")} Removed: ${label}`, "success"),
|
|
3165
2382
|
remaining > 0
|
|
3166
|
-
?
|
|
3167
|
-
:
|
|
2383
|
+
? `Remaining accounts: ${remaining}`
|
|
2384
|
+
: "No accounts remaining. Run: codex login",
|
|
3168
2385
|
].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");
|
|
2386
|
+
},
|
|
2387
|
+
}),
|
|
2388
|
+
"codex-refresh": tool({
|
|
2389
|
+
description: "Manually refresh OAuth tokens for all accounts to verify they're still valid.",
|
|
2390
|
+
args: {},
|
|
2391
|
+
async execute() {
|
|
2392
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2393
|
+
const storage = await loadAccounts();
|
|
2394
|
+
if (!storage || storage.accounts.length === 0) {
|
|
2395
|
+
if (ui.v2Enabled) {
|
|
2396
|
+
return [
|
|
2397
|
+
...formatUiHeader(ui, "Refresh accounts"),
|
|
2398
|
+
"",
|
|
2399
|
+
formatUiItem(ui, "No accounts configured.", "warning"),
|
|
2400
|
+
formatUiItem(ui, "Run: codex login", "accent"),
|
|
2401
|
+
].join("\n");
|
|
2402
|
+
}
|
|
2403
|
+
return "No Codex accounts configured. Run: codex login";
|
|
3193
2404
|
}
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
2405
|
+
const results = ui.v2Enabled
|
|
2406
|
+
? []
|
|
2407
|
+
: [`Refreshing ${storage.accounts.length} account(s):`, ""];
|
|
2408
|
+
let refreshedCount = 0;
|
|
2409
|
+
let failedCount = 0;
|
|
2410
|
+
for (let i = 0; i < storage.accounts.length; i++) {
|
|
2411
|
+
const account = storage.accounts[i];
|
|
2412
|
+
if (!account)
|
|
2413
|
+
continue;
|
|
2414
|
+
const label = formatAccountLabel(account, i);
|
|
2415
|
+
try {
|
|
2416
|
+
const refreshResult = await queuedRefresh(account.refreshToken);
|
|
2417
|
+
if (refreshResult.type === "success") {
|
|
2418
|
+
account.refreshToken = refreshResult.refresh;
|
|
2419
|
+
account.accessToken = refreshResult.access;
|
|
2420
|
+
account.expiresAt = refreshResult.expires;
|
|
2421
|
+
results.push(` ${getStatusMarker(ui, "ok")} ${label}: Refreshed`);
|
|
2422
|
+
refreshedCount++;
|
|
2423
|
+
}
|
|
2424
|
+
else {
|
|
2425
|
+
results.push(` ${getStatusMarker(ui, "error")} ${label}: Failed - ${refreshResult.message ?? refreshResult.reason}`);
|
|
2426
|
+
failedCount++;
|
|
2427
|
+
}
|
|
3214
2428
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
2429
|
+
catch (error) {
|
|
2430
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2431
|
+
results.push(` ${getStatusMarker(ui, "error")} ${label}: Error - ${errorMsg.slice(0, 120)}`);
|
|
3217
2432
|
failedCount++;
|
|
3218
2433
|
}
|
|
3219
2434
|
}
|
|
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");
|
|
2435
|
+
await saveAccounts(storage);
|
|
2436
|
+
if (cachedAccountManager) {
|
|
2437
|
+
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps());
|
|
3261
2438
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
catch (error) {
|
|
3265
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
2439
|
+
results.push("");
|
|
2440
|
+
results.push(`Summary: ${refreshedCount} refreshed, ${failedCount} failed`);
|
|
3266
2441
|
if (ui.v2Enabled) {
|
|
3267
2442
|
return [
|
|
3268
|
-
...formatUiHeader(ui, "
|
|
2443
|
+
...formatUiHeader(ui, "Refresh accounts"),
|
|
3269
2444
|
"",
|
|
3270
|
-
|
|
3271
|
-
formatUiKeyValue(ui, "Error", msg, "danger"),
|
|
2445
|
+
...results.map((line) => paintUiText(ui, line, "normal")),
|
|
3272
2446
|
].join("\n");
|
|
3273
2447
|
}
|
|
3274
|
-
return
|
|
3275
|
-
}
|
|
3276
|
-
},
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
2448
|
+
return results.join("\n");
|
|
2449
|
+
},
|
|
2450
|
+
}),
|
|
2451
|
+
"codex-export": tool({
|
|
2452
|
+
description: "Export accounts to a JSON file for backup or migration to another machine.",
|
|
2453
|
+
args: {
|
|
2454
|
+
path: tool.schema
|
|
2455
|
+
.string()
|
|
2456
|
+
.describe("File path to export to (e.g., ~/codex-backup.json)"),
|
|
2457
|
+
force: tool.schema
|
|
2458
|
+
.boolean()
|
|
2459
|
+
.optional()
|
|
2460
|
+
.describe("Overwrite existing file (default: true)"),
|
|
2461
|
+
},
|
|
2462
|
+
async execute({ path: filePath, force }) {
|
|
2463
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2464
|
+
try {
|
|
2465
|
+
await exportAccounts(filePath, force ?? true);
|
|
2466
|
+
const storage = await loadAccounts();
|
|
2467
|
+
const count = storage?.accounts.length ?? 0;
|
|
2468
|
+
if (ui.v2Enabled) {
|
|
2469
|
+
return [
|
|
2470
|
+
...formatUiHeader(ui, "Export accounts"),
|
|
2471
|
+
"",
|
|
2472
|
+
formatUiItem(ui, `${getStatusMarker(ui, "ok")} Exported ${count} account(s)`, "success"),
|
|
2473
|
+
formatUiKeyValue(ui, "Path", filePath, "muted"),
|
|
2474
|
+
].join("\n");
|
|
2475
|
+
}
|
|
2476
|
+
return `Exported ${count} account(s) to: ${filePath}`;
|
|
3291
2477
|
}
|
|
3292
|
-
|
|
3293
|
-
|
|
2478
|
+
catch (error) {
|
|
2479
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2480
|
+
if (ui.v2Enabled) {
|
|
2481
|
+
return [
|
|
2482
|
+
...formatUiHeader(ui, "Export accounts"),
|
|
2483
|
+
"",
|
|
2484
|
+
formatUiItem(ui, `${getStatusMarker(ui, "error")} Export failed`, "danger"),
|
|
2485
|
+
formatUiKeyValue(ui, "Error", msg, "danger"),
|
|
2486
|
+
].join("\n");
|
|
2487
|
+
}
|
|
2488
|
+
return `Export failed: ${msg}`;
|
|
3294
2489
|
}
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
2490
|
+
},
|
|
2491
|
+
}),
|
|
2492
|
+
"codex-import": tool({
|
|
2493
|
+
description: "Import accounts from a JSON file, merging with existing accounts.",
|
|
2494
|
+
args: {
|
|
2495
|
+
path: tool.schema
|
|
2496
|
+
.string()
|
|
2497
|
+
.describe("File path to import from (e.g., ~/codex-backup.json)"),
|
|
2498
|
+
},
|
|
2499
|
+
async execute({ path: filePath }) {
|
|
2500
|
+
const ui = applyUiRuntimeFromConfig(loadPluginConfig(), setUiRuntimeOptions);
|
|
2501
|
+
try {
|
|
2502
|
+
const result = await importAccounts(filePath);
|
|
2503
|
+
invalidateRuntimeAccountManagerCache(accountManagerCacheInvalidationDeps);
|
|
2504
|
+
const lines = [`Import complete.`, ``];
|
|
2505
|
+
if (result.imported > 0) {
|
|
2506
|
+
lines.push(`New accounts: ${result.imported}`);
|
|
2507
|
+
}
|
|
2508
|
+
if (result.skipped > 0) {
|
|
2509
|
+
lines.push(`Duplicates skipped: ${result.skipped}`);
|
|
2510
|
+
}
|
|
2511
|
+
lines.push(`Total accounts: ${result.total}`);
|
|
2512
|
+
if (ui.v2Enabled) {
|
|
2513
|
+
const styled = [
|
|
2514
|
+
...formatUiHeader(ui, "Import accounts"),
|
|
2515
|
+
"",
|
|
2516
|
+
formatUiItem(ui, `${getStatusMarker(ui, "ok")} Import complete`, "success"),
|
|
2517
|
+
formatUiKeyValue(ui, "Path", filePath, "muted"),
|
|
2518
|
+
formatUiKeyValue(ui, "New accounts", String(result.imported), result.imported > 0 ? "success" : "muted"),
|
|
2519
|
+
formatUiKeyValue(ui, "Duplicates skipped", String(result.skipped), result.skipped > 0 ? "warning" : "muted"),
|
|
2520
|
+
formatUiKeyValue(ui, "Total accounts", String(result.total), "accent"),
|
|
2521
|
+
];
|
|
2522
|
+
return styled.join("\n");
|
|
2523
|
+
}
|
|
2524
|
+
return lines.join("\n");
|
|
3307
2525
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
2526
|
+
catch (error) {
|
|
2527
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2528
|
+
if (ui.v2Enabled) {
|
|
2529
|
+
return [
|
|
2530
|
+
...formatUiHeader(ui, "Import accounts"),
|
|
2531
|
+
"",
|
|
2532
|
+
formatUiItem(ui, `${getStatusMarker(ui, "error")} Import failed`, "danger"),
|
|
2533
|
+
formatUiKeyValue(ui, "Error", msg, "danger"),
|
|
2534
|
+
].join("\n");
|
|
2535
|
+
}
|
|
2536
|
+
return `Import failed: ${msg}`;
|
|
3319
2537
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
}),
|
|
3324
|
-
} : {}),
|
|
2538
|
+
},
|
|
2539
|
+
}),
|
|
2540
|
+
}
|
|
2541
|
+
: {}),
|
|
3325
2542
|
},
|
|
3326
2543
|
};
|
|
3327
2544
|
};
|