opencode-usage-plugin 0.0.2-dev3 → 0.0.2-dev5

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/dist/index.js CHANGED
@@ -1,671 +1,991 @@
1
- // src/index.ts
2
- import { tool } from "@opencode-ai/plugin";
3
-
4
- // src/providers/common/logger.ts
5
- var maskSecret = (secret) => {
6
- if (!secret || typeof secret !== "string" || secret.length <= 8) {
7
- return "***";
8
- }
9
- return `${secret.slice(0, 4)}...${secret.slice(-4)}`;
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
5
  };
11
- var noOpLogger = {
12
- debug: async () => {
13
- },
14
- info: async () => {
15
- },
16
- warn: async () => {
17
- },
18
- error: async () => {
19
- }
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
20
9
  };
21
- var createLogger = (client) => {
22
- const service = "opencode-usage";
23
- return {
24
- debug: async (message, extra) => {
25
- await client.app.log({
26
- service,
27
- level: "debug",
28
- message,
29
- ...extra ?? {}
30
- });
31
- },
32
- info: async (message, extra) => {
33
- await client.app.log({
34
- service,
35
- level: "info",
36
- message,
37
- ...extra ?? {}
38
- });
39
- },
40
- warn: async (message, extra) => {
41
- await client.app.log({
42
- service,
43
- level: "warn",
44
- message,
45
- ...extra ?? {}
46
- });
47
- },
48
- error: async (message, extra) => {
49
- await client.app.log({
50
- service,
51
- level: "error",
52
- message,
53
- ...extra ?? {}
54
- });
55
- }
56
- };
57
- };
58
- var noopLogger = noOpLogger;
59
10
 
