@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.
- package/assets/activity.svg +30 -0
- package/assets/icon.png +0 -0
- package/dist/.setup/account-usage-runtime-Eem_WGqY.mjs +75 -0
- package/dist/{apply-CvT8tW5i.js → .setup/apply-DDOi8uNx.mjs} +24 -30
- package/dist/.setup/async-questions-DdhOCwGi.mjs +27 -0
- package/dist/{attempt-client-cleanup-DKHzbwt5.js → .setup/attempt-client-cleanup-CEv1cKwF.mjs} +79 -49
- package/dist/.setup/attempt-terminal-gZrxQ35N.mjs +5 -0
- package/dist/.setup/attempt-workspace-context-DUVEn_CS.mjs +331 -0
- package/dist/{auth-binding-CWlKVccJ.js → .setup/auth-binding-DhLWa_7K.mjs} +1 -1
- package/dist/{auth-cache-key-7orQae_j.js → .setup/auth-cache-key-B2mpaI9D.mjs} +9 -81
- package/dist/{auth-profile-CwzO4_RG.js → .setup/auth-profile-WqZtZfXN.mjs} +1 -1
- package/dist/.setup/auth-profile-health-oHizqgye.mjs +53 -0
- package/dist/.setup/auth-profile-recovery-BREezV0L.mjs +14 -0
- package/dist/.setup/binding-connection-ThoatDcb.mjs +50 -0
- package/dist/.setup/binding-connection.runtime-yoHrABt-.mjs +48 -0
- package/dist/{bounded-turn-C_Hneydj.js → .setup/bounded-turn-B2HScEgT.mjs} +143 -32
- package/dist/{build-state-BCmg9-5p.js → .setup/build-state-C7EnDVgr.mjs} +1 -1
- package/dist/{capabilities-8vx68WLH.js → .setup/capabilities-CDXOOFdZ.mjs} +1 -1
- package/dist/{client-B57KWPQx.js → .setup/client-Cs08OXVQ.mjs} +705 -512
- package/dist/.setup/client-catalog-response-BMgt_4q4.mjs +285 -0
- package/dist/{command-dispatch-BpduNGVf.js → .setup/command-dispatch-B4JuOsOk.mjs} +8 -4
- package/dist/{command-formatters-lxlkNZgU.js → .setup/command-formatters-Bmqvr9tO.mjs} +8 -4
- package/dist/{command-handlers-tiL9iWj7.js → .setup/command-handlers-DW7Lx9oH.mjs} +137 -186
- package/dist/{command-rpc-CR5EOEZX.js → .setup/command-rpc-DixIj3HW.mjs} +47 -23
- package/dist/{compact-B_H3AOd7.js → .setup/compact-Du5UvtxA.mjs} +29 -51
- package/dist/{config-oIORaQ5T.js → .setup/config-BoTP_mrL.mjs} +5 -4
- package/dist/{config-options-CXMq1T-C.js → .setup/config-options-BvaRs51b.mjs} +50 -446
- package/dist/{protocol-5bh1G-H7.js → .setup/config-parsing-CcB9iPoq.mjs} +9 -51
- package/dist/{config-runtime-D0zGNIHu.js → .setup/config-runtime-C8Rw1m1m.mjs} +2 -2
- package/dist/.setup/config-security-BEReZ6go.mjs +380 -0
- package/dist/{conversation-binding-DsB4-O7U.js → .setup/conversation-binding-DP-gMrBP.mjs} +29 -38
- package/dist/{conversation-binding-data-BiodpIzq.js → .setup/conversation-binding-data-CrcyZk6O.mjs} +1 -1
- package/dist/{conversation-control-CtUu1b3m.js → .setup/conversation-control-uK9Jomlc.mjs} +32 -67
- package/dist/.setup/cyber-failover-DYwbmf1o.mjs +123 -0
- package/dist/.setup/cyber-failover-notice-e9G3J9-e.mjs +17 -0
- package/dist/{dynamic-tools-CmN5RAip.js → .setup/dynamic-tools-BDXl-8Cw.mjs} +936 -620
- package/dist/{effective-mcp-catalog-Bd73bqA8.js → .setup/effective-mcp-catalog-CvnvnGIe.mjs} +3 -3
- package/dist/{ephemeral-turn-D78_VmzO.js → .setup/ephemeral-turn-poidXgek.mjs} +9 -7
- package/dist/{event-projector-assistant-message-BmBM4qBV.js → .setup/event-projector-assistant-message-BsruvNrH.mjs} +8 -7
- package/dist/{event-projector-items-5VhsECCO.js → .setup/event-projector-items-C6L_5ZiT.mjs} +42 -8
- package/dist/{attempt-notifications-C9XufG6l.js → .setup/event-projector-usage-CnBS8Wa3.mjs} +102 -9
- package/dist/{harness-C7rRa7bm.js → .setup/harness-a_MsE4-_.mjs} +81 -21
- package/dist/{inference-context-Dr_mzvk4.js → .setup/inference-context-D96l9-ew.mjs} +1 -1
- package/dist/.setup/inference-proxy-b-aJMSxm.mjs +601 -0
- package/dist/{isolated-completion-DWVb5U2J.js → .setup/isolated-completion-BIZFhTNG.mjs} +18 -11
- package/dist/.setup/launch-args-DbFCehO7.mjs +196 -0
- package/dist/{managed-binary-D22rNEcR.js → .setup/managed-binary-BnshlFag.mjs} +14 -3
- package/dist/{managed-thread-store-D1t5jh3G.js → .setup/managed-thread-store-BMqgThVL.mjs} +48 -14
- package/dist/{model-catalog-Dq2n8f_p.js → .setup/model-catalog-CfXJf9Ay.mjs} +36 -12
- package/dist/{models-PCKOim-h.js → .setup/models-KnqBv0hE.mjs} +3 -3
- package/dist/.setup/native-auth-rWbGbMn3.mjs +57 -0
- package/dist/{native-subagent-history-DjtPloaU.js → .setup/native-subagent-history-0nYYgo0k.mjs} +43 -35
- package/dist/.setup/native-subagent-monitor-D3filLY2.mjs +4859 -0
- package/dist/{native-subagent-task-ids-BzTdT0Uw.js → .setup/native-subagent-task-ids-wb8CKZGN.mjs} +98 -44
- package/dist/{node-cli-sessions-DKaOmVUh.js → .setup/node-cli-sessions-r3avj02l.mjs} +86 -68
- package/dist/.setup/node-exec-server.runtime-BDwWXVpK.mjs +247 -0
- package/dist/{plan-7uG-Fp36.js → .setup/plan-CN3VwfNU.mjs} +133 -119
- package/dist/{plugin-activation-BYQculil.js → .setup/plugin-activation-DT3MNtqj.mjs} +7 -16
- package/dist/{plugin-app-cache-key-d0eKxy7b.js → .setup/plugin-app-cache-key-B2CsSdnV.mjs} +32 -44
- package/dist/{plugin-approval-roundtrip-Bz3FT8W5.js → .setup/plugin-approval-roundtrip-C0MZzCuP.mjs} +1 -1
- package/dist/{plugin-inventory-oL4Na1ZG.js → .setup/plugin-inventory-BoRei8Z4.mjs} +21 -11
- package/dist/{plugin-marketplace-discovery-P-Vndfqq.js → .setup/plugin-marketplace-discovery-CDOi8IFs.mjs} +4 -1
- package/dist/{plugin-metadata-cache-ClPJpRio.js → .setup/plugin-metadata-cache-Ctmo83k_.mjs} +226 -141
- package/dist/.setup/protocol-CANUwXJ3.mjs +17 -0
- package/dist/{provider-MQ8PVkuE.js → .setup/provider-BOsxQHOM.mjs} +9 -5
- package/dist/{provider-capabilities-BvCUL69A.js → .setup/provider-capabilities-BfE88FZ5.mjs} +31 -3
- package/dist/{rate-limits-CBMmWBr9.js → .setup/rate-limits-CMU3DUD0.mjs} +174 -151
- package/dist/{request-B7hEkBGq.js → .setup/request-D2L0zMrq.mjs} +130 -42
- package/dist/{run-attempt-CvsAAGa0.js → .setup/run-attempt-BHu11r7h.mjs} +1628 -966
- package/dist/.setup/run-attempt-lifecycle-B7JbY4-F.mjs +71 -0
- package/dist/{runtime-artifact-BJ0rXBZ_.js → .setup/runtime-artifact-CLZ8udh-.mjs} +29 -45
- package/dist/{sandbox-guard-BxgyoM8M.js → .setup/sandbox-guard-C2sRMOEY.mjs} +3 -3
- package/dist/{session-binding-CI8UuilT.js → .setup/session-binding-Cm0apEbd.mjs} +36 -83
- package/dist/{session-binding-record-Bcvslnhf.js → .setup/session-binding-record-BGoz8wOK.mjs} +202 -5
- package/dist/.setup/session-binding-sidecars-BlB5EvIS.mjs +546 -0
- package/dist/.setup/session-catalog-Pno1xGcl.mjs +2764 -0
- package/dist/{session-catalog-adoption-tcZi1IfX.js → .setup/session-catalog-adoption-BI6UTDZT.mjs} +58 -26
- package/dist/{session-catalog-archive-CwKg8Xz2.js → .setup/session-catalog-archive-lHBqzhaO.mjs} +6 -6
- package/dist/.setup/session-catalog-events-Bj6j94E_.mjs +347 -0
- package/dist/.setup/session-catalog-index-B9_VQIUz.mjs +1170 -0
- package/dist/.setup/session-catalog-index-order-hcdz07yN.mjs +48 -0
- package/dist/.setup/session-catalog-index-state-Dgh8ORJm.mjs +183 -0
- package/dist/{thread-history-page-D0JIp48V.js → .setup/session-catalog-native-projection-DowriLid.mjs} +223 -95
- package/dist/{session-catalog-node-adoption-B1tgDW-m.js → .setup/session-catalog-node-adoption-ITddWx_X.mjs} +25 -13
- package/dist/{session-catalog-node-continue-Ci-DXyJP.js → .setup/session-catalog-node-continue-6g6qi4A9.mjs} +24 -15
- package/dist/.setup/session-catalog-projection-zK_PUsiE.mjs +422 -0
- package/dist/{session-cli-DNN_jhGc.js → .setup/session-cli-irrD-uo7.mjs} +3 -3
- package/dist/{session-history-Cz-tOOYj.js → .setup/session-history-BrqhSCf9.mjs} +19 -19
- package/dist/{session-history-import-DwyijFI6.js → .setup/session-history-import-D-3wAgOP.mjs} +4 -4
- package/dist/{session-history-read-cxPUxyVV.js → .setup/session-history-read-B3ZfSUbF.mjs} +2 -2
- package/dist/{session-history.worker-BeK1YOyT.js → .setup/session-history.worker-CaCh0xF_.mjs} +10 -24
- package/dist/{session-initialization-Ct0_Hzmg.js → .setup/session-initialization-rcgbZiid.mjs} +2 -2
- package/dist/{session-permission-policy-BBmXA8Eg.js → .setup/session-permission-policy-BI1QK_98.mjs} +2 -2
- package/dist/{session-retirement-B2YfbAAT.js → .setup/session-retirement-BH-S6qkJ.mjs} +26 -7
- package/dist/{settled-turn-context-Du6gdUDd.js → .setup/settled-turn-context-fmwouT5I.mjs} +2 -2
- package/dist/{settled-turn-finalizer-Dn91stmc.js → .setup/settled-turn-finalizer-DiAi6Kx6.mjs} +12 -12
- package/dist/{shared-client-CscigXXL.js → .setup/shared-client-DA4VR4Eb.mjs} +332 -221
- package/dist/{side-question-BGMJQf4U.js → .setup/side-question-BJ1wSDcB.mjs} +183 -230
- package/dist/{source-CElXU0Oy.js → .setup/source-CUzJq5WN.mjs} +11 -10
- package/dist/.setup/spawn-error-CL3n6MAa.mjs +57 -0
- package/dist/{thread-archive-guard-DfpBr-Sz.js → .setup/thread-archive-guard-C9frICzX.mjs} +10 -3
- package/dist/.setup/thread-history-page-DoVpnHim.mjs +83 -0
- package/dist/{thread-lifecycle-DXl1-I6b.js → .setup/thread-lifecycle-B3mfz1TG.mjs} +347 -2318
- package/dist/{thread-ownership-BsDRTsyp.js → .setup/thread-ownership-DcTtAcXj.mjs} +6 -4
- package/dist/{thread-policy-BuEQfE0g.js → .setup/thread-policy-DGuQsuFC.mjs} +19 -3
- package/dist/.setup/thread-requests-BLvGkP2R.mjs +2205 -0
- package/dist/{thread-resume-DsDOZpFg.js → .setup/thread-resume-DIBXwOpE.mjs} +8 -5
- package/dist/.setup/timeout-C910MdAB.mjs +80 -0
- package/dist/{transcript-history-projection-v17wXgdK.js → .setup/transcript-history-projection-ZZpkFkJ_.mjs} +96 -61
- package/dist/{transcript-mirror-C5CKgNy6.js → .setup/transcript-mirror-BtHDBp1G.mjs} +224 -195
- package/dist/{transcript-mirror-attestation-DibE0krH.js → .setup/transcript-mirror-attestation-CzM-94NC.mjs} +4 -1
- package/dist/{transport-stdio-BGjHoYLr.js → .setup/transport-stdio-h2VHOskC.mjs} +274 -89
- package/dist/{upstream-session-fork-BZerMte_.js → .setup/upstream-session-fork-BUr4OUbC.mjs} +51 -42
- package/dist/{usage-tCk-mg_x.js → .setup/usage-BML0r-ua.mjs} +5 -4
- package/dist/.setup/usage-limit-error-DUMbefVY.mjs +166 -0
- package/dist/{web-search-provider.runtime-B7bGbGWq.js → .setup/web-search-provider.runtime-CDKlN-Rv.mjs} +4 -3
- package/dist/api.js +23 -12
- package/dist/catalog-page-worker-entrypoint.js +12 -0
- package/dist/catalog-page.worker.js +9 -0
- package/dist/cli-metadata.js +1 -1
- package/dist/doctor-contract-api.js +8 -528
- package/dist/harness.js +1 -1
- package/dist/index.js +306 -215
- package/dist/media-understanding-provider.js +3 -3
- package/dist/migration-provider-api.js +1 -1
- package/dist/native-tool-policy.js +12 -0
- package/dist/provider-discovery.js +18 -0
- package/dist/session-history-worker-runtime.js +36 -35
- package/dist/session-history.worker.js +2 -2
- package/dist/web-search-contract-api.js +1 -1
- package/openclaw.plugin.json +354 -69
- package/package.json +14 -8
- package/dist/attempt-context-D44yeDII.js +0 -636
- package/dist/binding-connection-D3udKbIE.js +0 -60
- package/dist/event-projector-usage-BfXmpOec.js +0 -102
- package/dist/inference-proxy-HGXKUIre.js +0 -311
- package/dist/launch-args-RXQwn8zV.js +0 -90
- package/dist/native-subagent-history-owner-Pcp7JuVF.js +0 -34
- package/dist/native-subagent-monitor-fQ7eIXEf.js +0 -2978
- package/dist/node-exec-server.runtime-Cl2vapEi.js +0 -226
- package/dist/rpc-error-ttN_QjEm.js +0 -30
- package/dist/session-catalog-ct-AH01I.js +0 -1477
- package/dist/session-catalog-home-id-CwhLdC03.js +0 -18
- package/dist/session-catalog-provenance-D-dIfRAH.js +0 -96
- package/dist/timeout-BVw8aaM1.js +0 -33
- package/dist/version-omm6IPI3.js +0 -12
- /package/dist/{auth-profile-selection-DFu9e167.js → .setup/auth-profile-selection-DFu9e167.mjs} +0 -0
- /package/dist/{config-utils-DujwEnhg.js → .setup/config-utils-DujwEnhg.mjs} +0 -0
- /package/dist/{context-compaction-activity-CMla5FPJ.js → .setup/context-compaction-activity-CMla5FPJ.mjs} +0 -0
- /package/dist/{elicitation-input-DPoK8F7w.js → .setup/elicitation-input-DPoK8F7w.mjs} +0 -0
- /package/dist/{history-rejection-B4nOXNXF.js → .setup/history-rejection-B4nOXNXF.mjs} +0 -0
- /package/dist/{image-payload-sanitizer-tupAr-2o.js → .setup/image-payload-sanitizer-tupAr-2o.mjs} +0 -0
- /package/dist/{incognito-session-uhrBF6wJ.js → .setup/incognito-session-uhrBF6wJ.mjs} +0 -0
- /package/dist/{local-runtime-attribution-B_2mnqhb.js → .setup/local-runtime-attribution-B_2mnqhb.mjs} +0 -0
- /package/dist/{native-execution-policy-umd0tgh4.js → .setup/native-execution-policy-umd0tgh4.mjs} +0 -0
- /package/dist/{rolldown-runtime-8H4AJuhK.js → .setup/rolldown-runtime-8H4AJuhK.mjs} +0 -0
- /package/dist/{session-binding-meta-B7aEMU7g.js → .setup/session-binding-meta-B7aEMU7g.mjs} +0 -0
- /package/dist/{session-upstream-marker-D15C9NHp.js → .setup/session-upstream-marker-D15C9NHp.mjs} +0 -0
- /package/dist/{upstream-prompt-provenance-LphB8slG.js → .setup/upstream-prompt-provenance-LphB8slG.mjs} +0 -0
- /package/dist/{web-search-provider.shared-BrZmlqyR.js → .setup/web-search-provider.shared-BrZmlqyR.mjs} +0 -0
|
@@ -1,32 +1,122 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
|
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 (
|
|
111
|
+
if (params.codexErrorInfo !== "usageLimitExceeded") return;
|
|
26
112
|
const nowMs = params.nowMs ?? Date.now();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
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
|
-
|
|
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
|
|
78
|
-
if (
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
232
|
+
return result;
|
|
118
233
|
}
|
|
119
234
|
function selectNextRateLimitReset(value, nowMs) {
|
|
120
|
-
const futureWindows = collectCodexRateLimitSnapshots(value).flatMap((snapshot) => LIMIT_WINDOW_KEYS.flatMap((key) =>
|
|
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
|
|
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 =
|
|
251
|
+
const window = snapshot[key];
|
|
134
252
|
return window ? [formatRateLimitWindow(key, window, nowMs)] : [];
|
|
135
253
|
});
|
|
136
|
-
const reachedType =
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
256
|
-
const creditSummary = formatCodexCreditSummary(
|
|
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 (
|
|
263
|
-
|
|
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 =
|
|
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);
|