codex-multi-auth 2.9.2 → 2.11.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.
Files changed (81) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +2 -1
  3. package/config/README.md +3 -1
  4. package/config/codex-legacy.json +300 -0
  5. package/config/codex-modern.json +96 -0
  6. package/config/schema/config.schema.json +23 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +62 -2
  9. package/dist/lib/accounts.d.ts +25 -1
  10. package/dist/lib/accounts.d.ts.map +1 -1
  11. package/dist/lib/accounts.js +26 -6
  12. package/dist/lib/capability-policy.d.ts.map +1 -1
  13. package/dist/lib/capability-policy.js +16 -1
  14. package/dist/lib/codex-manager/backend-settings-helpers.d.ts.map +1 -1
  15. package/dist/lib/codex-manager/backend-settings-helpers.js +10 -1
  16. package/dist/lib/codex-manager/backend-settings-schema.d.ts +18 -0
  17. package/dist/lib/codex-manager/backend-settings-schema.d.ts.map +1 -1
  18. package/dist/lib/codex-manager/backend-settings-schema.js +19 -0
  19. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +1 -0
  20. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts.map +1 -1
  21. package/dist/lib/codex-manager/experimental-settings-prompt.js +12 -0
  22. package/dist/lib/codex-manager/experimental-settings-schema.d.ts +2 -0
  23. package/dist/lib/codex-manager/experimental-settings-schema.d.ts.map +1 -1
  24. package/dist/lib/codex-manager/experimental-settings-schema.js +2 -0
  25. package/dist/lib/config.d.ts +29 -0
  26. package/dist/lib/config.d.ts.map +1 -1
  27. package/dist/lib/config.js +91 -1
  28. package/dist/lib/context-budget/model-context-windows.d.ts +25 -0
  29. package/dist/lib/context-budget/model-context-windows.d.ts.map +1 -0
  30. package/dist/lib/context-budget/model-context-windows.js +130 -0
  31. package/dist/lib/context-budget-guard.d.ts +100 -0
  32. package/dist/lib/context-budget-guard.d.ts.map +1 -0
  33. package/dist/lib/context-budget-guard.js +183 -0
  34. package/dist/lib/context-budget-response.d.ts +25 -0
  35. package/dist/lib/context-budget-response.d.ts.map +1 -0
  36. package/dist/lib/context-budget-response.js +57 -0
  37. package/dist/lib/context-overflow.d.ts.map +1 -1
  38. package/dist/lib/context-overflow.js +5 -60
  39. package/dist/lib/model-capability-matrix.d.ts.map +1 -1
  40. package/dist/lib/model-capability-matrix.js +9 -1
  41. package/dist/lib/request/error-classification.d.ts.map +1 -1
  42. package/dist/lib/request/error-classification.js +43 -2
  43. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  44. package/dist/lib/request/helpers/model-map.js +181 -0
  45. package/dist/lib/request/rate-limit-decision.d.ts +4 -5
  46. package/dist/lib/request/rate-limit-decision.d.ts.map +1 -1
  47. package/dist/lib/request/rate-limit-decision.js +33 -4
  48. package/dist/lib/request/response-metadata.d.ts.map +1 -1
  49. package/dist/lib/request/response-metadata.js +1 -0
  50. package/dist/lib/request/stream-failover-runtime.d.ts +3 -1
  51. package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
  52. package/dist/lib/request/stream-failover-runtime.js +7 -2
  53. package/dist/lib/runtime/context-budget-settings.d.ts +13 -0
  54. package/dist/lib/runtime/context-budget-settings.d.ts.map +1 -0
  55. package/dist/lib/runtime/context-budget-settings.js +8 -0
  56. package/dist/lib/runtime/loader-setup.d.ts +1 -0
  57. package/dist/lib/runtime/loader-setup.d.ts.map +1 -1
  58. package/dist/lib/runtime/loader-setup.js +1 -0
  59. package/dist/lib/runtime/rotation-account-selection.d.ts +23 -0
  60. package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
  61. package/dist/lib/runtime/rotation-account-selection.js +15 -6
  62. package/dist/lib/runtime/rotation-proxy-state.d.ts +2 -0
  63. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  64. package/dist/lib/runtime/rotation-server-types.d.ts +11 -0
  65. package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
  66. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  67. package/dist/lib/runtime-rotation-proxy.js +251 -43
  68. package/dist/lib/schemas.d.ts +4 -0
  69. package/dist/lib/schemas.d.ts.map +1 -1
  70. package/dist/lib/schemas.js +10 -0
  71. package/dist/lib/synthetic-response.d.ts +31 -0
  72. package/dist/lib/synthetic-response.d.ts.map +1 -0
  73. package/dist/lib/synthetic-response.js +79 -0
  74. package/dist/lib/ui/ui-copy.d.ts +2 -1
  75. package/dist/lib/ui/ui-copy.d.ts.map +1 -1
  76. package/dist/lib/ui/ui-copy.js +2 -1
  77. package/dist/lib/usage/pricing.d.ts +1 -1
  78. package/dist/lib/usage/pricing.d.ts.map +1 -1
  79. package/dist/lib/usage/pricing.js +33 -0
  80. package/package.json +1 -1
  81. package/scripts/codex.js +153 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-multi-auth",
