sidekick-shared 0.18.1 → 0.18.2
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/codexQuota.d.ts +41 -1
- package/dist/codexQuota.js +411 -2
- package/dist/codexQuotaWatcher.d.ts +4 -0
- package/dist/codexQuotaWatcher.js +27 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -2
- package/dist/providers/codexDatabase.js +26 -1
- package/dist/providers/detect.js +34 -9
- package/dist/quota.d.ts +10 -0
- package/dist/types/codex.d.ts +7 -4
- package/dist/watchers/jsonlWatcher.js +9 -5
- package/package.json +1 -1
package/dist/codexQuota.d.ts
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
1
|
import type { QuotaState } from './quota';
|
|
2
|
+
import { CodexProvider } from './providers/codex';
|
|
3
|
+
import type { ProviderQuotaState } from './providerQuota';
|
|
4
|
+
import type { SavedAccountProfile } from './accountRegistry';
|
|
2
5
|
import type { CodexRateLimits } from './types/codex';
|
|
3
|
-
|
|
6
|
+
type SnapshotReader = (providerId: 'codex', accountId: string) => QuotaState | null;
|
|
7
|
+
type SnapshotWriter = (providerId: 'codex', accountId: string, quota: QuotaState) => void;
|
|
8
|
+
export type CodexQuotaResolveSource = 'local' | 'api' | 'auto';
|
|
9
|
+
export interface CodexQuotaCreditsSnapshot {
|
|
10
|
+
hasCredits?: boolean;
|
|
11
|
+
unlimited?: boolean;
|
|
12
|
+
balance?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CodexQuotaResolveOptions {
|
|
15
|
+
workspacePath?: string;
|
|
16
|
+
source?: CodexQuotaResolveSource;
|
|
17
|
+
codexHome?: string;
|
|
18
|
+
provider?: CodexProvider;
|
|
19
|
+
activeAccount?: SavedAccountProfile | null;
|
|
20
|
+
readSnapshot?: SnapshotReader;
|
|
21
|
+
writeSnapshot?: SnapshotWriter;
|
|
22
|
+
maxTailBytes?: number;
|
|
23
|
+
maxSessionFiles?: number;
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
accessToken?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CodexQuotaApiOptions {
|
|
28
|
+
codexHome?: string;
|
|
29
|
+
accessToken?: string;
|
|
30
|
+
fetchImpl?: typeof fetch;
|
|
31
|
+
usageUrl?: string;
|
|
32
|
+
capturedAt?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function quotaFromCodexRateLimits(rateLimits: CodexRateLimits | null | undefined, source?: 'api' | 'session' | 'cache', capturedAt?: string): QuotaState | null;
|
|
35
|
+
export declare function readLatestCodexQuotaFromRollouts(sessionPaths: string[], options?: {
|
|
36
|
+
source?: 'session' | 'cache';
|
|
37
|
+
maxTailBytes?: number;
|
|
38
|
+
maxSessionFiles?: number;
|
|
39
|
+
}): QuotaState | null;
|
|
40
|
+
export declare function resolveCodexQuotaFromLocalSources(options?: CodexQuotaResolveOptions): ProviderQuotaState<'codex'> | null;
|
|
41
|
+
export declare function resolveCodexQuota(options?: CodexQuotaResolveOptions): Promise<ProviderQuotaState<'codex'>>;
|
|
42
|
+
export declare function fetchCodexQuotaFromApi(options?: CodexQuotaApiOptions): Promise<QuotaState>;
|
|
43
|
+
export {};
|
package/dist/codexQuota.js
CHANGED
|
@@ -1,6 +1,153 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.quotaFromCodexRateLimits = quotaFromCodexRateLimits;
|
|
37
|
+
exports.readLatestCodexQuotaFromRollouts = readLatestCodexQuotaFromRollouts;
|
|
38
|
+
exports.resolveCodexQuotaFromLocalSources = resolveCodexQuotaFromLocalSources;
|
|
39
|
+
exports.resolveCodexQuota = resolveCodexQuota;
|
|
40
|
+
exports.fetchCodexQuotaFromApi = fetchCodexQuotaFromApi;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const codexProfiles_1 = require("./codexProfiles");
|
|
44
|
+
const quotaSnapshots_1 = require("./quotaSnapshots");
|
|
45
|
+
const codex_1 = require("./providers/codex");
|
|
46
|
+
const DEFAULT_TAIL_BYTES = 2 * 1024 * 1024;
|
|
47
|
+
const DEFAULT_MAX_SESSION_FILES = 50;
|
|
48
|
+
const CHATGPT_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage';
|
|
49
|
+
function normalizePercent(value) {
|
|
50
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
51
|
+
}
|
|
52
|
+
function timestampToIso(seconds) {
|
|
53
|
+
if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds <= 0) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
return new Date(seconds * 1000).toISOString();
|
|
57
|
+
}
|
|
58
|
+
function accountEmail(account) {
|
|
59
|
+
return account?.email ?? account?.metadata?.email;
|
|
60
|
+
}
|
|
61
|
+
function enrichCodexQuota(state, account) {
|
|
62
|
+
return {
|
|
63
|
+
...state,
|
|
64
|
+
runtimeProvider: 'codex',
|
|
65
|
+
providerId: 'codex',
|
|
66
|
+
accountLabel: account?.label,
|
|
67
|
+
accountDetail: accountEmail(account),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function unavailableCodexQuota(error, account, meta = {}) {
|
|
71
|
+
return enrichCodexQuota({
|
|
72
|
+
fiveHour: { utilization: 0, resetsAt: '' },
|
|
73
|
+
sevenDay: { utilization: 0, resetsAt: '' },
|
|
74
|
+
available: false,
|
|
75
|
+
error,
|
|
76
|
+
providerId: 'codex',
|
|
77
|
+
fiveHourLabel: 'Primary',
|
|
78
|
+
sevenDayLabel: 'Secondary',
|
|
79
|
+
...meta,
|
|
80
|
+
}, account);
|
|
81
|
+
}
|
|
82
|
+
function parseRetryAfterMs(retryAfter) {
|
|
83
|
+
if (!retryAfter)
|
|
84
|
+
return undefined;
|
|
85
|
+
const seconds = Number(retryAfter);
|
|
86
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
87
|
+
return Math.round(seconds * 1000);
|
|
88
|
+
}
|
|
89
|
+
const retryAt = Date.parse(retryAfter);
|
|
90
|
+
if (Number.isNaN(retryAt))
|
|
91
|
+
return undefined;
|
|
92
|
+
return Math.max(retryAt - Date.now(), 0);
|
|
93
|
+
}
|
|
94
|
+
function firstString(value) {
|
|
95
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
96
|
+
}
|
|
97
|
+
function normalizeCredits(credits) {
|
|
98
|
+
if (!credits)
|
|
99
|
+
return undefined;
|
|
100
|
+
return {
|
|
101
|
+
hasCredits: credits.has_credits ?? credits.hasCredits,
|
|
102
|
+
unlimited: credits.unlimited,
|
|
103
|
+
balance: credits.balance ?? undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function normalizeRateLimitReachedType(value) {
|
|
107
|
+
if (typeof value === 'string')
|
|
108
|
+
return value;
|
|
109
|
+
return firstString(value?.kind);
|
|
110
|
+
}
|
|
111
|
+
function normalizeApiWindow(window) {
|
|
112
|
+
if (!window)
|
|
113
|
+
return undefined;
|
|
114
|
+
const windowMinutes = typeof window.window_minutes === 'number' || window.window_minutes === null
|
|
115
|
+
? window.window_minutes
|
|
116
|
+
: typeof window.limit_window_seconds === 'number'
|
|
117
|
+
? Math.round(window.limit_window_seconds / 60)
|
|
118
|
+
: undefined;
|
|
119
|
+
const resetsAt = typeof window.resets_at === 'number' || window.resets_at === null
|
|
120
|
+
? window.resets_at
|
|
121
|
+
: window.reset_at;
|
|
122
|
+
return {
|
|
123
|
+
used_percent: normalizePercent(window.used_percent),
|
|
124
|
+
window_minutes: windowMinutes,
|
|
125
|
+
resets_at: resetsAt,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function rateLimitsFromUsagePayload(payload) {
|
|
129
|
+
if (payload.primary || payload.secondary) {
|
|
130
|
+
return {
|
|
131
|
+
limit_id: payload.limit_id ?? 'codex',
|
|
132
|
+
limit_name: payload.limit_name ?? null,
|
|
133
|
+
primary: payload.primary,
|
|
134
|
+
secondary: payload.secondary,
|
|
135
|
+
credits: normalizeCredits(payload.credits),
|
|
136
|
+
plan_type: payload.plan_type ?? undefined,
|
|
137
|
+
rate_limit_reached_type: normalizeRateLimitReachedType(payload.rate_limit_reached_type),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const preferred = payload.rate_limit;
|
|
141
|
+
return {
|
|
142
|
+
limit_id: 'codex',
|
|
143
|
+
limit_name: null,
|
|
144
|
+
primary: normalizeApiWindow(preferred?.primary_window),
|
|
145
|
+
secondary: normalizeApiWindow(preferred?.secondary_window),
|
|
146
|
+
credits: normalizeCredits(payload.credits),
|
|
147
|
+
plan_type: payload.plan_type ?? undefined,
|
|
148
|
+
rate_limit_reached_type: normalizeRateLimitReachedType(payload.rate_limit_reached_type),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
4
151
|
function quotaFromCodexRateLimits(rateLimits, source = 'session', capturedAt = new Date().toISOString()) {
|
|
5
152
|
const primary = rateLimits?.primary;
|
|
6
153
|
const secondary = rateLimits?.secondary;
|
|
@@ -8,10 +155,10 @@ function quotaFromCodexRateLimits(rateLimits, source = 'session', capturedAt = n
|
|
|
8
155
|
return null;
|
|
9
156
|
return {
|
|
10
157
|
fiveHour: primary
|
|
11
|
-
? { utilization: primary.used_percent, resetsAt:
|
|
158
|
+
? { utilization: normalizePercent(primary.used_percent), resetsAt: timestampToIso(primary.resets_at) }
|
|
12
159
|
: { utilization: 0, resetsAt: '' },
|
|
13
160
|
sevenDay: secondary
|
|
14
|
-
? { utilization: secondary.used_percent, resetsAt:
|
|
161
|
+
? { utilization: normalizePercent(secondary.used_percent), resetsAt: timestampToIso(secondary.resets_at) }
|
|
15
162
|
: { utilization: 0, resetsAt: '' },
|
|
16
163
|
available: true,
|
|
17
164
|
providerId: 'codex',
|
|
@@ -20,5 +167,267 @@ function quotaFromCodexRateLimits(rateLimits, source = 'session', capturedAt = n
|
|
|
20
167
|
stale: source === 'cache',
|
|
21
168
|
fiveHourLabel: 'Primary',
|
|
22
169
|
sevenDayLabel: 'Secondary',
|
|
170
|
+
limitId: rateLimits?.limit_id,
|
|
171
|
+
limitName: rateLimits?.limit_name ?? undefined,
|
|
172
|
+
credits: rateLimits?.credits,
|
|
173
|
+
planType: rateLimits?.plan_type,
|
|
174
|
+
rateLimitReachedType: rateLimits?.rate_limit_reached_type,
|
|
23
175
|
};
|
|
24
176
|
}
|
|
177
|
+
function readLatestCodexQuotaFromRollouts(sessionPaths, options = {}) {
|
|
178
|
+
const maxSessionFiles = options.maxSessionFiles ?? DEFAULT_MAX_SESSION_FILES;
|
|
179
|
+
const maxTailBytes = options.maxTailBytes ?? DEFAULT_TAIL_BYTES;
|
|
180
|
+
for (const sessionPath of sessionPaths.slice(0, maxSessionFiles)) {
|
|
181
|
+
const hit = readLatestQuotaFromRollout(sessionPath, maxTailBytes, options.source ?? 'session');
|
|
182
|
+
if (hit)
|
|
183
|
+
return hit.quota;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function resolveCodexQuotaFromLocalSources(options = {}) {
|
|
188
|
+
const account = options.activeAccount !== undefined ? options.activeAccount : (0, codexProfiles_1.getActiveCodexAccount)();
|
|
189
|
+
const readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
190
|
+
const writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
191
|
+
const maxTailBytes = options.maxTailBytes ?? DEFAULT_TAIL_BYTES;
|
|
192
|
+
const maxSessionFiles = options.maxSessionFiles ?? DEFAULT_MAX_SESSION_FILES;
|
|
193
|
+
let ownProvider = false;
|
|
194
|
+
const provider = options.provider ?? (() => {
|
|
195
|
+
ownProvider = true;
|
|
196
|
+
return new codex_1.CodexProvider();
|
|
197
|
+
})();
|
|
198
|
+
try {
|
|
199
|
+
const workspaceSessions = options.workspacePath ? provider.findAllSessions(options.workspacePath) : [];
|
|
200
|
+
const workspaceQuota = readLatestCodexQuotaFromRollouts(workspaceSessions, { maxTailBytes, maxSessionFiles });
|
|
201
|
+
if (workspaceQuota) {
|
|
202
|
+
if (account)
|
|
203
|
+
writeSnapshot('codex', account.id, workspaceQuota);
|
|
204
|
+
return enrichCodexQuota(workspaceQuota, account);
|
|
205
|
+
}
|
|
206
|
+
const codexHome = options.codexHome ?? (0, codexProfiles_1.resolveSidekickCodexHome)();
|
|
207
|
+
const accountSessions = findRolloutFiles(path.join(codexHome, 'sessions'));
|
|
208
|
+
const accountQuota = readLatestCodexQuotaFromRollouts(accountSessions, { maxTailBytes, maxSessionFiles });
|
|
209
|
+
if (accountQuota) {
|
|
210
|
+
if (account)
|
|
211
|
+
writeSnapshot('codex', account.id, accountQuota);
|
|
212
|
+
return enrichCodexQuota(accountQuota, account);
|
|
213
|
+
}
|
|
214
|
+
const cached = account ? readSnapshot('codex', account.id) : null;
|
|
215
|
+
if (cached) {
|
|
216
|
+
return enrichCodexQuota({
|
|
217
|
+
...cached,
|
|
218
|
+
providerId: 'codex',
|
|
219
|
+
source: 'cache',
|
|
220
|
+
stale: true,
|
|
221
|
+
fiveHourLabel: cached.fiveHourLabel ?? 'Primary',
|
|
222
|
+
sevenDayLabel: cached.sevenDayLabel ?? 'Secondary',
|
|
223
|
+
}, account);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
if (ownProvider)
|
|
228
|
+
provider.dispose();
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
async function resolveCodexQuota(options = {}) {
|
|
233
|
+
const source = options.source ?? 'local';
|
|
234
|
+
const account = options.activeAccount !== undefined ? options.activeAccount : (0, codexProfiles_1.getActiveCodexAccount)();
|
|
235
|
+
const writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
236
|
+
if (source === 'api') {
|
|
237
|
+
const apiQuota = await fetchCodexQuotaFromApi(options);
|
|
238
|
+
if (apiQuota.available) {
|
|
239
|
+
if (account)
|
|
240
|
+
writeSnapshot('codex', account.id, apiQuota);
|
|
241
|
+
return enrichCodexQuota(apiQuota, account);
|
|
242
|
+
}
|
|
243
|
+
const fallback = resolveCodexQuotaFromLocalSources(options);
|
|
244
|
+
return fallback ?? enrichCodexQuota(apiQuota, account);
|
|
245
|
+
}
|
|
246
|
+
const local = resolveCodexQuotaFromLocalSources(options);
|
|
247
|
+
if (local)
|
|
248
|
+
return local;
|
|
249
|
+
if (source === 'auto') {
|
|
250
|
+
const apiQuota = await fetchCodexQuotaFromApi(options);
|
|
251
|
+
if (apiQuota.available && account) {
|
|
252
|
+
writeSnapshot('codex', account.id, apiQuota);
|
|
253
|
+
}
|
|
254
|
+
return enrichCodexQuota(apiQuota, account);
|
|
255
|
+
}
|
|
256
|
+
return unavailableCodexQuota(account
|
|
257
|
+
? `No Codex rate-limit data is available for "${account.label ?? account.id}".`
|
|
258
|
+
: 'No Codex rate-limit data is available.', account, { source: 'session' });
|
|
259
|
+
}
|
|
260
|
+
async function fetchCodexQuotaFromApi(options = {}) {
|
|
261
|
+
const capturedAt = options.capturedAt ?? new Date().toISOString();
|
|
262
|
+
const accessToken = options.accessToken ?? readCodexAccessToken(options.codexHome ?? (0, codexProfiles_1.resolveSidekickCodexHome)());
|
|
263
|
+
if (!accessToken) {
|
|
264
|
+
return {
|
|
265
|
+
fiveHour: { utilization: 0, resetsAt: '' },
|
|
266
|
+
sevenDay: { utilization: 0, resetsAt: '' },
|
|
267
|
+
available: false,
|
|
268
|
+
error: 'Codex API refresh requires a ChatGPT login.',
|
|
269
|
+
failureKind: 'auth',
|
|
270
|
+
providerId: 'codex',
|
|
271
|
+
source: 'api',
|
|
272
|
+
capturedAt,
|
|
273
|
+
fiveHourLabel: 'Primary',
|
|
274
|
+
sevenDayLabel: 'Secondary',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
279
|
+
const response = await fetchImpl(options.usageUrl ?? CHATGPT_USAGE_URL, {
|
|
280
|
+
method: 'GET',
|
|
281
|
+
headers: {
|
|
282
|
+
Authorization: `Bearer ${accessToken}`,
|
|
283
|
+
Accept: 'application/json',
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
return {
|
|
288
|
+
fiveHour: { utilization: 0, resetsAt: '' },
|
|
289
|
+
sevenDay: { utilization: 0, resetsAt: '' },
|
|
290
|
+
available: false,
|
|
291
|
+
error: `Codex usage API error: ${response.status}`,
|
|
292
|
+
failureKind: response.status === 401 || response.status === 403
|
|
293
|
+
? 'auth'
|
|
294
|
+
: response.status === 429
|
|
295
|
+
? 'rate_limit'
|
|
296
|
+
: response.status >= 500 && response.status <= 599
|
|
297
|
+
? 'server'
|
|
298
|
+
: 'unknown',
|
|
299
|
+
httpStatus: response.status,
|
|
300
|
+
retryAfterMs: response.status === 429 ? parseRetryAfterMs(response.headers.get('retry-after')) : undefined,
|
|
301
|
+
providerId: 'codex',
|
|
302
|
+
source: 'api',
|
|
303
|
+
capturedAt,
|
|
304
|
+
fiveHourLabel: 'Primary',
|
|
305
|
+
sevenDayLabel: 'Secondary',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const payload = await response.json();
|
|
309
|
+
const quota = quotaFromCodexRateLimits(rateLimitsFromUsagePayload(payload), 'api', capturedAt);
|
|
310
|
+
if (!quota) {
|
|
311
|
+
return {
|
|
312
|
+
fiveHour: { utilization: 0, resetsAt: '' },
|
|
313
|
+
sevenDay: { utilization: 0, resetsAt: '' },
|
|
314
|
+
available: false,
|
|
315
|
+
error: 'Codex usage API returned no rate-limit windows.',
|
|
316
|
+
failureKind: 'unknown',
|
|
317
|
+
providerId: 'codex',
|
|
318
|
+
source: 'api',
|
|
319
|
+
capturedAt,
|
|
320
|
+
fiveHourLabel: 'Primary',
|
|
321
|
+
sevenDayLabel: 'Secondary',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return quota;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return {
|
|
328
|
+
fiveHour: { utilization: 0, resetsAt: '' },
|
|
329
|
+
sevenDay: { utilization: 0, resetsAt: '' },
|
|
330
|
+
available: false,
|
|
331
|
+
error: 'Codex usage API network error',
|
|
332
|
+
failureKind: 'network',
|
|
333
|
+
providerId: 'codex',
|
|
334
|
+
source: 'api',
|
|
335
|
+
capturedAt,
|
|
336
|
+
fiveHourLabel: 'Primary',
|
|
337
|
+
sevenDayLabel: 'Secondary',
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function readCodexAccessToken(codexHome) {
|
|
342
|
+
try {
|
|
343
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(codexHome, 'auth.json'), 'utf8'));
|
|
344
|
+
if (parsed.OPENAI_API_KEY || parsed.auth_mode === 'api_key')
|
|
345
|
+
return null;
|
|
346
|
+
return parsed.tokens?.access_token || null;
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function readLatestQuotaFromRollout(sessionPath, maxTailBytes, source) {
|
|
353
|
+
let fd = null;
|
|
354
|
+
try {
|
|
355
|
+
const stat = fs.statSync(sessionPath);
|
|
356
|
+
if (!stat.isFile() || stat.size <= 0)
|
|
357
|
+
return null;
|
|
358
|
+
const start = Math.max(0, stat.size - maxTailBytes);
|
|
359
|
+
const bytesToRead = stat.size - start;
|
|
360
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
361
|
+
fd = fs.openSync(sessionPath, 'r');
|
|
362
|
+
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, start);
|
|
363
|
+
fs.closeSync(fd);
|
|
364
|
+
fd = null;
|
|
365
|
+
let text = buffer.toString('utf8', 0, bytesRead);
|
|
366
|
+
if (start > 0) {
|
|
367
|
+
const firstNewline = text.indexOf('\n');
|
|
368
|
+
text = firstNewline >= 0 ? text.slice(firstNewline + 1) : '';
|
|
369
|
+
}
|
|
370
|
+
const lines = text.split('\n');
|
|
371
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
372
|
+
const line = lines[i].trim();
|
|
373
|
+
if (!line || !line.includes('rate_limits'))
|
|
374
|
+
continue;
|
|
375
|
+
try {
|
|
376
|
+
const parsed = JSON.parse(line);
|
|
377
|
+
if (parsed.type !== 'event_msg' || parsed.payload?.type !== 'token_count')
|
|
378
|
+
continue;
|
|
379
|
+
const quota = quotaFromCodexRateLimits(parsed.payload.rate_limits, source, parsed.timestamp ?? new Date(stat.mtime).toISOString());
|
|
380
|
+
if (quota)
|
|
381
|
+
return { quota, filePath: sessionPath };
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// Ignore malformed or partial lines.
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
if (fd !== null) {
|
|
393
|
+
try {
|
|
394
|
+
fs.closeSync(fd);
|
|
395
|
+
}
|
|
396
|
+
catch { /* ignore */ }
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
function findRolloutFiles(sessionsDir) {
|
|
402
|
+
const results = [];
|
|
403
|
+
function visit(dir) {
|
|
404
|
+
let entries;
|
|
405
|
+
try {
|
|
406
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (const entry of entries) {
|
|
412
|
+
const fullPath = path.join(dir, entry.name);
|
|
413
|
+
if (entry.isDirectory()) {
|
|
414
|
+
visit(fullPath);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (!entry.isFile() || !entry.name.startsWith('rollout-') || !entry.name.endsWith('.jsonl'))
|
|
418
|
+
continue;
|
|
419
|
+
try {
|
|
420
|
+
const stat = fs.statSync(fullPath);
|
|
421
|
+
if (stat.size > 0) {
|
|
422
|
+
results.push({ path: fullPath, mtime: stat.mtime.getTime() });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Skip inaccessible files.
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
visit(sessionsDir);
|
|
431
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
432
|
+
return results.map(item => item.path);
|
|
433
|
+
}
|
|
@@ -11,6 +11,8 @@ type SnapshotWriter = (providerId: 'codex', accountId: string, quota: QuotaState
|
|
|
11
11
|
type WatchFile = (filename: fs.PathLike, listener: fs.WatchListener<string>) => FSWatcher;
|
|
12
12
|
export interface CodexQuotaWatcherOptions {
|
|
13
13
|
discoveryPollIntervalMs?: number;
|
|
14
|
+
maxTailBytes?: number;
|
|
15
|
+
maxSessionFiles?: number;
|
|
14
16
|
providerFactory?: () => CodexProvider;
|
|
15
17
|
getActiveAccount?: CodexAccountReader;
|
|
16
18
|
readSnapshot?: SnapshotReader;
|
|
@@ -29,6 +31,8 @@ export declare class CodexQuotaWatcher implements Disposable {
|
|
|
29
31
|
private readonly readSnapshot;
|
|
30
32
|
private readonly writeSnapshot;
|
|
31
33
|
private readonly watchFile;
|
|
34
|
+
private readonly maxTailBytes;
|
|
35
|
+
private readonly maxSessionFiles;
|
|
32
36
|
private readonly listeners;
|
|
33
37
|
private discoveryTimer;
|
|
34
38
|
private provider;
|
|
@@ -78,6 +78,8 @@ class CodexQuotaWatcher {
|
|
|
78
78
|
readSnapshot;
|
|
79
79
|
writeSnapshot;
|
|
80
80
|
watchFile;
|
|
81
|
+
maxTailBytes;
|
|
82
|
+
maxSessionFiles;
|
|
81
83
|
listeners = [];
|
|
82
84
|
discoveryTimer;
|
|
83
85
|
provider = null;
|
|
@@ -94,6 +96,8 @@ class CodexQuotaWatcher {
|
|
|
94
96
|
this.readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
95
97
|
this.writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
96
98
|
this.watchFile = options.watchFile ?? fs.watch;
|
|
99
|
+
this.maxTailBytes = options.maxTailBytes;
|
|
100
|
+
this.maxSessionFiles = options.maxSessionFiles;
|
|
97
101
|
}
|
|
98
102
|
start() {
|
|
99
103
|
if (this.running)
|
|
@@ -201,6 +205,29 @@ class CodexQuotaWatcher {
|
|
|
201
205
|
}
|
|
202
206
|
emitCachedOrUnavailable() {
|
|
203
207
|
const account = this.getActiveAccount();
|
|
208
|
+
let localProvider = null;
|
|
209
|
+
try {
|
|
210
|
+
localProvider = this.providerFactory();
|
|
211
|
+
const local = (0, codexQuota_1.resolveCodexQuotaFromLocalSources)({
|
|
212
|
+
workspacePath: this.workspacePath,
|
|
213
|
+
activeAccount: account,
|
|
214
|
+
readSnapshot: this.readSnapshot,
|
|
215
|
+
writeSnapshot: this.writeSnapshot,
|
|
216
|
+
provider: localProvider,
|
|
217
|
+
maxTailBytes: this.maxTailBytes,
|
|
218
|
+
maxSessionFiles: this.maxSessionFiles,
|
|
219
|
+
});
|
|
220
|
+
if (local) {
|
|
221
|
+
this.emitState(local);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Fall through to account-scoped cache or unavailable state.
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
localProvider?.dispose();
|
|
230
|
+
}
|
|
204
231
|
const cached = account ? this.readSnapshot('codex', account.id) : null;
|
|
205
232
|
if (cached) {
|
|
206
233
|
this.emitState(enrichQuotaState({
|
package/dist/index.d.ts
CHANGED
|
@@ -104,7 +104,8 @@ export type { QuotaFailureDescriptor } from './quotaPresentation';
|
|
|
104
104
|
export { QuotaPoller } from './quotaPoller';
|
|
105
105
|
export type { QuotaPollerOptions } from './quotaPoller';
|
|
106
106
|
export { readQuotaSnapshot, writeQuotaSnapshot } from './quotaSnapshots';
|
|
107
|
-
export { quotaFromCodexRateLimits } from './codexQuota';
|
|
107
|
+
export { fetchCodexQuotaFromApi, quotaFromCodexRateLimits, readLatestCodexQuotaFromRollouts, resolveCodexQuota, resolveCodexQuotaFromLocalSources, } from './codexQuota';
|
|
108
|
+
export type { CodexQuotaApiOptions, CodexQuotaCreditsSnapshot, CodexQuotaResolveOptions, CodexQuotaResolveSource, } from './codexQuota';
|
|
108
109
|
export { CodexQuotaWatcher } from './codexQuotaWatcher';
|
|
109
110
|
export type { CodexQuotaWatcherOptions } from './codexQuotaWatcher';
|
|
110
111
|
export { MultiProviderQuotaService } from './multiProviderQuotaService';
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = void 0;
|
|
7
7
|
exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.formatTokenCount = exports.formatDurationMs = exports.createJsonlTail = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = void 0;
|
|
8
|
-
exports.
|
|
9
|
-
exports.fetchPeakHoursStatus = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = void 0;
|
|
8
|
+
exports.fetchCodexQuotaFromApi = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.isMultiAccountEnabled = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = exports.readAccountRegistry = exports.ensureDefaultAccounts = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = void 0;
|
|
9
|
+
exports.fetchPeakHoursStatus = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = void 0;
|
|
10
10
|
var taskPersistence_1 = require("./types/taskPersistence");
|
|
11
11
|
Object.defineProperty(exports, "TASK_PERSISTENCE_SCHEMA_VERSION", { enumerable: true, get: function () { return taskPersistence_1.TASK_PERSISTENCE_SCHEMA_VERSION; } });
|
|
12
12
|
Object.defineProperty(exports, "normalizeTaskStatus", { enumerable: true, get: function () { return taskPersistence_1.normalizeTaskStatus; } });
|
|
@@ -238,7 +238,11 @@ var quotaSnapshots_1 = require("./quotaSnapshots");
|
|
|
238
238
|
Object.defineProperty(exports, "readQuotaSnapshot", { enumerable: true, get: function () { return quotaSnapshots_1.readQuotaSnapshot; } });
|
|
239
239
|
Object.defineProperty(exports, "writeQuotaSnapshot", { enumerable: true, get: function () { return quotaSnapshots_1.writeQuotaSnapshot; } });
|
|
240
240
|
var codexQuota_1 = require("./codexQuota");
|
|
241
|
+
Object.defineProperty(exports, "fetchCodexQuotaFromApi", { enumerable: true, get: function () { return codexQuota_1.fetchCodexQuotaFromApi; } });
|
|
241
242
|
Object.defineProperty(exports, "quotaFromCodexRateLimits", { enumerable: true, get: function () { return codexQuota_1.quotaFromCodexRateLimits; } });
|
|
243
|
+
Object.defineProperty(exports, "readLatestCodexQuotaFromRollouts", { enumerable: true, get: function () { return codexQuota_1.readLatestCodexQuotaFromRollouts; } });
|
|
244
|
+
Object.defineProperty(exports, "resolveCodexQuota", { enumerable: true, get: function () { return codexQuota_1.resolveCodexQuota; } });
|
|
245
|
+
Object.defineProperty(exports, "resolveCodexQuotaFromLocalSources", { enumerable: true, get: function () { return codexQuota_1.resolveCodexQuotaFromLocalSources; } });
|
|
242
246
|
var codexQuotaWatcher_1 = require("./codexQuotaWatcher");
|
|
243
247
|
Object.defineProperty(exports, "CodexQuotaWatcher", { enumerable: true, get: function () { return codexQuotaWatcher_1.CodexQuotaWatcher; } });
|
|
244
248
|
var multiProviderQuotaService_1 = require("./multiProviderQuotaService");
|
|
@@ -45,7 +45,7 @@ class CodexDatabase {
|
|
|
45
45
|
dbPath;
|
|
46
46
|
sqlite3Available = null;
|
|
47
47
|
constructor(codexHome) {
|
|
48
|
-
this.dbPath = path.join(codexHome, 'state.sqlite');
|
|
48
|
+
this.dbPath = findLatestStateDatabase(codexHome) ?? path.join(codexHome, 'state.sqlite');
|
|
49
49
|
}
|
|
50
50
|
isAvailable() {
|
|
51
51
|
try {
|
|
@@ -138,6 +138,31 @@ class CodexDatabase {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
exports.CodexDatabase = CodexDatabase;
|
|
141
|
+
function findLatestStateDatabase(codexHome) {
|
|
142
|
+
try {
|
|
143
|
+
const entries = fs.readdirSync(codexHome, { withFileTypes: true });
|
|
144
|
+
const candidates = [];
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (!entry.isFile() || !/^state(?:_\d+)?\.sqlite$/.test(entry.name))
|
|
147
|
+
continue;
|
|
148
|
+
const dbPath = path.join(codexHome, entry.name);
|
|
149
|
+
try {
|
|
150
|
+
const stat = fs.statSync(dbPath);
|
|
151
|
+
if (stat.size > 0) {
|
|
152
|
+
candidates.push({ path: dbPath, mtime: stat.mtime.getTime() });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Skip inaccessible candidates.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
candidates.sort((a, b) => b.mtime - a.mtime);
|
|
160
|
+
return candidates[0]?.path ?? null;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
141
166
|
function normalizePath(input) {
|
|
142
167
|
try {
|
|
143
168
|
return fs.realpathSync(input);
|
package/dist/providers/detect.js
CHANGED
|
@@ -58,6 +58,35 @@ function getOpenCodeDataDir() {
|
|
|
58
58
|
function getCodexHomes() {
|
|
59
59
|
return (0, codexProfiles_1.getCodexMonitoringHomes)();
|
|
60
60
|
}
|
|
61
|
+
function hasCodexStateDb(codexHome) {
|
|
62
|
+
try {
|
|
63
|
+
return fs.readdirSync(codexHome).some(entry => /^state(?:_\d+)?\.sqlite$/.test(entry));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getCodexStateDbMtime(codexHome) {
|
|
70
|
+
try {
|
|
71
|
+
let latest = 0;
|
|
72
|
+
for (const entry of fs.readdirSync(codexHome)) {
|
|
73
|
+
if (!/^state(?:_\d+)?\.sqlite$/.test(entry))
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const mtime = fs.statSync(path.join(codexHome, entry)).mtime.getTime();
|
|
77
|
+
if (mtime > latest)
|
|
78
|
+
latest = mtime;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Skip inaccessible files.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return latest;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
61
90
|
function getMostRecentMtime(dir) {
|
|
62
91
|
try {
|
|
63
92
|
if (!fs.existsSync(dir))
|
|
@@ -97,13 +126,9 @@ function getOpenCodeActivityMtime() {
|
|
|
97
126
|
function getCodexActivityMtime() {
|
|
98
127
|
let latest = 0;
|
|
99
128
|
for (const codexHome of getCodexHomes()) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (dbMtime > latest)
|
|
104
|
-
latest = dbMtime;
|
|
105
|
-
}
|
|
106
|
-
catch { /* no DB */ }
|
|
129
|
+
const dbMtime = getCodexStateDbMtime(codexHome);
|
|
130
|
+
if (dbMtime > latest)
|
|
131
|
+
latest = dbMtime;
|
|
107
132
|
const sessionsMtime = getMostRecentMtime(path.join(codexHome, 'sessions'));
|
|
108
133
|
if (sessionsMtime > latest)
|
|
109
134
|
latest = sessionsMtime;
|
|
@@ -129,7 +154,7 @@ function getAllDetectedProviders() {
|
|
|
129
154
|
const { claudeBase, openCodeDbPath, openCodeStorageDir, codexHomes } = getProviderPaths();
|
|
130
155
|
const hasClaude = fs.existsSync(claudeBase);
|
|
131
156
|
const hasOpenCode = fs.existsSync(openCodeStorageDir) || fs.existsSync(openCodeDbPath);
|
|
132
|
-
const hasCodex = codexHomes.some(codexHome => fs.existsSync(path.join(codexHome, 'sessions')) ||
|
|
157
|
+
const hasCodex = codexHomes.some(codexHome => fs.existsSync(path.join(codexHome, 'sessions')) || hasCodexStateDb(codexHome));
|
|
133
158
|
const available = [];
|
|
134
159
|
if (hasClaude)
|
|
135
160
|
available.push({ id: 'claude-code', mtime: getMostRecentMtime(claudeBase) });
|
|
@@ -150,7 +175,7 @@ function detectProvider(override) {
|
|
|
150
175
|
const { claudeBase, openCodeDbPath, openCodeStorageDir, codexHomes } = getProviderPaths();
|
|
151
176
|
const hasClaude = fs.existsSync(claudeBase);
|
|
152
177
|
const hasOpenCode = fs.existsSync(openCodeStorageDir) || fs.existsSync(openCodeDbPath);
|
|
153
|
-
const hasCodex = codexHomes.some(codexHome => fs.existsSync(path.join(codexHome, 'sessions')) ||
|
|
178
|
+
const hasCodex = codexHomes.some(codexHome => fs.existsSync(path.join(codexHome, 'sessions')) || hasCodexStateDb(codexHome));
|
|
154
179
|
const available = [];
|
|
155
180
|
if (hasClaude) {
|
|
156
181
|
available.push({ id: 'claude-code', mtime: getMostRecentMtime(claudeBase) });
|
package/dist/quota.d.ts
CHANGED
|
@@ -39,6 +39,16 @@ export interface QuotaState {
|
|
|
39
39
|
fiveHourLabel?: string;
|
|
40
40
|
/** Provider-specific display label for the second window */
|
|
41
41
|
sevenDayLabel?: string;
|
|
42
|
+
/** Provider-specific rate-limit identifier */
|
|
43
|
+
limitId?: string;
|
|
44
|
+
/** Provider-specific rate-limit display name */
|
|
45
|
+
limitName?: string;
|
|
46
|
+
/** Provider-specific credits snapshot */
|
|
47
|
+
credits?: unknown;
|
|
48
|
+
/** Provider-specific plan type */
|
|
49
|
+
planType?: string;
|
|
50
|
+
/** Provider-specific rate-limit reached reason */
|
|
51
|
+
rateLimitReachedType?: string;
|
|
42
52
|
}
|
|
43
53
|
/**
|
|
44
54
|
* Fetch current quota utilization from the Anthropic OAuth usage API.
|
package/dist/types/codex.d.ts
CHANGED
|
@@ -180,14 +180,17 @@ export interface CodexRateLimits {
|
|
|
180
180
|
limit_name?: string | null;
|
|
181
181
|
primary?: {
|
|
182
182
|
used_percent: number;
|
|
183
|
-
window_minutes
|
|
184
|
-
resets_at
|
|
183
|
+
window_minutes?: number | null;
|
|
184
|
+
resets_at?: number | null;
|
|
185
185
|
};
|
|
186
186
|
secondary?: {
|
|
187
187
|
used_percent: number;
|
|
188
|
-
window_minutes
|
|
189
|
-
resets_at
|
|
188
|
+
window_minutes?: number | null;
|
|
189
|
+
resets_at?: number | null;
|
|
190
190
|
};
|
|
191
|
+
credits?: unknown;
|
|
192
|
+
plan_type?: string;
|
|
193
|
+
rate_limit_reached_type?: string;
|
|
191
194
|
}
|
|
192
195
|
export interface CodexTokenUsage {
|
|
193
196
|
input_tokens: number;
|
|
@@ -398,13 +398,17 @@ class JsonlSessionWatcher {
|
|
|
398
398
|
if (evtType === 'token_count') {
|
|
399
399
|
const info = payload?.info;
|
|
400
400
|
const usage = (info?.last_token_usage || info?.total_token_usage);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
401
|
+
const rl = payload?.rate_limits;
|
|
402
|
+
const rateLimits = rl ? extractRateLimits(rl) : undefined;
|
|
403
|
+
if (usage || rateLimits) {
|
|
404
404
|
events.push({
|
|
405
405
|
providerId: 'codex', type: 'system', timestamp: ts,
|
|
406
|
-
summary:
|
|
407
|
-
|
|
406
|
+
summary: usage
|
|
407
|
+
? `Tokens: ${usage.input_tokens ?? 0} in / ${usage.output_tokens ?? 0} out`
|
|
408
|
+
: 'Rate limits updated',
|
|
409
|
+
tokens: usage
|
|
410
|
+
? { input: usage.input_tokens || 0, output: usage.output_tokens || 0 }
|
|
411
|
+
: undefined,
|
|
408
412
|
rateLimits,
|
|
409
413
|
raw,
|
|
410
414
|
});
|
package/package.json
CHANGED