@monotykamary/pi-better-openai 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/THIRD_PARTY_NOTICES.md +28 -0
- package/index.ts +1328 -0
- package/package.json +94 -0
- package/src/codex-auth.ts +136 -0
- package/src/config.ts +725 -0
- package/src/fast-controller.ts +112 -0
- package/src/footer-layout.ts +97 -0
- package/src/format.ts +104 -0
- package/src/identity.ts +7 -0
- package/src/image.ts +672 -0
- package/src/live/attestation.ts +86 -0
- package/src/live/controller.ts +523 -0
- package/src/live/index.ts +197 -0
- package/src/live/native.ts +109 -0
- package/src/live/protocol.ts +234 -0
- package/src/live/transport.ts +445 -0
- package/src/live/visualizer.ts +269 -0
- package/src/live/voices.ts +21 -0
- package/src/paths.ts +17 -0
- package/src/pet-footer-controller.ts +642 -0
- package/src/pets.ts +983 -0
- package/src/usage-controller.ts +304 -0
- package/src/usage.ts +264 -0
package/index.ts
ADDED
|
@@ -0,0 +1,1328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better OpenAI for pi.
|
|
3
|
+
*
|
|
4
|
+
* Adds `service_tier: "priority"` to OpenAI provider payloads while fast mode is
|
|
5
|
+
* enabled and the selected model is in the configured allow-list.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
getSettingsListTheme,
|
|
9
|
+
type ExtensionAPI,
|
|
10
|
+
type ExtensionContext,
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { Container, Key, matchesKey, SettingsList } from "@earendil-works/pi-tui";
|
|
13
|
+
import { CONFIG_BASENAME, STATUS_KEY } from "./src/identity.ts";
|
|
14
|
+
import {
|
|
15
|
+
formatTokens,
|
|
16
|
+
redactDiagnosticValue,
|
|
17
|
+
sanitizeStatusText,
|
|
18
|
+
truncateToWidth,
|
|
19
|
+
visibleWidth,
|
|
20
|
+
} from "./src/format.ts";
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_CONFIG,
|
|
23
|
+
DEFAULT_IMAGE_CONFIG,
|
|
24
|
+
DEFAULT_LIVE_CONFIG,
|
|
25
|
+
DEFAULT_PET_CONFIG,
|
|
26
|
+
DEFAULT_SUPPORTED_MODELS,
|
|
27
|
+
PET_STATES,
|
|
28
|
+
FOOTER_SETTING_DESCRIPTORS,
|
|
29
|
+
USAGE_SETTING_DESCRIPTORS,
|
|
30
|
+
IMAGE_SETTING_DESCRIPTORS,
|
|
31
|
+
LIVE_SETTING_DESCRIPTORS,
|
|
32
|
+
PET_SETTING_DESCRIPTORS,
|
|
33
|
+
FAST_SETTING_DESCRIPTORS,
|
|
34
|
+
type SettingsOptionDescriptor,
|
|
35
|
+
configPaths,
|
|
36
|
+
type ResolvedConfig,
|
|
37
|
+
type PetState,
|
|
38
|
+
isRecord,
|
|
39
|
+
parseModelKey,
|
|
40
|
+
normalizeModelKeys,
|
|
41
|
+
parseModels,
|
|
42
|
+
readRawConfig,
|
|
43
|
+
resolveConfig,
|
|
44
|
+
writeConfig,
|
|
45
|
+
applySettingToRawConfig,
|
|
46
|
+
} from "./src/config.ts";
|
|
47
|
+
import {
|
|
48
|
+
formatPercent,
|
|
49
|
+
formatUsageSnapshot,
|
|
50
|
+
parseUsageSnapshot,
|
|
51
|
+
readCodexAuth,
|
|
52
|
+
} from "./src/usage.ts";
|
|
53
|
+
import { registerOpenAIImage, _imageTest } from "./src/image.ts";
|
|
54
|
+
import { registerOpenAILive } from "./src/live/index.ts";
|
|
55
|
+
import {
|
|
56
|
+
type CodexPetPackage,
|
|
57
|
+
codexHome,
|
|
58
|
+
describeCodexPetSelectionIssue,
|
|
59
|
+
findCodexPet,
|
|
60
|
+
findReadyCodexPet,
|
|
61
|
+
formatNoReadyCodexPetsMessage,
|
|
62
|
+
listCodexPets,
|
|
63
|
+
registerOpenAIPets,
|
|
64
|
+
_petsTest,
|
|
65
|
+
} from "./src/pets.ts";
|
|
66
|
+
import { FastController, modelList, supportsFast } from "./src/fast-controller.ts";
|
|
67
|
+
import { UsageController } from "./src/usage-controller.ts";
|
|
68
|
+
import { PetFooterController } from "./src/pet-footer-controller.ts";
|
|
69
|
+
import {
|
|
70
|
+
abbreviateHomePath,
|
|
71
|
+
combineInlinePetFooter,
|
|
72
|
+
isInlinePetPlacement,
|
|
73
|
+
isTerminalImageLine,
|
|
74
|
+
petSizeCellsForPlacement,
|
|
75
|
+
} from "./src/footer-layout.ts";
|
|
76
|
+
|
|
77
|
+
const COMMAND = "fast";
|
|
78
|
+
const OPENAI_STATUS_COMMAND = "openai-usage";
|
|
79
|
+
const OPENAI_SETTINGS_COMMAND = "openai-settings";
|
|
80
|
+
const FLAG = "fast";
|
|
81
|
+
const PET_EMPTY_VALUE = "not selected";
|
|
82
|
+
const SERVICE_TIER = "priority";
|
|
83
|
+
type SettingsPickerItem = {
|
|
84
|
+
id: string;
|
|
85
|
+
label: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
currentValue: string;
|
|
88
|
+
values?: string[];
|
|
89
|
+
submenu?: (
|
|
90
|
+
currentValue: string,
|
|
91
|
+
done: (selectedValue?: string) => void,
|
|
92
|
+
) => { render(width: number): string[]; invalidate(): void; handleInput?(data: string): void };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function hasTerminalUI(ctx: ExtensionContext): boolean {
|
|
96
|
+
return ctx.mode === "tui" || (ctx.mode === undefined && ctx.hasUI);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readyPetPickerValues(pets: CodexPetPackage[]): string[] | undefined {
|
|
100
|
+
const readyPets = pets.filter((pet) => pet.hasSpritesheet);
|
|
101
|
+
return readyPets.length > 0 ? readyPets.map((pet) => pet.slug) : undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function petConfigPickerValue(cfg: ResolvedConfig): string {
|
|
105
|
+
return cfg.pets.slug || PET_EMPTY_VALUE;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function petSlugFromPickerValue(value: string): string {
|
|
109
|
+
return value === PET_EMPTY_VALUE ? "" : value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function findPickerPet(value: string, pets: CodexPetPackage[]): CodexPetPackage | undefined {
|
|
113
|
+
return findCodexPet(
|
|
114
|
+
pets.filter((pet) => pet.hasSpritesheet),
|
|
115
|
+
value,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function petPickerDescription(cfg: ResolvedConfig, pets: CodexPetPackage[]): string {
|
|
120
|
+
const readyPets = pets.filter((pet) => pet.hasSpritesheet);
|
|
121
|
+
if (readyPets.length === 0)
|
|
122
|
+
return "No ready custom pets found. Use /pets help to create one, then return here.";
|
|
123
|
+
if (!cfg.pets.slug) {
|
|
124
|
+
const firstPet = readyPets[0]!;
|
|
125
|
+
const lastPet = readyPets[readyPets.length - 1]!;
|
|
126
|
+
return `No pet selected. Enter/Space/→ selects ${firstPet.name}; ← selects ${lastPet.name}.`;
|
|
127
|
+
}
|
|
128
|
+
const selectedPet = findPickerPet(cfg.pets.slug, readyPets);
|
|
129
|
+
const selected = selectedPet
|
|
130
|
+
? `Selected: ${selectedPet.name} (${selectedPet.slug})`
|
|
131
|
+
: `Selected: ${cfg.pets.slug}`;
|
|
132
|
+
return `${selected}. Enter/Space/→ cycles pets; ← cycles back. Preview appears in the footer while this menu is open.`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatPetSelectPrompt(
|
|
136
|
+
pets: CodexPetPackage[],
|
|
137
|
+
home = codexHome(),
|
|
138
|
+
): { message: string; level: "info" | "warning" } {
|
|
139
|
+
const readyPets = pets.filter((pet) => pet.hasSpritesheet);
|
|
140
|
+
if (readyPets.length === 0) {
|
|
141
|
+
return { message: formatNoReadyCodexPetsMessage(pets, home), level: "warning" };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const brokenPets = pets.filter((pet) => !pet.hasSpritesheet);
|
|
145
|
+
const lines = [
|
|
146
|
+
"Choose one with /pets select <slug>:",
|
|
147
|
+
...readyPets.map((pet) => `- ${pet.slug} (${pet.name})`),
|
|
148
|
+
];
|
|
149
|
+
if (brokenPets.length > 0) {
|
|
150
|
+
lines.push(
|
|
151
|
+
"",
|
|
152
|
+
"Not ready:",
|
|
153
|
+
...brokenPets.map(
|
|
154
|
+
(pet) =>
|
|
155
|
+
`- ${pet.slug} (${pet.name}) — ${pet.spritesheetIssue ?? `missing ${pet.spritesheetPath}`}`,
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return { message: lines.join("\n"), level: "info" };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function textPanel(title: string, lines: string[], done: () => void) {
|
|
163
|
+
return {
|
|
164
|
+
render(width: number) {
|
|
165
|
+
const clipped = lines.map((line) => truncateToWidth(line, width, "..."));
|
|
166
|
+
return [title, "", ...clipped, "", "Esc/q to go back"];
|
|
167
|
+
},
|
|
168
|
+
invalidate() {},
|
|
169
|
+
handleInput(data: string) {
|
|
170
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || data === "q") done();
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
176
|
+
const fastController = new FastController(SERVICE_TIER);
|
|
177
|
+
let cachedConfig: ResolvedConfig | undefined;
|
|
178
|
+
let footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
179
|
+
let footerInstalled = false;
|
|
180
|
+
let statusInstalled = false;
|
|
181
|
+
let statusWidgetInstalled = false;
|
|
182
|
+
let contextUsageCached = false;
|
|
183
|
+
let cachedContextUsage: ReturnType<ExtensionContext["getContextUsage"]>;
|
|
184
|
+
let cachedContextLeafId: string | null | undefined;
|
|
185
|
+
let cachedContextModel: ExtensionContext["model"];
|
|
186
|
+
let sessionNameCached = false;
|
|
187
|
+
let cachedSessionNameLeafId: string | null | undefined;
|
|
188
|
+
let cachedSessionName: string | undefined;
|
|
189
|
+
const usageController = new UsageController(config, updateFooter);
|
|
190
|
+
const petController = new PetFooterController(config, updateFooter, () => footerInstalled);
|
|
191
|
+
|
|
192
|
+
function refresh(ctx: ExtensionContext): ResolvedConfig {
|
|
193
|
+
cachedConfig = resolveConfig(ctx.cwd || process.cwd());
|
|
194
|
+
return cachedConfig;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function config(ctx: ExtensionContext): ResolvedConfig {
|
|
198
|
+
return cachedConfig ?? refresh(ctx);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function persist(nextConfig: ResolvedConfig): void {
|
|
202
|
+
cachedConfig = {
|
|
203
|
+
...nextConfig,
|
|
204
|
+
active: fastController.active,
|
|
205
|
+
desiredActive: fastController.desiredActive,
|
|
206
|
+
};
|
|
207
|
+
if (!nextConfig.persistState) return;
|
|
208
|
+
writeConfig(nextConfig.configPath, {
|
|
209
|
+
...readRawConfig(nextConfig.configPath),
|
|
210
|
+
active: fastController.active,
|
|
211
|
+
desiredActive: fastController.desiredActive,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function writePetConfig(ctx: ExtensionContext, patch: Record<string, unknown>): ResolvedConfig {
|
|
216
|
+
const cfg = refresh(ctx);
|
|
217
|
+
const current = readRawConfig(cfg.configPath);
|
|
218
|
+
const pets = isRecord(current.pets) ? current.pets : {};
|
|
219
|
+
writeConfig(cfg.configPath, { ...current, pets: { ...pets, ...patch } });
|
|
220
|
+
petController.invalidateLoadKey();
|
|
221
|
+
return refresh(ctx);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function setActive(ctx: ExtensionContext, next: boolean): void {
|
|
225
|
+
const nextConfig = refresh(ctx);
|
|
226
|
+
fastController.setDesired(ctx, nextConfig, next);
|
|
227
|
+
persist(nextConfig);
|
|
228
|
+
updateFooter(ctx);
|
|
229
|
+
if (next && !fastController.active) {
|
|
230
|
+
ctx.ui.notify(fastController.unsupportedRequestMessage(ctx, nextConfig), "warning");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
ctx.ui.notify(fastController.stateText(ctx, nextConfig), "info");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function refreshFooterTotals(ctx: ExtensionContext): void {
|
|
237
|
+
footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
238
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
239
|
+
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
240
|
+
footerTotals.input += entry.message.usage.input;
|
|
241
|
+
footerTotals.output += entry.message.usage.output;
|
|
242
|
+
footerTotals.cacheRead += entry.message.usage.cacheRead;
|
|
243
|
+
footerTotals.cacheWrite += entry.message.usage.cacheWrite;
|
|
244
|
+
footerTotals.cost += entry.message.usage.cost.total;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function invalidateContextUsage(): void {
|
|
249
|
+
contextUsageCached = false;
|
|
250
|
+
cachedContextUsage = undefined;
|
|
251
|
+
cachedContextLeafId = undefined;
|
|
252
|
+
cachedContextModel = undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function contextUsage(ctx: ExtensionContext): ReturnType<ExtensionContext["getContextUsage"]> {
|
|
256
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
257
|
+
const model = ctx.model;
|
|
258
|
+
if (!contextUsageCached || leafId !== cachedContextLeafId || model !== cachedContextModel) {
|
|
259
|
+
cachedContextUsage = ctx.getContextUsage();
|
|
260
|
+
contextUsageCached = true;
|
|
261
|
+
cachedContextLeafId = leafId;
|
|
262
|
+
cachedContextModel = model;
|
|
263
|
+
}
|
|
264
|
+
return cachedContextUsage;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function sessionName(ctx: ExtensionContext): string | undefined {
|
|
268
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
269
|
+
if (!sessionNameCached || leafId !== cachedSessionNameLeafId) {
|
|
270
|
+
cachedSessionName = ctx.sessionManager.getSessionName();
|
|
271
|
+
cachedSessionNameLeafId = leafId;
|
|
272
|
+
sessionNameCached = true;
|
|
273
|
+
}
|
|
274
|
+
return cachedSessionName;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function invalidateSessionName(): void {
|
|
278
|
+
sessionNameCached = false;
|
|
279
|
+
cachedSessionNameLeafId = undefined;
|
|
280
|
+
cachedSessionName = undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
pi.registerFlag(FLAG, {
|
|
284
|
+
description: "Start with OpenAI fast mode enabled (service_tier=priority)",
|
|
285
|
+
type: "boolean",
|
|
286
|
+
default: false,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
function formatDebugStatus(ctx: ExtensionContext): string {
|
|
290
|
+
const cfg = config(ctx);
|
|
291
|
+
return [
|
|
292
|
+
...fastController.debugLines(ctx, cfg),
|
|
293
|
+
`Footer mode: ${cfg.footer.mode}`,
|
|
294
|
+
"",
|
|
295
|
+
usageController.formatDebug(ctx),
|
|
296
|
+
"",
|
|
297
|
+
`Image enabled: ${cfg.image.enabled}`,
|
|
298
|
+
`Image default save: ${cfg.image.defaultSave}`,
|
|
299
|
+
`Live enabled: ${cfg.live.enabled}`,
|
|
300
|
+
`Live voice: ${cfg.live.voice}`,
|
|
301
|
+
`Pet enabled: ${cfg.pets.enabled}`,
|
|
302
|
+
`Pet slug: ${cfg.pets.slug || PET_EMPTY_VALUE}`,
|
|
303
|
+
`Pet placement: ${cfg.pets.placement}`,
|
|
304
|
+
`Pet failed tool state: ${cfg.pets.failedToolState}`,
|
|
305
|
+
`Pet idle emotes: ${cfg.pets.idleEmotes} (${cfg.pets.idleEmoteIntervalMs}ms)`,
|
|
306
|
+
`Pet loaded: ${petController.loadedPet?.pet.name ?? "none"}`,
|
|
307
|
+
`Pet error: ${petController.error ?? "none"}`,
|
|
308
|
+
`Config: ${cfg.configPath}`,
|
|
309
|
+
].join("\n");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
pi.registerCommand(COMMAND, {
|
|
313
|
+
description: "Toggle OpenAI fast mode",
|
|
314
|
+
handler: async (args, ctx) => {
|
|
315
|
+
const arg = args.trim().toLowerCase();
|
|
316
|
+
if (!arg) return setActive(ctx, !fastController.desiredActive);
|
|
317
|
+
ctx.ui.notify("Usage: /fast", "error");
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
pi.registerCommand(OPENAI_STATUS_COMMAND, {
|
|
322
|
+
description: "Show OpenAI subscription usage status",
|
|
323
|
+
handler: async (_args, ctx) => {
|
|
324
|
+
await usageController.refresh(ctx, ctx.model?.id, { notify: true, force: true });
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
function buildPetSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
329
|
+
return settingsItemsFromDescriptors(PET_SETTING_DESCRIPTORS, cfg, {
|
|
330
|
+
"pets.slug": {
|
|
331
|
+
currentValue: petConfigPickerValue(cfg),
|
|
332
|
+
values: readyPetPickerValues(petController.settingsPets),
|
|
333
|
+
description: petPickerDescription(cfg, petController.settingsPets),
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function petPreviewFromItem(item: SettingsPickerItem | undefined): PetState | undefined {
|
|
339
|
+
if (
|
|
340
|
+
item?.id !== "pets.state" &&
|
|
341
|
+
item?.id !== "pets.thinkingState" &&
|
|
342
|
+
item?.id !== "pets.toolState" &&
|
|
343
|
+
item?.id !== "pets.failedToolState"
|
|
344
|
+
)
|
|
345
|
+
return undefined;
|
|
346
|
+
const value = item.currentValue;
|
|
347
|
+
return (PET_STATES as readonly string[]).includes(value) ? (value as PetState) : undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function renderPetSettingsPreview(ctx: ExtensionContext, width: number): string[] {
|
|
351
|
+
return petController.renderSettingsPreview(ctx, width);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function settingsSubmenu(
|
|
355
|
+
title: string,
|
|
356
|
+
items: () => SettingsPickerItem[],
|
|
357
|
+
ctx: ExtensionContext,
|
|
358
|
+
done: () => void,
|
|
359
|
+
options?: {
|
|
360
|
+
onSelection?: (item: SettingsPickerItem | undefined) => void;
|
|
361
|
+
onClose?: () => void;
|
|
362
|
+
renderExtra?: (width: number) => string[];
|
|
363
|
+
},
|
|
364
|
+
) {
|
|
365
|
+
const theme = getSettingsListTheme();
|
|
366
|
+
let selectedIndex = 0;
|
|
367
|
+
let searchQuery = "";
|
|
368
|
+
let closed = false;
|
|
369
|
+
let submenuComponent: ReturnType<NonNullable<SettingsPickerItem["submenu"]>> | undefined;
|
|
370
|
+
let submenuItemIndex: number | undefined;
|
|
371
|
+
|
|
372
|
+
function currentItems(): SettingsPickerItem[] {
|
|
373
|
+
const allItems = items();
|
|
374
|
+
const query = searchQuery.trim().toLowerCase();
|
|
375
|
+
const current = query
|
|
376
|
+
? allItems.filter((item) => item.label.toLowerCase().includes(query))
|
|
377
|
+
: allItems;
|
|
378
|
+
selectedIndex = Math.max(0, Math.min(selectedIndex, Math.max(0, current.length - 1)));
|
|
379
|
+
return current;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function selectedItem(): SettingsPickerItem | undefined {
|
|
383
|
+
return currentItems()[selectedIndex];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function close(): void {
|
|
387
|
+
closed = true;
|
|
388
|
+
options?.onClose?.();
|
|
389
|
+
done();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function closeNestedSubmenu(): void {
|
|
393
|
+
submenuComponent = undefined;
|
|
394
|
+
if (submenuItemIndex !== undefined) selectedIndex = submenuItemIndex;
|
|
395
|
+
submenuItemIndex = undefined;
|
|
396
|
+
options?.onSelection?.(selectedItem());
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function cycleSelected(direction: 1 | -1 = 1): void {
|
|
400
|
+
const item = selectedItem();
|
|
401
|
+
if (!item?.values?.length) return;
|
|
402
|
+
const currentIndex = item.values.indexOf(item.currentValue);
|
|
403
|
+
const startIndex = currentIndex === -1 ? (direction === 1 ? -1 : 0) : currentIndex;
|
|
404
|
+
const newValue =
|
|
405
|
+
item.values[(startIndex + direction + item.values.length) % item.values.length] ??
|
|
406
|
+
item.currentValue;
|
|
407
|
+
writeSetting(ctx, item.id, newValue);
|
|
408
|
+
options?.onSelection?.(selectedItem());
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function activateSelected(): void {
|
|
412
|
+
const item = selectedItem();
|
|
413
|
+
if (!item) return;
|
|
414
|
+
if (item.submenu) {
|
|
415
|
+
submenuItemIndex = selectedIndex;
|
|
416
|
+
submenuComponent = item.submenu(item.currentValue, (selectedValue?: string) => {
|
|
417
|
+
if (selectedValue !== undefined) writeSetting(ctx, item.id, selectedValue);
|
|
418
|
+
closeNestedSubmenu();
|
|
419
|
+
});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
cycleSelected(1);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
render(width: number) {
|
|
427
|
+
if (submenuComponent) return submenuComponent.render(width);
|
|
428
|
+
const current = currentItems();
|
|
429
|
+
const selected = selectedItem();
|
|
430
|
+
if (!closed) options?.onSelection?.(selected);
|
|
431
|
+
const lines = [title, "", `> ${searchQuery}`, ""];
|
|
432
|
+
const maxVisible = 8;
|
|
433
|
+
const startIndex = Math.max(
|
|
434
|
+
0,
|
|
435
|
+
Math.min(selectedIndex - Math.floor(maxVisible / 2), current.length - maxVisible),
|
|
436
|
+
);
|
|
437
|
+
const visible = current.slice(startIndex, startIndex + maxVisible);
|
|
438
|
+
if (current.length === 0) {
|
|
439
|
+
lines.push(theme.hint(" No matching settings"));
|
|
440
|
+
lines.push("", theme.hint(" Type to search · Esc to go back"));
|
|
441
|
+
return lines;
|
|
442
|
+
}
|
|
443
|
+
const maxLabelWidth = Math.min(
|
|
444
|
+
30,
|
|
445
|
+
Math.max(1, ...current.map((item) => visibleWidth(item.label))),
|
|
446
|
+
);
|
|
447
|
+
for (let i = 0; i < visible.length; i++) {
|
|
448
|
+
const item = visible[i];
|
|
449
|
+
if (!item) continue;
|
|
450
|
+
const itemIndex = startIndex + i;
|
|
451
|
+
const isSelected = itemIndex === selectedIndex;
|
|
452
|
+
const prefix = isSelected ? theme.cursor : " ";
|
|
453
|
+
const labelPadded =
|
|
454
|
+
item.label + " ".repeat(Math.max(0, maxLabelWidth - visibleWidth(item.label)));
|
|
455
|
+
const valueMaxWidth = Math.max(1, width - visibleWidth(prefix) - maxLabelWidth - 4);
|
|
456
|
+
lines.push(
|
|
457
|
+
truncateToWidth(
|
|
458
|
+
prefix +
|
|
459
|
+
theme.label(labelPadded, isSelected) +
|
|
460
|
+
" " +
|
|
461
|
+
theme.value(truncateToWidth(item.currentValue, valueMaxWidth, ""), isSelected),
|
|
462
|
+
width,
|
|
463
|
+
),
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
if (selected?.description) {
|
|
467
|
+
lines.push(
|
|
468
|
+
"",
|
|
469
|
+
theme.description(` ${truncateToWidth(selected.description, width - 4)}`),
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
const extraLines = options?.renderExtra?.(width) ?? [];
|
|
473
|
+
if (extraLines.length > 0) {
|
|
474
|
+
lines.push("");
|
|
475
|
+
for (const line of extraLines) {
|
|
476
|
+
lines.push(isTerminalImageLine(line) ? line : truncateToWidth(line, width, "..."));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
lines.push(
|
|
480
|
+
"",
|
|
481
|
+
theme.hint(" Type to search · ↑↓ navigate · ←→/Enter/Space to change · Esc to go back"),
|
|
482
|
+
);
|
|
483
|
+
return lines;
|
|
484
|
+
},
|
|
485
|
+
invalidate() {
|
|
486
|
+
submenuComponent?.invalidate();
|
|
487
|
+
},
|
|
488
|
+
handleInput(data: string) {
|
|
489
|
+
if (submenuComponent) {
|
|
490
|
+
submenuComponent.handleInput?.(data);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const current = currentItems();
|
|
494
|
+
if (current.length === 0) {
|
|
495
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) close();
|
|
496
|
+
else if (matchesKey(data, Key.backspace)) searchQuery = searchQuery.slice(0, -1);
|
|
497
|
+
else if (data.length === 1 && data >= "!" && data <= "~") searchQuery += data;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (matchesKey(data, Key.up))
|
|
501
|
+
selectedIndex = selectedIndex === 0 ? current.length - 1 : selectedIndex - 1;
|
|
502
|
+
else if (matchesKey(data, Key.down))
|
|
503
|
+
selectedIndex = selectedIndex === current.length - 1 ? 0 : selectedIndex + 1;
|
|
504
|
+
else if (
|
|
505
|
+
matchesKey(data, Key.right) ||
|
|
506
|
+
matchesKey(data, Key.enter) ||
|
|
507
|
+
matchesKey(data, Key.space) ||
|
|
508
|
+
data === " "
|
|
509
|
+
)
|
|
510
|
+
activateSelected();
|
|
511
|
+
else if (matchesKey(data, Key.left)) cycleSelected(-1);
|
|
512
|
+
else if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) close();
|
|
513
|
+
else if (matchesKey(data, Key.backspace)) {
|
|
514
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
515
|
+
selectedIndex = 0;
|
|
516
|
+
} else if (data.length === 1 && data >= "!" && data <= "~") {
|
|
517
|
+
searchQuery += data;
|
|
518
|
+
selectedIndex = 0;
|
|
519
|
+
}
|
|
520
|
+
if (!closed) options?.onSelection?.(selectedItem());
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function fastSettingsSummary(ctx: ExtensionContext, cfg: ResolvedConfig): string {
|
|
526
|
+
return fastController.settingsSummary(ctx, cfg);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function usageSettingsSummary(cfg: ResolvedConfig): string {
|
|
530
|
+
return cfg.usage.enabled
|
|
531
|
+
? `enabled · ${Math.round(cfg.usage.refreshIntervalMs / 1000)}s`
|
|
532
|
+
: "disabled";
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function imageSettingsSummary(cfg: ResolvedConfig): string {
|
|
536
|
+
return cfg.image.enabled
|
|
537
|
+
? `enabled · ${cfg.image.defaultModel} · ${cfg.image.defaultSave}/${cfg.image.outputFormat}`
|
|
538
|
+
: "disabled";
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function liveSettingsSummary(cfg: ResolvedConfig): string {
|
|
542
|
+
return cfg.live.enabled ? `enabled · ${cfg.live.voice}` : "disabled";
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function petSettingsSummary(cfg: ResolvedConfig): string {
|
|
546
|
+
const selected = cfg.pets.slug || (cfg.pets.enabled ? "first ready" : PET_EMPTY_VALUE);
|
|
547
|
+
const status = cfg.pets.enabled ? "enabled" : "disabled";
|
|
548
|
+
return `${status} · ${selected} · ${cfg.pets.placement}`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
type SettingsItemOverrides = Record<
|
|
552
|
+
string,
|
|
553
|
+
Partial<Pick<SettingsPickerItem, "currentValue" | "description" | "values">>
|
|
554
|
+
>;
|
|
555
|
+
|
|
556
|
+
function settingsItemsFromDescriptors(
|
|
557
|
+
descriptors: readonly SettingsOptionDescriptor[],
|
|
558
|
+
cfg: ResolvedConfig,
|
|
559
|
+
overrides: SettingsItemOverrides = {},
|
|
560
|
+
): SettingsPickerItem[] {
|
|
561
|
+
return descriptors.map((descriptor) => {
|
|
562
|
+
const override = overrides[descriptor.id] ?? {};
|
|
563
|
+
const values = override.values ?? descriptor.values;
|
|
564
|
+
return {
|
|
565
|
+
id: descriptor.id,
|
|
566
|
+
label: descriptor.label,
|
|
567
|
+
currentValue: override.currentValue ?? descriptor.currentValue(cfg),
|
|
568
|
+
values: values ? [...values] : undefined,
|
|
569
|
+
description: override.description ?? descriptor.description,
|
|
570
|
+
};
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function buildFastSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
575
|
+
return [
|
|
576
|
+
{
|
|
577
|
+
id: "fast.enabled",
|
|
578
|
+
label: "Fast mode",
|
|
579
|
+
currentValue: String(fastController.desiredActive),
|
|
580
|
+
values: ["true", "false"],
|
|
581
|
+
description: `Request OpenAI fast mode. Activates for supported models: ${modelList(cfg.supportedModels)}.`,
|
|
582
|
+
},
|
|
583
|
+
...settingsItemsFromDescriptors(FAST_SETTING_DESCRIPTORS, cfg),
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function buildFooterSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
588
|
+
return settingsItemsFromDescriptors(FOOTER_SETTING_DESCRIPTORS, cfg);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function buildUsageSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
592
|
+
return settingsItemsFromDescriptors(USAGE_SETTING_DESCRIPTORS, cfg);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function buildImageSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
596
|
+
return settingsItemsFromDescriptors(IMAGE_SETTING_DESCRIPTORS, cfg);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function buildLiveSettingsItems(cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
600
|
+
return settingsItemsFromDescriptors(LIVE_SETTING_DESCRIPTORS, cfg);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function buildDiagnosticsSettingsItems(
|
|
604
|
+
ctx: ExtensionContext,
|
|
605
|
+
cfg: ResolvedConfig,
|
|
606
|
+
): SettingsPickerItem[] {
|
|
607
|
+
return [
|
|
608
|
+
{
|
|
609
|
+
id: "debug",
|
|
610
|
+
label: "Debug info",
|
|
611
|
+
currentValue: "open",
|
|
612
|
+
description: "Show Better OpenAI diagnostics.",
|
|
613
|
+
submenu: (_value, done) =>
|
|
614
|
+
textPanel("Debug info", formatDebugStatus(ctx).split("\n"), () => done()),
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
id: "config.path",
|
|
618
|
+
label: "Config path",
|
|
619
|
+
currentValue: cfg.configPath,
|
|
620
|
+
description: `Project: ${cfg.projectConfigPath}\nGlobal: ${cfg.globalConfigPath}`,
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
id: "config.print",
|
|
624
|
+
label: "Print config",
|
|
625
|
+
currentValue: "open",
|
|
626
|
+
description: "Show the selected config JSON with sensitive fields redacted.",
|
|
627
|
+
submenu: (_value, done) =>
|
|
628
|
+
textPanel(
|
|
629
|
+
"Config",
|
|
630
|
+
JSON.stringify(redactDiagnosticValue(readRawConfig(cfg.configPath)), null, 2).split(
|
|
631
|
+
"\n",
|
|
632
|
+
),
|
|
633
|
+
() => done(),
|
|
634
|
+
),
|
|
635
|
+
},
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function buildSettingsSections(ctx: ExtensionContext, cfg: ResolvedConfig): SettingsPickerItem[] {
|
|
640
|
+
return [
|
|
641
|
+
{
|
|
642
|
+
id: "section.fast",
|
|
643
|
+
label: "Fast mode",
|
|
644
|
+
currentValue: fastSettingsSummary(ctx, cfg),
|
|
645
|
+
description: "Configure OpenAI fast mode and persistence.",
|
|
646
|
+
submenu: (_value, done) =>
|
|
647
|
+
settingsSubmenu(
|
|
648
|
+
"Fast mode settings",
|
|
649
|
+
() => buildFastSettingsItems(config(ctx)),
|
|
650
|
+
ctx,
|
|
651
|
+
() => done(fastSettingsSummary(ctx, config(ctx))),
|
|
652
|
+
),
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: "section.footer",
|
|
656
|
+
label: "Footer",
|
|
657
|
+
currentValue: cfg.footer.mode,
|
|
658
|
+
description: "Configure Better OpenAI footer ownership.",
|
|
659
|
+
submenu: (_value, done) =>
|
|
660
|
+
settingsSubmenu(
|
|
661
|
+
"Footer settings",
|
|
662
|
+
() => buildFooterSettingsItems(config(ctx)),
|
|
663
|
+
ctx,
|
|
664
|
+
() => done(config(ctx).footer.mode),
|
|
665
|
+
),
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
id: "section.usage",
|
|
669
|
+
label: "Usage",
|
|
670
|
+
currentValue: usageSettingsSummary(cfg),
|
|
671
|
+
description: "Configure subscription usage fetching and display.",
|
|
672
|
+
submenu: (_value, done) =>
|
|
673
|
+
settingsSubmenu(
|
|
674
|
+
"Usage settings",
|
|
675
|
+
() => buildUsageSettingsItems(config(ctx)),
|
|
676
|
+
ctx,
|
|
677
|
+
() => done(usageSettingsSummary(config(ctx))),
|
|
678
|
+
),
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: "section.image",
|
|
682
|
+
label: "Image tool",
|
|
683
|
+
currentValue: imageSettingsSummary(cfg),
|
|
684
|
+
description: "Configure OpenAI image generation defaults.",
|
|
685
|
+
submenu: (_value, done) =>
|
|
686
|
+
settingsSubmenu(
|
|
687
|
+
"Image tool settings",
|
|
688
|
+
() => buildImageSettingsItems(config(ctx)),
|
|
689
|
+
ctx,
|
|
690
|
+
() => done(imageSettingsSummary(config(ctx))),
|
|
691
|
+
),
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: "section.live",
|
|
695
|
+
label: "Live voice",
|
|
696
|
+
currentValue: liveSettingsSummary(cfg),
|
|
697
|
+
description: "Configure Codex-backed realtime voice mode.",
|
|
698
|
+
submenu: (_value, done) =>
|
|
699
|
+
settingsSubmenu(
|
|
700
|
+
"Live voice settings",
|
|
701
|
+
() => buildLiveSettingsItems(config(ctx)),
|
|
702
|
+
ctx,
|
|
703
|
+
() => done(liveSettingsSummary(config(ctx))),
|
|
704
|
+
),
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
id: "section.pets",
|
|
708
|
+
label: "Footer pet",
|
|
709
|
+
currentValue: petSettingsSummary(cfg),
|
|
710
|
+
description: "Configure footer pet visibility, animation-state mapping, and size.",
|
|
711
|
+
submenu: (_value, done) => {
|
|
712
|
+
petController.setSettingsPreviewActive(ctx, true);
|
|
713
|
+
return settingsSubmenu(
|
|
714
|
+
"Footer pet settings",
|
|
715
|
+
() => buildPetSettingsItems(config(ctx)),
|
|
716
|
+
ctx,
|
|
717
|
+
() => done(petSettingsSummary(config(ctx))),
|
|
718
|
+
{
|
|
719
|
+
onSelection: (item) => {
|
|
720
|
+
const previewState = petPreviewFromItem(item);
|
|
721
|
+
if (previewState !== petController.previewState) {
|
|
722
|
+
petController.setPreviewState(previewState);
|
|
723
|
+
updateFooter(ctx);
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
onClose: () => {
|
|
727
|
+
petController.setPreviewState(undefined);
|
|
728
|
+
petController.setSettingsPreviewActive(ctx, false);
|
|
729
|
+
updateFooter(ctx);
|
|
730
|
+
},
|
|
731
|
+
renderExtra: (width) => renderPetSettingsPreview(ctx, width),
|
|
732
|
+
},
|
|
733
|
+
);
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
id: "section.diagnostics",
|
|
738
|
+
label: "Diagnostics",
|
|
739
|
+
currentValue: "debug / config",
|
|
740
|
+
description: "Show Better OpenAI diagnostics and raw config details.",
|
|
741
|
+
submenu: (_value, done) =>
|
|
742
|
+
settingsSubmenu(
|
|
743
|
+
"Diagnostics",
|
|
744
|
+
() => buildDiagnosticsSettingsItems(ctx, config(ctx)),
|
|
745
|
+
ctx,
|
|
746
|
+
() => done("debug / config"),
|
|
747
|
+
),
|
|
748
|
+
},
|
|
749
|
+
];
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function writeSetting(ctx: ExtensionContext, id: string, rawValue: string): void {
|
|
753
|
+
const cfg = refresh(ctx);
|
|
754
|
+
const current = readRawConfig(cfg.configPath);
|
|
755
|
+
const bool = rawValue === "true";
|
|
756
|
+
if (id === "fast.enabled") {
|
|
757
|
+
fastController.setDesired(ctx, cfg, bool);
|
|
758
|
+
}
|
|
759
|
+
const petKey = id.startsWith("pets.") ? id.slice("pets.".length) : undefined;
|
|
760
|
+
const nextRawConfig = applySettingToRawConfig(current, id, rawValue, {
|
|
761
|
+
persistState: cfg.persistState,
|
|
762
|
+
active: fastController.active,
|
|
763
|
+
desiredActive: fastController.desiredActive,
|
|
764
|
+
petEmptyValue: PET_EMPTY_VALUE,
|
|
765
|
+
});
|
|
766
|
+
if (petKey) {
|
|
767
|
+
if (petKey === "enabled" || petKey === "sizeCells" || petKey === "slug")
|
|
768
|
+
petController.invalidateLoadKey();
|
|
769
|
+
if (petKey === "placement" || petKey === "sizeCells" || petKey === "slug")
|
|
770
|
+
petController.resetRenderCache();
|
|
771
|
+
if (petKey === "idleEmotes" || petKey === "idleEmoteIntervalMs")
|
|
772
|
+
petController.stopIdleEmotes();
|
|
773
|
+
}
|
|
774
|
+
writeConfig(cfg.configPath, nextRawConfig);
|
|
775
|
+
const next = refresh(ctx);
|
|
776
|
+
if (id === "pets.enabled" || id === "pets.sizeCells" || id === "pets.slug")
|
|
777
|
+
void petController.refresh(ctx, next);
|
|
778
|
+
if (id.startsWith("usage.")) {
|
|
779
|
+
usageController.restartAfterSettingsChange(ctx, next);
|
|
780
|
+
}
|
|
781
|
+
updateFooter(ctx);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async function showSettingsPicker(ctx: ExtensionContext): Promise<void> {
|
|
785
|
+
if (!hasTerminalUI(ctx)) {
|
|
786
|
+
ctx.ui.notify("Better OpenAI settings require interactive TUI mode.", "warning");
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
petController.settingsPets = await listCodexPets();
|
|
791
|
+
} catch {
|
|
792
|
+
petController.settingsPets = [];
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
796
|
+
petController.setSettingsRenderRequest(() => tui.requestRender());
|
|
797
|
+
const container = new Container();
|
|
798
|
+
container.addChild(
|
|
799
|
+
new (class {
|
|
800
|
+
render(_width: number) {
|
|
801
|
+
const cfg = config(ctx);
|
|
802
|
+
return [
|
|
803
|
+
theme.fg("accent", theme.bold("Better OpenAI Settings")),
|
|
804
|
+
theme.fg("dim", cfg.configPath),
|
|
805
|
+
"",
|
|
806
|
+
];
|
|
807
|
+
}
|
|
808
|
+
invalidate() {}
|
|
809
|
+
})(),
|
|
810
|
+
);
|
|
811
|
+
const settingsList = new SettingsList(
|
|
812
|
+
buildSettingsSections(ctx, refresh(ctx)),
|
|
813
|
+
8,
|
|
814
|
+
getSettingsListTheme(),
|
|
815
|
+
(id, newValue) => {
|
|
816
|
+
if (!id.startsWith("section.")) writeSetting(ctx, id, newValue);
|
|
817
|
+
settingsList.updateValue(
|
|
818
|
+
id,
|
|
819
|
+
buildSettingsSections(ctx, config(ctx)).find((item) => item.id === id)
|
|
820
|
+
?.currentValue ?? newValue,
|
|
821
|
+
);
|
|
822
|
+
tui.requestRender();
|
|
823
|
+
},
|
|
824
|
+
() => done(undefined),
|
|
825
|
+
{ enableSearch: true },
|
|
826
|
+
);
|
|
827
|
+
container.addChild(settingsList);
|
|
828
|
+
return {
|
|
829
|
+
render(width: number) {
|
|
830
|
+
return container.render(width);
|
|
831
|
+
},
|
|
832
|
+
invalidate() {
|
|
833
|
+
container.invalidate();
|
|
834
|
+
},
|
|
835
|
+
handleInput(data: string) {
|
|
836
|
+
settingsList.handleInput(data);
|
|
837
|
+
tui.requestRender();
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
});
|
|
841
|
+
} finally {
|
|
842
|
+
petController.setSettingsRenderRequest(undefined);
|
|
843
|
+
petController.setPreviewState(undefined);
|
|
844
|
+
petController.setSettingsPreviewActive(ctx, false);
|
|
845
|
+
updateFooter(ctx);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
pi.registerCommand(OPENAI_SETTINGS_COMMAND, {
|
|
850
|
+
description: "Open Better OpenAI settings picker",
|
|
851
|
+
handler: async (_args, ctx) => {
|
|
852
|
+
await showSettingsPicker(ctx);
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
registerOpenAIImage(pi, config);
|
|
857
|
+
registerOpenAILive(pi, config);
|
|
858
|
+
registerOpenAIPets(pi, {
|
|
859
|
+
wake: async (ctx, slug) => {
|
|
860
|
+
const pets = await listCodexPets();
|
|
861
|
+
const requestedSlug = (slug ?? config(ctx).pets.slug) || undefined;
|
|
862
|
+
const selectedPet = findReadyCodexPet(pets, requestedSlug);
|
|
863
|
+
if (!selectedPet) {
|
|
864
|
+
const issue = describeCodexPetSelectionIssue(pets, requestedSlug);
|
|
865
|
+
ctx.ui.notify(issue.message, "warning");
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const next = writePetConfig(ctx, {
|
|
870
|
+
enabled: true,
|
|
871
|
+
slug: selectedPet.slug,
|
|
872
|
+
});
|
|
873
|
+
updateFooter(ctx);
|
|
874
|
+
if (hasTerminalUI(ctx)) await petController.refresh(ctx, next, true);
|
|
875
|
+
else
|
|
876
|
+
ctx.ui.notify(
|
|
877
|
+
`Enabled ${selectedPet.name} (${selectedPet.slug}); footer pets render in interactive TUI mode.`,
|
|
878
|
+
"info",
|
|
879
|
+
);
|
|
880
|
+
},
|
|
881
|
+
tuck: (ctx) => {
|
|
882
|
+
writePetConfig(ctx, { enabled: false });
|
|
883
|
+
petController.tuck();
|
|
884
|
+
updateFooter(ctx);
|
|
885
|
+
ctx.ui.notify("Footer pet tucked away.", "info");
|
|
886
|
+
},
|
|
887
|
+
select: async (ctx, slug) => {
|
|
888
|
+
const pets = await listCodexPets();
|
|
889
|
+
if (!slug) {
|
|
890
|
+
const prompt = formatPetSelectPrompt(pets);
|
|
891
|
+
ctx.ui.notify(prompt.message, prompt.level);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const selectedPet = findReadyCodexPet(pets, slug);
|
|
896
|
+
if (!selectedPet) {
|
|
897
|
+
const issue = describeCodexPetSelectionIssue(pets, slug);
|
|
898
|
+
ctx.ui.notify(issue.message, "warning");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const next = writePetConfig(ctx, { slug: selectedPet.slug });
|
|
903
|
+
updateFooter(ctx);
|
|
904
|
+
if (petController.shouldLoadForConfig(next)) {
|
|
905
|
+
if (hasTerminalUI(ctx)) await petController.refresh(ctx, next, true);
|
|
906
|
+
else
|
|
907
|
+
ctx.ui.notify(
|
|
908
|
+
`Selected ${selectedPet.name} (${selectedPet.slug}); footer pets render in interactive TUI mode.`,
|
|
909
|
+
"info",
|
|
910
|
+
);
|
|
911
|
+
} else {
|
|
912
|
+
ctx.ui.notify(
|
|
913
|
+
`Selected ${selectedPet.name} (${selectedPet.slug}) for the footer pet. Use /pets wake to show it.`,
|
|
914
|
+
"info",
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
function installFooter(ctx: ExtensionContext): void {
|
|
921
|
+
if (footerInstalled) {
|
|
922
|
+
petController.requestFooterRenderNow();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
footerInstalled = true;
|
|
926
|
+
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
927
|
+
petController.setFooterRenderRequest(() => tui.requestRender());
|
|
928
|
+
const unsubscribe = footerData.onBranchChange?.(() => tui.requestRender());
|
|
929
|
+
let lastFooterSizeKey: string | undefined;
|
|
930
|
+
return {
|
|
931
|
+
dispose: () => {
|
|
932
|
+
unsubscribe?.();
|
|
933
|
+
petController.stopIdleEmotes();
|
|
934
|
+
petController.stopAnimation();
|
|
935
|
+
petController.stopPendingRenderRequest();
|
|
936
|
+
petController.disposeKittyNow();
|
|
937
|
+
footerInstalled = false;
|
|
938
|
+
petController.setFooterRenderRequest(undefined);
|
|
939
|
+
},
|
|
940
|
+
invalidate() {
|
|
941
|
+
petController.queueKittyCleanup();
|
|
942
|
+
petController.resetRenderCache();
|
|
943
|
+
},
|
|
944
|
+
render(width: number): string[] {
|
|
945
|
+
const now = Date.now();
|
|
946
|
+
const footerSizeKey = `${width}:${process.stdout.rows ?? 0}`;
|
|
947
|
+
if (lastFooterSizeKey !== undefined && lastFooterSizeKey !== footerSizeKey) {
|
|
948
|
+
petController.freezeForResize(ctx, now);
|
|
949
|
+
}
|
|
950
|
+
lastFooterSizeKey = footerSizeKey;
|
|
951
|
+
const freezePetFrame = petController.isResizeFrozen(now);
|
|
952
|
+
|
|
953
|
+
const totalInput = footerTotals.input;
|
|
954
|
+
const totalOutput = footerTotals.output;
|
|
955
|
+
const totalCacheRead = footerTotals.cacheRead;
|
|
956
|
+
const totalCacheWrite = footerTotals.cacheWrite;
|
|
957
|
+
const totalCost = footerTotals.cost;
|
|
958
|
+
|
|
959
|
+
let pwd = abbreviateHomePath(ctx.sessionManager.getCwd());
|
|
960
|
+
|
|
961
|
+
const branch = footerData.getGitBranch?.();
|
|
962
|
+
if (branch) pwd = `${pwd} (${branch})`;
|
|
963
|
+
|
|
964
|
+
const currentSessionName = sessionName(ctx);
|
|
965
|
+
if (currentSessionName) pwd = `${pwd} • ${currentSessionName}`;
|
|
966
|
+
|
|
967
|
+
const parts: string[] = [];
|
|
968
|
+
if (totalInput) parts.push(`↑${formatTokens(totalInput)}`);
|
|
969
|
+
if (totalOutput) parts.push(`↓${formatTokens(totalOutput)}`);
|
|
970
|
+
if (totalCacheRead) parts.push(`R${formatTokens(totalCacheRead)}`);
|
|
971
|
+
if (totalCacheWrite) parts.push(`W${formatTokens(totalCacheWrite)}`);
|
|
972
|
+
|
|
973
|
+
const usingSubscription = ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
|
|
974
|
+
if (totalCost || usingSubscription)
|
|
975
|
+
parts.push(`$${totalCost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`);
|
|
976
|
+
|
|
977
|
+
const currentContextUsage = contextUsage(ctx);
|
|
978
|
+
const contextWindow = currentContextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
979
|
+
const contextPercentValue = currentContextUsage?.percent ?? 0;
|
|
980
|
+
const contextPercent =
|
|
981
|
+
currentContextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
982
|
+
const contextDisplay =
|
|
983
|
+
contextPercent === "?"
|
|
984
|
+
? `?/${formatTokens(contextWindow)} (auto)`
|
|
985
|
+
: `${contextPercent}%/${formatTokens(contextWindow)} (auto)`;
|
|
986
|
+
const contextText =
|
|
987
|
+
contextPercentValue > 90
|
|
988
|
+
? theme.fg("error", contextDisplay)
|
|
989
|
+
: contextPercentValue > 70
|
|
990
|
+
? theme.fg("warning", contextDisplay)
|
|
991
|
+
: contextDisplay;
|
|
992
|
+
parts.push(contextText);
|
|
993
|
+
|
|
994
|
+
const cfg = config(ctx);
|
|
995
|
+
const shouldRenderPet = petController.shouldRenderInFooter(cfg);
|
|
996
|
+
const requestedPetPlacement = cfg.pets.placement;
|
|
997
|
+
const requestedPetSizeCells = petSizeCellsForPlacement(
|
|
998
|
+
requestedPetPlacement,
|
|
999
|
+
cfg.pets.sizeCells,
|
|
1000
|
+
);
|
|
1001
|
+
const inlinePet = Boolean(
|
|
1002
|
+
shouldRenderPet &&
|
|
1003
|
+
petController.loadedPet &&
|
|
1004
|
+
isInlinePetPlacement(requestedPetPlacement) &&
|
|
1005
|
+
width >= requestedPetSizeCells + 32,
|
|
1006
|
+
);
|
|
1007
|
+
const petRenderSizeCells = inlinePet ? requestedPetSizeCells : cfg.pets.sizeCells;
|
|
1008
|
+
const petColumnWidth = Math.min(petRenderSizeCells, Math.max(1, width - 1));
|
|
1009
|
+
const footerTextWidth = inlinePet ? Math.max(1, width - petColumnWidth - 2) : width;
|
|
1010
|
+
|
|
1011
|
+
const usageStatusLine = usageController.statusLine(ctx, cfg, usingSubscription);
|
|
1012
|
+
const usageLine = usageStatusLine ? theme.fg("dim", usageStatusLine) : undefined;
|
|
1013
|
+
|
|
1014
|
+
let statsLeft = parts.join(" ");
|
|
1015
|
+
let statsLeftWidth = visibleWidth(statsLeft);
|
|
1016
|
+
if (statsLeftWidth > footerTextWidth) {
|
|
1017
|
+
statsLeft = truncateToWidth(statsLeft, footerTextWidth, "...");
|
|
1018
|
+
statsLeftWidth = visibleWidth(statsLeft);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const modelName = ctx.model?.id || "no-model";
|
|
1022
|
+
const thinkingLevel = pi.getThinkingLevel();
|
|
1023
|
+
const fastSuffix =
|
|
1024
|
+
fastController.active && supportsFast(ctx, cfg.supportedModels) ? " fast" : "";
|
|
1025
|
+
let rightWithoutProvider = modelName;
|
|
1026
|
+
if (ctx.model?.reasoning) {
|
|
1027
|
+
rightWithoutProvider =
|
|
1028
|
+
thinkingLevel === "off"
|
|
1029
|
+
? `${modelName}${fastSuffix} • thinking off`
|
|
1030
|
+
: `${modelName}${fastSuffix} • ${thinkingLevel}`;
|
|
1031
|
+
} else if (fastSuffix) {
|
|
1032
|
+
rightWithoutProvider = `${modelName}${fastSuffix}`;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
let rightSide = rightWithoutProvider;
|
|
1036
|
+
if ((footerData.getAvailableProviderCount?.() ?? 0) > 1 && ctx.model) {
|
|
1037
|
+
const withProvider = `(${ctx.model.provider}) ${rightWithoutProvider}`;
|
|
1038
|
+
if (statsLeftWidth + 2 + visibleWidth(withProvider) <= footerTextWidth)
|
|
1039
|
+
rightSide = withProvider;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const rightWidth = visibleWidth(rightSide);
|
|
1043
|
+
const totalNeeded = statsLeftWidth + 2 + rightWidth;
|
|
1044
|
+
let statsLine: string;
|
|
1045
|
+
if (totalNeeded <= footerTextWidth) {
|
|
1046
|
+
statsLine =
|
|
1047
|
+
statsLeft + " ".repeat(footerTextWidth - statsLeftWidth - rightWidth) + rightSide;
|
|
1048
|
+
} else {
|
|
1049
|
+
const availableForRight = footerTextWidth - statsLeftWidth - 2;
|
|
1050
|
+
if (availableForRight > 0) {
|
|
1051
|
+
const truncatedRight = truncateToWidth(rightSide, availableForRight, "");
|
|
1052
|
+
statsLine =
|
|
1053
|
+
statsLeft +
|
|
1054
|
+
" ".repeat(
|
|
1055
|
+
Math.max(0, footerTextWidth - statsLeftWidth - visibleWidth(truncatedRight)),
|
|
1056
|
+
) +
|
|
1057
|
+
truncatedRight;
|
|
1058
|
+
} else {
|
|
1059
|
+
statsLine = statsLeft;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const textLines: string[] = [
|
|
1064
|
+
truncateToWidth(theme.fg("dim", pwd), footerTextWidth, theme.fg("dim", "...")),
|
|
1065
|
+
theme.fg("dim", statsLeft) + theme.fg("dim", statsLine.slice(statsLeft.length)),
|
|
1066
|
+
];
|
|
1067
|
+
|
|
1068
|
+
if (usageLine) {
|
|
1069
|
+
textLines.push(truncateToWidth(usageLine, footerTextWidth, theme.fg("dim", "...")));
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const extensionStatuses = footerData.getExtensionStatuses?.();
|
|
1073
|
+
if (extensionStatuses?.size) {
|
|
1074
|
+
const statusLines = Array.from(extensionStatuses.entries())
|
|
1075
|
+
.sort(([a], [b]) => String(a).localeCompare(String(b)))
|
|
1076
|
+
.map(([, text]) => theme.fg("dim", sanitizeStatusText(String(text))));
|
|
1077
|
+
textLines.push(
|
|
1078
|
+
...statusLines.map((line) =>
|
|
1079
|
+
truncateToWidth(line, footerTextWidth, theme.fg("dim", "...")),
|
|
1080
|
+
),
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const petLines = petController.renderPetLines(ctx, cfg, {
|
|
1085
|
+
shouldRenderPet,
|
|
1086
|
+
freezePetFrame,
|
|
1087
|
+
requestedPetPlacement,
|
|
1088
|
+
petColumnWidth,
|
|
1089
|
+
petRenderSizeCells,
|
|
1090
|
+
width,
|
|
1091
|
+
theme,
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
if (!shouldRenderPet || petLines.length === 0)
|
|
1095
|
+
return petController.withPendingKittyCleanup(textLines);
|
|
1096
|
+
|
|
1097
|
+
if (inlinePet) {
|
|
1098
|
+
return petController.withPendingKittyCleanup(
|
|
1099
|
+
combineInlinePetFooter(
|
|
1100
|
+
petLines,
|
|
1101
|
+
textLines,
|
|
1102
|
+
width,
|
|
1103
|
+
requestedPetPlacement,
|
|
1104
|
+
petColumnWidth,
|
|
1105
|
+
),
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (requestedPetPlacement === "habitat" && petController.loadedPet) {
|
|
1110
|
+
const label = ` ${petController.loadedPet.pet.name} `;
|
|
1111
|
+
const divider = theme.fg(
|
|
1112
|
+
"dim",
|
|
1113
|
+
truncateToWidth(`─${label}${"─".repeat(width)}`, width, ""),
|
|
1114
|
+
);
|
|
1115
|
+
return petController.withPendingKittyCleanup([divider, ...petLines, ...textLines]);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return petController.withPendingKittyCleanup([...petLines, ...textLines]);
|
|
1119
|
+
},
|
|
1120
|
+
};
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function clearFooter(ctx: ExtensionContext): void {
|
|
1125
|
+
if (!footerInstalled) return;
|
|
1126
|
+
petController.disposeKittyNow();
|
|
1127
|
+
ctx.ui.setFooter(undefined);
|
|
1128
|
+
footerInstalled = false;
|
|
1129
|
+
petController.setFooterRenderRequest(undefined);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function setStatus(ctx: ExtensionContext, text: string | undefined): void {
|
|
1133
|
+
if (!text && !statusInstalled) return;
|
|
1134
|
+
ctx.ui.setStatus(STATUS_KEY, text);
|
|
1135
|
+
statusInstalled = text !== undefined;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function setStatusWidget(ctx: ExtensionContext, text: string | undefined): void {
|
|
1139
|
+
if (!text && !statusWidgetInstalled) return;
|
|
1140
|
+
ctx.ui.setWidget(
|
|
1141
|
+
STATUS_KEY,
|
|
1142
|
+
text
|
|
1143
|
+
? (_tui, theme) => ({
|
|
1144
|
+
invalidate() {},
|
|
1145
|
+
render(width: number): string[] {
|
|
1146
|
+
const line = theme.fg("dim", sanitizeStatusText(text));
|
|
1147
|
+
return [truncateToWidth(line, width, theme.fg("dim", "..."))];
|
|
1148
|
+
},
|
|
1149
|
+
})
|
|
1150
|
+
: undefined,
|
|
1151
|
+
{ placement: "belowEditor" },
|
|
1152
|
+
);
|
|
1153
|
+
statusWidgetInstalled = text !== undefined;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function updateFooter(ctx: ExtensionContext): void {
|
|
1157
|
+
const cfg = config(ctx);
|
|
1158
|
+
|
|
1159
|
+
if (!hasTerminalUI(ctx)) {
|
|
1160
|
+
if (cfg.footer.mode === "off") {
|
|
1161
|
+
setStatus(ctx, undefined);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const fast = fastController.statusSegment(ctx, cfg);
|
|
1165
|
+
const usage = usageController.statusLine(ctx, cfg);
|
|
1166
|
+
setStatus(ctx, [fast, usage].filter(Boolean).join(" | ") || undefined);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
petController.updateActivity(ctx, cfg);
|
|
1171
|
+
const shouldRenderPet = petController.shouldRenderInFooter(cfg);
|
|
1172
|
+
|
|
1173
|
+
if (cfg.footer.mode === "replace" || shouldRenderPet) {
|
|
1174
|
+
setStatus(ctx, undefined);
|
|
1175
|
+
setStatusWidget(ctx, undefined);
|
|
1176
|
+
installFooter(ctx);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
clearFooter(ctx);
|
|
1181
|
+
setStatus(ctx, undefined);
|
|
1182
|
+
|
|
1183
|
+
if (cfg.footer.mode === "off") {
|
|
1184
|
+
setStatusWidget(ctx, undefined);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const fast = fastController.statusSegment(ctx, cfg);
|
|
1189
|
+
const usage = usageController.statusLine(ctx, cfg);
|
|
1190
|
+
setStatusWidget(ctx, [fast, usage].filter(Boolean).join(" | ") || undefined);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1194
|
+
invalidateContextUsage();
|
|
1195
|
+
invalidateSessionName();
|
|
1196
|
+
const nextConfig = refresh(ctx);
|
|
1197
|
+
fastController.initializeForSession(ctx, nextConfig, pi.getFlag(FLAG) === true);
|
|
1198
|
+
if (
|
|
1199
|
+
fastController.desiredActive !== nextConfig.desiredActive ||
|
|
1200
|
+
fastController.active !== nextConfig.active
|
|
1201
|
+
)
|
|
1202
|
+
persist(nextConfig);
|
|
1203
|
+
if (fastController.desiredActive && !fastController.active) {
|
|
1204
|
+
ctx.ui.notify(fastController.unsupportedRequestMessage(ctx, nextConfig), "warning");
|
|
1205
|
+
}
|
|
1206
|
+
if (hasTerminalUI(ctx)) petController.installResizeGuard(ctx);
|
|
1207
|
+
refreshFooterTotals(ctx);
|
|
1208
|
+
updateFooter(ctx);
|
|
1209
|
+
if (hasTerminalUI(ctx) && nextConfig.pets.enabled) void petController.refresh(ctx, nextConfig);
|
|
1210
|
+
usageController.start(ctx);
|
|
1211
|
+
if (fastController.active) ctx.ui.notify(fastController.stateText(ctx, nextConfig), "info");
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
1215
|
+
invalidateContextUsage();
|
|
1216
|
+
petController.agentStart(ctx);
|
|
1217
|
+
updateFooter(ctx);
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
pi.on("tool_execution_start", (event, ctx) => {
|
|
1221
|
+
petController.toolStart(ctx, event.toolCallId);
|
|
1222
|
+
updateFooter(ctx);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
pi.on("tool_execution_end", (event, ctx) => {
|
|
1226
|
+
petController.toolEnd(ctx, event.toolCallId, event.isError);
|
|
1227
|
+
if (!event.isError) updateFooter(ctx);
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
pi.on("agent_end", (_event, ctx) => {
|
|
1231
|
+
petController.agentEnd();
|
|
1232
|
+
updateFooter(ctx);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1236
|
+
invalidateContextUsage();
|
|
1237
|
+
if (event.message?.role === "assistant") {
|
|
1238
|
+
footerTotals.input += event.message.usage.input;
|
|
1239
|
+
footerTotals.output += event.message.usage.output;
|
|
1240
|
+
footerTotals.cacheRead += event.message.usage.cacheRead;
|
|
1241
|
+
footerTotals.cacheWrite += event.message.usage.cacheWrite;
|
|
1242
|
+
footerTotals.cost += event.message.usage.cost.total;
|
|
1243
|
+
} else refreshFooterTotals(ctx);
|
|
1244
|
+
updateFooter(ctx);
|
|
1245
|
+
void usageController.refresh(ctx);
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
pi.on("session_compact", (_event, ctx) => {
|
|
1249
|
+
invalidateContextUsage();
|
|
1250
|
+
refreshFooterTotals(ctx);
|
|
1251
|
+
petController.queueKittyCleanup();
|
|
1252
|
+
petController.resetRenderCache();
|
|
1253
|
+
updateFooter(ctx);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
1257
|
+
invalidateContextUsage();
|
|
1258
|
+
refreshFooterTotals(ctx);
|
|
1259
|
+
petController.queueKittyCleanup();
|
|
1260
|
+
petController.resetRenderCache();
|
|
1261
|
+
updateFooter(ctx);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
pi.on("model_select", (event, ctx) => {
|
|
1265
|
+
invalidateContextUsage();
|
|
1266
|
+
const cfg = config(ctx);
|
|
1267
|
+
const wasActive = fastController.active;
|
|
1268
|
+
fastController.applyDesiredState(ctx, cfg);
|
|
1269
|
+
if (fastController.active !== wasActive) {
|
|
1270
|
+
persist(cfg);
|
|
1271
|
+
ctx.ui.notify(
|
|
1272
|
+
fastController.active
|
|
1273
|
+
? fastController.stateText(ctx, cfg)
|
|
1274
|
+
: fastController.inactiveForModelMessage(ctx),
|
|
1275
|
+
fastController.active ? "info" : "warning",
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
updateFooter(ctx);
|
|
1279
|
+
void usageController.refresh(ctx, event.model.id, { force: true });
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
pi.on("session_shutdown", () => {
|
|
1283
|
+
invalidateContextUsage();
|
|
1284
|
+
invalidateSessionName();
|
|
1285
|
+
usageController.shutdown();
|
|
1286
|
+
petController.shutdown();
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
1290
|
+
return fastController.injectProviderPayload(event, ctx, config(ctx));
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
pi.on("message_start", invalidateContextUsage);
|
|
1294
|
+
pi.on("message_update", invalidateContextUsage);
|
|
1295
|
+
pi.on("message_end", invalidateContextUsage);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
export const _test = {
|
|
1299
|
+
CONFIG_BASENAME,
|
|
1300
|
+
DEFAULT_SUPPORTED_MODELS,
|
|
1301
|
+
DEFAULT_CONFIG,
|
|
1302
|
+
DEFAULT_IMAGE_CONFIG,
|
|
1303
|
+
DEFAULT_LIVE_CONFIG,
|
|
1304
|
+
DEFAULT_PET_CONFIG,
|
|
1305
|
+
SERVICE_TIER,
|
|
1306
|
+
configPaths,
|
|
1307
|
+
abbreviateHomePath,
|
|
1308
|
+
parseModelKey,
|
|
1309
|
+
normalizeModelKeys,
|
|
1310
|
+
parseModels,
|
|
1311
|
+
resolveConfig,
|
|
1312
|
+
readRawConfig,
|
|
1313
|
+
supportsFast,
|
|
1314
|
+
combineInlinePetFooter,
|
|
1315
|
+
PET_EMPTY_VALUE,
|
|
1316
|
+
readyPetPickerValues,
|
|
1317
|
+
petConfigPickerValue,
|
|
1318
|
+
petSlugFromPickerValue,
|
|
1319
|
+
petPickerDescription,
|
|
1320
|
+
formatPetSelectPrompt,
|
|
1321
|
+
parseUsageSnapshot,
|
|
1322
|
+
formatPercent,
|
|
1323
|
+
formatUsageSnapshot,
|
|
1324
|
+
readCodexAuth,
|
|
1325
|
+
textPanel,
|
|
1326
|
+
imageTest: _imageTest,
|
|
1327
|
+
petsTest: _petsTest,
|
|
1328
|
+
};
|