codex-multi-auth 0.1.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 (327) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +162 -0
  3. package/assets/opencode-logo-ornate-dark.svg +18 -0
  4. package/assets/readme-hero.svg +31 -0
  5. package/config/README.md +87 -0
  6. package/config/minimal-opencode.json +13 -0
  7. package/config/opencode-legacy.json +571 -0
  8. package/config/opencode-modern.json +239 -0
  9. package/dist/index.d.ts +45 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +3160 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/accounts/rate-limits.d.ts +22 -0
  14. package/dist/lib/accounts/rate-limits.d.ts.map +1 -0
  15. package/dist/lib/accounts/rate-limits.js +63 -0
  16. package/dist/lib/accounts/rate-limits.js.map +1 -0
  17. package/dist/lib/accounts.d.ts +95 -0
  18. package/dist/lib/accounts.d.ts.map +1 -0
  19. package/dist/lib/accounts.js +668 -0
  20. package/dist/lib/accounts.js.map +1 -0
  21. package/dist/lib/audit.d.ts +45 -0
  22. package/dist/lib/audit.d.ts.map +1 -0
  23. package/dist/lib/audit.js +131 -0
  24. package/dist/lib/audit.js.map +1 -0
  25. package/dist/lib/auth/auth.d.ts +56 -0
  26. package/dist/lib/auth/auth.d.ts.map +1 -0
  27. package/dist/lib/auth/auth.js +214 -0
  28. package/dist/lib/auth/auth.js.map +1 -0
  29. package/dist/lib/auth/browser.d.ts +34 -0
  30. package/dist/lib/auth/browser.d.ts.map +1 -0
  31. package/dist/lib/auth/browser.js +185 -0
  32. package/dist/lib/auth/browser.js.map +1 -0
  33. package/dist/lib/auth/server.d.ts +24 -0
  34. package/dist/lib/auth/server.d.ts.map +1 -0
  35. package/dist/lib/auth/server.js +116 -0
  36. package/dist/lib/auth/server.js.map +1 -0
  37. package/dist/lib/auth/token-utils.d.ts +59 -0
  38. package/dist/lib/auth/token-utils.d.ts.map +1 -0
  39. package/dist/lib/auth/token-utils.js +331 -0
  40. package/dist/lib/auth/token-utils.js.map +1 -0
  41. package/dist/lib/auth-rate-limit.d.ts +20 -0
  42. package/dist/lib/auth-rate-limit.d.ts.map +1 -0
  43. package/dist/lib/auth-rate-limit.js +91 -0
  44. package/dist/lib/auth-rate-limit.js.map +1 -0
  45. package/dist/lib/auto-update-checker.d.ts +10 -0
  46. package/dist/lib/auto-update-checker.d.ts.map +1 -0
  47. package/dist/lib/auto-update-checker.js +216 -0
  48. package/dist/lib/auto-update-checker.js.map +1 -0
  49. package/dist/lib/capability-policy.d.ts +18 -0
  50. package/dist/lib/capability-policy.d.ts.map +1 -0
  51. package/dist/lib/capability-policy.js +150 -0
  52. package/dist/lib/capability-policy.js.map +1 -0
  53. package/dist/lib/circuit-breaker.d.ts +34 -0
  54. package/dist/lib/circuit-breaker.d.ts.map +1 -0
  55. package/dist/lib/circuit-breaker.js +124 -0
  56. package/dist/lib/circuit-breaker.js.map +1 -0
  57. package/dist/lib/cli.d.ts +64 -0
  58. package/dist/lib/cli.d.ts.map +1 -0
  59. package/dist/lib/cli.js +274 -0
  60. package/dist/lib/cli.js.map +1 -0
  61. package/dist/lib/codex-cli/observability.d.ts +22 -0
  62. package/dist/lib/codex-cli/observability.d.ts.map +1 -0
  63. package/dist/lib/codex-cli/observability.js +36 -0
  64. package/dist/lib/codex-cli/observability.js.map +1 -0
  65. package/dist/lib/codex-cli/state.d.ts +86 -0
  66. package/dist/lib/codex-cli/state.d.ts.map +1 -0
  67. package/dist/lib/codex-cli/state.js +470 -0
  68. package/dist/lib/codex-cli/state.js.map +1 -0
  69. package/dist/lib/codex-cli/sync.d.ts +27 -0
  70. package/dist/lib/codex-cli/sync.d.ts.map +1 -0
  71. package/dist/lib/codex-cli/sync.js +325 -0
  72. package/dist/lib/codex-cli/sync.js.map +1 -0
  73. package/dist/lib/codex-cli/writer.d.ts +12 -0
  74. package/dist/lib/codex-cli/writer.d.ts.map +1 -0
  75. package/dist/lib/codex-cli/writer.js +388 -0
  76. package/dist/lib/codex-cli/writer.js.map +1 -0
  77. package/dist/lib/codex-manager.d.ts +2 -0
  78. package/dist/lib/codex-manager.d.ts.map +1 -0
  79. package/dist/lib/codex-manager.js +4841 -0
  80. package/dist/lib/codex-manager.js.map +1 -0
  81. package/dist/lib/config.d.ts +269 -0
  82. package/dist/lib/config.d.ts.map +1 -0
  83. package/dist/lib/config.js +789 -0
  84. package/dist/lib/config.js.map +1 -0
  85. package/dist/lib/constants.d.ts +78 -0
  86. package/dist/lib/constants.d.ts.map +1 -0
  87. package/dist/lib/constants.js +78 -0
  88. package/dist/lib/constants.js.map +1 -0
  89. package/dist/lib/context-overflow.d.ts +27 -0
  90. package/dist/lib/context-overflow.d.ts.map +1 -0
  91. package/dist/lib/context-overflow.js +124 -0
  92. package/dist/lib/context-overflow.js.map +1 -0
  93. package/dist/lib/dashboard-settings.d.ts +90 -0
  94. package/dist/lib/dashboard-settings.d.ts.map +1 -0
  95. package/dist/lib/dashboard-settings.js +327 -0
  96. package/dist/lib/dashboard-settings.js.map +1 -0
  97. package/dist/lib/entitlement-cache.d.ts +41 -0
  98. package/dist/lib/entitlement-cache.d.ts.map +1 -0
  99. package/dist/lib/entitlement-cache.js +137 -0
  100. package/dist/lib/entitlement-cache.js.map +1 -0
  101. package/dist/lib/errors.d.ts +113 -0
  102. package/dist/lib/errors.d.ts.map +1 -0
  103. package/dist/lib/errors.js +103 -0
  104. package/dist/lib/errors.js.map +1 -0
  105. package/dist/lib/forecast.d.ts +42 -0
  106. package/dist/lib/forecast.d.ts.map +1 -0
  107. package/dist/lib/forecast.js +256 -0
  108. package/dist/lib/forecast.js.map +1 -0
  109. package/dist/lib/health.d.ts +33 -0
  110. package/dist/lib/health.d.ts.map +1 -0
  111. package/dist/lib/health.js +70 -0
  112. package/dist/lib/health.js.map +1 -0
  113. package/dist/lib/index.d.ts +32 -0
  114. package/dist/lib/index.d.ts.map +1 -0
  115. package/dist/lib/index.js +32 -0
  116. package/dist/lib/index.js.map +1 -0
  117. package/dist/lib/live-account-sync.d.ts +39 -0
  118. package/dist/lib/live-account-sync.d.ts.map +1 -0
  119. package/dist/lib/live-account-sync.js +196 -0
  120. package/dist/lib/live-account-sync.js.map +1 -0
  121. package/dist/lib/logger.d.ts +40 -0
  122. package/dist/lib/logger.d.ts.map +1 -0
  123. package/dist/lib/logger.js +364 -0
  124. package/dist/lib/logger.js.map +1 -0
  125. package/dist/lib/oauth-success.html +338 -0
  126. package/dist/lib/parallel-probe.d.ts +28 -0
  127. package/dist/lib/parallel-probe.d.ts.map +1 -0
  128. package/dist/lib/parallel-probe.js +97 -0
  129. package/dist/lib/parallel-probe.js.map +1 -0
  130. package/dist/lib/preemptive-quota-scheduler.d.ts +53 -0
  131. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -0
  132. package/dist/lib/preemptive-quota-scheduler.js +220 -0
  133. package/dist/lib/preemptive-quota-scheduler.js.map +1 -0
  134. package/dist/lib/proactive-refresh.d.ts +66 -0
  135. package/dist/lib/proactive-refresh.d.ts.map +1 -0
  136. package/dist/lib/proactive-refresh.js +143 -0
  137. package/dist/lib/proactive-refresh.js.map +1 -0
  138. package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
  139. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
  140. package/dist/lib/prompts/codex-opencode-bridge.js +169 -0
  141. package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
  142. package/dist/lib/prompts/codex.d.ts +41 -0
  143. package/dist/lib/prompts/codex.d.ts.map +1 -0
  144. package/dist/lib/prompts/codex.js +383 -0
  145. package/dist/lib/prompts/codex.js.map +1 -0
  146. package/dist/lib/prompts/opencode-codex.d.ts +25 -0
  147. package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
  148. package/dist/lib/prompts/opencode-codex.js +270 -0
  149. package/dist/lib/prompts/opencode-codex.js.map +1 -0
  150. package/dist/lib/quota-cache.d.ts +68 -0
  151. package/dist/lib/quota-cache.d.ts.map +1 -0
  152. package/dist/lib/quota-cache.js +224 -0
  153. package/dist/lib/quota-cache.js.map +1 -0
  154. package/dist/lib/quota-probe.d.ts +49 -0
  155. package/dist/lib/quota-probe.d.ts.map +1 -0
  156. package/dist/lib/quota-probe.js +368 -0
  157. package/dist/lib/quota-probe.js.map +1 -0
  158. package/dist/lib/recovery/constants.d.ts +12 -0
  159. package/dist/lib/recovery/constants.d.ts.map +1 -0
  160. package/dist/lib/recovery/constants.js +31 -0
  161. package/dist/lib/recovery/constants.js.map +1 -0
  162. package/dist/lib/recovery/index.d.ts +12 -0
  163. package/dist/lib/recovery/index.d.ts.map +1 -0
  164. package/dist/lib/recovery/index.js +12 -0
  165. package/dist/lib/recovery/index.js.map +1 -0
  166. package/dist/lib/recovery/storage.d.ts +24 -0
  167. package/dist/lib/recovery/storage.d.ts.map +1 -0
  168. package/dist/lib/recovery/storage.js +362 -0
  169. package/dist/lib/recovery/storage.js.map +1 -0
  170. package/dist/lib/recovery/types.d.ts +116 -0
  171. package/dist/lib/recovery/types.d.ts.map +1 -0
  172. package/dist/lib/recovery/types.js +7 -0
  173. package/dist/lib/recovery/types.js.map +1 -0
  174. package/dist/lib/recovery.d.ts +31 -0
  175. package/dist/lib/recovery.d.ts.map +1 -0
  176. package/dist/lib/recovery.js +313 -0
  177. package/dist/lib/recovery.js.map +1 -0
  178. package/dist/lib/refresh-guardian.d.ts +31 -0
  179. package/dist/lib/refresh-guardian.d.ts.map +1 -0
  180. package/dist/lib/refresh-guardian.js +151 -0
  181. package/dist/lib/refresh-guardian.js.map +1 -0
  182. package/dist/lib/refresh-lease.d.ts +37 -0
  183. package/dist/lib/refresh-lease.d.ts.map +1 -0
  184. package/dist/lib/refresh-lease.js +335 -0
  185. package/dist/lib/refresh-lease.js.map +1 -0
  186. package/dist/lib/refresh-queue.d.ts +117 -0
  187. package/dist/lib/refresh-queue.d.ts.map +1 -0
  188. package/dist/lib/refresh-queue.js +297 -0
  189. package/dist/lib/refresh-queue.js.map +1 -0
  190. package/dist/lib/request/failure-policy.d.ts +42 -0
  191. package/dist/lib/request/failure-policy.d.ts.map +1 -0
  192. package/dist/lib/request/failure-policy.js +133 -0
  193. package/dist/lib/request/failure-policy.js.map +1 -0
  194. package/dist/lib/request/fetch-helpers.d.ts +152 -0
  195. package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
  196. package/dist/lib/request/fetch-helpers.js +704 -0
  197. package/dist/lib/request/fetch-helpers.js.map +1 -0
  198. package/dist/lib/request/helpers/input-utils.d.ts +7 -0
  199. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
  200. package/dist/lib/request/helpers/input-utils.js +214 -0
  201. package/dist/lib/request/helpers/input-utils.js.map +1 -0
  202. package/dist/lib/request/helpers/model-map.d.ts +28 -0
  203. package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
  204. package/dist/lib/request/helpers/model-map.js +133 -0
  205. package/dist/lib/request/helpers/model-map.js.map +1 -0
  206. package/dist/lib/request/helpers/tool-utils.d.ts +29 -0
  207. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -0
  208. package/dist/lib/request/helpers/tool-utils.js +117 -0
  209. package/dist/lib/request/helpers/tool-utils.js.map +1 -0
  210. package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
  211. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
  212. package/dist/lib/request/rate-limit-backoff.js +83 -0
  213. package/dist/lib/request/rate-limit-backoff.js.map +1 -0
  214. package/dist/lib/request/request-transformer.d.ts +107 -0
  215. package/dist/lib/request/request-transformer.d.ts.map +1 -0
  216. package/dist/lib/request/request-transformer.js +814 -0
  217. package/dist/lib/request/request-transformer.js.map +1 -0
  218. package/dist/lib/request/response-handler.d.ts +23 -0
  219. package/dist/lib/request/response-handler.d.ts.map +1 -0
  220. package/dist/lib/request/response-handler.js +155 -0
  221. package/dist/lib/request/response-handler.js.map +1 -0
  222. package/dist/lib/request/stream-failover.d.ts +21 -0
  223. package/dist/lib/request/stream-failover.d.ts.map +1 -0
  224. package/dist/lib/request/stream-failover.js +204 -0
  225. package/dist/lib/request/stream-failover.js.map +1 -0
  226. package/dist/lib/rotation.d.ts +146 -0
  227. package/dist/lib/rotation.d.ts.map +1 -0
  228. package/dist/lib/rotation.js +321 -0
  229. package/dist/lib/rotation.js.map +1 -0
  230. package/dist/lib/runtime-paths.d.ts +58 -0
  231. package/dist/lib/runtime-paths.d.ts.map +1 -0
  232. package/dist/lib/runtime-paths.js +164 -0
  233. package/dist/lib/runtime-paths.js.map +1 -0
  234. package/dist/lib/schemas.d.ts +435 -0
  235. package/dist/lib/schemas.d.ts.map +1 -0
  236. package/dist/lib/schemas.js +268 -0
  237. package/dist/lib/schemas.js.map +1 -0
  238. package/dist/lib/session-affinity.d.ts +23 -0
  239. package/dist/lib/session-affinity.d.ts.map +1 -0
  240. package/dist/lib/session-affinity.js +127 -0
  241. package/dist/lib/session-affinity.js.map +1 -0
  242. package/dist/lib/shutdown.d.ts +7 -0
  243. package/dist/lib/shutdown.d.ts.map +1 -0
  244. package/dist/lib/shutdown.js +43 -0
  245. package/dist/lib/shutdown.js.map +1 -0
  246. package/dist/lib/storage/migrations.d.ts +59 -0
  247. package/dist/lib/storage/migrations.d.ts.map +1 -0
  248. package/dist/lib/storage/migrations.js +41 -0
  249. package/dist/lib/storage/migrations.js.map +1 -0
  250. package/dist/lib/storage/paths.d.ts +51 -0
  251. package/dist/lib/storage/paths.d.ts.map +1 -0
  252. package/dist/lib/storage/paths.js +152 -0
  253. package/dist/lib/storage/paths.js.map +1 -0
  254. package/dist/lib/storage.d.ts +106 -0
  255. package/dist/lib/storage.d.ts.map +1 -0
  256. package/dist/lib/storage.js +896 -0
  257. package/dist/lib/storage.js.map +1 -0
  258. package/dist/lib/table-formatter.d.ts +32 -0
  259. package/dist/lib/table-formatter.d.ts.map +1 -0
  260. package/dist/lib/table-formatter.js +44 -0
  261. package/dist/lib/table-formatter.js.map +1 -0
  262. package/dist/lib/tools/hashline-tools.d.ts +51 -0
  263. package/dist/lib/tools/hashline-tools.d.ts.map +1 -0
  264. package/dist/lib/tools/hashline-tools.js +456 -0
  265. package/dist/lib/tools/hashline-tools.js.map +1 -0
  266. package/dist/lib/types.d.ts +130 -0
  267. package/dist/lib/types.d.ts.map +1 -0
  268. package/dist/lib/types.js +2 -0
  269. package/dist/lib/types.js.map +1 -0
  270. package/dist/lib/ui/ansi.d.ts +40 -0
  271. package/dist/lib/ui/ansi.d.ts.map +1 -0
  272. package/dist/lib/ui/ansi.js +68 -0
  273. package/dist/lib/ui/ansi.js.map +1 -0
  274. package/dist/lib/ui/auth-menu.d.ts +76 -0
  275. package/dist/lib/ui/auth-menu.d.ts.map +1 -0
  276. package/dist/lib/ui/auth-menu.js +590 -0
  277. package/dist/lib/ui/auth-menu.js.map +1 -0
  278. package/dist/lib/ui/confirm.d.ts +11 -0
  279. package/dist/lib/ui/confirm.d.ts.map +1 -0
  280. package/dist/lib/ui/confirm.js +29 -0
  281. package/dist/lib/ui/confirm.js.map +1 -0
  282. package/dist/lib/ui/copy.d.ts +123 -0
  283. package/dist/lib/ui/copy.d.ts.map +1 -0
  284. package/dist/lib/ui/copy.js +127 -0
  285. package/dist/lib/ui/copy.js.map +1 -0
  286. package/dist/lib/ui/format.d.ts +62 -0
  287. package/dist/lib/ui/format.d.ts.map +1 -0
  288. package/dist/lib/ui/format.js +205 -0
  289. package/dist/lib/ui/format.js.map +1 -0
  290. package/dist/lib/ui/runtime.d.ts +43 -0
  291. package/dist/lib/ui/runtime.d.ts.map +1 -0
  292. package/dist/lib/ui/runtime.js +69 -0
  293. package/dist/lib/ui/runtime.js.map +1 -0
  294. package/dist/lib/ui/select.d.ts +60 -0
  295. package/dist/lib/ui/select.d.ts.map +1 -0
  296. package/dist/lib/ui/select.js +467 -0
  297. package/dist/lib/ui/select.js.map +1 -0
  298. package/dist/lib/ui/theme.d.ts +56 -0
  299. package/dist/lib/ui/theme.d.ts.map +1 -0
  300. package/dist/lib/ui/theme.js +186 -0
  301. package/dist/lib/ui/theme.js.map +1 -0
  302. package/dist/lib/unified-settings.d.ts +71 -0
  303. package/dist/lib/unified-settings.d.ts.map +1 -0
  304. package/dist/lib/unified-settings.js +299 -0
  305. package/dist/lib/unified-settings.js.map +1 -0
  306. package/dist/lib/utils.d.ts +29 -0
  307. package/dist/lib/utils.d.ts.map +1 -0
  308. package/dist/lib/utils.js +54 -0
  309. package/dist/lib/utils.js.map +1 -0
  310. package/package.json +115 -0
  311. package/scripts/audit-dev-allowlist.js +128 -0
  312. package/scripts/bench-format/hashline-v2.mjs +642 -0
  313. package/scripts/bench-format/models.mjs +105 -0
  314. package/scripts/bench-format/opencode.mjs +205 -0
  315. package/scripts/bench-format/render.mjs +496 -0
  316. package/scripts/bench-format/stats.mjs +54 -0
  317. package/scripts/bench-format/tasks.mjs +151 -0
  318. package/scripts/benchmark-edit-formats.mjs +1161 -0
  319. package/scripts/benchmark-render-dashboard.mjs +49 -0
  320. package/scripts/codex-multi-auth.js +6 -0
  321. package/scripts/codex-routing.js +34 -0
  322. package/scripts/codex.js +122 -0
  323. package/scripts/copy-oauth-success.js +37 -0
  324. package/scripts/install-opencode-codex-auth.js +193 -0
  325. package/scripts/test-all-models.sh +7 -0
  326. package/scripts/test-model-matrix.js +424 -0
  327. package/scripts/validate-model-map.sh +7 -0
