pi-better-openai 0.1.18 → 0.1.21
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 +90 -1
- package/index.ts +308 -1058
- package/package.json +9 -4
- package/src/codex-auth.ts +136 -0
- package/src/config.ts +297 -2
- package/src/fast-controller.ts +109 -0
- package/src/format.ts +81 -20
- package/src/image.ts +182 -200
- package/src/paths.ts +17 -0
- package/src/pet-footer-controller.ts +627 -0
- package/src/pets.ts +212 -63
- package/src/usage-controller.ts +299 -0
- package/src/usage.ts +99 -48
package/index.ts
CHANGED
|
@@ -4,37 +4,37 @@
|
|
|
4
4
|
* Adds `service_tier: "priority"` to OpenAI provider payloads while fast mode is
|
|
5
5
|
* enabled and the selected model is in the configured allow-list.
|
|
6
6
|
*/
|
|
7
|
+
import { sep } from "node:path";
|
|
7
8
|
import {
|
|
8
9
|
getSettingsListTheme,
|
|
9
10
|
type ExtensionAPI,
|
|
10
11
|
type ExtensionContext,
|
|
11
|
-
} from "@
|
|
12
|
-
import {
|
|
13
|
-
calculateImageRows,
|
|
14
|
-
Container,
|
|
15
|
-
getCapabilities,
|
|
16
|
-
getCellDimensions,
|
|
17
|
-
Key,
|
|
18
|
-
matchesKey,
|
|
19
|
-
SettingsList,
|
|
20
|
-
} from "@mariozechner/pi-tui";
|
|
12
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { Container, Key, matchesKey, SettingsList } from "@earendil-works/pi-tui";
|
|
21
14
|
import { CONFIG_BASENAME, STATUS_KEY } from "./src/identity.ts";
|
|
22
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
formatTokens,
|
|
17
|
+
redactDiagnosticValue,
|
|
18
|
+
sanitizeStatusText,
|
|
19
|
+
truncateToWidth,
|
|
20
|
+
visibleWidth,
|
|
21
|
+
} from "./src/format.ts";
|
|
23
22
|
import {
|
|
24
23
|
DEFAULT_CONFIG,
|
|
25
24
|
DEFAULT_IMAGE_CONFIG,
|
|
26
25
|
DEFAULT_PET_CONFIG,
|
|
27
26
|
DEFAULT_SUPPORTED_MODELS,
|
|
28
|
-
FOOTER_MODES,
|
|
29
|
-
IMAGE_OUTPUT_FORMATS,
|
|
30
|
-
IMAGE_SAVE_MODES,
|
|
31
|
-
PET_PLACEMENTS,
|
|
32
27
|
PET_STATES,
|
|
28
|
+
FOOTER_SETTING_DESCRIPTORS,
|
|
29
|
+
USAGE_SETTING_DESCRIPTORS,
|
|
30
|
+
IMAGE_SETTING_DESCRIPTORS,
|
|
31
|
+
PET_SETTING_DESCRIPTORS,
|
|
32
|
+
FAST_SETTING_DESCRIPTORS,
|
|
33
|
+
type SettingsOptionDescriptor,
|
|
33
34
|
configPaths,
|
|
34
35
|
type ResolvedConfig,
|
|
35
36
|
type PetPlacement,
|
|
36
37
|
type PetState,
|
|
37
|
-
type SupportedModel,
|
|
38
38
|
isRecord,
|
|
39
39
|
parseModelKey,
|
|
40
40
|
normalizeModelKeys,
|
|
@@ -42,42 +42,34 @@ import {
|
|
|
42
42
|
readRawConfig,
|
|
43
43
|
resolveConfig,
|
|
44
44
|
writeConfig,
|
|
45
|
+
applySettingToRawConfig,
|
|
45
46
|
} from "./src/config.ts";
|
|
46
47
|
import {
|
|
47
|
-
AUTH_FILE,
|
|
48
|
-
type UsageSnapshot,
|
|
49
48
|
formatPercent,
|
|
50
|
-
formatResetCountdown,
|
|
51
49
|
formatUsageSnapshot,
|
|
52
50
|
parseUsageSnapshot,
|
|
53
51
|
readCodexAuth,
|
|
54
|
-
requestCodexUsage,
|
|
55
52
|
} from "./src/usage.ts";
|
|
56
53
|
import { registerOpenAIImage, _imageTest } from "./src/image.ts";
|
|
57
54
|
import {
|
|
58
55
|
type CodexPetPackage,
|
|
59
|
-
type LoadedCodexPet,
|
|
60
|
-
loadCodexPet,
|
|
61
|
-
animationFrameAt,
|
|
62
56
|
codexHome,
|
|
63
57
|
describeCodexPetSelectionIssue,
|
|
58
|
+
findCodexPet,
|
|
64
59
|
findReadyCodexPet,
|
|
65
60
|
formatNoReadyCodexPetsMessage,
|
|
66
61
|
listCodexPets,
|
|
67
|
-
nextAnimationFrameDelayMs,
|
|
68
|
-
PET_ANIMATION_ROWS,
|
|
69
|
-
CodexPetKittyManager,
|
|
70
62
|
registerOpenAIPets,
|
|
71
|
-
renderCodexPetFrame,
|
|
72
63
|
_petsTest,
|
|
73
64
|
} from "./src/pets.ts";
|
|
65
|
+
import { FastController, modelList, supportsFast } from "./src/fast-controller.ts";
|
|
66
|
+
import { UsageController } from "./src/usage-controller.ts";
|
|
67
|
+
import { PetFooterController } from "./src/pet-footer-controller.ts";
|
|
74
68
|
|
|
75
69
|
const COMMAND = "fast";
|
|
76
70
|
const OPENAI_STATUS_COMMAND = "openai-usage";
|
|
77
71
|
const OPENAI_SETTINGS_COMMAND = "openai-settings";
|
|
78
72
|
const FLAG = "fast";
|
|
79
|
-
const PET_RESIZE_FREEZE_MS = 120;
|
|
80
|
-
const PET_RENDER_CACHE_LIMIT = 48;
|
|
81
73
|
const PET_EMPTY_VALUE = "not selected";
|
|
82
74
|
const SERVICE_TIER = "priority";
|
|
83
75
|
type SettingsPickerItem = {
|
|
@@ -92,53 +84,24 @@ type SettingsPickerItem = {
|
|
|
92
84
|
) => { render(width: number): string[]; invalidate(): void; handleInput?(data: string): void };
|
|
93
85
|
};
|
|
94
86
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
function supportsFast(ctx: ExtensionContext, supportedModels: SupportedModel[]): boolean {
|
|
100
|
-
const current = ctx.model;
|
|
101
|
-
if (!current) return false;
|
|
102
|
-
return supportedModels.some(
|
|
103
|
-
(model) => model.provider === current.provider && model.id === current.id,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function modelList(supportedModels: SupportedModel[]): string {
|
|
108
|
-
return supportedModels.length > 0
|
|
109
|
-
? supportedModels.map((model) => `${model.provider}/${model.id}`).join(", ")
|
|
110
|
-
: "none configured";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function stateText(
|
|
114
|
-
ctx: ExtensionContext,
|
|
115
|
-
desiredActive: boolean,
|
|
116
|
-
active: boolean,
|
|
117
|
-
supportedModels: SupportedModel[],
|
|
87
|
+
function abbreviateHomePath(
|
|
88
|
+
path: string,
|
|
89
|
+
home = process.env.HOME || process.env.USERPROFILE,
|
|
118
90
|
): string {
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
return `Fast mode is off. Current model: ${model}.`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function isOpenAISubscriptionModel(ctx: ExtensionContext, cfg: ResolvedConfig): boolean {
|
|
128
|
-
if (!ctx.model || (ctx.model.provider !== "openai" && ctx.model.provider !== "openai-codex"))
|
|
129
|
-
return false;
|
|
130
|
-
return !cfg.usage.showOnlyOnSubscriptionModels || ctx.modelRegistry.isUsingOAuth(ctx.model);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function randomIdleEmoteState(idleState: PetState, random = Math.random): PetState {
|
|
134
|
-
const candidates = (["waving", "jumping"] as const).filter((state) => state !== idleState);
|
|
135
|
-
return candidates[Math.floor(random() * candidates.length)] ?? "waving";
|
|
91
|
+
if (!home) return path;
|
|
92
|
+
if (path === home) return "~";
|
|
93
|
+
const homePrefix = home.endsWith(sep) ? home : `${home}${sep}`;
|
|
94
|
+
return path.startsWith(homePrefix) ? `~/${path.slice(homePrefix.length)}` : path;
|
|
136
95
|
}
|
|
137
96
|
|
|
138
97
|
function isInlinePetPlacement(placement: PetPlacement): boolean {
|
|
139
98
|
return placement === "inline-left" || placement === "inline-right" || placement === "badge";
|
|
140
99
|
}
|
|
141
100
|
|
|
101
|
+
function hasTerminalUI(ctx: ExtensionContext): boolean {
|
|
102
|
+
return ctx.mode === "tui" || (ctx.mode === undefined && ctx.hasUI);
|
|
103
|
+
}
|
|
104
|
+
|
|
142
105
|
function petSizeCellsForPlacement(placement: PetPlacement, sizeCells: number): number {
|
|
143
106
|
return placement === "badge" ? Math.min(6, sizeCells) : sizeCells;
|
|
144
107
|
}
|
|
@@ -156,19 +119,10 @@ function petSlugFromPickerValue(value: string): string {
|
|
|
156
119
|
return value === PET_EMPTY_VALUE ? "" : value;
|
|
157
120
|
}
|
|
158
121
|
|
|
159
|
-
function petPickerLookupKey(value: string): string {
|
|
160
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
122
|
function findPickerPet(value: string, pets: CodexPetPackage[]): CodexPetPackage | undefined {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
(pet) =>
|
|
168
|
-
pet.hasSpritesheet &&
|
|
169
|
-
(petPickerLookupKey(pet.slug) === requested ||
|
|
170
|
-
petPickerLookupKey(pet.name) === requested ||
|
|
171
|
-
(pet.id !== undefined && petPickerLookupKey(pet.id) === requested)),
|
|
123
|
+
return findCodexPet(
|
|
124
|
+
pets.filter((pet) => pet.hasSpritesheet),
|
|
125
|
+
value,
|
|
172
126
|
);
|
|
173
127
|
}
|
|
174
128
|
|
|
@@ -293,109 +247,34 @@ function combineInlinePetFooter(
|
|
|
293
247
|
return lines;
|
|
294
248
|
}
|
|
295
249
|
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
sizeCells: number,
|
|
308
|
-
): number {
|
|
309
|
-
const { frame } = petFrameInfo(pet, state, elapsedMs);
|
|
310
|
-
if (!frame || !getCapabilities().images) return 0;
|
|
311
|
-
const columns = Math.max(1, Math.min(Math.max(1, width - 2), sizeCells));
|
|
312
|
-
return calculateImageRows(
|
|
313
|
-
{ widthPx: frame.widthPx, heightPx: frame.heightPx },
|
|
314
|
-
columns,
|
|
315
|
-
getCellDimensions(),
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function petPlaceholderLines(
|
|
320
|
-
pet: LoadedCodexPet,
|
|
321
|
-
state: PetState,
|
|
322
|
-
elapsedMs: number,
|
|
323
|
-
width: number,
|
|
324
|
-
sizeCells: number,
|
|
325
|
-
): string[] {
|
|
326
|
-
return Array.from({ length: petFrameRows(pet, state, elapsedMs, width, sizeCells) }, () => "");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function petRenderCacheKey(
|
|
330
|
-
pet: LoadedCodexPet,
|
|
331
|
-
state: PetState,
|
|
332
|
-
frameIndex: number,
|
|
333
|
-
placement: PetPlacement,
|
|
334
|
-
width: number,
|
|
335
|
-
sizeCells: number,
|
|
336
|
-
): string {
|
|
337
|
-
const cellDimensions = getCellDimensions();
|
|
338
|
-
const imageProtocol = getCapabilities().images ?? "none";
|
|
339
|
-
return [
|
|
340
|
-
pet.pet.slug,
|
|
341
|
-
state,
|
|
342
|
-
frameIndex,
|
|
343
|
-
placement,
|
|
344
|
-
width,
|
|
345
|
-
sizeCells,
|
|
346
|
-
imageProtocol,
|
|
347
|
-
cellDimensions.widthPx,
|
|
348
|
-
cellDimensions.heightPx,
|
|
349
|
-
].join(":");
|
|
250
|
+
function textPanel(title: string, lines: string[], done: () => void) {
|
|
251
|
+
return {
|
|
252
|
+
render(width: number) {
|
|
253
|
+
const clipped = lines.map((line) => truncateToWidth(line, width, "..."));
|
|
254
|
+
return [title, "", ...clipped, "", "Esc/q to go back"];
|
|
255
|
+
},
|
|
256
|
+
invalidate() {},
|
|
257
|
+
handleInput(data: string) {
|
|
258
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || data === "q") done();
|
|
259
|
+
},
|
|
260
|
+
};
|
|
350
261
|
}
|
|
351
262
|
|
|
352
263
|
export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
353
|
-
|
|
354
|
-
let active = false;
|
|
264
|
+
const fastController = new FastController(SERVICE_TIER);
|
|
355
265
|
let cachedConfig: ResolvedConfig | undefined;
|
|
356
|
-
let usageSnapshot: UsageSnapshot | undefined;
|
|
357
|
-
let usageUpdatedAt: number | undefined;
|
|
358
|
-
let usageError: string | undefined;
|
|
359
|
-
let usageLastFetchAt: number | undefined;
|
|
360
|
-
let usageTimer: ReturnType<typeof setInterval> | undefined;
|
|
361
266
|
let footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
362
|
-
let usageRefreshInFlight = false;
|
|
363
|
-
let queuedUsageRefresh: { ctx: ExtensionContext; modelId?: string; notify?: boolean } | undefined;
|
|
364
|
-
let shuttingDown = false;
|
|
365
|
-
let usageAbortController: AbortController | undefined;
|
|
366
267
|
let footerInstalled = false;
|
|
367
268
|
let statusInstalled = false;
|
|
368
|
-
let
|
|
369
|
-
let
|
|
370
|
-
let
|
|
371
|
-
let
|
|
372
|
-
let
|
|
373
|
-
let
|
|
374
|
-
let
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
let petLoadingKey: string | undefined;
|
|
378
|
-
let petLoadNotify = false;
|
|
379
|
-
let queuedPetRefresh: { ctx: ExtensionContext; notify: boolean } | undefined;
|
|
380
|
-
let petTimer: ReturnType<typeof setTimeout> | undefined;
|
|
381
|
-
let petRenderRequestTimer: ReturnType<typeof setTimeout> | undefined;
|
|
382
|
-
let petRuntimeState: PetState = "idle";
|
|
383
|
-
let petPreviewState: PetState | undefined;
|
|
384
|
-
let petSettingsPreviewActive = false;
|
|
385
|
-
let petSettingsPets: CodexPetPackage[] = [];
|
|
386
|
-
let petFlashState: PetState | undefined;
|
|
387
|
-
let petFlashUntil: number | undefined;
|
|
388
|
-
let petFlashTimer: ReturnType<typeof setTimeout> | undefined;
|
|
389
|
-
let petIdleEmoteTimer: ReturnType<typeof setTimeout> | undefined;
|
|
390
|
-
let petResizeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
391
|
-
let petResizeFreezeUntil = 0;
|
|
392
|
-
let stdoutResizeHandler: (() => void) | undefined;
|
|
393
|
-
let petAnimationState: PetState | undefined;
|
|
394
|
-
let petAnimationStartedAt = Date.now();
|
|
395
|
-
const petRenderCache = new Map<string, string[]>();
|
|
396
|
-
const activeToolCallIds = new Set<string>();
|
|
397
|
-
const petImageId = 0x70657401;
|
|
398
|
-
const petKittyManager = new CodexPetKittyManager(petImageId);
|
|
269
|
+
let contextUsageCached = false;
|
|
270
|
+
let cachedContextUsage: ReturnType<ExtensionContext["getContextUsage"]>;
|
|
271
|
+
let cachedContextLeafId: string | null | undefined;
|
|
272
|
+
let cachedContextModel: ExtensionContext["model"];
|
|
273
|
+
let sessionNameCached = false;
|
|
274
|
+
let cachedSessionNameLeafId: string | null | undefined;
|
|
275
|
+
let cachedSessionName: string | undefined;
|
|
276
|
+
const usageController = new UsageController(config, updateFooter);
|
|
277
|
+
const petController = new PetFooterController(config, updateFooter, () => footerInstalled);
|
|
399
278
|
|
|
400
279
|
function refresh(ctx: ExtensionContext): ResolvedConfig {
|
|
401
280
|
cachedConfig = resolveConfig(ctx.cwd || process.cwd());
|
|
@@ -407,413 +286,38 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
407
286
|
}
|
|
408
287
|
|
|
409
288
|
function persist(nextConfig: ResolvedConfig): void {
|
|
410
|
-
cachedConfig = {
|
|
289
|
+
cachedConfig = {
|
|
290
|
+
...nextConfig,
|
|
291
|
+
active: fastController.active,
|
|
292
|
+
desiredActive: fastController.desiredActive,
|
|
293
|
+
};
|
|
411
294
|
if (!nextConfig.persistState) return;
|
|
412
295
|
writeConfig(nextConfig.configPath, {
|
|
413
296
|
...readRawConfig(nextConfig.configPath),
|
|
414
|
-
active,
|
|
415
|
-
desiredActive,
|
|
297
|
+
active: fastController.active,
|
|
298
|
+
desiredActive: fastController.desiredActive,
|
|
416
299
|
});
|
|
417
300
|
}
|
|
418
301
|
|
|
419
|
-
function shouldLoadPetForConfig(cfg: ResolvedConfig): boolean {
|
|
420
|
-
return cfg.pets.enabled || petSettingsPreviewActive;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function shouldRenderPetInFooter(cfg: ResolvedConfig): boolean {
|
|
424
|
-
return cfg.pets.enabled || (petSettingsPreviewActive && footerInstalled);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function setPetSettingsPreviewActive(ctx: ExtensionContext, next: boolean): void {
|
|
428
|
-
if (petSettingsPreviewActive === next) return;
|
|
429
|
-
petSettingsPreviewActive = next;
|
|
430
|
-
if (!config(ctx).pets.enabled) petLoadKey = undefined;
|
|
431
|
-
void refreshPet(ctx, config(ctx)).then(() => {
|
|
432
|
-
if (!shouldRenderPetInFooter(config(ctx))) flushPetKittyCleanupNow();
|
|
433
|
-
requestSettingsRender?.();
|
|
434
|
-
});
|
|
435
|
-
updateFooter(ctx);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function petStateAnimationDurationMs(state: PetState): number {
|
|
439
|
-
return PET_ANIMATION_ROWS[state].durations.reduce((sum, duration) => sum + duration, 0);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function clearPetFlash(): void {
|
|
443
|
-
if (petFlashTimer) clearTimeout(petFlashTimer);
|
|
444
|
-
petFlashTimer = undefined;
|
|
445
|
-
petFlashState = undefined;
|
|
446
|
-
petFlashUntil = undefined;
|
|
447
|
-
petAnimationState = undefined;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function stopPetIdleEmotes(): void {
|
|
451
|
-
if (petIdleEmoteTimer) clearTimeout(petIdleEmoteTimer);
|
|
452
|
-
petIdleEmoteTimer = undefined;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function resetPetRenderCache(): void {
|
|
456
|
-
petRenderCache.clear();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function queuePetKittyCleanup(target = pet): void {
|
|
460
|
-
petKittyManager.invalidate(target);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function takePetKittyCleanupSequence(): string {
|
|
464
|
-
return petKittyManager.takeCleanupSequence();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function withPendingPetKittyCleanup(lines: string[]): string[] {
|
|
468
|
-
const sequence = takePetKittyCleanupSequence();
|
|
469
|
-
if (!sequence) return lines;
|
|
470
|
-
if (lines.length === 0) return [sequence];
|
|
471
|
-
return [`${sequence}\x1b[0m${lines[0]}\x1b[0m`, ...lines.slice(1)];
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function writePetKittySequence(sequence: string): void {
|
|
475
|
-
if (sequence && process.stdout.isTTY) process.stdout.write(sequence);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function flushPetKittyCleanupNow(): void {
|
|
479
|
-
writePetKittySequence(takePetKittyCleanupSequence());
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function disposePetKittyNow(target = pet): void {
|
|
483
|
-
writePetKittySequence(petKittyManager.dispose(target));
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function rememberPetRender(key: string, lines: string[]): void {
|
|
487
|
-
petRenderCache.set(key, lines);
|
|
488
|
-
while (petRenderCache.size > PET_RENDER_CACHE_LIMIT) {
|
|
489
|
-
const firstKey = petRenderCache.keys().next().value;
|
|
490
|
-
if (firstKey === undefined) break;
|
|
491
|
-
petRenderCache.delete(firstKey);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function petResizeFrozen(now = Date.now()): boolean {
|
|
496
|
-
return now < petResizeFreezeUntil;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function clearPetResizeFreeze(): void {
|
|
500
|
-
if (petResizeTimer) clearTimeout(petResizeTimer);
|
|
501
|
-
petResizeTimer = undefined;
|
|
502
|
-
petResizeFreezeUntil = 0;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function freezePetForResize(ctx: ExtensionContext, now = Date.now()): void {
|
|
506
|
-
petResizeFreezeUntil = now + PET_RESIZE_FREEZE_MS;
|
|
507
|
-
queuePetKittyCleanup();
|
|
508
|
-
stopPetAnimation();
|
|
509
|
-
stopPetIdleEmotes();
|
|
510
|
-
stopPendingPetRenderRequest();
|
|
511
|
-
if (petResizeTimer) clearTimeout(petResizeTimer);
|
|
512
|
-
petResizeTimer = setTimeout(() => {
|
|
513
|
-
petResizeTimer = undefined;
|
|
514
|
-
petResizeFreezeUntil = 0;
|
|
515
|
-
petKittyManager.resetForResize(pet);
|
|
516
|
-
resetPetRenderCache();
|
|
517
|
-
updateFooter(ctx);
|
|
518
|
-
}, PET_RESIZE_FREEZE_MS);
|
|
519
|
-
petResizeTimer.unref?.();
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function installPetResizeGuard(ctx: ExtensionContext): void {
|
|
523
|
-
if (stdoutResizeHandler || !process.stdout.isTTY) return;
|
|
524
|
-
stdoutResizeHandler = () => freezePetForResize(ctx);
|
|
525
|
-
process.stdout.on("resize", stdoutResizeHandler);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function uninstallPetResizeGuard(): void {
|
|
529
|
-
if (stdoutResizeHandler) process.stdout.off("resize", stdoutResizeHandler);
|
|
530
|
-
stdoutResizeHandler = undefined;
|
|
531
|
-
clearPetResizeFreeze();
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function currentPetState(ctx: ExtensionContext, cfg = config(ctx)): PetState {
|
|
535
|
-
if (petPreviewState) return petPreviewState;
|
|
536
|
-
const now = Date.now();
|
|
537
|
-
if (petFlashState && petFlashUntil !== undefined && now < petFlashUntil) return petFlashState;
|
|
538
|
-
if (petFlashState) clearPetFlash();
|
|
539
|
-
return petRuntimeState === "idle" ? cfg.pets.state : petRuntimeState;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function currentPetAnimation(ctx: ExtensionContext, cfg = config(ctx)) {
|
|
543
|
-
const state = currentPetState(ctx, cfg);
|
|
544
|
-
const now = Date.now();
|
|
545
|
-
if (state !== petAnimationState) {
|
|
546
|
-
petAnimationState = state;
|
|
547
|
-
petAnimationStartedAt = now;
|
|
548
|
-
}
|
|
549
|
-
return { state, elapsedMs: now - petAnimationStartedAt };
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function stopPetAnimation(): void {
|
|
553
|
-
if (petTimer) clearTimeout(petTimer);
|
|
554
|
-
petTimer = undefined;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function stopPendingPetRenderRequest(): void {
|
|
558
|
-
if (petRenderRequestTimer) clearTimeout(petRenderRequestTimer);
|
|
559
|
-
petRenderRequestTimer = undefined;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function requestPetFooterRender(): void {
|
|
563
|
-
if (petRenderRequestTimer) return;
|
|
564
|
-
petRenderRequestTimer = setTimeout(() => {
|
|
565
|
-
petRenderRequestTimer = undefined;
|
|
566
|
-
requestFooterRender?.();
|
|
567
|
-
requestSettingsRender?.();
|
|
568
|
-
}, 16);
|
|
569
|
-
petRenderRequestTimer.unref?.();
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function schedulePetAnimation(ctx: ExtensionContext): void {
|
|
573
|
-
if (!pet || petTimer || petResizeFrozen() || !getCapabilities().images) return;
|
|
574
|
-
const { state, elapsedMs } = currentPetAnimation(ctx);
|
|
575
|
-
const frames = pet.states[state] ?? pet.states.idle;
|
|
576
|
-
petTimer = setTimeout(
|
|
577
|
-
() => {
|
|
578
|
-
petTimer = undefined;
|
|
579
|
-
if (petResizeFrozen()) return;
|
|
580
|
-
requestPetFooterRender();
|
|
581
|
-
schedulePetAnimation(ctx);
|
|
582
|
-
},
|
|
583
|
-
nextAnimationFrameDelayMs(frames, elapsedMs),
|
|
584
|
-
);
|
|
585
|
-
petTimer.unref?.();
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function startPetAnimation(ctx: ExtensionContext): void {
|
|
589
|
-
schedulePetAnimation(ctx);
|
|
590
|
-
void refreshPet(ctx);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function shouldRunIdleEmotes(ctx: ExtensionContext, cfg = config(ctx)): boolean {
|
|
594
|
-
return (
|
|
595
|
-
cfg.pets.enabled &&
|
|
596
|
-
cfg.pets.idleEmotes &&
|
|
597
|
-
getCapabilities().images !== null &&
|
|
598
|
-
pet !== undefined &&
|
|
599
|
-
petRuntimeState === "idle" &&
|
|
600
|
-
activeToolCallIds.size === 0 &&
|
|
601
|
-
petPreviewState === undefined &&
|
|
602
|
-
!petSettingsPreviewActive &&
|
|
603
|
-
petFlashState === undefined &&
|
|
604
|
-
!petResizeFrozen()
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function playPetFlash(ctx: ExtensionContext, state: PetState, cfg = config(ctx)): void {
|
|
609
|
-
if (!cfg.pets.enabled || !getCapabilities().images) return;
|
|
610
|
-
clearPetFlash();
|
|
611
|
-
const durationMs = petStateAnimationDurationMs(state);
|
|
612
|
-
petFlashState = state;
|
|
613
|
-
petFlashUntil = Date.now() + durationMs;
|
|
614
|
-
petFlashTimer = setTimeout(() => {
|
|
615
|
-
petFlashTimer = undefined;
|
|
616
|
-
petFlashState = undefined;
|
|
617
|
-
petFlashUntil = undefined;
|
|
618
|
-
petAnimationState = undefined;
|
|
619
|
-
updateFooter(ctx);
|
|
620
|
-
}, durationMs);
|
|
621
|
-
petFlashTimer.unref?.();
|
|
622
|
-
updateFooter(ctx);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function schedulePetIdleEmote(ctx: ExtensionContext, cfg = config(ctx)): void {
|
|
626
|
-
if (petIdleEmoteTimer || !shouldRunIdleEmotes(ctx, cfg)) return;
|
|
627
|
-
const delayMs = Math.round(cfg.pets.idleEmoteIntervalMs * (0.75 + Math.random() * 0.75));
|
|
628
|
-
petIdleEmoteTimer = setTimeout(() => {
|
|
629
|
-
petIdleEmoteTimer = undefined;
|
|
630
|
-
const nextConfig = config(ctx);
|
|
631
|
-
if (shouldRunIdleEmotes(ctx, nextConfig)) {
|
|
632
|
-
playPetFlash(ctx, randomIdleEmoteState(nextConfig.pets.state), nextConfig);
|
|
633
|
-
}
|
|
634
|
-
schedulePetIdleEmote(ctx, nextConfig);
|
|
635
|
-
}, delayMs);
|
|
636
|
-
petIdleEmoteTimer.unref?.();
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function petLoadKeyForConfig(cfg: ResolvedConfig): string {
|
|
640
|
-
const cellDimensions = getCellDimensions();
|
|
641
|
-
return [
|
|
642
|
-
cfg.pets.slug || "__first__",
|
|
643
|
-
cfg.pets.sizeCells,
|
|
644
|
-
getCapabilities().images ?? "none",
|
|
645
|
-
cellDimensions.widthPx,
|
|
646
|
-
cellDimensions.heightPx,
|
|
647
|
-
].join(":");
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async function refreshPet(
|
|
651
|
-
ctx: ExtensionContext,
|
|
652
|
-
cfg = config(ctx),
|
|
653
|
-
notify = false,
|
|
654
|
-
): Promise<void> {
|
|
655
|
-
if (shuttingDown) return;
|
|
656
|
-
if (!shouldLoadPetForConfig(cfg)) {
|
|
657
|
-
queuedPetRefresh = undefined;
|
|
658
|
-
queuePetKittyCleanup();
|
|
659
|
-
pet = undefined;
|
|
660
|
-
petError = undefined;
|
|
661
|
-
petLoadKey = undefined;
|
|
662
|
-
resetPetRenderCache();
|
|
663
|
-
clearPetFlash();
|
|
664
|
-
stopPetIdleEmotes();
|
|
665
|
-
stopPetAnimation();
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const key = petLoadKeyForConfig(cfg);
|
|
670
|
-
if (petLoadKey === key) return;
|
|
671
|
-
if (petLoadInFlight) {
|
|
672
|
-
if (petLoadingKey === key) petLoadNotify ||= notify;
|
|
673
|
-
else queuedPetRefresh = { ctx, notify: queuedPetRefresh?.notify || notify };
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
petLoadInFlight = true;
|
|
678
|
-
petLoadingKey = key;
|
|
679
|
-
petLoadNotify = notify;
|
|
680
|
-
petError = undefined;
|
|
681
|
-
const previousPet = pet;
|
|
682
|
-
|
|
683
|
-
function shouldApplyLoadResult(): boolean {
|
|
684
|
-
if (shuttingDown || queuedPetRefresh) return false;
|
|
685
|
-
const latestConfig = config(ctx);
|
|
686
|
-
return shouldLoadPetForConfig(latestConfig) && petLoadKeyForConfig(latestConfig) === key;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
try {
|
|
690
|
-
const loadedPet = await loadCodexPet(cfg.pets.slug || undefined, undefined, {
|
|
691
|
-
sizeCells: cfg.pets.sizeCells,
|
|
692
|
-
});
|
|
693
|
-
if (!shouldApplyLoadResult()) return;
|
|
694
|
-
if (previousPet) queuePetKittyCleanup(previousPet);
|
|
695
|
-
pet = loadedPet;
|
|
696
|
-
const petIssue = pet
|
|
697
|
-
? undefined
|
|
698
|
-
: describeCodexPetSelectionIssue(await listCodexPets(), cfg.pets.slug || undefined);
|
|
699
|
-
petAnimationState = undefined;
|
|
700
|
-
resetPetRenderCache();
|
|
701
|
-
petLoadKey = key;
|
|
702
|
-
if (!pet && petIssue) petError = petIssue.short;
|
|
703
|
-
if (petLoadNotify) {
|
|
704
|
-
ctx.ui.notify(
|
|
705
|
-
pet
|
|
706
|
-
? `Rendering ${pet.pet.name} in the Better OpenAI footer.`
|
|
707
|
-
: (petIssue?.message ?? "No ready custom Codex pet found."),
|
|
708
|
-
pet ? "info" : "warning",
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
} catch (error) {
|
|
712
|
-
if (!shouldApplyLoadResult()) return;
|
|
713
|
-
if (previousPet) queuePetKittyCleanup(previousPet);
|
|
714
|
-
pet = undefined;
|
|
715
|
-
petLoadKey = key;
|
|
716
|
-
petError = error instanceof Error ? error.message : String(error);
|
|
717
|
-
if (petLoadNotify) ctx.ui.notify(`Could not render Codex pet: ${petError}`, "warning");
|
|
718
|
-
} finally {
|
|
719
|
-
petLoadInFlight = false;
|
|
720
|
-
petLoadingKey = undefined;
|
|
721
|
-
petLoadNotify = false;
|
|
722
|
-
const queued = queuedPetRefresh;
|
|
723
|
-
queuedPetRefresh = undefined;
|
|
724
|
-
if (queued && !shuttingDown) void refreshPet(queued.ctx, config(queued.ctx), queued.notify);
|
|
725
|
-
if (!shuttingDown) updateFooter(ctx);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
302
|
function writePetConfig(ctx: ExtensionContext, patch: Record<string, unknown>): ResolvedConfig {
|
|
730
303
|
const cfg = refresh(ctx);
|
|
731
304
|
const current = readRawConfig(cfg.configPath);
|
|
732
305
|
const pets = isRecord(current.pets) ? current.pets : {};
|
|
733
306
|
writeConfig(cfg.configPath, { ...current, pets: { ...pets, ...patch } });
|
|
734
|
-
|
|
307
|
+
petController.invalidateLoadKey();
|
|
735
308
|
return refresh(ctx);
|
|
736
309
|
}
|
|
737
310
|
|
|
738
|
-
function applyDesiredFastState(ctx: ExtensionContext, cfg = config(ctx)): void {
|
|
739
|
-
active = desiredActive && supportsFast(ctx, cfg.supportedModels);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
311
|
function setActive(ctx: ExtensionContext, next: boolean): void {
|
|
743
312
|
const nextConfig = refresh(ctx);
|
|
744
|
-
|
|
745
|
-
applyDesiredFastState(ctx, nextConfig);
|
|
313
|
+
fastController.setDesired(ctx, nextConfig, next);
|
|
746
314
|
persist(nextConfig);
|
|
747
315
|
updateFooter(ctx);
|
|
748
|
-
if (next && !active) {
|
|
749
|
-
ctx.ui.notify(
|
|
750
|
-
`Fast mode requested, but ${currentModelKey(ctx)} is unsupported. It will activate automatically when you switch to a supported model: ${modelList(nextConfig.supportedModels)}.`,
|
|
751
|
-
"warning",
|
|
752
|
-
);
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
ctx.ui.notify(stateText(ctx, desiredActive, active, nextConfig.supportedModels), "info");
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
async function refreshUsage(
|
|
759
|
-
ctx: ExtensionContext,
|
|
760
|
-
modelId = ctx.model?.id,
|
|
761
|
-
options?: { notify?: boolean },
|
|
762
|
-
): Promise<void> {
|
|
763
|
-
if (shuttingDown || !ctx.hasUI) return;
|
|
764
|
-
if (usageRefreshInFlight) {
|
|
765
|
-
queuedUsageRefresh = { ctx, modelId, notify: queuedUsageRefresh?.notify || options?.notify };
|
|
316
|
+
if (next && !fastController.active) {
|
|
317
|
+
ctx.ui.notify(fastController.unsupportedRequestMessage(ctx, nextConfig), "warning");
|
|
766
318
|
return;
|
|
767
319
|
}
|
|
768
|
-
|
|
769
|
-
const cfg = config(ctx);
|
|
770
|
-
try {
|
|
771
|
-
if (!cfg.usage.enabled) {
|
|
772
|
-
usageSnapshot = undefined;
|
|
773
|
-
usageError = "Usage display is disabled.";
|
|
774
|
-
if (!shuttingDown) updateFooter(ctx);
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
if (!isOpenAISubscriptionModel(ctx, cfg)) {
|
|
778
|
-
if (!shuttingDown) updateFooter(ctx);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
usageAbortController = new AbortController();
|
|
782
|
-
const timeoutSignal = AbortSignal.timeout(10_000);
|
|
783
|
-
const signal = ctx.signal
|
|
784
|
-
? AbortSignal.any([ctx.signal, timeoutSignal, usageAbortController.signal])
|
|
785
|
-
: AbortSignal.any([timeoutSignal, usageAbortController.signal]);
|
|
786
|
-
const data = await requestCodexUsage(signal);
|
|
787
|
-
usageLastFetchAt = Date.now();
|
|
788
|
-
usageSnapshot = data ? parseUsageSnapshot(data, modelId) : undefined;
|
|
789
|
-
usageUpdatedAt = usageSnapshot ? Date.now() : undefined;
|
|
790
|
-
usageError = data ? undefined : `Missing openai-codex OAuth credentials in ${AUTH_FILE}.`;
|
|
791
|
-
if (!shuttingDown) updateFooter(ctx);
|
|
792
|
-
if (!shuttingDown && options?.notify)
|
|
793
|
-
ctx.ui.notify(formatUsageStatus(ctx), usageSnapshot ? "info" : "warning");
|
|
794
|
-
} catch (error) {
|
|
795
|
-
if (shuttingDown) return;
|
|
796
|
-
usageError = error instanceof Error ? error.message : String(error);
|
|
797
|
-
updateFooter(ctx);
|
|
798
|
-
if (options?.notify) ctx.ui.notify(formatUsageStatus(ctx), "warning");
|
|
799
|
-
} finally {
|
|
800
|
-
usageAbortController = undefined;
|
|
801
|
-
usageRefreshInFlight = false;
|
|
802
|
-
if (!shuttingDown && queuedUsageRefresh) {
|
|
803
|
-
const next = queuedUsageRefresh;
|
|
804
|
-
queuedUsageRefresh = undefined;
|
|
805
|
-
void refreshUsage(next.ctx, next.modelId, { notify: next.notify });
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function startUsageRefresh(ctx: ExtensionContext): void {
|
|
811
|
-
if (usageTimer) clearInterval(usageTimer);
|
|
812
|
-
const cfg = config(ctx);
|
|
813
|
-
if (!cfg.usage.enabled) return;
|
|
814
|
-
void refreshUsage(ctx);
|
|
815
|
-
usageTimer = setInterval(() => void refreshUsage(ctx), cfg.usage.refreshIntervalMs);
|
|
816
|
-
usageTimer.unref?.();
|
|
320
|
+
ctx.ui.notify(fastController.stateText(ctx, nextConfig), "info");
|
|
817
321
|
}
|
|
818
322
|
|
|
819
323
|
function refreshFooterTotals(ctx: ExtensionContext): void {
|
|
@@ -828,35 +332,39 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
828
332
|
}
|
|
829
333
|
}
|
|
830
334
|
|
|
831
|
-
function
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
`Current model: ${currentModelKey(ctx)}`,
|
|
837
|
-
`Current model eligible: ${isOpenAISubscriptionModel(ctx, cfg)}`,
|
|
838
|
-
`Requires subscription model: ${cfg.usage.showOnlyOnSubscriptionModels}`,
|
|
839
|
-
`Auth: ${auth ? "found" : "missing"}`,
|
|
840
|
-
`Account ID: ${auth?.accountId ?? "none"}`,
|
|
841
|
-
`Last fetch: ${usageLastFetchAt ? new Date(usageLastFetchAt).toLocaleTimeString() : "never"}`,
|
|
842
|
-
`Last successful update: ${usageUpdatedAt ? new Date(usageUpdatedAt).toLocaleTimeString() : "never"}`,
|
|
843
|
-
`Last error: ${usageError ?? "none"}`,
|
|
844
|
-
`Refresh interval: ${cfg.usage.refreshIntervalMs}ms`,
|
|
845
|
-
`Endpoint: https://chatgpt.com/backend-api/wham/usage`,
|
|
846
|
-
].join("\n");
|
|
335
|
+
function invalidateContextUsage(): void {
|
|
336
|
+
contextUsageCached = false;
|
|
337
|
+
cachedContextUsage = undefined;
|
|
338
|
+
cachedContextLeafId = undefined;
|
|
339
|
+
cachedContextModel = undefined;
|
|
847
340
|
}
|
|
848
341
|
|
|
849
|
-
function
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
if (!
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
342
|
+
function contextUsage(ctx: ExtensionContext): ReturnType<ExtensionContext["getContextUsage"]> {
|
|
343
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
344
|
+
const model = ctx.model;
|
|
345
|
+
if (!contextUsageCached || leafId !== cachedContextLeafId || model !== cachedContextModel) {
|
|
346
|
+
cachedContextUsage = ctx.getContextUsage();
|
|
347
|
+
contextUsageCached = true;
|
|
348
|
+
cachedContextLeafId = leafId;
|
|
349
|
+
cachedContextModel = model;
|
|
350
|
+
}
|
|
351
|
+
return cachedContextUsage;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function sessionName(ctx: ExtensionContext): string | undefined {
|
|
355
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
356
|
+
if (!sessionNameCached || leafId !== cachedSessionNameLeafId) {
|
|
357
|
+
cachedSessionName = ctx.sessionManager.getSessionName();
|
|
358
|
+
cachedSessionNameLeafId = leafId;
|
|
359
|
+
sessionNameCached = true;
|
|
360
|
+
}
|
|
361
|
+
return cachedSessionName;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function invalidateSessionName(): void {
|
|
365
|
+
sessionNameCached = false;
|
|
366
|
+
cachedSessionNameLeafId = undefined;
|
|
367
|
+
cachedSessionName = undefined;
|
|
860
368
|
}
|
|
861
369
|
|
|
862
370
|
pi.registerFlag(FLAG, {
|
|
@@ -868,15 +376,10 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
868
376
|
function formatDebugStatus(ctx: ExtensionContext): string {
|
|
869
377
|
const cfg = config(ctx);
|
|
870
378
|
return [
|
|
871
|
-
|
|
872
|
-
`Fast active: ${active}`,
|
|
873
|
-
`Current model: ${currentModelKey(ctx)}`,
|
|
874
|
-
`Supported model: ${supportsFast(ctx, cfg.supportedModels)}`,
|
|
875
|
-
`Configured service_tier: ${SERVICE_TIER}`,
|
|
876
|
-
`Last injected: ${lastInjectedAt ? `${new Date(lastInjectedAt).toLocaleTimeString()} (${lastInjectedModel}, ${lastInjectedTier})` : "never"}`,
|
|
379
|
+
...fastController.debugLines(ctx, cfg),
|
|
877
380
|
`Footer mode: ${cfg.footer.mode}`,
|
|
878
381
|
"",
|
|
879
|
-
|
|
382
|
+
usageController.formatDebug(ctx),
|
|
880
383
|
"",
|
|
881
384
|
`Image enabled: ${cfg.image.enabled}`,
|
|
882
385
|
`Image default save: ${cfg.image.defaultSave}`,
|
|
@@ -885,22 +388,17 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
885
388
|
`Pet placement: ${cfg.pets.placement}`,
|
|
886
389
|
`Pet failed tool state: ${cfg.pets.failedToolState}`,
|
|
887
390
|
`Pet idle emotes: ${cfg.pets.idleEmotes} (${cfg.pets.idleEmoteIntervalMs}ms)`,
|
|
888
|
-
`Pet loaded: ${
|
|
889
|
-
`Pet error: ${
|
|
391
|
+
`Pet loaded: ${petController.loadedPet?.pet.name ?? "none"}`,
|
|
392
|
+
`Pet error: ${petController.error ?? "none"}`,
|
|
890
393
|
`Config: ${cfg.configPath}`,
|
|
891
394
|
].join("\n");
|
|
892
395
|
}
|
|
893
396
|
|
|
894
|
-
function formatOpenAIStatus(ctx: ExtensionContext): string {
|
|
895
|
-
refresh(ctx);
|
|
896
|
-
return formatUsageStatus(ctx);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
397
|
pi.registerCommand(COMMAND, {
|
|
900
398
|
description: "Toggle OpenAI fast mode",
|
|
901
399
|
handler: async (args, ctx) => {
|
|
902
400
|
const arg = args.trim().toLowerCase();
|
|
903
|
-
if (!arg) return setActive(ctx, !desiredActive);
|
|
401
|
+
if (!arg) return setActive(ctx, !fastController.desiredActive);
|
|
904
402
|
ctx.ui.notify("Usage: /fast", "error");
|
|
905
403
|
},
|
|
906
404
|
});
|
|
@@ -908,98 +406,18 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
908
406
|
pi.registerCommand(OPENAI_STATUS_COMMAND, {
|
|
909
407
|
description: "Show OpenAI subscription usage status",
|
|
910
408
|
handler: async (_args, ctx) => {
|
|
911
|
-
|
|
409
|
+
await usageController.refresh(ctx, ctx.model?.id, { notify: true, force: true });
|
|
912
410
|
},
|
|
913
411
|
});
|
|
914
412
|
|
|
915
|
-
function textPanel(title: string, lines: string[], done: () => void) {
|
|
916
|
-
return {
|
|
917
|
-
render(width: number) {
|
|
918
|
-
const clipped = lines.map((line) => truncateToWidth(line, width, "..."));
|
|
919
|
-
return [title, "", ...clipped, "", "Esc/q to go back"];
|
|
920
|
-
},
|
|
921
|
-
invalidate() {},
|
|
922
|
-
handleInput(data: string) {
|
|
923
|
-
if (data.includes("\x1b") || data === "escape" || data === "q" || data === "\x03") done();
|
|
924
|
-
},
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
|
|
928
413
|
function buildPetSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
929
|
-
return
|
|
930
|
-
{
|
|
931
|
-
id: "pets.enabled",
|
|
932
|
-
label: "Enabled",
|
|
933
|
-
currentValue: String(cfg.pets.enabled),
|
|
934
|
-
values: ["true", "false"],
|
|
935
|
-
description:
|
|
936
|
-
"Render a custom Codex pet from ${CODEX_HOME:-~/.codex}/pets in the Better OpenAI footer.",
|
|
937
|
-
},
|
|
938
|
-
{
|
|
939
|
-
id: "pets.slug",
|
|
940
|
-
label: "Pet",
|
|
414
|
+
return settingsItemsFromDescriptors(PET_SETTING_DESCRIPTORS, cfg, {
|
|
415
|
+
"pets.slug": {
|
|
941
416
|
currentValue: petConfigPickerValue(cfg),
|
|
942
|
-
values: readyPetPickerValues(
|
|
943
|
-
description: petPickerDescription(cfg,
|
|
944
|
-
},
|
|
945
|
-
{
|
|
946
|
-
id: "pets.placement",
|
|
947
|
-
label: "Placement",
|
|
948
|
-
currentValue: cfg.pets.placement,
|
|
949
|
-
values: [...PET_PLACEMENTS],
|
|
950
|
-
description:
|
|
951
|
-
"Footer layout: stacked, inline-left, inline-right, badge, or habitat divider.",
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
id: "pets.state",
|
|
955
|
-
label: "Idle state",
|
|
956
|
-
currentValue: cfg.pets.state,
|
|
957
|
-
values: [...PET_STATES],
|
|
958
|
-
description: "Animation row to show when pi is idle.",
|
|
959
|
-
},
|
|
960
|
-
{
|
|
961
|
-
id: "pets.thinkingState",
|
|
962
|
-
label: "Thinking state",
|
|
963
|
-
currentValue: cfg.pets.thinkingState,
|
|
964
|
-
values: [...PET_STATES],
|
|
965
|
-
description: "Animation row to show while the model is thinking or streaming.",
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
id: "pets.toolState",
|
|
969
|
-
label: "Tool state",
|
|
970
|
-
currentValue: cfg.pets.toolState,
|
|
971
|
-
values: [...PET_STATES],
|
|
972
|
-
description: "Animation row to show during tool execution.",
|
|
973
|
-
},
|
|
974
|
-
{
|
|
975
|
-
id: "pets.failedToolState",
|
|
976
|
-
label: "Failed tool state",
|
|
977
|
-
currentValue: cfg.pets.failedToolState,
|
|
978
|
-
values: [...PET_STATES],
|
|
979
|
-
description: "Animation row to flash after any tool call returns an error.",
|
|
980
|
-
},
|
|
981
|
-
{
|
|
982
|
-
id: "pets.idleEmotes",
|
|
983
|
-
label: "Random idle emotes",
|
|
984
|
-
currentValue: String(cfg.pets.idleEmotes),
|
|
985
|
-
values: ["true", "false"],
|
|
986
|
-
description: "Occasionally flash a wave or jump while pi is idle.",
|
|
987
|
-
},
|
|
988
|
-
{
|
|
989
|
-
id: "pets.idleEmoteIntervalMs",
|
|
990
|
-
label: "Idle emote interval",
|
|
991
|
-
currentValue: String(cfg.pets.idleEmoteIntervalMs),
|
|
992
|
-
values: ["5000", "15000", "30000", "60000", "120000", "300000"],
|
|
993
|
-
description: "Average delay between random idle pet emotes in milliseconds.",
|
|
417
|
+
values: readyPetPickerValues(petController.settingsPets),
|
|
418
|
+
description: petPickerDescription(cfg, petController.settingsPets),
|
|
994
419
|
},
|
|
995
|
-
|
|
996
|
-
id: "pets.sizeCells",
|
|
997
|
-
label: "Size",
|
|
998
|
-
currentValue: String(cfg.pets.sizeCells),
|
|
999
|
-
values: ["4", "6", "8", "10", "12", "16"],
|
|
1000
|
-
description: "Pet image width in terminal cells.",
|
|
1001
|
-
},
|
|
1002
|
-
];
|
|
420
|
+
});
|
|
1003
421
|
}
|
|
1004
422
|
|
|
1005
423
|
function petPreviewFromItem(item: SettingsPickerItem | undefined): PetState | undefined {
|
|
@@ -1015,23 +433,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1015
433
|
}
|
|
1016
434
|
|
|
1017
435
|
function renderPetSettingsPreview(ctx: ExtensionContext, width: number): string[] {
|
|
1018
|
-
|
|
1019
|
-
if (!pet) {
|
|
1020
|
-
return [petError ? `Preview unavailable: ${petError}` : "Preview: loading pet…"];
|
|
1021
|
-
}
|
|
1022
|
-
const cfg = config(ctx);
|
|
1023
|
-
const { state, elapsedMs } = currentPetAnimation(ctx, cfg);
|
|
1024
|
-
const plainTheme = { fg: (_color: string, value: string) => value };
|
|
1025
|
-
return [
|
|
1026
|
-
`Preview: ${pet.pet.name}`,
|
|
1027
|
-
...renderCodexPetFrame(pet, state, width, plainTheme, {
|
|
1028
|
-
sizeCells: cfg.pets.sizeCells,
|
|
1029
|
-
imageId: petImageId,
|
|
1030
|
-
now: elapsedMs,
|
|
1031
|
-
durationMultiplier: 1,
|
|
1032
|
-
kittyManager: petKittyManager,
|
|
1033
|
-
}),
|
|
1034
|
-
];
|
|
436
|
+
return petController.renderSettingsPreview(ctx, width);
|
|
1035
437
|
}
|
|
1036
438
|
|
|
1037
439
|
function settingsSubmenu(
|
|
@@ -1206,10 +608,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1206
608
|
}
|
|
1207
609
|
|
|
1208
610
|
function fastSettingsSummary(ctx: ExtensionContext, cfg: ResolvedConfig): string {
|
|
1209
|
-
|
|
1210
|
-
if (desiredActive)
|
|
1211
|
-
return supportsFast(ctx, cfg.supportedModels) ? "requested" : "requested inactive";
|
|
1212
|
-
return "off";
|
|
611
|
+
return fastController.settingsSummary(ctx, cfg);
|
|
1213
612
|
}
|
|
1214
613
|
|
|
1215
614
|
function usageSettingsSummary(cfg: ResolvedConfig): string {
|
|
@@ -1230,113 +629,52 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1230
629
|
return `${status} · ${selected} · ${cfg.pets.placement}`;
|
|
1231
630
|
}
|
|
1232
631
|
|
|
1233
|
-
|
|
1234
|
-
|
|
632
|
+
type SettingsItemOverrides = Record<
|
|
633
|
+
string,
|
|
634
|
+
Partial<Pick<SettingsPickerItem, "currentValue" | "description" | "values">>
|
|
635
|
+
>;
|
|
636
|
+
|
|
637
|
+
function settingsItemsFromDescriptors(
|
|
638
|
+
descriptors: readonly SettingsOptionDescriptor[],
|
|
1235
639
|
cfg: ResolvedConfig,
|
|
640
|
+
overrides: SettingsItemOverrides = {},
|
|
1236
641
|
): SettingsPickerItem[] {
|
|
642
|
+
return descriptors.map((descriptor) => {
|
|
643
|
+
const override = overrides[descriptor.id] ?? {};
|
|
644
|
+
const values = override.values ?? descriptor.values;
|
|
645
|
+
return {
|
|
646
|
+
id: descriptor.id,
|
|
647
|
+
label: descriptor.label,
|
|
648
|
+
currentValue: override.currentValue ?? descriptor.currentValue(cfg),
|
|
649
|
+
values: values ? [...values] : undefined,
|
|
650
|
+
description: override.description ?? descriptor.description,
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function buildFastSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
1237
656
|
return [
|
|
1238
657
|
{
|
|
1239
658
|
id: "fast.enabled",
|
|
1240
659
|
label: "Fast mode",
|
|
1241
|
-
currentValue: String(desiredActive),
|
|
660
|
+
currentValue: String(fastController.desiredActive),
|
|
1242
661
|
values: ["true", "false"],
|
|
1243
662
|
description: `Request OpenAI fast mode. Activates for supported models: ${modelList(cfg.supportedModels)}.`,
|
|
1244
663
|
},
|
|
1245
|
-
|
|
1246
|
-
id: "persistState",
|
|
1247
|
-
label: "Persist fast state",
|
|
1248
|
-
currentValue: String(cfg.persistState),
|
|
1249
|
-
values: ["true", "false"],
|
|
1250
|
-
description: "Remember fast-mode state across sessions.",
|
|
1251
|
-
},
|
|
664
|
+
...settingsItemsFromDescriptors(FAST_SETTING_DESCRIPTORS, cfg),
|
|
1252
665
|
];
|
|
1253
666
|
}
|
|
1254
667
|
|
|
1255
668
|
function buildFooterSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
1256
|
-
return
|
|
1257
|
-
{
|
|
1258
|
-
id: "footer.mode",
|
|
1259
|
-
label: "Footer mode",
|
|
1260
|
-
currentValue: cfg.footer.mode,
|
|
1261
|
-
values: [...FOOTER_MODES],
|
|
1262
|
-
description:
|
|
1263
|
-
"replace = custom footer, status = pi footer plus status line, off = no Better OpenAI footer/status unless Footer pet is enabled.",
|
|
1264
|
-
},
|
|
1265
|
-
];
|
|
669
|
+
return settingsItemsFromDescriptors(FOOTER_SETTING_DESCRIPTORS, cfg);
|
|
1266
670
|
}
|
|
1267
671
|
|
|
1268
672
|
function buildUsageSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
1269
|
-
return
|
|
1270
|
-
{
|
|
1271
|
-
id: "usage.enabled",
|
|
1272
|
-
label: "Usage display",
|
|
1273
|
-
currentValue: String(cfg.usage.enabled),
|
|
1274
|
-
values: ["true", "false"],
|
|
1275
|
-
description: "Fetch and display OpenAI subscription usage windows.",
|
|
1276
|
-
},
|
|
1277
|
-
{
|
|
1278
|
-
id: "usage.refreshIntervalMs",
|
|
1279
|
-
label: "Usage refresh",
|
|
1280
|
-
currentValue: String(cfg.usage.refreshIntervalMs),
|
|
1281
|
-
values: ["15000", "30000", "60000", "120000", "300000", "600000"],
|
|
1282
|
-
description: "Usage refresh interval in milliseconds.",
|
|
1283
|
-
},
|
|
1284
|
-
{
|
|
1285
|
-
id: "usage.showOnlyOnSubscriptionModels",
|
|
1286
|
-
label: "Usage only on OAuth",
|
|
1287
|
-
currentValue: String(cfg.usage.showOnlyOnSubscriptionModels),
|
|
1288
|
-
values: ["true", "false"],
|
|
1289
|
-
description: "Only show usage when the current OpenAI model uses subscription/OAuth auth.",
|
|
1290
|
-
},
|
|
1291
|
-
{
|
|
1292
|
-
id: "usage.showResetTimes",
|
|
1293
|
-
label: "Usage reset times",
|
|
1294
|
-
currentValue: String(cfg.usage.showResetTimes),
|
|
1295
|
-
values: ["true", "false"],
|
|
1296
|
-
description: "Include compact reset countdowns and local reset times.",
|
|
1297
|
-
},
|
|
1298
|
-
];
|
|
673
|
+
return settingsItemsFromDescriptors(USAGE_SETTING_DESCRIPTORS, cfg);
|
|
1299
674
|
}
|
|
1300
675
|
|
|
1301
676
|
function buildImageSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
1302
|
-
return
|
|
1303
|
-
{
|
|
1304
|
-
id: "image.enabled",
|
|
1305
|
-
label: "Image tool",
|
|
1306
|
-
currentValue: String(cfg.image.enabled),
|
|
1307
|
-
values: ["true", "false"],
|
|
1308
|
-
description: "Allow the openai_image tool to make image requests.",
|
|
1309
|
-
},
|
|
1310
|
-
{
|
|
1311
|
-
id: "image.defaultModel",
|
|
1312
|
-
label: "Image model",
|
|
1313
|
-
currentValue: cfg.image.defaultModel,
|
|
1314
|
-
values: ["gpt-5.5", "gpt-5.4", "gpt-5.2", "gpt-5"],
|
|
1315
|
-
description:
|
|
1316
|
-
"Mainline model used for image generation when current model is not openai-codex.",
|
|
1317
|
-
},
|
|
1318
|
-
{
|
|
1319
|
-
id: "image.defaultSave",
|
|
1320
|
-
label: "Image save",
|
|
1321
|
-
currentValue: cfg.image.defaultSave,
|
|
1322
|
-
values: [...IMAGE_SAVE_MODES],
|
|
1323
|
-
description: "Where generated images are saved by default.",
|
|
1324
|
-
},
|
|
1325
|
-
{
|
|
1326
|
-
id: "image.outputFormat",
|
|
1327
|
-
label: "Image format",
|
|
1328
|
-
currentValue: cfg.image.outputFormat,
|
|
1329
|
-
values: [...IMAGE_OUTPUT_FORMATS],
|
|
1330
|
-
description: "Generated image file format.",
|
|
1331
|
-
},
|
|
1332
|
-
{
|
|
1333
|
-
id: "image.timeoutMs",
|
|
1334
|
-
label: "Image timeout",
|
|
1335
|
-
currentValue: String(cfg.image.timeoutMs),
|
|
1336
|
-
values: ["30000", "60000", "120000", "180000", "300000"],
|
|
1337
|
-
description: "Image request timeout in milliseconds.",
|
|
1338
|
-
},
|
|
1339
|
-
];
|
|
677
|
+
return settingsItemsFromDescriptors(IMAGE_SETTING_DESCRIPTORS, cfg);
|
|
1340
678
|
}
|
|
1341
679
|
|
|
1342
680
|
function buildDiagnosticsSettingsItems(
|
|
@@ -1362,11 +700,13 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1362
700
|
id: "config.print",
|
|
1363
701
|
label: "Print config",
|
|
1364
702
|
currentValue: "open",
|
|
1365
|
-
description: "Show the selected
|
|
703
|
+
description: "Show the selected config JSON with sensitive fields redacted.",
|
|
1366
704
|
submenu: (_value, done) =>
|
|
1367
705
|
textPanel(
|
|
1368
706
|
"Config",
|
|
1369
|
-
JSON.stringify(readRawConfig(cfg.configPath), null, 2).split(
|
|
707
|
+
JSON.stringify(redactDiagnosticValue(readRawConfig(cfg.configPath)), null, 2).split(
|
|
708
|
+
"\n",
|
|
709
|
+
),
|
|
1370
710
|
() => done(),
|
|
1371
711
|
),
|
|
1372
712
|
},
|
|
@@ -1383,7 +723,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1383
723
|
submenu: (_value, done) =>
|
|
1384
724
|
settingsSubmenu(
|
|
1385
725
|
"Fast mode settings",
|
|
1386
|
-
() => buildFastSettingsItems(
|
|
726
|
+
() => buildFastSettingsItems(config(ctx)),
|
|
1387
727
|
ctx,
|
|
1388
728
|
() => done(fastSettingsSummary(ctx, config(ctx))),
|
|
1389
729
|
),
|
|
@@ -1433,7 +773,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1433
773
|
currentValue: petSettingsSummary(cfg),
|
|
1434
774
|
description: "Configure footer pet visibility, animation-state mapping, and size.",
|
|
1435
775
|
submenu: (_value, done) => {
|
|
1436
|
-
|
|
776
|
+
petController.setSettingsPreviewActive(ctx, true);
|
|
1437
777
|
return settingsSubmenu(
|
|
1438
778
|
"Footer pet settings",
|
|
1439
779
|
() => buildPetSettingsItems(config(ctx)),
|
|
@@ -1442,14 +782,14 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1442
782
|
{
|
|
1443
783
|
onSelection: (item) => {
|
|
1444
784
|
const previewState = petPreviewFromItem(item);
|
|
1445
|
-
if (previewState !==
|
|
1446
|
-
|
|
785
|
+
if (previewState !== petController.previewState) {
|
|
786
|
+
petController.setPreviewState(previewState);
|
|
1447
787
|
updateFooter(ctx);
|
|
1448
788
|
}
|
|
1449
789
|
},
|
|
1450
790
|
onClose: () => {
|
|
1451
|
-
|
|
1452
|
-
|
|
791
|
+
petController.setPreviewState(undefined);
|
|
792
|
+
petController.setSettingsPreviewActive(ctx, false);
|
|
1453
793
|
updateFooter(ctx);
|
|
1454
794
|
},
|
|
1455
795
|
renderExtra: (width) => renderPetSettingsPreview(ctx, width),
|
|
@@ -1477,79 +817,47 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1477
817
|
const cfg = refresh(ctx);
|
|
1478
818
|
const current = readRawConfig(cfg.configPath);
|
|
1479
819
|
const bool = rawValue === "true";
|
|
1480
|
-
const num = Number(rawValue);
|
|
1481
820
|
if (id === "fast.enabled") {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
} else if (id.startsWith("pets.")) {
|
|
1499
|
-
const pets = isRecord(current.pets) ? current.pets : {};
|
|
1500
|
-
const key = id.slice("pets.".length);
|
|
1501
|
-
pets[key] =
|
|
1502
|
-
key === "sizeCells" || key === "idleEmoteIntervalMs"
|
|
1503
|
-
? num
|
|
1504
|
-
: key === "slug"
|
|
1505
|
-
? petSlugFromPickerValue(rawValue)
|
|
1506
|
-
: rawValue === "true"
|
|
1507
|
-
? true
|
|
1508
|
-
: rawValue === "false"
|
|
1509
|
-
? false
|
|
1510
|
-
: rawValue;
|
|
1511
|
-
current.pets = pets;
|
|
1512
|
-
if (key === "enabled" || key === "sizeCells" || key === "slug") petLoadKey = undefined;
|
|
1513
|
-
if (key === "placement" || key === "sizeCells" || key === "slug") resetPetRenderCache();
|
|
1514
|
-
if (key === "idleEmotes" || key === "idleEmoteIntervalMs") stopPetIdleEmotes();
|
|
1515
|
-
} else if (id.startsWith("image.")) {
|
|
1516
|
-
const image = isRecord(current.image) ? current.image : {};
|
|
1517
|
-
const key = id.slice("image.".length);
|
|
1518
|
-
image[key] =
|
|
1519
|
-
key === "timeoutMs"
|
|
1520
|
-
? num
|
|
1521
|
-
: rawValue === "true"
|
|
1522
|
-
? true
|
|
1523
|
-
: rawValue === "false"
|
|
1524
|
-
? false
|
|
1525
|
-
: rawValue;
|
|
1526
|
-
current.image = image;
|
|
821
|
+
fastController.setDesired(ctx, cfg, bool);
|
|
822
|
+
}
|
|
823
|
+
const petKey = id.startsWith("pets.") ? id.slice("pets.".length) : undefined;
|
|
824
|
+
const nextRawConfig = applySettingToRawConfig(current, id, rawValue, {
|
|
825
|
+
persistState: cfg.persistState,
|
|
826
|
+
active: fastController.active,
|
|
827
|
+
desiredActive: fastController.desiredActive,
|
|
828
|
+
petEmptyValue: PET_EMPTY_VALUE,
|
|
829
|
+
});
|
|
830
|
+
if (petKey) {
|
|
831
|
+
if (petKey === "enabled" || petKey === "sizeCells" || petKey === "slug")
|
|
832
|
+
petController.invalidateLoadKey();
|
|
833
|
+
if (petKey === "placement" || petKey === "sizeCells" || petKey === "slug")
|
|
834
|
+
petController.resetRenderCache();
|
|
835
|
+
if (petKey === "idleEmotes" || petKey === "idleEmoteIntervalMs")
|
|
836
|
+
petController.stopIdleEmotes();
|
|
1527
837
|
}
|
|
1528
|
-
writeConfig(cfg.configPath,
|
|
838
|
+
writeConfig(cfg.configPath, nextRawConfig);
|
|
1529
839
|
const next = refresh(ctx);
|
|
1530
840
|
if (id === "pets.enabled" || id === "pets.sizeCells" || id === "pets.slug")
|
|
1531
|
-
void
|
|
841
|
+
void petController.refresh(ctx, next);
|
|
1532
842
|
if (id.startsWith("usage.")) {
|
|
1533
|
-
|
|
1534
|
-
usageTimer = undefined;
|
|
1535
|
-
if (next.usage.enabled) startUsageRefresh(ctx);
|
|
1536
|
-
else {
|
|
1537
|
-
usageSnapshot = undefined;
|
|
1538
|
-
usageError = "Usage display is disabled.";
|
|
1539
|
-
}
|
|
843
|
+
usageController.restartAfterSettingsChange(ctx, next);
|
|
1540
844
|
}
|
|
1541
845
|
updateFooter(ctx);
|
|
1542
846
|
}
|
|
1543
847
|
|
|
1544
848
|
async function showSettingsPicker(ctx: ExtensionContext): Promise<void> {
|
|
849
|
+
if (!hasTerminalUI(ctx)) {
|
|
850
|
+
ctx.ui.notify("Better OpenAI settings require interactive TUI mode.", "warning");
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
1545
853
|
try {
|
|
1546
|
-
|
|
854
|
+
petController.settingsPets = await listCodexPets();
|
|
1547
855
|
} catch {
|
|
1548
|
-
|
|
856
|
+
petController.settingsPets = [];
|
|
1549
857
|
}
|
|
1550
858
|
try {
|
|
1551
859
|
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
1552
|
-
|
|
860
|
+
petController.setSettingsRenderRequest(() => tui.requestRender());
|
|
1553
861
|
const container = new Container();
|
|
1554
862
|
container.addChild(
|
|
1555
863
|
new (class {
|
|
@@ -1595,12 +903,10 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1595
903
|
};
|
|
1596
904
|
});
|
|
1597
905
|
} finally {
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
updateFooter(ctx);
|
|
1603
|
-
}
|
|
906
|
+
petController.setSettingsRenderRequest(undefined);
|
|
907
|
+
petController.setPreviewState(undefined);
|
|
908
|
+
petController.setSettingsPreviewActive(ctx, false);
|
|
909
|
+
updateFooter(ctx);
|
|
1604
910
|
}
|
|
1605
911
|
}
|
|
1606
912
|
|
|
@@ -1628,17 +934,16 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1628
934
|
slug: selectedPet.slug,
|
|
1629
935
|
});
|
|
1630
936
|
updateFooter(ctx);
|
|
1631
|
-
await
|
|
937
|
+
if (hasTerminalUI(ctx)) await petController.refresh(ctx, next, true);
|
|
938
|
+
else
|
|
939
|
+
ctx.ui.notify(
|
|
940
|
+
`Enabled ${selectedPet.name} (${selectedPet.slug}); footer pets render in interactive TUI mode.`,
|
|
941
|
+
"info",
|
|
942
|
+
);
|
|
1632
943
|
},
|
|
1633
944
|
tuck: (ctx) => {
|
|
1634
945
|
writePetConfig(ctx, { enabled: false });
|
|
1635
|
-
|
|
1636
|
-
pet = undefined;
|
|
1637
|
-
petError = undefined;
|
|
1638
|
-
resetPetRenderCache();
|
|
1639
|
-
clearPetFlash();
|
|
1640
|
-
stopPetIdleEmotes();
|
|
1641
|
-
stopPetAnimation();
|
|
946
|
+
petController.tuck();
|
|
1642
947
|
updateFooter(ctx);
|
|
1643
948
|
ctx.ui.notify("Footer pet tucked away.", "info");
|
|
1644
949
|
},
|
|
@@ -1659,8 +964,14 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1659
964
|
|
|
1660
965
|
const next = writePetConfig(ctx, { slug: selectedPet.slug });
|
|
1661
966
|
updateFooter(ctx);
|
|
1662
|
-
if (
|
|
1663
|
-
|
|
967
|
+
if (petController.shouldLoadForConfig(next)) {
|
|
968
|
+
if (hasTerminalUI(ctx)) await petController.refresh(ctx, next, true);
|
|
969
|
+
else
|
|
970
|
+
ctx.ui.notify(
|
|
971
|
+
`Selected ${selectedPet.name} (${selectedPet.slug}); footer pets render in interactive TUI mode.`,
|
|
972
|
+
"info",
|
|
973
|
+
);
|
|
974
|
+
} else {
|
|
1664
975
|
ctx.ui.notify(
|
|
1665
976
|
`Selected ${selectedPet.name} (${selectedPet.slug}) for the footer pet. Use /pets wake to show it.`,
|
|
1666
977
|
"info",
|
|
@@ -1671,36 +982,36 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1671
982
|
|
|
1672
983
|
function installFooter(ctx: ExtensionContext): void {
|
|
1673
984
|
if (footerInstalled) {
|
|
1674
|
-
|
|
985
|
+
petController.requestFooterRenderNow();
|
|
1675
986
|
return;
|
|
1676
987
|
}
|
|
1677
988
|
footerInstalled = true;
|
|
1678
989
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
1679
|
-
|
|
990
|
+
petController.setFooterRenderRequest(() => tui.requestRender());
|
|
1680
991
|
const unsubscribe = footerData.onBranchChange?.(() => tui.requestRender());
|
|
1681
992
|
let lastFooterSizeKey: string | undefined;
|
|
1682
993
|
return {
|
|
1683
994
|
dispose: () => {
|
|
1684
995
|
unsubscribe?.();
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
996
|
+
petController.stopIdleEmotes();
|
|
997
|
+
petController.stopAnimation();
|
|
998
|
+
petController.stopPendingRenderRequest();
|
|
999
|
+
petController.disposeKittyNow();
|
|
1689
1000
|
footerInstalled = false;
|
|
1690
|
-
|
|
1001
|
+
petController.setFooterRenderRequest(undefined);
|
|
1691
1002
|
},
|
|
1692
1003
|
invalidate() {
|
|
1693
|
-
|
|
1694
|
-
|
|
1004
|
+
petController.queueKittyCleanup();
|
|
1005
|
+
petController.resetRenderCache();
|
|
1695
1006
|
},
|
|
1696
1007
|
render(width: number): string[] {
|
|
1697
1008
|
const now = Date.now();
|
|
1698
1009
|
const footerSizeKey = `${width}:${process.stdout.rows ?? 0}`;
|
|
1699
1010
|
if (lastFooterSizeKey !== undefined && lastFooterSizeKey !== footerSizeKey) {
|
|
1700
|
-
|
|
1011
|
+
petController.freezeForResize(ctx, now);
|
|
1701
1012
|
}
|
|
1702
1013
|
lastFooterSizeKey = footerSizeKey;
|
|
1703
|
-
const freezePetFrame =
|
|
1014
|
+
const freezePetFrame = petController.isResizeFrozen(now);
|
|
1704
1015
|
|
|
1705
1016
|
const totalInput = footerTotals.input;
|
|
1706
1017
|
const totalOutput = footerTotals.output;
|
|
@@ -1708,15 +1019,13 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1708
1019
|
const totalCacheWrite = footerTotals.cacheWrite;
|
|
1709
1020
|
const totalCost = footerTotals.cost;
|
|
1710
1021
|
|
|
1711
|
-
let pwd = ctx.sessionManager.getCwd();
|
|
1712
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
1713
|
-
if (home && pwd.startsWith(home)) pwd = `~${pwd.slice(home.length)}`;
|
|
1022
|
+
let pwd = abbreviateHomePath(ctx.sessionManager.getCwd());
|
|
1714
1023
|
|
|
1715
1024
|
const branch = footerData.getGitBranch?.();
|
|
1716
1025
|
if (branch) pwd = `${pwd} (${branch})`;
|
|
1717
1026
|
|
|
1718
|
-
const
|
|
1719
|
-
if (
|
|
1027
|
+
const currentSessionName = sessionName(ctx);
|
|
1028
|
+
if (currentSessionName) pwd = `${pwd} • ${currentSessionName}`;
|
|
1720
1029
|
|
|
1721
1030
|
const parts: string[] = [];
|
|
1722
1031
|
if (totalInput) parts.push(`↑${formatTokens(totalInput)}`);
|
|
@@ -1728,11 +1037,11 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1728
1037
|
if (totalCost || usingSubscription)
|
|
1729
1038
|
parts.push(`$${totalCost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`);
|
|
1730
1039
|
|
|
1731
|
-
const
|
|
1732
|
-
const contextWindow =
|
|
1733
|
-
const contextPercentValue =
|
|
1040
|
+
const currentContextUsage = contextUsage(ctx);
|
|
1041
|
+
const contextWindow = currentContextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
1042
|
+
const contextPercentValue = currentContextUsage?.percent ?? 0;
|
|
1734
1043
|
const contextPercent =
|
|
1735
|
-
|
|
1044
|
+
currentContextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
1736
1045
|
const contextDisplay =
|
|
1737
1046
|
contextPercent === "?"
|
|
1738
1047
|
? `?/${formatTokens(contextWindow)} (auto)`
|
|
@@ -1746,27 +1055,24 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1746
1055
|
parts.push(contextText);
|
|
1747
1056
|
|
|
1748
1057
|
const cfg = config(ctx);
|
|
1749
|
-
const
|
|
1750
|
-
const
|
|
1751
|
-
const requestedPetPlacement = cfgForPets.pets.placement;
|
|
1058
|
+
const shouldRenderPet = petController.shouldRenderInFooter(cfg);
|
|
1059
|
+
const requestedPetPlacement = cfg.pets.placement;
|
|
1752
1060
|
const requestedPetSizeCells = petSizeCellsForPlacement(
|
|
1753
1061
|
requestedPetPlacement,
|
|
1754
|
-
|
|
1062
|
+
cfg.pets.sizeCells,
|
|
1755
1063
|
);
|
|
1756
1064
|
const inlinePet = Boolean(
|
|
1757
1065
|
shouldRenderPet &&
|
|
1758
|
-
|
|
1066
|
+
petController.loadedPet &&
|
|
1759
1067
|
isInlinePetPlacement(requestedPetPlacement) &&
|
|
1760
1068
|
width >= requestedPetSizeCells + 32,
|
|
1761
1069
|
);
|
|
1762
|
-
const petRenderSizeCells = inlinePet ? requestedPetSizeCells :
|
|
1070
|
+
const petRenderSizeCells = inlinePet ? requestedPetSizeCells : cfg.pets.sizeCells;
|
|
1763
1071
|
const petColumnWidth = Math.min(petRenderSizeCells, Math.max(1, width - 1));
|
|
1764
1072
|
const footerTextWidth = inlinePet ? Math.max(1, width - petColumnWidth - 2) : width;
|
|
1765
1073
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
usageLine = theme.fg("dim", formatUsageSnapshot(usageSnapshot, cfg.usage));
|
|
1769
|
-
}
|
|
1074
|
+
const usageStatusLine = usageController.statusLine(ctx, cfg, usingSubscription);
|
|
1075
|
+
const usageLine = usageStatusLine ? theme.fg("dim", usageStatusLine) : undefined;
|
|
1770
1076
|
|
|
1771
1077
|
let statsLeft = parts.join(" ");
|
|
1772
1078
|
let statsLeftWidth = visibleWidth(statsLeft);
|
|
@@ -1778,7 +1084,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1778
1084
|
const modelName = ctx.model?.id || "no-model";
|
|
1779
1085
|
const thinkingLevel = pi.getThinkingLevel();
|
|
1780
1086
|
const fastSuffix =
|
|
1781
|
-
active && supportsFast(ctx,
|
|
1087
|
+
fastController.active && supportsFast(ctx, cfg.supportedModels) ? " fast" : "";
|
|
1782
1088
|
let rightWithoutProvider = modelName;
|
|
1783
1089
|
if (ctx.model?.reasoning) {
|
|
1784
1090
|
rightWithoutProvider =
|
|
@@ -1835,65 +1141,21 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1835
1141
|
textLines.push(truncateToWidth(statusLine, footerTextWidth, theme.fg("dim", "...")));
|
|
1836
1142
|
}
|
|
1837
1143
|
|
|
1838
|
-
const petLines
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
elapsedMs,
|
|
1848
|
-
petColumnWidth,
|
|
1849
|
-
petRenderSizeCells,
|
|
1850
|
-
),
|
|
1851
|
-
);
|
|
1852
|
-
} else {
|
|
1853
|
-
const { frameIndex } = petFrameInfo(pet, petState, elapsedMs);
|
|
1854
|
-
const cacheKey = petRenderCacheKey(
|
|
1855
|
-
pet,
|
|
1856
|
-
petState,
|
|
1857
|
-
frameIndex,
|
|
1858
|
-
requestedPetPlacement,
|
|
1859
|
-
petColumnWidth,
|
|
1860
|
-
petRenderSizeCells,
|
|
1861
|
-
);
|
|
1862
|
-
const cachedPetLines = petRenderCache.get(cacheKey);
|
|
1863
|
-
const imageProtocol = getCapabilities().images;
|
|
1864
|
-
if (cachedPetLines) {
|
|
1865
|
-
petLines.push(...cachedPetLines);
|
|
1866
|
-
} else {
|
|
1867
|
-
const renderedPetLines = renderCodexPetFrame(
|
|
1868
|
-
pet,
|
|
1869
|
-
petState,
|
|
1870
|
-
petColumnWidth,
|
|
1871
|
-
theme,
|
|
1872
|
-
{
|
|
1873
|
-
sizeCells: petRenderSizeCells,
|
|
1874
|
-
imageId: petImageId,
|
|
1875
|
-
now: elapsedMs,
|
|
1876
|
-
durationMultiplier: 1,
|
|
1877
|
-
kittyManager: petKittyManager,
|
|
1878
|
-
},
|
|
1879
|
-
);
|
|
1880
|
-
petLines.push(...renderedPetLines);
|
|
1881
|
-
if (renderedPetLines.length > 0) {
|
|
1882
|
-
if (imageProtocol !== null && imageProtocol !== "kitty")
|
|
1883
|
-
rememberPetRender(cacheKey, renderedPetLines);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
} else if (petError) {
|
|
1888
|
-
petLines.push(truncateToWidth(theme.fg("warning", `pet: ${petError}`), width, "..."));
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1144
|
+
const petLines = petController.renderPetLines(ctx, cfg, {
|
|
1145
|
+
shouldRenderPet,
|
|
1146
|
+
freezePetFrame,
|
|
1147
|
+
requestedPetPlacement,
|
|
1148
|
+
petColumnWidth,
|
|
1149
|
+
petRenderSizeCells,
|
|
1150
|
+
width,
|
|
1151
|
+
theme,
|
|
1152
|
+
});
|
|
1891
1153
|
|
|
1892
1154
|
if (!shouldRenderPet || petLines.length === 0)
|
|
1893
|
-
return
|
|
1155
|
+
return petController.withPendingKittyCleanup(textLines);
|
|
1894
1156
|
|
|
1895
1157
|
if (inlinePet) {
|
|
1896
|
-
return
|
|
1158
|
+
return petController.withPendingKittyCleanup(
|
|
1897
1159
|
combineInlinePetFooter(
|
|
1898
1160
|
petLines,
|
|
1899
1161
|
textLines,
|
|
@@ -1904,16 +1166,16 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1904
1166
|
);
|
|
1905
1167
|
}
|
|
1906
1168
|
|
|
1907
|
-
if (requestedPetPlacement === "habitat" &&
|
|
1908
|
-
const label = ` ${
|
|
1169
|
+
if (requestedPetPlacement === "habitat" && petController.loadedPet) {
|
|
1170
|
+
const label = ` ${petController.loadedPet.pet.name} `;
|
|
1909
1171
|
const divider = theme.fg(
|
|
1910
1172
|
"dim",
|
|
1911
1173
|
truncateToWidth(`─${label}${"─".repeat(width)}`, width, ""),
|
|
1912
1174
|
);
|
|
1913
|
-
return
|
|
1175
|
+
return petController.withPendingKittyCleanup([divider, ...petLines, ...textLines]);
|
|
1914
1176
|
}
|
|
1915
1177
|
|
|
1916
|
-
return
|
|
1178
|
+
return petController.withPendingKittyCleanup([...petLines, ...textLines]);
|
|
1917
1179
|
},
|
|
1918
1180
|
};
|
|
1919
1181
|
});
|
|
@@ -1921,10 +1183,10 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1921
1183
|
|
|
1922
1184
|
function clearFooter(ctx: ExtensionContext): void {
|
|
1923
1185
|
if (!footerInstalled) return;
|
|
1924
|
-
|
|
1186
|
+
petController.disposeKittyNow();
|
|
1925
1187
|
ctx.ui.setFooter(undefined);
|
|
1926
1188
|
footerInstalled = false;
|
|
1927
|
-
|
|
1189
|
+
petController.setFooterRenderRequest(undefined);
|
|
1928
1190
|
}
|
|
1929
1191
|
|
|
1930
1192
|
function setStatus(ctx: ExtensionContext, text: string | undefined): void {
|
|
@@ -1935,13 +1197,20 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1935
1197
|
|
|
1936
1198
|
function updateFooter(ctx: ExtensionContext): void {
|
|
1937
1199
|
const cfg = config(ctx);
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1200
|
+
|
|
1201
|
+
if (!hasTerminalUI(ctx)) {
|
|
1202
|
+
if (cfg.footer.mode === "off") {
|
|
1203
|
+
setStatus(ctx, undefined);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const fast = fastController.statusSegment(ctx, cfg);
|
|
1207
|
+
const usage = usageController.statusLine(ctx, cfg);
|
|
1208
|
+
setStatus(ctx, [fast, usage].filter(Boolean).join(" | ") || undefined);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
petController.updateActivity(ctx, cfg);
|
|
1213
|
+
const shouldRenderPet = petController.shouldRenderInFooter(cfg);
|
|
1945
1214
|
|
|
1946
1215
|
if (cfg.footer.mode === "replace" || shouldRenderPet) {
|
|
1947
1216
|
setStatus(ctx, undefined);
|
|
@@ -1956,135 +1225,114 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1956
1225
|
return;
|
|
1957
1226
|
}
|
|
1958
1227
|
|
|
1959
|
-
const fast =
|
|
1960
|
-
|
|
1961
|
-
? `${ctx.model?.id ?? "model"} fast`
|
|
1962
|
-
: undefined;
|
|
1963
|
-
const usage =
|
|
1964
|
-
usageSnapshot && cfg.usage.enabled && isOpenAISubscriptionModel(ctx, cfg)
|
|
1965
|
-
? formatUsageSnapshot(usageSnapshot, cfg.usage)
|
|
1966
|
-
: undefined;
|
|
1228
|
+
const fast = fastController.statusSegment(ctx, cfg);
|
|
1229
|
+
const usage = usageController.statusLine(ctx, cfg);
|
|
1967
1230
|
setStatus(ctx, [fast, usage].filter(Boolean).join(" | ") || undefined);
|
|
1968
1231
|
}
|
|
1969
1232
|
|
|
1970
1233
|
pi.on("session_start", (_event, ctx) => {
|
|
1234
|
+
invalidateContextUsage();
|
|
1235
|
+
invalidateSessionName();
|
|
1971
1236
|
const nextConfig = refresh(ctx);
|
|
1972
|
-
|
|
1973
|
-
if (
|
|
1974
|
-
|
|
1975
|
-
|
|
1237
|
+
fastController.initializeForSession(ctx, nextConfig, pi.getFlag(FLAG) === true);
|
|
1238
|
+
if (
|
|
1239
|
+
fastController.desiredActive !== nextConfig.desiredActive ||
|
|
1240
|
+
fastController.active !== nextConfig.active
|
|
1241
|
+
)
|
|
1976
1242
|
persist(nextConfig);
|
|
1977
|
-
if (desiredActive && !active) {
|
|
1978
|
-
ctx.ui.notify(
|
|
1979
|
-
`Fast mode requested, but ${currentModelKey(ctx)} is unsupported. It will activate automatically when you switch to a supported model: ${modelList(nextConfig.supportedModels)}.`,
|
|
1980
|
-
"warning",
|
|
1981
|
-
);
|
|
1243
|
+
if (fastController.desiredActive && !fastController.active) {
|
|
1244
|
+
ctx.ui.notify(fastController.unsupportedRequestMessage(ctx, nextConfig), "warning");
|
|
1982
1245
|
}
|
|
1983
|
-
|
|
1246
|
+
if (hasTerminalUI(ctx)) petController.installResizeGuard(ctx);
|
|
1984
1247
|
refreshFooterTotals(ctx);
|
|
1985
1248
|
updateFooter(ctx);
|
|
1986
|
-
if (nextConfig.pets.enabled) void
|
|
1987
|
-
|
|
1988
|
-
if (active)
|
|
1989
|
-
ctx.ui.notify(stateText(ctx, desiredActive, active, nextConfig.supportedModels), "info");
|
|
1249
|
+
if (hasTerminalUI(ctx) && nextConfig.pets.enabled) void petController.refresh(ctx, nextConfig);
|
|
1250
|
+
usageController.start(ctx);
|
|
1251
|
+
if (fastController.active) ctx.ui.notify(fastController.stateText(ctx, nextConfig), "info");
|
|
1990
1252
|
});
|
|
1991
1253
|
|
|
1992
1254
|
pi.on("agent_start", (_event, ctx) => {
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
petRuntimeState = config(ctx).pets.thinkingState;
|
|
1255
|
+
invalidateContextUsage();
|
|
1256
|
+
petController.agentStart(ctx);
|
|
1996
1257
|
updateFooter(ctx);
|
|
1997
1258
|
});
|
|
1998
1259
|
|
|
1999
1260
|
pi.on("tool_execution_start", (event, ctx) => {
|
|
2000
|
-
|
|
2001
|
-
petRuntimeState = config(ctx).pets.toolState;
|
|
1261
|
+
petController.toolStart(ctx, event.toolCallId);
|
|
2002
1262
|
updateFooter(ctx);
|
|
2003
1263
|
});
|
|
2004
1264
|
|
|
2005
1265
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
petRuntimeState = activeToolCallIds.size > 0 ? cfg.pets.toolState : cfg.pets.thinkingState;
|
|
2009
|
-
if (event.isError) {
|
|
2010
|
-
playPetFlash(ctx, cfg.pets.failedToolState, cfg);
|
|
2011
|
-
return;
|
|
2012
|
-
}
|
|
2013
|
-
updateFooter(ctx);
|
|
1266
|
+
petController.toolEnd(ctx, event.toolCallId, event.isError);
|
|
1267
|
+
if (!event.isError) updateFooter(ctx);
|
|
2014
1268
|
});
|
|
2015
1269
|
|
|
2016
1270
|
pi.on("agent_end", (_event, ctx) => {
|
|
2017
|
-
|
|
2018
|
-
petRuntimeState = "idle";
|
|
1271
|
+
petController.agentEnd();
|
|
2019
1272
|
updateFooter(ctx);
|
|
2020
1273
|
});
|
|
2021
1274
|
|
|
2022
|
-
pi.on("turn_end", (
|
|
2023
|
-
|
|
1275
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1276
|
+
invalidateContextUsage();
|
|
1277
|
+
if (event.message?.role === "assistant") {
|
|
1278
|
+
footerTotals.input += event.message.usage.input;
|
|
1279
|
+
footerTotals.output += event.message.usage.output;
|
|
1280
|
+
footerTotals.cacheRead += event.message.usage.cacheRead;
|
|
1281
|
+
footerTotals.cacheWrite += event.message.usage.cacheWrite;
|
|
1282
|
+
footerTotals.cost += event.message.usage.cost.total;
|
|
1283
|
+
} else refreshFooterTotals(ctx);
|
|
2024
1284
|
updateFooter(ctx);
|
|
2025
|
-
void
|
|
1285
|
+
void usageController.refresh(ctx);
|
|
2026
1286
|
});
|
|
2027
1287
|
|
|
2028
1288
|
pi.on("session_compact", (_event, ctx) => {
|
|
1289
|
+
invalidateContextUsage();
|
|
2029
1290
|
refreshFooterTotals(ctx);
|
|
2030
|
-
|
|
2031
|
-
|
|
1291
|
+
petController.queueKittyCleanup();
|
|
1292
|
+
petController.resetRenderCache();
|
|
2032
1293
|
updateFooter(ctx);
|
|
2033
1294
|
});
|
|
2034
1295
|
|
|
2035
1296
|
pi.on("session_tree", (_event, ctx) => {
|
|
1297
|
+
invalidateContextUsage();
|
|
2036
1298
|
refreshFooterTotals(ctx);
|
|
2037
|
-
|
|
2038
|
-
|
|
1299
|
+
petController.queueKittyCleanup();
|
|
1300
|
+
petController.resetRenderCache();
|
|
2039
1301
|
updateFooter(ctx);
|
|
2040
1302
|
});
|
|
2041
1303
|
|
|
2042
1304
|
pi.on("model_select", (event, ctx) => {
|
|
1305
|
+
invalidateContextUsage();
|
|
2043
1306
|
const cfg = config(ctx);
|
|
2044
|
-
const wasActive = active;
|
|
2045
|
-
|
|
2046
|
-
if (active !== wasActive) {
|
|
1307
|
+
const wasActive = fastController.active;
|
|
1308
|
+
fastController.applyDesiredState(ctx, cfg);
|
|
1309
|
+
if (fastController.active !== wasActive) {
|
|
2047
1310
|
persist(cfg);
|
|
2048
1311
|
ctx.ui.notify(
|
|
2049
|
-
active
|
|
2050
|
-
? stateText(ctx,
|
|
2051
|
-
:
|
|
2052
|
-
active ? "info" : "warning",
|
|
1312
|
+
fastController.active
|
|
1313
|
+
? fastController.stateText(ctx, cfg)
|
|
1314
|
+
: fastController.inactiveForModelMessage(ctx),
|
|
1315
|
+
fastController.active ? "info" : "warning",
|
|
2053
1316
|
);
|
|
2054
1317
|
}
|
|
2055
1318
|
updateFooter(ctx);
|
|
2056
|
-
void
|
|
1319
|
+
void usageController.refresh(ctx, event.model.id, { force: true });
|
|
2057
1320
|
});
|
|
2058
1321
|
|
|
2059
1322
|
pi.on("session_shutdown", () => {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
petLoadNotify = false;
|
|
2065
|
-
usageAbortController?.abort();
|
|
2066
|
-
usageAbortController = undefined;
|
|
2067
|
-
if (usageTimer) clearInterval(usageTimer);
|
|
2068
|
-
usageTimer = undefined;
|
|
2069
|
-
activeToolCallIds.clear();
|
|
2070
|
-
uninstallPetResizeGuard();
|
|
2071
|
-
disposePetKittyNow();
|
|
2072
|
-
resetPetRenderCache();
|
|
2073
|
-
clearPetFlash();
|
|
2074
|
-
stopPetIdleEmotes();
|
|
2075
|
-
stopPetAnimation();
|
|
2076
|
-
stopPendingPetRenderRequest();
|
|
1323
|
+
invalidateContextUsage();
|
|
1324
|
+
invalidateSessionName();
|
|
1325
|
+
usageController.shutdown();
|
|
1326
|
+
petController.shutdown();
|
|
2077
1327
|
});
|
|
2078
1328
|
|
|
2079
1329
|
pi.on("before_provider_request", (event, ctx) => {
|
|
2080
|
-
|
|
2081
|
-
if (!active || !supportsFast(ctx, nextConfig.supportedModels) || !isRecord(event.payload))
|
|
2082
|
-
return;
|
|
2083
|
-
lastInjectedAt = Date.now();
|
|
2084
|
-
lastInjectedModel = currentModelKey(ctx);
|
|
2085
|
-
lastInjectedTier = SERVICE_TIER;
|
|
2086
|
-
return { ...event.payload, service_tier: SERVICE_TIER };
|
|
1330
|
+
return fastController.injectProviderPayload(event, ctx, config(ctx));
|
|
2087
1331
|
});
|
|
1332
|
+
|
|
1333
|
+
pi.on("message_start", invalidateContextUsage);
|
|
1334
|
+
pi.on("message_update", invalidateContextUsage);
|
|
1335
|
+
pi.on("message_end", invalidateContextUsage);
|
|
2088
1336
|
}
|
|
2089
1337
|
|
|
2090
1338
|
export const _test = {
|
|
@@ -2095,6 +1343,7 @@ export const _test = {
|
|
|
2095
1343
|
DEFAULT_PET_CONFIG,
|
|
2096
1344
|
SERVICE_TIER,
|
|
2097
1345
|
configPaths,
|
|
1346
|
+
abbreviateHomePath,
|
|
2098
1347
|
parseModelKey,
|
|
2099
1348
|
normalizeModelKeys,
|
|
2100
1349
|
parseModels,
|
|
@@ -2112,6 +1361,7 @@ export const _test = {
|
|
|
2112
1361
|
formatPercent,
|
|
2113
1362
|
formatUsageSnapshot,
|
|
2114
1363
|
readCodexAuth,
|
|
1364
|
+
textPanel,
|
|
2115
1365
|
imageTest: _imageTest,
|
|
2116
1366
|
petsTest: _petsTest,
|
|
2117
1367
|
};
|