sidekick-shared 0.24.3 → 0.24.5
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/CHANGELOG.md +436 -0
- package/LICENSE +21 -0
- package/README.md +172 -60
- package/dist/accountManager.d.ts +13 -0
- package/dist/accountManager.js +60 -32
- package/dist/accountRegistry.js +59 -41
- package/dist/accounts.js +40 -19
- package/dist/autoSwitch.d.ts +2 -1
- package/dist/autoSwitch.js +32 -7
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +3 -1
- package/dist/codexProfiles.d.ts +14 -0
- package/dist/codexProfiles.js +268 -116
- package/dist/context/sessionContext.js +2 -4
- package/dist/ensureDefaultAccounts.js +4 -4
- package/dist/index.d.ts +14 -7
- package/dist/index.js +33 -7
- package/dist/modelInfo.js +11 -2
- package/dist/node.d.ts +6 -0
- package/dist/node.js +8 -1
- package/dist/observedContextWindows.d.ts +20 -4
- package/dist/observedContextWindows.js +47 -7
- package/dist/parsers/codexHistory.d.ts +30 -0
- package/dist/parsers/codexHistory.js +118 -0
- package/dist/parsers/mcpToolName.d.ts +26 -0
- package/dist/parsers/mcpToolName.js +31 -0
- package/dist/parsers/sessionPathResolver.d.ts +11 -4
- package/dist/parsers/sessionPathResolver.js +11 -4
- package/dist/paths.d.ts +43 -4
- package/dist/paths.js +66 -6
- package/dist/peakHours.d.ts +16 -2
- package/dist/peakHours.js +18 -3
- package/dist/pricingCatalog.d.ts +7 -3
- package/dist/pricingCatalog.js +3 -2
- package/dist/providers/claudeCode.d.ts +2 -1
- package/dist/providers/claudeCode.js +18 -0
- package/dist/providers/codex.d.ts +22 -1
- package/dist/providers/codex.js +58 -0
- package/dist/providers/types.d.ts +5 -0
- package/dist/quotaHistory.d.ts +1 -1
- package/dist/quotaHistory.js +2 -2
- package/dist/quotaSnapshots.js +8 -77
- package/dist/sessionPreviews.d.ts +60 -0
- package/dist/sessionPreviews.js +234 -0
- package/dist/sessionTranscripts.js +10 -4
- package/dist/writers/atomic.d.ts +20 -0
- package/dist/writers/atomic.js +190 -19
- package/dist/zaiQuota.d.ts +87 -0
- package/dist/zaiQuota.js +49 -0
- package/dist/zaiQuotaWatcher.d.ts +7 -0
- package/dist/zaiQuotaWatcher.js +3 -0
- package/package.json +60 -2
package/dist/writers/atomic.js
CHANGED
|
@@ -35,11 +35,42 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.atomicWriteJson = atomicWriteJson;
|
|
37
37
|
exports.atomicWriteJsonSync = atomicWriteJsonSync;
|
|
38
|
+
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
38
39
|
exports.updateJsonStoreAtomic = updateJsonStoreAtomic;
|
|
40
|
+
exports.withFileLockSync = withFileLockSync;
|
|
41
|
+
exports.updateJsonStoreAtomicSync = updateJsonStoreAtomicSync;
|
|
39
42
|
const crypto = __importStar(require("crypto"));
|
|
40
43
|
const fs = __importStar(require("fs"));
|
|
41
44
|
const path = __importStar(require("path"));
|
|
42
45
|
const LOCK_STALE_MS = 10_000;
|
|
46
|
+
/**
|
|
47
|
+
* How long to wait for a lock that is making no progress.
|
|
48
|
+
*
|
|
49
|
+
* This budget detects a wedged holder, not a long queue. It is measured from the
|
|
50
|
+
* last time the lock changed hands rather than from the first attempt, because a
|
|
51
|
+
* writer queued behind other healthy writers is waiting, not failing — see
|
|
52
|
+
* {@link acquireLock}.
|
|
53
|
+
*/
|
|
54
|
+
const LOCK_NO_PROGRESS_MS = 3_000;
|
|
55
|
+
/**
|
|
56
|
+
* A live async holder heartbeats every LOCK_STALE_MS / 3; an mtime frozen this
|
|
57
|
+
* long means the heartbeat timer is gone — the owner is dead (possibly with its
|
|
58
|
+
* PID recycled by the OS) or wedged beyond recovery. Reclaim regardless of the
|
|
59
|
+
* PID probe, which cannot tell a recycled PID from a live owner. Sync holders
|
|
60
|
+
* have no heartbeat; they usually hold locks for milliseconds, and even the
|
|
61
|
+
* slowest (a keychain read through `security` behind the auth-swap lock, up to
|
|
62
|
+
* a few seconds) stays far under this ceiling.
|
|
63
|
+
*/
|
|
64
|
+
const LOCK_ABANDONED_MS = 120_000;
|
|
65
|
+
/**
|
|
66
|
+
* Hard ceiling on how long a synchronous waiter blocks, regardless of queue
|
|
67
|
+
* progress. Progress resets are correct for async waiters, but a sync waiter
|
|
68
|
+
* blocks its process's event loop while queued, so healthy-queue churn must not
|
|
69
|
+
* extend the block indefinitely.
|
|
70
|
+
*/
|
|
71
|
+
const SYNC_LOCK_MAX_WAIT_MS = 15_000;
|
|
72
|
+
const LOCK_WAIT_INTERVAL_MS = 15;
|
|
73
|
+
const lockSleepView = new Int32Array(new SharedArrayBuffer(4));
|
|
43
74
|
async function syncDirectory(directory) {
|
|
44
75
|
let handle;
|
|
45
76
|
try {
|
|
@@ -91,12 +122,16 @@ async function atomicWriteJson(filePath, value) {
|
|
|
91
122
|
}
|
|
92
123
|
}
|
|
93
124
|
function atomicWriteJsonSync(filePath, value) {
|
|
125
|
+
atomicWriteFileSync(filePath, JSON.stringify(value, null, 2));
|
|
126
|
+
}
|
|
127
|
+
/** Raw-string sibling of {@link atomicWriteJsonSync} for non-JSON payloads. */
|
|
128
|
+
function atomicWriteFileSync(filePath, content, mode = 0o600) {
|
|
94
129
|
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
95
130
|
const tempPath = `${filePath}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`;
|
|
96
131
|
let descriptor;
|
|
97
132
|
try {
|
|
98
|
-
descriptor = fs.openSync(tempPath, 'wx',
|
|
99
|
-
fs.writeFileSync(descriptor,
|
|
133
|
+
descriptor = fs.openSync(tempPath, 'wx', mode);
|
|
134
|
+
fs.writeFileSync(descriptor, content, { encoding: 'utf8' });
|
|
100
135
|
fs.fsyncSync(descriptor);
|
|
101
136
|
fs.closeSync(descriptor);
|
|
102
137
|
descriptor = undefined;
|
|
@@ -142,8 +177,22 @@ async function updateJsonStoreAtomic(filePath, createEmpty, update) {
|
|
|
142
177
|
await releaseLock(lockPath, lock);
|
|
143
178
|
}
|
|
144
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Waits for exclusive ownership of `lockPath`.
|
|
182
|
+
*
|
|
183
|
+
* The wait budget is reset every time the lock changes hands. A flat budget from
|
|
184
|
+
* the first attempt conflated two unrelated conditions — a wedged holder and a
|
|
185
|
+
* queue of healthy writers — so under real contention the writers at the back of
|
|
186
|
+
* the queue failed even though every lock was being acquired and released
|
|
187
|
+
* correctly. Each cycle costs three fsyncs (lock file, temp file, directory), so
|
|
188
|
+
* on slow storage a burst of concurrent captures drained slower than the budget.
|
|
189
|
+
*
|
|
190
|
+
* A stuck holder keeps the same token, so it makes no progress and still trips
|
|
191
|
+
* the timeout; the stale-reclaim path below still handles an owner that died.
|
|
192
|
+
*/
|
|
145
193
|
async function acquireLock(lockPath) {
|
|
146
|
-
|
|
194
|
+
let deadline = Date.now() + LOCK_NO_PROGRESS_MS;
|
|
195
|
+
let observedOwner = null;
|
|
147
196
|
while (true) {
|
|
148
197
|
try {
|
|
149
198
|
const handle = await fs.promises.open(lockPath, 'wx', 0o600);
|
|
@@ -159,29 +208,27 @@ async function acquireLock(lockPath) {
|
|
|
159
208
|
catch (error) {
|
|
160
209
|
if (error.code !== 'EEXIST')
|
|
161
210
|
throw error;
|
|
211
|
+
// The token identifies the current holder. mtime cannot stand in for it:
|
|
212
|
+
// the heartbeat keeps touching the file, so even a wedged holder looks
|
|
213
|
+
// fresh. A different token means the queue advanced.
|
|
214
|
+
const currentOwner = await fs.promises.readFile(lockPath, 'utf8').catch(() => null);
|
|
215
|
+
if (currentOwner !== null && currentOwner !== observedOwner) {
|
|
216
|
+
observedOwner = currentOwner;
|
|
217
|
+
deadline = Date.now() + LOCK_NO_PROGRESS_MS;
|
|
218
|
+
}
|
|
162
219
|
try {
|
|
163
220
|
const stat = await fs.promises.stat(lockPath);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
let ownerAlive = Number.isInteger(ownerPid) && ownerPid > 0;
|
|
168
|
-
if (ownerAlive) {
|
|
169
|
-
try {
|
|
170
|
-
process.kill(ownerPid, 0);
|
|
171
|
-
}
|
|
172
|
-
catch (ownerError) {
|
|
173
|
-
ownerAlive = ownerError.code === 'EPERM';
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (!ownerAlive)
|
|
177
|
-
await fs.promises.rm(lockPath, { force: true });
|
|
221
|
+
const frozenMs = Date.now() - stat.mtimeMs;
|
|
222
|
+
if (frozenMs > LOCK_STALE_MS && !lockOwnerAlive(currentOwner, frozenMs)) {
|
|
223
|
+
await fs.promises.rm(lockPath, { force: true });
|
|
178
224
|
}
|
|
179
225
|
}
|
|
180
226
|
catch {
|
|
181
227
|
// The other writer released between checks.
|
|
182
228
|
}
|
|
183
|
-
if (Date.now() >= deadline)
|
|
184
|
-
throw new Error(`Timed out waiting for store lock: ${lockPath}`);
|
|
229
|
+
if (Date.now() >= deadline) {
|
|
230
|
+
throw new Error(`Timed out waiting for store lock: ${lockPath} (no progress for ${LOCK_NO_PROGRESS_MS}ms)`);
|
|
231
|
+
}
|
|
185
232
|
await new Promise((resolve) => setTimeout(resolve, 15));
|
|
186
233
|
}
|
|
187
234
|
}
|
|
@@ -198,3 +245,127 @@ async function releaseLock(lockPath, lock) {
|
|
|
198
245
|
// The lock was already released or replaced; never delete another owner's lock.
|
|
199
246
|
}
|
|
200
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Whether the recorded owner of a stale lock should still be treated as alive.
|
|
250
|
+
* An mtime frozen past {@link LOCK_ABANDONED_MS} overrides the PID probe — see
|
|
251
|
+
* the constant for why the probe alone cannot be trusted at that age.
|
|
252
|
+
*/
|
|
253
|
+
function lockOwnerAlive(owner, frozenMs) {
|
|
254
|
+
if (frozenMs > LOCK_ABANDONED_MS)
|
|
255
|
+
return false;
|
|
256
|
+
const ownerPid = Number.parseInt((owner ?? '').split(':', 1)[0], 10);
|
|
257
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 0)
|
|
258
|
+
return false;
|
|
259
|
+
try {
|
|
260
|
+
process.kill(ownerPid, 0);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return error.code === 'EPERM';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Synchronous counterpart of {@link acquireLock}, sharing its lock-file
|
|
269
|
+
* protocol so sync and async waiters exclude each other across processes.
|
|
270
|
+
*
|
|
271
|
+
* Two timeout rules apply instead of one: the same progress-based budget as the
|
|
272
|
+
* async path (a token that never changes trips it in LOCK_NO_PROGRESS_MS), plus
|
|
273
|
+
* an absolute {@link SYNC_LOCK_MAX_WAIT_MS} ceiling, because a sync waiter
|
|
274
|
+
* blocks its event loop while queued. Sync holders take no heartbeat — their
|
|
275
|
+
* critical sections must stay well under LOCK_STALE_MS (in-repo users are
|
|
276
|
+
* local-filesystem read-modify-writes, plus keychain reads behind the
|
|
277
|
+
* auth-swap locks that can take a few seconds).
|
|
278
|
+
*
|
|
279
|
+
* The caller must ensure `dirname(lockPath)` exists — a missing parent
|
|
280
|
+
* surfaces as a raw ENOENT, not a timeout.
|
|
281
|
+
*/
|
|
282
|
+
function withFileLockSync(lockPath, operation) {
|
|
283
|
+
const token = acquireLockSync(lockPath);
|
|
284
|
+
try {
|
|
285
|
+
return operation();
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
try {
|
|
289
|
+
if (fs.readFileSync(lockPath, 'utf8') === token)
|
|
290
|
+
fs.rmSync(lockPath, { force: true });
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// The lock was already released or replaced; never delete another owner's lock.
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function acquireLockSync(lockPath) {
|
|
298
|
+
const start = Date.now();
|
|
299
|
+
let deadline = start + LOCK_NO_PROGRESS_MS;
|
|
300
|
+
let observedOwner = null;
|
|
301
|
+
while (true) {
|
|
302
|
+
try {
|
|
303
|
+
const descriptor = fs.openSync(lockPath, 'wx', 0o600);
|
|
304
|
+
const token = `${process.pid}:${crypto.randomBytes(16).toString('hex')}`;
|
|
305
|
+
fs.writeFileSync(descriptor, token, 'utf8');
|
|
306
|
+
fs.fsyncSync(descriptor);
|
|
307
|
+
fs.closeSync(descriptor);
|
|
308
|
+
return token;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (error.code !== 'EEXIST')
|
|
312
|
+
throw error;
|
|
313
|
+
let currentOwner = null;
|
|
314
|
+
try {
|
|
315
|
+
currentOwner = fs.readFileSync(lockPath, 'utf8');
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// The other writer released between checks.
|
|
319
|
+
}
|
|
320
|
+
if (currentOwner !== null && currentOwner !== observedOwner) {
|
|
321
|
+
observedOwner = currentOwner;
|
|
322
|
+
deadline = Date.now() + LOCK_NO_PROGRESS_MS;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const stat = fs.statSync(lockPath);
|
|
326
|
+
const frozenMs = Date.now() - stat.mtimeMs;
|
|
327
|
+
if (frozenMs > LOCK_STALE_MS && !lockOwnerAlive(currentOwner, frozenMs)) {
|
|
328
|
+
fs.rmSync(lockPath, { force: true });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// The other writer released between checks.
|
|
333
|
+
}
|
|
334
|
+
const now = Date.now();
|
|
335
|
+
if (now >= deadline) {
|
|
336
|
+
throw new Error(`Timed out waiting for store lock: ${lockPath} (no progress for ${LOCK_NO_PROGRESS_MS}ms)`);
|
|
337
|
+
}
|
|
338
|
+
if (now - start >= SYNC_LOCK_MAX_WAIT_MS) {
|
|
339
|
+
throw new Error(`Timed out waiting for store lock: ${lockPath} (blocked synchronously for ${SYNC_LOCK_MAX_WAIT_MS}ms)`);
|
|
340
|
+
}
|
|
341
|
+
Atomics.wait(lockSleepView, 0, 0, LOCK_WAIT_INTERVAL_MS);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Synchronous mirror of {@link updateJsonStoreAtomic}. */
|
|
346
|
+
function updateJsonStoreAtomicSync(filePath, createEmpty, update) {
|
|
347
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
348
|
+
return withFileLockSync(`${filePath}.lock`, () => {
|
|
349
|
+
let latest = createEmpty();
|
|
350
|
+
let raw;
|
|
351
|
+
try {
|
|
352
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
if (error.code !== 'ENOENT')
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
if (raw !== undefined) {
|
|
359
|
+
try {
|
|
360
|
+
latest = JSON.parse(raw);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
const corruptPath = `${filePath}.corrupt-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
364
|
+
fs.renameSync(filePath, corruptPath);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const next = update(latest);
|
|
368
|
+
atomicWriteJsonSync(filePath, next);
|
|
369
|
+
return next;
|
|
370
|
+
});
|
|
371
|
+
}
|
package/dist/zaiQuota.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ import type { QuotaState } from './quota';
|
|
|
27
27
|
* "One prompt ≈ one query; each prompt is estimated to invoke the model
|
|
28
28
|
* 15–20 times." We use the midpoint (17.5) when converting observed
|
|
29
29
|
* assistant turns into prompt equivalents.
|
|
30
|
+
*
|
|
31
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
32
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
33
|
+
* for backward compatibility only and drifts from actual billing.
|
|
30
34
|
*/
|
|
31
35
|
export declare const ZAI_TIER_BUDGETS: {
|
|
32
36
|
readonly lite: {
|
|
@@ -42,20 +46,43 @@ export declare const ZAI_TIER_BUDGETS: {
|
|
|
42
46
|
readonly weekly: 8000;
|
|
43
47
|
};
|
|
44
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
51
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
52
|
+
* for backward compatibility only and drifts from actual billing.
|
|
53
|
+
*/
|
|
45
54
|
export type ZaiTier = 'lite' | 'pro' | 'max';
|
|
46
55
|
/**
|
|
47
56
|
* Midpoint of z.ai's documented "15–20 model invocations per prompt" range.
|
|
48
57
|
* Used to convert accumulated assistant turns into prompt equivalents.
|
|
49
58
|
* Calibration note: this constant may need per-tier adjustment after
|
|
50
59
|
* real-world validation against the z.ai web UI.
|
|
60
|
+
*
|
|
61
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
62
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
63
|
+
* for backward compatibility only and drifts from actual billing.
|
|
51
64
|
*/
|
|
52
65
|
export declare const ZAI_PROMPT_INVOCATIONS = 17.5;
|
|
53
66
|
/**
|
|
54
67
|
* Provider IDs that OpenCode tags on z.ai-routed messages.
|
|
55
68
|
* `zai-coding-plan` is the Coding Plan key; `zai` is the bare API key.
|
|
69
|
+
*
|
|
70
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
71
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
72
|
+
* for backward compatibility only and drifts from actual billing.
|
|
56
73
|
*/
|
|
57
74
|
export declare const ZAI_PROVIDER_IDS: readonly ["zai", "zai-coding-plan"];
|
|
75
|
+
/**
|
|
76
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
77
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
78
|
+
* for backward compatibility only and drifts from actual billing.
|
|
79
|
+
*/
|
|
58
80
|
export type ZaiProviderId = (typeof ZAI_PROVIDER_IDS)[number];
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
83
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
84
|
+
* for backward compatibility only and drifts from actual billing.
|
|
85
|
+
*/
|
|
59
86
|
export declare function isZaiProviderId(value: unknown): value is ZaiProviderId;
|
|
60
87
|
/**
|
|
61
88
|
* Minimal description of a z.ai-routed assistant turn needed to accumulate
|
|
@@ -64,6 +91,10 @@ export declare function isZaiProviderId(value: unknown): value is ZaiProviderId;
|
|
|
64
91
|
* provider comparability; cost is omitted (always 0 for z.ai).
|
|
65
92
|
*
|
|
66
93
|
* `timestampMs` is when the assistant turn completed (epoch milliseconds).
|
|
94
|
+
*
|
|
95
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
96
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
97
|
+
* for backward compatibility only and drifts from actual billing.
|
|
67
98
|
*/
|
|
68
99
|
export interface ZaiAssistantTurn {
|
|
69
100
|
timestampMs: number;
|
|
@@ -74,6 +105,11 @@ export interface ZaiAssistantTurn {
|
|
|
74
105
|
cacheWriteTokens: number;
|
|
75
106
|
reasoningTokens: number;
|
|
76
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
110
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
111
|
+
* for backward compatibility only and drifts from actual billing.
|
|
112
|
+
*/
|
|
77
113
|
export interface ZaiAccumulatedUsage {
|
|
78
114
|
/** Number of z.ai assistant turns inside the 5-hour window. */
|
|
79
115
|
fiveHourTurns: number;
|
|
@@ -96,6 +132,10 @@ export interface ZaiAccumulatedUsage {
|
|
|
96
132
|
* Sums the token fields of a single turn into a scalar.
|
|
97
133
|
* Cache reads are weighted at 0.1× (Anthropic convention) to avoid
|
|
98
134
|
* over-counting repeated context. Output and reasoning tokens are weighted 1×.
|
|
135
|
+
*
|
|
136
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
137
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
138
|
+
* for backward compatibility only and drifts from actual billing.
|
|
99
139
|
*/
|
|
100
140
|
export declare function turnTokenWeight(turn: Pick<ZaiAssistantTurn, 'inputTokens' | 'outputTokens' | 'cacheReadTokens' | 'cacheWriteTokens' | 'reasoningTokens'>): number;
|
|
101
141
|
/**
|
|
@@ -104,6 +144,10 @@ export declare function turnTokenWeight(turn: Pick<ZaiAssistantTurn, 'inputToken
|
|
|
104
144
|
* Pure & stateless: the caller is responsible for passing the relevant turns.
|
|
105
145
|
* Turns outside the 7-day window are ignored; turns inside the 7-day window
|
|
106
146
|
* but outside the 5-hour window still count toward weekly usage.
|
|
147
|
+
*
|
|
148
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
149
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
150
|
+
* for backward compatibility only and drifts from actual billing.
|
|
107
151
|
*/
|
|
108
152
|
export declare function accumulateZaiUsage(turns: readonly ZaiAssistantTurn[], nowMs?: number): ZaiAccumulatedUsage;
|
|
109
153
|
/**
|
|
@@ -113,8 +157,17 @@ export declare function accumulateZaiUsage(turns: readonly ZaiAssistantTurn[], n
|
|
|
113
157
|
* the user must be on a higher tier. This under-detects at the start of a
|
|
114
158
|
* weekly cycle (a Max user looks identical to a Lite user until they cross
|
|
115
159
|
* 400 prompts/week), so callers should still expose an explicit override.
|
|
160
|
+
*
|
|
161
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
162
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
163
|
+
* for backward compatibility only and drifts from actual billing.
|
|
116
164
|
*/
|
|
117
165
|
export declare function resolveZaiTier(configured: ZaiTier | 'auto', accumulated: ZaiAccumulatedUsage): ZaiTier;
|
|
166
|
+
/**
|
|
167
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
168
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
169
|
+
* for backward compatibility only and drifts from actual billing.
|
|
170
|
+
*/
|
|
118
171
|
export interface InferZaiQuotaStateOptions {
|
|
119
172
|
/** ISO timestamp to stamp on the sample. Default: now. */
|
|
120
173
|
capturedAt?: string;
|
|
@@ -138,9 +191,23 @@ export interface InferZaiQuotaStateOptions {
|
|
|
138
191
|
* - `sevenDay.resetsAt` ← first turn in window + 7d (documented as
|
|
139
192
|
* "7 days from order time" — order time is unknown, so this is a
|
|
140
193
|
* lower bound at best).
|
|
194
|
+
*
|
|
195
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
196
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
197
|
+
* for backward compatibility only and drifts from actual billing.
|
|
141
198
|
*/
|
|
142
199
|
export declare function inferZaiQuotaState(accumulated: ZaiAccumulatedUsage, tier: ZaiTier, options?: InferZaiQuotaStateOptions): QuotaState;
|
|
200
|
+
/**
|
|
201
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
202
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
203
|
+
* for backward compatibility only and drifts from actual billing.
|
|
204
|
+
*/
|
|
143
205
|
export type ZaiQuotaErrorKind = 'exhausted' | 'fup' | 'expired' | 'invalid';
|
|
206
|
+
/**
|
|
207
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
208
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
209
|
+
* for backward compatibility only and drifts from actual billing.
|
|
210
|
+
*/
|
|
144
211
|
export interface ZaiQuotaError {
|
|
145
212
|
kind: ZaiQuotaErrorKind;
|
|
146
213
|
/** ISO timestamp parsed from `${next_flush_time}` in the error message, if present. */
|
|
@@ -159,6 +226,10 @@ export interface ZaiQuotaError {
|
|
|
159
226
|
* `YYYY-MM-DD HH:mm:ss` form defensively.
|
|
160
227
|
*
|
|
161
228
|
* Returns `null` when the code is not a quota-related business error.
|
|
229
|
+
*
|
|
230
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
231
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
232
|
+
* for backward compatibility only and drifts from actual billing.
|
|
162
233
|
*/
|
|
163
234
|
export declare function parseZaiQuotaError(error: {
|
|
164
235
|
code?: string | number;
|
|
@@ -171,17 +242,29 @@ export declare function parseZaiQuotaError(error: {
|
|
|
171
242
|
* Handles ISO 8601 (`2025-01-31T14:00:00Z`), space-separated
|
|
172
243
|
* (`2025-01-31 14:00:00`), and epoch-seconds (1234567890) forms.
|
|
173
244
|
* Returns undefined when no parseable timestamp is found.
|
|
245
|
+
*
|
|
246
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
247
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
248
|
+
* for backward compatibility only and drifts from actual billing.
|
|
174
249
|
*/
|
|
175
250
|
export declare function extractNextFlushTime(message: string): string | undefined;
|
|
176
251
|
/**
|
|
177
252
|
* Builds an unavailable z.ai `QuotaState` for the cold-start case where
|
|
178
253
|
* no z.ai traffic has been observed yet.
|
|
254
|
+
*
|
|
255
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
256
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
257
|
+
* for backward compatibility only and drifts from actual billing.
|
|
179
258
|
*/
|
|
180
259
|
export declare function makeUnavailableZaiQuotaState(error?: string, tier?: ZaiTier, capturedAt?: string): QuotaState;
|
|
181
260
|
/**
|
|
182
261
|
* Shape of a row returned by `OpenCodeDatabase.getAssistantMessagesByProviderId`.
|
|
183
262
|
* Defined here to keep the sidekick-shared helper self-contained without
|
|
184
263
|
* leaking the OpenCode db module into the public quota surface.
|
|
264
|
+
*
|
|
265
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
266
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
267
|
+
* for backward compatibility only and drifts from actual billing.
|
|
185
268
|
*/
|
|
186
269
|
export interface ZaiOpenCodeRow {
|
|
187
270
|
timeCreated: number;
|
|
@@ -202,6 +285,10 @@ export interface ZaiOpenCodeRow {
|
|
|
202
285
|
*
|
|
203
286
|
* Returns `{ turns, errors }` so callers can replay them through
|
|
204
287
|
* `ZaiQuotaWatcher.ingestAssistantTurns` and `ingestError`.
|
|
288
|
+
*
|
|
289
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
290
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
291
|
+
* for backward compatibility only and drifts from actual billing.
|
|
205
292
|
*/
|
|
206
293
|
export declare function rowsToZaiTurnsAndErrors(rows: readonly ZaiOpenCodeRow[]): {
|
|
207
294
|
turns: ZaiAssistantTurn[];
|
package/dist/zaiQuota.js
CHANGED
|
@@ -18,6 +18,10 @@ exports.rowsToZaiTurnsAndErrors = rowsToZaiTurnsAndErrors;
|
|
|
18
18
|
* "One prompt ≈ one query; each prompt is estimated to invoke the model
|
|
19
19
|
* 15–20 times." We use the midpoint (17.5) when converting observed
|
|
20
20
|
* assistant turns into prompt equivalents.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
23
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
24
|
+
* for backward compatibility only and drifts from actual billing.
|
|
21
25
|
*/
|
|
22
26
|
exports.ZAI_TIER_BUDGETS = {
|
|
23
27
|
lite: { fiveHour: 80, weekly: 400 },
|
|
@@ -29,6 +33,10 @@ exports.ZAI_TIER_BUDGETS = {
|
|
|
29
33
|
* Used to convert accumulated assistant turns into prompt equivalents.
|
|
30
34
|
* Calibration note: this constant may need per-tier adjustment after
|
|
31
35
|
* real-world validation against the z.ai web UI.
|
|
36
|
+
*
|
|
37
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
38
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
39
|
+
* for backward compatibility only and drifts from actual billing.
|
|
32
40
|
*/
|
|
33
41
|
exports.ZAI_PROMPT_INVOCATIONS = 17.5;
|
|
34
42
|
const FIVE_HOUR_MS = 5 * 3_600_000;
|
|
@@ -36,8 +44,17 @@ const SEVEN_DAY_MS = 7 * 86_400_000;
|
|
|
36
44
|
/**
|
|
37
45
|
* Provider IDs that OpenCode tags on z.ai-routed messages.
|
|
38
46
|
* `zai-coding-plan` is the Coding Plan key; `zai` is the bare API key.
|
|
47
|
+
*
|
|
48
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
49
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
50
|
+
* for backward compatibility only and drifts from actual billing.
|
|
39
51
|
*/
|
|
40
52
|
exports.ZAI_PROVIDER_IDS = ['zai', 'zai-coding-plan'];
|
|
53
|
+
/**
|
|
54
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
55
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
56
|
+
* for backward compatibility only and drifts from actual billing.
|
|
57
|
+
*/
|
|
41
58
|
function isZaiProviderId(value) {
|
|
42
59
|
return value === 'zai' || value === 'zai-coding-plan';
|
|
43
60
|
}
|
|
@@ -45,6 +62,10 @@ function isZaiProviderId(value) {
|
|
|
45
62
|
* Sums the token fields of a single turn into a scalar.
|
|
46
63
|
* Cache reads are weighted at 0.1× (Anthropic convention) to avoid
|
|
47
64
|
* over-counting repeated context. Output and reasoning tokens are weighted 1×.
|
|
65
|
+
*
|
|
66
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
67
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
68
|
+
* for backward compatibility only and drifts from actual billing.
|
|
48
69
|
*/
|
|
49
70
|
function turnTokenWeight(turn) {
|
|
50
71
|
return (turn.inputTokens +
|
|
@@ -59,6 +80,10 @@ function turnTokenWeight(turn) {
|
|
|
59
80
|
* Pure & stateless: the caller is responsible for passing the relevant turns.
|
|
60
81
|
* Turns outside the 7-day window are ignored; turns inside the 7-day window
|
|
61
82
|
* but outside the 5-hour window still count toward weekly usage.
|
|
83
|
+
*
|
|
84
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
85
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
86
|
+
* for backward compatibility only and drifts from actual billing.
|
|
62
87
|
*/
|
|
63
88
|
function accumulateZaiUsage(turns, nowMs = Date.now()) {
|
|
64
89
|
const fiveHourCutoff = nowMs - FIVE_HOUR_MS;
|
|
@@ -104,6 +129,10 @@ function accumulateZaiUsage(turns, nowMs = Date.now()) {
|
|
|
104
129
|
* the user must be on a higher tier. This under-detects at the start of a
|
|
105
130
|
* weekly cycle (a Max user looks identical to a Lite user until they cross
|
|
106
131
|
* 400 prompts/week), so callers should still expose an explicit override.
|
|
132
|
+
*
|
|
133
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
134
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
135
|
+
* for backward compatibility only and drifts from actual billing.
|
|
107
136
|
*/
|
|
108
137
|
function resolveZaiTier(configured, accumulated) {
|
|
109
138
|
if (configured !== 'auto')
|
|
@@ -128,6 +157,10 @@ function resolveZaiTier(configured, accumulated) {
|
|
|
128
157
|
* - `sevenDay.resetsAt` ← first turn in window + 7d (documented as
|
|
129
158
|
* "7 days from order time" — order time is unknown, so this is a
|
|
130
159
|
* lower bound at best).
|
|
160
|
+
*
|
|
161
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
162
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
163
|
+
* for backward compatibility only and drifts from actual billing.
|
|
131
164
|
*/
|
|
132
165
|
function inferZaiQuotaState(accumulated, tier, options = {}) {
|
|
133
166
|
const budget = exports.ZAI_TIER_BUDGETS[tier];
|
|
@@ -185,6 +218,10 @@ const ZAI_ERROR_CODES = {
|
|
|
185
218
|
* `YYYY-MM-DD HH:mm:ss` form defensively.
|
|
186
219
|
*
|
|
187
220
|
* Returns `null` when the code is not a quota-related business error.
|
|
221
|
+
*
|
|
222
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
223
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
224
|
+
* for backward compatibility only and drifts from actual billing.
|
|
188
225
|
*/
|
|
189
226
|
function parseZaiQuotaError(error) {
|
|
190
227
|
if (!error)
|
|
@@ -203,6 +240,10 @@ function parseZaiQuotaError(error) {
|
|
|
203
240
|
* Handles ISO 8601 (`2025-01-31T14:00:00Z`), space-separated
|
|
204
241
|
* (`2025-01-31 14:00:00`), and epoch-seconds (1234567890) forms.
|
|
205
242
|
* Returns undefined when no parseable timestamp is found.
|
|
243
|
+
*
|
|
244
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
245
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
246
|
+
* for backward compatibility only and drifts from actual billing.
|
|
206
247
|
*/
|
|
207
248
|
function extractNextFlushTime(message) {
|
|
208
249
|
if (!message)
|
|
@@ -236,6 +277,10 @@ function extractNextFlushTime(message) {
|
|
|
236
277
|
/**
|
|
237
278
|
* Builds an unavailable z.ai `QuotaState` for the cold-start case where
|
|
238
279
|
* no z.ai traffic has been observed yet.
|
|
280
|
+
*
|
|
281
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
282
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
283
|
+
* for backward compatibility only and drifts from actual billing.
|
|
239
284
|
*/
|
|
240
285
|
function makeUnavailableZaiQuotaState(error = 'No z.ai usage observed yet', tier = 'lite', capturedAt = new Date().toISOString()) {
|
|
241
286
|
return {
|
|
@@ -259,6 +304,10 @@ function makeUnavailableZaiQuotaState(error = 'No z.ai usage observed yet', tier
|
|
|
259
304
|
*
|
|
260
305
|
* Returns `{ turns, errors }` so callers can replay them through
|
|
261
306
|
* `ZaiQuotaWatcher.ingestAssistantTurns` and `ingestError`.
|
|
307
|
+
*
|
|
308
|
+
* @deprecated Use `resolveZaiQuota()` from `zaiQuotaApi`, which reads z.ai's
|
|
309
|
+
* authoritative quota endpoint. This observed-traffic estimator is retained
|
|
310
|
+
* for backward compatibility only and drifts from actual billing.
|
|
262
311
|
*/
|
|
263
312
|
function rowsToZaiTurnsAndErrors(rows) {
|
|
264
313
|
const turns = [];
|
|
@@ -10,6 +10,10 @@ type TierResolver = (configured: ZaiTier | 'auto', accumulated: ZaiAccumulatedUs
|
|
|
10
10
|
type SnapshotReader = (providerId: ZaiSnapshotProvider, accountId: string) => QuotaState | null;
|
|
11
11
|
type SnapshotWriter = (providerId: ZaiSnapshotProvider, accountId: string, quota: QuotaState) => void;
|
|
12
12
|
type HistoryAppender = (sample: QuotaHistorySample) => void | Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Unreachable from every package entrypoint and no longer used by
|
|
15
|
+
* product code. Use `resolveZaiQuota()` from `zaiQuotaApi` instead.
|
|
16
|
+
*/
|
|
13
17
|
export interface ZaiQuotaWatcherOptions {
|
|
14
18
|
/** Configured tier. Default: `'auto'` (heuristic from observed usage). */
|
|
15
19
|
tier?: ZaiTier | 'auto';
|
|
@@ -39,6 +43,9 @@ export interface ZaiQuotaWatcherOptions {
|
|
|
39
43
|
* for every observed z.ai assistant turn, and `ingestError` whenever a
|
|
40
44
|
* z.ai business error is observed. The watcher debounces recompute and
|
|
41
45
|
* emits via `onUpdate` / `onQuotaUpdate`.
|
|
46
|
+
*
|
|
47
|
+
* @deprecated Unreachable from every package entrypoint and no longer used by
|
|
48
|
+
* product code. Use `resolveZaiQuota()` from `zaiQuotaApi` instead.
|
|
42
49
|
*/
|
|
43
50
|
export declare class ZaiQuotaWatcher implements Disposable {
|
|
44
51
|
private readonly tier;
|
package/dist/zaiQuotaWatcher.js
CHANGED
|
@@ -37,6 +37,9 @@ function enrichZaiState(state) {
|
|
|
37
37
|
* for every observed z.ai assistant turn, and `ingestError` whenever a
|
|
38
38
|
* z.ai business error is observed. The watcher debounces recompute and
|
|
39
39
|
* emits via `onUpdate` / `onQuotaUpdate`.
|
|
40
|
+
*
|
|
41
|
+
* @deprecated Unreachable from every package entrypoint and no longer used by
|
|
42
|
+
* product code. Use `resolveZaiQuota()` from `zaiQuotaApi` instead.
|
|
40
43
|
*/
|
|
41
44
|
class ZaiQuotaWatcher {
|
|
42
45
|
tier;
|
package/package.json
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sidekick-shared",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.5",
|
|
4
4
|
"description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"codex-cli",
|
|
8
|
+
"opencode",
|
|
9
|
+
"session-monitor",
|
|
10
|
+
"token-usage",
|
|
11
|
+
"quota",
|
|
12
|
+
"jsonl",
|
|
13
|
+
"ai-coding-assistant",
|
|
14
|
+
"agent-hub",
|
|
15
|
+
"zod-schemas"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://cesarandreslopez.github.io/sidekick-agent-hub/",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/cesarandreslopez/sidekick-agent-hub",
|
|
21
|
+
"directory": "sidekick-shared"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/cesarandreslopez/sidekick-agent-hub/issues"
|
|
25
|
+
},
|
|
26
|
+
"author": {
|
|
27
|
+
"name": "Cesar Andres Lopez",
|
|
28
|
+
"email": "cesarandreslopez@gmail.com"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.0.0"
|
|
32
|
+
},
|
|
5
33
|
"main": "dist/index.js",
|
|
6
34
|
"types": "dist/index.d.ts",
|
|
7
35
|
"exports": {
|
|
@@ -47,8 +75,38 @@
|
|
|
47
75
|
},
|
|
48
76
|
"./package.json": "./package.json"
|
|
49
77
|
},
|
|
78
|
+
"typesVersions": {
|
|
79
|
+
"*": {
|
|
80
|
+
"browser": [
|
|
81
|
+
"dist/browser.d.ts"
|
|
82
|
+
],
|
|
83
|
+
"node": [
|
|
84
|
+
"dist/node.d.ts"
|
|
85
|
+
],
|
|
86
|
+
"schemas": [
|
|
87
|
+
"dist/schemas/index.d.ts"
|
|
88
|
+
],
|
|
89
|
+
"phrases": [
|
|
90
|
+
"dist/phrases.d.ts"
|
|
91
|
+
],
|
|
92
|
+
"modelContext": [
|
|
93
|
+
"dist/modelContext.d.ts"
|
|
94
|
+
],
|
|
95
|
+
"modelInfo": [
|
|
96
|
+
"dist/modelInfo.d.ts"
|
|
97
|
+
],
|
|
98
|
+
"formatting": [
|
|
99
|
+
"dist/formatting.d.ts"
|
|
100
|
+
],
|
|
101
|
+
"statusline": [
|
|
102
|
+
"dist/statusline/index.d.ts"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
},
|
|
50
106
|
"files": [
|
|
51
|
-
"dist"
|
|
107
|
+
"dist",
|
|
108
|
+
"CHANGELOG.md",
|
|
109
|
+
"LICENSE"
|
|
52
110
|
],
|
|
53
111
|
"sideEffects": false,
|
|
54
112
|
"publishConfig": {
|