3
- "version": "2.9.2",
3
+ "version": "2.11.0",
4
4
  "description": "Install and operate codex-multi-auth for the official @openai/codex CLI with multi-account OAuth rotation, switching, health checks, and recovery tools.",
5
5
  "interface": {
6
6
  "composerIcon": "./assets/codex-multi-auth-icon.svg"
package/README.md CHANGED
@@ -329,6 +329,7 @@ The Settings menu now includes an `Experimental` section for staged features:
329
329
  - preview-first sync into `oc-chatgpt-multi-auth`
330
330
  - named local pool backup export with filename prompt
331
331
  - refresh guard toggle and interval controls moved out of Backend Controls
332
+ - context budget guard: pause a session before it hits the model's context window, with independently configurable soft/hard thresholds (default 65% / 69%) — ships disabled; see [Context Budget Guard](docs/features.md#context-budget-guard-experimental)
332
333
 
333
334
  These flows are intentionally non-destructive by default: sync previews before apply, destination-only accounts are preserved, and backup filename collisions fail safely.
334
335
 
@@ -405,7 +406,7 @@ codex-multi-auth doctor --json
405
406
 
406
407
  ## Release Notes
407
408
 
408
- - Current stable: [docs/releases/v2.9.2.md](docs/releases/v2.9.2.md) — install via `npm i -g codex-multi-auth`
409
+ - Current stable: [docs/releases/v2.11.0.md](docs/releases/v2.11.0.md) — install via `npm i -g codex-multi-auth`
409
410
  - Previous stable: [docs/releases/v2.8.3.md](docs/releases/v2.8.3.md)
410
411
  - Previous stable: [docs/releases/v2.8.2.md](docs/releases/v2.8.2.md)
411
412
  - Previous stable: [docs/releases/v2.7.1.md](docs/releases/v2.7.1.md)
package/config/README.md CHANGED
@@ -17,7 +17,9 @@ These files are optional model/provider templates for host/plugin configurations
17
17
 
18
18
  ## Defaults Included
19
19
 
20
- - Current documented OpenAI/Codex model families (`gpt-5.6-sol`/`terra`/`luna`, `gpt-5.5`, `gpt-5.4`, and `gpt-5.3-codex`)
20
+ - Current documented OpenAI/Codex model families: GPT-6 Astra (`gpt-6-astra`, `gpt-6-astra-aeon`), then `gpt-5.6-sol`/`terra`/`luna`, `gpt-5.5`, `gpt-5.4`, and `gpt-5.3-codex`
21
+ - The Astra entries carry `limit.context: 272000`, OpenAI's stable Codex context value at launch. The GPT-5.6 entries still carry the pre-cut `372000` and were left alone here; change them only alongside a deliberate review, since the value feeds a picker rather than the context budget guard (which refuses to estimate either family)
22
+ - The Daybreak cyber models resolve at the code level but are deliberately absent from these templates, matching their hidden visibility in the upstream Codex picker
21
23
  - `store: false`
22
24
  - `include: ["reasoning.encrypted_content"]`
23
25
  - Sensible fallback behavior for unsupported model entitlements
@@ -15,6 +15,306 @@
15
15
  "store": false
16
16
  },
