@kunkun.sh/kuota 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +130 -2
- package/bin/kuota.mjs +3 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +1303 -0
- package/dist/dist-CIjerpSd.mjs +576 -0
- package/dist/history-G4o4HVvW.d.mts +36 -0
- package/dist/history-oOXsjdWR.mjs +236 -0
- package/dist/history.d.mts +3 -0
- package/dist/history.mjs +3 -0
- package/dist/index-2k6FkhpT.d.mts +81 -0
- package/dist/index-NHzh2TGn.d.mts +168 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +7 -0
- package/dist/main-B-hZbKK1.mjs +340 -0
- package/dist/metrics-Bzmrfgn-.mjs +307 -0
- package/dist/metrics-D7oNul_9.d.mts +30 -0
- package/dist/metrics.d.mts +3 -0
- package/dist/metrics.mjs +3 -0
- package/dist/parsers.d.mts +13 -0
- package/dist/parsers.mjs +19 -0
- package/dist/providers.d.mts +2 -0
- package/dist/providers.mjs +1 -0
- package/dist/src-C48Ew_L9.mjs +707 -0
- package/dist/src-y_QKmTyj.mjs +309 -0
- package/dist/web/assets/index-ihoe7C_u.js +27 -0
- package/dist/web/index.html +30 -0
- package/dist/web-server-DkdA5Ixj.mjs +154 -0
- package/package.json +73 -4
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { r as decodeHistoryEvent } from "./dist-CIjerpSd.mjs";
|
|
2
|
+
import "./src-y_QKmTyj.mjs";
|
|
3
|
+
import { Either } from "effect";
|
|
4
|
+
import { mkdir, open, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
// ../history/src/local-jsonl.ts
|
|
8
|
+
const FILE = /^events-(\d{4}-\d{2}-\d{2})(?:\.(\d+))?\.jsonl$/;
|
|
9
|
+
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
const utcDate = (iso) => new Date(iso).toISOString().slice(0, 10);
|
|
12
|
+
async function listParts(directory, date) {
|
|
13
|
+
let names;
|
|
14
|
+
try {
|
|
15
|
+
names = await readdir(directory);
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const parts = [];
|
|
20
|
+
for (const name of names) {
|
|
21
|
+
const match = FILE.exec(name);
|
|
22
|
+
if (!match || !DATE.test(match[1])) continue;
|
|
23
|
+
if (date !== void 0 && match[1] !== date) continue;
|
|
24
|
+
parts.push({
|
|
25
|
+
name,
|
|
26
|
+
date: match[1],
|
|
27
|
+
part: match[2] === void 0 ? 0 : Number(match[2])
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return parts.sort((a, b) => a.date.localeCompare(b.date) || a.part - b.part);
|
|
31
|
+
}
|
|
32
|
+
function partName(date, part) {
|
|
33
|
+
return part === 0 ? `events-${date}.jsonl` : `events-${date}.${part}.jsonl`;
|
|
34
|
+
}
|
|
35
|
+
async function parseFile(path) {
|
|
36
|
+
let text;
|
|
37
|
+
try {
|
|
38
|
+
text = await readFile(path, "utf8");
|
|
39
|
+
} catch {
|
|
40
|
+
return {
|
|
41
|
+
events: [],
|
|
42
|
+
invalid: 0
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const events = [];
|
|
46
|
+
let invalid = 0;
|
|
47
|
+
for (const line of text.split("\n")) {
|
|
48
|
+
if (line.trim() === "") continue;
|
|
49
|
+
try {
|
|
50
|
+
const decoded = decodeHistoryEvent(JSON.parse(line));
|
|
51
|
+
if (Either.isRight(decoded)) events.push(decoded.right);
|
|
52
|
+
else invalid++;
|
|
53
|
+
} catch {
|
|
54
|
+
invalid++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
events,
|
|
59
|
+
invalid
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createLocalJsonlJournal(options) {
|
|
63
|
+
const directory = options.directory;
|
|
64
|
+
const maxFileBytes = options.maxFileBytes ?? 16777216;
|
|
65
|
+
const retentionDays = options.retentionDays ?? 400;
|
|
66
|
+
const doSync = options.fsync ?? true;
|
|
67
|
+
const lockStaleMs = options.lockStaleMs ?? 3e4;
|
|
68
|
+
const lockTimeoutMs = options.lockTimeoutMs ?? 15e3;
|
|
69
|
+
const lockPath = join(directory, ".lock");
|
|
70
|
+
let queue = Promise.resolve();
|
|
71
|
+
async function withLock(fn) {
|
|
72
|
+
await mkdir(directory, {
|
|
73
|
+
recursive: true,
|
|
74
|
+
mode: 448
|
|
75
|
+
});
|
|
76
|
+
const deadline = Date.now() + lockTimeoutMs;
|
|
77
|
+
for (;;) try {
|
|
78
|
+
const handle = await open(lockPath, "wx", 384);
|
|
79
|
+
await handle.writeFile(String(process.pid));
|
|
80
|
+
await handle.close();
|
|
81
|
+
break;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error.code !== "EEXIST") throw error;
|
|
84
|
+
try {
|
|
85
|
+
const info = await stat(lockPath);
|
|
86
|
+
if (Date.now() - info.mtimeMs > lockStaleMs) {
|
|
87
|
+
await rm(lockPath, { force: true });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (Date.now() > deadline) throw new Error("JOURNAL_LOCK_TIMEOUT");
|
|
94
|
+
await sleep(5);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return await fn();
|
|
98
|
+
} finally {
|
|
99
|
+
await rm(lockPath, { force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function appendDate(date, events) {
|
|
103
|
+
const parts = await listParts(directory, date);
|
|
104
|
+
const known = /* @__PURE__ */ new Set();
|
|
105
|
+
for (const part of parts) for (const event of (await parseFile(join(directory, part.name))).events) known.add(event.eventId);
|
|
106
|
+
const fresh = [];
|
|
107
|
+
let duplicates = 0;
|
|
108
|
+
for (const event of events) if (known.has(event.eventId)) duplicates++;
|
|
109
|
+
else {
|
|
110
|
+
known.add(event.eventId);
|
|
111
|
+
fresh.push(event);
|
|
112
|
+
}
|
|
113
|
+
if (fresh.length === 0) return {
|
|
114
|
+
written: 0,
|
|
115
|
+
duplicates
|
|
116
|
+
};
|
|
117
|
+
let partNumber = parts.length === 0 ? 0 : parts[parts.length - 1].part;
|
|
118
|
+
let path = join(directory, partName(date, partNumber));
|
|
119
|
+
let size = 0;
|
|
120
|
+
let endsWithNewline = true;
|
|
121
|
+
try {
|
|
122
|
+
size = (await stat(path)).size;
|
|
123
|
+
if (size > 0) {
|
|
124
|
+
const handle = await open(path, "r");
|
|
125
|
+
const last = Buffer.alloc(1);
|
|
126
|
+
await handle.read(last, 0, 1, size - 1);
|
|
127
|
+
await handle.close();
|
|
128
|
+
endsWithNewline = last[0] === 10;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
size = 0;
|
|
132
|
+
}
|
|
133
|
+
const buckets = [[]];
|
|
134
|
+
let current = size;
|
|
135
|
+
for (const event of fresh) {
|
|
136
|
+
const line = JSON.stringify(event) + "\n";
|
|
137
|
+
if (current > 0 && current + line.length > maxFileBytes) {
|
|
138
|
+
buckets.push([]);
|
|
139
|
+
current = 0;
|
|
140
|
+
}
|
|
141
|
+
buckets[buckets.length - 1].push(line);
|
|
142
|
+
current += line.length;
|
|
143
|
+
}
|
|
144
|
+
for (const [index, lines] of buckets.entries()) {
|
|
145
|
+
if (index > 0) {
|
|
146
|
+
partNumber += 1;
|
|
147
|
+
path = join(directory, partName(date, partNumber));
|
|
148
|
+
endsWithNewline = true;
|
|
149
|
+
}
|
|
150
|
+
const payload = (endsWithNewline ? "" : "\n") + lines.join("");
|
|
151
|
+
const handle = await open(path, "a", 384);
|
|
152
|
+
try {
|
|
153
|
+
await handle.write(payload);
|
|
154
|
+
if (doSync) await handle.sync();
|
|
155
|
+
} finally {
|
|
156
|
+
await handle.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
written: fresh.length,
|
|
161
|
+
duplicates
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function readAll(opts) {
|
|
165
|
+
const fromMs = Date.parse(opts.from);
|
|
166
|
+
const toMs = Date.parse(opts.to);
|
|
167
|
+
if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) throw new Error("INVALID_RANGE");
|
|
168
|
+
const fromDate = utcDate(opts.from);
|
|
169
|
+
const toDate = utcDate(opts.to);
|
|
170
|
+
const events = [];
|
|
171
|
+
let invalidLines = 0;
|
|
172
|
+
for (const part of await listParts(directory)) {
|
|
173
|
+
if (part.date < fromDate || part.date > toDate) continue;
|
|
174
|
+
const parsed = await parseFile(join(directory, part.name));
|
|
175
|
+
invalidLines += parsed.invalid;
|
|
176
|
+
for (const event of parsed.events) {
|
|
177
|
+
const t = Date.parse(event.observedAt);
|
|
178
|
+
if (t < fromMs || t > toMs) continue;
|
|
179
|
+
if (opts.provider !== void 0 && event.provider !== opts.provider) continue;
|
|
180
|
+
if (opts.windowId !== void 0 && event.windowId !== opts.windowId) continue;
|
|
181
|
+
events.push(event);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
events,
|
|
186
|
+
invalidLines
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
append(events) {
|
|
191
|
+
for (const event of events) if (Either.isLeft(decodeHistoryEvent(event))) return Promise.reject(/* @__PURE__ */ new Error("INVALID_HISTORY_EVENT"));
|
|
192
|
+
const run = queue.then(() => withLock(async () => {
|
|
193
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
194
|
+
for (const event of events) {
|
|
195
|
+
const date = utcDate(event.observedAt);
|
|
196
|
+
byDate.set(date, [...byDate.get(date) ?? [], event]);
|
|
197
|
+
}
|
|
198
|
+
let written = 0;
|
|
199
|
+
let duplicates = 0;
|
|
200
|
+
for (const [date, list] of [...byDate].sort(([a], [b]) => a.localeCompare(b))) {
|
|
201
|
+
const result = await appendDate(date, list);
|
|
202
|
+
written += result.written;
|
|
203
|
+
duplicates += result.duplicates;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
written,
|
|
207
|
+
duplicates
|
|
208
|
+
};
|
|
209
|
+
}));
|
|
210
|
+
queue = run.catch(() => void 0);
|
|
211
|
+
return run;
|
|
212
|
+
},
|
|
213
|
+
readAll,
|
|
214
|
+
async *read(opts) {
|
|
215
|
+
for (const event of (await readAll(opts)).events) yield event;
|
|
216
|
+
},
|
|
217
|
+
async applyRetention(now = /* @__PURE__ */ new Date()) {
|
|
218
|
+
const cutoffMs = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) - (Math.max(1, retentionDays) - 1) * 864e5;
|
|
219
|
+
const cutoff = new Date(cutoffMs).toISOString().slice(0, 10);
|
|
220
|
+
return queue.then(() => withLock(async () => {
|
|
221
|
+
let removed = 0;
|
|
222
|
+
for (const part of await listParts(directory)) if (part.date < cutoff) {
|
|
223
|
+
await rm(join(directory, part.name), { force: true });
|
|
224
|
+
removed++;
|
|
225
|
+
}
|
|
226
|
+
return removed;
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function defaultHistoryDirectory(env = process.env, platform = process.platform) {
|
|
232
|
+
if (env.KUOTA_HISTORY_DIR) return env.KUOTA_HISTORY_DIR;
|
|
233
|
+
if (platform === "darwin") return join(homedir(), "Library", "Application Support", "kuota", "history");
|
|
234
|
+
return join(env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "kuota", "history");
|
|
235
|
+
}
|
|
236
|
+
export { defaultHistoryDirectory as n, createLocalJsonlJournal as t };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { d as decodeHistoryEvent, f as historyEventsFromSnapshot } from "./index-NHzh2TGn.mjs";
|
|
2
|
+
import { a as createLocalJsonlJournal, i as ReadResult, n as JournalStore, o as defaultHistoryDirectory, r as ReadOptions, t as JournalOptions } from "./history-G4o4HVvW.mjs";
|
|
3
|
+
export { type JournalOptions, type JournalStore, type ReadOptions, type ReadResult, createLocalJsonlJournal, decodeHistoryEvent, defaultHistoryDirectory, historyEventsFromSnapshot };
|
package/dist/history.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as decodeHistoryEvent, r as historyEventsFromSnapshot } from "./src-y_QKmTyj.mjs";
|
|
2
|
+
import { n as defaultHistoryDirectory, t as createLocalJsonlJournal } from "./history-oOXsjdWR.mjs";
|
|
3
|
+
export { createLocalJsonlJournal, decodeHistoryEvent, defaultHistoryDirectory, historyEventsFromSnapshot };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { S as QuotaSnapshot, b as ProviderId, m as QuotaObservation } from "./index-NHzh2TGn.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
type CodexTransportErrorCode = "BINARY_NOT_FOUND" | "TIMEOUT" | "PROTOCOL_ERROR";
|
|
4
|
+
/** Fixed, safe error messages: never forward stderr or a raw RPC response. */
|
|
5
|
+
declare class CodexTransportError extends Error {
|
|
6
|
+
readonly code: CodexTransportErrorCode;
|
|
7
|
+
constructor(code: CodexTransportErrorCode);
|
|
8
|
+
}
|
|
9
|
+
interface CodexRpcOptions {
|
|
10
|
+
binary?: string;
|
|
11
|
+
args?: readonly string[];
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/** One bounded RPC session. Never spawns a bare Codex TUI and never prints raw output. */
|
|
15
|
+
declare function requestCodexRateLimits(options?: CodexRpcOptions): Promise<unknown>;
|
|
16
|
+
/** Parses only the non-sensitive subset of the Codex app-server v2 quota response. */
|
|
17
|
+
declare function parseCodexRateLimits(raw: unknown, fetchedAt?: Date): QuotaSnapshot;
|
|
18
|
+
declare function readCodex(options?: CodexRpcOptions): Effect.Effect<QuotaSnapshot>;
|
|
19
|
+
interface QuotaProvider {
|
|
20
|
+
id: ProviderId;
|
|
21
|
+
label: string;
|
|
22
|
+
read: () => Effect.Effect<QuotaSnapshot, unknown>;
|
|
23
|
+
}
|
|
24
|
+
interface ProviderMetadata {
|
|
25
|
+
id: ProviderId;
|
|
26
|
+
label: string;
|
|
27
|
+
implemented: boolean;
|
|
28
|
+
source: string;
|
|
29
|
+
}
|
|
30
|
+
declare const PROVIDER_METADATA: readonly ProviderMetadata[];
|
|
31
|
+
/** Static names are extensible to runtime manifests in the later plugin phase. */
|
|
32
|
+
declare function isProviderId(value: string): value is ProviderId;
|
|
33
|
+
declare function builtInProviders(): QuotaProvider[];
|
|
34
|
+
/** One failed adapter never prevents other providers from reporting. */
|
|
35
|
+
declare function readAll(ids: readonly ProviderId[], providers?: readonly QuotaProvider[]): Effect.Effect<readonly QuotaSnapshot[]>;
|
|
36
|
+
/** Parses agy >=1.1.11's structured `-p /usage --output-format json` response. */
|
|
37
|
+
declare function parseAntigravityUsageReport(raw: unknown): QuotaSnapshot;
|
|
38
|
+
/** Version strings with suffixes are intentionally rejected to avoid guessing support. */
|
|
39
|
+
declare function parseAgyVersion(value: string): boolean;
|
|
40
|
+
interface AgyOptions {
|
|
41
|
+
binary?: string;
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
maxOutputBytes?: number;
|
|
44
|
+
}
|
|
45
|
+
/** No new account login. Existing agy credentials only, guarded from browser launch and auto-update. */
|
|
46
|
+
declare function readAntigravity(options?: AgyOptions): Effect.Effect<QuotaSnapshot>;
|
|
47
|
+
/** Claude API reports utilization (used percent), not remaining percent. */
|
|
48
|
+
declare function parseClaudeOAuthUsage(raw: unknown): QuotaSnapshot;
|
|
49
|
+
interface ClaudeOptions {
|
|
50
|
+
/** Manually provided existing Claude Code OAuth token; never use Anthropic API keys here. */
|
|
51
|
+
token?: string;
|
|
52
|
+
credentialsFile?: string;
|
|
53
|
+
fetchImpl?: typeof fetch;
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
}
|
|
56
|
+
/** No Keychain UI access, credential refresh, browser sign-in or CLI session side effects. */
|
|
57
|
+
declare function readClaude(options?: ClaudeOptions): Effect.Effect<QuotaSnapshot>;
|
|
58
|
+
type RefreshPolicy = Readonly<{
|
|
59
|
+
successTtlMs: number;
|
|
60
|
+
errorRetryMs: number;
|
|
61
|
+
authRetryMs: number;
|
|
62
|
+
rateLimitCooldownMs: number;
|
|
63
|
+
staleMaxAgeMs: number;
|
|
64
|
+
}>;
|
|
65
|
+
interface QuotaService {
|
|
66
|
+
read(ids: readonly ProviderId[], options?: {
|
|
67
|
+
force?: boolean;
|
|
68
|
+
}): Effect.Effect<readonly QuotaObservation[]>;
|
|
69
|
+
peek(id: ProviderId): QuotaObservation | null;
|
|
70
|
+
invalidate(id?: ProviderId): void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Process-local TTL cache with per-provider singleflight, failure cooldowns and honest
|
|
74
|
+
* stale labelling. Only normalized snapshots are retained.
|
|
75
|
+
*/
|
|
76
|
+
declare function createQuotaService(options?: {
|
|
77
|
+
providers?: readonly QuotaProvider[];
|
|
78
|
+
now?: () => number;
|
|
79
|
+
policies?: Partial<Record<ProviderId, Partial<RefreshPolicy>>>;
|
|
80
|
+
}): QuotaService;
|
|
81
|
+
export { CodexRpcOptions as _, readClaude as a, readAntigravity as c, QuotaProvider as d, builtInProviders as f, parseCodexRateLimits as g, readCodex as h, parseClaudeOAuthUsage as i, PROVIDER_METADATA as l, readAll as m, RefreshPolicy as n, parseAgyVersion as o, isProviderId as p, createQuotaService as r, parseAntigravityUsageReport as s, QuotaService as t, ProviderMetadata as u, CodexTransportError as v, requestCodexRateLimits as y };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Either, Schema } from "effect";
|
|
2
|
+
declare const ProviderIdSchema: Schema.Literal<["codex", "claude", "antigravity"]>;
|
|
3
|
+
type ProviderId = Schema.Schema.Type<typeof ProviderIdSchema>;
|
|
4
|
+
declare const QuotaStatusSchema: Schema.Literal<["ok", "partial", "unauthenticated", "unavailable", "unsupported", "error"]>;
|
|
5
|
+
type QuotaStatus = Schema.Schema.Type<typeof QuotaStatusSchema>;
|
|
6
|
+
declare const QuotaWindowSchema: Schema.Struct<{
|
|
7
|
+
windowId: typeof Schema.String;
|
|
8
|
+
usedPercent: Schema.NullOr<typeof Schema.Number>;
|
|
9
|
+
remainingPercent: Schema.NullOr<typeof Schema.Number>;
|
|
10
|
+
resetAt: Schema.NullOr<typeof Schema.String>;
|
|
11
|
+
}>;
|
|
12
|
+
type QuotaWindow = Schema.Schema.Type<typeof QuotaWindowSchema>;
|
|
13
|
+
declare const QuotaSnapshotSchema: Schema.Struct<{
|
|
14
|
+
provider: Schema.Literal<["codex", "claude", "antigravity"]>;
|
|
15
|
+
status: Schema.Literal<["ok", "partial", "unauthenticated", "unavailable", "unsupported", "error"]>;
|
|
16
|
+
fetchedAt: typeof Schema.String;
|
|
17
|
+
source: typeof Schema.String;
|
|
18
|
+
windows: Schema.Array$<Schema.Struct<{
|
|
19
|
+
windowId: typeof Schema.String;
|
|
20
|
+
usedPercent: Schema.NullOr<typeof Schema.Number>;
|
|
21
|
+
remainingPercent: Schema.NullOr<typeof Schema.Number>;
|
|
22
|
+
resetAt: Schema.NullOr<typeof Schema.String>;
|
|
23
|
+
}>>;
|
|
24
|
+
errorCode: Schema.NullOr<typeof Schema.String>;
|
|
25
|
+
}>;
|
|
26
|
+
type QuotaSnapshot = Schema.Schema.Type<typeof QuotaSnapshotSchema>;
|
|
27
|
+
type ProviderErrorCode = "BINARY_NOT_FOUND" | "AUTH_REQUIRED" | "TIMEOUT" | "PROTOCOL_ERROR" | "UNSUPPORTED" | "UNKNOWN";
|
|
28
|
+
interface WindowInput {
|
|
29
|
+
windowId: string;
|
|
30
|
+
usedPercent?: unknown;
|
|
31
|
+
resetAt?: unknown;
|
|
32
|
+
}
|
|
33
|
+
declare function normalizeWindow(input: WindowInput): QuotaWindow;
|
|
34
|
+
/** Whitelist public response fields: never forward token-like keys or provider raw payloads. */
|
|
35
|
+
declare function redactSnapshot(input: Record<string, unknown>): QuotaSnapshot;
|
|
36
|
+
declare function makeUnavailableSnapshot(provider: ProviderId, errorCode: string, status?: QuotaStatus): QuotaSnapshot;
|
|
37
|
+
type Freshness = "live" | "cached" | "stale";
|
|
38
|
+
/** Non-breaking wrapper: QuotaSnapshot itself (and status --json schemaVersion 1) is unchanged. */
|
|
39
|
+
interface QuotaObservation {
|
|
40
|
+
readonly snapshot: QuotaSnapshot;
|
|
41
|
+
readonly freshness: Freshness;
|
|
42
|
+
readonly observedAt: string;
|
|
43
|
+
readonly checkedAt: string;
|
|
44
|
+
readonly ageMs: number;
|
|
45
|
+
readonly nextAttemptAt: string | null;
|
|
46
|
+
readonly latestErrorCode: string | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pure constructor. If `snapshot.fetchedAt` is not a valid timestamp we fall back to the
|
|
50
|
+
* injected `checkedAtMs` as the observation time, so no NaN / Invalid Date is ever emitted.
|
|
51
|
+
*/
|
|
52
|
+
declare function makeObservation(input: {
|
|
53
|
+
snapshot: QuotaSnapshot;
|
|
54
|
+
freshness: Freshness;
|
|
55
|
+
checkedAtMs: number;
|
|
56
|
+
nowMs: number;
|
|
57
|
+
nextAttemptAtMs?: number | null;
|
|
58
|
+
latestErrorCode?: string | null;
|
|
59
|
+
}): QuotaObservation;
|
|
60
|
+
type WindowSemantics = "fixed" | "rolling" | "first_use" | "unknown";
|
|
61
|
+
interface WindowProfile {
|
|
62
|
+
readonly semantics: WindowSemantics;
|
|
63
|
+
readonly durationMs: number | null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Window cadence heuristics, derived from window ids the adapters emit. Only cadences named in
|
|
67
|
+
* the id (5h / weekly / daily) are trusted; everything else stays `unknown` rather than guessed.
|
|
68
|
+
* Codex `primary` / `secondary` follow the documented 5h / weekly convention of its app-server.
|
|
69
|
+
*/
|
|
70
|
+
declare function windowProfile(provider: ProviderId, windowId: string, resetAt?: string | null, now?: Date): WindowProfile;
|
|
71
|
+
declare const QuotaHistoryEventV1Schema: Schema.Struct<{
|
|
72
|
+
schemaVersion: Schema.Literal<[1]>;
|
|
73
|
+
kind: Schema.Literal<["quota_observed", "quota_unavailable", "usage_observed"]>;
|
|
74
|
+
eventId: typeof Schema.String;
|
|
75
|
+
observedAt: Schema.filter<typeof Schema.String>;
|
|
76
|
+
recordedAt: Schema.filter<typeof Schema.String>;
|
|
77
|
+
provider: typeof Schema.String;
|
|
78
|
+
accountScope: Schema.NullOr<typeof Schema.String>;
|
|
79
|
+
deviceId: typeof Schema.String;
|
|
80
|
+
windowId: typeof Schema.String;
|
|
81
|
+
epochId: Schema.NullOr<typeof Schema.String>;
|
|
82
|
+
semantics: Schema.Literal<["fixed", "rolling", "first_use", "unknown"]>;
|
|
83
|
+
startAt: Schema.NullOr<Schema.filter<typeof Schema.String>>;
|
|
84
|
+
resetAt: Schema.NullOr<Schema.filter<typeof Schema.String>>;
|
|
85
|
+
usedPercent: Schema.NullOr<Schema.filter<typeof Schema.Number>>;
|
|
86
|
+
remainingPercent: Schema.NullOr<Schema.filter<typeof Schema.Number>>;
|
|
87
|
+
source: typeof Schema.String;
|
|
88
|
+
state: Schema.Literal<["ok", "partial", "stale", "error", "unauthenticated"]>;
|
|
89
|
+
errorCode: Schema.NullOr<typeof Schema.String>;
|
|
90
|
+
usage: Schema.optional<Schema.Struct<{
|
|
91
|
+
inputTokens: Schema.optional<typeof Schema.Number>;
|
|
92
|
+
outputTokens: Schema.optional<typeof Schema.Number>;
|
|
93
|
+
cacheReadTokens: Schema.optional<typeof Schema.Number>;
|
|
94
|
+
cacheWriteTokens: Schema.optional<typeof Schema.Number>;
|
|
95
|
+
model: Schema.optional<typeof Schema.String>;
|
|
96
|
+
estimatedUsd: Schema.optional<typeof Schema.Number>;
|
|
97
|
+
}>>;
|
|
98
|
+
}>;
|
|
99
|
+
type QuotaHistoryEventV1 = Schema.Schema.Type<typeof QuotaHistoryEventV1Schema>;
|
|
100
|
+
/** Strict decode: unknown (possibly secret-bearing) fields are rejected, not silently kept. */
|
|
101
|
+
declare function decodeHistoryEvent(input: unknown): Either.Either<QuotaHistoryEventV1, unknown>;
|
|
102
|
+
interface HistoryEventOptions {
|
|
103
|
+
deviceId: string;
|
|
104
|
+
accountScope: string | null;
|
|
105
|
+
recordedAt: string;
|
|
106
|
+
/** Mark observations that came from a stale cache rather than a live probe. */
|
|
107
|
+
stale?: boolean;
|
|
108
|
+
semanticsForWindow?: (id: string) => WindowSemantics;
|
|
109
|
+
startForWindow?: (id: string) => string | null;
|
|
110
|
+
}
|
|
111
|
+
/** Pure normalization: one event per measurable window, or one sanitized unavailable event. */
|
|
112
|
+
declare function historyEventsFromSnapshot(snapshot: QuotaSnapshot, options: HistoryEventOptions): readonly QuotaHistoryEventV1[];
|
|
113
|
+
type ForecastQuality = "insufficient" | "low" | "medium" | "high" | "unsupported";
|
|
114
|
+
type ForecastMode = "all-window" | "recent-activity";
|
|
115
|
+
interface ForecastInput {
|
|
116
|
+
now: Date;
|
|
117
|
+
resetAt: Date | null;
|
|
118
|
+
observations: readonly QuotaHistoryEventV1[];
|
|
119
|
+
mode: ForecastMode;
|
|
120
|
+
}
|
|
121
|
+
interface ForecastResult {
|
|
122
|
+
slopePercentPerHour: number | null;
|
|
123
|
+
exhaustedAt: string | null;
|
|
124
|
+
projectedUsedAtResetPercent: number | null;
|
|
125
|
+
sampleCount: number;
|
|
126
|
+
observedSpanHours: number;
|
|
127
|
+
quality: ForecastQuality;
|
|
128
|
+
reasons: readonly string[];
|
|
129
|
+
}
|
|
130
|
+
interface EpochSegment {
|
|
131
|
+
events: QuotaHistoryEventV1[];
|
|
132
|
+
/** Why this segment started (null for the first). */
|
|
133
|
+
reason: "EPOCH_RESET_DETECTED" | "USAGE_DECREASE_SPLIT" | null;
|
|
134
|
+
}
|
|
135
|
+
declare function isMeasurable(e: QuotaHistoryEventV1): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Reset/epoch detection. A new segment starts when the provider-reported reset boundary moves
|
|
138
|
+
* (beyond jitter tolerance) or when used% drops (credit / unannounced reset). Fixed windows only
|
|
139
|
+
* ever compare points inside one segment.
|
|
140
|
+
*/
|
|
141
|
+
declare function detectEpochs(events: readonly QuotaHistoryEventV1[]): EpochSegment[];
|
|
142
|
+
declare function forecastQuota(input: ForecastInput): ForecastResult;
|
|
143
|
+
interface QuotaForecastV1 {
|
|
144
|
+
schemaVersion: 1;
|
|
145
|
+
provider: string;
|
|
146
|
+
windowId: string;
|
|
147
|
+
computedAt: string;
|
|
148
|
+
currentUsedPercent: number | null;
|
|
149
|
+
idealUsedPercent: number | null;
|
|
150
|
+
observedSpanHours: number;
|
|
151
|
+
sampleCount: number;
|
|
152
|
+
slopePercentPerHour: number | null;
|
|
153
|
+
exhaustedAt: string | null;
|
|
154
|
+
projectedUsedAtResetPercent: number | null;
|
|
155
|
+
expectedTimeToLimitHours: number | null;
|
|
156
|
+
mode: ForecastMode;
|
|
157
|
+
quality: ForecastQuality;
|
|
158
|
+
reasons: string[];
|
|
159
|
+
}
|
|
160
|
+
/** Account-safe wrapper: only events sharing the newest event's accountScope are considered. */
|
|
161
|
+
declare function buildForecastV1(input: {
|
|
162
|
+
now: Date;
|
|
163
|
+
provider: string;
|
|
164
|
+
windowId: string;
|
|
165
|
+
observations: readonly QuotaHistoryEventV1[];
|
|
166
|
+
mode: ForecastMode;
|
|
167
|
+
}): QuotaForecastV1;
|
|
168
|
+
export { QuotaSnapshotSchema as C, QuotaWindowSchema as D, QuotaWindow as E, QuotaSnapshot as S, QuotaStatusSchema as T, normalizeWindow as _, detectEpochs as a, ProviderId as b, QuotaHistoryEventV1 as c, decodeHistoryEvent as d, historyEventsFromSnapshot as f, makeUnavailableSnapshot as g, makeObservation as h, buildForecastV1 as i, QuotaHistoryEventV1Schema as l, QuotaObservation as m, ForecastQuality as n, forecastQuota as o, windowProfile as p, QuotaForecastV1 as r, isMeasurable as s, ForecastMode as t, WindowProfile as u, redactSnapshot as v, QuotaStatus as w, ProviderIdSchema as x, ProviderErrorCode as y };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { parseProviderQuota } from "./parsers.mjs";
|
|
2
|
+
import { i as calculatePace, n as PaceInput, r as PaceResult, t as sha256Hex } from "./metrics-D7oNul_9.mjs";
|
|
3
|
+
import { a as createLocalJsonlJournal, i as ReadResult, n as JournalStore, o as defaultHistoryDirectory, r as ReadOptions, t as JournalOptions } from "./history-G4o4HVvW.mjs";
|
|
4
|
+
import { _ as CodexRpcOptions, a as readClaude, c as readAntigravity, d as QuotaProvider, f as builtInProviders, g as parseCodexRateLimits, h as readCodex, i as parseClaudeOAuthUsage, l as PROVIDER_METADATA, m as readAll, n as RefreshPolicy, o as parseAgyVersion, p as isProviderId, r as createQuotaService, s as parseAntigravityUsageReport, t as QuotaService, u as ProviderMetadata, v as CodexTransportError, y as requestCodexRateLimits } from "./index-2k6FkhpT.mjs";
|
|
5
|
+
import "./providers.mjs";
|
|
6
|
+
import { C as QuotaSnapshotSchema, D as QuotaWindowSchema, E as QuotaWindow, S as QuotaSnapshot, T as QuotaStatusSchema, _ as normalizeWindow, a as detectEpochs, b as ProviderId, c as QuotaHistoryEventV1, d as decodeHistoryEvent, f as historyEventsFromSnapshot, g as makeUnavailableSnapshot, h as makeObservation, i as buildForecastV1, l as QuotaHistoryEventV1Schema, n as ForecastQuality, o as forecastQuota, p as windowProfile, r as QuotaForecastV1, s as isMeasurable, t as ForecastMode, u as WindowProfile, v as redactSnapshot, w as QuotaStatus, x as ProviderIdSchema, y as ProviderErrorCode } from "./index-NHzh2TGn.mjs";
|
|
7
|
+
export { type CodexRpcOptions, CodexTransportError, type ForecastMode, type ForecastQuality, type JournalOptions, type JournalStore, PROVIDER_METADATA, type PaceInput, type PaceResult, type ProviderErrorCode, type ProviderId, ProviderIdSchema, type ProviderMetadata, type QuotaForecastV1, type QuotaHistoryEventV1, QuotaHistoryEventV1Schema, type QuotaProvider, type QuotaService, type QuotaSnapshot, QuotaSnapshotSchema, type QuotaStatus, QuotaStatusSchema, type QuotaWindow, QuotaWindowSchema, type ReadOptions, type ReadResult, type RefreshPolicy, type WindowProfile, buildForecastV1, builtInProviders, calculatePace, createLocalJsonlJournal, createQuotaService, decodeHistoryEvent, defaultHistoryDirectory, detectEpochs, forecastQuota, historyEventsFromSnapshot, isMeasurable, isProviderId, makeObservation, makeUnavailableSnapshot, normalizeWindow, parseAgyVersion, parseAntigravityUsageReport, parseClaudeOAuthUsage, parseCodexRateLimits, parseProviderQuota, readAll, readAntigravity, readClaude, readCodex, redactSnapshot, requestCodexRateLimits, sha256Hex, windowProfile };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { a as readAll, c as parseAgyVersion, d as readCodex, f as CodexTransportError, i as isProviderId, l as parseAntigravityUsageReport, m as parseCodexRateLimits, n as PROVIDER_METADATA, o as parseClaudeOAuthUsage, p as requestCodexRateLimits, r as builtInProviders, s as readClaude, t as createQuotaService, u as readAntigravity } from "./src-C48Ew_L9.mjs";
|
|
2
|
+
import { parseProviderQuota } from "./parsers.mjs";
|
|
3
|
+
import { a as makeObservation, c as normalizeWindow, i as isMeasurable, l as redactSnapshot, n as detectEpochs, o as calculatePace, r as forecastQuota, s as makeUnavailableSnapshot, t as buildForecastV1 } from "./metrics-Bzmrfgn-.mjs";
|
|
4
|
+
import { n as defaultHistoryDirectory, t as createLocalJsonlJournal } from "./history-oOXsjdWR.mjs";
|
|
5
|
+
import "./providers.mjs";
|
|
6
|
+
import { a as sha256Hex, c as QuotaStatusSchema, i as windowProfile, l as QuotaWindowSchema, n as decodeHistoryEvent, o as ProviderIdSchema, r as historyEventsFromSnapshot, s as QuotaSnapshotSchema, t as QuotaHistoryEventV1Schema } from "./src-y_QKmTyj.mjs";
|
|
7
|
+
export { CodexTransportError, PROVIDER_METADATA, ProviderIdSchema, QuotaHistoryEventV1Schema, QuotaSnapshotSchema, QuotaStatusSchema, QuotaWindowSchema, buildForecastV1, builtInProviders, calculatePace, createLocalJsonlJournal, createQuotaService, decodeHistoryEvent, defaultHistoryDirectory, detectEpochs, forecastQuota, historyEventsFromSnapshot, isMeasurable, isProviderId, makeObservation, makeUnavailableSnapshot, normalizeWindow, parseAgyVersion, parseAntigravityUsageReport, parseClaudeOAuthUsage, parseCodexRateLimits, parseProviderQuota, readAll, readAntigravity, readClaude, readCodex, redactSnapshot, requestCodexRateLimits, sha256Hex, windowProfile };
|