pi-better-openai 0.1.21 → 0.1.22
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 +12 -2
- package/index.ts +9 -100
- package/package.json +2 -2
- package/src/config.ts +3 -0
- package/src/fast-controller.ts +4 -1
- package/src/footer-layout.ts +97 -0
- package/src/image.ts +5 -1
- package/src/pet-footer-controller.ts +20 -5
- package/src/pets.ts +7 -3
- package/src/usage-controller.ts +8 -3
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ A pi extension for OpenAI subscription workflows: fast mode, usage visibility, f
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
+
Requires Node.js 22.19.0 or newer.
|
|
8
|
+
|
|
7
9
|
Install from GitHub:
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -52,7 +54,15 @@ Project overrides global. Global values fill fields omitted by the project file.
|
|
|
52
54
|
Default supported models:
|
|
53
55
|
|
|
54
56
|
```json
|
|
55
|
-
[
|
|
57
|
+
[
|
|
58
|
+
"openai/gpt-5.4",
|
|
59
|
+
"openai/gpt-5.5",
|
|
60
|
+
"openai-codex/gpt-5.6-sol",
|
|
61
|
+
"openai-codex/gpt-5.6-terra",
|
|
62
|
+
"openai-codex/gpt-5.6-luna",
|
|
63
|
+
"openai-codex/gpt-5.4",
|
|
64
|
+
"openai-codex/gpt-5.5"
|
|
65
|
+
]
|
|
56
66
|
```
|
|
57
67
|
|
|
58
68
|
Example config:
|
|
@@ -146,7 +156,7 @@ Then reload Codex skills (`Cmd/Ctrl+K → Force Reload Skills`) and ask:
|
|
|
146
156
|
$hatch-pet create a new pet inspired by pi-better-openai
|
|
147
157
|
```
|
|
148
158
|
|
|
149
|
-
Custom pets should end up in `${CODEX_HOME:-~/.codex}/pets/<pet-name>/` with `pet.json` and `spritesheet.webp`. Refresh custom pets in Codex settings and toggle the overlay with `/pet`.
|
|
159
|
+
Custom pets should end up in `${CODEX_HOME:-~/.codex}/pets/<pet-name>/` with `pet.json` and `spritesheet.webp`. The spritesheet must be a 1536×1872 atlas arranged as 8 columns by 9 animation rows. Animated footer rendering also requires a terminal image protocol supported by pi. Refresh custom pets in Codex settings and toggle the overlay with `/pet`.
|
|
150
160
|
|
|
151
161
|
## Screenshots
|
|
152
162
|
|
package/index.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
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";
|
|
8
7
|
import {
|
|
9
8
|
getSettingsListTheme,
|
|
10
9
|
type ExtensionAPI,
|
|
@@ -33,7 +32,6 @@ import {
|
|
|
33
32
|
type SettingsOptionDescriptor,
|
|
34
33
|
configPaths,
|
|
35
34
|
type ResolvedConfig,
|
|
36
|
-
type PetPlacement,
|
|
37
35
|
type PetState,
|
|
38
36
|
isRecord,
|
|
39
37
|
parseModelKey,
|
|
@@ -65,6 +63,13 @@ import {
|
|
|
65
63
|
import { FastController, modelList, supportsFast } from "./src/fast-controller.ts";
|
|
66
64
|
import { UsageController } from "./src/usage-controller.ts";
|
|
67
65
|
import { PetFooterController } from "./src/pet-footer-controller.ts";
|
|
66
|
+
import {
|
|
67
|
+
abbreviateHomePath,
|
|
68
|
+
combineInlinePetFooter,
|
|
69
|
+
isInlinePetPlacement,
|
|
70
|
+
isTerminalImageLine,
|
|
71
|
+
petSizeCellsForPlacement,
|
|
72
|
+
} from "./src/footer-layout.ts";
|
|
68
73
|
|
|
69
74
|
const COMMAND = "fast";
|
|
70
75
|
const OPENAI_STATUS_COMMAND = "openai-usage";
|
|
@@ -84,28 +89,10 @@ type SettingsPickerItem = {
|
|
|
84
89
|
) => { render(width: number): string[]; invalidate(): void; handleInput?(data: string): void };
|
|
85
90
|
};
|
|
86
91
|
|
|
87
|
-
function abbreviateHomePath(
|
|
88
|
-
path: string,
|
|
89
|
-
home = process.env.HOME || process.env.USERPROFILE,
|
|
90
|
-
): string {
|
|
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;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function isInlinePetPlacement(placement: PetPlacement): boolean {
|
|
98
|
-
return placement === "inline-left" || placement === "inline-right" || placement === "badge";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
92
|
function hasTerminalUI(ctx: ExtensionContext): boolean {
|
|
102
93
|
return ctx.mode === "tui" || (ctx.mode === undefined && ctx.hasUI);
|
|
103
94
|
}
|
|
104
95
|
|
|
105
|
-
function petSizeCellsForPlacement(placement: PetPlacement, sizeCells: number): number {
|
|
106
|
-
return placement === "badge" ? Math.min(6, sizeCells) : sizeCells;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
96
|
function readyPetPickerValues(pets: CodexPetPackage[]): string[] | undefined {
|
|
110
97
|
const readyPets = pets.filter((pet) => pet.hasSpritesheet);
|
|
111
98
|
return readyPets.length > 0 ? readyPets.map((pet) => pet.slug) : undefined;
|
|
@@ -131,8 +118,8 @@ function petPickerDescription(cfg: ResolvedConfig, pets: CodexPetPackage[]): str
|
|
|
131
118
|
if (readyPets.length === 0)
|
|
132
119
|
return "No ready custom pets found. Use /pets help to create one, then return here.";
|
|
133
120
|
if (!cfg.pets.slug) {
|
|
134
|
-
const firstPet = readyPets[0]
|
|
135
|
-
const lastPet = readyPets[readyPets.length - 1]
|
|
121
|
+
const firstPet = readyPets[0]!;
|
|
122
|
+
const lastPet = readyPets[readyPets.length - 1]!;
|
|
136
123
|
return `No pet selected. Enter/Space/→ selects ${firstPet.name}; ← selects ${lastPet.name}.`;
|
|
137
124
|
}
|
|
138
125
|
const selectedPet = findPickerPet(cfg.pets.slug, readyPets);
|
|
@@ -169,84 +156,6 @@ function formatPetSelectPrompt(
|
|
|
169
156
|
return { message: lines.join("\n"), level: "info" };
|
|
170
157
|
}
|
|
171
158
|
|
|
172
|
-
function spaces(width: number): string {
|
|
173
|
-
return " ".repeat(Math.max(0, width));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function padTextToWidth(value: string, width: number): string {
|
|
177
|
-
return value + spaces(width - visibleWidth(value));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function isTerminalImageLine(line: string): boolean {
|
|
181
|
-
return line.includes("\x1b_G") || line.includes("\x1b]1337;File=");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function petLineCell(line: string, width: number): string {
|
|
185
|
-
if (!line) return spaces(width);
|
|
186
|
-
if (isTerminalImageLine(line)) return `\x1b[0m${line}`;
|
|
187
|
-
const clipped = truncateToWidth(line, width, "");
|
|
188
|
-
return clipped + spaces(width - visibleWidth(clipped));
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function stripLeadingCursorUp(line: string): string {
|
|
192
|
-
if (!line.startsWith("\x1b[")) return line;
|
|
193
|
-
const end = line.indexOf("A", 2);
|
|
194
|
-
if (end === -1) return line;
|
|
195
|
-
return /^\d+$/.test(line.slice(2, end)) ? line.slice(end + 1) : line;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function stripTrailingCursorDown(line: string): string {
|
|
199
|
-
if (!line.endsWith("B")) return line;
|
|
200
|
-
const start = line.lastIndexOf("\x1b[");
|
|
201
|
-
if (start === -1) return line;
|
|
202
|
-
return /^\d+$/.test(line.slice(start + 2, -1)) ? line.slice(0, start) : line;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function terminalImageInlineLeftSequence(line: string, totalRows: number): string {
|
|
206
|
-
const moveUp = totalRows > 1 ? `\x1b[${totalRows - 1}A` : "";
|
|
207
|
-
const moveDown = totalRows > 1 ? `\x1b[${totalRows - 1}B` : "";
|
|
208
|
-
const balancedLine = stripTrailingCursorDown(stripLeadingCursorUp(line));
|
|
209
|
-
return `\x1b[0m\r${moveUp}${balancedLine}${moveDown}`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function combineInlinePetFooter(
|
|
213
|
-
petLines: string[],
|
|
214
|
-
textLines: string[],
|
|
215
|
-
width: number,
|
|
216
|
-
placement: PetPlacement,
|
|
217
|
-
petWidth: number,
|
|
218
|
-
): string[] {
|
|
219
|
-
const gap = 2;
|
|
220
|
-
const textWidth = Math.max(1, width - petWidth - gap);
|
|
221
|
-
const totalRows = Math.max(petLines.length, textLines.length);
|
|
222
|
-
const petStart = 0;
|
|
223
|
-
const textStart = 0;
|
|
224
|
-
const hasTerminalImageLine = petLines.some(isTerminalImageLine);
|
|
225
|
-
const leftImageLine =
|
|
226
|
-
placement === "inline-left" ? petLines.find(isTerminalImageLine) : undefined;
|
|
227
|
-
const renderPetOnRight =
|
|
228
|
-
placement === "inline-right" || (placement !== "inline-left" && hasTerminalImageLine);
|
|
229
|
-
const lines: string[] = [];
|
|
230
|
-
|
|
231
|
-
for (let row = 0; row < totalRows; row++) {
|
|
232
|
-
const petLine = row >= petStart ? (petLines[row - petStart] ?? "") : "";
|
|
233
|
-
const textLine = row >= textStart ? (textLines[row - textStart] ?? "") : "";
|
|
234
|
-
const textPart = truncateToWidth(textLine, textWidth, "...");
|
|
235
|
-
const petPart = leftImageLine ? spaces(petWidth) : petLineCell(petLine, petWidth);
|
|
236
|
-
if (renderPetOnRight) {
|
|
237
|
-
lines.push(`${padTextToWidth(textPart, textWidth)}${spaces(gap)}${petPart}`);
|
|
238
|
-
} else {
|
|
239
|
-
lines.push(`${petPart}${spaces(gap)}${textPart}`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (leftImageLine && lines.length > 0) {
|
|
244
|
-
lines[lines.length - 1] += terminalImageInlineLeftSequence(leftImageLine, totalRows);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return lines;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
159
|
function textPanel(title: string, lines: string[], done: () => void) {
|
|
251
160
|
return {
|
|
252
161
|
render(width: number) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-openai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Personal pi extension that improves OpenAI with fast mode, usage stats, and footer polish.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fast",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@earendil-works/pi-coding-agent": "0.80.6",
|
|
45
45
|
"@earendil-works/pi-tui": "0.80.6",
|
|
46
|
-
"@types/node": "
|
|
46
|
+
"@types/node": "~22.19.0",
|
|
47
47
|
"oxfmt": "^0.47.0",
|
|
48
48
|
"oxlint": "^1.62.0",
|
|
49
49
|
"typescript": "^6.0.3",
|
package/src/config.ts
CHANGED
|
@@ -29,6 +29,9 @@ export const PET_STATES = [
|
|
|
29
29
|
export const DEFAULT_SUPPORTED_MODELS = [
|
|
30
30
|
"openai/gpt-5.4",
|
|
31
31
|
"openai/gpt-5.5",
|
|
32
|
+
"openai-codex/gpt-5.6-sol",
|
|
33
|
+
"openai-codex/gpt-5.6-terra",
|
|
34
|
+
"openai-codex/gpt-5.6-luna",
|
|
32
35
|
"openai-codex/gpt-5.4",
|
|
33
36
|
"openai-codex/gpt-5.5",
|
|
34
37
|
] as const;
|
package/src/fast-controller.ts
CHANGED
|
@@ -40,8 +40,11 @@ export class FastController {
|
|
|
40
40
|
private lastInjectedAt: number | undefined;
|
|
41
41
|
private lastInjectedModel: string | undefined;
|
|
42
42
|
private lastInjectedTier: string | undefined;
|
|
43
|
+
private readonly serviceTier: string;
|
|
43
44
|
|
|
44
|
-
constructor(
|
|
45
|
+
constructor(serviceTier: string) {
|
|
46
|
+
this.serviceTier = serviceTier;
|
|
47
|
+
}
|
|
45
48
|
|
|
46
49
|
applyDesiredState(ctx: ExtensionContext, cfg: ResolvedConfig): void {
|
|
47
50
|
this.active = this.desiredActive && supportsFast(ctx, cfg.supportedModels);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { sep } from "node:path";
|
|
2
|
+
import type { PetPlacement } from "./config.ts";
|
|
3
|
+
import { truncateToWidth, visibleWidth } from "./format.ts";
|
|
4
|
+
|
|
5
|
+
export function abbreviateHomePath(
|
|
6
|
+
path: string,
|
|
7
|
+
home = process.env.HOME || process.env.USERPROFILE,
|
|
8
|
+
): string {
|
|
9
|
+
if (!home) return path;
|
|
10
|
+
if (path === home) return "~";
|
|
11
|
+
const homePrefix = home.endsWith(sep) ? home : `${home}${sep}`;
|
|
12
|
+
return path.startsWith(homePrefix) ? `~/${path.slice(homePrefix.length)}` : path;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isInlinePetPlacement(placement: PetPlacement): boolean {
|
|
16
|
+
return placement === "inline-left" || placement === "inline-right" || placement === "badge";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function petSizeCellsForPlacement(placement: PetPlacement, sizeCells: number): number {
|
|
20
|
+
return placement === "badge" ? Math.min(6, sizeCells) : sizeCells;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function spaces(width: number): string {
|
|
24
|
+
return " ".repeat(Math.max(0, width));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function padTextToWidth(value: string, width: number): string {
|
|
28
|
+
return value + spaces(width - visibleWidth(value));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isTerminalImageLine(line: string): boolean {
|
|
32
|
+
return line.includes("\x1b_G") || line.includes("\x1b]1337;File=");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function petLineCell(line: string, width: number): string {
|
|
36
|
+
if (!line) return spaces(width);
|
|
37
|
+
if (isTerminalImageLine(line)) return `\x1b[0m${line}`;
|
|
38
|
+
const clipped = truncateToWidth(line, width, "");
|
|
39
|
+
return clipped + spaces(width - visibleWidth(clipped));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function stripLeadingCursorUp(line: string): string {
|
|
43
|
+
if (!line.startsWith("\x1b[")) return line;
|
|
44
|
+
const end = line.indexOf("A", 2);
|
|
45
|
+
if (end === -1) return line;
|
|
46
|
+
return /^\d+$/.test(line.slice(2, end)) ? line.slice(end + 1) : line;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function stripTrailingCursorDown(line: string): string {
|
|
50
|
+
if (!line.endsWith("B")) return line;
|
|
51
|
+
const start = line.lastIndexOf("\x1b[");
|
|
52
|
+
if (start === -1) return line;
|
|
53
|
+
return /^\d+$/.test(line.slice(start + 2, -1)) ? line.slice(0, start) : line;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function terminalImageInlineLeftSequence(line: string, totalRows: number): string {
|
|
57
|
+
const moveUp = totalRows > 1 ? `\x1b[${totalRows - 1}A` : "";
|
|
58
|
+
const moveDown = totalRows > 1 ? `\x1b[${totalRows - 1}B` : "";
|
|
59
|
+
const balancedLine = stripTrailingCursorDown(stripLeadingCursorUp(line));
|
|
60
|
+
return `\x1b[0m\r${moveUp}${balancedLine}${moveDown}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function combineInlinePetFooter(
|
|
64
|
+
petLines: string[],
|
|
65
|
+
textLines: string[],
|
|
66
|
+
width: number,
|
|
67
|
+
placement: PetPlacement,
|
|
68
|
+
petWidth: number,
|
|
69
|
+
): string[] {
|
|
70
|
+
const gap = 2;
|
|
71
|
+
const textWidth = Math.max(1, width - petWidth - gap);
|
|
72
|
+
const totalRows = Math.max(petLines.length, textLines.length);
|
|
73
|
+
const hasTerminalImageLine = petLines.some(isTerminalImageLine);
|
|
74
|
+
const leftImageLine =
|
|
75
|
+
placement === "inline-left" ? petLines.find(isTerminalImageLine) : undefined;
|
|
76
|
+
const renderPetOnRight =
|
|
77
|
+
placement === "inline-right" || (placement !== "inline-left" && hasTerminalImageLine);
|
|
78
|
+
const lines: string[] = [];
|
|
79
|
+
|
|
80
|
+
for (let row = 0; row < totalRows; row++) {
|
|
81
|
+
const petLine = petLines[row] ?? "";
|
|
82
|
+
const textLine = textLines[row] ?? "";
|
|
83
|
+
const textPart = truncateToWidth(textLine, textWidth, "...");
|
|
84
|
+
const petPart = leftImageLine ? spaces(petWidth) : petLineCell(petLine, petWidth);
|
|
85
|
+
if (renderPetOnRight) {
|
|
86
|
+
lines.push(`${padTextToWidth(textPart, textWidth)}${spaces(gap)}${petPart}`);
|
|
87
|
+
} else {
|
|
88
|
+
lines.push(`${petPart}${spaces(gap)}${textPart}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (leftImageLine && lines.length > 0) {
|
|
93
|
+
lines[lines.length - 1] += terminalImageInlineLeftSequence(leftImageLine, totalRows);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
package/src/image.ts
CHANGED
|
@@ -296,7 +296,11 @@ function buildRequest(
|
|
|
296
296
|
|
|
297
297
|
function dataUrlParts(value: string, fallbackMimeType: string): { data: string; mimeType: string } {
|
|
298
298
|
const match = value.match(/^data:([^;,]+);base64,(.*)$/s);
|
|
299
|
-
if (match)
|
|
299
|
+
if (match)
|
|
300
|
+
return {
|
|
301
|
+
mimeType: match[1] || fallbackMimeType,
|
|
302
|
+
data: (match[2] ?? "").trim(),
|
|
303
|
+
};
|
|
300
304
|
return { data: value.trim(), mimeType: fallbackMimeType };
|
|
301
305
|
}
|
|
302
306
|
|
|
@@ -117,12 +117,19 @@ export class PetFooterController {
|
|
|
117
117
|
private requestFooterRender: (() => void) | undefined;
|
|
118
118
|
private requestSettingsRender: (() => void) | undefined;
|
|
119
119
|
private shuttingDown = false;
|
|
120
|
+
private readonly getConfig: (ctx: ExtensionContext) => ResolvedConfig;
|
|
121
|
+
private readonly updateFooter: (ctx: ExtensionContext) => void;
|
|
122
|
+
private readonly getFooterInstalled: () => boolean;
|
|
120
123
|
|
|
121
124
|
constructor(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
) {
|
|
125
|
+
getConfig: (ctx: ExtensionContext) => ResolvedConfig,
|
|
126
|
+
updateFooter: (ctx: ExtensionContext) => void,
|
|
127
|
+
getFooterInstalled: () => boolean = () => false,
|
|
128
|
+
) {
|
|
129
|
+
this.getConfig = getConfig;
|
|
130
|
+
this.updateFooter = updateFooter;
|
|
131
|
+
this.getFooterInstalled = getFooterInstalled;
|
|
132
|
+
}
|
|
126
133
|
|
|
127
134
|
get loadedPet(): LoadedCodexPet | undefined {
|
|
128
135
|
return this.pet;
|
|
@@ -239,6 +246,14 @@ export class PetFooterController {
|
|
|
239
246
|
}
|
|
240
247
|
}
|
|
241
248
|
|
|
249
|
+
private recallRender(key: string): string[] | undefined {
|
|
250
|
+
const lines = this.petRenderCache.get(key);
|
|
251
|
+
if (!lines) return undefined;
|
|
252
|
+
this.petRenderCache.delete(key);
|
|
253
|
+
this.petRenderCache.set(key, lines);
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
|
|
242
257
|
isResizeFrozen(now = Date.now()): boolean {
|
|
243
258
|
return now < this.petResizeFreezeUntil;
|
|
244
259
|
}
|
|
@@ -568,7 +583,7 @@ export class PetFooterController {
|
|
|
568
583
|
options.petColumnWidth,
|
|
569
584
|
options.petRenderSizeCells,
|
|
570
585
|
);
|
|
571
|
-
const cachedPetLines = this.
|
|
586
|
+
const cachedPetLines = this.recallRender(cacheKey);
|
|
572
587
|
if (cachedPetLines) {
|
|
573
588
|
petLines.push(...cachedPetLines);
|
|
574
589
|
return petLines;
|
package/src/pets.ts
CHANGED
|
@@ -276,7 +276,8 @@ async function readCodexPetPackages(
|
|
|
276
276
|
const readNext = async (): Promise<void> => {
|
|
277
277
|
while (nextIndex < entries.length) {
|
|
278
278
|
const index = nextIndex++;
|
|
279
|
-
|
|
279
|
+
const entry = entries[index];
|
|
280
|
+
if (entry) pets[index] = await readCodexPetPackage(petsDir, entry, options);
|
|
280
281
|
}
|
|
281
282
|
};
|
|
282
283
|
await Promise.all(
|
|
@@ -622,7 +623,7 @@ export function nextAnimationFrameDelayMs(
|
|
|
622
623
|
if (cursor < duration) return Math.max(1, Math.ceil(duration - cursor));
|
|
623
624
|
cursor -= duration;
|
|
624
625
|
}
|
|
625
|
-
return Math.max(1, Math.ceil(frames[0]
|
|
626
|
+
return Math.max(1, Math.ceil(frames[0]!.durationMs));
|
|
626
627
|
}
|
|
627
628
|
|
|
628
629
|
function forEachCodexPetFrame(
|
|
@@ -691,8 +692,11 @@ function encodeKittyRawRgba(frame: PetFrame, imageId: number): string {
|
|
|
691
692
|
export class CodexPetKittyManager {
|
|
692
693
|
private previousFrameImageId: number | undefined;
|
|
693
694
|
private readonly pendingCleanupImageIds = new Set<number>();
|
|
695
|
+
readonly placementImageId: number;
|
|
694
696
|
|
|
695
|
-
constructor(
|
|
697
|
+
constructor(placementImageId: number) {
|
|
698
|
+
this.placementImageId = placementImageId;
|
|
699
|
+
}
|
|
696
700
|
|
|
697
701
|
queueCleanup(pet?: LoadedCodexPet): void {
|
|
698
702
|
for (const imageId of codexPetKittyImageIds(pet)) this.pendingCleanupImageIds.add(imageId);
|
package/src/usage-controller.ts
CHANGED
|
@@ -55,11 +55,16 @@ export class UsageController {
|
|
|
55
55
|
private sessionAbortSignal: AbortSignal | undefined;
|
|
56
56
|
private sessionAbortHandler: (() => void) | undefined;
|
|
57
57
|
private sessionGeneration = 0;
|
|
58
|
+
private readonly getConfig: (ctx: ExtensionContext) => ResolvedConfig;
|
|
59
|
+
private readonly updateFooter: (ctx: ExtensionContext) => void;
|
|
58
60
|
|
|
59
61
|
constructor(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
) {
|
|
62
|
+
getConfig: (ctx: ExtensionContext) => ResolvedConfig,
|
|
63
|
+
updateFooter: (ctx: ExtensionContext) => void,
|
|
64
|
+
) {
|
|
65
|
+
this.getConfig = getConfig;
|
|
66
|
+
this.updateFooter = updateFooter;
|
|
67
|
+
}
|
|
63
68
|
|
|
64
69
|
get snapshot(): UsageSnapshot | undefined {
|
|
65
70
|
return this.usageSnapshot;
|