@narumitw/pi-usage 0.57.0 → 0.59.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/README.md +118 -2
- package/dist/index.ts +1247 -240
- package/dist/index.ts.map +4 -4
- package/package.json +8 -1
- package/src/codex-fast-runtime.ts +34 -26
- package/src/format.ts +189 -3
- package/src/index.ts +20 -0
- package/src/providers/baseten.ts +55 -0
- package/src/providers/fireworks.ts +198 -0
- package/src/providers/minimax.ts +264 -0
- package/src/providers/moonshot.ts +64 -0
- package/src/providers/vercel-ai-gateway.ts +43 -0
- package/src/query.ts +355 -9
- package/src/settings.ts +16 -1
- package/src/types.ts +44 -0
- package/src/usage-settings-ui.ts +125 -33
- package/src/usage.ts +55 -23
package/src/usage-settings-ui.ts
CHANGED
|
@@ -11,12 +11,16 @@ import {
|
|
|
11
11
|
Text,
|
|
12
12
|
} from "@earendil-works/pi-tui";
|
|
13
13
|
import { errorMessage } from "./core.js";
|
|
14
|
+
import { isFireworksAccountId } from "./providers/fireworks.js";
|
|
14
15
|
import type { UsageSettings, UsageSettingsRuntime } from "./settings.js";
|
|
15
16
|
|
|
17
|
+
const AUTO = "Auto";
|
|
18
|
+
const EDIT = "Edit…";
|
|
16
19
|
const OFF = "Off";
|
|
17
20
|
const ON = "On";
|
|
18
21
|
|
|
19
22
|
type UsageSettingId = keyof UsageSettings;
|
|
23
|
+
type SettingsScreenResult = { changed: boolean; editFireworksAccount: boolean };
|
|
20
24
|
|
|
21
25
|
export async function showUsageSettings(
|
|
22
26
|
ctx: ExtensionCommandContext,
|
|
@@ -29,15 +33,38 @@ export async function showUsageSettings(
|
|
|
29
33
|
if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
|
|
30
34
|
return false;
|
|
31
35
|
}
|
|
32
|
-
|
|
36
|
+
let changed = false;
|
|
37
|
+
while (!parentSignal.aborted && isCurrent()) {
|
|
38
|
+
const result = await showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied);
|
|
39
|
+
if (!result) return changed;
|
|
40
|
+
changed ||= result.changed;
|
|
41
|
+
if (!result.editFireworksAccount) return changed;
|
|
42
|
+
changed ||= await editFireworksAccount(
|
|
43
|
+
ctx,
|
|
44
|
+
settingsRuntime,
|
|
45
|
+
parentSignal,
|
|
46
|
+
isCurrent,
|
|
47
|
+
onApplied,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return changed;
|
|
51
|
+
}
|
|
33
52
|
|
|
34
|
-
|
|
53
|
+
async function showSettingsList(
|
|
54
|
+
ctx: ExtensionCommandContext,
|
|
55
|
+
settingsRuntime: UsageSettingsRuntime,
|
|
56
|
+
parentSignal: AbortSignal,
|
|
57
|
+
isCurrent: () => boolean,
|
|
58
|
+
onApplied: (id: UsageSettingId) => void,
|
|
59
|
+
): Promise<SettingsScreenResult | undefined> {
|
|
60
|
+
return ctx.ui.custom<SettingsScreenResult>((tui, theme, _keybindings, done) => {
|
|
35
61
|
const localController = new AbortController();
|
|
36
62
|
const signal = AbortSignal.any([parentSignal, localController.signal]);
|
|
37
63
|
let changed = false;
|
|
38
64
|
let closing = false;
|
|
39
65
|
let saveQueue = Promise.resolve();
|
|
40
66
|
const state = settingsRuntime.get();
|
|
67
|
+
const fireworksValue = state.settings.fireworksAccountId ?? AUTO;
|
|
41
68
|
const items: SettingItem[] = [
|
|
42
69
|
{
|
|
43
70
|
id: "codexFastMode",
|
|
@@ -53,6 +80,15 @@ export async function showUsageSettings(
|
|
|
53
80
|
currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
|
|
54
81
|
values: [OFF, ON],
|
|
55
82
|
},
|
|
83
|
+
{
|
|
84
|
+
id: "fireworksAccountId",
|
|
85
|
+
label: "Fireworks account",
|
|
86
|
+
description: "Select Edit to enter a visible account slug, or submit blank to clear it.",
|
|
87
|
+
currentValue: fireworksValue,
|
|
88
|
+
values: state.settings.fireworksAccountId
|
|
89
|
+
? [state.settings.fireworksAccountId, EDIT]
|
|
90
|
+
: [AUTO, EDIT],
|
|
91
|
+
},
|
|
56
92
|
];
|
|
57
93
|
const container = new Container();
|
|
58
94
|
container.addChild(new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 1));
|
|
@@ -62,7 +98,40 @@ export async function showUsageSettings(
|
|
|
62
98
|
if (closing) return;
|
|
63
99
|
closing = true;
|
|
64
100
|
localController.abort();
|
|
65
|
-
done(changed);
|
|
101
|
+
done({ changed, editFireworksAccount: false });
|
|
102
|
+
};
|
|
103
|
+
const queueUpdate = (
|
|
104
|
+
id: UsageSettingId,
|
|
105
|
+
requested: UsageSettings[UsageSettingId],
|
|
106
|
+
display: string,
|
|
107
|
+
) => {
|
|
108
|
+
saveQueue = saveQueue.then(async () => {
|
|
109
|
+
const previous = settingsRuntime.get().settings[id];
|
|
110
|
+
if (settingsRuntime.get().kind === "invalid") {
|
|
111
|
+
settingsList.updateValue(id, displaySetting(id, previous));
|
|
112
|
+
if (!signal.aborted && isCurrent()) {
|
|
113
|
+
ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
|
|
114
|
+
tui.requestRender();
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await settingsRuntime.update({ [id]: requested }, signal);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (signal.aborted || !isCurrent()) return;
|
|
122
|
+
settingsList.updateValue(id, displaySetting(id, previous));
|
|
123
|
+
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
124
|
+
tui.requestRender();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (previous !== requested) {
|
|
128
|
+
changed = true;
|
|
129
|
+
onApplied(id);
|
|
130
|
+
}
|
|
131
|
+
if (signal.aborted || !isCurrent()) return;
|
|
132
|
+
settingsList.updateValue(id, display);
|
|
133
|
+
tui.requestRender();
|
|
134
|
+
});
|
|
66
135
|
};
|
|
67
136
|
settingsList = new SettingsList(
|
|
68
137
|
items,
|
|
@@ -70,35 +139,18 @@ export async function showUsageSettings(
|
|
|
70
139
|
getSettingsListTheme(),
|
|
71
140
|
(id, value) => {
|
|
72
141
|
if (closing || signal.aborted || !isCurrent()) return;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
|
|
81
|
-
tui.requestRender();
|
|
82
|
-
}
|
|
83
|
-
return;
|
|
142
|
+
if (id === "fireworksAccountId") {
|
|
143
|
+
if (value === EDIT) {
|
|
144
|
+
saveQueue = saveQueue.then(() => {
|
|
145
|
+
if (closing || signal.aborted || !isCurrent()) return;
|
|
146
|
+
closing = true;
|
|
147
|
+
done({ changed, editFireworksAccount: true });
|
|
148
|
+
});
|
|
84
149
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
settingsList.updateValue(id, displayValue(previous));
|
|
90
|
-
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
91
|
-
tui.requestRender();
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (previous !== requested) {
|
|
95
|
-
changed = true;
|
|
96
|
-
onApplied(settingId);
|
|
97
|
-
}
|
|
98
|
-
if (signal.aborted || !isCurrent()) return;
|
|
99
|
-
settingsList.updateValue(id, displayValue(requested));
|
|
100
|
-
tui.requestRender();
|
|
101
|
-
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const settingId = id as "codexFastMode" | "codexStatusResetCountdown";
|
|
153
|
+
queueUpdate(settingId, value !== OFF, value);
|
|
102
154
|
},
|
|
103
155
|
cancel,
|
|
104
156
|
);
|
|
@@ -122,6 +174,46 @@ export async function showUsageSettings(
|
|
|
122
174
|
});
|
|
123
175
|
}
|
|
124
176
|
|
|
125
|
-
function
|
|
126
|
-
|
|
177
|
+
async function editFireworksAccount(
|
|
178
|
+
ctx: ExtensionCommandContext,
|
|
179
|
+
settingsRuntime: UsageSettingsRuntime,
|
|
180
|
+
signal: AbortSignal,
|
|
181
|
+
isCurrent: () => boolean,
|
|
182
|
+
onApplied: (id: UsageSettingId) => void,
|
|
183
|
+
): Promise<boolean> {
|
|
184
|
+
while (!signal.aborted && isCurrent()) {
|
|
185
|
+
const state = settingsRuntime.get();
|
|
186
|
+
if (state.kind === "invalid") {
|
|
187
|
+
ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const entered = await ctx.ui.input(
|
|
191
|
+
"Fireworks account slug · submit blank for Auto",
|
|
192
|
+
state.settings.fireworksAccountId ?? "Example: acme",
|
|
193
|
+
{ signal },
|
|
194
|
+
);
|
|
195
|
+
if (signal.aborted || !isCurrent() || entered === undefined) return false;
|
|
196
|
+
const normalized = entered.trim();
|
|
197
|
+
const requested = normalized || undefined;
|
|
198
|
+
if (requested !== undefined && !isFireworksAccountId(requested)) {
|
|
199
|
+
ctx.ui.notify("Enter a URL-safe Fireworks account slug.", "warning");
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (requested === state.settings.fireworksAccountId) return false;
|
|
203
|
+
try {
|
|
204
|
+
await settingsRuntime.update({ fireworksAccountId: requested }, signal);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (signal.aborted || !isCurrent()) return false;
|
|
207
|
+
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
onApplied("fireworksAccountId");
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function displaySetting(id: UsageSettingId, value: UsageSettings[UsageSettingId]): string {
|
|
217
|
+
if (id === "fireworksAccountId") return typeof value === "string" ? value : AUTO;
|
|
218
|
+
return value ? ON : OFF;
|
|
127
219
|
}
|
package/src/usage.ts
CHANGED
|
@@ -207,13 +207,7 @@ export default function usageExtension(
|
|
|
207
207
|
const generation = statusGeneration;
|
|
208
208
|
statusCountdownTimer = setTimeout(() => {
|
|
209
209
|
statusCountdownTimer = undefined;
|
|
210
|
-
if (
|
|
211
|
-
!sessionActive ||
|
|
212
|
-
generation !== statusGeneration ||
|
|
213
|
-
modelIdentity(ctx.model) !== modelIdentity(model)
|
|
214
|
-
) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
210
|
+
if (!sessionActive || generation !== statusGeneration) return;
|
|
217
211
|
publishStatus(ctx, outcome, model, false);
|
|
218
212
|
}, STATUS_COUNTDOWN_REFRESH_MS);
|
|
219
213
|
statusCountdownTimer.unref?.();
|
|
@@ -254,6 +248,10 @@ export default function usageExtension(
|
|
|
254
248
|
const expectedSessionGeneration = sessionGeneration;
|
|
255
249
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
256
250
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
251
|
+
const expectedFireworksAccountId =
|
|
252
|
+
adapter.id === "fireworks" ? settingsRuntime.get().settings.fireworksAccountId : undefined;
|
|
253
|
+
const querySettings =
|
|
254
|
+
adapter.id === "fireworks" ? { fireworksAccountId: expectedFireworksAccountId } : undefined;
|
|
257
255
|
let auth: ResolvedUsageAuth | undefined;
|
|
258
256
|
try {
|
|
259
257
|
auth = await awaitWithDeadline(
|
|
@@ -277,11 +275,23 @@ export default function usageExtension(
|
|
|
277
275
|
},
|
|
278
276
|
};
|
|
279
277
|
}
|
|
280
|
-
const requiresRequestBoundaryGuard =
|
|
278
|
+
const requiresRequestBoundaryGuard = [
|
|
279
|
+
"baseten",
|
|
280
|
+
"deepseek",
|
|
281
|
+
"fireworks",
|
|
282
|
+
"minimax",
|
|
283
|
+
"minimax-cn",
|
|
284
|
+
"moonshotai",
|
|
285
|
+
"moonshotai-cn",
|
|
286
|
+
"vercel-ai-gateway",
|
|
287
|
+
"xai",
|
|
288
|
+
].includes(adapter.id);
|
|
281
289
|
const requestContextChanged = () =>
|
|
282
290
|
expectedSessionGeneration !== sessionGeneration ||
|
|
283
291
|
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
284
|
-
modelIdentity(ctx.model) !== expectedModelIdentity
|
|
292
|
+
modelIdentity(ctx.model) !== expectedModelIdentity ||
|
|
293
|
+
(adapter.id === "fireworks" &&
|
|
294
|
+
settingsRuntime.get().settings.fireworksAccountId !== expectedFireworksAccountId);
|
|
285
295
|
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
286
296
|
if (!auth) {
|
|
287
297
|
if (displayState === "current") {
|
|
@@ -298,11 +308,15 @@ export default function usageExtension(
|
|
|
298
308
|
authState: "unavailable",
|
|
299
309
|
};
|
|
300
310
|
}
|
|
311
|
+
const queryFingerprint =
|
|
312
|
+
adapter.id === "fireworks"
|
|
313
|
+
? `${auth.fingerprint}:account:${expectedFireworksAccountId ?? "auto"}`
|
|
314
|
+
: auth.fingerprint;
|
|
301
315
|
if (displayState === "current") {
|
|
302
|
-
transitionCurrentIdentity(`${adapter.id}:${
|
|
316
|
+
transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
|
|
303
317
|
}
|
|
304
318
|
|
|
305
|
-
const cached = !force ? cache.get(adapter.id,
|
|
319
|
+
const cached = !force ? cache.get(adapter.id, queryFingerprint) : undefined;
|
|
306
320
|
if (cached) {
|
|
307
321
|
return {
|
|
308
322
|
state: {
|
|
@@ -316,7 +330,7 @@ export default function usageExtension(
|
|
|
316
330
|
};
|
|
317
331
|
}
|
|
318
332
|
|
|
319
|
-
const failureKey = `${adapter.id}:${
|
|
333
|
+
const failureKey = `${adapter.id}:${queryFingerprint}`;
|
|
320
334
|
const previousFailure = failureBackoff.get(failureKey);
|
|
321
335
|
if (!force && previousFailure && previousFailure.until > Date.now()) {
|
|
322
336
|
return {
|
|
@@ -335,7 +349,7 @@ export default function usageExtension(
|
|
|
335
349
|
const queryId = querySequence;
|
|
336
350
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
337
351
|
|
|
338
|
-
let
|
|
352
|
+
let retryableAuthChanged = false;
|
|
339
353
|
try {
|
|
340
354
|
const remainingMs = Math.max(1, deadlineAt - Date.now());
|
|
341
355
|
const guard = requiresRequestBoundaryGuard
|
|
@@ -349,18 +363,27 @@ export default function usageExtension(
|
|
|
349
363
|
);
|
|
350
364
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
351
365
|
if (revalidated?.fingerprint !== auth.fingerprint) {
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
throw new Error(
|
|
366
|
+
if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
|
|
367
|
+
retryableAuthChanged = true;
|
|
368
|
+
throw new Error(
|
|
369
|
+
`${adapter.displayName} runtime credential changed during the usage query.`,
|
|
370
|
+
);
|
|
355
371
|
}
|
|
356
372
|
throw abortError();
|
|
357
373
|
}
|
|
358
374
|
}
|
|
359
375
|
: undefined;
|
|
360
|
-
const report = await queryProviderUsage(
|
|
376
|
+
const report = await queryProviderUsage(
|
|
377
|
+
adapter,
|
|
378
|
+
auth,
|
|
379
|
+
signal,
|
|
380
|
+
remainingMs,
|
|
381
|
+
guard,
|
|
382
|
+
querySettings,
|
|
383
|
+
);
|
|
361
384
|
if (guard) await guard();
|
|
362
385
|
if (latestQueries.get(failureKey) === queryId) {
|
|
363
|
-
cache.set(adapter.id,
|
|
386
|
+
cache.set(adapter.id, queryFingerprint, report);
|
|
364
387
|
failureBackoff.delete(failureKey);
|
|
365
388
|
}
|
|
366
389
|
return {
|
|
@@ -376,7 +399,7 @@ export default function usageExtension(
|
|
|
376
399
|
} catch (error) {
|
|
377
400
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
378
401
|
if (
|
|
379
|
-
|
|
402
|
+
retryableAuthChanged &&
|
|
380
403
|
authRetry === 0 &&
|
|
381
404
|
!signal.aborted &&
|
|
382
405
|
!requestContextChanged() &&
|
|
@@ -1103,7 +1126,7 @@ export default function usageExtension(
|
|
|
1103
1126
|
}
|
|
1104
1127
|
},
|
|
1105
1128
|
});
|
|
1106
|
-
pi.on("session_start", (_event, ctx) => {
|
|
1129
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
1107
1130
|
sessionGeneration += 1;
|
|
1108
1131
|
statusGeneration += 1;
|
|
1109
1132
|
clearStatusTimers();
|
|
@@ -1111,7 +1134,13 @@ export default function usageExtension(
|
|
|
1111
1134
|
activeControllers.clear();
|
|
1112
1135
|
statusController = undefined;
|
|
1113
1136
|
sessionActive = true;
|
|
1114
|
-
|
|
1137
|
+
const ownerGeneration = sessionGeneration;
|
|
1138
|
+
try {
|
|
1139
|
+
await fastRuntime.prepareSession(ctx);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
if (isStaleExtensionContextError(error) || ownerGeneration !== sessionGeneration) return;
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1115
1144
|
});
|
|
1116
1145
|
pi.on("session_tree", (_event, ctx) => {
|
|
1117
1146
|
startStatusRefresh(ctx, ctx.model, false);
|
|
@@ -1137,7 +1166,10 @@ export default function usageExtension(
|
|
|
1137
1166
|
safeSetStatus(ctx, undefined);
|
|
1138
1167
|
});
|
|
1139
1168
|
|
|
1140
|
-
fastRuntime = registerCodexFastMode(
|
|
1141
|
-
|
|
1169
|
+
fastRuntime = registerCodexFastMode(
|
|
1170
|
+
pi,
|
|
1171
|
+
settingsRuntime,
|
|
1172
|
+
(ctx) => startStatusRefresh(ctx, ctx.model, false),
|
|
1173
|
+
{ registerSessionStart: false },
|
|
1142
1174
|
);
|
|
1143
1175
|
}
|