oc-codex-multi-auth 6.1.3 → 6.1.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.
@@ -0,0 +1,247 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { renameWithWindowsRetry } from "./storage/atomic-write.js";
5
+ export const TUI_QUOTA_CACHE_VERSION = 1;
6
+ export const TUI_QUOTA_CACHE_FILE = "oc-codex-multi-auth-tui-quota.json";
7
+ const TUI_QUOTA_CACHE_WRITE_SKIP_MS = 500;
8
+ const recentTuiQuotaWrites = new Map();
9
+ function getDefaultOpenCodeStateDir() {
10
+ return join(homedir(), ".local", "state", "opencode");
11
+ }
12
+ export function getTuiQuotaCachePath(stateDir) {
13
+ const envStateDir = process.env.OPENCODE_STATE_DIR?.trim();
14
+ return join(stateDir?.trim() || envStateDir || getDefaultOpenCodeStateDir(), TUI_QUOTA_CACHE_FILE);
15
+ }
16
+ function parseFiniteNumberHeader(headers, name) {
17
+ const raw = headers.get(name);
18
+ if (!raw)
19
+ return undefined;
20
+ const parsed = Number(raw);
21
+ return Number.isFinite(parsed) ? parsed : undefined;
22
+ }
23
+ function parseFiniteIntHeader(headers, name) {
24
+ const raw = headers.get(name);
25
+ if (!raw)
26
+ return undefined;
27
+ const parsed = Number.parseInt(raw, 10);
28
+ return Number.isFinite(parsed) ? parsed : undefined;
29
+ }
30
+ function parseResetAtMs(headers, prefix) {
31
+ const resetAfterSeconds = parseFiniteIntHeader(headers, `${prefix}-reset-after-seconds`);
32
+ if (typeof resetAfterSeconds === "number" &&
33
+ Number.isFinite(resetAfterSeconds) &&
34
+ resetAfterSeconds > 0) {
35
+ return Date.now() + resetAfterSeconds * 1000;
36
+ }
37
+ const resetAtRaw = headers.get(`${prefix}-reset-at`);
38
+ if (!resetAtRaw)
39
+ return undefined;
40
+ const trimmed = resetAtRaw.trim();
41
+ if (/^\d+$/.test(trimmed)) {
42
+ const parsedNumber = Number.parseInt(trimmed, 10);
43
+ if (Number.isFinite(parsedNumber) && parsedNumber > 0) {
44
+ return parsedNumber < 10_000_000_000
45
+ ? parsedNumber * 1000
46
+ : parsedNumber;
47
+ }
48
+ }
49
+ const parsedDate = Date.parse(trimmed);
50
+ return Number.isFinite(parsedDate) ? parsedDate : undefined;
51
+ }
52
+ function formatWindowLabel(windowMinutes) {
53
+ if (!windowMinutes ||
54
+ !Number.isFinite(windowMinutes) ||
55
+ windowMinutes <= 0) {
56
+ return "quota";
57
+ }
58
+ if (windowMinutes % 1440 === 0)
59
+ return `${windowMinutes / 1440}d`;
60
+ if (windowMinutes % 60 === 0)
61
+ return `${windowMinutes / 60}h`;
62
+ return `${windowMinutes}m`;
63
+ }
64
+ function getLeftPercent(usedPercent) {
65
+ return typeof usedPercent === "number" && Number.isFinite(usedPercent)
66
+ ? Math.max(0, Math.min(100, Math.round(100 - usedPercent)))
67
+ : null;
68
+ }
69
+ function hasCodexQuotaHeaders(headers) {
70
+ const keys = [
71
+ "x-codex-primary-used-percent",
72
+ "x-codex-primary-window-minutes",
73
+ "x-codex-primary-reset-at",
74
+ "x-codex-primary-reset-after-seconds",
75
+ "x-codex-secondary-used-percent",
76
+ "x-codex-secondary-window-minutes",
77
+ "x-codex-secondary-reset-at",
78
+ "x-codex-secondary-reset-after-seconds",
79
+ ];
80
+ return keys.some((key) => headers.get(key) !== null);
81
+ }
82
+ function parseLimit(headers, prefix) {
83
+ const usedPercent = parseFiniteNumberHeader(headers, `${prefix}-used-percent`);
84
+ const windowMinutes = parseFiniteIntHeader(headers, `${prefix}-window-minutes`);
85
+ const resetAtMs = parseResetAtMs(headers, prefix);
86
+ return {
87
+ label: formatWindowLabel(windowMinutes),
88
+ leftPercent: getLeftPercent(usedPercent),
89
+ usedPercent,
90
+ windowMinutes,
91
+ resetAtMs,
92
+ };
93
+ }
94
+ function hasUsefulLimit(limit) {
95
+ return Boolean(limit.windowMinutes ||
96
+ typeof limit.usedPercent === "number" ||
97
+ typeof limit.resetAtMs === "number");
98
+ }
99
+ export function createTuiQuotaSnapshot(input) {
100
+ return {
101
+ version: TUI_QUOTA_CACHE_VERSION,
102
+ ...input,
103
+ fetchedAt: input.fetchedAt ?? Date.now(),
104
+ };
105
+ }
106
+ function getSnapshotWriteKey(snapshot) {
107
+ return JSON.stringify({
108
+ fingerprint: snapshot.fingerprint,
109
+ source: snapshot.source,
110
+ accountIndex: snapshot.accountIndex,
111
+ accountCount: snapshot.accountCount,
112
+ accountEmail: snapshot.accountEmail,
113
+ accountLabel: snapshot.accountLabel,
114
+ planType: snapshot.planType,
115
+ activeLimit: snapshot.activeLimit,
116
+ limits: snapshot.limits.map((limit) => ({
117
+ label: limit.label,
118
+ leftPercent: limit.leftPercent,
119
+ usedPercent: limit.usedPercent,
120
+ windowMinutes: limit.windowMinutes,
121
+ resetAtMs: limit.resetAtMs,
122
+ })),
123
+ });
124
+ }
125
+ export function parseTuiQuotaSnapshotFromHeaders(headers, input) {
126
+ if (!hasCodexQuotaHeaders(headers))
127
+ return undefined;
128
+ const limits = [
129
+ parseLimit(headers, "x-codex-primary"),
130
+ parseLimit(headers, "x-codex-secondary"),
131
+ ].filter(hasUsefulLimit);
132
+ if (limits.length === 0)
133
+ return undefined;
134
+ const planTypeRaw = headers.get("x-codex-plan-type");
135
+ const planType = planTypeRaw?.trim() || undefined;
136
+ const activeLimit = parseFiniteIntHeader(headers, "x-codex-active-limit");
137
+ return createTuiQuotaSnapshot({
138
+ ...input,
139
+ source: "headers",
140
+ planType,
141
+ activeLimit,
142
+ limits,
143
+ });
144
+ }
145
+ function isRecord(value) {
146
+ return value !== null && typeof value === "object" && !Array.isArray(value);
147
+ }
148
+ function isOptionalFiniteNumber(value) {
149
+ return value === undefined || (typeof value === "number" && Number.isFinite(value));
150
+ }
151
+ function isNullablePercent(value) {
152
+ return (value === null ||
153
+ (typeof value === "number" &&
154
+ Number.isFinite(value) &&
155
+ value >= 0 &&
156
+ value <= 100));
157
+ }
158
+ function isTuiQuotaLimit(value) {
159
+ return (isRecord(value) &&
160
+ typeof value.label === "string" &&
161
+ value.label.trim().length > 0 &&
162
+ isNullablePercent(value.leftPercent) &&
163
+ isOptionalFiniteNumber(value.usedPercent) &&
164
+ isOptionalFiniteNumber(value.windowMinutes) &&
165
+ isOptionalFiniteNumber(value.resetAtMs));
166
+ }
167
+ export function isTuiQuotaSnapshot(value) {
168
+ return (isRecord(value) &&
169
+ value.version === TUI_QUOTA_CACHE_VERSION &&
170
+ typeof value.fingerprint === "string" &&
171
+ value.fingerprint.trim().length > 0 &&
172
+ typeof value.fetchedAt === "number" &&
173
+ Number.isFinite(value.fetchedAt) &&
174
+ (value.source === "headers" || value.source === "usage") &&
175
+ isOptionalFiniteNumber(value.accountIndex) &&
176
+ isOptionalFiniteNumber(value.accountCount) &&
177
+ (value.accountEmail === undefined ||
178
+ typeof value.accountEmail === "string") &&
179
+ (value.accountLabel === undefined ||
180
+ typeof value.accountLabel === "string") &&
181
+ (value.planType === undefined || typeof value.planType === "string") &&
182
+ isOptionalFiniteNumber(value.activeLimit) &&
183
+ Array.isArray(value.limits) &&
184
+ value.limits.every(isTuiQuotaLimit));
185
+ }
186
+ export async function readTuiQuotaSnapshot(cachePath) {
187
+ try {
188
+ const raw = await fs.readFile(cachePath ?? getTuiQuotaCachePath(), "utf-8");
189
+ const parsed = JSON.parse(raw);
190
+ return isTuiQuotaSnapshot(parsed) ? parsed : undefined;
191
+ }
192
+ catch {
193
+ return undefined;
194
+ }
195
+ }
196
+ export async function writeTuiQuotaSnapshot(snapshot, cachePath) {
197
+ const target = cachePath ?? getTuiQuotaCachePath();
198
+ const writeKey = getSnapshotWriteKey(snapshot);
199
+ const now = Date.now();
200
+ const recentWrite = recentTuiQuotaWrites.get(target);
201
+ if (recentWrite?.key === writeKey &&
202
+ now - recentWrite.at < TUI_QUOTA_CACHE_WRITE_SKIP_MS) {
203
+ await recentWrite.promise;
204
+ return;
205
+ }
206
+ const temporary = `${target}.${process.pid}.${now}.${Math.random().toString(36).slice(2)}.tmp`;
207
+ const writePromise = (async () => {
208
+ await fs.mkdir(dirname(target), { recursive: true });
209
+ await fs.writeFile(temporary, `${JSON.stringify(snapshot, null, 2)}\n`, {
210
+ encoding: "utf-8",
211
+ mode: 0o600,
212
+ });
213
+ await renameWithWindowsRetry(temporary, target);
214
+ })();
215
+ recentTuiQuotaWrites.set(target, {
216
+ key: writeKey,
217
+ at: now,
218
+ promise: writePromise,
219
+ });
220
+ try {
221
+ await writePromise;
222
+ recentTuiQuotaWrites.set(target, {
223
+ key: writeKey,
224
+ at: Date.now(),
225
+ });
226
+ }
227
+ catch (error) {
228
+ await fs.unlink(temporary).catch(() => undefined);
229
+ if (recentTuiQuotaWrites.get(target)?.promise === writePromise) {
230
+ recentTuiQuotaWrites.delete(target);
231
+ }
232
+ throw error;
233
+ }
234
+ }
235
+ export async function clearTuiQuotaSnapshot(cachePath) {
236
+ const target = cachePath ?? getTuiQuotaCachePath();
237
+ recentTuiQuotaWrites.delete(target);
238
+ try {
239
+ await fs.unlink(target);
240
+ }
241
+ catch (error) {
242
+ if (error?.code === "ENOENT")
243
+ return;
244
+ throw error;
245
+ }
246
+ }
247
+ //# sourceMappingURL=tui-quota-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-quota-cache.js","sourceRoot":"","sources":["../../lib/tui-quota-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { Config } from "@opencode-ai/sdk/v2";
2
+ export type ReasoningVariant = "none" | "low" | "medium" | "high" | "xhigh";
3
+ export type CompactQuotaLimit = {
4
+ label: string;
5
+ leftPercent: number | null;
6
+ usedPercent?: number;
7
+ windowMinutes?: number;
8
+ resetAtMs?: number;
9
+ };
10
+ export type CompactQuotaSource = "headers" | "usage";
11
+ export type CompactQuotaStatus = {
12
+ type: "loading";
13
+ } | {
14
+ type: "missing";
15
+ } | {
16
+ type: "unavailable";
17
+ } | {
18
+ type: "ready";
19
+ limits: readonly CompactQuotaLimit[];
20
+ stale: boolean;
21
+ source?: CompactQuotaSource;
22
+ fetchedAt?: number;
23
+ fingerprint?: string;
24
+ accountIndex?: number;
25
+ accountCount?: number;
26
+ accountEmail?: string;
27
+ accountLabel?: string;
28
+ planType?: string;
29
+ activeLimit?: number;
30
+ };
31
+ export type PromptStatusMessage = {
32
+ role: "user" | "assistant";
33
+ modelID?: string;
34
+ variant?: string;
35
+ userModel?: {
36
+ modelID?: string;
37
+ variant?: string;
38
+ };
39
+ };
40
+ export type PromptStatusConfig = Pick<Config, "model" | "default_agent" | "agent" | "mode" | "provider">;
41
+ export declare function normalizeReasoningVariant(value: string | undefined): ReasoningVariant | undefined;
42
+ export declare function inferReasoningVariantFromModelId(modelID: string | undefined): ReasoningVariant | undefined;
43
+ export declare function resolvePromptReasoningVariant(params: {
44
+ messages?: readonly PromptStatusMessage[];
45
+ config?: PromptStatusConfig;
46
+ }): ReasoningVariant | undefined;
47
+ export declare function formatPromptStatusText(params: {
48
+ variant?: ReasoningVariant;
49
+ quota: CompactQuotaStatus;
50
+ width?: number;
51
+ }): string;
52
+ export type QuotaPromptTone = "normal" | "warning" | "danger" | "stale" | "unknown";
53
+ export declare function resolveQuotaPromptTone(quota: CompactQuotaStatus): QuotaPromptTone;
54
+ export declare function formatQuotaDetailsText(quota: CompactQuotaStatus, now?: number): string;
55
+ //# sourceMappingURL=tui-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-status.d.ts","sourceRoot":"","sources":["../../lib/tui-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IACA,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEL,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE;QACX,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACF,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACpC,MAAM,EACN,OAAO,GAAG,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CACzD,CAAC;AAqBF,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,MAAM,GAAG,SAAS,GACvB,gBAAgB,GAAG,SAAS,CAO9B;AAED,wBAAgB,gCAAgC,CAC/C,OAAO,EAAE,MAAM,GAAG,SAAS,GACzB,gBAAgB,GAAG,SAAS,CAO9B;AAiED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE;IACrD,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC5B,GAAG,gBAAgB,GAAG,SAAS,CA8B/B;AAmGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CA6BT;AAED,MAAM,MAAM,eAAe,GACxB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,GACP,SAAS,CAAC;AAEb,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,kBAAkB,GACvB,eAAe,CAcjB;AA4DD,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,kBAAkB,EACzB,GAAG,SAAa,GACd,MAAM,CA8BR"}
@@ -0,0 +1,343 @@
1
+ const variantSuffixes = [
2
+ "xhigh",
3
+ "high",
4
+ "medium",
5
+ "low",
6
+ "none",
7
+ ];
8
+ const STATUS_SEPARATOR = ` ${String.fromCharCode(183)} `;
9
+ const WARNING_LIMIT_LEFT_PERCENT = 25;
10
+ const DANGER_LIMIT_LEFT_PERCENT = 10;
11
+ function isRecord(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+ function getString(value) {
15
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
16
+ }
17
+ export function normalizeReasoningVariant(value) {
18
+ const normalized = value?.trim().toLowerCase();
19
+ if (!normalized)
20
+ return undefined;
21
+ if (normalized === "extra-high" || normalized === "extra_high") {
22
+ return "xhigh";
23
+ }
24
+ return variantSuffixes.find((variant) => variant === normalized);
25
+ }
26
+ export function inferReasoningVariantFromModelId(modelID) {
27
+ const modelPart = modelID?.split("/").pop()?.toLowerCase();
28
+ if (!modelPart)
29
+ return undefined;
30
+ for (const variant of variantSuffixes) {
31
+ if (modelPart.endsWith(`-${variant}`))
32
+ return variant;
33
+ }
34
+ return undefined;
35
+ }
36
+ function splitProviderModel(model) {
37
+ const trimmed = model?.trim();
38
+ if (!trimmed)
39
+ return undefined;
40
+ const slashIndex = trimmed.indexOf("/");
41
+ if (slashIndex <= 0 || slashIndex === trimmed.length - 1) {
42
+ return { providerID: "openai", modelID: trimmed };
43
+ }
44
+ return {
45
+ providerID: trimmed.slice(0, slashIndex),
46
+ modelID: trimmed.slice(slashIndex + 1),
47
+ };
48
+ }
49
+ function resolveAgentConfig(config) {
50
+ const agentName = config.default_agent ?? "build";
51
+ const agents = config.agent;
52
+ const selectedAgent = agents?.[agentName];
53
+ if (isRecord(selectedAgent))
54
+ return selectedAgent;
55
+ if (isRecord(agents?.build))
56
+ return agents.build;
57
+ if (isRecord(agents?.general))
58
+ return agents.general;
59
+ const legacyMode = config.mode;
60
+ if (isRecord(legacyMode?.build))
61
+ return legacyMode.build;
62
+ return undefined;
63
+ }
64
+ function resolveProviderReasoningVariant(config, model) {
65
+ const resolved = splitProviderModel(model);
66
+ if (!resolved)
67
+ return undefined;
68
+ const provider = config.provider?.[resolved.providerID];
69
+ const modelConfig = provider?.models?.[resolved.modelID];
70
+ const modelOptions = isRecord(modelConfig?.options)
71
+ ? modelConfig.options
72
+ : undefined;
73
+ const providerOptions = isRecord(provider?.options)
74
+ ? provider.options
75
+ : undefined;
76
+ return (normalizeReasoningVariant(getString(modelOptions?.reasoningEffort)) ??
77
+ normalizeReasoningVariant(getString(providerOptions?.reasoningEffort)));
78
+ }
79
+ function resolveAgentReasoningVariant(agent) {
80
+ if (!agent)
81
+ return undefined;
82
+ const options = isRecord(agent.options) ? agent.options : undefined;
83
+ return (normalizeReasoningVariant(getString(agent.variant)) ??
84
+ normalizeReasoningVariant(getString(agent.reasoningEffort)) ??
85
+ normalizeReasoningVariant(getString(options?.reasoningEffort)));
86
+ }
87
+ export function resolvePromptReasoningVariant(params) {
88
+ const messages = params.messages ?? [];
89
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
90
+ const message = messages[i];
91
+ if (!message)
92
+ continue;
93
+ if (message.role === "user") {
94
+ const variant = normalizeReasoningVariant(message.userModel?.variant) ??
95
+ inferReasoningVariantFromModelId(message.userModel?.modelID);
96
+ if (variant)
97
+ return variant;
98
+ continue;
99
+ }
100
+ const variant = normalizeReasoningVariant(message.variant) ??
101
+ inferReasoningVariantFromModelId(message.modelID);
102
+ if (variant)
103
+ return variant;
104
+ }
105
+ const config = params.config;
106
+ if (!config)
107
+ return undefined;
108
+ const agent = resolveAgentConfig(config);
109
+ const agentVariant = resolveAgentReasoningVariant(agent);
110
+ if (agentVariant)
111
+ return agentVariant;
112
+ const agentModel = getString(agent?.model);
113
+ const model = agentModel ?? config.model;
114
+ return (inferReasoningVariantFromModelId(model) ??
115
+ resolveProviderReasoningVariant(config, model));
116
+ }
117
+ function isPercent(value) {
118
+ return typeof value === "number" && Number.isFinite(value);
119
+ }
120
+ function extractEmailFromLabel(label) {
121
+ const match = label?.match(/[^\s(),<>]+@[^\s(),<>]+/);
122
+ return match?.[0];
123
+ }
124
+ function formatAccountEmail(email) {
125
+ const trimmed = email?.trim() || undefined;
126
+ return trimmed ? `[${trimmed}]` : undefined;
127
+ }
128
+ function formatQuotaLimit(limit, resetLimit, includeReset) {
129
+ if (!isPercent(limit.leftPercent))
130
+ return undefined;
131
+ const label = limit.label.trim() || "quota";
132
+ const base = `${label} ${limit.leftPercent}%`;
133
+ const reset = includeReset && limit === resetLimit ? formatResetTime(limit.resetAtMs) : undefined;
134
+ return reset ? `${base} resets ${reset}` : base;
135
+ }
136
+ function formatAccountHint(quota) {
137
+ if (quota.type !== "ready")
138
+ return undefined;
139
+ if (typeof quota.accountIndex !== "number" ||
140
+ !Number.isFinite(quota.accountIndex)) {
141
+ return undefined;
142
+ }
143
+ if (typeof quota.accountCount === "number" &&
144
+ Number.isFinite(quota.accountCount) &&
145
+ quota.accountCount <= 1) {
146
+ return undefined;
147
+ }
148
+ const email = formatAccountEmail(quota.accountEmail) ??
149
+ formatAccountEmail(extractEmailFromLabel(quota.accountLabel));
150
+ if (email)
151
+ return email;
152
+ return `A${quota.accountIndex}`;
153
+ }
154
+ function findResetLimitForStatus(limits) {
155
+ const eligible = limits.filter((limit) => isPercent(limit.leftPercent));
156
+ if (eligible.length === 0)
157
+ return undefined;
158
+ const lowest = eligible.reduce((current, limit) => (limit.leftPercent ?? 100) < (current.leftPercent ?? 100)
159
+ ? limit
160
+ : current);
161
+ if ((lowest.leftPercent ?? 100) > WARNING_LIMIT_LEFT_PERCENT) {
162
+ return undefined;
163
+ }
164
+ return formatResetTime(lowest.resetAtMs) ? lowest : undefined;
165
+ }
166
+ function formatQuotaParts(quota, includeReset) {
167
+ if (quota.type !== "ready")
168
+ return [];
169
+ const resetLimit = includeReset
170
+ ? findResetLimitForStatus(quota.limits)
171
+ : undefined;
172
+ return quota.limits
173
+ .map((limit) => formatQuotaLimit(limit, resetLimit, includeReset))
174
+ .filter((part) => Boolean(part));
175
+ }
176
+ function formatQuota(quota) {
177
+ if (quota.type === "ready") {
178
+ const parts = formatQuotaParts(quota, true);
179
+ return parts.length > 0 ? parts.join(STATUS_SEPARATOR) : undefined;
180
+ }
181
+ if (quota.type === "missing")
182
+ return "no auth";
183
+ if (quota.type === "unavailable")
184
+ return "limits ?";
185
+ return undefined;
186
+ }
187
+ function maxStatusChars(width) {
188
+ if (!width || !Number.isFinite(width))
189
+ return 32;
190
+ if (width >= 120)
191
+ return 48;
192
+ if (width >= 96)
193
+ return 40;
194
+ if (width >= 78)
195
+ return 32;
196
+ if (width >= 60)
197
+ return 22;
198
+ return 12;
199
+ }
200
+ export function formatPromptStatusText(params) {
201
+ const variant = params.variant;
202
+ const account = formatAccountHint(params.quota);
203
+ const quotaParts = formatQuotaParts(params.quota, true);
204
+ const quotaPartsWithoutReset = formatQuotaParts(params.quota, false);
205
+ const quota = quotaParts.length > 0
206
+ ? quotaParts.join(STATUS_SEPARATOR)
207
+ : formatQuota(params.quota);
208
+ const primaryQuota = quotaParts[0] ?? quota;
209
+ const quotaWithoutReset = quotaPartsWithoutReset.length > 0
210
+ ? quotaPartsWithoutReset.join(STATUS_SEPARATOR)
211
+ : quota;
212
+ const primaryQuotaWithoutReset = quotaPartsWithoutReset[0] ?? primaryQuota;
213
+ const candidates = [
214
+ [account, quota].filter(Boolean).join(STATUS_SEPARATOR),
215
+ [account, primaryQuota].filter(Boolean).join(STATUS_SEPARATOR),
216
+ quota,
217
+ primaryQuota,
218
+ [account, quotaWithoutReset].filter(Boolean).join(STATUS_SEPARATOR),
219
+ [account, primaryQuotaWithoutReset].filter(Boolean).join(STATUS_SEPARATOR),
220
+ quotaWithoutReset,
221
+ primaryQuotaWithoutReset,
222
+ [variant, account, quota].filter(Boolean).join(STATUS_SEPARATOR),
223
+ [variant, quota].filter(Boolean).join(STATUS_SEPARATOR),
224
+ variant,
225
+ account,
226
+ ].filter((candidate) => Boolean(candidate));
227
+ const maxChars = maxStatusChars(params.width);
228
+ return candidates.find((candidate) => candidate.length <= maxChars) ?? "";
229
+ }
230
+ export function resolveQuotaPromptTone(quota) {
231
+ if (quota.type === "ready") {
232
+ if (quota.stale)
233
+ return "stale";
234
+ const percents = quota.limits
235
+ .map((limit) => limit.leftPercent)
236
+ .filter(isPercent);
237
+ if (percents.length === 0)
238
+ return "unknown";
239
+ const lowest = Math.min(...percents);
240
+ if (lowest <= DANGER_LIMIT_LEFT_PERCENT)
241
+ return "danger";
242
+ if (lowest <= WARNING_LIMIT_LEFT_PERCENT)
243
+ return "warning";
244
+ return "normal";
245
+ }
246
+ if (quota.type === "loading")
247
+ return "unknown";
248
+ return "warning";
249
+ }
250
+ function formatReset(resetAtMs) {
251
+ if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0) {
252
+ return undefined;
253
+ }
254
+ const date = new Date(resetAtMs);
255
+ if (!Number.isFinite(date.getTime()))
256
+ return undefined;
257
+ const now = new Date();
258
+ const sameDay = now.getFullYear() === date.getFullYear() &&
259
+ now.getMonth() === date.getMonth() &&
260
+ now.getDate() === date.getDate();
261
+ const time = date.toLocaleTimeString(undefined, {
262
+ hour: "2-digit",
263
+ minute: "2-digit",
264
+ hour12: false,
265
+ });
266
+ if (sameDay)
267
+ return time;
268
+ const day = date.toLocaleDateString(undefined, {
269
+ month: "short",
270
+ day: "2-digit",
271
+ });
272
+ return `${time} on ${day}`;
273
+ }
274
+ function formatResetTime(resetAtMs) {
275
+ if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0) {
276
+ return undefined;
277
+ }
278
+ const date = new Date(resetAtMs);
279
+ if (!Number.isFinite(date.getTime()))
280
+ return undefined;
281
+ return date.toLocaleTimeString(undefined, {
282
+ hour: "2-digit",
283
+ minute: "2-digit",
284
+ hour12: false,
285
+ });
286
+ }
287
+ function formatUpdatedAge(fetchedAt, now) {
288
+ if (!fetchedAt || !Number.isFinite(fetchedAt))
289
+ return "unknown";
290
+ const ageMs = Math.max(0, now - fetchedAt);
291
+ if (ageMs < 60_000)
292
+ return "just now";
293
+ const minutes = Math.floor(ageMs / 60_000);
294
+ if (minutes < 60)
295
+ return `${minutes}m ago`;
296
+ const hours = Math.floor(minutes / 60);
297
+ if (hours < 24)
298
+ return `${hours}h ago`;
299
+ const days = Math.floor(hours / 24);
300
+ return `${days}d ago`;
301
+ }
302
+ function formatDetailsLimit(limit) {
303
+ const label = limit.label.trim() || "quota";
304
+ const left = isPercent(limit.leftPercent)
305
+ ? `${limit.leftPercent}% left`
306
+ : "unavailable";
307
+ const reset = formatReset(limit.resetAtMs);
308
+ return reset ? `${label}: ${left}, resets ${reset}` : `${label}: ${left}`;
309
+ }
310
+ export function formatQuotaDetailsText(quota, now = Date.now()) {
311
+ if (quota.type === "loading")
312
+ return "Quota is loading.";
313
+ if (quota.type === "missing")
314
+ return "No Codex OAuth account is configured.";
315
+ if (quota.type === "unavailable")
316
+ return "Quota is unavailable.";
317
+ const lines = [];
318
+ const accountHint = formatAccountHint(quota);
319
+ if (quota.accountLabel && accountHint) {
320
+ lines.push(`Account: ${accountHint} (${quota.accountLabel})`);
321
+ }
322
+ else if (quota.accountLabel) {
323
+ lines.push(`Account: ${quota.accountLabel}`);
324
+ }
325
+ else if (accountHint) {
326
+ lines.push(`Account: ${accountHint}`);
327
+ }
328
+ for (const limit of quota.limits) {
329
+ lines.push(formatDetailsLimit(limit));
330
+ }
331
+ if (quota.planType)
332
+ lines.push(`Plan: ${quota.planType}`);
333
+ if (typeof quota.activeLimit === "number" &&
334
+ Number.isFinite(quota.activeLimit)) {
335
+ lines.push(`Active limit: ${quota.activeLimit}`);
336
+ }
337
+ lines.push(`Source: ${quota.source === "headers" ? "response headers" : "usage endpoint"}`);
338
+ lines.push(`Updated: ${formatUpdatedAge(quota.fetchedAt, now)}`);
339
+ if (quota.stale)
340
+ lines.push("Status: stale fallback");
341
+ return lines.join("\n");
342
+ }
343
+ //# sourceMappingURL=tui-status.js.map