@openclaw/codex 2026.9.4 → 2026.9.6

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 (160) hide show
  1. package/assets/activity.svg +30 -0
  2. package/assets/icon.png +0 -0
  3. package/dist/.setup/account-usage-runtime-Eem_WGqY.mjs +75 -0
  4. package/dist/{apply-CvT8tW5i.js → .setup/apply-DDOi8uNx.mjs} +24 -30
  5. package/dist/.setup/async-questions-DdhOCwGi.mjs +27 -0
  6. package/dist/{attempt-client-cleanup-DKHzbwt5.js → .setup/attempt-client-cleanup-CEv1cKwF.mjs} +79 -49
  7. package/dist/.setup/attempt-terminal-gZrxQ35N.mjs +5 -0
  8. package/dist/.setup/attempt-workspace-context-DUVEn_CS.mjs +331 -0
  9. package/dist/{auth-binding-CWlKVccJ.js → .setup/auth-binding-DhLWa_7K.mjs} +1 -1
  10. package/dist/{auth-cache-key-7orQae_j.js → .setup/auth-cache-key-B2mpaI9D.mjs} +9 -81
  11. package/dist/{auth-profile-CwzO4_RG.js → .setup/auth-profile-WqZtZfXN.mjs} +1 -1
  12. package/dist/.setup/auth-profile-health-oHizqgye.mjs +53 -0
  13. package/dist/.setup/auth-profile-recovery-BREezV0L.mjs +14 -0
  14. package/dist/.setup/binding-connection-ThoatDcb.mjs +50 -0
  15. package/dist/.setup/binding-connection.runtime-yoHrABt-.mjs +48 -0
  16. package/dist/{bounded-turn-C_Hneydj.js → .setup/bounded-turn-B2HScEgT.mjs} +143 -32
  17. package/dist/{build-state-BCmg9-5p.js → .setup/build-state-C7EnDVgr.mjs} +1 -1
  18. package/dist/{capabilities-8vx68WLH.js → .setup/capabilities-CDXOOFdZ.mjs} +1 -1
  19. package/dist/{client-B57KWPQx.js → .setup/client-Cs08OXVQ.mjs} +705 -512
  20. package/dist/.setup/client-catalog-response-BMgt_4q4.mjs +285 -0
  21. package/dist/{command-dispatch-BpduNGVf.js → .setup/command-dispatch-B4JuOsOk.mjs} +8 -4
  22. package/dist/{command-formatters-lxlkNZgU.js → .setup/command-formatters-Bmqvr9tO.mjs} +8 -4
  23. package/dist/{command-handlers-tiL9iWj7.js → .setup/command-handlers-DW7Lx9oH.mjs} +137 -186
  24. package/dist/{command-rpc-CR5EOEZX.js → .setup/command-rpc-DixIj3HW.mjs} +47 -23
  25. package/dist/{compact-B_H3AOd7.js → .setup/compact-Du5UvtxA.mjs} +29 -51
  26. package/dist/{config-oIORaQ5T.js → .setup/config-BoTP_mrL.mjs} +5 -4
  27. package/dist/{config-options-CXMq1T-C.js → .setup/config-options-BvaRs51b.mjs} +50 -446
  28. package/dist/{protocol-5bh1G-H7.js → .setup/config-parsing-CcB9iPoq.mjs} +9 -51
  29. package/dist/{config-runtime-D0zGNIHu.js → .setup/config-runtime-C8Rw1m1m.mjs} +2 -2
  30. package/dist/.setup/config-security-BEReZ6go.mjs +380 -0
  31. package/dist/{conversation-binding-DsB4-O7U.js → .setup/conversation-binding-DP-gMrBP.mjs} +29 -38
  32. package/dist/{conversation-binding-data-BiodpIzq.js → .setup/conversation-binding-data-CrcyZk6O.mjs} +1 -1
  33. package/dist/{conversation-control-CtUu1b3m.js → .setup/conversation-control-uK9Jomlc.mjs} +32 -67
  34. package/dist/.setup/cyber-failover-DYwbmf1o.mjs +123 -0
  35. package/dist/.setup/cyber-failover-notice-e9G3J9-e.mjs +17 -0
  36. package/dist/{dynamic-tools-CmN5RAip.js → .setup/dynamic-tools-BDXl-8Cw.mjs} +936 -620
  37. package/dist/{effective-mcp-catalog-Bd73bqA8.js → .setup/effective-mcp-catalog-CvnvnGIe.mjs} +3 -3
  38. package/dist/{ephemeral-turn-D78_VmzO.js → .setup/ephemeral-turn-poidXgek.mjs} +9 -7
  39. package/dist/{event-projector-assistant-message-BmBM4qBV.js → .setup/event-projector-assistant-message-BsruvNrH.mjs} +8 -7
  40. package/dist/{event-projector-items-5VhsECCO.js → .setup/event-projector-items-C6L_5ZiT.mjs} +42 -8
  41. package/dist/{attempt-notifications-C9XufG6l.js → .setup/event-projector-usage-CnBS8Wa3.mjs} +102 -9
  42. package/dist/{harness-C7rRa7bm.js → .setup/harness-a_MsE4-_.mjs} +81 -21
  43. package/dist/{inference-context-Dr_mzvk4.js → .setup/inference-context-D96l9-ew.mjs} +1 -1
  44. package/dist/.setup/inference-proxy-b-aJMSxm.mjs +601 -0
  45. package/dist/{isolated-completion-DWVb5U2J.js → .setup/isolated-completion-BIZFhTNG.mjs} +18 -11
  46. package/dist/.setup/launch-args-DbFCehO7.mjs +196 -0
  47. package/dist/{managed-binary-D22rNEcR.js → .setup/managed-binary-BnshlFag.mjs} +14 -3
  48. package/dist/{managed-thread-store-D1t5jh3G.js → .setup/managed-thread-store-BMqgThVL.mjs} +48 -14
  49. package/dist/{model-catalog-Dq2n8f_p.js → .setup/model-catalog-CfXJf9Ay.mjs} +36 -12
  50. package/dist/{models-PCKOim-h.js → .setup/models-KnqBv0hE.mjs} +3 -3
  51. package/dist/.setup/native-auth-rWbGbMn3.mjs +57 -0
  52. package/dist/{native-subagent-history-DjtPloaU.js → .setup/native-subagent-history-0nYYgo0k.mjs} +43 -35
  53. package/dist/.setup/native-subagent-monitor-D3filLY2.mjs +4859 -0
  54. package/dist/{native-subagent-task-ids-BzTdT0Uw.js → .setup/native-subagent-task-ids-wb8CKZGN.mjs} +98 -44
  55. package/dist/{node-cli-sessions-DKaOmVUh.js → .setup/node-cli-sessions-r3avj02l.mjs} +86 -68
  56. package/dist/.setup/node-exec-server.runtime-BDwWXVpK.mjs +247 -0
  57. package/dist/{plan-7uG-Fp36.js → .setup/plan-CN3VwfNU.mjs} +133 -119
  58. package/dist/{plugin-activation-BYQculil.js → .setup/plugin-activation-DT3MNtqj.mjs} +7 -16
  59. package/dist/{plugin-app-cache-key-d0eKxy7b.js → .setup/plugin-app-cache-key-B2CsSdnV.mjs} +32 -44
  60. package/dist/{plugin-approval-roundtrip-Bz3FT8W5.js → .setup/plugin-approval-roundtrip-C0MZzCuP.mjs} +1 -1
  61. package/dist/{plugin-inventory-oL4Na1ZG.js → .setup/plugin-inventory-BoRei8Z4.mjs} +21 -11
  62. package/dist/{plugin-marketplace-discovery-P-Vndfqq.js → .setup/plugin-marketplace-discovery-CDOi8IFs.mjs} +4 -1
  63. package/dist/{plugin-metadata-cache-ClPJpRio.js → .setup/plugin-metadata-cache-Ctmo83k_.mjs} +226 -141
  64. package/dist/.setup/protocol-CANUwXJ3.mjs +17 -0
  65. package/dist/{provider-MQ8PVkuE.js → .setup/provider-BOsxQHOM.mjs} +9 -5
  66. package/dist/{provider-capabilities-BvCUL69A.js → .setup/provider-capabilities-BfE88FZ5.mjs} +31 -3
  67. package/dist/{rate-limits-CBMmWBr9.js → .setup/rate-limits-CMU3DUD0.mjs} +174 -151
  68. package/dist/{request-B7hEkBGq.js → .setup/request-D2L0zMrq.mjs} +130 -42
  69. package/dist/{run-attempt-CvsAAGa0.js → .setup/run-attempt-BHu11r7h.mjs} +1628 -966
  70. package/dist/.setup/run-attempt-lifecycle-B7JbY4-F.mjs +71 -0
  71. package/dist/{runtime-artifact-BJ0rXBZ_.js → .setup/runtime-artifact-CLZ8udh-.mjs} +29 -45
  72. package/dist/{sandbox-guard-BxgyoM8M.js → .setup/sandbox-guard-C2sRMOEY.mjs} +3 -3
  73. package/dist/{session-binding-CI8UuilT.js → .setup/session-binding-Cm0apEbd.mjs} +36 -83
  74. package/dist/{session-binding-record-Bcvslnhf.js → .setup/session-binding-record-BGoz8wOK.mjs} +202 -5
  75. package/dist/.setup/session-binding-sidecars-BlB5EvIS.mjs +546 -0
  76. package/dist/.setup/session-catalog-Pno1xGcl.mjs +2764 -0
  77. package/dist/{session-catalog-adoption-tcZi1IfX.js → .setup/session-catalog-adoption-BI6UTDZT.mjs} +58 -26
  78. package/dist/{session-catalog-archive-CwKg8Xz2.js → .setup/session-catalog-archive-lHBqzhaO.mjs} +6 -6
  79. package/dist/.setup/session-catalog-events-Bj6j94E_.mjs +347 -0
  80. package/dist/.setup/session-catalog-index-B9_VQIUz.mjs +1170 -0
  81. package/dist/.setup/session-catalog-index-order-hcdz07yN.mjs +48 -0
  82. package/dist/.setup/session-catalog-index-state-Dgh8ORJm.mjs +183 -0
  83. package/dist/{thread-history-page-D0JIp48V.js → .setup/session-catalog-native-projection-DowriLid.mjs} +223 -95
  84. package/dist/{session-catalog-node-adoption-B1tgDW-m.js → .setup/session-catalog-node-adoption-ITddWx_X.mjs} +25 -13
  85. package/dist/{session-catalog-node-continue-Ci-DXyJP.js → .setup/session-catalog-node-continue-6g6qi4A9.mjs} +24 -15
  86. package/dist/.setup/session-catalog-projection-zK_PUsiE.mjs +422 -0
  87. package/dist/{session-cli-DNN_jhGc.js → .setup/session-cli-irrD-uo7.mjs} +3 -3
  88. package/dist/{session-history-Cz-tOOYj.js → .setup/session-history-BrqhSCf9.mjs} +19 -19
  89. package/dist/{session-history-import-DwyijFI6.js → .setup/session-history-import-D-3wAgOP.mjs} +4 -4
  90. package/dist/{session-history-read-cxPUxyVV.js → .setup/session-history-read-B3ZfSUbF.mjs} +2 -2
  91. package/dist/{session-history.worker-BeK1YOyT.js → .setup/session-history.worker-CaCh0xF_.mjs} +10 -24
  92. package/dist/{session-initialization-Ct0_Hzmg.js → .setup/session-initialization-rcgbZiid.mjs} +2 -2
  93. package/dist/{session-permission-policy-BBmXA8Eg.js → .setup/session-permission-policy-BI1QK_98.mjs} +2 -2
  94. package/dist/{session-retirement-B2YfbAAT.js → .setup/session-retirement-BH-S6qkJ.mjs} +26 -7
  95. package/dist/{settled-turn-context-Du6gdUDd.js → .setup/settled-turn-context-fmwouT5I.mjs} +2 -2
  96. package/dist/{settled-turn-finalizer-Dn91stmc.js → .setup/settled-turn-finalizer-DiAi6Kx6.mjs} +12 -12
  97. package/dist/{shared-client-CscigXXL.js → .setup/shared-client-DA4VR4Eb.mjs} +332 -221
  98. package/dist/{side-question-BGMJQf4U.js → .setup/side-question-BJ1wSDcB.mjs} +183 -230
  99. package/dist/{source-CElXU0Oy.js → .setup/source-CUzJq5WN.mjs} +11 -10
  100. package/dist/.setup/spawn-error-CL3n6MAa.mjs +57 -0
  101. package/dist/{thread-archive-guard-DfpBr-Sz.js → .setup/thread-archive-guard-C9frICzX.mjs} +10 -3
  102. package/dist/.setup/thread-history-page-DoVpnHim.mjs +83 -0
  103. package/dist/{thread-lifecycle-DXl1-I6b.js → .setup/thread-lifecycle-B3mfz1TG.mjs} +347 -2318
  104. package/dist/{thread-ownership-BsDRTsyp.js → .setup/thread-ownership-DcTtAcXj.mjs} +6 -4
  105. package/dist/{thread-policy-BuEQfE0g.js → .setup/thread-policy-DGuQsuFC.mjs} +19 -3
  106. package/dist/.setup/thread-requests-BLvGkP2R.mjs +2205 -0
  107. package/dist/{thread-resume-DsDOZpFg.js → .setup/thread-resume-DIBXwOpE.mjs} +8 -5
  108. package/dist/.setup/timeout-C910MdAB.mjs +80 -0
  109. package/dist/{transcript-history-projection-v17wXgdK.js → .setup/transcript-history-projection-ZZpkFkJ_.mjs} +96 -61
  110. package/dist/{transcript-mirror-C5CKgNy6.js → .setup/transcript-mirror-BtHDBp1G.mjs} +224 -195
  111. package/dist/{transcript-mirror-attestation-DibE0krH.js → .setup/transcript-mirror-attestation-CzM-94NC.mjs} +4 -1
  112. package/dist/{transport-stdio-BGjHoYLr.js → .setup/transport-stdio-h2VHOskC.mjs} +274 -89
  113. package/dist/{upstream-session-fork-BZerMte_.js → .setup/upstream-session-fork-BUr4OUbC.mjs} +51 -42
  114. package/dist/{usage-tCk-mg_x.js → .setup/usage-BML0r-ua.mjs} +5 -4
  115. package/dist/.setup/usage-limit-error-DUMbefVY.mjs +166 -0
  116. package/dist/{web-search-provider.runtime-B7bGbGWq.js → .setup/web-search-provider.runtime-CDKlN-Rv.mjs} +4 -3
  117. package/dist/api.js +23 -12
  118. package/dist/catalog-page-worker-entrypoint.js +12 -0
  119. package/dist/catalog-page.worker.js +9 -0
  120. package/dist/cli-metadata.js +1 -1
  121. package/dist/doctor-contract-api.js +8 -528
  122. package/dist/harness.js +1 -1
  123. package/dist/index.js +306 -215
  124. package/dist/media-understanding-provider.js +3 -3
  125. package/dist/migration-provider-api.js +1 -1
  126. package/dist/native-tool-policy.js +12 -0
  127. package/dist/provider-discovery.js +18 -0
  128. package/dist/session-history-worker-runtime.js +36 -35
  129. package/dist/session-history.worker.js +2 -2
  130. package/dist/web-search-contract-api.js +1 -1
  131. package/openclaw.plugin.json +354 -69
  132. package/package.json +14 -8
  133. package/dist/attempt-context-D44yeDII.js +0 -636
  134. package/dist/binding-connection-D3udKbIE.js +0 -60
  135. package/dist/event-projector-usage-BfXmpOec.js +0 -102
  136. package/dist/inference-proxy-HGXKUIre.js +0 -311
  137. package/dist/launch-args-RXQwn8zV.js +0 -90
  138. package/dist/native-subagent-history-owner-Pcp7JuVF.js +0 -34
  139. package/dist/native-subagent-monitor-fQ7eIXEf.js +0 -2978
  140. package/dist/node-exec-server.runtime-Cl2vapEi.js +0 -226
  141. package/dist/rpc-error-ttN_QjEm.js +0 -30
  142. package/dist/session-catalog-ct-AH01I.js +0 -1477
  143. package/dist/session-catalog-home-id-CwhLdC03.js +0 -18
  144. package/dist/session-catalog-provenance-D-dIfRAH.js +0 -96
  145. package/dist/timeout-BVw8aaM1.js +0 -33
  146. package/dist/version-omm6IPI3.js +0 -12
  147. /package/dist/{auth-profile-selection-DFu9e167.js → .setup/auth-profile-selection-DFu9e167.mjs} +0 -0
  148. /package/dist/{config-utils-DujwEnhg.js → .setup/config-utils-DujwEnhg.mjs} +0 -0
  149. /package/dist/{context-compaction-activity-CMla5FPJ.js → .setup/context-compaction-activity-CMla5FPJ.mjs} +0 -0
  150. /package/dist/{elicitation-input-DPoK8F7w.js → .setup/elicitation-input-DPoK8F7w.mjs} +0 -0
  151. /package/dist/{history-rejection-B4nOXNXF.js → .setup/history-rejection-B4nOXNXF.mjs} +0 -0
  152. /package/dist/{image-payload-sanitizer-tupAr-2o.js → .setup/image-payload-sanitizer-tupAr-2o.mjs} +0 -0
  153. /package/dist/{incognito-session-uhrBF6wJ.js → .setup/incognito-session-uhrBF6wJ.mjs} +0 -0
  154. /package/dist/{local-runtime-attribution-B_2mnqhb.js → .setup/local-runtime-attribution-B_2mnqhb.mjs} +0 -0
  155. /package/dist/{native-execution-policy-umd0tgh4.js → .setup/native-execution-policy-umd0tgh4.mjs} +0 -0
  156. /package/dist/{rolldown-runtime-8H4AJuhK.js → .setup/rolldown-runtime-8H4AJuhK.mjs} +0 -0
  157. /package/dist/{session-binding-meta-B7aEMU7g.js → .setup/session-binding-meta-B7aEMU7g.mjs} +0 -0
  158. /package/dist/{session-upstream-marker-D15C9NHp.js → .setup/session-upstream-marker-D15C9NHp.mjs} +0 -0
  159. /package/dist/{upstream-prompt-provenance-LphB8slG.js → .setup/upstream-prompt-provenance-LphB8slG.mjs} +0 -0
  160. /package/dist/{web-search-provider.shared-BrZmlqyR.js → .setup/web-search-provider.shared-BrZmlqyR.mjs} +0 -0
