codex-multi-auth 2.3.2 → 2.4.0
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/.codex-plugin/plugin.json +1 -1
- package/README.md +4 -1
- package/dist/lib/accounts.d.ts +14 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +59 -4
- package/dist/lib/constants.d.ts +7 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +7 -0
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +15 -6
- package/dist/lib/local-client-tokens.d.ts.map +1 -1
- package/dist/lib/local-client-tokens.js +11 -4
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +5 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +29 -3
- package/dist/lib/refresh-lease.d.ts +1 -0
- package/dist/lib/refresh-lease.d.ts.map +1 -1
- package/dist/lib/refresh-lease.js +35 -4
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +3 -2
- package/dist/lib/request/rate-limit-decision.d.ts.map +1 -1
- package/dist/lib/request/rate-limit-decision.js +7 -2
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +63 -11
- package/dist/lib/runtime/rotation-proxy-state.d.ts +7 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +9 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +10 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +38 -1
- package/dist/lib/schemas.js +2 -2
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +6 -1
- package/package.json +1 -1
- package/scripts/codex.js +189 -8
- package/dist/index.js.map +0 -1
- package/dist/lib/account-policy.js.map +0 -1
- package/dist/lib/accounts/rate-limits.js.map +0 -1
- package/dist/lib/accounts.js.map +0 -1
- package/dist/lib/audit.js.map +0 -1
- package/dist/lib/auth/auth.js.map +0 -1
- package/dist/lib/auth/browser.js.map +0 -1
- package/dist/lib/auth/device-auth.js.map +0 -1
- package/dist/lib/auth/index.js.map +0 -1
- package/dist/lib/auth/org-override.js.map +0 -1
- package/dist/lib/auth/server.js.map +0 -1
- package/dist/lib/auth/token-utils.js.map +0 -1
- package/dist/lib/auth-rate-limit.js.map +0 -1
- package/dist/lib/budget-guard.js.map +0 -1
- package/dist/lib/capability-policy.js.map +0 -1
- package/dist/lib/circuit-breaker.js.map +0 -1
- package/dist/lib/cli.js.map +0 -1
- package/dist/lib/codex-cli/index.js.map +0 -1
- package/dist/lib/codex-cli/observability.js.map +0 -1
- package/dist/lib/codex-cli/state.js.map +0 -1
- package/dist/lib/codex-cli/sync.js.map +0 -1
- package/dist/lib/codex-cli/writer.js.map +0 -1
- package/dist/lib/codex-manager/account-manager-commands.js.map +0 -1
- package/dist/lib/codex-manager/account-pool-write.js.map +0 -1
- package/dist/lib/codex-manager/backend-category-entry.js.map +0 -1
- package/dist/lib/codex-manager/backend-category-helpers.js.map +0 -1
- package/dist/lib/codex-manager/backend-category-prompt.js.map +0 -1
- package/dist/lib/codex-manager/backend-settings-controller.js.map +0 -1
- package/dist/lib/codex-manager/backend-settings-entry.js.map +0 -1
- package/dist/lib/codex-manager/backend-settings-helpers.js.map +0 -1
- package/dist/lib/codex-manager/backend-settings-prompt.js.map +0 -1
- package/dist/lib/codex-manager/backend-settings-schema.js.map +0 -1
- package/dist/lib/codex-manager/behavior-settings-panel.js.map +0 -1
- package/dist/lib/codex-manager/commands/account.js.map +0 -1
- package/dist/lib/codex-manager/commands/best.js.map +0 -1
- package/dist/lib/codex-manager/commands/bridge.js.map +0 -1
- package/dist/lib/codex-manager/commands/budget.js.map +0 -1
- package/dist/lib/codex-manager/commands/check.js.map +0 -1
- package/dist/lib/codex-manager/commands/config-explain.js.map +0 -1
- package/dist/lib/codex-manager/commands/debug-bundle.js.map +0 -1
- package/dist/lib/codex-manager/commands/forecast.js.map +0 -1
- package/dist/lib/codex-manager/commands/init-config.js.map +0 -1
- package/dist/lib/codex-manager/commands/integrations.js.map +0 -1
- package/dist/lib/codex-manager/commands/models.js.map +0 -1
- package/dist/lib/codex-manager/commands/monitor.js.map +0 -1
- package/dist/lib/codex-manager/commands/report.js.map +0 -1
- package/dist/lib/codex-manager/commands/rotation.js.map +0 -1
- package/dist/lib/codex-manager/commands/status.js.map +0 -1
- package/dist/lib/codex-manager/commands/switch.js.map +0 -1
- package/dist/lib/codex-manager/commands/uninstall.js.map +0 -1
- package/dist/lib/codex-manager/commands/unpin.js.map +0 -1
- package/dist/lib/codex-manager/commands/usage.js.map +0 -1
- package/dist/lib/codex-manager/commands/verify.js.map +0 -1
- package/dist/lib/codex-manager/commands/why-selected.js.map +0 -1
- package/dist/lib/codex-manager/commands/workspace.js.map +0 -1
- package/dist/lib/codex-manager/dashboard-display-panel.js.map +0 -1
- package/dist/lib/codex-manager/dashboard-formatters.js.map +0 -1
- package/dist/lib/codex-manager/dashboard-settings-controller.js.map +0 -1
- package/dist/lib/codex-manager/dashboard-settings-data.js.map +0 -1
- package/dist/lib/codex-manager/dashboard-settings-entry.js.map +0 -1
- package/dist/lib/codex-manager/experimental-settings-entry.js.map +0 -1
- package/dist/lib/codex-manager/experimental-settings-prompt.js.map +0 -1
- package/dist/lib/codex-manager/experimental-settings-schema.js.map +0 -1
- package/dist/lib/codex-manager/experimental-sync-target-entry.js.map +0 -1
- package/dist/lib/codex-manager/experimental-sync-target.js.map +0 -1
- package/dist/lib/codex-manager/forecast-report-commands.d.ts +0 -54
- package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +0 -1
- package/dist/lib/codex-manager/forecast-report-commands.js +0 -443
- package/dist/lib/codex-manager/forecast-report-commands.js.map +0 -1
- package/dist/lib/codex-manager/forecast-report-shared.js.map +0 -1
- package/dist/lib/codex-manager/help.js.map +0 -1
- package/dist/lib/codex-manager/manual-callback.js.map +0 -1
- package/dist/lib/codex-manager/rate-limit-markers.js.map +0 -1
- package/dist/lib/codex-manager/repair-commands.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub/backend.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub/dashboard.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub/experimental.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub/index.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub/shared.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub-entry.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub-menu.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub-prompt.js.map +0 -1
- package/dist/lib/codex-manager/settings-hub.js.map +0 -1
- package/dist/lib/codex-manager/settings-panels.js.map +0 -1
- package/dist/lib/codex-manager/settings-persist-utils.js.map +0 -1
- package/dist/lib/codex-manager/settings-preview.js.map +0 -1
- package/dist/lib/codex-manager/settings-write-queue.js.map +0 -1
- package/dist/lib/codex-manager/statusline-order.d.ts +0 -3
- package/dist/lib/codex-manager/statusline-order.d.ts.map +0 -1
- package/dist/lib/codex-manager/statusline-order.js +0 -16
- package/dist/lib/codex-manager/statusline-order.js.map +0 -1
- package/dist/lib/codex-manager/statusline-settings-panel.js.map +0 -1
- package/dist/lib/codex-manager/theme-settings-panel.js.map +0 -1
- package/dist/lib/codex-manager/unified-settings-controller.js.map +0 -1
- package/dist/lib/codex-manager/unified-settings-entry.js.map +0 -1
- package/dist/lib/codex-manager.js.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/constants.js.map +0 -1
- package/dist/lib/context-overflow.js.map +0 -1
- package/dist/lib/dashboard-settings.js.map +0 -1
- package/dist/lib/entitlement-cache.js.map +0 -1
- package/dist/lib/env-parsing.js.map +0 -1
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/forecast.js.map +0 -1
- package/dist/lib/fs-retry.js.map +0 -1
- package/dist/lib/health.js.map +0 -1
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/integration-generators.js.map +0 -1
- package/dist/lib/live-account-sync.js.map +0 -1
- package/dist/lib/local-bridge.js.map +0 -1
- package/dist/lib/local-client-tokens.js.map +0 -1
- package/dist/lib/logger.js.map +0 -1
- package/dist/lib/model-capability-matrix.js.map +0 -1
- package/dist/lib/named-backup-export.js.map +0 -1
- package/dist/lib/oc-chatgpt-import-adapter.js.map +0 -1
- package/dist/lib/oc-chatgpt-orchestrator.js.map +0 -1
- package/dist/lib/oc-chatgpt-target-detection.js.map +0 -1
- package/dist/lib/parallel-probe.js.map +0 -1
- package/dist/lib/policy/runtime-policy.js.map +0 -1
- package/dist/lib/preemptive-quota-scheduler.js.map +0 -1
- package/dist/lib/proactive-refresh.js.map +0 -1
- package/dist/lib/prompts/codex-host-bridge.js.map +0 -1
- package/dist/lib/prompts/codex.js.map +0 -1
- package/dist/lib/prompts/fetch-utils.js.map +0 -1
- package/dist/lib/prompts/host-codex-prompt.js.map +0 -1
- package/dist/lib/quota-cache.js.map +0 -1
- package/dist/lib/quota-probe.js.map +0 -1
- package/dist/lib/quota-readiness.js.map +0 -1
- package/dist/lib/recovery/constants.js.map +0 -1
- package/dist/lib/recovery/index.d.ts +0 -12
- package/dist/lib/recovery/index.d.ts.map +0 -1
- package/dist/lib/recovery/index.js +0 -11
- package/dist/lib/recovery/index.js.map +0 -1
- package/dist/lib/recovery/storage.js.map +0 -1
- package/dist/lib/recovery/types.js.map +0 -1
- package/dist/lib/recovery.js.map +0 -1
- package/dist/lib/refresh-guardian.js.map +0 -1
- package/dist/lib/refresh-lease.js.map +0 -1
- package/dist/lib/refresh-queue.js.map +0 -1
- package/dist/lib/request/failover-config.js.map +0 -1
- package/dist/lib/request/failure-policy.js.map +0 -1
- package/dist/lib/request/fetch-helpers.js.map +0 -1
- package/dist/lib/request/helpers/input-utils.js.map +0 -1
- package/dist/lib/request/helpers/model-map.js.map +0 -1
- package/dist/lib/request/helpers/tool-utils.js.map +0 -1
- package/dist/lib/request/index.js.map +0 -1
- package/dist/lib/request/rate-limit-backoff.js.map +0 -1
- package/dist/lib/request/request-attempt-budget.js.map +0 -1
- package/dist/lib/request/request-init.js.map +0 -1
- package/dist/lib/request/request-resilience.js.map +0 -1
- package/dist/lib/request/request-transformer.js.map +0 -1
- package/dist/lib/request/response-compaction.js.map +0 -1
- package/dist/lib/request/response-handler.js.map +0 -1
- package/dist/lib/request/response-metadata.js.map +0 -1
- package/dist/lib/request/stream-failover.js.map +0 -1
- package/dist/lib/request/wait-utils.js.map +0 -1
- package/dist/lib/rotation.js.map +0 -1
- package/dist/lib/routing-mutex.js.map +0 -1
- package/dist/lib/routing-profiles.js.map +0 -1
- package/dist/lib/runtime/account-check-helpers.js.map +0 -1
- package/dist/lib/runtime/account-check-types.js.map +0 -1
- package/dist/lib/runtime/account-check.js.map +0 -1
- package/dist/lib/runtime/account-health-check.d.ts +0 -8
- package/dist/lib/runtime/account-health-check.d.ts.map +0 -1
- package/dist/lib/runtime/account-health-check.js +0 -29
- package/dist/lib/runtime/account-health-check.js.map +0 -1
- package/dist/lib/runtime/account-manager-cache-entry.js.map +0 -1
- package/dist/lib/runtime/account-manager-cache.js.map +0 -1
- package/dist/lib/runtime/account-pool.js.map +0 -1
- package/dist/lib/runtime/account-scope.js.map +0 -1
- package/dist/lib/runtime/account-select-event.js.map +0 -1
- package/dist/lib/runtime/account-selection.js.map +0 -1
- package/dist/lib/runtime/account-state.js.map +0 -1
- package/dist/lib/runtime/account-status.js.map +0 -1
- package/dist/lib/runtime/account-storage-scope-entry.js.map +0 -1
- package/dist/lib/runtime/app-bind.js.map +0 -1
- package/dist/lib/runtime/auth-facade.js.map +0 -1
- package/dist/lib/runtime/browser-oauth-flow.js.map +0 -1
- package/dist/lib/runtime/capability-boost.js.map +0 -1
- package/dist/lib/runtime/config-toml.js.map +0 -1
- package/dist/lib/runtime/event-handler.js.map +0 -1
- package/dist/lib/runtime/flagged-verify-types.js.map +0 -1
- package/dist/lib/runtime/hydrate-emails.js.map +0 -1
- package/dist/lib/runtime/live-sync-entry.js.map +0 -1
- package/dist/lib/runtime/live-sync.js.map +0 -1
- package/dist/lib/runtime/loader-setup.js.map +0 -1
- package/dist/lib/runtime/login-menu-accounts.js.map +0 -1
- package/dist/lib/runtime/manual-oauth-flow.js.map +0 -1
- package/dist/lib/runtime/oauth-browser-flow.d.ts +0 -9
- package/dist/lib/runtime/oauth-browser-flow.d.ts.map +0 -1
- package/dist/lib/runtime/oauth-browser-flow.js +0 -33
- package/dist/lib/runtime/oauth-browser-flow.js.map +0 -1
- package/dist/lib/runtime/preemptive-quota.js.map +0 -1
- package/dist/lib/runtime/quota-headers.js.map +0 -1
- package/dist/lib/runtime/quota-probe.js.map +0 -1
- package/dist/lib/runtime/quota-settings.js.map +0 -1
- package/dist/lib/runtime/refresh-guardian-entry.js.map +0 -1
- package/dist/lib/runtime/refresh-guardian.js.map +0 -1
- package/dist/lib/runtime/request-init.js.map +0 -1
- package/dist/lib/runtime/runtime-current-account.js.map +0 -1
- package/dist/lib/runtime/runtime-observability.js.map +0 -1
- package/dist/lib/runtime/runtime-services.js.map +0 -1
- package/dist/lib/runtime/session-affinity-entry.js.map +0 -1
- package/dist/lib/runtime/session-affinity.d.ts +0 -13
- package/dist/lib/runtime/session-affinity.d.ts.map +0 -1
- package/dist/lib/runtime/session-affinity.js +0 -16
- package/dist/lib/runtime/session-affinity.js.map +0 -1
- package/dist/lib/runtime/session-recovery.js.map +0 -1
- package/dist/lib/runtime/status-marker.js.map +0 -1
- package/dist/lib/runtime/storage-scope.js.map +0 -1
- package/dist/lib/runtime/toast.js.map +0 -1
- package/dist/lib/runtime/ui-runtime-entry.js.map +0 -1
- package/dist/lib/runtime/ui-runtime.js.map +0 -1
- package/dist/lib/runtime/verify-flagged.js.map +0 -1
- package/dist/lib/runtime-constants.js.map +0 -1
- package/dist/lib/runtime-paths.js.map +0 -1
- package/dist/lib/runtime-rotation-proxy.js.map +0 -1
- package/dist/lib/schemas.js.map +0 -1
- package/dist/lib/session-affinity.js.map +0 -1
- package/dist/lib/shutdown.js.map +0 -1
- package/dist/lib/storage/account-clear-entry.js.map +0 -1
- package/dist/lib/storage/account-clear.js.map +0 -1
- package/dist/lib/storage/account-match-utils.js.map +0 -1
- package/dist/lib/storage/account-persistence.js.map +0 -1
- package/dist/lib/storage/account-port.js.map +0 -1
- package/dist/lib/storage/account-save-entry.js.map +0 -1
- package/dist/lib/storage/account-save.js.map +0 -1
- package/dist/lib/storage/account-snapshot.js.map +0 -1
- package/dist/lib/storage/backup-metadata-builder.js.map +0 -1
- package/dist/lib/storage/backup-metadata.js.map +0 -1
- package/dist/lib/storage/backup-paths.js.map +0 -1
- package/dist/lib/storage/backup-restore.js.map +0 -1
- package/dist/lib/storage/cache-artifacts.js.map +0 -1
- package/dist/lib/storage/error-hints.js.map +0 -1
- package/dist/lib/storage/file-paths.js.map +0 -1
- package/dist/lib/storage/fixture-guards.js.map +0 -1
- package/dist/lib/storage/flagged-entry.js.map +0 -1
- package/dist/lib/storage/flagged-load-entry.js.map +0 -1
- package/dist/lib/storage/flagged-save-entry.js.map +0 -1
- package/dist/lib/storage/flagged-storage-file.js.map +0 -1
- package/dist/lib/storage/flagged-storage-io.js.map +0 -1
- package/dist/lib/storage/flagged-storage.js.map +0 -1
- package/dist/lib/storage/gitignore.js.map +0 -1
- package/dist/lib/storage/hash.js.map +0 -1
- package/dist/lib/storage/health.js.map +0 -1
- package/dist/lib/storage/identity.js.map +0 -1
- package/dist/lib/storage/import-export.js.map +0 -1
- package/dist/lib/storage/metadata-section.js.map +0 -1
- package/dist/lib/storage/migrations.js.map +0 -1
- package/dist/lib/storage/named-backup-entry.js.map +0 -1
- package/dist/lib/storage/named-backups-entry.js.map +0 -1
- package/dist/lib/storage/named-backups.js.map +0 -1
- package/dist/lib/storage/path-state.js.map +0 -1
- package/dist/lib/storage/paths.js.map +0 -1
- package/dist/lib/storage/project-migration.js.map +0 -1
- package/dist/lib/storage/record-utils.js.map +0 -1
- package/dist/lib/storage/restore-assessment.js.map +0 -1
- package/dist/lib/storage/restore-backup-entry.js.map +0 -1
- package/dist/lib/storage/restore-metadata.js.map +0 -1
- package/dist/lib/storage/restore.d.ts +0 -14
- package/dist/lib/storage/restore.d.ts.map +0 -1
- package/dist/lib/storage/restore.js +0 -52
- package/dist/lib/storage/restore.js.map +0 -1
- package/dist/lib/storage/snapshot-inspectors.js.map +0 -1
- package/dist/lib/storage/storage-parser.js.map +0 -1
- package/dist/lib/storage/transactions.js.map +0 -1
- package/dist/lib/storage.js.map +0 -1
- package/dist/lib/table-formatter.js.map +0 -1
- package/dist/lib/tools/hashline-tools.js.map +0 -1
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/ui/ansi.js.map +0 -1
- package/dist/lib/ui/auth-menu.js.map +0 -1
- package/dist/lib/ui/confirm.js.map +0 -1
- package/dist/lib/ui/copy.d.ts +0 -159
- package/dist/lib/ui/copy.d.ts.map +0 -1
- package/dist/lib/ui/copy.js +0 -164
- package/dist/lib/ui/copy.js.map +0 -1
- package/dist/lib/ui/display-width.js.map +0 -1
- package/dist/lib/ui/format.js.map +0 -1
- package/dist/lib/ui/runtime.js.map +0 -1
- package/dist/lib/ui/select.js.map +0 -1
- package/dist/lib/ui/theme.js.map +0 -1
- package/dist/lib/unified-settings.js.map +0 -1
- package/dist/lib/update-notice.js.map +0 -1
- package/dist/lib/usage/index.js.map +0 -1
- package/dist/lib/usage/ledger.js.map +0 -1
- package/dist/lib/usage/pricing.js.map +0 -1
- package/dist/lib/usage/redaction.js.map +0 -1
- package/dist/lib/usage/types.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger, logRequest, LOGGING_ENABLED } from "../logger.js";
|
|
2
|
-
import { PLUGIN_NAME } from "../constants.js";
|
|
2
|
+
import { HTTP_STATUS, PLUGIN_NAME } from "../constants.js";
|
|
3
3
|
import { isRecord } from "../utils.js";
|
|
4
4
|
const log = createLogger("response-handler");
|
|
5
5
|
const MAX_SSE_SIZE = 10 * 1024 * 1024; // 10MB limit to prevent memory exhaustion
|
|
@@ -456,7 +456,23 @@ function maybeCaptureResponseEvent(state, data, onResponseId) {
|
|
|
456
456
|
state.encounteredError = true;
|
|
457
457
|
return;
|
|
458
458
|
}
|
|
459
|
-
|
|
459
|
+
// response.failed is a genuine terminal failure: the turn did not produce a
|
|
460
|
+
// usable result even though HTTP opened 200. Treat it as an error so the
|
|
461
|
+
// stream is not misclassified as a successful response and misattributed as
|
|
462
|
+
// an account success (stress audit H7).
|
|
463
|
+
if (data.type === "response.failed") {
|
|
464
|
+
log.warn("SSE terminal failure event received", { type: data.type });
|
|
465
|
+
state.encounteredError = true;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// response.completed / response.done / response.incomplete are all terminal
|
|
469
|
+
// envelopes that carry a final response object. response.incomplete (e.g.
|
|
470
|
+
// hitting max_output_tokens or a content filter) is a NORMAL early stop whose
|
|
471
|
+
// partial output is the answer — it must be delivered to the client and
|
|
472
|
+
// counts as a healthy account, not a failure (re-audit correction to H7).
|
|
473
|
+
if (data.type === "response.done" ||
|
|
474
|
+
data.type === "response.completed" ||
|
|
475
|
+
data.type === "response.incomplete") {
|
|
460
476
|
if (isRecord(data.response)) {
|
|
461
477
|
state.finalResponse = { ...data.response };
|
|
462
478
|
}
|
|
@@ -518,15 +534,19 @@ function parseSseStream(sseText, onResponseId) {
|
|
|
518
534
|
let firstMalformedSample = null;
|
|
519
535
|
for (const line of lines) {
|
|
520
536
|
const trimmedLine = line.trim();
|
|
521
|
-
|
|
522
|
-
|
|
537
|
+
// Accept "data:" with or without the optional trailing space. The SSE
|
|
538
|
+
// spec allows "data:value"; requiring the space silently dropped every
|
|
539
|
+
// event on an upstream/proxy formatting change (stress audit M1).
|
|
540
|
+
if (trimmedLine.startsWith("data:")) {
|
|
541
|
+
const payload = trimmedLine.substring(5).trim();
|
|
523
542
|
if (!payload || payload === "[DONE]")
|
|
524
543
|
continue;
|
|
525
544
|
try {
|
|
526
545
|
const data = JSON.parse(payload);
|
|
527
546
|
maybeCaptureResponseEvent(state, data, onResponseId);
|
|
528
|
-
if (state.encounteredError)
|
|
529
|
-
return null;
|
|
547
|
+
if (state.encounteredError) {
|
|
548
|
+
return { finalResponse: null, encounteredError: true };
|
|
549
|
+
}
|
|
530
550
|
}
|
|
531
551
|
catch (error) {
|
|
532
552
|
// AUDIT-H9 / H-03: previously these malformed chunks were
|
|
@@ -553,7 +573,10 @@ function parseSseStream(sseText, onResponseId) {
|
|
|
553
573
|
firstSample: firstMalformedSample,
|
|
554
574
|
});
|
|
555
575
|
}
|
|
556
|
-
return
|
|
576
|
+
return {
|
|
577
|
+
finalResponse: finalizeParsedResponse(state),
|
|
578
|
+
encounteredError: state.encounteredError,
|
|
579
|
+
};
|
|
557
580
|
}
|
|
558
581
|
/**
|
|
559
582
|
* Convert SSE stream response to JSON for generateText()
|
|
@@ -597,13 +620,42 @@ export async function convertSseToJson(response, headers, options) {
|
|
|
597
620
|
logRequest("stream-full", { fullContent: fullText });
|
|
598
621
|
}
|
|
599
622
|
// Parse SSE events to extract the final response
|
|
600
|
-
const finalResponse = parseSseStream(fullText, options?.onResponseId);
|
|
623
|
+
const { finalResponse, encounteredError } = parseSseStream(fullText, options?.onResponseId);
|
|
624
|
+
// A terminal failure event (mid-stream `error`, `response.failed`, or
|
|
625
|
+
// `response.incomplete`) means the upstream turn failed even though HTTP
|
|
626
|
+
// opened 200. Returning the raw SSE at 200 here makes the proxy record an
|
|
627
|
+
// account success and skip rotation/retry (stress audit H6+H7). Synthesize
|
|
628
|
+
// a non-2xx so the caller routes to failure. (A stream that simply yields
|
|
629
|
+
// no final response WITHOUT an error — empty/truncated — is left as the
|
|
630
|
+
// original passthrough below so the empty-response retry path still runs.)
|
|
631
|
+
if (encounteredError) {
|
|
632
|
+
log.warn("SSE stream ended with a terminal failure event");
|
|
633
|
+
logRequest("stream-error", {
|
|
634
|
+
error: "SSE stream terminated with an error/failed/incomplete event",
|
|
635
|
+
});
|
|
636
|
+
const upstreamFailed = typeof response.status === "number" && response.status >= 400;
|
|
637
|
+
const status = upstreamFailed ? response.status : HTTP_STATUS.BAD_GATEWAY;
|
|
638
|
+
const errorHeaders = new Headers(headers);
|
|
639
|
+
errorHeaders.set("content-type", "application/json; charset=utf-8");
|
|
640
|
+
return new Response(JSON.stringify({
|
|
641
|
+
error: {
|
|
642
|
+
message: "Upstream SSE stream terminated with a failure event",
|
|
643
|
+
type: "upstream_stream_error",
|
|
644
|
+
code: "sse_terminal_error",
|
|
645
|
+
},
|
|
646
|
+
}), {
|
|
647
|
+
status,
|
|
648
|
+
statusText: upstreamFailed ? response.statusText : "Bad Gateway",
|
|
649
|
+
headers: errorHeaders,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
601
652
|
if (!finalResponse) {
|
|
602
653
|
log.warn("Could not find final response in SSE stream");
|
|
603
654
|
logRequest("stream-error", {
|
|
604
655
|
error: "No terminal response event found in SSE stream",
|
|
605
656
|
});
|
|
606
|
-
// Return original stream if we can't parse
|
|
657
|
+
// Return original stream if we can't parse (no error event, just no
|
|
658
|
+
// terminal response). Preserves the empty-response retry path.
|
|
607
659
|
return new Response(fullText, {
|
|
608
660
|
status: response.status,
|
|
609
661
|
statusText: response.statusText,
|
|
@@ -650,9 +702,9 @@ function createResponseIdCapturingStream(body, onResponseId) {
|
|
|
650
702
|
}
|
|
651
703
|
for (const rawLine of lines) {
|
|
652
704
|
const trimmedLine = rawLine.trim();
|
|
653
|
-
if (!trimmedLine.startsWith("data:
|
|
705
|
+
if (!trimmedLine.startsWith("data:"))
|
|
654
706
|
continue;
|
|
655
|
-
const payload = trimmedLine.slice(
|
|
707
|
+
const payload = trimmedLine.slice(5).trim();
|
|
656
708
|
if (!payload || payload === "[DONE]")
|
|
657
709
|
continue;
|
|
658
710
|
try {
|
|
@@ -29,6 +29,13 @@ export interface RotationProxyStateInit {
|
|
|
29
29
|
quotaRemainingPercentThreshold: number;
|
|
30
30
|
sessionAffinityStore: SessionAffinityStore | null;
|
|
31
31
|
lastObservedAffinityGeneration: number;
|
|
32
|
+
/**
|
|
33
|
+
* Ephemeral per-invocation account pin (0-based) or null. When a number,
|
|
34
|
+
* the request handler treats it exactly like a manual `switch` pin — a
|
|
35
|
+
* deterministic, fail-hard selection — but it is never written to disk, so
|
|
36
|
+
* concurrent invocations with different pins never interfere (issue #623).
|
|
37
|
+
*/
|
|
38
|
+
forcedAccountIndex: number | null;
|
|
32
39
|
}
|
|
33
40
|
/**
|
|
34
41
|
* Mutable rotation-loop state shared by every request of one proxy instance.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
|
|
@@ -32,6 +32,15 @@ export interface RuntimeRotationProxyOptions {
|
|
|
32
32
|
maxRequestBodyBytes?: number;
|
|
33
33
|
fetchTimeoutMs?: number;
|
|
34
34
|
streamStallTimeoutMs?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Ephemeral, per-instance account pin (0-based) for a single invocation
|
|
37
|
+
* (issue #623: `codex-multi-auth-codex --account`). When set, this proxy
|
|
38
|
+
* routes every request to exactly this account and never rotates, without
|
|
39
|
+
* touching the persisted `switch` pin on disk. Falls back to
|
|
40
|
+
* `CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX` in the environment when omitted so
|
|
41
|
+
* the value survives the launcher -> detached app-helper process boundary.
|
|
42
|
+
*/
|
|
43
|
+
forcedAccountIndex?: number | null;
|
|
35
44
|
}
|
|
36
45
|
export interface RequestContext {
|
|
37
46
|
body: Buffer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;CAC5C;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;CAC5C;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -5,5 +5,15 @@ export type { PinnedUnavailableErrorBody } from "./request/rate-limit-decision.j
|
|
|
5
5
|
export { chooseAccount } from "./runtime/rotation-account-selection.js";
|
|
6
6
|
export { maybeInvalidateAffinityFromDisk, readPinnedAccountIndexFromDisk, readStorageMetaFromDisk, resetPinCacheForTesting, } from "./runtime/rotation-storage-meta.js";
|
|
7
7
|
export type { StorageMeta } from "./runtime/rotation-storage-meta.js";
|
|
8
|
+
/**
|
|
9
|
+
* Coerce a forced-account pin from an option (number) or the launcher's env
|
|
10
|
+
* string into a normalized 0-based index, or null when absent/invalid. Invalid
|
|
11
|
+
* or negative values collapse to null so an out-of-range env value can never
|
|
12
|
+
* throw at proxy startup; chooseAccount reports the deterministic
|
|
13
|
+
* pinned-account failure per-request instead. Exported for unit tests.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeForcedAccountIndex(value: number | string | null | undefined): number | null;
|
|
8
18
|
export declare function startRuntimeRotationProxy(options: RuntimeRotationProxyOptions): Promise<RuntimeRotationProxyServer>;
|
|
9
19
|
//# sourceMappingURL=runtime-rotation-proxy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAwEA,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AA6etE,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,
|
|
1
|
+
{"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAwEA,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AA6etE;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA6JrC"}
|
|
@@ -420,6 +420,25 @@ function writePoolExhausted(params) {
|
|
|
420
420
|
},
|
|
421
421
|
});
|
|
422
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Coerce a forced-account pin from an option (number) or the launcher's env
|
|
425
|
+
* string into a normalized 0-based index, or null when absent/invalid. Invalid
|
|
426
|
+
* or negative values collapse to null so an out-of-range env value can never
|
|
427
|
+
* throw at proxy startup; chooseAccount reports the deterministic
|
|
428
|
+
* pinned-account failure per-request instead. Exported for unit tests.
|
|
429
|
+
*
|
|
430
|
+
* @internal
|
|
431
|
+
*/
|
|
432
|
+
export function normalizeForcedAccountIndex(value) {
|
|
433
|
+
if (value === null || value === undefined) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
const parsed = typeof value === "number" ? value : Number.parseInt(value.trim(), 10);
|
|
437
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
return parsed;
|
|
441
|
+
}
|
|
423
442
|
export async function startRuntimeRotationProxy(options) {
|
|
424
443
|
const pluginConfig = loadPluginConfig();
|
|
425
444
|
const activeAccountManager = options.accountManager ?? (await AccountManager.loadFromDisk());
|
|
@@ -455,6 +474,17 @@ export async function startRuntimeRotationProxy(options) {
|
|
|
455
474
|
throw new CodexValidationError("Runtime rotation proxy requires a clientApiKey.", { field: "clientApiKey", expected: "a non-empty string" });
|
|
456
475
|
}
|
|
457
476
|
const now = options.now ?? Date.now;
|
|
477
|
+
// Ephemeral per-invocation pin (issue #623). Prefer an explicit option (used by
|
|
478
|
+
// tests and the in-process inline proxy); otherwise fall back to the numeric env
|
|
479
|
+
// the launcher publishes, which is how the value reaches a detached app-helper
|
|
480
|
+
// process. `??` means both `undefined` and an explicit `null` option defer to the
|
|
481
|
+
// env, so "no option" and "no pin" behave identically. Invalid/negative values
|
|
482
|
+
// are ignored (treated as "no forced pin") rather than throwing — the launcher
|
|
483
|
+
// already validated the selector, and an out-of-range index is surfaced
|
|
484
|
+
// per-request as a deterministic pinned-account failure by chooseAccount rather
|
|
485
|
+
// than crashing proxy startup.
|
|
486
|
+
const forcedAccountIndex = normalizeForcedAccountIndex(options.forcedAccountIndex ??
|
|
487
|
+
process.env.CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX);
|
|
458
488
|
const tokenRefreshSkewMs = getTokenRefreshSkewMs(pluginConfig);
|
|
459
489
|
const networkErrorCooldownMs = getNetworkErrorCooldownMs(pluginConfig);
|
|
460
490
|
const serverErrorCooldownMs = getServerErrorCooldownMs(pluginConfig);
|
|
@@ -500,6 +530,7 @@ export async function startRuntimeRotationProxy(options) {
|
|
|
500
530
|
quotaRemainingPercentThreshold,
|
|
501
531
|
sessionAffinityStore,
|
|
502
532
|
lastObservedAffinityGeneration,
|
|
533
|
+
forcedAccountIndex,
|
|
503
534
|
});
|
|
504
535
|
const server = createServer((req, res) => {
|
|
505
536
|
void handleRequest(state, req, res);
|
|
@@ -669,7 +700,13 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
669
700
|
// account. The proxy itself never bumps the generation, so its own
|
|
670
701
|
// debounced disk writes do not clear affinity. See issue #474.
|
|
671
702
|
const storageMeta = readStorageMetaFromDisk();
|
|
672
|
-
|
|
703
|
+
// The ephemeral --account pin (issue #623) takes precedence over the
|
|
704
|
+
// persisted `switch` pin for this invocation, without ever mutating disk
|
|
705
|
+
// state. Use `??` (not `||`) so a forced index of 0 is honored. When set,
|
|
706
|
+
// everything downstream — deterministic pick, no cursor advance, no
|
|
707
|
+
// stale-state recovery, the `codex_pinned_account_unavailable` failure —
|
|
708
|
+
// applies unchanged because it all keys off `pinnedIndex` / `isPinned`.
|
|
709
|
+
const pinnedIndex = state.forcedAccountIndex ?? storageMeta.pinnedAccountIndex;
|
|
673
710
|
const isPinned = typeof pinnedIndex === "number";
|
|
674
711
|
if (storageMeta.affinityGeneration > state.lastObservedAffinityGeneration) {
|
|
675
712
|
state.sessionAffinityStore?.clearAll();
|
package/dist/lib/schemas.js
CHANGED
|
@@ -274,7 +274,7 @@ export const TokenSuccessSchema = z.object({
|
|
|
274
274
|
type: z.literal("success"),
|
|
275
275
|
access: z.string().min(1),
|
|
276
276
|
refresh: z.string().min(1),
|
|
277
|
-
expires: z.number(),
|
|
277
|
+
expires: z.number().int().positive(),
|
|
278
278
|
idToken: z.string().optional(),
|
|
279
279
|
multiAccount: z.boolean().optional(),
|
|
280
280
|
});
|
|
@@ -303,7 +303,7 @@ export const TokenResultSchema = z.discriminatedUnion("type", [
|
|
|
303
303
|
export const OAuthTokenResponseSchema = z.object({
|
|
304
304
|
access_token: z.string().min(1),
|
|
305
305
|
refresh_token: z.string().optional(),
|
|
306
|
-
expires_in: z.number(),
|
|
306
|
+
expires_in: z.number().int().positive(),
|
|
307
307
|
id_token: z.string().optional(),
|
|
308
308
|
token_type: z.string().optional(),
|
|
309
309
|
scope: z.string().optional(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../lib/storage.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../lib/storage.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/storage.js
CHANGED
|
@@ -814,7 +814,12 @@ export function normalizeAccountStorage(data) {
|
|
|
814
814
|
const baseStorage = fromVersion === 1
|
|
815
815
|
? migrateV1ToV3(data)
|
|
816
816
|
: data;
|
|
817
|
-
|
|
817
|
+
// Build from baseStorage.accounts (the migrated V3 shape), NOT the raw V1
|
|
818
|
+
// objects. Otherwise a V1->V3 upgrade discards migrateV1ToV3's per-account
|
|
819
|
+
// transforms — most importantly the scalar `rateLimitResetTime` -> map
|
|
820
|
+
// `rateLimitResetTimes` conversion — so a rate-limited V1 account would be
|
|
821
|
+
// treated as immediately available on upgrade and burst 429s (stress audit M3).
|
|
822
|
+
const validAccounts = baseStorage.accounts.filter((account) => isRecord(account) &&
|
|
818
823
|
typeof account.refreshToken === "string" &&
|
|
819
824
|
!!account.refreshToken.trim());
|
|
820
825
|
const deduplicatedAccounts = deduplicateAccounts(validAccounts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-multi-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
package/scripts/codex.js
CHANGED
|
@@ -307,6 +307,172 @@ function findAccountIndexByIdOrEmail(accounts, id, email) {
|
|
|
307
307
|
return -1;
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
// --- Forced-account selection (`--account`, issue #623) ---------------------
|
|
311
|
+
//
|
|
312
|
+
// `codex-multi-auth-codex --account <selector>` (or the CODEX_MULTI_AUTH_FORCE_ACCOUNT
|
|
313
|
+
// env var) pins ONE account for a single invocation. The selector is resolved to a
|
|
314
|
+
// 0-based index here in the launcher and published as CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX,
|
|
315
|
+
// which the runtime rotation proxy consumes as an ephemeral pin — never touching the
|
|
316
|
+
// persisted `switch` pin on disk. The flag wins over the env var.
|
|
317
|
+
const FORCE_ACCOUNT_ENV = "CODEX_MULTI_AUTH_FORCE_ACCOUNT";
|
|
318
|
+
const FORCE_ACCOUNT_INDEX_ENV = "CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX";
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Extract and strip `--account <sel>` / `--account=<sel>` from a forwarded arg
|
|
322
|
+
* list. The flag is a launcher concept; real Codex has no `--account`, so it must
|
|
323
|
+
* never be forwarded. Returns { selector, strippedArgs, error }; selector is null
|
|
324
|
+
* when the flag is absent. The last occurrence wins if repeated.
|
|
325
|
+
*/
|
|
326
|
+
function extractForcedAccountFlag(args) {
|
|
327
|
+
const strippedArgs = [];
|
|
328
|
+
let selector = null;
|
|
329
|
+
let sawFlag = false;
|
|
330
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
331
|
+
const arg = args[index];
|
|
332
|
+
if (arg === "--account") {
|
|
333
|
+
sawFlag = true;
|
|
334
|
+
const next = args[index + 1];
|
|
335
|
+
if (typeof next !== "string") {
|
|
336
|
+
return {
|
|
337
|
+
selector: null,
|
|
338
|
+
strippedArgs: args,
|
|
339
|
+
error:
|
|
340
|
+
"codex-multi-auth: --account requires a value (account index, email, or account id).",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
selector = next;
|
|
344
|
+
index += 1;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (typeof arg === "string" && arg.startsWith("--account=")) {
|
|
348
|
+
sawFlag = true;
|
|
349
|
+
selector = arg.slice("--account=".length);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
strippedArgs.push(arg);
|
|
353
|
+
}
|
|
354
|
+
return { selector, strippedArgs, error: null, sawFlag };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Resolve the effective forced-account selector from args (flag) or environment,
|
|
359
|
+
* with the flag taking precedence. Returns { selector, strippedArgs, error };
|
|
360
|
+
* selector is null when neither source requests a forced account.
|
|
361
|
+
*/
|
|
362
|
+
function resolveForcedAccountSelector(rawArgs, env = process.env) {
|
|
363
|
+
const { selector: flagSelector, strippedArgs, error, sawFlag } =
|
|
364
|
+
extractForcedAccountFlag(rawArgs);
|
|
365
|
+
if (error) {
|
|
366
|
+
return { selector: null, strippedArgs, error };
|
|
367
|
+
}
|
|
368
|
+
if (sawFlag) {
|
|
369
|
+
const trimmed = flagSelector.trim();
|
|
370
|
+
if (trimmed.length === 0) {
|
|
371
|
+
return {
|
|
372
|
+
selector: null,
|
|
373
|
+
strippedArgs,
|
|
374
|
+
error:
|
|
375
|
+
"codex-multi-auth: --account requires a non-empty value (account index, email, or account id).",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return { selector: trimmed, strippedArgs, error: null };
|
|
379
|
+
}
|
|
380
|
+
const envSelector = (env[FORCE_ACCOUNT_ENV] ?? "").trim();
|
|
381
|
+
if (envSelector.length > 0) {
|
|
382
|
+
return { selector: envSelector, strippedArgs, error: null };
|
|
383
|
+
}
|
|
384
|
+
return { selector: null, strippedArgs, error: null };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function formatForcedAccountList(accounts) {
|
|
388
|
+
const lines = accounts.map((account, index) => {
|
|
389
|
+
const email =
|
|
390
|
+
typeof account?.email === "string" && account.email.trim().length > 0
|
|
391
|
+
? account.email.trim()
|
|
392
|
+
: `account ${index + 1}`;
|
|
393
|
+
return ` ${index + 1}. ${email}`;
|
|
394
|
+
});
|
|
395
|
+
return `Available accounts:\n${lines.join("\n")}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Resolve a forced-account selector to a 0-based index against the same scoped
|
|
400
|
+
* accounts pool the runtime rotation proxy will load. Selector forms: a 1-based
|
|
401
|
+
* integer index, an email, or an account id. Returns { ok, index } or
|
|
402
|
+
* { ok: false, error }.
|
|
403
|
+
*/
|
|
404
|
+
async function resolveForcedAccountIndex(selector, env = process.env) {
|
|
405
|
+
const accountsDir = await resolveStatusAccountsDir(env);
|
|
406
|
+
const storage = readJsonFileQuiet(resolveAccountsPath(env, accountsDir));
|
|
407
|
+
const accounts = Array.isArray(storage?.accounts) ? storage.accounts : [];
|
|
408
|
+
if (accounts.length === 0) {
|
|
409
|
+
return {
|
|
410
|
+
ok: false,
|
|
411
|
+
error:
|
|
412
|
+
"codex-multi-auth: --account was set but no Codex accounts are configured. Run `codex-multi-auth login` first.",
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (/^\d+$/.test(selector)) {
|
|
416
|
+
const oneBased = Number.parseInt(selector, 10);
|
|
417
|
+
if (oneBased < 1 || oneBased > accounts.length) {
|
|
418
|
+
return {
|
|
419
|
+
ok: false,
|
|
420
|
+
error: `codex-multi-auth: --account ${selector} is out of range (have ${accounts.length} account${
|
|
421
|
+
accounts.length === 1 ? "" : "s"
|
|
422
|
+
}).\n${formatForcedAccountList(accounts)}`,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return { ok: true, index: oneBased - 1 };
|
|
426
|
+
}
|
|
427
|
+
const index = findAccountIndexByIdOrEmail(accounts, selector, selector);
|
|
428
|
+
if (index < 0) {
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
error: `codex-multi-auth: --account "${selector}" did not match any configured account.\n${formatForcedAccountList(accounts)}`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return { ok: true, index };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Preflight the `--account` flag / env var before forwarding to Codex. On success
|
|
439
|
+
* returns { forwardArgs } with the flag stripped and (when a forced account was
|
|
440
|
+
* requested) CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX published on the environment for
|
|
441
|
+
* the proxy to consume. On failure returns { error } so the caller can exit
|
|
442
|
+
* non-zero WITHOUT launching Codex — a forced account must never silently fall
|
|
443
|
+
* back to a different one.
|
|
444
|
+
*/
|
|
445
|
+
async function applyForcedAccountSelection(rawArgs, env = process.env) {
|
|
446
|
+
const forced = resolveForcedAccountSelector(rawArgs, env);
|
|
447
|
+
if (forced.error) {
|
|
448
|
+
return { error: forced.error };
|
|
449
|
+
}
|
|
450
|
+
if (forced.selector === null) {
|
|
451
|
+
// Defensive: the resolved-index var is internal and only ever set below.
|
|
452
|
+
// Clear any stray value from the ambient environment so a request without
|
|
453
|
+
// --account can never inherit an unintended pin.
|
|
454
|
+
delete env[FORCE_ACCOUNT_INDEX_ENV];
|
|
455
|
+
return { forwardArgs: rawArgs };
|
|
456
|
+
}
|
|
457
|
+
// The pin can only be honored when the runtime rotation proxy is active for
|
|
458
|
+
// this command; otherwise the run would silently use a non-forced account,
|
|
459
|
+
// so fail hard instead of ignoring the flag.
|
|
460
|
+
if (!(await isRuntimeRotationProxyEnabled(forced.strippedArgs, env))) {
|
|
461
|
+
return {
|
|
462
|
+
error: [
|
|
463
|
+
"codex-multi-auth: --account requires the runtime rotation proxy, which is not active for this command.",
|
|
464
|
+
"Enable it with `codex-multi-auth rotation enable`, and make sure CODEX_MULTI_AUTH_BYPASS is not set.",
|
|
465
|
+
].join("\n"),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const resolved = await resolveForcedAccountIndex(forced.selector, env);
|
|
469
|
+
if (!resolved.ok) {
|
|
470
|
+
return { error: resolved.error };
|
|
471
|
+
}
|
|
472
|
+
env[FORCE_ACCOUNT_INDEX_ENV] = String(resolved.index);
|
|
473
|
+
return { forwardArgs: forced.strippedArgs };
|
|
474
|
+
}
|
|
475
|
+
|
|
310
476
|
function resolveModelFamilyForStatus(model) {
|
|
311
477
|
const normalized = typeof model === "string" ? model.trim().toLowerCase() : "";
|
|
312
478
|
if (normalized.startsWith("gpt-5.2")) return "gpt-5.2";
|
|
@@ -657,15 +823,20 @@ function maybeRefreshQuotaCacheInBackground(env = process.env) {
|
|
|
657
823
|
// process. The close/error handlers below remove the lock dir, but if the parent
|
|
658
824
|
// exits before the child settles they will NOT fire — in that case the lock is
|
|
659
825
|
// reclaimed by the 10-minute stale-lock recovery in acquireStatusRefreshLock.
|
|
826
|
+
const refreshChildEnv = {
|
|
827
|
+
...env,
|
|
828
|
+
CODEX_MULTI_AUTH_STATUS_REFRESH_CHILD: "1",
|
|
829
|
+
CODEX_MULTI_AUTH_STATUSLINE: "0",
|
|
830
|
+
};
|
|
831
|
+
// A forced-account pin is scoped to a single forwarded Codex run; this
|
|
832
|
+
// management child (`forecast`) must never inherit it, so it can never be
|
|
833
|
+
// coupled to a specific account if a future change routes it through the proxy.
|
|
834
|
+
delete refreshChildEnv[FORCE_ACCOUNT_INDEX_ENV];
|
|
660
835
|
const child = spawn(
|
|
661
836
|
process.execPath,
|
|
662
837
|
[scriptPath, "forecast", "--live", "--json"],
|
|
663
838
|
{
|
|
664
|
-
env:
|
|
665
|
-
...env,
|
|
666
|
-
CODEX_MULTI_AUTH_STATUS_REFRESH_CHILD: "1",
|
|
667
|
-
CODEX_MULTI_AUTH_STATUSLINE: "0",
|
|
668
|
-
},
|
|
839
|
+
env: refreshChildEnv,
|
|
669
840
|
stdio: "ignore",
|
|
670
841
|
detached: true,
|
|
671
842
|
},
|
|
@@ -4766,12 +4937,22 @@ async function main() {
|
|
|
4766
4937
|
return 1;
|
|
4767
4938
|
}
|
|
4768
4939
|
|
|
4940
|
+
// Resolve `--account` / CODEX_MULTI_AUTH_FORCE_ACCOUNT before forwarding: strip
|
|
4941
|
+
// the launcher-only flag from the Codex args and publish the resolved pin, or
|
|
4942
|
+
// fail hard so a forced account can never silently fall back to another one.
|
|
4943
|
+
const forcedAccount = await applyForcedAccountSelection(rawArgs, process.env);
|
|
4944
|
+
if (forcedAccount.error) {
|
|
4945
|
+
console.error(forcedAccount.error);
|
|
4946
|
+
return 1;
|
|
4947
|
+
}
|
|
4948
|
+
const forwardArgs = forcedAccount.forwardArgs;
|
|
4949
|
+
|
|
4769
4950
|
await autoSyncManagerActiveSelectionIfEnabled();
|
|
4770
|
-
await maybePrintForwardStatusLine(
|
|
4951
|
+
await maybePrintForwardStatusLine(forwardArgs);
|
|
4771
4952
|
maybeRefreshQuotaCacheInBackground();
|
|
4772
4953
|
try {
|
|
4773
|
-
return await withForwardedRuntimeObservability(
|
|
4774
|
-
forwardToRealCodex(realCodexBin,
|
|
4954
|
+
return await withForwardedRuntimeObservability(forwardArgs, () =>
|
|
4955
|
+
forwardToRealCodex(realCodexBin, forwardArgs),
|
|
4775
4956
|
);
|
|
4776
4957
|
} finally {
|
|
4777
4958
|
await autoSyncManagerActiveSelectionIfEnabled();
|