@openclaw/codex 2026.6.5 → 2026.6.6-beta.1
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/dist/{client-kMCtlApt.js → client-DovmVtbr.js} +3 -10
- package/dist/{client-factory-Bt49r45B.js → client-factory-XajDuvQ1.js} +1 -1
- package/dist/{command-handlers-DMn2M7W7.js → command-handlers-DIUFKqaF.js} +11 -9
- package/dist/{compact-CwnPeYnM.js → compact-BWiNkDSs.js} +96 -9
- package/dist/{computer-use-ClweWaMz.js → computer-use-D3pi5csE.js} +3 -2
- package/dist/{config-BT6SLiE6.js → config-DJXpBkGf.js} +214 -13
- package/dist/{conversation-binding-CzpvaBs1.js → conversation-binding-CMwrZrt7.js} +190 -65
- package/dist/harness.js +12 -5
- package/dist/index.js +11 -10
- package/dist/media-understanding-provider.js +6 -6
- package/dist/{models-DXorTaja.js → models-CQGfP7nG.js} +2 -2
- package/dist/{native-hook-relay-DHwz_ICg.js → native-hook-relay-CmUUO3Eu.js} +160 -4
- package/dist/notification-correlation-D-MYfJwV.js +382 -0
- package/dist/{request-D64BfplD.js → plugin-app-cache-key-NpzxEcSR.js} +4 -36
- package/dist/protocol-oeJQu4rs.js +9 -0
- package/dist/{protocol-validators-CIpP8IJ2.js → protocol-validators-B48C6S9O.js} +2296 -2273
- package/dist/provider-Cs6kWhDQ.js +584 -0
- package/dist/provider.js +1 -164
- package/dist/request-Dq0LUOqZ.js +36 -0
- package/dist/{run-attempt-BWdNnok4.js → run-attempt-DG9p9ReO.js} +374 -65
- package/dist/{session-binding-BgTv_YGm.js → session-binding-ElbcSq2o.js} +107 -44
- package/dist/{shared-client-xytpSKD0.js → shared-client-CflavQ_i.js} +3 -3
- package/dist/{side-question-BnvBQPqW.js → side-question-Ctu2VXAG.js} +82 -26
- package/dist/{thread-lifecycle-BJsazZ8j.js → thread-lifecycle-Cq1-IfZB.js} +115 -34
- package/npm-shrinkwrap.json +30 -30
- package/package.json +5 -5
- package/dist/notification-correlation-o8quHmTK.js +0 -656
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { CODEX_APP_SERVER_AUTH_MARKER, CODEX_BASE_URL, CODEX_PROVIDER_ID, FALLBACK_CODEX_MODELS, buildCodexModelDefinition, buildCodexProviderConfig } from "./provider-catalog.js";
|
|
2
|
+
import { d as resolveCodexAppServerRuntimeOptions, u as readCodexPluginConfig } from "./config-DJXpBkGf.js";
|
|
3
|
+
import { t as isJsonObject } from "./protocol-oeJQu4rs.js";
|
|
4
|
+
import { resolveCodexSystemPromptContribution } from "./prompt-overlay.js";
|
|
5
|
+
import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
6
|
+
import { MAX_DATE_TIMESTAMP_MS, resolveExpiresAtMsFromEpochSeconds } from "openclaw/plugin-sdk/number-runtime";
|
|
7
|
+
import { asFiniteNumber, parseStrictFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
8
|
+
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared";
|
|
9
|
+
import { createSubsystemLogger } from "openclaw/plugin-sdk/core";
|
|
10
|
+
import { PROVIDER_LABELS, clampPercent } from "openclaw/plugin-sdk/provider-usage";
|
|
11
|
+
//#region extensions/codex/src/app-server/rate-limits.ts
|
|
12
|
+
/**
|
|
13
|
+
* Parses Codex account rate-limit payloads into user-facing usage summaries,
|
|
14
|
+
* reset hints, and enriched usage-limit error messages.
|
|
15
|
+
*/
|
|
16
|
+
const CODEX_LIMIT_ID = "codex";
|
|
17
|
+
const LIMIT_WINDOW_KEYS = ["primary", "secondary"];
|
|
18
|
+
const ONE_SECOND_MS = 1e3;
|
|
19
|
+
const ONE_MINUTE_MS = 6e4;
|
|
20
|
+
const ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
|
|
21
|
+
const ONE_DAY_MS = 24 * ONE_HOUR_MS;
|
|
22
|
+
const DAY_WINDOW_MINUTES = 1440;
|
|
23
|
+
const WEEKLY_WINDOW_MINUTES = 7 * DAY_WINDOW_MINUTES;
|
|
24
|
+
const WEEKLY_RESET_GAP_MS = 3 * ONE_DAY_MS;
|
|
25
|
+
/** Enriches Codex usage-limit failures with reset timing and recovery guidance. */
|
|
26
|
+
function formatCodexUsageLimitErrorMessage(params) {
|
|
27
|
+
const message = normalizeText(params.message);
|
|
28
|
+
if (!isCodexUsageLimitError(params.codexErrorInfo, message)) return;
|
|
29
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
30
|
+
const usageSummary = summarizeCodexAccountUsage(params.rateLimits, nowMs);
|
|
31
|
+
const nextReset = selectBlockingRateLimitReset(params.rateLimits, nowMs) ?? (usageSummary?.blocked ? void 0 : selectNextRateLimitReset(params.rateLimits, nowMs));
|
|
32
|
+
const parts = ["You've reached your Codex subscription usage limit."];
|
|
33
|
+
let recoveryAction = "Wait until Codex becomes available";
|
|
34
|
+
if (nextReset) {
|
|
35
|
+
parts.push(`Next reset ${formatResetTime(nextReset.resetsAtMs, nowMs)}.`);
|
|
36
|
+
recoveryAction = "Wait until the reset time";
|
|
37
|
+
} else {
|
|
38
|
+
const codexRetryHint = extractCodexRetryHint(message);
|
|
39
|
+
if (codexRetryHint) {
|
|
40
|
+
parts.push(`Codex says to try again ${codexRetryHint}.`);
|
|
41
|
+
recoveryAction = "Wait until the retry time";
|
|
42
|
+
} else {
|
|
43
|
+
if (usageSummary?.blockingPeriod && usageSummary.blockingReason) parts.push(`Your ${usageSummary.blockingReason}.`);
|
|
44
|
+
parts.push("OpenClaw could not determine a reset time from Codex.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
parts.push(`${recoveryAction}, use another Codex account if available, or switch to another configured model/provider.`);
|
|
48
|
+
return parts.join(" ");
|
|
49
|
+
}
|
|
50
|
+
/** Detects usage-limit messages that need a fresh rate-limit query before display. */
|
|
51
|
+
function shouldRefreshCodexRateLimitsForUsageLimitMessage(message) {
|
|
52
|
+
const text = normalizeText(message);
|
|
53
|
+
return Boolean(text?.includes("You've reached your Codex subscription usage limit.") && !text.includes("Next reset "));
|
|
54
|
+
}
|
|
55
|
+
/** Formats compact summaries for raw Codex rate-limit snapshot payloads. */
|
|
56
|
+
function summarizeCodexRateLimits(value, nowMs = Date.now()) {
|
|
57
|
+
const snapshots = collectCodexRateLimitSnapshots(value).filter(snapshotHasDisplayableData);
|
|
58
|
+
if (snapshots.length === 0) return;
|
|
59
|
+
const summaries = snapshots.slice(0, 4).map((snapshot) => summarizeRateLimitSnapshot(snapshot, nowMs)).filter((summary) => summary !== void 0);
|
|
60
|
+
return summaries.length > 0 ? summaries.join("; ") : void 0;
|
|
61
|
+
}
|
|
62
|
+
/** Returns true when a value contains any recognizable Codex rate-limit snapshots. */
|
|
63
|
+
function hasCodexRateLimitSnapshots(value) {
|
|
64
|
+
return collectCodexRateLimitSnapshots(value).length > 0;
|
|
65
|
+
}
|
|
66
|
+
/** Builds short account availability lines suitable for status surfaces. */
|
|
67
|
+
function summarizeCodexAccountRateLimits(value, nowMs = Date.now()) {
|
|
68
|
+
const summary = summarizeCodexAccountUsage(value, nowMs);
|
|
69
|
+
if (!summary) return;
|
|
70
|
+
if (!summary.blocked) return ["Codex is available."];
|
|
71
|
+
return [summary.blockedUntilText ? `Codex is paused until ${summary.blockedUntilText}.` : "Codex is paused by a usage limit.", summary.blockingReason ? `Your ${summary.blockingReason}.` : "Your Codex usage limit is reached."];
|
|
72
|
+
}
|
|
73
|
+
/** Returns the reset timestamp for the currently blocking Codex usage limit. */
|
|
74
|
+
function resolveCodexUsageLimitResetAtMs(value, nowMs = Date.now()) {
|
|
75
|
+
return selectBlockingRateLimitReset(value, nowMs)?.resetsAtMs;
|
|
76
|
+
}
|
|
77
|
+
/** Summarizes account availability, blocking reason, and reset time from rate-limit data. */
|
|
78
|
+
function summarizeCodexAccountUsage(value, nowMs = Date.now()) {
|
|
79
|
+
const snapshots = collectCodexRateLimitSnapshots(value).filter(snapshotHasDisplayableData);
|
|
80
|
+
if (snapshots.length === 0) return;
|
|
81
|
+
const usageSnapshot = snapshots.find(isCodexLimitSnapshot) ?? snapshots[0];
|
|
82
|
+
const blockedSnapshots = snapshots.filter(snapshotHasLimitBlock);
|
|
83
|
+
const blockingSnapshot = blockedSnapshots.find(isCodexLimitSnapshot) ?? blockedSnapshots[0] ?? void 0;
|
|
84
|
+
const blockingEntries = blockingSnapshot ? readWindowEntries(blockingSnapshot) : [];
|
|
85
|
+
const blockingWindowEntry = selectBlockingWindowEntry(blockingEntries, nowMs);
|
|
86
|
+
const blockingWindow = blockingWindowEntry?.window;
|
|
87
|
+
const blockingReset = blockingWindow && blockingWindow.resetsAtMs > nowMs ? blockingWindow : void 0;
|
|
88
|
+
const blockingPeriod = formatBlockingLimitPeriod(blockingWindowEntry, blockingEntries);
|
|
89
|
+
const blockedUntilText = blockingReset ? formatAccountResetTime(blockingReset.resetsAtMs, nowMs) : void 0;
|
|
90
|
+
const blockedResetRelative = blockingReset ? `in ${formatRelativeDuration(blockingReset.resetsAtMs - nowMs)}` : void 0;
|
|
91
|
+
const blockingReason = blockingPeriod ? `${blockingPeriod} Codex usage limit is reached` : blockingSnapshot ? "Codex usage limit is reached" : void 0;
|
|
92
|
+
return {
|
|
93
|
+
usageLine: formatUsageLine(usageSnapshot),
|
|
94
|
+
blocked: Boolean(blockingSnapshot),
|
|
95
|
+
...blockingReset ? { blockedUntilMs: blockingReset.resetsAtMs } : {},
|
|
96
|
+
...blockedUntilText ? { blockedUntilText } : {},
|
|
97
|
+
...blockedResetRelative ? { blockedResetRelative } : {},
|
|
98
|
+
...blockingPeriod ? { blockingPeriod } : {},
|
|
99
|
+
...blockingReason ? { blockingReason } : {}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Converts Codex app-server rate-limit payloads into OpenAI/Codex usage windows. */
|
|
103
|
+
function buildCodexAppServerUsageSnapshot(value) {
|
|
104
|
+
const snapshot = selectCodexProviderUsageSnapshot(value);
|
|
105
|
+
const entries = snapshot ? readWindowEntries(snapshot) : [];
|
|
106
|
+
const windows = entries.map((entry) => readProviderUsageWindow(entry, entries)).filter((window) => Boolean(window));
|
|
107
|
+
return {
|
|
108
|
+
provider: "openai",
|
|
109
|
+
displayName: PROVIDER_LABELS.openai,
|
|
110
|
+
windows,
|
|
111
|
+
...snapshot ? { plan: resolveCodexProviderUsagePlan(snapshot) } : {}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function isCodexUsageLimitError(codexErrorInfo, message) {
|
|
115
|
+
if (codexErrorInfo === "usageLimitExceeded") return true;
|
|
116
|
+
if (typeof codexErrorInfo === "string") {
|
|
117
|
+
if (codexErrorInfo.replace(/[_\s-]/gu, "").toLowerCase() === "usagelimitexceeded") return true;
|
|
118
|
+
}
|
|
119
|
+
return Boolean(message?.toLowerCase().includes("usage limit"));
|
|
120
|
+
}
|
|
121
|
+
function selectNextRateLimitReset(value, nowMs) {
|
|
122
|
+
const futureWindows = collectCodexRateLimitSnapshots(value).flatMap((snapshot) => LIMIT_WINDOW_KEYS.flatMap((key) => readRateLimitWindow(snapshot, key) ?? [])).filter((window) => window.resetsAtMs > nowMs);
|
|
123
|
+
if (futureWindows.length === 0) return;
|
|
124
|
+
const exhaustedWindows = futureWindows.filter((window) => window.usedPercent !== void 0 && window.usedPercent >= 100);
|
|
125
|
+
return (exhaustedWindows.length > 0 ? exhaustedWindows : futureWindows).toSorted((left, right) => left.resetsAtMs - right.resetsAtMs)[0];
|
|
126
|
+
}
|
|
127
|
+
function selectBlockingRateLimitReset(value, nowMs) {
|
|
128
|
+
const blockedSnapshots = collectCodexRateLimitSnapshots(value).filter(snapshotHasLimitBlock);
|
|
129
|
+
const blockingSnapshot = blockedSnapshots.find(isCodexLimitSnapshot) ?? blockedSnapshots[0] ?? void 0;
|
|
130
|
+
return blockingSnapshot ? selectSnapshotBlockingReset(blockingSnapshot, nowMs) : void 0;
|
|
131
|
+
}
|
|
132
|
+
function summarizeRateLimitSnapshot(snapshot, nowMs) {
|
|
133
|
+
const label = formatLimitLabel(snapshot);
|
|
134
|
+
const windows = LIMIT_WINDOW_KEYS.flatMap((key) => {
|
|
135
|
+
const window = readRateLimitWindow(snapshot, key);
|
|
136
|
+
return window ? [formatRateLimitWindow(key, window, nowMs)] : [];
|
|
137
|
+
});
|
|
138
|
+
const reachedType = readString(snapshot, "rateLimitReachedType") ?? readString(snapshot, "rate_limit_reached_type");
|
|
139
|
+
const suffix = reachedType ? ` (${formatReachedType(reachedType)})` : "";
|
|
140
|
+
if (windows.length > 0) return `${label}: ${windows.join(" · ")}${suffix}`;
|
|
141
|
+
if (reachedType) return `${label}: ${formatReachedType(reachedType)}`;
|
|
142
|
+
}
|
|
143
|
+
function collectCodexRateLimitSnapshots(value) {
|
|
144
|
+
const snapshots = [];
|
|
145
|
+
collectRateLimitSnapshots(value, snapshots, /* @__PURE__ */ new Set());
|
|
146
|
+
return snapshots;
|
|
147
|
+
}
|
|
148
|
+
function collectRateLimitSnapshots(value, snapshots, seen) {
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
for (const entry of value) collectRateLimitSnapshots(entry, snapshots, seen);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!isJsonObject(value)) return;
|
|
154
|
+
if (isRateLimitSnapshot(value)) {
|
|
155
|
+
addRateLimitSnapshot(value, snapshots, seen);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const byLimitId = value.rateLimitsByLimitId;
|
|
159
|
+
if (isJsonObject(byLimitId)) for (const key of sortedRateLimitKeys(Object.keys(byLimitId))) collectRateLimitSnapshots(byLimitId[key], snapshots, seen);
|
|
160
|
+
const snakeByLimitId = value.rate_limits_by_limit_id;
|
|
161
|
+
if (isJsonObject(snakeByLimitId)) for (const key of sortedRateLimitKeys(Object.keys(snakeByLimitId))) collectRateLimitSnapshots(snakeByLimitId[key], snapshots, seen);
|
|
162
|
+
collectRateLimitSnapshots(value.rateLimits, snapshots, seen);
|
|
163
|
+
collectRateLimitSnapshots(value.rate_limits, snapshots, seen);
|
|
164
|
+
collectRateLimitSnapshots(value.data, snapshots, seen);
|
|
165
|
+
collectRateLimitSnapshots(value.items, snapshots, seen);
|
|
166
|
+
}
|
|
167
|
+
function sortedRateLimitKeys(keys) {
|
|
168
|
+
return keys.toSorted((left, right) => {
|
|
169
|
+
if (left === CODEX_LIMIT_ID) return -1;
|
|
170
|
+
if (right === CODEX_LIMIT_ID) return 1;
|
|
171
|
+
return left.localeCompare(right);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function addRateLimitSnapshot(snapshot, snapshots, seen) {
|
|
175
|
+
const signature = [
|
|
176
|
+
readNullableString(snapshot, "limitId") ?? readNullableString(snapshot, "limit_id") ?? "",
|
|
177
|
+
readNullableString(snapshot, "limitName") ?? readNullableString(snapshot, "limit_name") ?? "",
|
|
178
|
+
formatWindowSignature(snapshot.primary),
|
|
179
|
+
formatWindowSignature(snapshot.secondary)
|
|
180
|
+
].join("|");
|
|
181
|
+
if (seen.has(signature)) return;
|
|
182
|
+
seen.add(signature);
|
|
183
|
+
snapshots.push(snapshot);
|
|
184
|
+
}
|
|
185
|
+
function isRateLimitSnapshot(value) {
|
|
186
|
+
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;
|
|
187
|
+
}
|
|
188
|
+
function readRateLimitWindow(snapshot, key) {
|
|
189
|
+
const window = snapshot[key];
|
|
190
|
+
if (!isJsonObject(window)) return;
|
|
191
|
+
return {
|
|
192
|
+
resetsAtMs: resolveExpiresAtMsFromEpochSeconds(readNumber(window, "resetsAt") ?? readNumber(window, "resets_at"), { maxMs: MAX_DATE_TIMESTAMP_MS }) ?? 0,
|
|
193
|
+
...readOptionalNumberField(window, "usedPercent", "used_percent"),
|
|
194
|
+
...readOptionalNumberField(window, "windowDurationMins", "window_duration_mins", "windowMinutes", "window_minutes")
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function snapshotHasDisplayableData(snapshot) {
|
|
198
|
+
if (readString(snapshot, "rateLimitReachedType") ?? readString(snapshot, "rate_limit_reached_type")) return true;
|
|
199
|
+
return readWindowEntries(snapshot).some((entry) => entry.window.usedPercent !== void 0 || entry.window.resetsAtMs > 0);
|
|
200
|
+
}
|
|
201
|
+
function readOptionalNumberField(record, ...keys) {
|
|
202
|
+
const value = keys.map((key) => readNumber(record, key)).find((entry) => entry !== void 0);
|
|
203
|
+
if (value === void 0) return {};
|
|
204
|
+
return keys.some((key) => key.toLowerCase().includes("window")) ? { windowDurationMins: value } : { usedPercent: value };
|
|
205
|
+
}
|
|
206
|
+
function formatRateLimitWindow(key, window, nowMs) {
|
|
207
|
+
return `${key} ${formatRateLimitWindowDetails(window, nowMs)}`;
|
|
208
|
+
}
|
|
209
|
+
function formatRateLimitWindowDetails(window, nowMs) {
|
|
210
|
+
return `${window.usedPercent === void 0 ? "usage unknown" : `${Math.max(0, 100 - Math.round(window.usedPercent))}% left`}${window.resetsAtMs > nowMs ? ` ⏱${formatResetDuration(window.resetsAtMs, nowMs)}` : ""}`;
|
|
211
|
+
}
|
|
212
|
+
function formatLimitLabel(snapshot) {
|
|
213
|
+
const label = readNullableString(snapshot, "limitName") ?? readNullableString(snapshot, "limit_name") ?? readNullableString(snapshot, "limitId") ?? readNullableString(snapshot, "limit_id");
|
|
214
|
+
if (!label || label === CODEX_LIMIT_ID) return "Codex";
|
|
215
|
+
return label.replace(/[_-]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
216
|
+
}
|
|
217
|
+
function formatReachedType(value) {
|
|
218
|
+
return value.replace(/[_-]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
219
|
+
}
|
|
220
|
+
function formatResetTime(resetsAtMs, nowMs) {
|
|
221
|
+
return `in ${formatRelativeDuration(resetsAtMs - nowMs)}, ${formatCalendarResetTime(resetsAtMs, nowMs)}`;
|
|
222
|
+
}
|
|
223
|
+
function formatAccountResetTime(resetsAtMs, nowMs) {
|
|
224
|
+
return `${formatCalendarResetTime(resetsAtMs, nowMs)} (in ${formatRelativeDuration(resetsAtMs - nowMs)})`;
|
|
225
|
+
}
|
|
226
|
+
function snapshotHasLimitBlock(snapshot) {
|
|
227
|
+
return Boolean(readString(snapshot, "rateLimitReachedType") ?? readString(snapshot, "rate_limit_reached_type") ?? readWindowEntries(snapshot).some((entry) => entry.window.usedPercent !== void 0 && entry.window.usedPercent >= 100));
|
|
228
|
+
}
|
|
229
|
+
function isCodexLimitSnapshot(snapshot) {
|
|
230
|
+
const id = readNullableString(snapshot, "limitId") ?? readNullableString(snapshot, "limit_id");
|
|
231
|
+
return !id || id === CODEX_LIMIT_ID;
|
|
232
|
+
}
|
|
233
|
+
function selectCodexProviderUsageSnapshot(value) {
|
|
234
|
+
const snapshots = collectCodexRateLimitSnapshots(value);
|
|
235
|
+
return snapshots.find(isCodexLimitSnapshot) ?? snapshots[0];
|
|
236
|
+
}
|
|
237
|
+
function readProviderUsageWindow(entry, entries) {
|
|
238
|
+
const { window } = entry;
|
|
239
|
+
if (window.usedPercent === void 0 && window.resetsAtMs <= 0) return;
|
|
240
|
+
return {
|
|
241
|
+
label: formatProviderUsageWindowLabel(entry, entries),
|
|
242
|
+
usedPercent: clampPercent(window.usedPercent ?? 0),
|
|
243
|
+
resetAt: window.resetsAtMs > 0 ? window.resetsAtMs : void 0
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function formatProviderUsageWindowLabel(entry, entries) {
|
|
247
|
+
const minutes = entry.window.windowDurationMins;
|
|
248
|
+
if (minutes === WEEKLY_WINDOW_MINUTES || hasWeeklySecondaryResetCadence(entry, entries)) return "Week";
|
|
249
|
+
if (minutes === DAY_WINDOW_MINUTES) return "Day";
|
|
250
|
+
if (minutes !== void 0 && minutes > 0 && minutes < DAY_WINDOW_MINUTES) return minutes % 60 === 0 ? `${minutes / 60}h` : `${minutes}m`;
|
|
251
|
+
if (minutes !== void 0 && minutes > 0 && minutes % DAY_WINDOW_MINUTES === 0) return `${minutes / DAY_WINDOW_MINUTES}d`;
|
|
252
|
+
if (minutes !== void 0 && minutes > 0 && minutes % 60 === 0) return `${minutes / 60}h`;
|
|
253
|
+
return entry.key === "primary" ? "Short" : "Long";
|
|
254
|
+
}
|
|
255
|
+
function resolveCodexProviderUsagePlan(snapshot) {
|
|
256
|
+
const plan = readString(snapshot, "planType") ?? readString(snapshot, "plan_type");
|
|
257
|
+
const creditSummary = formatCodexCreditSummary(isJsonObject(snapshot.credits) ? snapshot.credits : void 0);
|
|
258
|
+
if (!creditSummary) return plan;
|
|
259
|
+
return plan ? `${plan} (${creditSummary})` : creditSummary;
|
|
260
|
+
}
|
|
261
|
+
function formatCodexCreditSummary(credits) {
|
|
262
|
+
if (!credits) return;
|
|
263
|
+
if ((readBoolean(credits, "hasCredits") ?? readBoolean(credits, "has_credits")) === false) return;
|
|
264
|
+
if (readBoolean(credits, "unlimited")) return "Unlimited credits";
|
|
265
|
+
const balance = typeof credits.balance === "string" ? parseStrictFiniteNumber(credits.balance) : asFiniteNumber(credits.balance);
|
|
266
|
+
if (balance === void 0 || balance <= 0) return;
|
|
267
|
+
const roundedBalance = Math.round(balance);
|
|
268
|
+
return roundedBalance > 0 ? `${roundedBalance} credits` : void 0;
|
|
269
|
+
}
|
|
270
|
+
function selectSnapshotBlockingReset(snapshot, nowMs) {
|
|
271
|
+
const futureWindows = readWindowEntries(snapshot).map((entry) => entry.window).filter((window) => window.resetsAtMs > nowMs);
|
|
272
|
+
const exhaustedWindows = futureWindows.filter((window) => window.usedPercent !== void 0 && window.usedPercent >= 100);
|
|
273
|
+
const candidates = exhaustedWindows.length > 0 ? exhaustedWindows : futureWindows;
|
|
274
|
+
const resetSort = exhaustedWindows.length > 0 ? (left, right) => right.resetsAtMs - left.resetsAtMs : (left, right) => left.resetsAtMs - right.resetsAtMs;
|
|
275
|
+
return candidates.toSorted(resetSort)[0];
|
|
276
|
+
}
|
|
277
|
+
function selectBlockingWindowEntry(entries, nowMs) {
|
|
278
|
+
const futureEntries = entries.filter((entry) => entry.window.resetsAtMs > nowMs);
|
|
279
|
+
const exhaustedFutureEntries = futureEntries.filter((entry) => entry.window.usedPercent !== void 0 && entry.window.usedPercent >= 100);
|
|
280
|
+
const resetCandidates = exhaustedFutureEntries.length > 0 ? exhaustedFutureEntries : futureEntries;
|
|
281
|
+
if (resetCandidates.length > 0) {
|
|
282
|
+
const resetSort = exhaustedFutureEntries.length > 0 ? (left, right) => right.window.resetsAtMs - left.window.resetsAtMs : (left, right) => left.window.resetsAtMs - right.window.resetsAtMs;
|
|
283
|
+
return resetCandidates.toSorted(resetSort)[0];
|
|
284
|
+
}
|
|
285
|
+
return entries.filter((entry) => entry.window.usedPercent !== void 0 && entry.window.usedPercent >= 100).toSorted((left, right) => (right.window.windowDurationMins ?? 0) - (left.window.windowDurationMins ?? 0))[0];
|
|
286
|
+
}
|
|
287
|
+
function readWindowEntries(snapshot) {
|
|
288
|
+
return LIMIT_WINDOW_KEYS.flatMap((key) => {
|
|
289
|
+
const window = readRateLimitWindow(snapshot, key);
|
|
290
|
+
return window ? [{
|
|
291
|
+
key,
|
|
292
|
+
window
|
|
293
|
+
}] : [];
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function formatBlockingLimitPeriod(entry, entries) {
|
|
297
|
+
const minutes = entry?.window.windowDurationMins;
|
|
298
|
+
if (entry && (minutes === WEEKLY_WINDOW_MINUTES || hasWeeklySecondaryResetCadence(entry, entries))) return "weekly";
|
|
299
|
+
if (minutes === DAY_WINDOW_MINUTES) return "daily";
|
|
300
|
+
if (minutes !== void 0 && minutes > 0 && minutes < DAY_WINDOW_MINUTES) return "short-term";
|
|
301
|
+
}
|
|
302
|
+
function formatUsageLine(snapshot) {
|
|
303
|
+
const entries = readWindowEntries(snapshot);
|
|
304
|
+
const windows = entries.filter((entry) => entry.window.usedPercent !== void 0).toSorted((left, right) => (right.window.windowDurationMins ?? 0) - (left.window.windowDurationMins ?? 0)).map((entry) => {
|
|
305
|
+
return `${formatUsageWindowLabel(entry, entries)} ${Math.round(entry.window.usedPercent ?? 0)}%`;
|
|
306
|
+
});
|
|
307
|
+
return windows.length > 0 ? windows.join(" · ") : void 0;
|
|
308
|
+
}
|
|
309
|
+
function formatUsageWindowLabel(entry, entries) {
|
|
310
|
+
const minutes = entry.window.windowDurationMins;
|
|
311
|
+
if (minutes === WEEKLY_WINDOW_MINUTES || hasWeeklySecondaryResetCadence(entry, entries)) return "weekly";
|
|
312
|
+
if (minutes === DAY_WINDOW_MINUTES) return "daily";
|
|
313
|
+
if (minutes !== void 0 && minutes > 0 && minutes < DAY_WINDOW_MINUTES) return "short-term";
|
|
314
|
+
if (minutes !== void 0 && minutes > 0 && minutes % DAY_WINDOW_MINUTES === 0) return `${minutes / DAY_WINDOW_MINUTES}-day`;
|
|
315
|
+
if (minutes !== void 0 && minutes > 0 && minutes % 60 === 0) return `${minutes / 60}-hour`;
|
|
316
|
+
return "usage";
|
|
317
|
+
}
|
|
318
|
+
function hasWeeklySecondaryResetCadence(entry, entries) {
|
|
319
|
+
if (entry.key !== "secondary" || entry.window.windowDurationMins !== DAY_WINDOW_MINUTES) return false;
|
|
320
|
+
const primaryResetMs = entries.find((candidate) => candidate.key === "primary")?.window.resetsAtMs;
|
|
321
|
+
return typeof primaryResetMs === "number" && primaryResetMs > 0 && entry.window.resetsAtMs > 0 && entry.window.resetsAtMs - primaryResetMs >= WEEKLY_RESET_GAP_MS;
|
|
322
|
+
}
|
|
323
|
+
function formatCalendarResetTime(resetsAtMs, nowMs) {
|
|
324
|
+
const resetDate = new Date(resetsAtMs);
|
|
325
|
+
const resetParts = new Intl.DateTimeFormat("en-US", {
|
|
326
|
+
month: "short",
|
|
327
|
+
day: "numeric",
|
|
328
|
+
...resetDate.getFullYear() === new Date(nowMs).getFullYear() ? {} : { year: "numeric" },
|
|
329
|
+
hour: "numeric",
|
|
330
|
+
minute: "2-digit",
|
|
331
|
+
timeZoneName: "short"
|
|
332
|
+
}).formatToParts(resetDate);
|
|
333
|
+
const part = (type) => resetParts.find((entry) => entry.type === type)?.value;
|
|
334
|
+
const dateParts = [
|
|
335
|
+
part("month"),
|
|
336
|
+
part("day"),
|
|
337
|
+
part("year")
|
|
338
|
+
].filter(Boolean);
|
|
339
|
+
return [
|
|
340
|
+
dateParts.length > 1 ? `${dateParts[0]} ${dateParts.slice(1).join(", ")}` : dateParts[0],
|
|
341
|
+
"at",
|
|
342
|
+
[
|
|
343
|
+
[part("hour"), part("minute")].filter(Boolean).join(":"),
|
|
344
|
+
part("dayPeriod"),
|
|
345
|
+
part("timeZoneName")
|
|
346
|
+
].filter(Boolean).join(" ")
|
|
347
|
+
].filter(Boolean).join(" ");
|
|
348
|
+
}
|
|
349
|
+
function formatRelativeDuration(durationMs) {
|
|
350
|
+
const safeMs = Math.max(1e3, durationMs);
|
|
351
|
+
if (safeMs < ONE_MINUTE_MS) return `${Math.ceil(safeMs / 1e3)} seconds`;
|
|
352
|
+
if (safeMs < ONE_HOUR_MS) {
|
|
353
|
+
const minutes = Math.ceil(safeMs / ONE_MINUTE_MS);
|
|
354
|
+
return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
355
|
+
}
|
|
356
|
+
if (safeMs < ONE_DAY_MS) {
|
|
357
|
+
const hours = Math.ceil(safeMs / ONE_HOUR_MS);
|
|
358
|
+
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
359
|
+
}
|
|
360
|
+
const days = Math.ceil(safeMs / ONE_DAY_MS);
|
|
361
|
+
return `${days} ${days === 1 ? "day" : "days"}`;
|
|
362
|
+
}
|
|
363
|
+
function formatResetDuration(resetsAtMs, nowMs) {
|
|
364
|
+
const durationMs = Math.round(Math.max(ONE_SECOND_MS, resetsAtMs - nowMs) / ONE_SECOND_MS) * ONE_SECOND_MS;
|
|
365
|
+
const days = Math.floor(durationMs / ONE_DAY_MS);
|
|
366
|
+
const hours = Math.floor(durationMs % ONE_DAY_MS / ONE_HOUR_MS);
|
|
367
|
+
const minutes = Math.floor(durationMs % ONE_HOUR_MS / ONE_MINUTE_MS);
|
|
368
|
+
const seconds = Math.floor(durationMs % ONE_MINUTE_MS / ONE_SECOND_MS);
|
|
369
|
+
if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
370
|
+
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
371
|
+
if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
372
|
+
return `${seconds}s`;
|
|
373
|
+
}
|
|
374
|
+
function formatWindowSignature(value) {
|
|
375
|
+
if (!isJsonObject(value)) return "";
|
|
376
|
+
return `${readNumber(value, "usedPercent") ?? readNumber(value, "used_percent") ?? ""}:${readNumber(value, "resetsAt") ?? readNumber(value, "resets_at") ?? ""}`;
|
|
377
|
+
}
|
|
378
|
+
function extractCodexRetryHint(message) {
|
|
379
|
+
if (!message) return;
|
|
380
|
+
const tryAgainAt = /\btry again\s+(at\s+[^.!?\n]+)(?:[.!?]|$)/iu.exec(message);
|
|
381
|
+
if (tryAgainAt?.[1]) return tryAgainAt[1].trim();
|
|
382
|
+
return /\btry again\s+((?:tomorrow|in\s+[^.!?\n]+)[^.!?\n]*)(?:[.!?]|$)/iu.exec(message)?.[1]?.trim();
|
|
383
|
+
}
|
|
384
|
+
function readString(record, key) {
|
|
385
|
+
const value = record[key];
|
|
386
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
387
|
+
}
|
|
388
|
+
function readNullableString(record, key) {
|
|
389
|
+
return readString(record, key) ?? void 0;
|
|
390
|
+
}
|
|
391
|
+
function readNumber(record, key) {
|
|
392
|
+
return asFiniteNumber(record[key]);
|
|
393
|
+
}
|
|
394
|
+
function readBoolean(record, key) {
|
|
395
|
+
const value = record[key];
|
|
396
|
+
return typeof value === "boolean" ? value : void 0;
|
|
397
|
+
}
|
|
398
|
+
function normalizeText(value) {
|
|
399
|
+
const text = value?.trim();
|
|
400
|
+
return text ? text : void 0;
|
|
401
|
+
}
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region extensions/codex/provider.ts
|
|
404
|
+
/**
|
|
405
|
+
* Codex provider plugin and live app-server model catalog discovery.
|
|
406
|
+
*/
|
|
407
|
+
const DEFAULT_DISCOVERY_TIMEOUT_MS = 2500;
|
|
408
|
+
const LIVE_DISCOVERY_ENV = "OPENCLAW_CODEX_DISCOVERY_LIVE";
|
|
409
|
+
const MODEL_DISCOVERY_PAGE_LIMIT = 100;
|
|
410
|
+
const CODEX_APP_SERVER_SETUP_METHOD_ID = "app-server";
|
|
411
|
+
const CODEX_DEFAULT_MODEL_REF = `${CODEX_PROVIDER_ID}/${FALLBACK_CODEX_MODELS[0].id}`;
|
|
412
|
+
const codexCatalogLog = createSubsystemLogger("codex/catalog");
|
|
413
|
+
/**
|
|
414
|
+
* Builds the Codex provider plugin, including setup metadata, catalog discovery,
|
|
415
|
+
* dynamic model resolution, and prompt/thinking hooks.
|
|
416
|
+
*/
|
|
417
|
+
function buildCodexProvider(options = {}) {
|
|
418
|
+
return {
|
|
419
|
+
id: CODEX_PROVIDER_ID,
|
|
420
|
+
label: "Codex",
|
|
421
|
+
docsPath: "/providers/models",
|
|
422
|
+
auth: [{
|
|
423
|
+
id: CODEX_APP_SERVER_SETUP_METHOD_ID,
|
|
424
|
+
label: "Codex app-server",
|
|
425
|
+
hint: "Use the Codex app-server runtime and managed model catalog.",
|
|
426
|
+
kind: "custom",
|
|
427
|
+
wizard: {
|
|
428
|
+
choiceId: CODEX_PROVIDER_ID,
|
|
429
|
+
choiceLabel: "Codex app-server",
|
|
430
|
+
choiceHint: "Use the Codex app-server runtime and managed model catalog.",
|
|
431
|
+
assistantPriority: -40,
|
|
432
|
+
groupId: CODEX_PROVIDER_ID,
|
|
433
|
+
groupLabel: "Codex",
|
|
434
|
+
groupHint: "Codex app-server model provider",
|
|
435
|
+
onboardingScopes: ["text-inference"]
|
|
436
|
+
},
|
|
437
|
+
run: async () => ({
|
|
438
|
+
profiles: [],
|
|
439
|
+
defaultModel: CODEX_DEFAULT_MODEL_REF
|
|
440
|
+
})
|
|
441
|
+
}],
|
|
442
|
+
catalog: {
|
|
443
|
+
order: "late",
|
|
444
|
+
run: async (ctx) => {
|
|
445
|
+
const pluginConfig = resolvePluginConfigObject(ctx.config, "codex") ?? (ctx.config ? void 0 : options.pluginConfig);
|
|
446
|
+
return await buildCodexProviderCatalog({
|
|
447
|
+
env: ctx.env,
|
|
448
|
+
pluginConfig,
|
|
449
|
+
listModels: options.listModels
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
staticCatalog: {
|
|
454
|
+
order: "late",
|
|
455
|
+
run: async () => ({ provider: buildCodexProviderConfig(FALLBACK_CODEX_MODELS) })
|
|
456
|
+
},
|
|
457
|
+
resolveDynamicModel: (ctx) => resolveCodexDynamicModel(ctx.modelId),
|
|
458
|
+
resolveSyntheticAuth: () => ({
|
|
459
|
+
apiKey: CODEX_APP_SERVER_AUTH_MARKER,
|
|
460
|
+
source: "codex-app-server",
|
|
461
|
+
mode: "token"
|
|
462
|
+
}),
|
|
463
|
+
fetchUsageSnapshot: async (ctx) => {
|
|
464
|
+
if (ctx.token !== "codex-app-server") return null;
|
|
465
|
+
const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig: resolvePluginConfigObject(ctx.config, "codex") ?? (ctx.config ? void 0 : options.pluginConfig) });
|
|
466
|
+
return buildCodexAppServerUsageSnapshot(await (options.readRateLimits ?? requestCodexAppServerRateLimitsLazy)({
|
|
467
|
+
timeoutMs: ctx.timeoutMs,
|
|
468
|
+
agentDir: ctx.agentDir,
|
|
469
|
+
...ctx.authProfileId ? { authProfileId: ctx.authProfileId } : {},
|
|
470
|
+
config: ctx.config,
|
|
471
|
+
startOptions: appServer.start
|
|
472
|
+
}));
|
|
473
|
+
},
|
|
474
|
+
resolveThinkingProfile: ({ modelId }) => ({ levels: [
|
|
475
|
+
{ id: "off" },
|
|
476
|
+
{ id: "minimal" },
|
|
477
|
+
{ id: "low" },
|
|
478
|
+
{ id: "medium" },
|
|
479
|
+
{ id: "high" },
|
|
480
|
+
...isKnownXHighCodexModel(modelId) ? [{ id: "xhigh" }] : []
|
|
481
|
+
] }),
|
|
482
|
+
resolveSystemPromptContribution: ({ config, modelId }) => resolveCodexSystemPromptContribution({
|
|
483
|
+
config,
|
|
484
|
+
modelId
|
|
485
|
+
}),
|
|
486
|
+
isModernModelRef: ({ modelId }) => isModernCodexModel(modelId)
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Builds the Codex model catalog from live app-server discovery, falling back
|
|
491
|
+
* to built-in model records when discovery is disabled or unavailable.
|
|
492
|
+
*/
|
|
493
|
+
async function buildCodexProviderCatalog(options = {}) {
|
|
494
|
+
const config = readCodexPluginConfig(options.pluginConfig);
|
|
495
|
+
const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig: options.pluginConfig });
|
|
496
|
+
const timeoutMs = normalizeTimeoutMs(config.discovery?.timeoutMs);
|
|
497
|
+
let discovered = [];
|
|
498
|
+
if (config.discovery?.enabled !== false && !shouldSkipLiveDiscovery(options.env)) discovered = await listModelsBestEffort({
|
|
499
|
+
listModels: options.listModels ?? listCodexAppServerModelsLazy,
|
|
500
|
+
timeoutMs,
|
|
501
|
+
startOptions: appServer.start,
|
|
502
|
+
onDiscoveryFailure: options.onDiscoveryFailure
|
|
503
|
+
});
|
|
504
|
+
return { provider: buildCodexProviderConfig(discovered.length > 0 ? discovered : FALLBACK_CODEX_MODELS) };
|
|
505
|
+
}
|
|
506
|
+
function resolveCodexDynamicModel(modelId) {
|
|
507
|
+
const id = modelId.trim();
|
|
508
|
+
if (!id) return;
|
|
509
|
+
const fallbackModel = FALLBACK_CODEX_MODELS.find((model) => model.id === id);
|
|
510
|
+
return normalizeModelCompat({
|
|
511
|
+
...buildCodexModelDefinition({
|
|
512
|
+
id,
|
|
513
|
+
model: id,
|
|
514
|
+
inputModalities: fallbackModel?.inputModalities ?? ["text"],
|
|
515
|
+
supportedReasoningEfforts: fallbackModel?.supportedReasoningEfforts ?? (shouldDefaultToReasoningModel(id) ? ["medium"] : [])
|
|
516
|
+
}),
|
|
517
|
+
provider: CODEX_PROVIDER_ID,
|
|
518
|
+
baseUrl: CODEX_BASE_URL
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
async function listModelsBestEffort(params) {
|
|
522
|
+
try {
|
|
523
|
+
const models = [];
|
|
524
|
+
let cursor;
|
|
525
|
+
do {
|
|
526
|
+
const result = await params.listModels({
|
|
527
|
+
timeoutMs: params.timeoutMs,
|
|
528
|
+
limit: MODEL_DISCOVERY_PAGE_LIMIT,
|
|
529
|
+
cursor,
|
|
530
|
+
startOptions: params.startOptions,
|
|
531
|
+
sharedClient: false
|
|
532
|
+
});
|
|
533
|
+
models.push(...result.models.filter((model) => !model.hidden));
|
|
534
|
+
cursor = result.nextCursor;
|
|
535
|
+
} while (cursor);
|
|
536
|
+
return models;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
params.onDiscoveryFailure?.(error);
|
|
539
|
+
codexCatalogLog.debug("codex model discovery failed; using fallback catalog", { error: error instanceof Error ? error.message : String(error) });
|
|
540
|
+
return [];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function listCodexAppServerModelsLazy(options) {
|
|
544
|
+
const { listCodexAppServerModels } = await import("./models-CQGfP7nG.js").then((n) => n.r);
|
|
545
|
+
return listCodexAppServerModels(options);
|
|
546
|
+
}
|
|
547
|
+
async function requestCodexAppServerRateLimitsLazy(options) {
|
|
548
|
+
const { requestCodexAppServerJson } = await import("./request-Dq0LUOqZ.js").then((n) => n.n);
|
|
549
|
+
return await requestCodexAppServerJson({
|
|
550
|
+
method: "account/rateLimits/read",
|
|
551
|
+
timeoutMs: options.timeoutMs,
|
|
552
|
+
agentDir: options.agentDir,
|
|
553
|
+
...options.authProfileId ? { authProfileId: options.authProfileId } : {},
|
|
554
|
+
config: options.config,
|
|
555
|
+
startOptions: options.startOptions,
|
|
556
|
+
isolated: true
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function normalizeTimeoutMs(value) {
|
|
560
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : DEFAULT_DISCOVERY_TIMEOUT_MS;
|
|
561
|
+
}
|
|
562
|
+
function shouldSkipLiveDiscovery(env = process.env) {
|
|
563
|
+
const override = env[LIVE_DISCOVERY_ENV]?.trim().toLowerCase();
|
|
564
|
+
if (override === "0" || override === "false") return true;
|
|
565
|
+
return Boolean(env.VITEST) && override !== "1";
|
|
566
|
+
}
|
|
567
|
+
function shouldDefaultToReasoningModel(modelId) {
|
|
568
|
+
const lower = modelId.toLowerCase();
|
|
569
|
+
return lower.startsWith("gpt-5") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4");
|
|
570
|
+
}
|
|
571
|
+
function isKnownXHighCodexModel(modelId) {
|
|
572
|
+
const lower = modelId.trim().toLowerCase();
|
|
573
|
+
return lower.startsWith("gpt-5") || lower.startsWith("o3") || lower.startsWith("o4") || lower.includes("codex");
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Returns true for Codex models that use the modern reasoning effort enum and
|
|
577
|
+
* reject the legacy CLI `minimal` default.
|
|
578
|
+
*/
|
|
579
|
+
function isModernCodexModel(modelId) {
|
|
580
|
+
const lower = modelId.trim().toLowerCase();
|
|
581
|
+
return lower === "gpt-5.5" || lower === "gpt-5.4" || lower === "gpt-5.4-mini" || lower === "gpt-5.3-codex-spark";
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
584
|
+
export { hasCodexRateLimitSnapshots as a, summarizeCodexAccountRateLimits as c, formatCodexUsageLimitErrorMessage as i, summarizeCodexAccountUsage as l, buildCodexProviderCatalog as n, resolveCodexUsageLimitResetAtMs as o, isModernCodexModel as r, shouldRefreshCodexRateLimitsForUsageLimitMessage as s, buildCodexProvider as t, summarizeCodexRateLimits as u };
|