@diegopetrucci/pi-extensions 0.1.73 → 0.1.76
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 +2 -0
- package/README.md +9 -4
- package/extensions/agent-workflow-audit/index.ts +1398 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +853 -767
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +1 -1
- package/extensions/code-reviewer/index.ts +1598 -1350
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +55 -51
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/index.ts +312 -304
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/index.ts +1854 -1735
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +41 -41
- package/extensions/dynamic-context-pruning/README.md +6 -38
- package/extensions/dynamic-context-pruning/index.ts +2124 -1980
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +71 -21
- package/extensions/fast/index.ts +513 -313
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +88 -88
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/index.ts +1669 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/index.ts +994 -1012
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/index.ts +1731 -1625
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/index.ts +658 -608
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/index.ts +282 -264
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +284 -265
- package/extensions/triage-comments/README.md +2 -2
- package/extensions/triage-comments/index.ts +2294 -1882
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +6 -3
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
|
@@ -14,271 +14,276 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
|
14
14
|
export type UsageAvailability = "unsupported" | "unavailable";
|
|
15
15
|
|
|
16
16
|
interface WhamUsageWindow {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
limit_window_seconds?: number;
|
|
18
|
+
reset_at?: number;
|
|
19
|
+
used_percent?: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
interface WhamUsageResponse {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
rate_limit?: {
|
|
24
|
+
primary_window?: WhamUsageWindow;
|
|
25
|
+
secondary_window?: WhamUsageWindow;
|
|
26
|
+
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface UsageWindow {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
usedPercent?: number;
|
|
31
|
+
windowSeconds?: number;
|
|
32
|
+
resetAt?: number;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export interface UsageSnapshot {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
primary?: UsageWindow;
|
|
37
|
+
secondary?: UsageWindow;
|
|
38
|
+
fetchedAt: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface UsageSummaryWindowsConfig {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
primary: {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
label: string;
|
|
45
|
+
};
|
|
46
|
+
secondary: {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
label: string;
|
|
49
|
+
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function normalizeUsedPercent(value?: number): number | undefined {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
54
|
+
return Math.min(100, Math.max(0, value));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function normalizeWindowSeconds(value?: number): number | undefined {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return undefined;
|
|
59
|
+
return value;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function normalizeResetAt(value?: number): number | undefined {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
64
|
+
return value * 1000;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function parseUsageWindow(window?: WhamUsageWindow): UsageWindow | undefined {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
if (!window) return undefined;
|
|
69
|
+
const usedPercent = normalizeUsedPercent(window.used_percent);
|
|
70
|
+
const windowSeconds = normalizeWindowSeconds(window.limit_window_seconds);
|
|
71
|
+
const resetAt = normalizeResetAt(window.reset_at);
|
|
72
|
+
if (usedPercent === undefined && windowSeconds === undefined && resetAt === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return { usedPercent, windowSeconds, resetAt };
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function parseUsageSnapshot(data: WhamUsageResponse): Omit<UsageSnapshot, "fetchedAt"> {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
return {
|
|
79
|
+
primary: parseUsageWindow(data.rate_limit?.primary_window),
|
|
80
|
+
secondary: parseUsageWindow(data.rate_limit?.secondary_window),
|
|
81
|
+
};
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
84
|
-
|
|
85
|
+
return typeof value === "object" && value !== null;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function readStoredProviderCredential(providerId: string): unknown {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
try {
|
|
90
|
+
const data = JSON.parse(readFileSync(join(getAgentDir(), "auth.json"), "utf-8")) as unknown;
|
|
91
|
+
if (!isRecord(data)) return undefined;
|
|
92
|
+
return data[providerId];
|
|
93
|
+
} catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
async function getAccessToken(authSource: unknown): Promise<string | undefined> {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
99
|
+
if (isRecord(authSource) && typeof authSource.getProviderAuth === "function") {
|
|
100
|
+
const result = await (
|
|
101
|
+
authSource as {
|
|
102
|
+
getProviderAuth(providerId: string): Promise<unknown>;
|
|
103
|
+
}
|
|
104
|
+
).getProviderAuth(PROVIDER_ID);
|
|
105
|
+
if (isRecord(result) && isRecord(result.auth) && typeof result.auth.apiKey === "string") {
|
|
106
|
+
return result.auth.apiKey;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isRecord(authSource) && typeof authSource.getApiKey === "function") {
|
|
111
|
+
const token = await (
|
|
112
|
+
authSource as {
|
|
113
|
+
getApiKey(providerId: string, options: { includeFallback: boolean }): Promise<unknown>;
|
|
114
|
+
}
|
|
115
|
+
).getApiKey(PROVIDER_ID, { includeFallback: false });
|
|
116
|
+
return typeof token === "string" ? token : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const credential = readStoredProviderCredential(PROVIDER_ID);
|
|
120
|
+
if (!isRecord(credential) || credential.type !== "oauth") return undefined;
|
|
121
|
+
return typeof credential.access === "string" ? credential.access : undefined;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
function getOAuthAccountId(authSource: unknown): string | undefined {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
125
|
+
let credential: unknown;
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
isRecord(authSource) &&
|
|
129
|
+
typeof authSource.getApiKey === "function" &&
|
|
130
|
+
typeof authSource.get === "function"
|
|
131
|
+
) {
|
|
132
|
+
if (typeof authSource.reload === "function") authSource.reload();
|
|
133
|
+
credential = (authSource as { get(providerId: string): unknown }).get(PROVIDER_ID);
|
|
134
|
+
} else {
|
|
135
|
+
credential = readStoredProviderCredential(PROVIDER_ID);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!isRecord(credential) || credential.type !== "oauth") return undefined;
|
|
139
|
+
const accountId = credential.accountId;
|
|
140
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : undefined;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
function getAccountIdFromAccessToken(accessToken: string): string | undefined {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
try {
|
|
145
|
+
// Keep this parser aligned with Pi's openai-codex OAuth implementation:
|
|
146
|
+
// the account identity is the exact JWT claim used when credentials are
|
|
147
|
+
// created, not a separately loaded account option.
|
|
148
|
+
const parts = accessToken.split(".");
|
|
149
|
+
if (parts.length !== 3) return undefined;
|
|
150
|
+
const payload = JSON.parse(atob(parts[1] ?? "")) as unknown;
|
|
151
|
+
if (!isRecord(payload)) return undefined;
|
|
152
|
+
const auth = payload[JWT_CLAIM_PATH];
|
|
153
|
+
if (!isRecord(auth)) return undefined;
|
|
154
|
+
const accountId = auth.chatgpt_account_id;
|
|
155
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : undefined;
|
|
156
|
+
} catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
export function getOpenAICodexAccountId(authSource: unknown): string | undefined {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
try {
|
|
163
|
+
return getOAuthAccountId(authSource);
|
|
164
|
+
} catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
function formatUsagePercent(value?: number): string | undefined {
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
171
|
+
return `${Math.round(value)}%`;
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
// Primary/secondary describe positions, not fixed periods. Match each reported
|
|
170
175
|
// duration the same way Codex does, allowing small backend rounding differences.
|
|
171
176
|
const KNOWN_WINDOW_LABELS: ReadonlyArray<{ seconds: number; label: string }> = [
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
{ seconds: 5 * 60 * 60, label: "5h" },
|
|
178
|
+
{ seconds: 24 * 60 * 60, label: "1d" },
|
|
179
|
+
{ seconds: 7 * 24 * 60 * 60, label: "7d" },
|
|
180
|
+
{ seconds: 30 * 24 * 60 * 60, label: "30d" },
|
|
181
|
+
{ seconds: 365 * 24 * 60 * 60, label: "365d" },
|
|
177
182
|
];
|
|
178
183
|
|
|
179
184
|
function isApproximateWindow(actualSeconds: number, expectedSeconds: number): boolean {
|
|
180
|
-
|
|
185
|
+
return Math.abs(actualSeconds - expectedSeconds) <= expectedSeconds * 0.05;
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
function formatUsageWindowLabel(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
window: UsageWindow | undefined,
|
|
190
|
+
configuredLabel: string,
|
|
191
|
+
fallbackLabel: string,
|
|
187
192
|
): string {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
if (configuredLabel.toLowerCase() !== "auto") return configuredLabel;
|
|
194
|
+
const windowSeconds = window?.windowSeconds;
|
|
195
|
+
if (windowSeconds !== undefined) {
|
|
196
|
+
const knownWindow = KNOWN_WINDOW_LABELS.find(({ seconds }) =>
|
|
197
|
+
isApproximateWindow(windowSeconds, seconds),
|
|
198
|
+
);
|
|
199
|
+
if (knownWindow) return knownWindow.label;
|
|
200
|
+
}
|
|
201
|
+
return fallbackLabel;
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
export function isOpenAICodexProvider(provider?: string): boolean {
|
|
200
|
-
|
|
205
|
+
return provider === PROVIDER_ID;
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
export function isOpenAIProvider(provider?: string): boolean {
|
|
204
|
-
|
|
209
|
+
return provider === "openai";
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
export function formatUsageStatus(status?: UsageAvailability): string | undefined {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
if (status === "unsupported") return "usage unsupported";
|
|
214
|
+
if (status === "unavailable") return "usage unavailable";
|
|
215
|
+
return undefined;
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
export function formatUsageSummary(
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
snapshot: UsageSnapshot | undefined,
|
|
220
|
+
windows: UsageSummaryWindowsConfig,
|
|
216
221
|
): string | undefined {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
222
|
+
if (!snapshot) return undefined;
|
|
223
|
+
|
|
224
|
+
const primary = formatUsagePercent(snapshot.primary?.usedPercent);
|
|
225
|
+
const secondary = formatUsagePercent(snapshot.secondary?.usedPercent);
|
|
226
|
+
const parts: string[] = [];
|
|
227
|
+
|
|
228
|
+
if (windows.primary.enabled && primary) {
|
|
229
|
+
const label = formatUsageWindowLabel(snapshot.primary, windows.primary.label, "usage");
|
|
230
|
+
parts.push(`${label} ${primary}`);
|
|
231
|
+
}
|
|
232
|
+
if (windows.secondary.enabled && secondary) {
|
|
233
|
+
const label = formatUsageWindowLabel(
|
|
234
|
+
snapshot.secondary,
|
|
235
|
+
windows.secondary.label,
|
|
236
|
+
"secondary usage",
|
|
237
|
+
);
|
|
238
|
+
parts.push(`${label} ${secondary}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
export async function fetchOpenAICodexUsage(
|
|
240
|
-
|
|
241
|
-
|
|
245
|
+
authSource: unknown,
|
|
246
|
+
options?: { timeoutMs?: number; accountId?: string },
|
|
242
247
|
): Promise<UsageSnapshot | undefined> {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
248
|
+
const accessToken = await getAccessToken(authSource);
|
|
249
|
+
if (!accessToken) return undefined;
|
|
250
|
+
|
|
251
|
+
// Pi derives the Codex credential's accountId from this access-token claim.
|
|
252
|
+
// Resolve it before constructing the request so a stale account option can
|
|
253
|
+
// never become the header, and reject credentials that cannot be bound to an
|
|
254
|
+
// account at all.
|
|
255
|
+
const tokenAccountId = getAccountIdFromAccessToken(accessToken);
|
|
256
|
+
if (!tokenAccountId) return undefined;
|
|
257
|
+
|
|
258
|
+
// A lifecycle caller may provide the account identity observed at the
|
|
259
|
+
// refresh boundary. It is only an expected identity: the token claim remains
|
|
260
|
+
// authoritative, and any mismatch fails closed before fetch is called.
|
|
261
|
+
if (options && "accountId" in options && options.accountId !== tokenAccountId) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const controller = new AbortController();
|
|
266
|
+
const timeoutMs = options?.timeoutMs ?? 10_000;
|
|
267
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const headers: Record<string, string> = {
|
|
271
|
+
Authorization: `Bearer ${accessToken}`,
|
|
272
|
+
Accept: "application/json",
|
|
273
|
+
};
|
|
274
|
+
headers["ChatGPT-Account-Id"] = tokenAccountId;
|
|
275
|
+
|
|
276
|
+
const response = await fetch("https://chatgpt.com/backend-api/wham/usage", {
|
|
277
|
+
headers,
|
|
278
|
+
signal: controller.signal,
|
|
279
|
+
});
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
throw new Error(`Usage request failed: ${response.status}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const data = (await response.json()) as WhamUsageResponse;
|
|
285
|
+
return { ...parseUsageSnapshot(data), fetchedAt: Date.now() };
|
|
286
|
+
} finally {
|
|
287
|
+
clearTimeout(timeout);
|
|
288
|
+
}
|
|
284
289
|
}
|