17
17
  "models": {
18
+ "gpt-6-astra-low": {
19
+ "name": "GPT 6 Astra Low (OAuth)",
20
+ "limit": {
21
+ "context": 272000,
22
+ "output": 128000
23
+ },
24
+ "modalities": {
25
+ "input": [
26
+ "text",
27
+ "image"
28
+ ],
29
+ "output": [
30
+ "text"
31
+ ]
32
+ },
33
+ "options": {
34
+ "reasoningEffort": "low",
35
+ "reasoningSummary": "auto",
36
+ "textVerbosity": "medium",
37
+ "include": [
38
+ "reasoning.encrypted_content"
39
+ ],
40
+ "store": false
41
+ }
42
+ },
43
+ "gpt-6-astra-medium": {
44
+ "name": "GPT 6 Astra Medium (OAuth)",
45
+ "limit": {
46
+ "context": 272000,
47
+ "output": 128000
48
+ },
49
+ "modalities": {
50
+ "input": [
51
+ "text",
52
+ "image"
53
+ ],
54
+ "output": [
55
+ "text"
56
+ ]
57
+ },
58
+ "options": {
59
+ "reasoningEffort": "medium",
60
+ "reasoningSummary": "auto",
61
+ "textVerbosity": "medium",
62
+ "include": [
63
+ "reasoning.encrypted_content"
64
+ ],
65
+ "store": false
66
+ }
67
+ },
68
+ "gpt-6-astra-high": {
69
+ "name": "GPT 6 Astra High (OAuth)",
70
+ "limit": {
71
+ "context": 272000,
72
+ "output": 128000
73
+ },
74
+ "modalities": {
75
+ "input": [
76
+ "text",
77
+ "image"
78
+ ],
79
+ "output": [
80
+ "text"
81
+ ]
82
+ },
83
+ "options": {
84
+ "reasoningEffort": "high",
85
+ "reasoningSummary": "detailed",
86
+ "textVerbosity": "medium",
87
+ "include": [
88
+ "reasoning.encrypted_content"
89
+ ],
90
+ "store": false
91
+ }
92
+ },
93
+ "gpt-6-astra-xhigh": {
94
+ "name": "GPT 6 Astra Extra High (OAuth)",
95
+ "limit": {
96
+ "context": 272000,
97
+ "output": 128000
98
+ },
99
+ "modalities": {
100
+ "input": [
101
+ "text",
102
+ "image"
103
+ ],
104
+ "output": [
105
+ "text"
106
+ ]
107
+ },
108
+ "options": {
109
+ "reasoningEffort": "xhigh",
110
+ "reasoningSummary": "detailed",
111
+ "textVerbosity": "medium",
112
+ "include": [
113
+ "reasoning.encrypted_content"
114
+ ],
115
+ "store": false
116
+ }
117
+ },
118
+ "gpt-6-astra-max": {
119
+ "name": "GPT 6 Astra Max (OAuth)",
120
+ "limit": {
121
+ "context": 272000,
122
+ "output": 128000
123
+ },
124
+ "modalities": {
125
+ "input": [
126
+ "text",
127
+ "image"
128
+ ],
129
+ "output": [
130
+ "text"
131
+ ]
132
+ },
133
+ "options": {
134
+ "reasoningEffort": "max",
135
+ "reasoningSummary": "detailed",
136
+ "textVerbosity": "medium",
137
+ "include": [
138
+ "reasoning.encrypted_content"
139
+ ],
140
+ "store": false
141
+ }
142
+ },
143
+ "gpt-6-astra-ultra": {
144
+ "name": "GPT 6 Astra Ultra (OAuth)",
145
+ "limit": {
146
+ "context": 272000,
147
+ "output": 128000
148
+ },
149
+ "modalities": {
150
+ "input": [
151
+ "text",
152
+ "image"
153
+ ],
154
+ "output": [
155
+ "text"
156
+ ]
157
+ },
158
+ "options": {
159
+ "reasoningEffort": "ultra",
160
+ "reasoningSummary": "detailed",
161
+ "textVerbosity": "medium",
162
+ "include": [
163
+ "reasoning.encrypted_content"
164
+ ],
165
+ "store": false
166
+ }
167
+ },
168
+ "gpt-6-astra-aeon-low": {
169
+ "name": "GPT 6 Astra Aeon Low (OAuth)",
170
+ "limit": {
171
+ "context": 272000,
172
+ "output": 128000
173
+ },
174
+ "modalities": {
175
+ "input": [
176
+ "text",
177
+ "image"
178
+ ],
179
+ "output": [
180
+ "text"
181
+ ]
182
+ },
183
+ "options": {
184
+ "reasoningEffort": "low",
185
+ "reasoningSummary": "auto",
186
+ "textVerbosity": "medium",
187
+ "include": [
188
+ "reasoning.encrypted_content"
189
+ ],
190
+ "store": false
191
+ }
192
+ },
193
+ "gpt-6-astra-aeon-medium": {
194
+ "name": "GPT 6 Astra Aeon Medium (OAuth)",
195
+ "limit": {
196
+ "context": 272000,
197
+ "output": 128000
198
+ },
199
+ "modalities": {
200
+ "input": [
201
+ "text",
202
+ "image"
203
+ ],
204
+ "output": [
205
+ "text"
206
+ ]
207
+ },
208
+ "options": {
209
+ "reasoningEffort": "medium",
210
+ "reasoningSummary": "auto",
211
+ "textVerbosity": "medium",
212
+ "include": [
213
+ "reasoning.encrypted_content"
214
+ ],
215
+ "store": false
216
+ }
217
+ },
218
+ "gpt-6-astra-aeon-high": {
219
+ "name": "GPT 6 Astra Aeon High (OAuth)",
220
+ "limit": {
221
+ "context": 272000,
222
+ "output": 128000
223
+ },
224
+ "modalities": {
225
+ "input": [
226
+ "text",
227
+ "image"
228
+ ],
229
+ "output": [
230
+ "text"
231
+ ]
232
+ },
233
+ "options": {
234
+ "reasoningEffort": "high",
235
+ "reasoningSummary": "detailed",
236
+ "textVerbosity": "medium",
237
+ "include": [
238
+ "reasoning.encrypted_content"
239
+ ],
240
+ "store": false
241
+ }
242
+ },
243
+ "gpt-6-astra-aeon-xhigh": {
244
+ "name": "GPT 6 Astra Aeon Extra High (OAuth)",
245
+ "limit": {
246
+ "context": 272000,
247
+ "output": 128000
248
+ },
249
+ "modalities": {
250
+ "input": [
251
+ "text",
252
+ "image"
253
+ ],
254
+ "output": [
255
+ "text"
256
+ ]
257
+ },
258
+ "options": {
259
+ "reasoningEffort": "xhigh",
260
+ "reasoningSummary": "detailed",
261
+ "textVerbosity": "medium",
262
+ "include": [
263
+ "reasoning.encrypted_content"
264
+ ],
265
+ "store": false
266
+ }
267
+ },
268
+ "gpt-6-astra-aeon-max": {
269
+ "name": "GPT 6 Astra Aeon Max (OAuth)",
270
+ "limit": {
271
+ "context": 272000,
272
+ "output": 128000
273
+ },
274
+ "modalities": {
275
+ "input": [
276
+ "text",
277
+ "image"
278
+ ],
279
+ "output": [
280
+ "text"
281
+ ]
282
+ },
283
+ "options": {
284
+ "reasoningEffort": "max",
285
+ "reasoningSummary": "detailed",
286
+ "textVerbosity": "medium",
287
+ "include": [
288
+ "reasoning.encrypted_content"
289
+ ],
290
+ "store": false
291
+ }
292
+ },
293
+ "gpt-6-astra-aeon-ultra": {
294
+ "name": "GPT 6 Astra Aeon Ultra (OAuth)",
295
+ "limit": {
296
+ "context": 272000,
297
+ "output": 128000
298
+ },
299
+ "modalities": {
300
+ "input": [
301
+ "text",
302
+ "image"
303
+ ],
304
+ "output": [
305
+ "text"
306
+ ]
307
+ },
308
+ "options": {
309
+ "reasoningEffort": "ultra",
310
+ "reasoningSummary": "detailed",
311
+ "textVerbosity": "medium",
312
+ "include": [
313
+ "reasoning.encrypted_content"
314
+ ],
315
+ "store": false
316
+ }
317
+ },
18
318
  "gpt-5.6-sol-low": {
19
319
  "name": "GPT 5.6 Sol Low (OAuth)",
20
320
  "limit": {
@@ -15,6 +15,102 @@
15
15
  "store": false
16
16
  },
17
17
  "models": {
18
+ "gpt-6-astra": {
19
+ "name": "GPT 6 Astra (OAuth)",
20
+ "limit": {
21
+ "context": 272000,
22
+ "output": 128000
23
+ },
24
+ "modalities": {
25
+ "input": [
26
+ "text",
27
+ "image"
28
+ ],
29
+ "output": [
30
+ "text"
31
+ ]
32
+ },
33
+ "variants": {
34
+ "low": {
35
+ "reasoningEffort": "low",
36
+ "reasoningSummary": "auto",
37
+ "textVerbosity": "medium"
38
+ },
39
+ "medium": {
40
+ "reasoningEffort": "medium",
41
+ "reasoningSummary": "auto",
42
+ "textVerbosity": "medium"
43
+ },
44
+ "high": {
45
+ "reasoningEffort": "high",
46
+ "reasoningSummary": "detailed",
47
+ "textVerbosity": "medium"
48
+ },
49
+ "xhigh": {
50
+ "reasoningEffort": "xhigh",
51
+ "reasoningSummary": "detailed",
52
+ "textVerbosity": "medium"
53
+ },
54
+ "max": {
55
+ "reasoningEffort": "max",
56
+ "reasoningSummary": "detailed",
57
+ "textVerbosity": "medium"
58
+ },
59
+ "ultra": {
60
+ "reasoningEffort": "ultra",
61
+ "reasoningSummary": "detailed",
62
+ "textVerbosity": "medium"
63
+ }
64
+ }
65
+ },
66
+ "gpt-6-astra-aeon": {
67
+ "name": "GPT 6 Astra Aeon (OAuth)",
68
+ "limit": {
69
+ "context": 272000,
70
+ "output": 128000
71
+ },
72
+ "modalities": {
73
+ "input": [
74
+ "text",
75
+ "image"
76
+ ],
77
+ "output": [
78
+ "text"
79
+ ]
80
+ },
81
+ "variants": {
82
+ "low": {
83
+ "reasoningEffort": "low",
84
+ "reasoningSummary": "auto",
85
+ "textVerbosity": "medium"
86
+ },
87
+ "medium": {
88
+ "reasoningEffort": "medium",
89
+ "reasoningSummary": "auto",
90
+ "textVerbosity": "medium"
91
+ },
92
+ "high": {
93
+ "reasoningEffort": "high",
94
+ "reasoningSummary": "detailed",
95
+ "textVerbosity": "medium"
96
+ },
97
+ "xhigh": {
98
+ "reasoningEffort": "xhigh",
99
+ "reasoningSummary": "detailed",
100
+ "textVerbosity": "medium"
101
+ },
102
+ "max": {
103
+ "reasoningEffort": "max",
104
+ "reasoningSummary": "detailed",
105
+ "textVerbosity": "medium"
106
+ },
107
+ "ultra": {
108
+ "reasoningEffort": "ultra",
109
+ "reasoningSummary": "detailed",
110
+ "textVerbosity": "medium"
111
+ }
112
+ }
113
+ },
18
114
  "gpt-5.6-sol": {
19
115
  "name": "GPT 5.6 Sol (OAuth)",
20
116
  "limit": {
@@ -247,6 +247,29 @@
247
247
  "type": "number",
248
248
  "minimum": 1000
249
249
  },
250
+ "contextBudgetGuardEnabled": {
251
+ "type": "boolean"
252
+ },
253
+ "contextBudgetGuardSoftPercent": {
254
+ "type": "number",
255
+ "minimum": 0,
256
+ "maximum": 100
257
+ },
258
+ "contextBudgetGuardHardPercent": {
259
+ "type": "number",
260
+ "minimum": 10,
261
+ "maximum": 100
262
+ },
263
+ "contextBudgetGuardModelWindowOverrides": {
264
+ "type": "object",
265
+ "propertyNames": {
266
+ "type": "string"
267
+ },
268
+ "additionalProperties": {
269
+ "type": "number",
270
+ "exclusiveMinimum": 0
271
+ }
272
+ },
250
273
  "routingMutex": {
251
274
  "type": "string",
252
275
  "enum": [
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAoW5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAuwI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AA8W5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA80I/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -30,9 +30,11 @@ import { startLocalOAuthServer } from "./lib/auth/server.js";
30
30
  import { checkAndNotify } from "./lib/update-notice.js";
31
31
  import { CapabilityPolicyStore } from "./lib/capability-policy.js";
32
32
  import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
33
- import { getAutoResume, getCodexMode, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getFallbackToGpt52OnUnsupportedGpt53, getFastSession, getFastSessionMaxInputItems, getFastSessionStrategy, getFetchTimeoutMs, getRateLimitDedupWindowMs, getRateLimitMaxBackoffMs, getRateLimitShortRetryThresholdMs, getRateLimitStateResetMs, 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";
33
+ import { getAutoResume, getCodexMode, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getFallbackToGpt52OnUnsupportedGpt53, getFastSession, getFastSessionMaxInputItems, getFastSessionStrategy, getFetchTimeoutMs, getRateLimitDedupWindowMs, getRateLimitMaxBackoffMs, getRateLimitShortRetryThresholdMs, getRateLimitStateResetMs, getLiveAccountSync, getLiveAccountSyncDebounceMs, getLiveAccountSyncPollMs, getNetworkErrorCooldownMs, getPerProjectAccounts, getPidOffsetEnabled, getPreemptiveQuotaEnabled, getPreemptiveQuotaMaxDeferralMs, getPreemptiveQuotaRemainingPercent5h, getPreemptiveQuotaRemainingPercent7d, getContextBudgetGuardEnabled, getContextBudgetGuardSoftPercent, getContextBudgetGuardHardPercent, getContextBudgetGuardModelWindowOverrides, 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
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 { ContextBudgetGuard } from "./lib/context-budget-guard.js";
37
+ import { buildContextBudgetHeaders, createContextBudgetPauseResponse, } from "./lib/context-budget-response.js";
36
38
  import { EntitlementCache, resolveEntitlementAccountKey, } from "./lib/entitlement-cache.js";
37
39
  import { LiveAccountSync } from "./lib/live-account-sync.js";
38
40
  import { clearCorrelationId, initLogger, logDebug, logError, logInfo, logRequest, logWarn, setCorrelationId, } from "./lib/logger.js";
@@ -89,6 +91,7 @@ import { ensureLiveAccountSyncEntry } from "./lib/runtime/live-sync-entry.js";
89
91
  import { applyLoaderRuntimeSetup } from "./lib/runtime/loader-setup.js";
90
92
  import { buildManualOAuthFlow } from "./lib/runtime/manual-oauth-flow.js";
91
93
  import { applyPreemptiveQuotaSettingsFromConfig } from "./lib/runtime/quota-settings.js";
94
+ import { applyContextBudgetGuardSettingsFromConfig } from "./lib/runtime/context-budget-settings.js";
92
95
  import { ensureLiveAccountSyncState, ensureRefreshGuardianState, ensureSessionAffinityState, } from "./lib/runtime/runtime-services.js";
93
96
  import { getRuntimeObservabilitySnapshot, mutateRuntimeObservabilitySnapshot, } from "./lib/runtime/runtime-observability.js";
94
97
  import { applyAccountStorageScopeFromConfig } from "./lib/runtime/storage-scope.js";
@@ -166,6 +169,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
166
169
  let sessionAffinityConfigKey = null;
167
170
  const entitlementCache = new EntitlementCache();
168
171
  const preemptiveQuotaScheduler = new PreemptiveQuotaScheduler();
172
+ const contextBudgetGuard = new ContextBudgetGuard();
169
173
  const capabilityPolicyStore = new CapabilityPolicyStore();
170
174
  let accountReloadInFlight = null;
171
175
  const exposeAdvancedCodexTools = (process.env.CODEX_MULTI_AUTH_EXPOSE_ADMIN_TOOLS ?? "").trim() === "1";
@@ -359,6 +363,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
359
363
  getPreemptiveQuotaRemainingPercent7d,
360
364
  getPreemptiveQuotaMaxDeferralMs,
361
365
  });
366
+ const applyContextBudgetGuardSettings = (pluginConfig) => applyContextBudgetGuardSettingsFromConfig(pluginConfig, {
367
+ configure: (options) => contextBudgetGuard.configure(options),
368
+ getContextBudgetGuardEnabled,
369
+ getContextBudgetGuardSoftPercent,
370
+ getContextBudgetGuardHardPercent,
371
+ getContextBudgetGuardModelWindowOverrides,
372
+ });
362
373
  // Event handler for session recovery and account selection
363
374
  const eventHandler = async (input) => handleRuntimeEvent({
364
375
  input,
@@ -410,6 +421,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
410
421
  ensureSessionAffinity,
411
422
  ensureRefreshGuardian,
412
423
  applyPreemptiveQuotaSettings,
424
+ applyContextBudgetGuardSettings,
413
425
  });
414
426
  // Only handle OAuth auth type, skip API key auth
415
427
  if (auth.type !== "oauth") {
@@ -603,6 +615,23 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
603
615
  .toString()
604
616
  .trim() || undefined;
605
617
  const sessionAffinityKey = threadIdCandidate ?? promptCacheKey ?? null;
618
+ // Context budget guard: pause BEFORE spending an upstream
619
+ // round-trip on a request the tracked session is already over
620
+ // the hard threshold for, rather than waiting on the eventual
621
+ // context_length_exceeded 400 that handleContextOverflow below
622
+ // only reacts to after the fact. Independent of which account
623
+ // ends up serving the request, so it runs ahead of account
624
+ // selection entirely rather than inside its retry loop.
625
+ const contextBudgetAdvisory = contextBudgetGuard.getAdvisory(sessionAffinityKey ?? "", Date.now(), model);
626
+ if (contextBudgetAdvisory.level === "hard") {
627
+ // One-shot: this pause returns before the request is
628
+ // forwarded, so the onUsage hook that would lower the
629
+ // recorded usage never runs. Dropping the snapshot keeps
630
+ // the session recoverable — otherwise the `/compact` turn
631
+ // this message asks for is blocked by the same key.
632
+ contextBudgetGuard.noteHardPauseEmitted(sessionAffinityKey ?? "");
633
+ return createContextBudgetPauseResponse(contextBudgetAdvisory);
634
+ }
606
635
  const sessionAffinityVersion = (sessionAffinityWriteVersion += 1);
607
636
  const effectivePromptCacheKey = (sessionAffinityKey ?? promptCacheKey ?? "")
608
637
  .toString()
@@ -1614,7 +1643,22 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1614
1643
  sessionAffinityStore?.updateLastResponseId(sessionAffinityKey, responseId, Date.now(), sessionAffinityVersion);
1615
1644
  storedResponseIdForSuccess = true;
1616
1645
  },
1617
- onUsage: usageDeferral.onUsage,
1646
+ onUsage: (usage) => {
1647
+ usageDeferral.onUsage(usage);
1648
+ // Responses is mostly stateless (store=false): this
1649
+ // turn's input_tokens reflects the full conversation
1650
+ // resent this call, so it doubles as a live read of
1651
+ // the session's current context size. input + output, NOT
1652
+ // total: total_tokens also counts reasoning tokens, which
1653
+ // are not resent as context on the next turn.
1654
+ if (sessionAffinityKey && model) {
1655
+ contextBudgetGuard.update(sessionAffinityKey, {
1656
+ model,
1657
+ contextTokens: usage.inputTokens + usage.outputTokens,
1658
+ updatedAt: Date.now(),
1659
+ });
1660
+ }
1661
+ },
1618
1662
  streamStallTimeoutMs,
1619
1663
  });
1620
1664
  if (!isStreaming && emptyResponseMaxRetries > 0) {
@@ -1723,6 +1767,22 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1723
1767
  clearPoolExhaustionCooldown();
1724
1768
  clearServerBurstCooldown();
1725
1769
  syncRuntimeObservability(requestTraceId);
1770
+ // Non-blocking: contextBudgetAdvisory reflects usage
1771
+ // observed as of the PRIOR turn (this turn's own usage
1772
+ // isn't known yet for a streaming body), attached as a
1773
+ // header for `codex-multi-auth status` / the statusline
1774
+ // rather than altering this response's body.
1775
+ if (contextBudgetAdvisory.level === "soft") {
1776
+ const headers = new Headers(successResponse.headers);
1777
+ for (const [name, value] of Object.entries(buildContextBudgetHeaders(contextBudgetAdvisory))) {
1778
+ headers.set(name, value);
1779
+ }
1780
+ return new Response(successResponse.body, {
1781
+ status: successResponse.status,
1782
+ statusText: successResponse.statusText,
1783
+ headers,
1784
+ });
1785
+ }
1726
1786
  return successResponse;
1727
1787
  }
1728
1788
  if (retryNextAccountBeforeFallback) {
@@ -147,7 +147,31 @@ export declare class AccountManager {
147
147
  recordSuccess(account: ManagedAccount, family: ModelFamily, model?: string | null): void;
148
148
  recordRateLimit(account: ManagedAccount, family: ModelFamily, model?: string | null): void;
149
149
  recordFailure(account: ManagedAccount, family: ModelFamily, model?: string | null): void;
150
- consumeToken(account: ManagedAccount, family: ModelFamily, model?: string | null): boolean;
150
+ consumeToken(account: ManagedAccount, family: ModelFamily, model?: string | null, options?: {
151
+ bypassTokenBucket?: boolean;
152
+ }): boolean;
153
+ /**
154
+ * `consumeToken`, but it says which admission gate rejected the request.
155
+ *
156
+ * There are two gates and they are not interchangeable to a caller writing
157
+ * `account_skip_reasons`: the pool-scoring token bucket, and the circuit
158
+ * breaker's race-safe admission slot. Re-deriving the answer afterwards
159
+ * with `getManagedAccountRuntimeSkipReason` gets it wrong in both
160
+ * directions -- that helper reports the FIRST blocker it finds (so a live
161
+ * cooldown masks a drained bucket), and its `isAvailable()` check answers
162
+ * true for a half-open breaker whose single probe `canExecute()` just
163
+ * handed to a concurrent request. Reporting the gate that actually
164
+ * rejected removes the race and the mis-attribution, and avoids a second
165
+ * state-mutating evaluation (`clearExpiredRateLimits`) on the hot path.
166
+ */
167
+ consumeTokenWithReason(account: ManagedAccount, family: ModelFamily, model?: string | null, options?: {
168
+ bypassTokenBucket?: boolean;
169
+ }): {
170
+ ok: true;
171
+ } | {
172
+ ok: false;
173
+ reason: "token-exhausted" | "circuit-open";
174
+ };
151
175
  /**
152
176
  * Refund a token consumed within the refund window (30 seconds).
153
177
  * Use this when a request fails due to network errors (not rate limits).