60
- // src/providers/common/time.ts
61
- var calculateResetAfterSeconds = (resetAt, now = Date.now()) => {
62
- if (!resetAt) {
63
- return null;
64
- }
65
- const diffMs = resetAt - now;
66
- if (diffMs <= 0) {
67
- return 0;
11
+ // src/providers/common/logger.ts
12
+ var logger_exports = {};
13
+ __export(logger_exports, {
14
+ createLogger: () => createLogger,
15
+ maskSecret: () => maskSecret,
16
+ noopLogger: () => noopLogger
17
+ });
18
+ var maskSecret, noOpLogger, createLogger, noopLogger;
19
+ var init_logger = __esm({
20
+ "src/providers/common/logger.ts"() {
21
+ "use strict";
22
+ maskSecret = (secret) => {
23
+ if (!secret || typeof secret !== "string" || secret.length <= 8) {
24
+ return "***";
25
+ }
26
+ return `${secret.slice(0, 4)}...${secret.slice(-4)}`;
27
+ };
28
+ noOpLogger = {
29
+ debug: async () => {
30
+ },
31
+ info: async () => {
32
+ },
33
+ warn: async () => {
34
+ },
35
+ error: async () => {
36
+ }
37
+ };
38
+ createLogger = (client) => {
39
+ const service = "opencode-usage";
40
+ return {
41
+ debug: async (message, extra) => {
42
+ await client.app.log({
43
+ service,
44
+ level: "debug",
45
+ message,
46
+ ...extra ?? {}
47
+ });
48
+ },
49
+ info: async (message, extra) => {
50
+ await client.app.log({
51
+ service,
52
+ level: "info",
53
+ message,
54
+ ...extra ?? {}
55
+ });
56
+ },
57
+ warn: async (message, extra) => {
58
+ await client.app.log({
59
+ service,
60
+ level: "warn",
61
+ message,
62
+ ...extra ?? {}
63
+ });
64
+ },
65
+ error: async (message, extra) => {
66
+ await client.app.log({
67
+ service,
68
+ level: "error",
69
+ message,
70
+ ...extra ?? {}
71
+ });
72
+ }
73
+ };
74
+ };
75
+ noopLogger = noOpLogger;
68
76
  }
69
- return Math.floor(diffMs / 1e3);
70
- };
71
- var formatDuration = (seconds) => {
72
- if (seconds <= 0) {
73
- return "0s";
74
- }
75
- const weeks = Math.floor(seconds / 604800);
76
- const days = Math.floor(seconds % 604800 / 86400);
77
- const hours = Math.floor(seconds % 86400 / 3600);
78
- const minutes = Math.floor(seconds % 3600 / 60);
79
- const secs = seconds % 60;
80
- const parts = [];
81
- if (weeks > 0) parts.push(`${weeks}w`);
82
- if (days > 0) parts.push(`${days}d`);
83
- if (hours > 0) parts.push(`${hours}h`);
84
- if (minutes > 0) parts.push(`${minutes}m`);
85
- if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
86
- return parts.join(" ");
87
- };
88
- var formatResetAt = (resetAtMs) => {
89
- return new Date(resetAtMs).toLocaleString(void 0, {
90
- weekday: "long",
91
- year: "numeric",
92
- month: "long",
93
- day: "numeric",
94
- hour: "numeric",
95
- minute: "numeric",
96
- second: "numeric",
97
- timeZoneName: "short"
98
- });
99
- };
77
+ });
100
78
 
101
79
  // src/providers/common/files.ts
102
80
  import { readFile } from "node:fs/promises";
103
81
  import { homedir } from "node:os";
104
82
  import { join } from "node:path";
105
- var xdgDataHome = () => process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
106
- var xdgConfigHome = () => process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
107
- var AUTH_PATHS = {
108
- opencode: () => join(xdgDataHome(), "opencode", "auth.json"),
109
- openaiPlugin: () => join(homedir(), ".opencode", "auth", "openai.json"),
110
- antigravityConfig: () => join(xdgConfigHome(), "opencode", "antigravity-accounts.json"),
111
- antigravityData: () => join(xdgDataHome(), "opencode", "antigravity-accounts.json")
112
- };
113
- var readJson = async (filePath, logger) => {
114
- try {
115
- const content = await readFile(filePath, "utf-8");
116
- return JSON.parse(content);
117
- } catch (error) {
118
- if (logger) {
119
- const message = error instanceof Error ? error.message : String(error);
120
- await logger.debug(`Auth file not found or invalid: ${filePath}`, { error: message });
121
- }
122
- return null;
83
+ var xdgDataHome, xdgConfigHome, xdgCacheHome, AUTH_PATHS, readJson, loadOpenCodeAuth;
84
+ var init_files = __esm({
85
+ "src/providers/common/files.ts"() {
86
+ "use strict";
87
+ xdgDataHome = () => process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
88
+ xdgConfigHome = () => process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
89
+ xdgCacheHome = () => process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
90
+ AUTH_PATHS = {
91
+ opencode: () => join(xdgDataHome(), "opencode", "auth.json"),
92
+ openaiPlugin: () => join(homedir(), ".opencode", "auth", "openai.json"),
93
+ antigravityConfig: () => join(xdgConfigHome(), "opencode", "antigravity-accounts.json"),
94
+ antigravityData: () => join(xdgDataHome(), "opencode", "antigravity-accounts.json")
95
+ };
96
+ readJson = async (filePath, logger) => {
97
+ try {
98
+ const content = await readFile(filePath, "utf-8");
99
+ return JSON.parse(content);
100
+ } catch (error) {
101
+ if (logger) {
102
+ const message = error instanceof Error ? error.message : String(error);
103
+ await logger.debug(`Auth file not found or invalid: ${filePath}`, { error: message });
104
+ }
105
+ return null;
106
+ }
107
+ };
108
+ loadOpenCodeAuth = async (logger) => {
109
+ return readJson(AUTH_PATHS.opencode(), logger);
110
+ };
123
111
  }
124
- };
125
- var loadOpenCodeAuth = async (logger) => {
126
- return readJson(AUTH_PATHS.opencode(), logger);
127
- };
128
-
129
- // src/providers/common/registry.ts
130
- var PROVIDER_ALIASES = {
131
- openai: ["openai", "codex", "chatgpt"],
132
- google: ["google", "antigravity"],
133
- "zai-coding-plan": ["zai-coding-plan", "zai", "z.ai"]
134
- };
135
- var getProviderAliases = (provider) => {
136
- return PROVIDER_ALIASES[provider];
137
- };
112
+ });
138
113
 
139
- // src/providers/google/auth.ts
140
- var toAuthData = (entry) => {
141
- if (!entry) {
142
- return null;
143
- }
144
- if (typeof entry === "string") {
145
- return { token: entry };
146
- }
147
- if (typeof entry === "object") {
148
- return entry;
149
- }
150
- return null;
151
- };
152
- var loadOpenCodeAuthEntry = async (logger) => {
153
- const auth = await loadOpenCodeAuth(logger);
154
- if (!auth) {
155
- return null;
156
- }
157
- for (const alias of getProviderAliases("google")) {
158
- const entry = toAuthData(auth[alias]);
159
- if (entry) {
160
- return entry;
161
- }
162
- }
163
- return null;
164
- };
165
- var toAuthContext = (entry) => {
166
- if (!entry) {
167
- return null;
168
- }
169
- const accessToken = entry.access ?? entry.token;
170
- let refreshToken = entry.refresh;
171
- let projectId = void 0;
172
- if (refreshToken && refreshToken.includes("|")) {
173
- const parts = refreshToken.split("|");
174
- refreshToken = parts[0];
175
- projectId = parts[1];
176
- }
177
- if (!accessToken && !refreshToken) {
178
- return null;
179
- }
180
- return {
181
- accessToken,
182
- refreshToken,
183
- expires: entry.expires,
184
- projectId
185
- };
186
- };
187
- var selectAccount = (accounts) => {
188
- if (!accounts?.accounts?.length) {
189
- return null;
114
+ // src/cache/file.ts
115
+ import { mkdir, readFile as readFile2, rename, unlink, writeFile } from "node:fs/promises";
116
+ var CACHE_DIR, CACHE_PATH, TMP_PATH, ensureCacheDir, readCache, writeCache;
117
+ var init_file = __esm({
118
+ "src/cache/file.ts"() {
119
+ "use strict";
120
+ init_files();
121
+ CACHE_DIR = () => `${xdgCacheHome()}/opencode/opencode-usage-plugin`;
122
+ CACHE_PATH = () => `${CACHE_DIR()}/usage.json`;
123
+ TMP_PATH = () => `${CACHE_PATH()}.tmp`;
124
+ ensureCacheDir = async () => {
125
+ try {
126
+ await mkdir(CACHE_DIR(), { recursive: true });
127
+ } catch (error) {
128
+ if (error instanceof Error && "code" in error && error.code !== "EEXIST") {
129
+ throw error;
130
+ }
131
+ }
132
+ };
133
+ readCache = async (logger) => {
134
+ try {
135
+ const content = await readFile2(CACHE_PATH(), "utf-8");
136
+ const parsed = JSON.parse(content);
137
+ if (typeof parsed === "object" && parsed !== null && "schema_version" in parsed && "updated_at" in parsed && "refresh_interval_seconds" in parsed && "providers" in parsed) {
138
+ return parsed;
139
+ }
140
+ await logger?.warn("Invalid cache schema, returning null");
141
+ return null;
142
+ } catch (error) {
143
+ if (logger) {
144
+ const message = error instanceof Error ? error.message : String(error);
145
+ await logger.debug("Cache file not found or invalid", { error: message });
146
+ }
147
+ return null;
148
+ }
149
+ };
150
+ writeCache = async (cache, logger) => {
151
+ try {
152
+ await ensureCacheDir();
153
+ const content = JSON.stringify(cache, null, 2);
154
+ await writeFile(TMP_PATH(), content, "utf-8");
155
+ await rename(TMP_PATH(), CACHE_PATH());
156
+ await logger?.debug("Cache written successfully", { path: CACHE_PATH() });
157
+ } catch (error) {
158
+ await logger?.error("Failed to write cache", {
159
+ error: error instanceof Error ? error.message : String(error)
160
+ });
161
+ throw error;
162
+ }
163
+ };
190
164
  }
191
- const candidateIndex = accounts.activeIndex ?? 0;
192
- const account = accounts.accounts[candidateIndex] ?? accounts.accounts[0];
193
- return account ?? null;
194
- };
195
- var loadAuthFromAccounts = async (logger) => {
196
- const configAccounts = await readJson(
197
- AUTH_PATHS.antigravityConfig(),
198
- logger
199
- );
200
- const account = selectAccount(configAccounts);
201
- if (account) {
202
- return {
203
- refreshToken: account.refreshToken,
204
- projectId: account.projectId ?? account.managedProjectId,
205
- email: account.email
165
+ });
166
+
167
+ // src/cache/types.ts
168
+ var SCHEMA_VERSION, REFRESH_INTERVAL_SECONDS, STALE_THRESHOLD_MULTIPLIER;
169
+ var init_types = __esm({
170
+ "src/cache/types.ts"() {
171
+ "use strict";
172
+ SCHEMA_VERSION = 1;
173
+ REFRESH_INTERVAL_SECONDS = 300;
174
+ STALE_THRESHOLD_MULTIPLIER = 2;
175
+ }
176
+ });
177
+
178
+ // src/cache/reader.ts
179
+ var reader_exports = {};
180
+ __export(reader_exports, {
181
+ loadCacheForDisplay: () => loadCacheForDisplay
182
+ });
183
+ var loadCacheForDisplay;
184
+ var init_reader = __esm({
185
+ "src/cache/reader.ts"() {
186
+ "use strict";
187
+ init_file();
188
+ init_types();
189
+ loadCacheForDisplay = async (logger) => {
190
+ const cache = await readCache(logger);
191
+ if (!cache) {
192
+ await logger.warn("No cache available");
193
+ return null;
194
+ }
195
+ const updatedAt = new Date(cache.updated_at);
196
+ const now = /* @__PURE__ */ new Date();
197
+ const staleThresholdMs = REFRESH_INTERVAL_SECONDS * 1e3 * STALE_THRESHOLD_MULTIPLIER;
198
+ const isStale = now.getTime() - updatedAt.getTime() > staleThresholdMs;
199
+ if (isStale) {
200
+ await logger.debug("Cache is stale", {
201
+ updatedAt: cache.updated_at,
202
+ staleThresholdSeconds: REFRESH_INTERVAL_SECONDS * STALE_THRESHOLD_MULTIPLIER
203
+ });
204
+ }
205
+ const configuredProviders = {};
206
+ for (const [providerId, entry] of Object.entries(cache.providers)) {
207
+ if (entry.configured) {
208
+ configuredProviders[providerId] = entry;
209
+ }
210
+ }
211
+ return {
212
+ providers: configuredProviders,
213
+ updatedAt: cache.updated_at,
214
+ isStale
215
+ };
206
216
  };
207
217
  }
208
- const dataAccounts = await readJson(
209
- AUTH_PATHS.antigravityData(),
210
- logger
211
- );
212
- const fallbackAccount = selectAccount(dataAccounts);
213
- if (!fallbackAccount) {
214
- return null;
218
+ });
219
+
220
+ // src/providers/common/time.ts
221
+ var calculateResetAfterSeconds, formatDuration, formatResetAt;
222
+ var init_time = __esm({
223
+ "src/providers/common/time.ts"() {
224
+ "use strict";
225
+ calculateResetAfterSeconds = (resetAt, now = Date.now()) => {
226
+ if (!resetAt) {
227
+ return null;
228
+ }
229
+ const diffMs = resetAt - now;
230
+ if (diffMs <= 0) {
231
+ return 0;
232
+ }
233
+ return Math.floor(diffMs / 1e3);
234
+ };
235
+ formatDuration = (seconds) => {
236
+ if (seconds <= 0) {
237
+ return "0s";
238
+ }
239
+ const weeks = Math.floor(seconds / 604800);
240
+ const days = Math.floor(seconds % 604800 / 86400);
241
+ const hours = Math.floor(seconds % 86400 / 3600);
242
+ const minutes = Math.floor(seconds % 3600 / 60);
243
+ const secs = seconds % 60;
244
+ const parts = [];
245
+ if (weeks > 0) parts.push(`${weeks}w`);
246
+ if (days > 0) parts.push(`${days}d`);
247
+ if (hours > 0) parts.push(`${hours}h`);
248
+ if (minutes > 0) parts.push(`${minutes}m`);
249
+ if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
250
+ return parts.join(" ");
251
+ };
252
+ formatResetAt = (resetAtMs) => {
253
+ return new Date(resetAtMs).toLocaleString(void 0, {
254
+ weekday: "long",
255
+ year: "numeric",
256
+ month: "long",
257
+ day: "numeric",
258
+ hour: "numeric",
259
+ minute: "numeric",
260
+ second: "numeric",
261
+ timeZoneName: "short"
262
+ });
263
+ };
215
264
  }
216
- return {
217
- refreshToken: fallbackAccount.refreshToken,
218
- projectId: fallbackAccount.projectId ?? fallbackAccount.managedProjectId,
219
- email: fallbackAccount.email
220
- };
221
- };
222
- var getGoogleAuth = async (logger) => {
223
- const openCodeAuth = await loadOpenCodeAuthEntry(logger);
224
- const authContext = toAuthContext(openCodeAuth);
225
- if (authContext) {
226
- return authContext;
265
+ });
266
+
267
+ // src/providers/common/registry.ts
268
+ var PROVIDER_ALIASES, getProviderAliases;
269
+ var init_registry = __esm({
270
+ "src/providers/common/registry.ts"() {
271
+ "use strict";
272
+ PROVIDER_ALIASES = {
273
+ openai: ["openai", "codex", "chatgpt"],
274
+ google: ["google", "antigravity"],
275
+ "zai-coding-plan": ["zai-coding-plan", "zai", "z.ai"]
276
+ };
277
+ getProviderAliases = (provider) => {
278
+ return PROVIDER_ALIASES[provider];
279
+ };
227
280
  }
228
- return loadAuthFromAccounts(logger);
229
- };
281
+ });
230
282
 
231
- // src/providers/google/fetch.ts
232
- var GOOGLE_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
233
- var GOOGLE_CLIENT_SECRET = "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";
234
- var DEFAULT_PROJECT_ID = "rising-fact-p41fc";
235
- var WINDOW_SECONDS = 5 * 60 * 60;
236
- var ENDPOINTS = [
237
- "https://daily-cloudcode-pa.sandbox.googleapis.com",
238
- "https://autopush-cloudcode-pa.sandbox.googleapis.com",
239
- "https://cloudcode-pa.googleapis.com"
240
- ];
241
- var HEADERS = {
242
- "User-Agent": "antigravity/1.11.5 windows/amd64",
243
- "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
244
- "Client-Metadata": '{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}'
245
- };
246
- var refreshAccessToken = async (refreshToken, logger) => {
247
- try {
248
- const response = await fetch("https://oauth2.googleapis.com/token", {
249
- method: "POST",
250
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
251
- body: new URLSearchParams({
252
- client_id: GOOGLE_CLIENT_ID,
253
- client_secret: GOOGLE_CLIENT_SECRET,
254
- refresh_token: refreshToken,
255
- grant_type: "refresh_token"
256
- })
257
- });
258
- if (!response.ok) {
259
- await logger.warn("Failed to refresh OAuth token for google", {
260
- status: response.status,
261
- token: maskSecret(refreshToken)
262
- });
283
+ // src/providers/google/auth.ts
284
+ var toAuthData, loadOpenCodeAuthEntry, toAuthContext, selectAccount, loadAuthFromAccounts, getGoogleAuth;
285
+ var init_auth = __esm({
286
+ "src/providers/google/auth.ts"() {
287
+ "use strict";
288
+ init_files();
289
+ init_registry();
290
+ toAuthData = (entry) => {
291
+ if (!entry) {
292
+ return null;
293
+ }
294
+ if (typeof entry === "string") {
295
+ return { token: entry };
296
+ }
297
+ if (typeof entry === "object") {
298
+ return entry;
299
+ }
263
300
  return null;
264
- }
265
- return await response.json();
266
- } catch (error) {
267
- const message = error instanceof Error ? error.message : String(error);
268
- await logger.warn(`Token refresh failed for google: ${message}`);
269
- return null;
301
+ };
302
+ loadOpenCodeAuthEntry = async (logger) => {
303
+ const auth = await loadOpenCodeAuth(logger);
304
+ if (!auth) {
305
+ return null;
306
+ }
307
+ for (const alias of getProviderAliases("google")) {
308
+ const entry = toAuthData(auth[alias]);
309
+ if (entry) {
310
+ return entry;
311
+ }
312
+ }
313
+ return null;
314
+ };
315
+ toAuthContext = (entry) => {
316
+ if (!entry) {
317
+ return null;
318
+ }
319
+ const accessToken = entry.access ?? entry.token;
320
+ let refreshToken = entry.refresh;
321
+ let projectId = void 0;
322
+ if (refreshToken && refreshToken.includes("|")) {
323
+ const parts = refreshToken.split("|");
324
+ refreshToken = parts[0];
325
+ projectId = parts[1];
326
+ }
327
+ if (!accessToken && !refreshToken) {
328
+ return null;
329
+ }
330
+ return {
331
+ accessToken,
332
+ refreshToken,
333
+ expires: entry.expires,
334
+ projectId
335
+ };
336
+ };
337
+ selectAccount = (accounts) => {
338
+ if (!accounts?.accounts?.length) {
339
+ return null;
340
+ }
341
+ const candidateIndex = accounts.activeIndex ?? 0;
342
+ const account = accounts.accounts[candidateIndex] ?? accounts.accounts[0];
343
+ return account ?? null;
344
+ };
345
+ loadAuthFromAccounts = async (logger) => {
346
+ const configAccounts = await readJson(
347
+ AUTH_PATHS.antigravityConfig(),
348
+ logger
349
+ );
350
+ const account = selectAccount(configAccounts);
351
+ if (account) {
352
+ return {
353
+ refreshToken: account.refreshToken,
354
+ projectId: account.projectId ?? account.managedProjectId,
355
+ email: account.email
356
+ };
357
+ }
358
+ const dataAccounts = await readJson(
359
+ AUTH_PATHS.antigravityData(),
360
+ logger
361
+ );
362
+ const fallbackAccount = selectAccount(dataAccounts);
363
+ if (!fallbackAccount) {
364
+ return null;
365
+ }
366
+ return {
367
+ refreshToken: fallbackAccount.refreshToken,
368
+ projectId: fallbackAccount.projectId ?? fallbackAccount.managedProjectId,
369
+ email: fallbackAccount.email
370
+ };
371
+ };
372
+ getGoogleAuth = async (logger) => {
373
+ const openCodeAuth = await loadOpenCodeAuthEntry(logger);
374
+ const authContext = toAuthContext(openCodeAuth);
375
+ if (authContext) {
376
+ return authContext;
377
+ }
378
+ return loadAuthFromAccounts(logger);
379
+ };
270
380
  }
271
- };
272
- var fetchModels = async (accessToken, projectId, logger) => {
273
- const body = projectId ? { project: projectId } : {};
274
- for (const endpoint of ENDPOINTS) {
275
- try {
276
- const response = await fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
277
- method: "POST",
278
- headers: {
279
- Authorization: `Bearer ${accessToken}`,
280
- "Content-Type": "application/json",
281
- ...HEADERS
282
- },
283
- body: JSON.stringify(body),
284
- signal: AbortSignal.timeout(15e3)
285
- });
286
- if (response.ok) {
287
- await logger.debug(`Fetched models from ${endpoint}`, { projectId });
381
+ });
382
+
383
+ // src/providers/google/fetch.ts
384
+ var GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, DEFAULT_PROJECT_ID, WINDOW_SECONDS, ENDPOINTS, HEADERS, refreshAccessToken, fetchModels, toWindow, buildUsage, resolveAccessToken, fetchGoogleUsage;
385
+ var init_fetch = __esm({
386
+ "src/providers/google/fetch.ts"() {
387
+ "use strict";
388
+ init_logger();
389
+ init_time();
390
+ init_auth();
391
+ GOOGLE_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
392
+ GOOGLE_CLIENT_SECRET = "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";
393
+ DEFAULT_PROJECT_ID = "rising-fact-p41fc";
394
+ WINDOW_SECONDS = 5 * 60 * 60;
395
+ ENDPOINTS = [
396
+ "https://daily-cloudcode-pa.sandbox.googleapis.com",
397
+ "https://autopush-cloudcode-pa.sandbox.googleapis.com",
398
+ "https://cloudcode-pa.googleapis.com"
399
+ ];
400
+ HEADERS = {
401
+ "User-Agent": "antigravity/1.11.5 windows/amd64",
402
+ "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
403
+ "Client-Metadata": '{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}'
404
+ };
405
+ refreshAccessToken = async (refreshToken, logger) => {
406
+ try {
407
+ const response = await fetch("https://oauth2.googleapis.com/token", {
408
+ method: "POST",
409
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
410
+ body: new URLSearchParams({
411
+ client_id: GOOGLE_CLIENT_ID,
412
+ client_secret: GOOGLE_CLIENT_SECRET,
413
+ refresh_token: refreshToken,
414
+ grant_type: "refresh_token"
415
+ })
416
+ });
417
+ if (!response.ok) {
418
+ await logger.warn("Failed to refresh OAuth token for google", {
419
+ status: response.status,
420
+ token: maskSecret(refreshToken)
421
+ });
422
+ return null;
423
+ }
288
424
  return await response.json();
425
+ } catch (error) {
426
+ const message = error instanceof Error ? error.message : String(error);
427
+ await logger.warn(`Token refresh failed for google: ${message}`);
428
+ return null;
289
429
  }
290
- } catch {
291
- continue;
292
- }
293
- }
294
- await logger.error("Failed to fetch models from all google endpoints", { projectId });
295
- return null;
296
- };
297
- var toWindow = (remainingFraction, resetTime) => {
298
- const remainingPercent = remainingFraction !== void 0 ? Math.round(remainingFraction * 100) : null;
299
- const usedPercent = remainingPercent !== null ? Math.max(0, 100 - remainingPercent) : null;
300
- const resetAt = resetTime ? new Date(resetTime).getTime() : null;
301
- const resetAfterSeconds = calculateResetAfterSeconds(resetAt);
302
- return {
303
- usedPercent,
304
- remainingPercent,
305
- windowSeconds: WINDOW_SECONDS,
306
- resetAfterSeconds,
307
- resetAt,
308
- resetAtFormatted: resetAt ? formatResetAt(resetAt) : null,
309
- resetAfterFormatted: resetAfterSeconds !== null ? formatDuration(resetAfterSeconds) : null
310
- };
311
- };
312
- var buildUsage = (data) => {
313
- const models = {};
314
- for (const [modelName, modelData] of Object.entries(data.models ?? {})) {
315
- const window = toWindow(modelData.quotaInfo?.remainingFraction, modelData.quotaInfo?.resetTime);
316
- models[modelName] = {
317
- windows: {
318
- "5h": window
430
+ };
431
+ fetchModels = async (accessToken, projectId, logger) => {
432
+ const body = projectId ? { project: projectId } : {};
433
+ for (const endpoint of ENDPOINTS) {
434
+ try {
435
+ const response = await fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
436
+ method: "POST",
437
+ headers: {
438
+ Authorization: `Bearer ${accessToken}`,
439
+ "Content-Type": "application/json",
440
+ ...HEADERS
441
+ },
442
+ body: JSON.stringify(body),
443
+ signal: AbortSignal.timeout(15e3)
444
+ });
445
+ if (response.ok) {
446
+ await logger.debug(`Fetched models from ${endpoint}`, { projectId });
447
+ return await response.json();
448
+ }
449
+ } catch {
450
+ continue;
451
+ }
452
+ }
453
+ await logger.error("Failed to fetch models from all google endpoints", { projectId });
454
+ return null;
455
+ };
456
+ toWindow = (remainingFraction, resetTime) => {
457
+ const remainingPercent = remainingFraction !== void 0 ? Math.round(remainingFraction * 100) : null;
458
+ const usedPercent = remainingPercent !== null ? Math.max(0, 100 - remainingPercent) : null;
459
+ const resetAt = resetTime ? new Date(resetTime).getTime() : null;
460
+ const resetAfterSeconds = calculateResetAfterSeconds(resetAt);
461
+ return {
462
+ usedPercent,
463
+ remainingPercent,
464
+ windowSeconds: WINDOW_SECONDS,
465
+ resetAfterSeconds,
466
+ resetAt,
467
+ resetAtFormatted: resetAt ? formatResetAt(resetAt) : null,
468
+ resetAfterFormatted: resetAfterSeconds !== null ? formatDuration(resetAfterSeconds) : null
469
+ };
470
+ };
471
+ buildUsage = (data) => {
472
+ const models = {};
473
+ for (const [modelName, modelData] of Object.entries(data.models ?? {})) {
474
+ const window = toWindow(modelData.quotaInfo?.remainingFraction, modelData.quotaInfo?.resetTime);
475
+ models[modelName] = {
476
+ windows: {
477
+ "5h": window
478
+ }
479
+ };
319
480
  }
481
+ return {
482
+ windows: {},
483
+ models: Object.keys(models).length ? models : void 0
484
+ };
320
485
  };
321
- }
322
- return {
323
- windows: {},
324
- models: Object.keys(models).length ? models : void 0
325
- };
326
- };
327
- var resolveAccessToken = async (refreshToken, accessToken, expires, logger) => {
328
- const now = Date.now();
329
- if (accessToken && (!expires || expires > now)) {
330
- return accessToken;
331
- }
332
- if (!refreshToken) {
333
- return null;
334
- }
335
- const refreshed = await refreshAccessToken(refreshToken, logger);
336
- return refreshed?.access_token ?? null;
337
- };
338
- var fetchGoogleUsage = async (logger = noopLogger) => {
339
- const auth = await getGoogleAuth(logger);
340
- if (!auth) {
341
- await logger.warn("No auth configured for google");
342
- return {
343
- provider: "google",
344
- ok: false,
345
- configured: false,
346
- error: "Not configured - no accounts found",
347
- usage: null
348
- };
349
- }
350
- const accessToken = await resolveAccessToken(
351
- auth.refreshToken,
352
- auth.accessToken,
353
- auth.expires,
354
- logger
355
- );
356
- if (!accessToken) {
357
- await logger.warn("Failed to refresh OAuth token for google", { email: auth.email });
358
- return {
359
- provider: "google",
360
- ok: false,
361
- configured: true,
362
- error: "Failed to refresh OAuth token",
363
- usage: null
486
+ resolveAccessToken = async (refreshToken, accessToken, expires, logger) => {
487
+ const now = Date.now();
488
+ if (accessToken && (!expires || expires > now)) {
489
+ return accessToken;
490
+ }
491
+ if (!refreshToken) {
492
+ return null;
493
+ }
494
+ const refreshed = await refreshAccessToken(refreshToken, logger);
495
+ return refreshed?.access_token ?? null;
364
496
  };
365
- }
366
- const projectId = auth.projectId ?? DEFAULT_PROJECT_ID;
367
- const modelsData = await fetchModels(accessToken, projectId, logger);
368
- if (!modelsData) {
369
- await logger.error("Failed to fetch models from google API", { projectId });
370
- return {
371
- provider: "google",
372
- ok: false,
373
- configured: true,
374
- error: "Failed to fetch models from API",
375
- usage: null
497
+ fetchGoogleUsage = async (logger = noopLogger) => {
498
+ const auth = await getGoogleAuth(logger);
499
+ if (!auth) {
500
+ await logger.warn("No auth configured for google");
501
+ return {
502
+ provider: "google",
503
+ ok: false,
504
+ configured: false,
505
+ error: "Not configured - no accounts found",
506
+ usage: null
507
+ };
508
+ }
509
+ const accessToken = await resolveAccessToken(
510
+ auth.refreshToken,
511
+ auth.accessToken,
512
+ auth.expires,
513
+ logger
514
+ );
515
+ if (!accessToken) {
516
+ await logger.warn("Failed to refresh OAuth token for google", { email: auth.email });
517
+ return {
518
+ provider: "google",
519
+ ok: false,
520
+ configured: true,
521
+ error: "Failed to refresh OAuth token",
522
+ usage: null
523
+ };
524
+ }
525
+ const projectId = auth.projectId ?? DEFAULT_PROJECT_ID;
526
+ const modelsData = await fetchModels(accessToken, projectId, logger);
527
+ if (!modelsData) {
528
+ await logger.error("Failed to fetch models from google API", { projectId });
529
+ return {
530
+ provider: "google",
531
+ ok: false,
532
+ configured: true,
533
+ error: "Failed to fetch models from API",
534
+ usage: null
535
+ };
536
+ }
537
+ await logger.info("google usage fetched successfully");
538
+ return {
539
+ provider: "google",
540
+ ok: true,
541
+ configured: true,
542
+ usage: buildUsage(modelsData)
543
+ };
376
544
  };
377
545
  }
378
- await logger.info("google usage fetched successfully");
379
- return {
380
- provider: "google",
381
- ok: true,
382
- configured: true,
383
- usage: buildUsage(modelsData)
384
- };
385
- };
546
+ });
386
547
 
387
548
  // src/providers/openai/auth.ts
388
- var toAuthData2 = (entry) => {
389
- if (!entry) {
390
- return null;
391
- }
392
- if (typeof entry === "string") {
393
- return { token: entry };
394
- }
395
- if (typeof entry === "object") {
396
- return entry;
397
- }
398
- return null;
399
- };
400
- var hasAccessToken = (auth) => {
401
- return Boolean(auth?.access || auth?.token);
402
- };
403
- var loadOpenCodeAuthEntry2 = async (logger) => {
404
- const auth = await loadOpenCodeAuth(logger);
405
- if (!auth) {
406
- return null;
407
- }
408
- for (const alias of getProviderAliases("openai")) {
409
- const entry = toAuthData2(auth[alias]);
410
- if (entry && hasAccessToken(entry)) {
411
- return entry;
412
- }
413
- }
414
- return null;
415
- };
416
- var getOpenaiAuth = async (logger) => {
417
- const openCodeAuth = await loadOpenCodeAuthEntry2(logger);
418
- if (openCodeAuth && hasAccessToken(openCodeAuth)) {
419
- return openCodeAuth;
420
- }
421
- const pluginAuth = await readJson(AUTH_PATHS.openaiPlugin(), logger);
422
- if (pluginAuth && hasAccessToken(pluginAuth)) {
423
- return pluginAuth;
549
+ var toAuthData2, hasAccessToken, loadOpenCodeAuthEntry2, getOpenaiAuth;
550
+ var init_auth2 = __esm({
551
+ "src/providers/openai/auth.ts"() {
552
+ "use strict";
553
+ init_files();
554
+ init_registry();
555
+ toAuthData2 = (entry) => {
556
+ if (!entry) {
557
+ return null;
558
+ }
559
+ if (typeof entry === "string") {
560
+ return { token: entry };
561
+ }
562
+ if (typeof entry === "object") {
563
+ return entry;
564
+ }
565
+ return null;
566
+ };
567
+ hasAccessToken = (auth) => {
568
+ return Boolean(auth?.access || auth?.token);
569
+ };
570
+ loadOpenCodeAuthEntry2 = async (logger) => {
571
+ const auth = await loadOpenCodeAuth(logger);
572
+ if (!auth) {
573
+ return null;
574
+ }
575
+ for (const alias of getProviderAliases("openai")) {
576
+ const entry = toAuthData2(auth[alias]);
577
+ if (entry && hasAccessToken(entry)) {
578
+ return entry;
579
+ }
580
+ }
581
+ return null;
582
+ };
583
+ getOpenaiAuth = async (logger) => {
584
+ const openCodeAuth = await loadOpenCodeAuthEntry2(logger);
585
+ if (openCodeAuth && hasAccessToken(openCodeAuth)) {
586
+ return openCodeAuth;
587
+ }
588
+ const pluginAuth = await readJson(AUTH_PATHS.openaiPlugin(), logger);
589
+ if (pluginAuth && hasAccessToken(pluginAuth)) {
590
+ return pluginAuth;
591
+ }
592
+ return null;
593
+ };
424
594
  }
425
- return null;
426
- };
595
+ });
427
596
 
428
597
  // src/providers/openai/fetch.ts
429
- var toWindow2 = (window) => {
430
- if (!window) {
431
- return null;
432
- }
433
- const usedPercent = window.used_percent;
434
- const resetAt = window.reset_at ? window.reset_at * 1e3 : null;
435
- const resetAfterSeconds = window.reset_after_seconds ?? calculateResetAfterSeconds(resetAt);
436
- return {
437
- usedPercent,
438
- remainingPercent: Math.max(0, 100 - usedPercent),
439
- windowSeconds: window.limit_window_seconds ?? null,
440
- resetAfterSeconds,
441
- resetAt,
442
- resetAtFormatted: resetAt ? formatResetAt(resetAt) : null,
443
- resetAfterFormatted: resetAfterSeconds !== null ? formatDuration(resetAfterSeconds) : null
444
- };
445
- };
446
- var fetchOpenaiUsage = async (logger = noopLogger) => {
447
- const auth = await getOpenaiAuth(logger);
448
- if (!auth) {
449
- await logger.warn("No auth configured for openai");
450
- return {
451
- provider: "openai",
452
- ok: false,
453
- configured: false,
454
- error: "Not configured - no OAuth token found",
455
- usage: null
456
- };
457
- }
458
- const accessToken = auth.access ?? auth.token;
459
- if (!accessToken) {
460
- await logger.warn("Auth configured but access token missing for openai");
461
- return {
462
- provider: "openai",
463
- ok: false,
464
- configured: false,
465
- error: "Not configured - access token missing",
466
- usage: null
467
- };
468
- }
469
- try {
470
- const response = await fetch("https://chatgpt.com/backend-api/wham/usage", {
471
- method: "GET",
472
- headers: {
473
- Authorization: `Bearer ${accessToken}`,
474
- "Content-Type": "application/json"
475
- }
476
- });
477
- if (!response.ok) {
478
- await logger.error(`API error ${response.status} for openai`, {
479
- token: maskSecret(accessToken)
480
- });
598
+ var toWindow2, fetchOpenaiUsage;
599
+ var init_fetch2 = __esm({
600
+ "src/providers/openai/fetch.ts"() {
601
+ "use strict";
602
+ init_logger();
603
+ init_time();
604
+ init_auth2();
605
+ toWindow2 = (window) => {
606
+ if (!window) {
607
+ return null;
608
+ }
609
+ const usedPercent = window.used_percent;
610
+ const resetAt = window.reset_at ? window.reset_at * 1e3 : null;
611
+ const resetAfterSeconds = window.reset_after_seconds ?? calculateResetAfterSeconds(resetAt);
481
612
  return {
482
- provider: "openai",
483
- ok: false,
484
- configured: true,
485
- error: `API error: ${response.status}`,
486
- usage: null
613
+ usedPercent,
614
+ remainingPercent: Math.max(0, 100 - usedPercent),
615
+ windowSeconds: window.limit_window_seconds ?? null,
616
+ resetAfterSeconds,
617
+ resetAt,
618
+ resetAtFormatted: resetAt ? formatResetAt(resetAt) : null,
619
+ resetAfterFormatted: resetAfterSeconds !== null ? formatDuration(resetAfterSeconds) : null
487
620
  };
488
- }
489
- const payload = await response.json();
490
- const primary = toWindow2(payload.rate_limit.primary_window);
491
- const secondary = toWindow2(payload.rate_limit.secondary_window);
492
- const windows = {};
493
- if (primary) {
494
- windows["5h"] = primary;
495
- }
496
- if (secondary) {
497
- windows["weekly"] = secondary;
498
- }
499
- const usage = {
500
- windows
501
621
  };
502
- await logger.info("openai usage fetched successfully");
503
- return {
504
- provider: "openai",
505
- ok: true,
506
- configured: true,
507
- usage
508
- };
509
- } catch (error) {
510
- const message = error instanceof Error ? error.message : String(error);
511
- await logger.error(`Request failed for openai: ${message}`);
512
- return {
513
- provider: "openai",
514
- ok: false,
515
- configured: true,
516
- error: `Request failed: ${message}`,
517
- usage: null
622
+ fetchOpenaiUsage = async (logger = noopLogger) => {
623
+ const auth = await getOpenaiAuth(logger);
624
+ if (!auth) {
625
+ await logger.warn("No auth configured for openai");
626
+ return {
627
+ provider: "openai",
628
+ ok: false,
629
+ configured: false,
630
+ error: "Not configured - no OAuth token found",
631
+ usage: null
632
+ };
633
+ }
634
+ const accessToken = auth.access ?? auth.token;
635
+ if (!accessToken) {
636
+ await logger.warn("Auth configured but access token missing for openai");
637
+ return {
638
+ provider: "openai",
639
+ ok: false,
640
+ configured: false,
641
+ error: "Not configured - access token missing",
642
+ usage: null
643
+ };
644
+ }
645
+ try {
646
+ const response = await fetch("https://chatgpt.com/backend-api/wham/usage", {
647
+ method: "GET",
648
+ headers: {
649
+ Authorization: `Bearer ${accessToken}`,
650
+ "Content-Type": "application/json"
651
+ }
652
+ });
653
+ if (!response.ok) {
654
+ await logger.error(`API error ${response.status} for openai`, {
655
+ token: maskSecret(accessToken)
656
+ });
657
+ return {
658
+ provider: "openai",
659
+ ok: false,
660
+ configured: true,
661
+ error: `API error: ${response.status}`,
662
+ usage: null
663
+ };
664
+ }
665
+ const payload = await response.json();
666
+ const primary = toWindow2(payload.rate_limit.primary_window);
667
+ const secondary = toWindow2(payload.rate_limit.secondary_window);
668
+ const windows = {};
669
+ if (primary) {
670
+ windows["5h"] = primary;
671
+ }
672
+ if (secondary) {
673
+ windows["weekly"] = secondary;
674
+ }
675
+ const usage = {
676
+ windows
677
+ };
678
+ await logger.info("openai usage fetched successfully");
679
+ return {
680
+ provider: "openai",
681
+ ok: true,
682
+ configured: true,
683
+ usage
684
+ };
685
+ } catch (error) {
686
+ const message = error instanceof Error ? error.message : String(error);
687
+ await logger.error(`Request failed for openai: ${message}`);
688
+ return {
689
+ provider: "openai",
690
+ ok: false,
691
+ configured: true,
692
+ error: `Request failed: ${message}`,
693
+ usage: null
694
+ };
695
+ }
518
696
  };
519
697
  }
520
- };
698
+ });
521
699
 
522
700
  // src/providers/zai-coding-plan/auth.ts
523
- var resolveAuthValue = (entry) => {
524
- if (!entry) {
525
- return null;
526
- }
527
- if (typeof entry === "string") {
528
- return entry;
529
- }
530
- if (typeof entry === "object") {
531
- return entry.api_key ?? entry.token ?? entry.key ?? null;
532
- }
533
- return null;
534
- };
535
- var getZaiApiKey = async (logger) => {
536
- if (process.env.ZAI_API_KEY) {
537
- return process.env.ZAI_API_KEY;
538
- }
539
- const auth = await loadOpenCodeAuth(logger);
540
- if (!auth) {
541
- return null;
542
- }
543
- for (const alias of getProviderAliases("zai-coding-plan")) {
544
- const value = resolveAuthValue(auth[alias]);
545
- if (value) {
546
- return value;
547
- }
701
+ var resolveAuthValue, getZaiApiKey;
702
+ var init_auth3 = __esm({
703
+ "src/providers/zai-coding-plan/auth.ts"() {
704
+ "use strict";
705
+ init_files();
706
+ init_registry();
707
+ resolveAuthValue = (entry) => {
708
+ if (!entry) {
709
+ return null;
710
+ }
711
+ if (typeof entry === "string") {
712
+ return entry;
713
+ }
714
+ if (typeof entry === "object") {
715
+ return entry.api_key ?? entry.token ?? entry.key ?? null;
716
+ }
717
+ return null;
718
+ };
719
+ getZaiApiKey = async (logger) => {
720
+ if (process.env.ZAI_API_KEY) {
721
+ return process.env.ZAI_API_KEY;
722
+ }
723
+ const auth = await loadOpenCodeAuth(logger);
724
+ if (!auth) {
725
+ return null;
726
+ }
727
+ for (const alias of getProviderAliases("zai-coding-plan")) {
728
+ const value = resolveAuthValue(auth[alias]);
729
+ if (value) {
730
+ return value;
731
+ }
732
+ }
733
+ return null;
734
+ };
548
735
  }
549
- return null;
550
- };
736
+ });
551
737
 
552
738
  // src/providers/zai-coding-plan/fetch.ts
553
- var normalizeTimestamp = (value) => {
554
- return value < 1e12 ? value * 1e3 : value;
555
- };
556
- var TOKEN_WINDOW_SECONDS = {
557
- 3: 3600
558
- };
559
- var resolveWindowSeconds = (limit) => {
560
- if (!limit) {
561
- return null;
562
- }
563
- if (!limit.number) {
564
- return null;
565
- }
566
- const unitSeconds = TOKEN_WINDOW_SECONDS[limit.unit];
567
- if (!unitSeconds) {
568
- return null;
739
+ var normalizeTimestamp, TOKEN_WINDOW_SECONDS, resolveWindowSeconds, resolveWindowLabel, toWindow3, fetchZaiUsage;
740
+ var init_fetch3 = __esm({
741
+ "src/providers/zai-coding-plan/fetch.ts"() {
742
+ "use strict";
743
+ init_logger();
744
+ init_time();
745
+ init_auth3();
746
+ normalizeTimestamp = (value) => {
747
+ return value < 1e12 ? value * 1e3 : value;
748
+ };
749
+ TOKEN_WINDOW_SECONDS = {
750
+ 3: 3600
751
+ };
752
+ resolveWindowSeconds = (limit) => {
753
+ if (!limit) {
754
+ return null;
755
+ }
756
+ if (!limit.number) {
757
+ return null;
758
+ }
759
+ const unitSeconds = TOKEN_WINDOW_SECONDS[limit.unit];
760
+ if (!unitSeconds) {
761
+ return null;
762
+ }
763
+ return unitSeconds * limit.number;
764
+ };
765
+ resolveWindowLabel = (windowSeconds) => {
766
+ if (!windowSeconds) {
767
+ return "tokens";
768
+ }
769
+ if (windowSeconds % 86400 === 0) {
770
+ const days = windowSeconds / 86400;
771
+ return days === 7 ? "weekly" : `${days}d`;
772
+ }
773
+ if (windowSeconds % 3600 === 0) {
774
+ return `${windowSeconds / 3600}h`;
775
+ }
776
+ return `${windowSeconds}s`;
777
+ };
778
+ toWindow3 = (limit) => {
779
+ if (!limit) {
780
+ return null;
781
+ }
782
+ const usedPercent = limit.percentage ?? null;
783
+ const remainingPercent = usedPercent !== null ? Math.max(0, 100 - usedPercent) : null;
784
+ const resetAt = limit.nextResetTime ? normalizeTimestamp(limit.nextResetTime) : null;
785
+ const resetAfterSeconds = calculateResetAfterSeconds(resetAt);
786
+ return {
787
+ usedPercent,
788
+ remainingPercent,
789
+ windowSeconds: resolveWindowSeconds(limit),
790
+ resetAfterSeconds,
791
+ resetAt,
792
+ resetAtFormatted: resetAt ? formatResetAt(resetAt) : null,
793
+ resetAfterFormatted: resetAfterSeconds !== null ? formatDuration(resetAfterSeconds) : null
794
+ };
795
+ };
796
+ fetchZaiUsage = async (logger = noopLogger) => {
797
+ const apiKey = await getZaiApiKey(logger);
798
+ if (!apiKey) {
799
+ await logger.warn("No auth configured for zai-coding-plan");
800
+ return {
801
+ provider: "zai-coding-plan",
802
+ ok: false,
803
+ configured: false,
804
+ error: "Not configured - no API key found",
805
+ usage: null
806
+ };
807
+ }
808
+ try {
809
+ const response = await fetch("https://api.z.ai/api/monitor/usage/quota/limit", {
810
+ method: "GET",
811
+ headers: {
812
+ Authorization: `Bearer ${apiKey}`,
813
+ "Content-Type": "application/json"
814
+ }
815
+ });
816
+ if (!response.ok) {
817
+ await logger.error(`API error ${response.status} for zai-coding-plan`, {
818
+ token: maskSecret(apiKey)
819
+ });
820
+ return {
821
+ provider: "zai-coding-plan",
822
+ ok: false,
823
+ configured: true,
824
+ error: `API error: ${response.status}`,
825
+ usage: null
826
+ };
827
+ }
828
+ const payload = await response.json();
829
+ const limits = payload.data?.limits ?? [];
830
+ const tokensLimit = limits.find((limit) => limit.type === "TOKENS_LIMIT");
831
+ const windows = {};
832
+ const window = toWindow3(tokensLimit);
833
+ if (window) {
834
+ const label = resolveWindowLabel(window.windowSeconds);
835
+ windows[label] = window;
836
+ }
837
+ const usage = {
838
+ windows
839
+ };
840
+ await logger.info("zai-coding-plan usage fetched successfully");
841
+ return {
842
+ provider: "zai-coding-plan",
843
+ ok: true,
844
+ configured: true,
845
+ usage
846
+ };
847
+ } catch (error) {
848
+ const message = error instanceof Error ? error.message : String(error);
849
+ await logger.error(`Request failed for zai-coding-plan: ${message}`);
850
+ return {
851
+ provider: "zai-coding-plan",
852
+ ok: false,
853
+ configured: true,
854
+ error: `Request failed: ${message}`,
855
+ usage: null
856
+ };
857
+ }
858
+ };
569
859
  }
570
- return unitSeconds * limit.number;
571
- };
572
- var resolveWindowLabel = (windowSeconds) => {
573
- if (!windowSeconds) {
574
- return "tokens";
860
+ });
861
+
862
+ // src/types/provider.ts
863
+ var PROVIDERS;
864
+ var init_provider = __esm({
865
+ "src/types/provider.ts"() {
866
+ "use strict";
867
+ PROVIDERS = ["openai", "google", "zai-coding-plan"];
575
868
  }
576
- if (windowSeconds % 86400 === 0) {
577
- const days = windowSeconds / 86400;
578
- return days === 7 ? "weekly" : `${days}d`;
869
+ });
870
+
871
+ // src/types/dashboard.ts
872
+ var init_dashboard = __esm({
873
+ "src/types/dashboard.ts"() {
874
+ "use strict";
579
875
  }
580
- if (windowSeconds % 3600 === 0) {
581
- return `${windowSeconds / 3600}h`;
876
+ });
877
+
878
+ // src/types/index.ts
879
+ var init_types2 = __esm({
880
+ "src/types/index.ts"() {
881
+ "use strict";
882
+ init_provider();
883
+ init_dashboard();
884
+ }
885
+ });
886
+
887
+ // src/cache/fetcher.ts
888
+ var fetchUsage, fetchAllProviders;
889
+ var init_fetcher = __esm({
890
+ "src/cache/fetcher.ts"() {
891
+ "use strict";
892
+ init_fetch();
893
+ init_fetch2();
894
+ init_fetch3();
895
+ init_types2();
896
+ init_file();
897
+ init_types();
898
+ fetchUsage = async (provider, logger) => {
899
+ switch (provider) {
900
+ case "openai":
901
+ return fetchOpenaiUsage(logger);
902
+ case "google":
903
+ return fetchGoogleUsage(logger);
904
+ case "zai-coding-plan":
905
+ return fetchZaiUsage(logger);
906
+ }
907
+ };
908
+ fetchAllProviders = async (logger) => {
909
+ await logger.info("Fetching usage for all providers");
910
+ const existingCache = await readCache(logger);
911
+ const results = await Promise.all(PROVIDERS.map((provider) => fetchUsage(provider, logger)));
912
+ const providers = {};
913
+ const now = (/* @__PURE__ */ new Date()).toISOString();
914
+ for (const result of results) {
915
+ const providerId = result.provider;
916
+ const wasConfigured = existingCache?.providers[providerId]?.configured ?? false;
917
+ providers[providerId] = {
918
+ supported: true,
919
+ configured: result.configured,
920
+ last_attempt_at: now,
921
+ last_success_at: result.ok ? now : null,
922
+ data: result.ok ? result.usage : null,
923
+ error: result.ok ? null : result.error ?? null
924
+ };
925
+ if (!result.configured && wasConfigured) {
926
+ providers[providerId].data = null;
927
+ await logger.debug(`Wiped data for ${providerId} (no longer configured)`);
928
+ }
929
+ }
930
+ const cache = {
931
+ schema_version: SCHEMA_VERSION,
932
+ updated_at: now,
933
+ refresh_interval_seconds: REFRESH_INTERVAL_SECONDS,
934
+ providers
935
+ };
936
+ await writeCache(cache, logger);
937
+ return cache;
938
+ };
582
939
  }
583
- return `${windowSeconds}s`;
584
- };
585
- var toWindow3 = (limit) => {
586
- if (!limit) {
587
- return null;
588
- }
589
- const usedPercent = limit.percentage ?? null;
590
- const remainingPercent = usedPercent !== null ? Math.max(0, 100 - usedPercent) : null;
591
- const resetAt = limit.nextResetTime ? normalizeTimestamp(limit.nextResetTime) : null;
592
- const resetAfterSeconds = calculateResetAfterSeconds(resetAt);
593
- return {
594
- usedPercent,
595
- remainingPercent,
596
- windowSeconds: resolveWindowSeconds(limit),
597
- resetAfterSeconds,
598
- resetAt,
599
- resetAtFormatted: resetAt ? formatResetAt(resetAt) : null,
600
- resetAfterFormatted: resetAfterSeconds !== null ? formatDuration(resetAfterSeconds) : null
601
- };
602
- };
603
- var fetchZaiUsage = async (logger = noopLogger) => {
604
- const apiKey = await getZaiApiKey(logger);
605
- if (!apiKey) {
606
- await logger.warn("No auth configured for zai-coding-plan");
607
- return {
608
- provider: "zai-coding-plan",
609
- ok: false,
610
- configured: false,
611
- error: "Not configured - no API key found",
612
- usage: null
613
- };
614
- }
615
- try {
616
- const response = await fetch("https://api.z.ai/api/monitor/usage/quota/limit", {
617
- method: "GET",
618
- headers: {
619
- Authorization: `Bearer ${apiKey}`,
620
- "Content-Type": "application/json"
621
- }
622
- });
623
- if (!response.ok) {
624
- await logger.error(`API error ${response.status} for zai-coding-plan`, {
625
- token: maskSecret(apiKey)
626
- });
627
- return {
628
- provider: "zai-coding-plan",
629
- ok: false,
630
- configured: true,
631
- error: `API error: ${response.status}`,
632
- usage: null
940
+ });
941
+
942
+ // src/cache/worker.ts
943
+ var worker_exports = {};
944
+ __export(worker_exports, {
945
+ isWorkerRunning: () => isWorkerRunning,
946
+ startWorker: () => startWorker,
947
+ stopWorker: () => stopWorker
948
+ });
949
+ var intervalId, startWorker, stopWorker, isWorkerRunning;
950
+ var init_worker = __esm({
951
+ "src/cache/worker.ts"() {
952
+ "use strict";
953
+ init_fetcher();
954
+ init_types();
955
+ intervalId = null;
956
+ startWorker = (logger) => {
957
+ if (intervalId !== null) {
958
+ logger.warn("Worker already started");
959
+ return;
960
+ }
961
+ const refreshLoop = async () => {
962
+ try {
963
+ await fetchAllProviders(logger);
964
+ } catch (error) {
965
+ await logger.error("Worker refresh failed", {
966
+ error: error instanceof Error ? error.message : String(error)
967
+ });
968
+ }
633
969
  };
634
- }
635
- const payload = await response.json();
636
- const limits = payload.data?.limits ?? [];
637
- const tokensLimit = limits.find((limit) => limit.type === "TOKENS_LIMIT");
638
- const windows = {};
639
- const window = toWindow3(tokensLimit);
640
- if (window) {
641
- const label = resolveWindowLabel(window.windowSeconds);
642
- windows[label] = window;
643
- }
644
- const usage = {
645
- windows
970
+ void refreshLoop();
971
+ intervalId = globalThis.setInterval(refreshLoop, REFRESH_INTERVAL_SECONDS * 1e3);
972
+ void logger.info(`Worker started, refreshing every ${REFRESH_INTERVAL_SECONDS}s`);
646
973
  };
647
- await logger.info("zai-coding-plan usage fetched successfully");
648
- return {
649
- provider: "zai-coding-plan",
650
- ok: true,
651
- configured: true,
652
- usage
653
- };
654
- } catch (error) {
655
- const message = error instanceof Error ? error.message : String(error);
656
- await logger.error(`Request failed for zai-coding-plan: ${message}`);
657
- return {
658
- provider: "zai-coding-plan",
659
- ok: false,
660
- configured: true,
661
- error: `Request failed: ${message}`,
662
- usage: null
974
+ stopWorker = (logger) => {
975
+ if (intervalId === null) {
976
+ return;
977
+ }
978
+ globalThis.clearInterval(intervalId);
979
+ intervalId = null;
980
+ void logger.info("Worker stopped");
663
981
  };
982
+ isWorkerRunning = () => intervalId !== null;
664
983
  }
665
- };
984
+ });
666
985
 
667
- // src/types/provider.ts
668
- var PROVIDERS = ["openai", "google", "zai-coding-plan"];
986
+ // src/index.ts
987
+ init_logger();
988
+ import { tool } from "@opencode-ai/plugin";
669
989
 
670
990
  // src/toast/filter.ts
671
991
  var FLAGSHIP_PATTERNS = [
@@ -689,7 +1009,7 @@ var filterFlagshipModels = (models) => {
689
1009
  return filtered;
690
1010
  };
691
1011
 
692
- // src/table/format.ts
1012
+ // src/dashboard/format.ts
693
1013
  var getStatus = (remainingPercent) => {
694
1014
  if (remainingPercent === null) {
695
1015
  return { emoji: "\u26AA", text: "N/A" };
@@ -702,186 +1022,335 @@ var getStatus = (remainingPercent) => {
702
1022
  }
703
1023
  return { emoji: "\u{1F7E2}", text: "OK" };
704
1024
  };
705
- var formatProviderWindow = (provider, model, usage) => {
706
- let window = null;
707
- const globalWindows = Object.values(usage.windows);
708
- if (globalWindows.length > 0) {
709
- window = globalWindows[0];
710
- } else if (usage.models && model) {
711
- const modelWindows = usage.models[model];
712
- if (modelWindows) {
713
- const modelWindowEntries = Object.values(modelWindows.windows);
714
- if (modelWindowEntries.length > 0) {
715
- window = modelWindowEntries[0];
716
- }
717
- }
718
- }
719
- if (!window) {
720
- return null;
1025
+ var formatWindowLabel = (key) => {
1026
+ switch (key) {
1027
+ case "5h":
1028
+ return "5h Window";
1029
+ case "weekly":
1030
+ return "Weekly Window";
1031
+ default:
1032
+ return `${key} Window`;
721
1033
  }
722
- const usedPercent = window.usedPercent ?? null;
1034
+ };
1035
+ var renderBar = (percent, width = 20) => {
1036
+ if (percent === null) {
1037
+ return `[${"\u2591".repeat(width)}]`;
1038
+ }
1039
+ const validPercent = Math.max(0, Math.min(100, percent));
1040
+ const filledLength = Math.round(validPercent / 100 * width);
1041
+ const emptyLength = width - filledLength;
1042
+ return `[${"\u2588".repeat(filledLength)}${"\u2591".repeat(emptyLength)}]`;
1043
+ };
1044
+ var formatWindow = (key, window) => {
723
1045
  const remainingPercent = window.remainingPercent ?? null;
724
- const resetsIn = window.resetAfterFormatted ?? "N/A";
725
1046
  const { emoji, text } = getStatus(remainingPercent);
726
1047
  return {
727
- provider,
728
- model: model ?? "-",
729
- usedPercent,
1048
+ label: formatWindowLabel(key),
1049
+ usedPercent: window.usedPercent ?? null,
730
1050
  remainingPercent,
731
1051
  status: emoji,
732
1052
  statusText: text,
733
- resetsIn
1053
+ resetsIn: window.resetAfterFormatted ?? "N/A"
734
1054
  };
735
1055
  };
736
- var formatProviderRows = (result) => {
737
- if (!result.usage) {
738
- return [];
1056
+ var formatDashboardData = (input) => {
1057
+ if (input === null) {
1058
+ return { providers: [] };
739
1059
  }
740
- let usage = result.usage;
741
- if (result.provider === "google" && usage?.models) {
742
- usage = {
743
- ...usage,
744
- models: filterFlagshipModels(usage.models)
745
- };
1060
+ if (!("providers" in input)) {
1061
+ return formatDashboardDataFromResults(input);
746
1062
  }
747
- const globalWindows = Object.values(usage.windows);
748
- const hasGlobalWindow = globalWindows.length > 0;
749
- const models = usage.models ? Object.keys(usage.models) : [];
750
- if (hasGlobalWindow) {
751
- const row = formatProviderWindow(result.provider, null, usage);
752
- return row ? [row] : [];
753
- }
754
- if (models.length > 0) {
755
- const rows = [];
756
- for (const modelName of models) {
757
- const row = formatProviderWindow(result.provider, modelName, usage);
758
- if (row) {
759
- rows.push(row);
1063
+ return formatDashboardDataFromCache(input);
1064
+ };
1065
+ var formatDashboardDataFromResults = (results) => {
1066
+ const providers = [];
1067
+ for (const result of results) {
1068
+ if (!result.usage) {
1069
+ continue;
1070
+ }
1071
+ const sections = [];
1072
+ let usage = result.usage;
1073
+ if (result.provider === "google" && usage.models) {
1074
+ usage = {
1075
+ ...usage,
1076
+ models: filterFlagshipModels(usage.models)
1077
+ };
1078
+ }
1079
+ const globalWindows = Object.entries(usage.windows);
1080
+ if (globalWindows.length > 0) {
1081
+ sections.push({
1082
+ title: "Overall Usage",
1083
+ windows: globalWindows.map(([key, win]) => formatWindow(key, win))
1084
+ });
1085
+ }
1086
+ if (usage.models) {
1087
+ const modelSections = [];
1088
+ for (const [modelName, modelUsage] of Object.entries(usage.models)) {
1089
+ const modelWindowEntries = Object.entries(modelUsage.windows);
1090
+ if (modelWindowEntries.length > 0) {
1091
+ modelSections.push({
1092
+ title: modelName,
1093
+ windows: modelWindowEntries.map(([key, win]) => formatWindow(key, win))
1094
+ });
1095
+ }
760
1096
  }
1097
+ if (modelSections.length > 0) {
1098
+ sections.push({
1099
+ title: "Model Usage",
1100
+ windows: [],
1101
+ sections: modelSections
1102
+ });
1103
+ }
1104
+ }
1105
+ if (sections.length > 0) {
1106
+ providers.push({
1107
+ name: result.provider.toUpperCase().replace(/-/g, " "),
1108
+ sections
1109
+ });
761
1110
  }
762
- return rows;
763
1111
  }
764
- return [];
1112
+ return { providers };
765
1113
  };
766
- var formatTableCell = (text, width, align = "left") => {
767
- const str = String(text ?? "N/A");
768
- if (align === "right") {
769
- return str.padStart(width, " ");
1114
+ var formatDashboardDataFromCache = (displayCache) => {
1115
+ const providers = [];
1116
+ for (const [providerId, entry] of Object.entries(displayCache.providers)) {
1117
+ if (!entry.data) {
1118
+ continue;
1119
+ }
1120
+ const sections = [];
1121
+ let usage = entry.data;
1122
+ if (providerId === "google" && usage.models) {
1123
+ usage = {
1124
+ ...usage,
1125
+ models: filterFlagshipModels(usage.models)
1126
+ };
1127
+ }
1128
+ const globalWindows = Object.entries(usage.windows);
1129
+ if (globalWindows.length > 0) {
1130
+ sections.push({
1131
+ title: "Overall Usage",
1132
+ windows: globalWindows.map(([key, win]) => formatWindow(key, win))
1133
+ });
1134
+ }
1135
+ if (usage.models) {
1136
+ const modelSections = [];
1137
+ for (const [modelName, modelUsage] of Object.entries(usage.models)) {
1138
+ const modelWindowEntries = Object.entries(modelUsage.windows);
1139
+ if (modelWindowEntries.length > 0) {
1140
+ modelSections.push({
1141
+ title: modelName,
1142
+ windows: modelWindowEntries.map(([key, win]) => formatWindow(key, win))
1143
+ });
1144
+ }
1145
+ }
1146
+ if (modelSections.length > 0) {
1147
+ sections.push({
1148
+ title: "Model Usage",
1149
+ windows: [],
1150
+ sections: modelSections
1151
+ });
1152
+ }
1153
+ }
1154
+ if (sections.length > 0) {
1155
+ providers.push({
1156
+ name: providerId.toUpperCase().replace(/-/g, " "),
1157
+ sections
1158
+ });
1159
+ }
770
1160
  }
771
- return str.padEnd(width, " ");
1161
+ return { providers };
772
1162
  };
773
- var formatUsageTable = (results) => {
774
- const rows = [];
775
- for (const result of results) {
776
- const providerRows = formatProviderRows(result);
777
- rows.push(...providerRows);
1163
+ var formatDashboardString = (data, updatedAt, isStale) => {
1164
+ if (data.providers.length === 0) {
1165
+ let result2 = "No usage data available";
1166
+ if (updatedAt) {
1167
+ result2 += `
1168
+
1169
+ Updated at: ${updatedAt}`;
1170
+ }
1171
+ return result2;
778
1172
  }
779
- return { rows };
780
- };
781
- var formatTableString = (tableData) => {
782
- if (tableData.rows.length === 0) {
783
- return "No usage data available";
784
- }
785
- const colWidths = {
786
- provider: 16,
787
- model: 18,
788
- used: 6,
789
- remaining: 11,
790
- status: 8,
791
- resets: 12
792
- };
793
- const header = `| ${formatTableCell("Provider", colWidths.provider)} | ${formatTableCell("Model", colWidths.model)} | ${formatTableCell("Used", colWidths.used, "right")} | ${formatTableCell("Remaining", colWidths.remaining, "right")} | ${formatTableCell("Status", colWidths.status)} | ${formatTableCell("Resets In", colWidths.resets)} |`;
794
- const separator = `|${"-".repeat(colWidths.provider + 2)}|${"-".repeat(colWidths.model + 2)}|${"-".repeat(colWidths.used + 2)}|${"-".repeat(colWidths.remaining + 2)}|${"-".repeat(colWidths.status + 2)}|${"-".repeat(colWidths.resets + 2)}|`;
795
- const rows = tableData.rows.map((row) => {
796
- return `| ${formatTableCell(row.provider, colWidths.provider)} | ${formatTableCell(row.model, colWidths.model)} | ${formatTableCell(row.usedPercent !== null ? `${row.usedPercent}%` : null, colWidths.used, "right")} | ${formatTableCell(row.remainingPercent !== null ? `${row.remainingPercent}%` : null, colWidths.remaining, "right")} | ${formatTableCell(row.status, colWidths.status)} | ${formatTableCell(row.resetsIn, colWidths.resets)} |`;
797
- });
798
- return [header, separator, ...rows].join("\n");
1173
+ const lines = [];
1174
+ const HEADER_WIDTH = 65;
1175
+ for (const provider of data.providers) {
1176
+ const providerName = provider.name;
1177
+ const dashCount = Math.max(0, HEADER_WIDTH - providerName.length - 1);
1178
+ lines.push(`${providerName} ${"\u2500".repeat(dashCount)}`);
1179
+ for (let i = 0; i < provider.sections.length; i++) {
1180
+ const section = provider.sections[i];
1181
+ lines.push(section.title);
1182
+ for (let j = 0; j < section.windows.length; j++) {
1183
+ const window = section.windows[j];
1184
+ const isLastWindow = j === section.windows.length - 1;
1185
+ const branch = isLastWindow ? "\u2514\u2500" : "\u251C\u2500";
1186
+ const pipe = isLastWindow ? " " : "\u2502 ";
1187
+ lines.push(` ${branch} ${window.label}`);
1188
+ const percentStr = window.usedPercent !== null ? `${Math.round(window.usedPercent)}%` : "N/A";
1189
+ lines.push(
1190
+ ` ${pipe} ${renderBar(window.usedPercent)} ${percentStr} \u2022 Resets in ${window.resetsIn}`
1191
+ );
1192
+ if (!isLastWindow) {
1193
+ lines.push(` ${pipe}`);
1194
+ }
1195
+ }
1196
+ if (section.sections) {
1197
+ for (let k = 0; k < section.sections.length; k++) {
1198
+ const subsection = section.sections[k];
1199
+ const isLastSubsection = k === section.sections.length - 1;
1200
+ const branch = isLastSubsection ? "\u2514\u2500" : "\u251C\u2500";
1201
+ const pipe = isLastSubsection ? " " : "\u2502 ";
1202
+ lines.push(` ${branch} ${subsection.title}`);
1203
+ for (let m = 0; m < subsection.windows.length; m++) {
1204
+ const window = subsection.windows[m];
1205
+ const isLastWindow = m === subsection.windows.length - 1;
1206
+ const subBranch = isLastWindow ? "\u2514\u2500" : "\u251C\u2500";
1207
+ lines.push(` ${pipe} ${subBranch} ${window.label}`);
1208
+ const percentStr = window.usedPercent !== null ? `${Math.round(window.usedPercent)}%` : "N/A";
1209
+ const subPipe = isLastWindow ? " " : "\u2502 ";
1210
+ lines.push(
1211
+ ` ${pipe} ${subPipe} ${renderBar(window.usedPercent)} ${percentStr} \u2022 Resets in ${window.resetsIn}`
1212
+ );
1213
+ if (!isLastWindow) {
1214
+ lines.push(` ${pipe} ${subPipe}`);
1215
+ }
1216
+ }
1217
+ if (!isLastSubsection) {
1218
+ lines.push(` ${pipe}`);
1219
+ }
1220
+ }
1221
+ }
1222
+ lines.push("");
1223
+ }
1224
+ }
1225
+ let result = lines.join("\n").trim();
1226
+ if (updatedAt) {
1227
+ result += `
1228
+
1229
+ Updated at: ${updatedAt}`;
1230
+ }
1231
+ if (isStale) {
1232
+ result += " (STALE)";
1233
+ }
1234
+ return result;
799
1235
  };
800
1236
 
801
1237
  // src/toast/format.ts
802
- var formatProviderLine = (provider, usage) => {
1238
+ var getStatus2 = (remainingPercent) => {
1239
+ if (remainingPercent === null) return "\u26AA";
1240
+ if (remainingPercent < 10) return "\u{1F534}";
1241
+ if (remainingPercent < 30) return "\u{1F7E1}";
1242
+ return "\u{1F7E2}";
1243
+ };
1244
+ var formatWindowLine = (name, window, isModel = false) => {
1245
+ const used = window.usedPercent !== null ? Math.round(window.usedPercent) : 0;
1246
+ const reset = window.resetAfterFormatted ?? "N/A";
1247
+ const remaining = window.remainingPercent ?? null;
1248
+ const emoji = isModel ? "" : `${getStatus2(remaining)} `;
1249
+ const indent = isModel ? " " : "";
1250
+ return `${indent}${emoji}${name.padEnd(isModel ? 13 : 10)} ${used}% \u2022 ${reset}`;
1251
+ };
1252
+ var formatProviderSection = (provider, usage) => {
803
1253
  if (!usage) {
804
- return `${provider}: Not configured`;
1254
+ return [`\u26AA ${provider}: Not configured`];
805
1255
  }
1256
+ const lines = [];
806
1257
  const globalWindows = Object.values(usage.windows);
807
1258
  if (globalWindows.length > 0) {
808
- const window = globalWindows[0];
809
- const used = window.usedPercent ?? 0;
810
- const reset = window.resetAfterFormatted ?? "N/A";
811
- return `${provider}: ${used}% used \u2022 resets in ${reset}`;
1259
+ lines.push(formatWindowLine(provider, globalWindows[0]));
1260
+ } else {
1261
+ lines.push(`\u26AA ${provider}: No usage data`);
812
1262
  }
813
1263
  if (usage.models) {
814
- const models = Object.entries(usage.models);
815
- if (models.length === 1) {
816
- const [modelName, modelData] = models[0];
817
- const window = Object.values(modelData.windows)[0];
818
- const used = window.usedPercent ?? 0;
819
- const reset = window.resetAfterFormatted ?? "N/A";
820
- return `${provider}: ${modelName} ${used}% \u2022 resets in ${reset}`;
821
- }
822
- if (models.length > 1) {
823
- const parts = [];
824
- for (const [modelName, modelData] of models) {
825
- const window = Object.values(modelData.windows)[0];
826
- const used = window.usedPercent ?? 0;
827
- parts.push(`${modelName} ${used}%`);
1264
+ for (const [modelName, modelData] of Object.entries(usage.models)) {
1265
+ const modelWindow = Object.values(modelData.windows)[0];
1266
+ if (modelWindow) {
1267
+ lines.push(formatWindowLine(modelName, modelWindow, true));
828
1268
  }
829
- return `${provider}: ${parts.join(", ")}`;
830
1269
  }
831
1270
  }
832
- return `${provider}: No usage data`;
1271
+ return lines;
833
1272
  };
834
- var formatUsageToast = async (results, logger) => {
1273
+ var formatUsageToast = async (input, logger) => {
1274
+ if (!input) {
1275
+ return {
1276
+ title: "\u{1F4CA} Usage",
1277
+ message: "No providers configured",
1278
+ variant: "info"
1279
+ };
1280
+ }
1281
+ const displayCache = "providers" in input ? input : null;
1282
+ const results = "providers" in input ? null : input;
835
1283
  const lines = [];
836
- for (const result of results) {
837
- if (!result.usage) {
838
- await logger?.debug(`Provider ${result.provider} not configured, skipping`);
839
- continue;
1284
+ const contentLines = [];
1285
+ const DIVIDER = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
1286
+ let updatedAt = "";
1287
+ let isStale = false;
1288
+ if (displayCache) {
1289
+ updatedAt = displayCache.updatedAt;
1290
+ isStale = displayCache.isStale;
1291
+ for (const [providerId, entry] of Object.entries(displayCache.providers)) {
1292
+ if (!entry.data) continue;
1293
+ let usage = entry.data;
1294
+ if (providerId === "google" && usage?.models) {
1295
+ usage = {
1296
+ ...usage,
1297
+ models: filterFlagshipModels(usage.models)
1298
+ };
1299
+ }
1300
+ contentLines.push(...formatProviderSection(providerId, usage));
840
1301
  }
841
- let usage = result.usage;
842
- if (result.provider === "google" && usage?.models) {
843
- usage = {
844
- ...usage,
845
- models: filterFlagshipModels(usage.models)
846
- };
1302
+ if (Object.values(displayCache.providers).some((e) => e.error !== null)) {
1303
+ contentLines.push("\u26A0\uFE0F Some providers failed");
1304
+ }
1305
+ } else if (results) {
1306
+ const now = /* @__PURE__ */ new Date();
1307
+ updatedAt = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}`;
1308
+ for (const result of results) {
1309
+ if (!result.usage) {
1310
+ await logger?.debug(`Provider ${result.provider} not configured, skipping`);
1311
+ continue;
1312
+ }
1313
+ let usage = result.usage;
1314
+ if (result.provider === "google" && usage?.models) {
1315
+ usage = {
1316
+ ...usage,
1317
+ models: filterFlagshipModels(usage.models)
1318
+ };
1319
+ }
1320
+ contentLines.push(...formatProviderSection(result.provider, usage));
847
1321
  }
848
- const line = formatProviderLine(result.provider, usage);
849
- lines.push(line);
850
1322
  }
851
- if (lines.length === 0) {
1323
+ if (contentLines.length === 0) {
852
1324
  return {
853
- title: "Usage",
1325
+ title: "\u{1F4CA} Usage",
854
1326
  message: "No providers configured",
855
1327
  variant: "info"
856
1328
  };
857
1329
  }
1330
+ lines.push(DIVIDER);
1331
+ lines.push(...contentLines);
1332
+ lines.push(DIVIDER);
1333
+ let footer = `Updated: ${updatedAt}`;
1334
+ if (isStale) footer += " (stale)";
1335
+ lines.push(footer);
858
1336
  return {
859
- title: "Usage",
1337
+ title: "\u{1F4CA} Usage",
860
1338
  message: lines.join("\n"),
861
1339
  variant: "info"
862
1340
  };
863
1341
  };
864
1342
 
865
1343
  // src/index.ts
866
- var fetchUsage = async (provider, logger) => {
867
- switch (provider) {
868
- case "openai":
869
- return fetchOpenaiUsage(logger);
870
- case "google":
871
- return fetchGoogleUsage(logger);
872
- case "zai-coding-plan":
873
- return fetchZaiUsage(logger);
874
- }
875
- };
876
1344
  var UsagePlugin = async ({ client }) => {
877
1345
  const logger = createLogger(client);
878
1346
  const usageToastTool = tool({
879
1347
  description: "Show subscription usage as toast for OpenAI, Google, and z.ai providers",
880
1348
  args: {},
881
1349
  async execute() {
882
- await logger.info("Fetching usage for all providers");
883
- const results = await Promise.all(PROVIDERS.map((provider) => fetchUsage(provider, logger)));
884
- const toast = await formatUsageToast(results, logger);
1350
+ const { loadCacheForDisplay: loadCacheForDisplay2 } = await Promise.resolve().then(() => (init_reader(), reader_exports));
1351
+ await logger.info("Loading usage from cache");
1352
+ const displayCache = await loadCacheForDisplay2(logger);
1353
+ const toast = await formatUsageToast(displayCache, logger);
885
1354
  await client.tui.showToast({
886
1355
  body: {
887
1356
  title: toast.title,
@@ -896,10 +1365,14 @@ var UsagePlugin = async ({ client }) => {
896
1365
  description: "Get subscription usage data for OpenAI, Google, and z.ai providers as a formatted table",
897
1366
  args: {},
898
1367
  async execute() {
899
- await logger.info("Fetching usage for all providers");
900
- const results = await Promise.all(PROVIDERS.map((provider) => fetchUsage(provider, logger)));
901
- const tableData = formatUsageTable(results);
902
- return formatTableString(tableData);
1368
+ const { loadCacheForDisplay: loadCacheForDisplay2 } = await Promise.resolve().then(() => (init_reader(), reader_exports));
1369
+ await logger.info("Loading usage from cache");
1370
+ const displayCache = await loadCacheForDisplay2(logger);
1371
+ if (!displayCache) {
1372
+ return "No cache available";
1373
+ }
1374
+ const dashboardData = formatDashboardData(displayCache);
1375
+ return formatDashboardString(dashboardData, displayCache.updatedAt, displayCache.isStale);
903
1376
  }
904
1377
  });
905
1378
  return {
@@ -907,6 +1380,12 @@ var UsagePlugin = async ({ client }) => {
907
1380
  usage_toast: usageToastTool,
908
1381
  usage_table: usageTableTool
909
1382
  },
1383
+ async event({ event }) {
1384
+ if (event.type === "server.connected") {
1385
+ const { startWorker: startWorker2 } = await Promise.resolve().then(() => (init_worker(), worker_exports));
1386
+ startWorker2(logger);
1387
+ }
1388
+ },
910
1389
  async config(config) {
911
1390
  config.command = config.command ?? {};
912
1391
  config.command["usage-toast"] = {
@@ -920,6 +1399,22 @@ var UsagePlugin = async ({ client }) => {
920
1399
  }
921
1400
  };
922
1401
  };
1402
+ var setupShutdownHandlers = async () => {
1403
+ const { isWorkerRunning: isWorkerRunning2, stopWorker: stopWorker2 } = await Promise.resolve().then(() => (init_worker(), worker_exports));
1404
+ const { createLogger: createLogger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
1405
+ const logger = createLogger2({
1406
+ app: { log: () => Promise.resolve(true) }
1407
+ });
1408
+ const onShutdown = async () => {
1409
+ if (await isWorkerRunning2()) {
1410
+ stopWorker2(logger);
1411
+ }
1412
+ };
1413
+ process.on("SIGINT", onShutdown);
1414
+ process.on("SIGTERM", onShutdown);
1415
+ process.on("exit", onShutdown);
1416
+ };
1417
+ void setupShutdownHandlers();
923
1418
  var index_default = UsagePlugin;
924
1419
  export {
925
1420
  UsagePlugin,