@diousk/pi-usage-fast 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 +259 -0
- package/dist/index.ts +5478 -0
- package/dist/index.ts.map +7 -0
- package/docs/operations.md +31 -0
- package/docs/providers.md +273 -0
- package/package.json +82 -0
- package/src/codex-fast-runtime.ts +184 -0
- package/src/codex-fast.ts +118 -0
- package/src/codex-resets.ts +347 -0
- package/src/core.ts +245 -0
- package/src/format.ts +678 -0
- package/src/index.ts +116 -0
- package/src/oauth-credential-source.ts +117 -0
- package/src/providers/baseten.ts +51 -0
- package/src/providers/codex.ts +139 -0
- package/src/providers/deepseek.ts +80 -0
- package/src/providers/fireworks.ts +291 -0
- package/src/providers/github-copilot.ts +114 -0
- package/src/providers/kimi-coding.ts +265 -0
- package/src/providers/minimax.ts +280 -0
- package/src/providers/moonshot.ts +59 -0
- package/src/providers/opencode-zen.ts +80 -0
- package/src/providers/openrouter.ts +70 -0
- package/src/providers/vercel-ai-gateway.ts +43 -0
- package/src/providers/xai.ts +179 -0
- package/src/providers/zai-errors.ts +94 -0
- package/src/providers/zai.ts +239 -0
- package/src/query.ts +1073 -0
- package/src/settings.ts +366 -0
- package/src/types.ts +235 -0
- package/src/usage-helpers.ts +40 -0
- package/src/usage-settings-ui.ts +152 -0
- package/src/usage-targets.ts +127 -0
- package/src/usage.ts +1335 -0
package/src/settings.ts
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { chmod, mkdir, open, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { isFireworksAccountId } from "./providers/fireworks.js";
|
|
7
|
+
import { isBoundedTargetId } from "./usage-targets.js";
|
|
8
|
+
|
|
9
|
+
export const USAGE_SETTINGS_FILE = "pi-usage.json";
|
|
10
|
+
export const MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
|
|
11
|
+
|
|
12
|
+
export type CodexStatusPercentage = "remaining" | "used";
|
|
13
|
+
|
|
14
|
+
export interface UsageSettings {
|
|
15
|
+
codexFastMode: boolean;
|
|
16
|
+
codexStatusResetCountdown: boolean;
|
|
17
|
+
codexStatusPercentage: CodexStatusPercentage;
|
|
18
|
+
selectedTargets: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_USAGE_SETTINGS: Readonly<UsageSettings> = Object.freeze({
|
|
22
|
+
codexFastMode: false,
|
|
23
|
+
codexStatusResetCountdown: true,
|
|
24
|
+
codexStatusPercentage: "remaining",
|
|
25
|
+
selectedTargets: Object.freeze({}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export interface UsageSettingsState {
|
|
29
|
+
kind: "missing" | "loaded" | "invalid";
|
|
30
|
+
path: string;
|
|
31
|
+
settings: UsageSettings;
|
|
32
|
+
document?: Record<string, unknown>;
|
|
33
|
+
issue?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type UsageTargetPublicationCheck = () => Promise<void>;
|
|
37
|
+
|
|
38
|
+
export interface UsageSettingsRuntime {
|
|
39
|
+
get(): Readonly<UsageSettingsState>;
|
|
40
|
+
reload(signal?: AbortSignal): Promise<Readonly<UsageSettingsState>>;
|
|
41
|
+
update(patch: Partial<UsageSettings>, signal?: AbortSignal): Promise<Readonly<UsageSettingsState>>;
|
|
42
|
+
updateSelectedTarget(
|
|
43
|
+
providerId: string,
|
|
44
|
+
targetId: string,
|
|
45
|
+
signal?: AbortSignal,
|
|
46
|
+
checkPublishedSelection?: UsageTargetPublicationCheck,
|
|
47
|
+
): Promise<Readonly<UsageSettingsState>>;
|
|
48
|
+
flush(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface UsageSettingsFileOperations {
|
|
52
|
+
rename: typeof rename;
|
|
53
|
+
writeFile: typeof writeFile;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface UsageSettingsRuntimeOptions {
|
|
57
|
+
operations?: Partial<UsageSettingsFileOperations>;
|
|
58
|
+
path?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function usageSettingsPath(): string {
|
|
62
|
+
return join(getAgentDir(), USAGE_SETTINGS_FILE);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function normalizeUsageSettings(value: unknown): UsageSettings | undefined {
|
|
66
|
+
if (!isRecord(value)) return undefined;
|
|
67
|
+
if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (Object.hasOwn(value, "codexStatusResetCountdown") && typeof value.codexStatusResetCountdown !== "boolean") {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
Object.hasOwn(value, "codexStatusPercentage") &&
|
|
75
|
+
value.codexStatusPercentage !== "remaining" &&
|
|
76
|
+
value.codexStatusPercentage !== "used"
|
|
77
|
+
) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const selectedTargets = normalizeSelectedTargets(value.selectedTargets);
|
|
84
|
+
if (Object.hasOwn(value, "selectedTargets") && !selectedTargets) return undefined;
|
|
85
|
+
const effectiveTargets = { ...(selectedTargets ?? {}) };
|
|
86
|
+
if (!effectiveTargets.fireworks && isFireworksAccountId(value.fireworksAccountId)) {
|
|
87
|
+
effectiveTargets.fireworks = value.fireworksAccountId;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
codexFastMode:
|
|
91
|
+
typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
|
|
92
|
+
codexStatusResetCountdown:
|
|
93
|
+
typeof value.codexStatusResetCountdown === "boolean"
|
|
94
|
+
? value.codexStatusResetCountdown
|
|
95
|
+
: DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
|
|
96
|
+
codexStatusPercentage:
|
|
97
|
+
value.codexStatusPercentage === "used" ? "used" : DEFAULT_USAGE_SETTINGS.codexStatusPercentage,
|
|
98
|
+
selectedTargets: effectiveTargets,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function loadUsageSettings(path = usageSettingsPath(), signal?: AbortSignal): Promise<UsageSettingsState> {
|
|
103
|
+
throwIfAborted(signal);
|
|
104
|
+
try {
|
|
105
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
106
|
+
let text: string;
|
|
107
|
+
try {
|
|
108
|
+
const stats = await handle.stat();
|
|
109
|
+
throwIfAborted(signal);
|
|
110
|
+
if (!stats.isFile()) throw new Error("settings path is not a regular file");
|
|
111
|
+
if (stats.size > MAX_USAGE_SETTINGS_BYTES) {
|
|
112
|
+
throw new Error("settings file exceeds 64 KiB");
|
|
113
|
+
}
|
|
114
|
+
text = await handle.readFile("utf8");
|
|
115
|
+
} finally {
|
|
116
|
+
await handle.close();
|
|
117
|
+
}
|
|
118
|
+
throwIfAborted(signal);
|
|
119
|
+
const document = JSON.parse(text) as unknown;
|
|
120
|
+
const settings = normalizeUsageSettings(document);
|
|
121
|
+
if (!settings || !isRecord(document)) throw new Error("invalid settings shape");
|
|
122
|
+
return { kind: "loaded", path, settings, document };
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (signal?.aborted) throw error;
|
|
125
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
126
|
+
return {
|
|
127
|
+
kind: "missing",
|
|
128
|
+
path,
|
|
129
|
+
settings: { ...DEFAULT_USAGE_SETTINGS },
|
|
130
|
+
document: {},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
kind: "invalid",
|
|
135
|
+
path,
|
|
136
|
+
settings: { ...DEFAULT_USAGE_SETTINGS },
|
|
137
|
+
issue:
|
|
138
|
+
isNodeError(error) && error.code === "ELOOP"
|
|
139
|
+
? "symbolic links are not accepted"
|
|
140
|
+
: error instanceof Error
|
|
141
|
+
? error.message
|
|
142
|
+
: String(error),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createUsageSettingsRuntime(options: UsageSettingsRuntimeOptions | string = {}): UsageSettingsRuntime {
|
|
148
|
+
const path = typeof options === "string" ? options : (options.path ?? usageSettingsPath());
|
|
149
|
+
const operations: UsageSettingsFileOperations = {
|
|
150
|
+
rename,
|
|
151
|
+
writeFile,
|
|
152
|
+
...(typeof options === "string" ? undefined : options.operations),
|
|
153
|
+
};
|
|
154
|
+
let state: UsageSettingsState = {
|
|
155
|
+
kind: "missing",
|
|
156
|
+
path,
|
|
157
|
+
settings: { ...DEFAULT_USAGE_SETTINGS },
|
|
158
|
+
document: {},
|
|
159
|
+
};
|
|
160
|
+
let queue = Promise.resolve();
|
|
161
|
+
const enqueue = <T>(operation: () => Promise<T>): Promise<T> => {
|
|
162
|
+
const result = queue.then(operation, operation);
|
|
163
|
+
queue = result.then(
|
|
164
|
+
() => undefined,
|
|
165
|
+
() => undefined,
|
|
166
|
+
);
|
|
167
|
+
return result;
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
get: () => structuredClone(state),
|
|
171
|
+
reload: (signal) =>
|
|
172
|
+
enqueue(async () => {
|
|
173
|
+
const loaded = await loadUsageSettings(path, signal);
|
|
174
|
+
state = loaded;
|
|
175
|
+
return structuredClone(state);
|
|
176
|
+
}),
|
|
177
|
+
update: (patch, signal) =>
|
|
178
|
+
enqueue(async () => {
|
|
179
|
+
const saved = await saveUsageSettingsPatch(path, patch, operations, signal);
|
|
180
|
+
state = saved;
|
|
181
|
+
return structuredClone(state);
|
|
182
|
+
}),
|
|
183
|
+
updateSelectedTarget: (providerId, targetId, signal, checkPublishedSelection) =>
|
|
184
|
+
enqueue(async () => {
|
|
185
|
+
const transaction = await saveUsageTargetSelection(path, providerId, targetId, operations, signal);
|
|
186
|
+
try {
|
|
187
|
+
await checkPublishedSelection?.();
|
|
188
|
+
throwIfAborted(signal);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
try {
|
|
191
|
+
await restoreUsageSettingsState(path, transaction.saved, transaction.previous, operations);
|
|
192
|
+
state = transaction.previous;
|
|
193
|
+
} catch (rollbackError) {
|
|
194
|
+
state = await loadUsageSettings(path);
|
|
195
|
+
throw new AggregateError(
|
|
196
|
+
[error, rollbackError],
|
|
197
|
+
"Target selection changed after publication and pi-usage.json rollback failed",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
state = transaction.saved;
|
|
203
|
+
return structuredClone(state);
|
|
204
|
+
}),
|
|
205
|
+
flush: () => queue,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function saveUsageSettingsPatch(
|
|
210
|
+
path: string,
|
|
211
|
+
patch: Partial<UsageSettings>,
|
|
212
|
+
operations: UsageSettingsFileOperations,
|
|
213
|
+
signal?: AbortSignal,
|
|
214
|
+
): Promise<UsageSettingsState> {
|
|
215
|
+
return saveUsageSettingsDocument(
|
|
216
|
+
path,
|
|
217
|
+
(document) => {
|
|
218
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
219
|
+
if (value === undefined) delete document[key];
|
|
220
|
+
else document[key] = value;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
operations,
|
|
224
|
+
signal,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function saveUsageTargetSelection(
|
|
229
|
+
path: string,
|
|
230
|
+
providerId: string,
|
|
231
|
+
targetId: string,
|
|
232
|
+
operations: UsageSettingsFileOperations,
|
|
233
|
+
signal?: AbortSignal,
|
|
234
|
+
): Promise<{ saved: UsageSettingsState; previous: UsageSettingsState }> {
|
|
235
|
+
if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) {
|
|
236
|
+
throw new Error("Refusing to save an invalid usage target selection");
|
|
237
|
+
}
|
|
238
|
+
const previous = await loadUsageSettings(path, signal);
|
|
239
|
+
const saved = await saveUsageSettingsDocument(
|
|
240
|
+
path,
|
|
241
|
+
(document) => {
|
|
242
|
+
document.selectedTargets = {
|
|
243
|
+
...(normalizeSelectedTargets(document.selectedTargets) ?? {}),
|
|
244
|
+
[providerId]: targetId,
|
|
245
|
+
};
|
|
246
|
+
if (providerId === "fireworks") delete document.fireworksAccountId;
|
|
247
|
+
},
|
|
248
|
+
operations,
|
|
249
|
+
signal,
|
|
250
|
+
previous,
|
|
251
|
+
);
|
|
252
|
+
return { saved, previous };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function saveUsageSettingsDocument(
|
|
256
|
+
path: string,
|
|
257
|
+
mutate: (document: Record<string, unknown>) => void,
|
|
258
|
+
operations: UsageSettingsFileOperations,
|
|
259
|
+
signal?: AbortSignal,
|
|
260
|
+
expected?: UsageSettingsState,
|
|
261
|
+
): Promise<UsageSettingsState> {
|
|
262
|
+
const latest = await loadUsageSettings(path, signal);
|
|
263
|
+
if (latest.kind === "invalid") {
|
|
264
|
+
throw new Error("Cannot overwrite an invalid pi-usage.json; repair it and reload first");
|
|
265
|
+
}
|
|
266
|
+
if (expected && !sameUsageSettingsDocument(latest, expected)) {
|
|
267
|
+
throw new Error("pi-usage.json changed while saving; retry the action");
|
|
268
|
+
}
|
|
269
|
+
const document = { ...latest.document };
|
|
270
|
+
mutate(document);
|
|
271
|
+
const settings = normalizeUsageSettings(document);
|
|
272
|
+
if (!settings) throw new Error("Refusing to save invalid pi-usage settings");
|
|
273
|
+
const directory = dirname(path);
|
|
274
|
+
const temporaryPath = join(directory, `.${basename(path)}.${randomUUID()}.tmp`);
|
|
275
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
276
|
+
throwIfAborted(signal);
|
|
277
|
+
try {
|
|
278
|
+
await operations.writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}\n`, {
|
|
279
|
+
encoding: "utf8",
|
|
280
|
+
flag: "wx",
|
|
281
|
+
mode: 0o600,
|
|
282
|
+
});
|
|
283
|
+
if (process.platform !== "win32") await chmodPrivate(temporaryPath);
|
|
284
|
+
throwIfAborted(signal);
|
|
285
|
+
const current = await loadUsageSettings(path, signal);
|
|
286
|
+
if (
|
|
287
|
+
current.kind === "invalid" ||
|
|
288
|
+
current.kind !== latest.kind ||
|
|
289
|
+
JSON.stringify(current.document) !== JSON.stringify(latest.document)
|
|
290
|
+
) {
|
|
291
|
+
throw new Error("pi-usage.json changed while saving; retry the action");
|
|
292
|
+
}
|
|
293
|
+
throwIfAborted(signal);
|
|
294
|
+
await operations.rename(temporaryPath, path);
|
|
295
|
+
} finally {
|
|
296
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
297
|
+
}
|
|
298
|
+
return { kind: "loaded", path, settings, document };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function restoreUsageSettingsState(
|
|
302
|
+
path: string,
|
|
303
|
+
published: UsageSettingsState,
|
|
304
|
+
previous: UsageSettingsState,
|
|
305
|
+
operations: UsageSettingsFileOperations,
|
|
306
|
+
): Promise<void> {
|
|
307
|
+
if (previous.kind === "missing") {
|
|
308
|
+
const current = await loadUsageSettings(path);
|
|
309
|
+
if (!sameUsageSettingsDocument(current, published)) {
|
|
310
|
+
throw new Error("pi-usage.json changed before target selection rollback");
|
|
311
|
+
}
|
|
312
|
+
await rm(path);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (previous.kind !== "loaded" || !previous.document) {
|
|
316
|
+
throw new Error("Cannot restore invalid prior pi-usage.json settings");
|
|
317
|
+
}
|
|
318
|
+
await saveUsageSettingsDocument(
|
|
319
|
+
path,
|
|
320
|
+
(document) => {
|
|
321
|
+
for (const key of Object.keys(document)) delete document[key];
|
|
322
|
+
Object.assign(document, previous.document);
|
|
323
|
+
},
|
|
324
|
+
operations,
|
|
325
|
+
undefined,
|
|
326
|
+
published,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function sameUsageSettingsDocument(
|
|
331
|
+
left: Pick<UsageSettingsState, "kind" | "document">,
|
|
332
|
+
right: Pick<UsageSettingsState, "kind" | "document">,
|
|
333
|
+
): boolean {
|
|
334
|
+
return left.kind === right.kind && JSON.stringify(left.document) === JSON.stringify(right.document);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function chmodPrivate(path: string): Promise<void> {
|
|
338
|
+
await chmod(path, 0o600);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
342
|
+
if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
346
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
350
|
+
return error instanceof Error && "code" in error;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function normalizeSelectedTargets(value: unknown): Record<string, string> | undefined {
|
|
354
|
+
if (value === undefined) return {};
|
|
355
|
+
if (!isRecord(value)) return undefined;
|
|
356
|
+
const targets: Record<string, string> = {};
|
|
357
|
+
for (const [providerId, targetId] of Object.entries(value)) {
|
|
358
|
+
if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) return undefined;
|
|
359
|
+
targets[providerId] = targetId;
|
|
360
|
+
}
|
|
361
|
+
return targets;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function isProviderId(value: string): boolean {
|
|
365
|
+
return /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/u.test(value);
|
|
366
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export type PiModel = NonNullable<ExtensionContext["model"]>;
|
|
4
|
+
export type UsageModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
5
|
+
|
|
6
|
+
export type UsageSemanticsKind = "consumer-subscription" | "api-key" | "project";
|
|
7
|
+
export type UsageUnit = "percent" | "usd" | "currency" | "count";
|
|
8
|
+
export type UsageDisplayState = "current" | "configured";
|
|
9
|
+
|
|
10
|
+
export interface UsageSemantics {
|
|
11
|
+
kind: UsageSemanticsKind;
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UsageBucket {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
groupId?: string;
|
|
19
|
+
groupLabel?: string;
|
|
20
|
+
modelKeys?: string[];
|
|
21
|
+
used?: number;
|
|
22
|
+
remaining?: number;
|
|
23
|
+
limit?: number;
|
|
24
|
+
unit: UsageUnit;
|
|
25
|
+
period?: string;
|
|
26
|
+
windowMinutes?: number;
|
|
27
|
+
resetsAt?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface UsageMetric {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
value: number | string;
|
|
34
|
+
unit?: UsageUnit;
|
|
35
|
+
currency?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UsageReport {
|
|
39
|
+
providerId: string;
|
|
40
|
+
providerName: string;
|
|
41
|
+
capturedAt: number;
|
|
42
|
+
source: string;
|
|
43
|
+
semantics: UsageSemantics;
|
|
44
|
+
accountLabel?: string;
|
|
45
|
+
buckets: UsageBucket[];
|
|
46
|
+
metrics: UsageMetric[];
|
|
47
|
+
notes?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ResolvedUsageAuth {
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
headers: Record<string, string>;
|
|
53
|
+
fingerprint: string;
|
|
54
|
+
secrets: string[];
|
|
55
|
+
model: PiModel;
|
|
56
|
+
auth?: {
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
headers?: Record<string, string | null>;
|
|
59
|
+
baseUrl?: string;
|
|
60
|
+
};
|
|
61
|
+
env?: Record<string, string>;
|
|
62
|
+
source?: string;
|
|
63
|
+
effectiveBaseUrl?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface UsageQuerySettings {
|
|
67
|
+
fireworksAccountId?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type UsageRequestGuard = () => Promise<void>;
|
|
71
|
+
|
|
72
|
+
export interface UsageProviderTarget {
|
|
73
|
+
id: string;
|
|
74
|
+
label: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface UsageTargetResolver {
|
|
79
|
+
singularLabel: string;
|
|
80
|
+
pluralLabel: string;
|
|
81
|
+
list(
|
|
82
|
+
auth: ResolvedUsageAuth,
|
|
83
|
+
signal: AbortSignal,
|
|
84
|
+
timeoutMs: number,
|
|
85
|
+
guard: UsageRequestGuard,
|
|
86
|
+
): Promise<readonly UsageProviderTarget[]>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface UsageProviderAdapter {
|
|
90
|
+
id: string;
|
|
91
|
+
displayName: string;
|
|
92
|
+
semantics: UsageSemantics;
|
|
93
|
+
publishesStatusline?: boolean;
|
|
94
|
+
/** Invalidate the matching ready report when the latest query fails; default preserves it. */
|
|
95
|
+
invalidateCacheOnFailure?: boolean;
|
|
96
|
+
targets?: UsageTargetResolver;
|
|
97
|
+
query(
|
|
98
|
+
auth: ResolvedUsageAuth,
|
|
99
|
+
signal: AbortSignal,
|
|
100
|
+
timeoutMs: number,
|
|
101
|
+
guard?: UsageRequestGuard,
|
|
102
|
+
targetId?: string,
|
|
103
|
+
): Promise<UsageReport>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ProviderUsageState =
|
|
107
|
+
| {
|
|
108
|
+
providerId: string;
|
|
109
|
+
providerName: string;
|
|
110
|
+
displayState: UsageDisplayState;
|
|
111
|
+
status: "ready";
|
|
112
|
+
report: UsageReport;
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
providerId: string;
|
|
116
|
+
providerName: string;
|
|
117
|
+
displayState: UsageDisplayState;
|
|
118
|
+
status: "selection-required";
|
|
119
|
+
singularLabel: string;
|
|
120
|
+
pluralLabel: string;
|
|
121
|
+
choices: readonly UsageProviderTarget[];
|
|
122
|
+
}
|
|
123
|
+
| {
|
|
124
|
+
providerId: string;
|
|
125
|
+
providerName: string;
|
|
126
|
+
displayState: UsageDisplayState;
|
|
127
|
+
status: "unsupported" | "auth-unavailable" | "query-failed";
|
|
128
|
+
message: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type BasetenBillingUsagePayload = {
|
|
132
|
+
dedicated_usage?: unknown;
|
|
133
|
+
model_apis_usage?: unknown;
|
|
134
|
+
training_usage?: unknown;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export type DeepSeekBalancePayload = {
|
|
138
|
+
is_available?: unknown;
|
|
139
|
+
balance_infos?: unknown;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export type FireworksAccountsPayload = {
|
|
143
|
+
accounts?: unknown;
|
|
144
|
+
nextPageToken?: unknown;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export type FireworksBillingSummaryPayload = {
|
|
148
|
+
lineItems?: unknown;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type GitHubCopilotUsagePayload = {
|
|
152
|
+
login?: unknown;
|
|
153
|
+
copilot_plan?: unknown;
|
|
154
|
+
access_type_sku?: unknown;
|
|
155
|
+
limited_user_quotas?: unknown;
|
|
156
|
+
limited_user_reset_date?: unknown;
|
|
157
|
+
monthly_quotas?: unknown;
|
|
158
|
+
quota_reset_date?: unknown;
|
|
159
|
+
quota_reset_date_utc?: unknown;
|
|
160
|
+
quota_snapshots?: unknown;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export type OpenRouterKeyPayload = {
|
|
164
|
+
data?: unknown;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export type MiniMaxUsagePayload = {
|
|
168
|
+
available_amount?: unknown;
|
|
169
|
+
balance_alert_switch?: unknown;
|
|
170
|
+
balance_alert_threshold?: unknown;
|
|
171
|
+
base_resp?: unknown;
|
|
172
|
+
cash_balance?: unknown;
|
|
173
|
+
credit_balance?: unknown;
|
|
174
|
+
model_remains?: unknown;
|
|
175
|
+
owed_amount?: unknown;
|
|
176
|
+
voucher_balance?: unknown;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export type MoonshotBalancePayload = {
|
|
180
|
+
code?: unknown;
|
|
181
|
+
data?: unknown;
|
|
182
|
+
scode?: unknown;
|
|
183
|
+
status?: unknown;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export type VercelAIGatewayCreditsPayload = {
|
|
187
|
+
balance?: unknown;
|
|
188
|
+
total_used?: unknown;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export type OpenCodeZenPayload = {
|
|
192
|
+
usage?: unknown;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export type ZaiQuotaPayload = {
|
|
196
|
+
error?: unknown;
|
|
197
|
+
code?: unknown;
|
|
198
|
+
success?: unknown;
|
|
199
|
+
data?: unknown;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export type ZaiSubscriptionPayload = {
|
|
203
|
+
error?: unknown;
|
|
204
|
+
code?: unknown;
|
|
205
|
+
success?: unknown;
|
|
206
|
+
data?: unknown;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export interface ZaiPlanInfo {
|
|
210
|
+
name: string;
|
|
211
|
+
renewsAt?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export type KimiCodingUsagePayload = {
|
|
215
|
+
usage?: unknown;
|
|
216
|
+
limits?: unknown;
|
|
217
|
+
boosterWallet?: unknown;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export type XaiUserPayload = {
|
|
221
|
+
userId?: unknown;
|
|
222
|
+
subscriptionTier?: unknown;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export type XaiBillingPayload = {
|
|
226
|
+
config?: unknown;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export type CodexBackendPayload = {
|
|
230
|
+
plan_type?: unknown;
|
|
231
|
+
rate_limit?: unknown;
|
|
232
|
+
additional_rate_limits?: unknown;
|
|
233
|
+
credits?: unknown;
|
|
234
|
+
rate_limit_reset_credits?: unknown;
|
|
235
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { sanitizeDisplayText } from "./core.js";
|
|
3
|
+
import { providerIsConfigured, usageAdapters } from "./query.js";
|
|
4
|
+
import type { PiModel, UsageProviderAdapter } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export function configuredAdapters(ctx: ExtensionContext): UsageProviderAdapter[] {
|
|
7
|
+
return usageAdapters().filter(
|
|
8
|
+
(adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id),
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function providerDisplayName(ctx: ExtensionContext, providerId: string): string {
|
|
13
|
+
try {
|
|
14
|
+
return sanitizeDisplayText(ctx.modelRegistry.getProviderDisplayName(providerId), 80);
|
|
15
|
+
} catch {
|
|
16
|
+
return sanitizeDisplayText(providerId, 80);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function setBoundedMap<T>(map: Map<string, T>, key: string, value: T, limit: number): void {
|
|
21
|
+
map.delete(key);
|
|
22
|
+
while (map.size >= limit) {
|
|
23
|
+
const oldest = map.keys().next().value;
|
|
24
|
+
if (oldest === undefined) break;
|
|
25
|
+
map.delete(oldest);
|
|
26
|
+
}
|
|
27
|
+
map.set(key, value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function modelIdentity(model: PiModel | undefined): string | undefined {
|
|
31
|
+
return model ? `${model.provider}/${model.id}` : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isAbortError(error: unknown): boolean {
|
|
35
|
+
return error instanceof Error && error.name === "AbortError";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isTimeoutError(error: unknown): boolean {
|
|
39
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
40
|
+
}
|