pi-better-openai 0.1.2

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.
@@ -0,0 +1,623 @@
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 type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
8
+ import { CONFIG_BASENAME, STATUS_KEY } from "./identity.ts";
9
+ import { formatTokens, sanitizeStatusText, truncateToWidth, visibleWidth } from "./format.ts";
10
+ import {
11
+ DEFAULT_CONFIG,
12
+ DEFAULT_IMAGE_CONFIG,
13
+ DEFAULT_SUPPORTED_MODELS,
14
+ FOOTER_MODES,
15
+ IMAGE_OUTPUT_FORMATS,
16
+ IMAGE_SAVE_MODES,
17
+ configPaths,
18
+ type FooterMode,
19
+ type ResolvedConfig,
20
+ type SupportedModel,
21
+ isRecord,
22
+ parseModelKey,
23
+ normalizeModelKeys,
24
+ parseModels,
25
+ readRawConfig,
26
+ resolveConfig,
27
+ writeConfig
28
+ } from "./config.ts";
29
+ import {
30
+ AUTH_FILE,
31
+ type UsageSnapshot,
32
+ formatPercent,
33
+ formatResetCountdown,
34
+ formatUsageSnapshot,
35
+ parseUsageSnapshot,
36
+ readCodexAuth,
37
+ requestCodexUsage
38
+ } from "./usage.ts";
39
+ import { registerOpenAIImage, _imageTest } from "./image.ts";
40
+
41
+ const COMMAND = "fast";
42
+ const OPENAI_STATUS_COMMAND = "openai-usage";
43
+ const OPENAI_SETTINGS_COMMAND = "openai-settings";
44
+ const FLAG = "fast";
45
+ const SERVICE_TIER = "priority";
46
+ type SettingsPickerItem = {
47
+ id: string;
48
+ label: string;
49
+ description?: string;
50
+ currentValue: string;
51
+ values?: string[];
52
+ submenu?: (currentValue: string, done: (selectedValue?: string) => void) => { render(width: number): string[]; invalidate(): void; handleInput?(data: string): void };
53
+ };
54
+
55
+ function currentModelKey(ctx: ExtensionContext): string {
56
+ return ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "none";
57
+ }
58
+
59
+ function supportsFast(ctx: ExtensionContext, supportedModels: SupportedModel[]): boolean {
60
+ if (!ctx.model) return false;
61
+ return supportedModels.some((model) => model.provider === ctx.model.provider && model.id === ctx.model.id);
62
+ }
63
+
64
+ function modelList(supportedModels: SupportedModel[]): string {
65
+ return supportedModels.length > 0
66
+ ? supportedModels.map((model) => `${model.provider}/${model.id}`).join(", ")
67
+ : "none configured";
68
+ }
69
+
70
+ function stateText(ctx: ExtensionContext, desiredActive: boolean, active: boolean, supportedModels: SupportedModel[]): string {
71
+ const model = currentModelKey(ctx);
72
+ if (active) return `Fast mode is on for ${model}.`;
73
+ if (desiredActive) {
74
+ return `Fast mode is requested, but inactive for unsupported model ${model}. Supported models: ${modelList(supportedModels)}.`;
75
+ }
76
+ return `Fast mode is off. Current model: ${model}.`;
77
+ }
78
+
79
+ function isOpenAISubscriptionModel(ctx: ExtensionContext, cfg: ResolvedConfig): boolean {
80
+ if (!ctx.model || (ctx.model.provider !== "openai" && ctx.model.provider !== "openai-codex")) return false;
81
+ return !cfg.usage.showOnlyOnSubscriptionModels || ctx.modelRegistry.isUsingOAuth(ctx.model);
82
+ }
83
+
84
+ export default function betterOpenAI(pi: ExtensionAPI): void {
85
+ let desiredActive = false;
86
+ let active = false;
87
+ let cachedConfig: ResolvedConfig | undefined;
88
+ let usageSnapshot: UsageSnapshot | undefined;
89
+ let usageUpdatedAt: number | undefined;
90
+ let usageError: string | undefined;
91
+ let usageLastFetchAt: number | undefined;
92
+ let usageTimer: ReturnType<typeof setInterval> | undefined;
93
+ let footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
94
+ let usageRefreshInFlight = false;
95
+ let queuedUsageRefresh: { ctx: ExtensionContext; modelId?: string; notify?: boolean } | undefined;
96
+ let shuttingDown = false;
97
+ let usageAbortController: AbortController | undefined;
98
+ let footerInstalled = false;
99
+ let requestFooterRender: (() => void) | undefined;
100
+ let lastInjectedAt: number | undefined;
101
+ let lastInjectedModel: string | undefined;
102
+ let lastInjectedTier: string | undefined;
103
+
104
+ function refresh(ctx: ExtensionContext): ResolvedConfig {
105
+ cachedConfig = resolveConfig(ctx.cwd || process.cwd());
106
+ return cachedConfig;
107
+ }
108
+
109
+ function config(ctx: ExtensionContext): ResolvedConfig {
110
+ return cachedConfig ?? refresh(ctx);
111
+ }
112
+
113
+ function persist(nextConfig: ResolvedConfig): void {
114
+ cachedConfig = { ...nextConfig, active, desiredActive };
115
+ if (!nextConfig.persistState) return;
116
+ writeConfig(nextConfig.configPath, { ...readRawConfig(nextConfig.configPath), active, desiredActive });
117
+ }
118
+
119
+ function applyDesiredFastState(ctx: ExtensionContext, cfg = config(ctx)): void {
120
+ active = desiredActive && supportsFast(ctx, cfg.supportedModels);
121
+ }
122
+
123
+ function setActive(ctx: ExtensionContext, next: boolean): void {
124
+ const nextConfig = refresh(ctx);
125
+ desiredActive = next;
126
+ applyDesiredFastState(ctx, nextConfig);
127
+ persist(nextConfig);
128
+ updateFooter(ctx);
129
+ if (next && !active) {
130
+ ctx.ui.notify(`Fast mode requested, but ${currentModelKey(ctx)} is unsupported. It will activate automatically when you switch to a supported model: ${modelList(nextConfig.supportedModels)}.`, "warning");
131
+ return;
132
+ }
133
+ ctx.ui.notify(stateText(ctx, desiredActive, active, nextConfig.supportedModels), "info");
134
+ }
135
+
136
+ async function refreshUsage(ctx: ExtensionContext, modelId = ctx.model?.id, options?: { notify?: boolean }): Promise<void> {
137
+ if (shuttingDown || !ctx.hasUI) return;
138
+ if (usageRefreshInFlight) {
139
+ queuedUsageRefresh = { ctx, modelId, notify: queuedUsageRefresh?.notify || options?.notify };
140
+ return;
141
+ }
142
+ usageRefreshInFlight = true;
143
+ const cfg = config(ctx);
144
+ try {
145
+ if (!cfg.usage.enabled) {
146
+ usageSnapshot = undefined;
147
+ usageError = "Usage display is disabled.";
148
+ if (!shuttingDown) updateFooter(ctx);
149
+ return;
150
+ }
151
+ if (!isOpenAISubscriptionModel(ctx, cfg)) {
152
+ if (!shuttingDown) updateFooter(ctx);
153
+ return;
154
+ }
155
+ usageAbortController = new AbortController();
156
+ const timeoutSignal = AbortSignal.timeout(10_000);
157
+ const signal = ctx.signal
158
+ ? AbortSignal.any([ctx.signal, timeoutSignal, usageAbortController.signal])
159
+ : AbortSignal.any([timeoutSignal, usageAbortController.signal]);
160
+ const data = await requestCodexUsage(signal);
161
+ usageLastFetchAt = Date.now();
162
+ usageSnapshot = data ? parseUsageSnapshot(data, modelId) : undefined;
163
+ usageUpdatedAt = usageSnapshot ? Date.now() : undefined;
164
+ usageError = data ? undefined : `Missing openai-codex OAuth credentials in ${AUTH_FILE}.`;
165
+ if (!shuttingDown) updateFooter(ctx);
166
+ if (!shuttingDown && options?.notify) ctx.ui.notify(formatUsageStatus(ctx), usageSnapshot ? "info" : "warning");
167
+ } catch (error) {
168
+ if (shuttingDown) return;
169
+ usageError = error instanceof Error ? error.message : String(error);
170
+ updateFooter(ctx);
171
+ if (options?.notify) ctx.ui.notify(formatUsageStatus(ctx), "warning");
172
+ } finally {
173
+ usageAbortController = undefined;
174
+ usageRefreshInFlight = false;
175
+ if (!shuttingDown && queuedUsageRefresh) {
176
+ const next = queuedUsageRefresh;
177
+ queuedUsageRefresh = undefined;
178
+ void refreshUsage(next.ctx, next.modelId, { notify: next.notify });
179
+ }
180
+ }
181
+ }
182
+
183
+ function startUsageRefresh(ctx: ExtensionContext): void {
184
+ if (usageTimer) clearInterval(usageTimer);
185
+ const cfg = config(ctx);
186
+ if (!cfg.usage.enabled) return;
187
+ void refreshUsage(ctx);
188
+ usageTimer = setInterval(() => void refreshUsage(ctx), cfg.usage.refreshIntervalMs);
189
+ usageTimer.unref?.();
190
+ }
191
+
192
+ function refreshFooterTotals(ctx: ExtensionContext): void {
193
+ footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
194
+ for (const entry of ctx.sessionManager.getEntries()) {
195
+ if (entry.type !== "message" || entry.message.role !== "assistant") continue;
196
+ footerTotals.input += entry.message.usage.input;
197
+ footerTotals.output += entry.message.usage.output;
198
+ footerTotals.cacheRead += entry.message.usage.cacheRead;
199
+ footerTotals.cacheWrite += entry.message.usage.cacheWrite;
200
+ footerTotals.cost += entry.message.usage.cost.total;
201
+ }
202
+ }
203
+
204
+ function formatUsageDebug(ctx: ExtensionContext): string {
205
+ const cfg = config(ctx);
206
+ const auth = readCodexAuth();
207
+ return [
208
+ `Usage enabled: ${cfg.usage.enabled}`,
209
+ `Current model: ${currentModelKey(ctx)}`,
210
+ `Current model eligible: ${isOpenAISubscriptionModel(ctx, cfg)}`,
211
+ `Requires subscription model: ${cfg.usage.showOnlyOnSubscriptionModels}`,
212
+ `Auth: ${auth ? "found" : "missing"}`,
213
+ `Account ID: ${auth?.accountId ?? "none"}`,
214
+ `Last fetch: ${usageLastFetchAt ? new Date(usageLastFetchAt).toLocaleTimeString() : "never"}`,
215
+ `Last successful update: ${usageUpdatedAt ? new Date(usageUpdatedAt).toLocaleTimeString() : "never"}`,
216
+ `Last error: ${usageError ?? "none"}`,
217
+ `Refresh interval: ${cfg.usage.refreshIntervalMs}ms`,
218
+ `Endpoint: https://chatgpt.com/backend-api/wham/usage`
219
+ ].join("\n");
220
+ }
221
+
222
+ function formatUsageStatus(ctx: ExtensionContext): string {
223
+ const cfg = config(ctx);
224
+ if (!cfg.usage.enabled) return "Usage display is disabled.";
225
+ if (!isOpenAISubscriptionModel(ctx, cfg)) return "Usage hidden: current model is not an OpenAI subscription model.";
226
+ if (!usageSnapshot) return `Usage unavailable${usageError ? `: ${usageError}` : "."}`;
227
+ const stale = usageUpdatedAt && Date.now() - usageUpdatedAt > cfg.usage.refreshIntervalMs * 2
228
+ ? ` | stale ${formatResetCountdown((Date.now() - usageUpdatedAt) / 1000)}`
229
+ : "";
230
+ return `${formatUsageSnapshot(usageSnapshot, cfg.usage)}${stale}`;
231
+ }
232
+
233
+ pi.registerFlag(FLAG, {
234
+ description: "Start with OpenAI fast mode enabled (service_tier=priority)",
235
+ type: "boolean",
236
+ default: false
237
+ });
238
+
239
+ function formatDebugStatus(ctx: ExtensionContext): string {
240
+ const cfg = config(ctx);
241
+ return [
242
+ `Fast desired: ${desiredActive}`,
243
+ `Fast active: ${active}`,
244
+ `Current model: ${currentModelKey(ctx)}`,
245
+ `Supported model: ${supportsFast(ctx, cfg.supportedModels)}`,
246
+ `Configured service_tier: ${SERVICE_TIER}`,
247
+ `Last injected: ${lastInjectedAt ? `${new Date(lastInjectedAt).toLocaleTimeString()} (${lastInjectedModel}, ${lastInjectedTier})` : "never"}`,
248
+ `Footer mode: ${cfg.footer.mode}`,
249
+ `Usage enabled: ${cfg.usage.enabled}`,
250
+ `Image enabled: ${cfg.image.enabled}`,
251
+ `Image default save: ${cfg.image.defaultSave}`,
252
+ `Config: ${cfg.configPath}`
253
+ ].join("\n");
254
+ }
255
+
256
+ function formatOpenAIStatus(ctx: ExtensionContext): string {
257
+ refresh(ctx);
258
+ return formatUsageStatus(ctx);
259
+ }
260
+
261
+ pi.registerCommand(COMMAND, {
262
+ description: "Toggle OpenAI fast mode",
263
+ handler: async (args, ctx) => {
264
+ const arg = args.trim().toLowerCase();
265
+ if (!arg) return setActive(ctx, !desiredActive);
266
+ ctx.ui.notify("Usage: /fast", "error");
267
+ }
268
+ });
269
+
270
+ pi.registerCommand(OPENAI_STATUS_COMMAND, {
271
+ description: "Show OpenAI subscription usage status",
272
+ handler: async (_args, ctx) => {
273
+ ctx.ui.notify(formatOpenAIStatus(ctx), "info");
274
+ }
275
+ });
276
+
277
+ function textPanel(title: string, lines: string[], done: () => void) {
278
+ return {
279
+ render(width: number) {
280
+ const clipped = lines.map((line) => truncateToWidth(line, width, "..."));
281
+ return [title, "", ...clipped, "", "Esc/q to go back"];
282
+ },
283
+ invalidate() {},
284
+ handleInput(data: string) {
285
+ if (data.includes("\x1b") || data === "escape" || data === "q" || data === "\x03") done();
286
+ }
287
+ };
288
+ }
289
+
290
+ function buildSettingsItems(ctx: ExtensionContext, cfg: ResolvedConfig): SettingsPickerItem[] {
291
+ return [
292
+ { id: "fast.enabled", label: "Fast mode", currentValue: String(desiredActive), values: ["true", "false"], description: `Request OpenAI fast mode. Activates for supported models: ${modelList(cfg.supportedModels)}.` },
293
+ { id: "persistState", label: "Persist fast state", currentValue: String(cfg.persistState), values: ["true", "false"], description: "Remember fast-mode state across sessions." },
294
+ { id: "footer.mode", label: "Footer mode", currentValue: cfg.footer.mode, values: [...FOOTER_MODES], description: "replace = custom footer, status = pi footer plus status line, off = no Better OpenAI footer/status." },
295
+ { id: "usage.enabled", label: "Usage display", currentValue: String(cfg.usage.enabled), values: ["true", "false"], description: "Fetch and display OpenAI subscription usage windows." },
296
+ { id: "usage.refreshIntervalMs", label: "Usage refresh", currentValue: String(cfg.usage.refreshIntervalMs), values: ["15000", "30000", "60000", "120000", "300000", "600000"], description: "Usage refresh interval in milliseconds." },
297
+ { id: "usage.showOnlyOnSubscriptionModels", label: "Usage only on OAuth", currentValue: String(cfg.usage.showOnlyOnSubscriptionModels), values: ["true", "false"], description: "Only show usage when the current OpenAI model uses subscription/OAuth auth." },
298
+ { id: "usage.showResetTimes", label: "Usage reset times", currentValue: String(cfg.usage.showResetTimes), values: ["true", "false"], description: "Include compact reset countdowns and local reset times." },
299
+ { id: "image.enabled", label: "Image tool", currentValue: String(cfg.image.enabled), values: ["true", "false"], description: "Allow the openai_image tool to make image requests." },
300
+ { id: "image.defaultModel", label: "Image model", currentValue: cfg.image.defaultModel, values: ["gpt-5.5", "gpt-5.4", "gpt-5.2", "gpt-5"], description: "Mainline model used for image generation when current model is not openai-codex." },
301
+ { id: "image.defaultSave", label: "Image save", currentValue: cfg.image.defaultSave, values: [...IMAGE_SAVE_MODES], description: "Where generated images are saved by default." },
302
+ { id: "image.outputFormat", label: "Image format", currentValue: cfg.image.outputFormat, values: [...IMAGE_OUTPUT_FORMATS], description: "Generated image file format." },
303
+ { id: "image.timeoutMs", label: "Image timeout", currentValue: String(cfg.image.timeoutMs), values: ["30000", "60000", "120000", "180000", "300000"], description: "Image request timeout in milliseconds." },
304
+ { id: "debug", label: "Debug info", currentValue: "open", description: "Show Better OpenAI diagnostics.", submenu: (_value, done) => textPanel("Debug info", formatDebugStatus(ctx).split("\n"), () => done()) },
305
+ { id: "config.path", label: "Config path", currentValue: cfg.configPath, description: `Project: ${cfg.projectConfigPath}\nGlobal: ${cfg.globalConfigPath}` },
306
+ { id: "config.print", label: "Print config", currentValue: "open", description: "Show the selected raw config JSON.", submenu: (_value, done) => textPanel("Config", JSON.stringify(readRawConfig(cfg.configPath), null, 2).split("\n"), () => done()) }
307
+ ];
308
+ }
309
+
310
+ function writeSetting(ctx: ExtensionContext, id: string, rawValue: string): void {
311
+ const cfg = refresh(ctx);
312
+ const current = readRawConfig(cfg.configPath);
313
+ const bool = rawValue === "true";
314
+ const num = Number(rawValue);
315
+ if (id === "fast.enabled") {
316
+ desiredActive = bool;
317
+ applyDesiredFastState(ctx, cfg);
318
+ if (cfg.persistState) {
319
+ current.active = active;
320
+ current.desiredActive = desiredActive;
321
+ }
322
+ } else if (id === "persistState") current.persistState = bool;
323
+ else if (id.startsWith("usage.")) {
324
+ const usage = isRecord(current.usage) ? current.usage : {};
325
+ const key = id.slice("usage.".length);
326
+ usage[key] = key === "refreshIntervalMs" ? num : bool;
327
+ current.usage = usage;
328
+ } else if (id === "footer.mode") {
329
+ const footer = isRecord(current.footer) ? current.footer : {};
330
+ footer.mode = rawValue;
331
+ current.footer = footer;
332
+ } else if (id.startsWith("image.")) {
333
+ const image = isRecord(current.image) ? current.image : {};
334
+ const key = id.slice("image.".length);
335
+ image[key] = key === "timeoutMs" ? num : rawValue === "true" ? true : rawValue === "false" ? false : rawValue;
336
+ current.image = image;
337
+ }
338
+ writeConfig(cfg.configPath, current);
339
+ const next = refresh(ctx);
340
+ if (id.startsWith("usage.")) {
341
+ if (usageTimer) clearInterval(usageTimer);
342
+ usageTimer = undefined;
343
+ if (next.usage.enabled) startUsageRefresh(ctx);
344
+ else {
345
+ usageSnapshot = undefined;
346
+ usageError = "Usage display is disabled.";
347
+ }
348
+ }
349
+ updateFooter(ctx);
350
+ }
351
+
352
+ async function showSettingsPicker(ctx: ExtensionContext): Promise<void> {
353
+ const [{ getSettingsListTheme }, { Container, SettingsList }] = await Promise.all([
354
+ import("@mariozechner/pi-coding-agent"),
355
+ import("@mariozechner/pi-tui")
356
+ ]);
357
+ await ctx.ui.custom((tui, theme, _kb, done) => {
358
+ const container = new Container();
359
+ container.addChild(new (class {
360
+ render(_width: number) {
361
+ const cfg = config(ctx);
362
+ return [theme.fg("accent", theme.bold("Better OpenAI Settings")), theme.fg("dim", cfg.configPath), ""];
363
+ }
364
+ invalidate() {}
365
+ })());
366
+ const settingsList = new SettingsList(
367
+ buildSettingsItems(ctx, refresh(ctx)),
368
+ 13,
369
+ getSettingsListTheme(),
370
+ (id, newValue) => {
371
+ writeSetting(ctx, id, newValue);
372
+ settingsList.updateValue(id, buildSettingsItems(ctx, config(ctx)).find((item) => item.id === id)?.currentValue ?? newValue);
373
+ tui.requestRender();
374
+ },
375
+ () => done(undefined),
376
+ { enableSearch: true }
377
+ );
378
+ container.addChild(settingsList);
379
+ return {
380
+ render(width: number) { return container.render(width); },
381
+ invalidate() { container.invalidate(); },
382
+ handleInput(data: string) {
383
+ settingsList.handleInput(data);
384
+ tui.requestRender();
385
+ }
386
+ };
387
+ });
388
+ }
389
+
390
+ pi.registerCommand(OPENAI_SETTINGS_COMMAND, {
391
+ description: "Open Better OpenAI settings picker",
392
+ handler: async (_args, ctx) => {
393
+ await showSettingsPicker(ctx);
394
+ }
395
+ });
396
+
397
+
398
+ registerOpenAIImage(pi, config);
399
+
400
+ function installFooter(ctx: ExtensionContext): void {
401
+ if (footerInstalled) {
402
+ requestFooterRender?.();
403
+ return;
404
+ }
405
+ footerInstalled = true;
406
+ ctx.ui.setFooter((tui, theme, footerData) => {
407
+ requestFooterRender = () => tui.requestRender();
408
+ const unsubscribe = footerData.onBranchChange?.(() => tui.requestRender());
409
+ return {
410
+ dispose: () => {
411
+ unsubscribe?.();
412
+ footerInstalled = false;
413
+ requestFooterRender = undefined;
414
+ },
415
+ invalidate() {},
416
+ render(width: number): string[] {
417
+ const totalInput = footerTotals.input;
418
+ const totalOutput = footerTotals.output;
419
+ const totalCacheRead = footerTotals.cacheRead;
420
+ const totalCacheWrite = footerTotals.cacheWrite;
421
+ const totalCost = footerTotals.cost;
422
+
423
+ let pwd = ctx.sessionManager.getCwd();
424
+ const home = process.env.HOME || process.env.USERPROFILE;
425
+ if (home && pwd.startsWith(home)) pwd = `~${pwd.slice(home.length)}`;
426
+
427
+ const branch = footerData.getGitBranch?.();
428
+ if (branch) pwd = `${pwd} (${branch})`;
429
+
430
+ const sessionName = ctx.sessionManager.getSessionName();
431
+ if (sessionName) pwd = `${pwd} • ${sessionName}`;
432
+
433
+ const parts: string[] = [];
434
+ if (totalInput) parts.push(`↑${formatTokens(totalInput)}`);
435
+ if (totalOutput) parts.push(`↓${formatTokens(totalOutput)}`);
436
+ if (totalCacheRead) parts.push(`R${formatTokens(totalCacheRead)}`);
437
+ if (totalCacheWrite) parts.push(`W${formatTokens(totalCacheWrite)}`);
438
+
439
+ const usingSubscription = ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
440
+ if (totalCost || usingSubscription) parts.push(`$${totalCost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`);
441
+
442
+ const contextUsage = ctx.getContextUsage();
443
+ const contextWindow = contextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
444
+ const contextPercentValue = contextUsage?.percent ?? 0;
445
+ const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
446
+ const contextDisplay = contextPercent === "?" ? `?/${formatTokens(contextWindow)} (auto)` : `${contextPercent}%/${formatTokens(contextWindow)} (auto)`;
447
+ const contextText = contextPercentValue > 90
448
+ ? theme.fg("error", contextDisplay)
449
+ : contextPercentValue > 70
450
+ ? theme.fg("warning", contextDisplay)
451
+ : contextDisplay;
452
+ parts.push(contextText);
453
+
454
+ let usageLine: string | undefined;
455
+ const cfg = config(ctx);
456
+ if (usageSnapshot && cfg.usage.enabled && isOpenAISubscriptionModel(ctx, cfg)) {
457
+ usageLine = theme.fg("dim", formatUsageSnapshot(usageSnapshot, cfg.usage));
458
+ }
459
+
460
+ let statsLeft = parts.join(" ");
461
+ let statsLeftWidth = visibleWidth(statsLeft);
462
+ if (statsLeftWidth > width) {
463
+ statsLeft = truncateToWidth(statsLeft, width, "...");
464
+ statsLeftWidth = visibleWidth(statsLeft);
465
+ }
466
+
467
+ const modelName = ctx.model?.id || "no-model";
468
+ const thinkingLevel = pi.getThinkingLevel();
469
+ const fastSuffix = active && supportsFast(ctx, config(ctx).supportedModels) ? " fast" : "";
470
+ let rightWithoutProvider = modelName;
471
+ if (ctx.model?.reasoning) {
472
+ rightWithoutProvider = thinkingLevel === "off"
473
+ ? `${modelName}${fastSuffix} • thinking off`
474
+ : `${modelName}${fastSuffix} • ${thinkingLevel}`;
475
+ } else if (fastSuffix) {
476
+ rightWithoutProvider = `${modelName}${fastSuffix}`;
477
+ }
478
+
479
+ let rightSide = rightWithoutProvider;
480
+ if ((footerData.getAvailableProviderCount?.() ?? 0) > 1 && ctx.model) {
481
+ const withProvider = `(${ctx.model.provider}) ${rightWithoutProvider}`;
482
+ if (statsLeftWidth + 2 + visibleWidth(withProvider) <= width) rightSide = withProvider;
483
+ }
484
+
485
+ const rightWidth = visibleWidth(rightSide);
486
+ const totalNeeded = statsLeftWidth + 2 + rightWidth;
487
+ let statsLine: string;
488
+ if (totalNeeded <= width) {
489
+ statsLine = statsLeft + " ".repeat(width - statsLeftWidth - rightWidth) + rightSide;
490
+ } else {
491
+ const availableForRight = width - statsLeftWidth - 2;
492
+ if (availableForRight > 0) {
493
+ const truncatedRight = truncateToWidth(rightSide, availableForRight, "");
494
+ statsLine = statsLeft + " ".repeat(Math.max(0, width - statsLeftWidth - visibleWidth(truncatedRight))) + truncatedRight;
495
+ } else {
496
+ statsLine = statsLeft;
497
+ }
498
+ }
499
+
500
+ const lines = [
501
+ truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "...")),
502
+ theme.fg("dim", statsLeft) + theme.fg("dim", statsLine.slice(statsLeft.length))
503
+ ];
504
+
505
+ if (usageLine) {
506
+ lines.push(truncateToWidth(usageLine, width, theme.fg("dim", "...")));
507
+ }
508
+
509
+ const extensionStatuses = footerData.getExtensionStatuses?.();
510
+ if (extensionStatuses?.size) {
511
+ const statusLine = Array.from(extensionStatuses.entries())
512
+ .sort(([a], [b]) => String(a).localeCompare(String(b)))
513
+ .map(([, text]) => sanitizeStatusText(String(text)))
514
+ .join(" ");
515
+ lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
516
+ }
517
+
518
+ return lines;
519
+ }
520
+ };
521
+ });
522
+ }
523
+
524
+ function updateFooter(ctx: ExtensionContext): void {
525
+ const cfg = config(ctx);
526
+ if (cfg.footer.mode === "replace") {
527
+ ctx.ui.setStatus(STATUS_KEY, undefined);
528
+ installFooter(ctx);
529
+ return;
530
+ }
531
+ footerInstalled = false;
532
+ requestFooterRender = undefined;
533
+ ctx.ui.setFooter(undefined);
534
+ if (cfg.footer.mode === "off") {
535
+ ctx.ui.setStatus(STATUS_KEY, undefined);
536
+ return;
537
+ }
538
+ const fast = active && supportsFast(ctx, cfg.supportedModels) ? `${ctx.model?.id ?? "model"} fast` : undefined;
539
+ const usage = usageSnapshot && cfg.usage.enabled && isOpenAISubscriptionModel(ctx, cfg) ? formatUsageSnapshot(usageSnapshot, cfg.usage) : undefined;
540
+ ctx.ui.setStatus(STATUS_KEY, [fast, usage].filter(Boolean).join(" | ") || undefined);
541
+ }
542
+
543
+ pi.on("session_start", (_event, ctx) => {
544
+ const nextConfig = refresh(ctx);
545
+ desiredActive = nextConfig.persistState ? nextConfig.desiredActive : false;
546
+ if (pi.getFlag(FLAG) === true) desiredActive = true;
547
+ applyDesiredFastState(ctx, nextConfig);
548
+ if (desiredActive !== nextConfig.desiredActive || active !== nextConfig.active) persist(nextConfig);
549
+ if (desiredActive && !active) {
550
+ ctx.ui.notify(`Fast mode requested, but ${currentModelKey(ctx)} is unsupported. It will activate automatically when you switch to a supported model: ${modelList(nextConfig.supportedModels)}.`, "warning");
551
+ }
552
+ refreshFooterTotals(ctx);
553
+ updateFooter(ctx);
554
+ startUsageRefresh(ctx);
555
+ if (active) ctx.ui.notify(stateText(ctx, desiredActive, active, nextConfig.supportedModels), "info");
556
+ });
557
+
558
+ pi.on("turn_end", (_event, ctx) => {
559
+ refreshFooterTotals(ctx);
560
+ updateFooter(ctx);
561
+ void refreshUsage(ctx);
562
+ });
563
+
564
+ pi.on("session_compact", (_event, ctx) => {
565
+ refreshFooterTotals(ctx);
566
+ updateFooter(ctx);
567
+ });
568
+
569
+ pi.on("session_tree", (_event, ctx) => {
570
+ refreshFooterTotals(ctx);
571
+ updateFooter(ctx);
572
+ });
573
+
574
+ pi.on("model_select", (event, ctx) => {
575
+ const cfg = config(ctx);
576
+ const wasActive = active;
577
+ applyDesiredFastState(ctx, cfg);
578
+ if (active !== wasActive) {
579
+ persist(cfg);
580
+ ctx.ui.notify(active ? stateText(ctx, desiredActive, active, cfg.supportedModels) : `Fast mode inactive for unsupported model ${currentModelKey(ctx)}.`, active ? "info" : "warning");
581
+ }
582
+ updateFooter(ctx);
583
+ void refreshUsage(ctx, event.model.id);
584
+ });
585
+
586
+ pi.on("session_shutdown", () => {
587
+ shuttingDown = true;
588
+ queuedUsageRefresh = undefined;
589
+ usageAbortController?.abort();
590
+ usageAbortController = undefined;
591
+ if (usageTimer) clearInterval(usageTimer);
592
+ usageTimer = undefined;
593
+ });
594
+
595
+ pi.on("before_provider_request", (event, ctx) => {
596
+ const nextConfig = config(ctx);
597
+ if (!active || !supportsFast(ctx, nextConfig.supportedModels) || !isRecord(event.payload)) return;
598
+ lastInjectedAt = Date.now();
599
+ lastInjectedModel = currentModelKey(ctx);
600
+ lastInjectedTier = SERVICE_TIER;
601
+ return { ...event.payload, service_tier: SERVICE_TIER };
602
+ });
603
+ }
604
+
605
+ export const _test = {
606
+ CONFIG_BASENAME,
607
+ DEFAULT_SUPPORTED_MODELS,
608
+ DEFAULT_CONFIG,
609
+ DEFAULT_IMAGE_CONFIG,
610
+ SERVICE_TIER,
611
+ configPaths,
612
+ parseModelKey,
613
+ normalizeModelKeys,
614
+ parseModels,
615
+ resolveConfig,
616
+ readRawConfig,
617
+ supportsFast,
618
+ parseUsageSnapshot,
619
+ formatPercent,
620
+ formatUsageSnapshot,
621
+ readCodexAuth,
622
+ imageTest: _imageTest
623
+ };