@@ -1,32 +1,122 @@
1
- import { o as isJsonObject } from "./protocol-5bh1G-H7.js";
2
- import { asBoolean, asFiniteNumber, normalizeOptionalString, parseStrictFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { i as isJsonObject } from "./protocol-CANUwXJ3.mjs";
2
+ import { normalizeOptionalString, parseStrictFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { z } from "zod";
3
4
  import { MAX_DATE_TIMESTAMP_MS, resolveExpiresAtMsFromEpochSeconds } from "openclaw/plugin-sdk/number-runtime";
4
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
5
5
  import { PROVIDER_LABELS, clampPercent } from "openclaw/plugin-sdk/provider-usage";
6
+ //#region extensions/codex/src/app-server/rate-limit-time.ts
7
+ /** Human-readable Codex quota reset times; no account or routing decisions. */
8
+ const ONE_SECOND_MS = 1e3;
9
+ const ONE_MINUTE_MS = 6e4;
10
+ const ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
11
+ const ONE_DAY_MS$1 = 24 * ONE_HOUR_MS;
12
+ function formatCalendarResetTime(resetsAtMs, nowMs) {
13
+ const resetDate = new Date(resetsAtMs);
14
+ const resetParts = new Intl.DateTimeFormat("en-US", {
15
+ month: "short",
16
+ day: "numeric",
17
+ ...resetDate.getFullYear() === new Date(nowMs).getFullYear() ? {} : { year: "numeric" },
18
+ hour: "numeric",
19
+ minute: "2-digit",
20
+ timeZoneName: "short"
21
+ }).formatToParts(resetDate);
22
+ const part = (type) => resetParts.find((entry) => entry.type === type)?.value;
23
+ const dateParts = [
24
+ part("month"),
25
+ part("day"),
26
+ part("year")
27
+ ].filter(Boolean);
28
+ return [
29
+ dateParts.length > 1 ? `${dateParts[0]} ${dateParts.slice(1).join(", ")}` : dateParts[0],
30
+ "at",
31
+ [
32
+ [part("hour"), part("minute")].filter(Boolean).join(":"),
33
+ part("dayPeriod"),
34
+ part("timeZoneName")
35
+ ].filter(Boolean).join(" ")
36
+ ].filter(Boolean).join(" ");
37
+ }
38
+ function formatRelativeDuration(durationMs) {
39
+ const safeMs = Math.max(1e3, durationMs);
40
+ if (safeMs < ONE_MINUTE_MS) return `${Math.ceil(safeMs / 1e3)} seconds`;
41
+ if (safeMs < ONE_HOUR_MS) {
42
+ const minutes = Math.ceil(safeMs / ONE_MINUTE_MS);
43
+ return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
44
+ }
45
+ if (safeMs < ONE_DAY_MS$1) {
46
+ const hours = Math.ceil(safeMs / ONE_HOUR_MS);
47
+ return `${hours} ${hours === 1 ? "hour" : "hours"}`;
48
+ }
49
+ const days = Math.ceil(safeMs / ONE_DAY_MS$1);
50
+ return `${days} ${days === 1 ? "day" : "days"}`;
51
+ }
52
+ function formatResetDuration(resetsAtMs, nowMs) {
53
+ const durationMs = Math.round(Math.max(ONE_SECOND_MS, resetsAtMs - nowMs) / ONE_SECOND_MS) * ONE_SECOND_MS;
54
+ const days = Math.floor(durationMs / ONE_DAY_MS$1);
55
+ const hours = Math.floor(durationMs % ONE_DAY_MS$1 / ONE_HOUR_MS);
56
+ const minutes = Math.floor(durationMs % ONE_HOUR_MS / ONE_MINUTE_MS);
57
+ const seconds = Math.floor(durationMs % ONE_MINUTE_MS / ONE_SECOND_MS);
58
+ if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
59
+ if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
60
+ if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
61
+ return `${seconds}s`;
62
+ }
63
+ //#endregion
6
64
  //#region extensions/codex/src/app-server/rate-limits.ts
7
65
  /**
8
66
  * Parses Codex account rate-limit payloads into user-facing usage summaries,
9
67
  * reset hints, and enriched usage-limit error messages.
10
68
  */
11
69
  const CODEX_LIMIT_ID = "codex";
70
+ const CODEX_RESERVE_ROUTE = "gpt-reserve";
71
+ const CODEX_RESERVE_USAGE_NOTICE = "Luna Reserve is a separate, backend-authorized route. Ordinary Luna does not use this reserve, even with Fast off. An unused reserve does not establish eligibility or per-request billing. Ordinary usage may consume credits after included limits are reached.";
12
72
  const LIMIT_WINDOW_KEYS = ["primary", "secondary"];
13
- const ONE_SECOND_MS = 1e3;
14
- const ONE_MINUTE_MS = 6e4;
15
- const ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
16
- const ONE_DAY_MS = 24 * ONE_HOUR_MS;
73
+ const ONE_DAY_MS = 864e5;
17
74
  const DAY_WINDOW_MINUTES = 1440;
18
75
  const WEEKLY_WINDOW_MINUTES = 7 * DAY_WINDOW_MINUTES;
19
76
  const WEEKLY_RESET_GAP_MS = 3 * ONE_DAY_MS;
20
77
  const CODEX_USAGE_LIMIT_MESSAGE_PREFIX = "You've reached your Codex subscription usage limit.";
21
78
  const CODEX_USAGE_LIMIT_STATE_MISMATCH_MESSAGE = "Codex rejected the request with a usage-limit error, but its current account usage does not report an exhausted limit.";
79
+ const optionalNumber = z.number().optional().catch(void 0);
80
+ const optionalBoolean = z.boolean().optional().catch(void 0);
81
+ const rateLimitWindowSchema = z.object({
82
+ usedPercent: optionalNumber,
83
+ resetsAt: optionalNumber,
84
+ windowDurationMins: optionalNumber
85
+ }).transform((window) => ({
86
+ usedPercent: window.usedPercent,
87
+ resetsAtMs: resolveExpiresAtMsFromEpochSeconds(window.resetsAt, { maxMs: MAX_DATE_TIMESTAMP_MS }) ?? 0,
88
+ windowDurationMins: window.windowDurationMins
89
+ })).optional().catch(void 0);
90
+ const creditsSchema = z.object({
91
+ hasCredits: optionalBoolean,
92
+ unlimited: optionalBoolean,
93
+ balance: z.preprocess(parseStrictFiniteNumber, optionalNumber)
94
+ }).optional().catch(void 0);
95
+ const rateLimitSnapshotSchema = z.looseObject({
96
+ primary: rateLimitWindowSchema,
97
+ secondary: rateLimitWindowSchema,
98
+ credits: creditsSchema
99
+ }).transform((snapshot) => ({
100
+ primary: snapshot.primary,
101
+ secondary: snapshot.secondary,
102
+ credits: snapshot.credits,
103
+ limitId: normalizeOptionalString(snapshot.limitId),
104
+ limitName: normalizeOptionalString(snapshot.limitName),
105
+ planType: normalizeOptionalString(snapshot.planType),
106
+ rateLimitReachedType: normalizeOptionalString(snapshot.rateLimitReachedType)
107
+ }));
22
108
  /** Enriches Codex usage-limit failures with reset timing and recovery guidance. */
23
109
  function formatCodexUsageLimitErrorMessage(params) {
24
110
  const message = normalizeOptionalString(params.message);
25
- if (!isCodexUsageLimitError(params.codexErrorInfo)) return;
111
+ if (params.codexErrorInfo !== "usageLimitExceeded") return;
26
112
  const nowMs = params.nowMs ?? Date.now();
27
- const usageSummary = summarizeCodexAccountUsage(params.rateLimits, nowMs);
28
- if (params.rateLimitsAuthoritative && hasCodexRateLimitSnapshots(params.rateLimits) && !usageSummary?.blocked) return [CODEX_USAGE_LIMIT_STATE_MISMATCH_MESSAGE, "Retry the request, use another Codex account if available, or switch to another configured model/provider."].join(" ");
29
- const nextReset = selectBlockingRateLimitReset(params.rateLimits, nowMs) ?? (usageSummary?.blocked ? void 0 : selectNextRateLimitReset(params.rateLimits, nowMs));
113
+ const ordinaryUsageAllowed = readOrdinaryUsageAllowed(params.rateLimits);
114
+ const snapshots = collectCodexRateLimitSnapshots(params.rateLimits).filter(snapshotHasDisplayableData);
115
+ const usageSnapshot = snapshots.find(isCodexLimitSnapshot) ?? snapshots[0];
116
+ const blockingSnapshot = selectBlockingRateLimitSnapshot(snapshots, ordinaryUsageAllowed);
117
+ const usageSummary = usageSnapshot ? summarizeRateLimitUsage(usageSnapshot, blockingSnapshot, nowMs) : void 0;
118
+ if (params.rateLimitsAuthoritative && (ordinaryUsageAllowed === true && !blockingSnapshot || ordinaryUsageAllowed === void 0 && usageSummary?.blocked === false)) return [CODEX_USAGE_LIMIT_STATE_MISMATCH_MESSAGE, "Retry the request, use another Codex account if available, or switch to another configured model/provider."].join(" ");
119
+ const nextReset = (blockingSnapshot ? selectSnapshotBlockingReset(blockingSnapshot, nowMs) : void 0) ?? (ordinaryUsageAllowed === void 0 && !usageSummary?.blocked ? selectNextRateLimitReset(params.rateLimits, nowMs) : void 0);
30
120
  const parts = [CODEX_USAGE_LIMIT_MESSAGE_PREFIX];
31
121
  let recoveryAction = "Wait until Codex becomes available";
32
122
  if (nextReset) {
@@ -55,7 +145,8 @@ function summarizeCodexRateLimits(value, nowMs = Date.now()) {
55
145
  const snapshots = collectCodexRateLimitSnapshots(value).filter(snapshotHasDisplayableData);
56
146
  if (snapshots.length === 0) return;
57
147
  const summaries = snapshots.slice(0, 4).map((snapshot) => summarizeRateLimitSnapshot(snapshot, nowMs)).filter((summary) => summary !== void 0);
58
- return summaries.length > 0 ? summaries.join("; ") : void 0;
148
+ if (summaries.length === 0) return;
149
+ return [summaries.join("; "), reserveUsageNotice(snapshots)].filter(Boolean).join(". ");
59
150
  }
60
151
  /** Returns true when a value contains any recognizable Codex rate-limit snapshots. */
61
152
  function hasCodexRateLimitSnapshots(value) {
@@ -74,11 +165,17 @@ function resolveCodexUsageLimitResetAtMs(value, nowMs = Date.now()) {
74
165
  }
75
166
  /** Summarizes account availability, blocking reason, and reset time from rate-limit data. */
76
167
  function summarizeCodexAccountUsage(value, nowMs = Date.now()) {
77
- const snapshots = collectCodexRateLimitSnapshots(value).filter(snapshotHasDisplayableData);
78
- if (snapshots.length === 0) return;
79
- const usageSnapshot = snapshots.find(isCodexLimitSnapshot) ?? expectDefined(snapshots[0], "displayable Codex rate-limit snapshot");
80
- const blockedSnapshots = snapshots.filter(snapshotHasLimitBlock);
81
- const blockingSnapshot = blockedSnapshots.find(isCodexLimitSnapshot) ?? blockedSnapshots[0] ?? void 0;
168
+ const ordinaryUsageAllowed = readOrdinaryUsageAllowed(value);
169
+ if (ordinaryUsageAllowed === null) return;
170
+ const snapshot = collectCodexRateLimitSnapshots(value).find(isCodexLimitSnapshot);
171
+ if (ordinaryUsageAllowed !== void 0) return {
172
+ usageLine: snapshot ? formatUsageLine(snapshot) : void 0,
173
+ blocked: !ordinaryUsageAllowed,
174
+ ...!ordinaryUsageAllowed ? { blockingReason: "Codex usage limit is reached" } : {}
175
+ };
176
+ return snapshot && snapshotHasDisplayableData(snapshot) ? summarizeRateLimitUsage(snapshot, snapshotHasLimitBlock(snapshot) ? snapshot : void 0, nowMs) : void 0;
177
+ }
178
+ function summarizeRateLimitUsage(usageSnapshot, blockingSnapshot, nowMs) {
82
179
  const blockingEntries = blockingSnapshot ? readWindowEntries(blockingSnapshot) : [];
83
180
  const blockingWindowEntry = selectBlockingWindowEntry(blockingEntries, nowMs);
84
181
  const blockingWindow = blockingWindowEntry?.window;
@@ -98,69 +195,76 @@ function summarizeCodexAccountUsage(value, nowMs = Date.now()) {
98
195
  };
99
196
  }
100
197
  /** Converts Codex app-server rate-limit payloads into OpenAI/Codex usage windows. */
101
- function buildCodexAppServerUsageSnapshot(value) {
102
- const snapshot = selectCodexProviderUsageSnapshot(value);
198
+ function buildCodexAppServerUsageSnapshot(value, options = {}) {
199
+ const snapshots = collectCodexRateLimitSnapshots(value);
200
+ const snapshot = snapshots.find(isCodexLimitSnapshot) ?? snapshots[0];
103
201
  const entries = snapshot ? readWindowEntries(snapshot) : [];
104
202
  const windows = entries.map((entry) => readProviderUsageWindow(entry, entries)).filter((window) => Boolean(window));
105
- return {
203
+ const summary = reserveUsageNotice(snapshots);
204
+ const result = {
205
+ ...summary ? { summary } : {},
106
206
  provider: "openai",
107
207
  displayName: PROVIDER_LABELS.openai,
108
208
  windows,
109
209
  ...snapshot ? { plan: resolveCodexProviderUsagePlan(snapshot) } : {}
110
210
  };
111
- }
112
- function isCodexUsageLimitError(codexErrorInfo) {
113
- if (codexErrorInfo === "usageLimitExceeded") return true;
114
- if (typeof codexErrorInfo === "string") {
115
- if (codexErrorInfo.replace(/[_\s-]/gu, "").toLowerCase() === "usagelimitexceeded") return true;
211
+ if (options.accountDetails && snapshot) {
212
+ result.plan = snapshot.planType;
213
+ for (const extra of snapshots) {
214
+ if (extra === snapshot) continue;
215
+ const extraEntries = readWindowEntries(extra);
216
+ for (const entry of extraEntries) {
217
+ const window = readProviderUsageWindow(entry, extraEntries);
218
+ if (window) windows.push({
219
+ ...window,
220
+ groupLabel: formatLimitLabel(extra)
221
+ });
222
+ }
223
+ }
224
+ const credits = snapshot.credits;
225
+ const balance = credits?.balance;
226
+ if (balance !== void 0 && balance >= 0 && credits?.unlimited !== true) result.billing = [{
227
+ type: "balance",
228
+ amount: balance,
229
+ unit: "credits"
230
+ }];
116
231
  }
117
- return false;
232
+ return result;
118
233
  }
119
234
  function selectNextRateLimitReset(value, nowMs) {
120
- const futureWindows = collectCodexRateLimitSnapshots(value).flatMap((snapshot) => LIMIT_WINDOW_KEYS.flatMap((key) => readRateLimitWindow(snapshot, key) ?? [])).filter((window) => window.resetsAtMs > nowMs);
235
+ const futureWindows = collectCodexRateLimitSnapshots(value).flatMap((snapshot) => LIMIT_WINDOW_KEYS.flatMap((key) => snapshot[key] ?? [])).filter((window) => window.resetsAtMs > nowMs);
121
236
  if (futureWindows.length === 0) return;
122
237
  const exhaustedWindows = futureWindows.filter((window) => window.usedPercent !== void 0 && window.usedPercent >= 100);
123
238
  return (exhaustedWindows.length > 0 ? exhaustedWindows : futureWindows).toSorted((left, right) => left.resetsAtMs - right.resetsAtMs)[0];
124
239
  }
125
240
  function selectBlockingRateLimitReset(value, nowMs) {
126
- const blockedSnapshots = collectCodexRateLimitSnapshots(value).filter(snapshotHasLimitBlock);
127
- const blockingSnapshot = blockedSnapshots.find(isCodexLimitSnapshot) ?? blockedSnapshots[0] ?? void 0;
241
+ const blockingSnapshot = selectBlockingRateLimitSnapshot(collectCodexRateLimitSnapshots(value), readOrdinaryUsageAllowed(value));
128
242
  return blockingSnapshot ? selectSnapshotBlockingReset(blockingSnapshot, nowMs) : void 0;
129
243
  }
244
+ function selectBlockingRateLimitSnapshot(snapshots, ordinaryUsageAllowed) {
245
+ const blockedSnapshots = snapshots.filter((snapshot) => snapshotHasLimitBlock(snapshot) && (ordinaryUsageAllowed !== true && ordinaryUsageAllowed !== null || !isCodexLimitSnapshot(snapshot)));
246
+ return blockedSnapshots.find(isCodexLimitSnapshot) ?? blockedSnapshots[0];
247
+ }
130
248
  function summarizeRateLimitSnapshot(snapshot, nowMs) {
131
249
  const label = formatLimitLabel(snapshot);
132
250
  const windows = LIMIT_WINDOW_KEYS.flatMap((key) => {
133
- const window = readRateLimitWindow(snapshot, key);
251
+ const window = snapshot[key];
134
252
  return window ? [formatRateLimitWindow(key, window, nowMs)] : [];
135
253
  });
136
- const reachedType = normalizeOptionalString(snapshot.rateLimitReachedType) ?? normalizeOptionalString(snapshot.rate_limit_reached_type);
254
+ const reachedType = snapshot.rateLimitReachedType;
137
255
  const suffix = reachedType ? ` (${formatReachedType(reachedType)})` : "";
138
256
  if (windows.length > 0) return `${label}: ${windows.join(" · ")}${suffix}`;
139
257
  if (reachedType) return `${label}: ${formatReachedType(reachedType)}`;
140
258
  }
141
259
  function collectCodexRateLimitSnapshots(value) {
142
- const snapshots = [];
143
- collectRateLimitSnapshots(value, snapshots, /* @__PURE__ */ new Set());
144
- return snapshots;
145
- }
146
- function collectRateLimitSnapshots(value, snapshots, seen) {
147
- if (Array.isArray(value)) {
148
- for (const entry of value) collectRateLimitSnapshots(entry, snapshots, seen);
149
- return;
150
- }
151
- if (!isJsonObject(value)) return;
152
- if (isRateLimitSnapshot(value)) {
153
- addRateLimitSnapshot(value, snapshots, seen);
154
- return;
155
- }
260
+ if (!isJsonObject(value)) return [];
261
+ if (isRateLimitSnapshot(value)) return [rateLimitSnapshotSchema.parse(value)];
156
262
  const byLimitId = value.rateLimitsByLimitId;
157
- if (isJsonObject(byLimitId)) for (const key of sortedRateLimitKeys(Object.keys(byLimitId))) collectRateLimitSnapshots(byLimitId[key], snapshots, seen);
158
- const snakeByLimitId = value.rate_limits_by_limit_id;
159
- if (isJsonObject(snakeByLimitId)) for (const key of sortedRateLimitKeys(Object.keys(snakeByLimitId))) collectRateLimitSnapshots(snakeByLimitId[key], snapshots, seen);
160
- collectRateLimitSnapshots(value.rateLimits, snapshots, seen);
161
- collectRateLimitSnapshots(value.rate_limits, snapshots, seen);
162
- collectRateLimitSnapshots(value.data, snapshots, seen);
163
- collectRateLimitSnapshots(value.items, snapshots, seen);
263
+ return (isJsonObject(byLimitId) ? sortedRateLimitKeys(Object.keys(byLimitId)).map((key) => byLimitId[key]) : [value.rateLimits]).filter(isJsonObject).filter(isRateLimitSnapshot).map((snapshot) => rateLimitSnapshotSchema.parse(snapshot));
264
+ }
265
+ function readOrdinaryUsageAllowed(value) {
266
+ const allowed = isJsonObject(value) ? value.ordinaryUsageAllowed : void 0;
267
+ return allowed === null || typeof allowed === "boolean" ? allowed : void 0;
164
268
  }
165
269
  function sortedRateLimitKeys(keys) {
166
270
  return keys.toSorted((left, right) => {
@@ -169,38 +273,11 @@ function sortedRateLimitKeys(keys) {
169
273
  return left.localeCompare(right);
170
274
  });
171
275
  }
172
- function addRateLimitSnapshot(snapshot, snapshots, seen) {
173
- const signature = [
174
- normalizeOptionalString(snapshot.limitId) ?? normalizeOptionalString(snapshot.limit_id) ?? "",
175
- normalizeOptionalString(snapshot.limitName) ?? normalizeOptionalString(snapshot.limit_name) ?? "",
176
- formatWindowSignature(snapshot.primary),
177
- formatWindowSignature(snapshot.secondary)
178
- ].join("|");
179
- if (seen.has(signature)) return;
180
- seen.add(signature);
181
- snapshots.push(snapshot);
182
- }
183
276
  function isRateLimitSnapshot(value) {
184
- return isJsonObject(value.primary) || isJsonObject(value.secondary) || value.rateLimitReachedType !== void 0 || value.rate_limit_reached_type !== void 0 || value.limitId !== void 0 || value.limit_id !== void 0 || value.limitName !== void 0 || value.limit_name !== void 0;
185
- }
186
- function readRateLimitWindow(snapshot, key) {
187
- const window = snapshot[key];
188
- if (!isJsonObject(window)) return;
189
- const resetsAt = asFiniteNumber(window.resetsAt) ?? asFiniteNumber(window.resets_at);
190
- return {
191
- resetsAtMs: resolveExpiresAtMsFromEpochSeconds(resetsAt, { maxMs: MAX_DATE_TIMESTAMP_MS }) ?? 0,
192
- ...readOptionalNumberField(window, "usedPercent", "used_percent"),
193
- ...readOptionalNumberField(window, "windowDurationMins", "window_duration_mins", "windowMinutes", "window_minutes")
194
- };
277
+ return isJsonObject(value.primary) || isJsonObject(value.secondary) || value.rateLimitReachedType !== void 0 || value.limitId !== void 0 || value.limitName !== void 0;
195
278
  }
196
279
  function snapshotHasDisplayableData(snapshot) {
197
- if (normalizeOptionalString(snapshot.rateLimitReachedType) ?? normalizeOptionalString(snapshot.rate_limit_reached_type)) return true;
198
- return readWindowEntries(snapshot).some((entry) => entry.window.usedPercent !== void 0 || entry.window.resetsAtMs > 0);
199
- }
200
- function readOptionalNumberField(record, ...keys) {
201
- const value = keys.map((key) => asFiniteNumber(record[key])).find((entry) => entry !== void 0);
202
- if (value === void 0) return {};
203
- return keys.some((key) => key.toLowerCase().includes("window")) ? { windowDurationMins: value } : { usedPercent: value };
280
+ return Boolean(snapshot.rateLimitReachedType) || readWindowEntries(snapshot).some((entry) => entry.window.usedPercent !== void 0 || entry.window.resetsAtMs > 0);
204
281
  }
205
282
  function formatRateLimitWindow(key, window, nowMs) {
206
283
  return `${key} ${formatRateLimitWindowDetails(window, nowMs)}`;
@@ -208,8 +285,15 @@ function formatRateLimitWindow(key, window, nowMs) {
208
285
  function formatRateLimitWindowDetails(window, nowMs) {
209
286
  return `${window.usedPercent === void 0 ? "usage unknown" : `${Math.max(0, 100 - Math.round(window.usedPercent))}% left`}${window.resetsAtMs > nowMs ? ` ⏱${formatResetDuration(window.resetsAtMs, nowMs)}` : ""}`;
210
287
  }
288
+ function reserveUsageNotice(snapshots) {
289
+ return snapshots.some(isReserveSnapshot) ? CODEX_RESERVE_USAGE_NOTICE : void 0;
290
+ }
291
+ function isReserveSnapshot(snapshot) {
292
+ return snapshot.limitName === CODEX_RESERVE_ROUTE || snapshot.limitId === CODEX_RESERVE_ROUTE;
293
+ }
211
294
  function formatLimitLabel(snapshot) {
212
- const label = normalizeOptionalString(snapshot.limitName) ?? normalizeOptionalString(snapshot.limit_name) ?? normalizeOptionalString(snapshot.limitId) ?? normalizeOptionalString(snapshot.limit_id);
295
+ if (isReserveSnapshot(snapshot)) return "Luna Reserve (separate route)";
296
+ const label = snapshot.limitName ?? snapshot.limitId;
213
297
  if (!label || label === CODEX_LIMIT_ID) return "Codex";
214
298
  return label.replace(/[_-]+/gu, " ").replace(/\s+/gu, " ").trim();
215
299
  }
@@ -223,15 +307,10 @@ function formatAccountResetTime(resetsAtMs, nowMs) {
223
307
  return `${formatCalendarResetTime(resetsAtMs, nowMs)} (in ${formatRelativeDuration(resetsAtMs - nowMs)})`;
224
308
  }
225
309
  function snapshotHasLimitBlock(snapshot) {
226
- return Boolean(normalizeOptionalString(snapshot.rateLimitReachedType) ?? normalizeOptionalString(snapshot.rate_limit_reached_type) ?? readWindowEntries(snapshot).some((entry) => entry.window.usedPercent !== void 0 && entry.window.usedPercent >= 100));
310
+ return Boolean(snapshot.rateLimitReachedType ?? readWindowEntries(snapshot).some((entry) => entry.window.usedPercent !== void 0 && entry.window.usedPercent >= 100));
227
311
  }
228
312
  function isCodexLimitSnapshot(snapshot) {
229
- const id = normalizeOptionalString(snapshot.limitId) ?? normalizeOptionalString(snapshot.limit_id);
230
- return !id || id === CODEX_LIMIT_ID;
231
- }
232
- function selectCodexProviderUsageSnapshot(value) {
233
- const snapshots = collectCodexRateLimitSnapshots(value);
234
- return snapshots.find(isCodexLimitSnapshot) ?? snapshots[0];
313
+ return !snapshot.limitId || snapshot.limitId === CODEX_LIMIT_ID;
235
314
  }
236
315
  function readProviderUsageWindow(entry, entries) {
237
316
  const { window } = entry;
@@ -252,16 +331,15 @@ function formatProviderUsageWindowLabel(entry, entries) {
252
331
  return entry.key === "primary" ? "Short" : "Long";
253
332
  }
254
333
  function resolveCodexProviderUsagePlan(snapshot) {
255
- const plan = normalizeOptionalString(snapshot.planType) ?? normalizeOptionalString(snapshot.plan_type);
256
- const creditSummary = formatCodexCreditSummary(isJsonObject(snapshot.credits) ? snapshot.credits : void 0);
334
+ const plan = snapshot.planType;
335
+ const creditSummary = formatCodexCreditSummary(snapshot.credits);
257
336
  if (!creditSummary) return plan;
258
337
  return plan ? `${plan} (${creditSummary})` : creditSummary;
259
338
  }
260
339
  function formatCodexCreditSummary(credits) {
261
- if (!credits) return;
262
- if ((asBoolean(credits.hasCredits) ?? asBoolean(credits.has_credits)) === false) return;
263
- if (asBoolean(credits.unlimited)) return "Unlimited credits";
264
- const balance = typeof credits.balance === "string" ? parseStrictFiniteNumber(credits.balance) : asFiniteNumber(credits.balance);
340
+ if (!credits || credits.hasCredits === false) return;
341
+ if (credits.unlimited) return "Unlimited credits";
342
+ const balance = credits.balance;
265
343
  if (balance === void 0 || balance <= 0) return;
266
344
  const roundedBalance = Math.round(balance);
267
345
  return roundedBalance > 0 ? `${roundedBalance} credits` : void 0;
@@ -285,7 +363,7 @@ function selectBlockingWindowEntry(entries, nowMs) {
285
363
  }
286
364
  function readWindowEntries(snapshot) {
287
365
  return LIMIT_WINDOW_KEYS.flatMap((key) => {
288
- const window = readRateLimitWindow(snapshot, key);
366
+ const window = snapshot[key];
289
367
  return window ? [{
290
368
  key,
291
369
  window
@@ -319,61 +397,6 @@ function hasWeeklySecondaryResetCadence(entry, entries) {
319
397
  const primaryResetMs = entries.find((candidate) => candidate.key === "primary")?.window.resetsAtMs;
320
398
  return typeof primaryResetMs === "number" && primaryResetMs > 0 && entry.window.resetsAtMs > 0 && entry.window.resetsAtMs - primaryResetMs >= WEEKLY_RESET_GAP_MS;
321
399
  }
322
- function formatCalendarResetTime(resetsAtMs, nowMs) {
323
- const resetDate = new Date(resetsAtMs);
324
- const resetParts = new Intl.DateTimeFormat("en-US", {
325
- month: "short",
326
- day: "numeric",
327
- ...resetDate.getFullYear() === new Date(nowMs).getFullYear() ? {} : { year: "numeric" },
328
- hour: "numeric",
329
- minute: "2-digit",
330
- timeZoneName: "short"
331
- }).formatToParts(resetDate);
332
- const part = (type) => resetParts.find((entry) => entry.type === type)?.value;
333
- const dateParts = [
334
- part("month"),
335
- part("day"),
336
- part("year")
337
- ].filter(Boolean);
338
- return [
339
- dateParts.length > 1 ? `${dateParts[0]} ${dateParts.slice(1).join(", ")}` : dateParts[0],
340
- "at",
341
- [
342
- [part("hour"), part("minute")].filter(Boolean).join(":"),
343
- part("dayPeriod"),
344
- part("timeZoneName")
345
- ].filter(Boolean).join(" ")
346
- ].filter(Boolean).join(" ");
347
- }
348
- function formatRelativeDuration(durationMs) {
349
- const safeMs = Math.max(1e3, durationMs);
350
- if (safeMs < ONE_MINUTE_MS) return `${Math.ceil(safeMs / 1e3)} seconds`;
351
- if (safeMs < ONE_HOUR_MS) {
352
- const minutes = Math.ceil(safeMs / ONE_MINUTE_MS);
353
- return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
354
- }
355
- if (safeMs < ONE_DAY_MS) {
356
- const hours = Math.ceil(safeMs / ONE_HOUR_MS);
357
- return `${hours} ${hours === 1 ? "hour" : "hours"}`;
358
- }
359
- const days = Math.ceil(safeMs / ONE_DAY_MS);
360
- return `${days} ${days === 1 ? "day" : "days"}`;
361
- }
362
- function formatResetDuration(resetsAtMs, nowMs) {
363
- const durationMs = Math.round(Math.max(ONE_SECOND_MS, resetsAtMs - nowMs) / ONE_SECOND_MS) * ONE_SECOND_MS;
364
- const days = Math.floor(durationMs / ONE_DAY_MS);
365
- const hours = Math.floor(durationMs % ONE_DAY_MS / ONE_HOUR_MS);
366
- const minutes = Math.floor(durationMs % ONE_HOUR_MS / ONE_MINUTE_MS);
367
- const seconds = Math.floor(durationMs % ONE_MINUTE_MS / ONE_SECOND_MS);
368
- if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
369
- if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
370
- if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
371
- return `${seconds}s`;
372
- }
373
- function formatWindowSignature(value) {
374
- if (!isJsonObject(value)) return "";
375
- return `${asFiniteNumber(value.usedPercent) ?? asFiniteNumber(value.used_percent) ?? ""}:${asFiniteNumber(value.resetsAt) ?? asFiniteNumber(value.resets_at) ?? ""}`;
376
- }
377
400
  function extractCodexRetryHint(message) {
378
401
  if (!message) return;
379
402
  const tryAgainAt = /\btry again\s+(at\s+[^.!?\n]+)(?:[.!?]|$)/iu.exec(message);