@@ -0,0 +1,789 @@
1
+ import { readFileSync, existsSync, promises as fs } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { logWarn } from "./logger.js";
4
+ import { PluginConfigSchema, getValidationErrors } from "./schemas.js";
5
+ import { getCodexHomeDir, getCodexMultiAuthDir, getLegacyOpenCodeDir } from "./runtime-paths.js";
6
+ import { getUnifiedSettingsPath, loadUnifiedPluginConfigSync, saveUnifiedPluginConfig, } from "./unified-settings.js";
7
+ const CONFIG_DIR = getCodexMultiAuthDir();
8
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
9
+ const LEGACY_CODEX_CONFIG_PATH = join(getCodexHomeDir(), "codex-multi-auth-config.json");
10
+ const LEGACY_OPENCODE_CONFIG_PATH = join(getLegacyOpenCodeDir(), "codex-multi-auth-config.json");
11
+ const LEGACY_OPENCODE_AUTH_CONFIG_PATH = join(getLegacyOpenCodeDir(), "openai-codex-auth-config.json");
12
+ const TUI_COLOR_PROFILES = new Set(["truecolor", "ansi16", "ansi256"]);
13
+ const TUI_GLYPH_MODES = new Set(["ascii", "unicode", "auto"]);
14
+ const UNSUPPORTED_CODEX_POLICIES = new Set(["strict", "fallback"]);
15
+ const emittedConfigWarnings = new Set();
16
+ const configSaveQueues = new Map();
17
+ const RETRYABLE_FS_CODES = new Set(["EBUSY", "EPERM"]);
18
+ function logConfigWarnOnce(message) {
19
+ if (emittedConfigWarnings.has(message)) {
20
+ return;
21
+ }
22
+ emittedConfigWarnings.add(message);
23
+ logWarn(message);
24
+ }
25
+ export function __resetConfigWarningCacheForTests() {
26
+ emittedConfigWarnings.clear();
27
+ }
28
+ /**
29
+ * Determines the filesystem path to the plugin configuration file, preferring an explicit environment override and falling back to current and legacy locations.
30
+ *
31
+ * The lookup order is:
32
+ * 1. `CODEX_MULTI_AUTH_CONFIG_PATH` environment variable (if set and non-empty)
33
+ * 2. current CONFIG_PATH
34
+ * 3. legacy Codex/OpenCode config locations (with a one-time migration warning)
35
+ *
36
+ * Concurrency: the function is synchronous and relies on the filesystem state at call time; callers should handle concurrent config writes externally.
37
+ *
38
+ * Windows: path existence checks use Node's filesystem semantics (case sensitivity and symlink behavior follow the host OS).
39
+ *
40
+ * Security: the returned path may reference files containing sensitive tokens; callers MUST redact or avoid logging full paths or file contents.
41
+ *
42
+ * @returns The resolved config file path as a string, or `null` if no config file was found.
43
+ */
44
+ function resolvePluginConfigPath() {
45
+ const envPath = (process.env.CODEX_MULTI_AUTH_CONFIG_PATH ?? "").trim();
46
+ if (envPath.length > 0) {
47
+ return envPath;
48
+ }
49
+ if (existsSync(CONFIG_PATH)) {
50
+ return CONFIG_PATH;
51
+ }
52
+ if (existsSync(LEGACY_CODEX_CONFIG_PATH)) {
53
+ logConfigWarnOnce(`Using legacy config path ${LEGACY_CODEX_CONFIG_PATH}. ` +
54
+ `Please migrate to ${CONFIG_PATH}.`);
55
+ return LEGACY_CODEX_CONFIG_PATH;
56
+ }
57
+ if (existsSync(LEGACY_OPENCODE_CONFIG_PATH)) {
58
+ logConfigWarnOnce(`Using legacy OpenCode config path ${LEGACY_OPENCODE_CONFIG_PATH}. ` +
59
+ `Please migrate to ${CONFIG_PATH}.`);
60
+ return LEGACY_OPENCODE_CONFIG_PATH;
61
+ }
62
+ if (existsSync(LEGACY_OPENCODE_AUTH_CONFIG_PATH)) {
63
+ logConfigWarnOnce(`Using legacy OpenCode config path ${LEGACY_OPENCODE_AUTH_CONFIG_PATH}. ` +
64
+ `Please migrate to ${CONFIG_PATH}.`);
65
+ return LEGACY_OPENCODE_AUTH_CONFIG_PATH;
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * Default plugin configuration
71
+ * CODEX_MODE is enabled by default for better Codex CLI parity
72
+ */
73
+ export const DEFAULT_PLUGIN_CONFIG = {
74
+ codexMode: true,
75
+ codexTuiV2: true,
76
+ codexTuiColorProfile: "truecolor",
77
+ codexTuiGlyphMode: "ascii",
78
+ fastSession: false,
79
+ fastSessionStrategy: "hybrid",
80
+ fastSessionMaxInputItems: 30,
81
+ retryAllAccountsRateLimited: true,
82
+ retryAllAccountsMaxWaitMs: 0,
83
+ retryAllAccountsMaxRetries: Infinity,
84
+ unsupportedCodexPolicy: "strict",
85
+ fallbackOnUnsupportedCodexModel: false,
86
+ fallbackToGpt52OnUnsupportedGpt53: true,
87
+ unsupportedCodexFallbackChain: {},
88
+ tokenRefreshSkewMs: 60_000,
89
+ rateLimitToastDebounceMs: 60_000,
90
+ toastDurationMs: 5_000,
91
+ perProjectAccounts: true,
92
+ sessionRecovery: true,
93
+ autoResume: true,
94
+ parallelProbing: false,
95
+ parallelProbingMaxConcurrency: 2,
96
+ emptyResponseMaxRetries: 2,
97
+ emptyResponseRetryDelayMs: 1_000,
98
+ pidOffsetEnabled: false,
99
+ fetchTimeoutMs: 60_000,
100
+ streamStallTimeoutMs: 45_000,
101
+ liveAccountSync: true,
102
+ liveAccountSyncDebounceMs: 250,
103
+ liveAccountSyncPollMs: 2_000,
104
+ sessionAffinity: true,
105
+ sessionAffinityTtlMs: 20 * 60_000,
106
+ sessionAffinityMaxEntries: 512,
107
+ proactiveRefreshGuardian: true,
108
+ proactiveRefreshIntervalMs: 60_000,
109
+ proactiveRefreshBufferMs: 5 * 60_000,
110
+ networkErrorCooldownMs: 6_000,
111
+ serverErrorCooldownMs: 4_000,
112
+ storageBackupEnabled: true,
113
+ preemptiveQuotaEnabled: true,
114
+ preemptiveQuotaRemainingPercent5h: 5,
115
+ preemptiveQuotaRemainingPercent7d: 5,
116
+ preemptiveQuotaMaxDeferralMs: 2 * 60 * 60_000,
117
+ };
118
+ /**
119
+ * Return a shallow copy of the default plugin configuration.
120
+ *
121
+ * Safe to call concurrently; performs no I/O and has no filesystem or Windows atomicity implications.
122
+ * The returned object may include placeholder fields for secrets or tokens — callers must redact sensitive values before logging or persisting.
123
+ *
124
+ * @returns A shallow copy of DEFAULT_PLUGIN_CONFIG
125
+ */
126
+ export function getDefaultPluginConfig() {
127
+ return { ...DEFAULT_PLUGIN_CONFIG };
128
+ }
129
+ /**
130
+ * Load the plugin configuration, merging validated user settings with defaults and applying legacy fallbacks.
131
+ *
132
+ * Attempts to read unified settings first; if absent, falls back to legacy per-user JSON files (UTF-8 BOM is stripped on Windows before parsing).
133
+ * Emits one-time warnings for validation or migration issues and avoids exposing sensitive tokens in logged messages.
134
+ * This function performs filesystem reads and may write a migrated unified config; callers should avoid concurrent writers to the same config paths.
135
+ *
136
+ * @returns The effective PluginConfig: a shallow merge of DEFAULT_PLUGIN_CONFIG with any validated user-provided settings
137
+ */
138
+ export function loadPluginConfig() {
139
+ try {
140
+ const unifiedConfig = loadUnifiedPluginConfigSync();
141
+ let userConfig = unifiedConfig;
142
+ let sourceKind = "unified";
143
+ if (!isRecord(userConfig)) {
144
+ const configPath = resolvePluginConfigPath();
145
+ if (!configPath) {
146
+ return { ...DEFAULT_PLUGIN_CONFIG };
147
+ }
148
+ const fileContent = readFileSync(configPath, "utf-8");
149
+ const normalizedFileContent = stripUtf8Bom(fileContent);
150
+ userConfig = JSON.parse(normalizedFileContent);
151
+ sourceKind = "file";
152
+ }
153
+ const hasFallbackEnvOverride = process.env.CODEX_AUTH_FALLBACK_UNSUPPORTED_MODEL !== undefined ||
154
+ process.env.CODEX_AUTH_FALLBACK_GPT53_TO_GPT52 !== undefined;
155
+ if (isRecord(userConfig)) {
156
+ const hasPolicyKey = Object.hasOwn(userConfig, "unsupportedCodexPolicy");
157
+ const hasLegacyFallbackKey = Object.hasOwn(userConfig, "fallbackOnUnsupportedCodexModel") ||
158
+ Object.hasOwn(userConfig, "fallbackToGpt52OnUnsupportedGpt53") ||
159
+ Object.hasOwn(userConfig, "unsupportedCodexFallbackChain");
160
+ if (!hasPolicyKey && (hasLegacyFallbackKey || hasFallbackEnvOverride)) {
161
+ logConfigWarnOnce("Legacy unsupported-model fallback settings detected without unsupportedCodexPolicy. " +
162
+ 'Using backward-compat behavior; prefer unsupportedCodexPolicy: "strict" | "fallback".');
163
+ }
164
+ }
165
+ const schemaErrors = getValidationErrors(PluginConfigSchema, userConfig);
166
+ if (schemaErrors.length > 0) {
167
+ logConfigWarnOnce(`Plugin config validation warnings: ${schemaErrors.slice(0, 3).join(", ")}`);
168
+ }
169
+ if (sourceKind === "file" &&
170
+ isRecord(userConfig) &&
171
+ (process.env.CODEX_MULTI_AUTH_CONFIG_PATH ?? "").trim().length === 0) {
172
+ logConfigWarnOnce(`Legacy config file is still in use; settings will migrate to ${getUnifiedSettingsPath()} on next save.`);
173
+ }
174
+ return {
175
+ ...DEFAULT_PLUGIN_CONFIG,
176
+ ...userConfig,
177
+ };
178
+ }
179
+ catch (error) {
180
+ const configPath = resolvePluginConfigPath() ?? CONFIG_PATH;
181
+ logConfigWarnOnce(`Failed to load config from ${configPath}: ${error.message}`);
182
+ return { ...DEFAULT_PLUGIN_CONFIG };
183
+ }
184
+ }
185
+ /**
186
+ * Remove a leading UTF‑8 byte order mark (BOM) from the given string if present.
187
+ *
188
+ * This is a pure, idempotent operation with no side effects. It is commonly used to normalize text read from files (including Windows-generated files) before JSON parsing or token redaction.
189
+ *
190
+ * @param content - The string to normalize
191
+ * @returns The input string without a leading UTF‑8 BOM; returns the original string if no BOM is present
192
+ */
193
+ function stripUtf8Bom(content) {
194
+ return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
195
+ }
196
+ /**
197
+ * Determines whether a value is a non-null object that can be treated as a record.
198
+ *
199
+ * @param value - The value to test
200
+ * @returns `true` if `value` is a non-null object and can be treated as `Record<string, unknown>`, `false` otherwise.
201
+ */
202
+ function isRecord(value) {
203
+ return value !== null && typeof value === "object" && !Array.isArray(value);
204
+ }
205
+ function isRetryableFsError(error) {
206
+ const code = error?.code;
207
+ return typeof code === "string" && RETRYABLE_FS_CODES.has(code);
208
+ }
209
+ function sleep(ms) {
210
+ return new Promise((resolve) => setTimeout(resolve, ms));
211
+ }
212
+ async function writeJsonFileAtomicWithRetry(filePath, payload) {
213
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
214
+ await fs.mkdir(dirname(filePath), { recursive: true });
215
+ await fs.writeFile(tempPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
216
+ let renamed = false;
217
+ try {
218
+ for (let attempt = 0; attempt < 5; attempt += 1) {
219
+ try {
220
+ await fs.rename(tempPath, filePath);
221
+ renamed = true;
222
+ return;
223
+ }
224
+ catch (error) {
225
+ if (!isRetryableFsError(error) || attempt >= 4) {
226
+ throw error;
227
+ }
228
+ await sleep(10 * 2 ** attempt);
229
+ }
230
+ }
231
+ }
232
+ finally {
233
+ if (!renamed) {
234
+ try {
235
+ await fs.unlink(tempPath);
236
+ }
237
+ catch {
238
+ // Best-effort temp cleanup.
239
+ }
240
+ }
241
+ }
242
+ }
243
+ async function withConfigSaveLock(path, task) {
244
+ const previous = configSaveQueues.get(path) ?? Promise.resolve();
245
+ const queued = previous.catch(() => { }).then(task);
246
+ configSaveQueues.set(path, queued);
247
+ try {
248
+ await queued;
249
+ }
250
+ finally {
251
+ if (configSaveQueues.get(path) === queued) {
252
+ configSaveQueues.delete(path);
253
+ }
254
+ }
255
+ }
256
+ /**
257
+ * Read and parse a JSON configuration file and return its top-level object when present and valid.
258
+ *
259
+ * This function tolerates transient read/parse failures caused by concurrent writers; callers should handle a `null` return as "unavailable". Log messages include the file path and error message — callers should avoid logging or displaying raw paths that may contain sensitive tokens without redaction. On Windows, path casing or exclusive file locks can make existing files temporarily unreadable.
260
+ *
261
+ * @param configPath - Filesystem path to the JSON config file. Concurrent writes may cause transient read/parse failures; callers should tolerate `null`. On Windows, path casing and exclusive locks can affect readability.
262
+ * @returns The parsed top-level JSON object as a Record<string, unknown> when the file exists and contains an object, or `null` if the file is missing, malformed, or could not be read.
263
+ */
264
+ function readConfigRecordFromPath(configPath) {
265
+ if (!existsSync(configPath))
266
+ return null;
267
+ try {
268
+ const fileContent = readFileSync(configPath, "utf-8");
269
+ const normalizedFileContent = stripUtf8Bom(fileContent);
270
+ const parsed = JSON.parse(normalizedFileContent);
271
+ return isRecord(parsed) ? parsed : null;
272
+ }
273
+ catch (error) {
274
+ logConfigWarnOnce(`Failed to read config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
275
+ return null;
276
+ }
277
+ }
278
+ /**
279
+ * Prepare a partial PluginConfig for persistence by removing undefined values,
280
+ * omitting non-finite numbers, and shallow-copying nested object records.
281
+ *
282
+ * @param config - Partial plugin configuration to sanitize before saving. Note: this function does not redact or mask secrets (tokens/credentials); callers must handle redaction before writing to disk.
283
+ * @returns A plain Record<string, unknown> suitable for JSON serialization: keys with `undefined` or non-finite numeric values are omitted and nested objects are shallow-copied.
284
+ *
285
+ * Concurrency: synchronous and side-effect free; callers are responsible for coordinating concurrent writes to the filesystem.
286
+ * Filesystem: no Windows-specific path normalization or filesystem I/O is performed by this function.
287
+ */
288
+ function sanitizePluginConfigForSave(config) {
289
+ const entries = Object.entries(config);
290
+ const sanitized = {};
291
+ for (const [key, value] of entries) {
292
+ if (value === undefined)
293
+ continue;
294
+ if (typeof value === "number" && !Number.isFinite(value))
295
+ continue;
296
+ if (isRecord(value)) {
297
+ sanitized[key] = { ...value };
298
+ continue;
299
+ }
300
+ sanitized[key] = value;
301
+ }
302
+ return sanitized;
303
+ }
304
+ /**
305
+ * Persist a partial plugin configuration to disk, merging it with existing stored settings.
306
+ *
307
+ * This writes the sanitized patch either to the path specified by the CODEX_MULTI_AUTH_CONFIG_PATH
308
+ * environment variable (if set) or into the unified settings store. The function does not take
309
+ * internal locks; callers should avoid concurrent invocations that might overwrite each other.
310
+ * On Windows and other platforms the write behavior follows the Node.js filesystem semantics and may
311
+ * not be atomic across processes. Callers are responsible for redacting any sensitive values
312
+ * (tokens, secrets) before calling if redaction is required; this function writes merged values as-is.
313
+ *
314
+ * @param configPatch - Partial PluginConfig containing changes to persist; undefined fields are ignored.
315
+ * @returns void
316
+ */
317
+ export async function savePluginConfig(configPatch) {
318
+ const sanitizedPatch = sanitizePluginConfigForSave(configPatch);
319
+ const envPath = (process.env.CODEX_MULTI_AUTH_CONFIG_PATH ?? "").trim();
320
+ if (envPath.length > 0) {
321
+ await withConfigSaveLock(envPath, async () => {
322
+ const merged = {
323
+ ...(readConfigRecordFromPath(envPath) ?? {}),
324
+ ...sanitizedPatch,
325
+ };
326
+ await writeJsonFileAtomicWithRetry(envPath, merged);
327
+ });
328
+ return;
329
+ }
330
+ const unifiedPath = getUnifiedSettingsPath();
331
+ await withConfigSaveLock(unifiedPath, async () => {
332
+ const unifiedConfig = loadUnifiedPluginConfigSync();
333
+ const legacyPath = unifiedConfig ? null : resolvePluginConfigPath();
334
+ const merged = {
335
+ ...(unifiedConfig ?? (legacyPath ? readConfigRecordFromPath(legacyPath) : null) ?? {}),
336
+ ...sanitizedPatch,
337
+ };
338
+ await saveUnifiedPluginConfig(merged);
339
+ });
340
+ }
341
+ /**
342
+ * Get the effective CODEX_MODE setting
343
+ * Priority: environment variable > config file > default (true)
344
+ *
345
+ * @param pluginConfig - Plugin configuration from file
346
+ * @returns True if CODEX_MODE should be enabled
347
+ */
348
+ function parseBooleanEnv(value) {
349
+ if (value === undefined)
350
+ return undefined;
351
+ return value === "1";
352
+ }
353
+ function parseNumberEnv(value) {
354
+ if (value === undefined)
355
+ return undefined;
356
+ const parsed = Number(value);
357
+ if (!Number.isFinite(parsed))
358
+ return undefined;
359
+ return parsed;
360
+ }
361
+ function parseStringEnv(value) {
362
+ if (value === undefined)
363
+ return undefined;
364
+ const trimmed = value.trim().toLowerCase();
365
+ return trimmed.length > 0 ? trimmed : undefined;
366
+ }
367
+ /**
368
+ * Resolves a boolean configuration value, preferring an explicit environment variable.
369
+ *
370
+ * Checks the environment variable named by `envName` first (using the module's boolean parsing rules);
371
+ * if present, that value is returned. Otherwise returns `configValue` when defined, or `defaultValue`.
372
+ *
373
+ * This function is synchronous, has no filesystem interactions, and does not log or expose token values.
374
+ *
375
+ * @param envName - Name of the environment variable to check (e.g., "CODEX_FEATURE_FLAG")
376
+ * @param configValue - Value from the plugin configuration, used when the env var is not set
377
+ * @param defaultValue - Fallback value used when neither env nor config provide a value
378
+ * @returns `true` or `false` according to the resolution order: environment → config → default
379
+ */
380
+ function resolveBooleanSetting(envName, configValue, defaultValue) {
381
+ const envValue = parseBooleanEnv(process.env[envName]);
382
+ if (envValue !== undefined)
383
+ return envValue;
384
+ return configValue ?? defaultValue;
385
+ }
386
+ /**
387
+ * Resolve a numeric setting using an environment override, then a config value, then a default, and clamp the result to optional bounds.
388
+ *
389
+ * This function prefers a numeric value from the environment variable named by `envName`, falls back to `configValue`, then to `defaultValue`, and enforces inclusive `options.min`/`options.max` when provided. It is safe to call concurrently (reads only from `process.env`), performs no filesystem I/O (including on Windows), and does not emit or log secrets or tokens; callers should handle any redaction of sensitive values before logging.
390
+ *
391
+ * @param envName - Environment variable name to check for an override
392
+ * @param configValue - Configuration-provided numeric value to use if the environment variable is absent
393
+ * @param defaultValue - Fallback numeric value used when neither env nor config provide one
394
+ * @param options - Optional inclusive `min` and `max` bounds to clamp the resolved value
395
+ * @returns The resolved number, clamped to `options.min`/`options.max` when specified
396
+ */
397
+ function resolveNumberSetting(envName, configValue, defaultValue, options) {
398
+ const envValue = parseNumberEnv(process.env[envName]);
399
+ const candidate = envValue ?? configValue ?? defaultValue;
400
+ const min = options?.min ?? Number.NEGATIVE_INFINITY;
401
+ const max = options?.max ?? Number.POSITIVE_INFINITY;
402
+ return Math.max(min, Math.min(max, candidate));
403
+ }
404
+ /**
405
+ * Choose the effective string value from an environment variable, a config value, or a default while enforcing a whitelist.
406
+ *
407
+ * This reads the environment variable named by `envName` once and prefers it if its trimmed/lowercased value is in `allowedValues`; otherwise it falls back to `configValue` if allowed, then to `defaultValue`. Concurrency: concurrent mutations to `process.env` may change the outcome. Filesystem: performs no filesystem I/O and has no platform-specific behavior (including Windows). Secrets: the function does not redact or log values; callers are responsible for handling sensitive values.
408
+ *
409
+ * @param envName - Name of the environment variable to consult first
410
+ * @param configValue - Configuration-provided value to use if the environment value is absent or not allowed
411
+ * @param defaultValue - Fallback value used when neither environment nor config provide an allowed value
412
+ * @param allowedValues - Set of permitted values; only values in this set will be accepted
413
+ * @returns The resolved value, guaranteed to be one of the values in `allowedValues`
414
+ */
415
+ function resolveStringSetting(envName, configValue, defaultValue, allowedValues) {
416
+ const envValue = parseStringEnv(process.env[envName]);
417
+ if (envValue && allowedValues.has(envValue)) {
418
+ return envValue;
419
+ }
420
+ if (configValue && allowedValues.has(configValue)) {
421
+ return configValue;
422
+ }
423
+ return defaultValue;
424
+ }
425
+ export function getCodexMode(pluginConfig) {
426
+ return resolveBooleanSetting("CODEX_MODE", pluginConfig.codexMode, true);
427
+ }
428
+ export function getCodexTuiV2(pluginConfig) {
429
+ return resolveBooleanSetting("CODEX_TUI_V2", pluginConfig.codexTuiV2, true);
430
+ }
431
+ export function getCodexTuiColorProfile(pluginConfig) {
432
+ return resolveStringSetting("CODEX_TUI_COLOR_PROFILE", pluginConfig.codexTuiColorProfile, "truecolor", TUI_COLOR_PROFILES);
433
+ }
434
+ export function getCodexTuiGlyphMode(pluginConfig) {
435
+ return resolveStringSetting("CODEX_TUI_GLYPHS", pluginConfig.codexTuiGlyphMode, "ascii", TUI_GLYPH_MODES);
436
+ }
437
+ export function getFastSession(pluginConfig) {
438
+ return resolveBooleanSetting("CODEX_AUTH_FAST_SESSION", pluginConfig.fastSession, false);
439
+ }
440
+ export function getFastSessionStrategy(pluginConfig) {
441
+ const env = (process.env.CODEX_AUTH_FAST_SESSION_STRATEGY ?? "").trim().toLowerCase();
442
+ if (env === "always")
443
+ return "always";
444
+ if (env === "hybrid")
445
+ return "hybrid";
446
+ return pluginConfig.fastSessionStrategy === "always" ? "always" : "hybrid";
447
+ }
448
+ export function getFastSessionMaxInputItems(pluginConfig) {
449
+ return resolveNumberSetting("CODEX_AUTH_FAST_SESSION_MAX_INPUT_ITEMS", pluginConfig.fastSessionMaxInputItems, 30, { min: 8 });
450
+ }
451
+ export function getRetryAllAccountsRateLimited(pluginConfig) {
452
+ return resolveBooleanSetting("CODEX_AUTH_RETRY_ALL_RATE_LIMITED", pluginConfig.retryAllAccountsRateLimited, true);
453
+ }
454
+ export function getRetryAllAccountsMaxWaitMs(pluginConfig) {
455
+ return resolveNumberSetting("CODEX_AUTH_RETRY_ALL_MAX_WAIT_MS", pluginConfig.retryAllAccountsMaxWaitMs, 0, { min: 0 });
456
+ }
457
+ export function getRetryAllAccountsMaxRetries(pluginConfig) {
458
+ return resolveNumberSetting("CODEX_AUTH_RETRY_ALL_MAX_RETRIES", pluginConfig.retryAllAccountsMaxRetries, Infinity, { min: 0 });
459
+ }
460
+ export function getUnsupportedCodexPolicy(pluginConfig) {
461
+ const envPolicy = parseStringEnv(process.env.CODEX_AUTH_UNSUPPORTED_MODEL_POLICY);
462
+ if (envPolicy && UNSUPPORTED_CODEX_POLICIES.has(envPolicy)) {
463
+ return envPolicy;
464
+ }
465
+ const configPolicy = typeof pluginConfig.unsupportedCodexPolicy === "string"
466
+ ? pluginConfig.unsupportedCodexPolicy.toLowerCase()
467
+ : undefined;
468
+ if (configPolicy && UNSUPPORTED_CODEX_POLICIES.has(configPolicy)) {
469
+ return configPolicy;
470
+ }
471
+ const legacyEnvFallback = parseBooleanEnv(process.env.CODEX_AUTH_FALLBACK_UNSUPPORTED_MODEL);
472
+ if (legacyEnvFallback !== undefined) {
473
+ return legacyEnvFallback ? "fallback" : "strict";
474
+ }
475
+ if (typeof pluginConfig.fallbackOnUnsupportedCodexModel === "boolean") {
476
+ return pluginConfig.fallbackOnUnsupportedCodexModel
477
+ ? "fallback"
478
+ : "strict";
479
+ }
480
+ return "strict";
481
+ }
482
+ export function getFallbackOnUnsupportedCodexModel(pluginConfig) {
483
+ return getUnsupportedCodexPolicy(pluginConfig) === "fallback";
484
+ }
485
+ export function getFallbackToGpt52OnUnsupportedGpt53(pluginConfig) {
486
+ return resolveBooleanSetting("CODEX_AUTH_FALLBACK_GPT53_TO_GPT52", pluginConfig.fallbackToGpt52OnUnsupportedGpt53, true);
487
+ }
488
+ export function getUnsupportedCodexFallbackChain(pluginConfig) {
489
+ const chain = pluginConfig.unsupportedCodexFallbackChain;
490
+ if (!chain || typeof chain !== "object") {
491
+ return {};
492
+ }
493
+ const normalizeModel = (value) => {
494
+ const trimmed = value.trim().toLowerCase();
495
+ if (!trimmed)
496
+ return "";
497
+ const stripped = trimmed.includes("/")
498
+ ? (trimmed.split("/").pop() ?? trimmed)
499
+ : trimmed;
500
+ return stripped.replace(/-(none|minimal|low|medium|high|xhigh)$/i, "");
501
+ };
502
+ const normalized = {};
503
+ for (const [key, value] of Object.entries(chain)) {
504
+ if (typeof key !== "string" || !Array.isArray(value))
505
+ continue;
506
+ const normalizedKey = normalizeModel(key);
507
+ if (!normalizedKey)
508
+ continue;
509
+ const targets = value
510
+ .map((target) => (typeof target === "string" ? normalizeModel(target) : ""))
511
+ .filter((target) => target.length > 0);
512
+ if (targets.length > 0) {
513
+ normalized[normalizedKey] = targets;
514
+ }
515
+ }
516
+ return normalized;
517
+ }
518
+ export function getTokenRefreshSkewMs(pluginConfig) {
519
+ return resolveNumberSetting("CODEX_AUTH_TOKEN_REFRESH_SKEW_MS", pluginConfig.tokenRefreshSkewMs, 60_000, { min: 0 });
520
+ }
521
+ export function getRateLimitToastDebounceMs(pluginConfig) {
522
+ return resolveNumberSetting("CODEX_AUTH_RATE_LIMIT_TOAST_DEBOUNCE_MS", pluginConfig.rateLimitToastDebounceMs, 60_000, { min: 0 });
523
+ }
524
+ export function getSessionRecovery(pluginConfig) {
525
+ return resolveBooleanSetting("CODEX_AUTH_SESSION_RECOVERY", pluginConfig.sessionRecovery, true);
526
+ }
527
+ export function getAutoResume(pluginConfig) {
528
+ return resolveBooleanSetting("CODEX_AUTH_AUTO_RESUME", pluginConfig.autoResume, true);
529
+ }
530
+ export function getToastDurationMs(pluginConfig) {
531
+ return resolveNumberSetting("CODEX_AUTH_TOAST_DURATION_MS", pluginConfig.toastDurationMs, 5_000, { min: 1_000 });
532
+ }
533
+ export function getPerProjectAccounts(pluginConfig) {
534
+ return resolveBooleanSetting("CODEX_AUTH_PER_PROJECT_ACCOUNTS", pluginConfig.perProjectAccounts, true);
535
+ }
536
+ export function getParallelProbing(pluginConfig) {
537
+ return resolveBooleanSetting("CODEX_AUTH_PARALLEL_PROBING", pluginConfig.parallelProbing, false);
538
+ }
539
+ export function getParallelProbingMaxConcurrency(pluginConfig) {
540
+ return resolveNumberSetting("CODEX_AUTH_PARALLEL_PROBING_MAX_CONCURRENCY", pluginConfig.parallelProbingMaxConcurrency, 2, { min: 1 });
541
+ }
542
+ export function getEmptyResponseMaxRetries(pluginConfig) {
543
+ return resolveNumberSetting("CODEX_AUTH_EMPTY_RESPONSE_MAX_RETRIES", pluginConfig.emptyResponseMaxRetries, 2, { min: 0 });
544
+ }
545
+ export function getEmptyResponseRetryDelayMs(pluginConfig) {
546
+ return resolveNumberSetting("CODEX_AUTH_EMPTY_RESPONSE_RETRY_DELAY_MS", pluginConfig.emptyResponseRetryDelayMs, 1_000, { min: 0 });
547
+ }
548
+ export function getPidOffsetEnabled(pluginConfig) {
549
+ return resolveBooleanSetting("CODEX_AUTH_PID_OFFSET_ENABLED", pluginConfig.pidOffsetEnabled, false);
550
+ }
551
+ /**
552
+ * Resolve the HTTP fetch timeout to use for account/token requests.
553
+ *
554
+ * Concurrency: value is read-only and safe to use concurrently; callers must enforce timeout usage in their request code. On Windows, filesystem-derived overrides (via env or config file) are subject to typical path encoding and newline semantics. Configuration values may contain sensitive tokens elsewhere; this function only returns a numeric timeout and does not expose or log secrets.
555
+ *
556
+ * @param pluginConfig - Plugin configuration object to read the `fetchTimeoutMs` fallback from
557
+ * @returns The resolved fetch timeout in milliseconds (at least 1000)
558
+ */
559
+ export function getFetchTimeoutMs(pluginConfig) {
560
+ return resolveNumberSetting("CODEX_AUTH_FETCH_TIMEOUT_MS", pluginConfig.fetchTimeoutMs, 60_000, { min: 1_000 });
561
+ }
562
+ /**
563
+ * Compute the effective stream stall timeout used to detect stalled streams.
564
+ *
565
+ * This value applies across concurrent operations and should be treated as a global per-process timeout; callers may use it from multiple async contexts without additional synchronization. The function performs no filesystem I/O and has no special Windows filesystem behavior. Returned values do not contain or reveal any tokens and no redaction is performed by this function.
566
+ *
567
+ * @param pluginConfig - Plugin configuration that may contain a `streamStallTimeoutMs` override
568
+ * @returns The effective stream stall timeout in milliseconds; at least 1000 ms, defaults to 45000 ms
569
+ */
570
+ export function getStreamStallTimeoutMs(pluginConfig) {
571
+ return resolveNumberSetting("CODEX_AUTH_STREAM_STALL_TIMEOUT_MS", pluginConfig.streamStallTimeoutMs, 45_000, { min: 1_000 });
572
+ }
573
+ /**
574
+ * Determine whether live account synchronization is enabled.
575
+ *
576
+ * Respects the environment override `CODEX_AUTH_LIVE_ACCOUNT_SYNC`, falls back to
577
+ * `pluginConfig.liveAccountSync` when present, and defaults to `true`. This accessor performs no
578
+ * filesystem operations (behaves the same on Windows paths) and does not mutate or log token or
579
+ * credential material; callers are responsible for concurrency and must redact tokens before
580
+ * logging or persisting them.
581
+ *
582
+ * @param pluginConfig - The plugin configuration object used as the non-environment fallback
583
+ * @returns `true` if live account synchronization is enabled, `false` otherwise
584
+ */
585
+ export function getLiveAccountSync(pluginConfig) {
586
+ return resolveBooleanSetting("CODEX_AUTH_LIVE_ACCOUNT_SYNC", pluginConfig.liveAccountSync, true);
587
+ }
588
+ /**
589
+ * Get the debounce interval, in milliseconds, used when synchronizing live accounts.
590
+ *
591
+ * @param pluginConfig - Plugin configuration which may contain an override for the debounce value
592
+ * @returns The debounce interval in milliseconds; defaults to 250, and will be at least 50
593
+ *
594
+ * Concurrency: safe to call from multiple threads/tasks concurrently.
595
+ * Windows filesystem: value is independent of filesystem semantics.
596
+ * Token redaction: this value contains no secrets and is safe to log.
597
+ */
598
+ export function getLiveAccountSyncDebounceMs(pluginConfig) {
599
+ return resolveNumberSetting("CODEX_AUTH_LIVE_ACCOUNT_SYNC_DEBOUNCE_MS", pluginConfig.liveAccountSyncDebounceMs, 250, { min: 50 });
600
+ }
601
+ /**
602
+ * Determines the polling interval (in milliseconds) used by live account synchronization.
603
+ *
604
+ * @param pluginConfig - The plugin configuration to read the setting from.
605
+ * @returns The effective poll interval in milliseconds; guaranteed to be at least 500.
606
+ *
607
+ * Notes:
608
+ * - Concurrency: this value is used to debounce/drive polling and should be treated as a minimum per-worker interval when multiple workers run concurrently.
609
+ * - Platform: value is independent of Windows filesystem semantics.
610
+ * - Secrets: the returned value contains no sensitive tokens and is safe for logging (no redaction required).
611
+ */
612
+ export function getLiveAccountSyncPollMs(pluginConfig) {
613
+ return resolveNumberSetting("CODEX_AUTH_LIVE_ACCOUNT_SYNC_POLL_MS", pluginConfig.liveAccountSyncPollMs, 2_000, { min: 500 });
614
+ }
615
+ /**
616
+ * Indicates whether session affinity is enabled.
617
+ *
618
+ * Reads the `sessionAffinity` value from `pluginConfig` and allows an environment
619
+ * override via `CODEX_AUTH_SESSION_AFFINITY`. Safe for concurrent reads, unaffected
620
+ * by Windows filesystem semantics, and does not expose or log authentication tokens.
621
+ *
622
+ * @param pluginConfig - The plugin configuration to consult for the setting
623
+ * @returns `true` if session affinity is enabled, `false` otherwise
624
+ */
625
+ export function getSessionAffinity(pluginConfig) {
626
+ return resolveBooleanSetting("CODEX_AUTH_SESSION_AFFINITY", pluginConfig.sessionAffinity, true);
627
+ }
628
+ /**
629
+ * Get the session-affinity time-to-live in milliseconds.
630
+ *
631
+ * Reads CODEX_AUTH_SESSION_AFFINITY_TTL_MS from the environment if present, otherwise uses
632
+ * `pluginConfig.sessionAffinityTtlMs`, falling back to 20 minutes. The returned value is
633
+ * clamped to a minimum of 1000 ms.
634
+ *
635
+ * This function performs no filesystem I/O, is safe for concurrent callers, and does not
636
+ * read or emit any token or secret material (suitable for logging without redaction).
637
+ * Because it does no file operations, there are no Windows filesystem semantics to consider.
638
+ *
639
+ * @param pluginConfig - The plugin configuration to read the setting from
640
+ * @returns The effective session-affinity TTL in milliseconds (minimum 1000)
641
+ */
642
+ export function getSessionAffinityTtlMs(pluginConfig) {
643
+ return resolveNumberSetting("CODEX_AUTH_SESSION_AFFINITY_TTL_MS", pluginConfig.sessionAffinityTtlMs, 20 * 60_000, { min: 1_000 });
644
+ }
645
+ /**
646
+ * Determine the configured maximum number of session-affinity entries.
647
+ *
648
+ * @param pluginConfig - The plugin configuration to read the `sessionAffinityMaxEntries` setting from.
649
+ * @returns The effective maximum number of affinity entries (minimum 8, default 512).
650
+ *
651
+ * Concurrency: value is used for in-memory sizing and should be safe for concurrent use by runtime components.
652
+ * Filesystem: value is runtime-only and unaffected by Windows filesystem semantics.
653
+ * Security: this setting contains no secrets and is safe to log; it does not include tokens or credentials.
654
+ */
655
+ export function getSessionAffinityMaxEntries(pluginConfig) {
656
+ return resolveNumberSetting("CODEX_AUTH_SESSION_AFFINITY_MAX_ENTRIES", pluginConfig.sessionAffinityMaxEntries, 512, { min: 8 });
657
+ }
658
+ /**
659
+ * Controls whether the proactive refresh guardian is enabled.
660
+ *
661
+ * When enabled, background refreshes may run concurrently; callers should assume safe concurrent access.
662
+ * Configuration respects cross-platform semantics (including Windows filesystem behavior) when persisting or migrating settings.
663
+ * Any tokens or sensitive values observed during refresh operations are redacted from logs and persisted records.
664
+ *
665
+ * @param pluginConfig - The plugin configuration object to read the setting from
666
+ * @returns `true` if the proactive refresh guardian is enabled, `false` otherwise.
667
+ */
668
+ export function getProactiveRefreshGuardian(pluginConfig) {
669
+ return resolveBooleanSetting("CODEX_AUTH_PROACTIVE_GUARDIAN", pluginConfig.proactiveRefreshGuardian, true);
670
+ }
671
+ /**
672
+ * Determines the proactive refresh guardian interval in milliseconds.
673
+ *
674
+ * Uses the environment override `CODEX_AUTH_PROACTIVE_GUARDIAN_INTERVAL_MS` if present; otherwise uses
675
+ * the configured `pluginConfig.proactiveRefreshIntervalMs` or the default of 60000 ms. The resulting
676
+ * value is constrained to be at least 5000 ms.
677
+ *
678
+ * Concurrency assumption: callers may be invoked from multiple timers/workers concurrently.
679
+ * Windows filesystem and token-redaction concerns do not affect this getter.
680
+ *
681
+ * @param pluginConfig - Plugin configuration used as the fallback source for the interval value
682
+ * @returns The proactive refresh interval in milliseconds (>= 5000)
683
+ */
684
+ export function getProactiveRefreshIntervalMs(pluginConfig) {
685
+ return resolveNumberSetting("CODEX_AUTH_PROACTIVE_GUARDIAN_INTERVAL_MS", pluginConfig.proactiveRefreshIntervalMs, 60_000, { min: 5_000 });
686
+ }
687
+ /**
688
+ * Get the proactive refresh guardian buffer interval in milliseconds.
689
+ *
690
+ * @param pluginConfig - Plugin configuration object; `proactiveRefreshBufferMs` may override the default
691
+ * @returns The buffer interval in milliseconds: at least 30000, default 300000
692
+ *
693
+ * Concurrency: this value is shared across concurrent proactive-refresh workers and should be treated as a global timing setting.
694
+ * Windows filesystem: not related to filesystem behavior.
695
+ * Token redaction: environment values and config contents may be redacted in logs and diagnostics.
696
+ */
697
+ export function getProactiveRefreshBufferMs(pluginConfig) {
698
+ return resolveNumberSetting("CODEX_AUTH_PROACTIVE_GUARDIAN_BUFFER_MS", pluginConfig.proactiveRefreshBufferMs, 5 * 60_000, { min: 30_000 });
699
+ }
700
+ /**
701
+ * Get the network error cooldown interval used before retrying network operations.
702
+ *
703
+ * @param pluginConfig - Plugin configuration to read override values from
704
+ * @returns The cooldown interval in milliseconds (greater than or equal to 0)
705
+ *
706
+ * Concurrency: callers may read and cache this value; it is read-only at call time.
707
+ * Windows filesystem: no platform-specific filesystem behavior affects this setting.
708
+ * Token redaction: this function does not expose or log sensitive tokens.
709
+ */
710
+ export function getNetworkErrorCooldownMs(pluginConfig) {
711
+ return resolveNumberSetting("CODEX_AUTH_NETWORK_ERROR_COOLDOWN_MS", pluginConfig.networkErrorCooldownMs, 6_000, { min: 0 });
712
+ }
713
+ /**
714
+ * Get the cooldown duration in milliseconds to apply after a server error.
715
+ *
716
+ * Callers may invoke this concurrently; the returned value is read-only and safe for concurrent use.
717
+ * This function performs no filesystem access and is unaffected by Windows path semantics.
718
+ * It does not log or expose secrets — environment-derived values are treated as configuration, not token data.
719
+ *
720
+ * @param pluginConfig - Plugin configuration used to resolve the setting
721
+ * @returns The cooldown in milliseconds to use after a server error (minimum 0, default 4000)
722
+ */
723
+ export function getServerErrorCooldownMs(pluginConfig) {
724
+ return resolveNumberSetting("CODEX_AUTH_SERVER_ERROR_COOLDOWN_MS", pluginConfig.serverErrorCooldownMs, 4_000, { min: 0 });
725
+ }
726
+ /**
727
+ * Determines whether periodic storage backups are enabled.
728
+ *
729
+ * When enabled, background backup tasks may run concurrently; backups follow platform filesystem semantics (including Windows path behavior), and persisted backup data will have sensitive tokens redacted.
730
+ *
731
+ * @param pluginConfig - The plugin configuration to read the setting from
732
+ * @returns `true` if storage backup is enabled, `false` otherwise
733
+ */
734
+ export function getStorageBackupEnabled(pluginConfig) {
735
+ return resolveBooleanSetting("CODEX_AUTH_STORAGE_BACKUP_ENABLED", pluginConfig.storageBackupEnabled, true);
736
+ }
737
+ /**
738
+ * Determines whether preemptive quota checks are enabled.
739
+ *
740
+ * Safe to call concurrently; this function does not access the filesystem (no Windows-specific behavior)
741
+ * and does not expose or log any authentication tokens.
742
+ *
743
+ * @param pluginConfig - Plugin configuration to read the preemptive quota setting from
744
+ * @returns `true` if preemptive quota is enabled, `false` otherwise
745
+ */
746
+ export function getPreemptiveQuotaEnabled(pluginConfig) {
747
+ return resolveBooleanSetting("CODEX_AUTH_PREEMPTIVE_QUOTA_ENABLED", pluginConfig.preemptiveQuotaEnabled, true);
748
+ }
749
+ /**
750
+ * Get the configured preemptive-quota remaining percentage for 5-hour windows.
751
+ *
752
+ * @param pluginConfig - Plugin configuration to read the setting from. The value may be overridden by the CODEX_AUTH_PREEMPTIVE_QUOTA_5H_REMAINING_PCT environment variable; environment override semantics are the same on Windows. Safe to call concurrently. The returned value does not contain sensitive tokens and requires no redaction.
753
+ * @returns The percentage (0–100) used as the preemptive quota threshold for 5-hour intervals.
754
+ */
755
+ export function getPreemptiveQuotaRemainingPercent5h(pluginConfig) {
756
+ return resolveNumberSetting("CODEX_AUTH_PREEMPTIVE_QUOTA_5H_REMAINING_PCT", pluginConfig.preemptiveQuotaRemainingPercent5h, 5, { min: 0, max: 100 });
757
+ }
758
+ /**
759
+ * Determine the percentage of quota to reserve for the 7-day window.
760
+ *
761
+ * Resolves the effective value from the environment variable `CODEX_AUTH_PREEMPTIVE_QUOTA_7D_REMAINING_PCT`,
762
+ * then from `pluginConfig.preemptiveQuotaRemainingPercent7d`, falling back to `5` if unset, and clamps the result
763
+ * to the inclusive range `0`–`100`.
764
+ *
765
+ * Concurrent reads are safe. Behavior is independent of Windows filesystem semantics. No sensitive tokens are included
766
+ * or returned by this function.
767
+ *
768
+ * @param pluginConfig - Plugin configuration object used as a fallback when the environment variable is not set
769
+ * @returns The reserved quota percentage for the 7-day window, an integer between `0` and `100`
770
+ */
771
+ export function getPreemptiveQuotaRemainingPercent7d(pluginConfig) {
772
+ return resolveNumberSetting("CODEX_AUTH_PREEMPTIVE_QUOTA_7D_REMAINING_PCT", pluginConfig.preemptiveQuotaRemainingPercent7d, 5, { min: 0, max: 100 });
773
+ }
774
+ /**
775
+ * Get the configured maximum deferral time (in milliseconds) for preemptive quota checks.
776
+ *
777
+ * Reads an environment override or the plugin configuration and enforces a minimum of 1000 ms.
778
+ *
779
+ * @param pluginConfig - Plugin configuration object to read the setting from
780
+ * @returns The maximum deferral interval in milliseconds
781
+ *
782
+ * Concurrency: concurrent config writers may not be observed immediately by readers.
783
+ * Filesystem note: config persistence/visibility may differ on Windows vs POSIX filesystems.
784
+ * Security: the returned value contains no sensitive tokens and is safe to log.
785
+ */
786
+ export function getPreemptiveQuotaMaxDeferralMs(pluginConfig) {
787
+ return resolveNumberSetting("CODEX_AUTH_PREEMPTIVE_QUOTA_MAX_DEFERRAL_MS", pluginConfig.preemptiveQuotaMaxDeferralMs, 2 * 60 * 60_000, { min: 1_000 });
788
+ }
789
+ //# sourceMappingURL=config.js.map