@getpaseo/server 0.11.0-beta.2 → 0.11.0-beta.3

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.
Files changed (48) hide show
  1. package/dist/server/builtin-plugins/claude-usage-source/index.server.ts +1 -2
  2. package/dist/server/builtin-plugins/claude-usage-source/server/stores.ts +0 -1
  3. package/dist/server/builtin-plugins/claude-usage-source/server/usage.ts +72 -36
  4. package/dist/server/builtin-plugins/codex-usage-source/index.server.ts +1 -2
  5. package/dist/server/builtin-plugins/codex-usage-source/server/stores.ts +0 -1
  6. package/dist/server/builtin-plugins/codex-usage-source/server/usage.ts +22 -12
  7. package/dist/server/builtin-plugins/copilot-usage-source/index.server.ts +2 -3
  8. package/dist/server/builtin-plugins/copilot-usage-source/server/usage.ts +28 -39
  9. package/dist/server/builtin-plugins/copilot-usage-source/shared/input.ts +6 -2
  10. package/dist/server/builtin-plugins/cursor-usage-source/index.server.ts +2 -3
  11. package/dist/server/builtin-plugins/cursor-usage-source/server/usage.ts +53 -54
  12. package/dist/server/builtin-plugins/cursor-usage-source/shared/input.ts +6 -2
  13. package/dist/server/builtin-plugins/grok-usage-source/index.server.ts +2 -3
  14. package/dist/server/builtin-plugins/grok-usage-source/server/usage.ts +23 -27
  15. package/dist/server/builtin-plugins/grok-usage-source/shared/input.ts +6 -2
  16. package/dist/server/builtin-plugins/kimi-usage-source/index.server.ts +2 -3
  17. package/dist/server/builtin-plugins/kimi-usage-source/server/usage.ts +44 -67
  18. package/dist/server/builtin-plugins/kimi-usage-source/shared/input.ts +6 -2
  19. package/dist/server/builtin-plugins/minimax-usage-source/index.server.ts +2 -3
  20. package/dist/server/builtin-plugins/minimax-usage-source/server/usage.ts +64 -100
  21. package/dist/server/builtin-plugins/minimax-usage-source/shared/input.ts +6 -2
  22. package/dist/server/builtin-plugins/muse-provider/server/usage.ts +9 -9
  23. package/dist/server/builtin-plugins/opencode-go-usage-source/index.server.ts +1 -2
  24. package/dist/server/builtin-plugins/opencode-go-usage-source/server/usage.ts +8 -12
  25. package/dist/server/builtin-plugins/opencode-go-usage-source/shared/input.ts +1 -2
  26. package/dist/server/builtin-plugins/package.json +1 -1
  27. package/dist/server/builtin-plugins/zai-usage-source/index.server.ts +2 -3
  28. package/dist/server/builtin-plugins/zai-usage-source/server/usage.ts +13 -11
  29. package/dist/server/builtin-plugins/zai-usage-source/shared/input.ts +6 -2
  30. package/dist/server/server/plugins/index.d.ts +18 -3
  31. package/dist/server/server/plugins/index.js +1 -2
  32. package/dist/server/server/plugins/plugin-process-protocol.d.ts +0 -5
  33. package/dist/server/server/plugins/plugin-process-protocol.js +0 -8
  34. package/dist/server/server/plugins/plugin-process.js +2 -7
  35. package/dist/server/server/plugins/runtime.d.ts +0 -1
  36. package/dist/server/server/plugins/runtime.js +0 -11
  37. package/dist/server/server/plugins/test-fixtures/usage-source/index.server.js +11 -2
  38. package/dist/server/server/plugins/test-fixtures/usage-source-directory/index.server.js +11 -2
  39. package/dist/server/server/plugins/usage-sources/index.d.ts +6 -8
  40. package/dist/server/server/plugins/usage-sources/index.js +79 -84
  41. package/dist/server/web-ui/_expo/static/js/web/{index-a43718759ebeb27df202e8b9fc2fe58a.js → index-1a3199cb049d45e47bfd4c216ce98838.js} +8 -8
  42. package/dist/server/web-ui/_expo/static/js/web/index-1a3199cb049d45e47bfd4c216ce98838.js.br +0 -0
  43. package/dist/server/web-ui/_expo/static/js/web/{index-a43718759ebeb27df202e8b9fc2fe58a.js.gz → index-1a3199cb049d45e47bfd4c216ce98838.js.gz} +0 -0
  44. package/dist/server/web-ui/index.html +1 -1
  45. package/dist/server/web-ui/index.html.br +0 -0
  46. package/dist/server/web-ui/index.html.gz +0 -0
  47. package/package.json +6 -6
  48. package/dist/server/web-ui/_expo/static/js/web/index-a43718759ebeb27df202e8b9fc2fe58a.js.br +0 -0
@@ -1,7 +1,8 @@
1
1
  import type { UsageInput } from "../shared/input.js";
2
2
  import { z } from "zod";
3
3
  import {
4
- unavailableUsage,
4
+ unavailable,
5
+ type UsageAccount,
5
6
  type UsageReport,
6
7
  type UsageDetail,
7
8
  } from "@getpaseo/plugin/server/usage";
@@ -28,10 +29,8 @@ export async function fetchUsage(
28
29
  input: UsageInput,
29
30
  fetchApi: typeof fetch = fetch,
30
31
  ): Promise<UsageReport> {
31
- void input;
32
-
33
- const token = process.env["ZAI_API_KEY"] || process.env["GLM_API_KEY"];
34
- if (!token) return unavailableUsage();
32
+ const token = process.env[input.locator];
33
+ if (!token) throw new Error("Z.ai login store no longer exists");
35
34
 
36
35
  const res = await fetchApi("https://api.z.ai/api/biz/subscription/list", {
37
36
  signal: AbortSignal.timeout(15_000),
@@ -41,13 +40,13 @@ export async function fetchUsage(
41
40
  },
42
41
  });
43
42
 
44
- if (!res.ok) {
45
- return unavailableUsage();
46
- }
43
+ if (res.status === 401 || res.status === 403)
44
+ return unavailable({ kind: "rejected", status: res.status });
45
+ if (!res.ok) throw new Error(`Z.ai usage API returned ${res.status}`);
47
46
 
48
47
  const resp = ZaiUsageResponseSchema.parse(await res.json());
49
48
  const sub = resp.data?.[0];
50
- if (!sub) return unavailableUsage();
49
+ if (!sub) return unavailable({ kind: "no_quota", detail: "No active coding plan" });
51
50
 
52
51
  const details: UsageDetail[] = [];
53
52
  if (sub.status) details.push({ id: "status", label: "Status", value: sub.status });
@@ -65,6 +64,9 @@ export async function fetchUsage(
65
64
  };
66
65
  }
67
66
 
68
- export async function identify() {
69
- return process.env["ZAI_API_KEY"] || process.env["GLM_API_KEY"] ? { key: "default" } : null;
67
+ export async function discover(): Promise<UsageAccount[]> {
68
+ for (const locator of ["ZAI_API_KEY", "GLM_API_KEY"]) {
69
+ if (process.env[locator]) return [{ key: "default", input: { store: "env", locator } }];
70
+ }
71
+ return [];
70
72
  }
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
- export const inputSchema = z.object({}).strict();
3
-
2
+ export const inputSchema = z
3
+ .object({
4
+ store: z.literal("env"),
5
+ locator: z.string().min(1),
6
+ })
7
+ .strict();
4
8
  export type UsageInput = z.infer<typeof inputSchema>;
@@ -17,7 +17,6 @@ interface PluginRuntimePort {
17
17
  clearLogs(pluginId: string): void;
18
18
  getProviderRegistrations?(pluginId: string): readonly PluginProviderMetadata[];
19
19
  getUsageSourceRegistrations(pluginId: string): readonly PluginUsageSourceMetadata[];
20
- identifyUsage: PluginRuntime["identifyUsage"];
21
20
  fetchUsage: PluginRuntime["fetchUsage"];
22
21
  discoverUsage: PluginRuntime["discoverUsage"];
23
22
  connectProvider: PluginRuntime["connectProvider"];
@@ -76,7 +75,7 @@ export declare class PluginService {
76
75
  sourceId: string;
77
76
  sourceLabel: string;
78
77
  report: {
79
- status: "error" | "unavailable" | "available";
78
+ status: "available";
80
79
  windows: {
81
80
  id: string;
82
81
  label: string;
@@ -106,7 +105,23 @@ export declare class PluginService {
106
105
  value: string;
107
106
  tone?: "default" | "warning" | "danger" | "ok" | undefined;
108
107
  }[] | undefined;
109
- error?: string | undefined;
108
+ } | {
109
+ status: "unavailable";
110
+ problem: {
111
+ kind: "expired";
112
+ expiresAt: string;
113
+ refreshedBy?: string | undefined;
114
+ } | {
115
+ kind: "rejected";
116
+ status: number;
117
+ refreshedBy?: string | undefined;
118
+ } | {
119
+ kind: "no_quota";
120
+ detail: string;
121
+ };
122
+ } | {
123
+ status: "error";
124
+ error: string;
110
125
  };
111
126
  icon?: string | undefined;
112
127
  }[]>;
@@ -24,7 +24,6 @@ export class PluginService {
24
24
  this.errors = new Map();
25
25
  this.listeners = new Set();
26
26
  this.providers = new Map();
27
- this.usageSources = new UsageSourceRegistry();
28
27
  this.usageSourceIdsByPlugin = new Map();
29
28
  this.providerIdsByPlugin = new Map();
30
29
  this.providerListeners = new Set();
@@ -42,6 +41,7 @@ export class PluginService {
42
41
  return request;
43
42
  };
44
43
  this.logger = logger.child({ module: "plugin-service" });
44
+ this.usageSources = new UsageSourceRegistry(Date.now, 300000, this.logger);
45
45
  this.runtime =
46
46
  dependencies.runtime ??
47
47
  new PluginRuntime(logger, daemonVersion, {
@@ -477,7 +477,6 @@ export class PluginService {
477
477
  throw new Error(`Invalid usage discovery from ${source.id}`);
478
478
  return result;
479
479
  },
480
- identify: (input) => this.runtime.identifyUsage(pluginId, source.id, input),
481
480
  fetch: (input) => {
482
481
  return this.runtime.fetchUsage(pluginId, source.id, input);
483
482
  },
@@ -40,11 +40,6 @@ export type PluginProcessRequest = {
40
40
  } | {
41
41
  type: "hook.cancel";
42
42
  requestId: string;
43
- } | {
44
- type: "usage.identify";
45
- requestId: string;
46
- sourceId: string;
47
- input: unknown;
48
43
  } | {
49
44
  type: "usage.fetch";
50
45
  requestId: string;
@@ -83,14 +83,6 @@ export const PluginProcessRequestSchema = z.discriminatedUnion("type", [
83
83
  })
84
84
  .strict(),
85
85
  z.object({ type: z.literal("hook.cancel"), requestId: z.string() }).strict(),
86
- z
87
- .object({
88
- type: z.literal("usage.identify"),
89
- requestId: z.string(),
90
- sourceId: z.string(),
91
- input: z.unknown(),
92
- })
93
- .strict(),
94
86
  z
95
87
  .object({
96
88
  type: z.literal("usage.fetch"),
@@ -296,15 +296,12 @@ export function createPluginWorker(options) {
296
296
  if (message.type === "usage.discover")
297
297
  return jsonTransportValue(await source.discover());
298
298
  const input = await source.input.parseAsync(message.input);
299
- return jsonTransportValue(message.type === "usage.identify"
300
- ? await source.identify(input)
301
- : await source.fetch(input));
299
+ return jsonTransportValue(await source.fetch(input));
302
300
  })().then((output) => send({ type: "result", requestId: message.requestId, output }), (error) => send({ type: "error", requestId: message.requestId, error: describeError(error) }));
303
301
  }
304
302
  function rejectWhileStopping(message) {
305
303
  if (message.type === "provider.status" ||
306
304
  message.type === "provider.catalog_key" ||
307
- message.type === "usage.identify" ||
308
305
  message.type === "usage.fetch" ||
309
306
  message.type === "usage.discover") {
310
307
  send({ type: "error", requestId: message.requestId, error: "Plugin is stopping" });
@@ -389,9 +386,7 @@ export function createPluginWorker(options) {
389
386
  })().catch((error) => send({ type: "error", requestId: message.requestId, error: describeError(error) }));
390
387
  return;
391
388
  }
392
- if (message.type === "usage.identify" ||
393
- message.type === "usage.fetch" ||
394
- message.type === "usage.discover") {
389
+ if (message.type === "usage.fetch" || message.type === "usage.discover") {
395
390
  handleUsageRequest(message);
396
391
  return;
397
392
  }
@@ -57,7 +57,6 @@ export declare class PluginRuntime {
57
57
  }>;
58
58
  getProviderRegistrations(pluginId: string): readonly PluginProviderMetadata[];
59
59
  getUsageSourceRegistrations(pluginId: string): readonly PluginUsageSourceMetadata[];
60
- identifyUsage(pluginId: string, sourceId: string, input: unknown): Promise<unknown>;
61
60
  fetchUsage(pluginId: string, sourceId: string, input: unknown): Promise<unknown>;
62
61
  discoverUsage(pluginId: string, sourceId: string): Promise<unknown>;
63
62
  getProviderStatus(pluginId: string, providerId: string, request: ProviderStatusRequest): Promise<ProviderStatus>;
@@ -241,17 +241,6 @@ export class PluginRuntime {
241
241
  getUsageSourceRegistrations(pluginId) {
242
242
  return this.plugins.get(pluginId)?.usageSources ?? [];
243
243
  }
244
- identifyUsage(pluginId, sourceId, input) {
245
- const loaded = this.plugins.get(pluginId);
246
- if (!loaded)
247
- throw new Error(`Plugin is not available: ${pluginId}`);
248
- return this.request(loaded, {
249
- type: "usage.identify",
250
- requestId: randomUUID(),
251
- sourceId,
252
- input,
253
- });
254
- }
255
244
  fetchUsage(pluginId, sourceId, input) {
256
245
  const loaded = this.plugins.get(pluginId);
257
246
  if (!loaded)
@@ -11,12 +11,21 @@ export default function contribute(server) {
11
11
  { account: "one" },
12
12
  { account: "bad", extra: true },
13
13
  { account: "throws" },
14
+ { account: "expired" },
14
15
  ];
15
- return inputs;
16
+ return inputs.map((input) => ({ key: String(input.account), input }));
16
17
  },
17
- identify: async (input) => ({ key: input.account }),
18
18
  fetch: async (input) => {
19
19
  const account = input.account;
20
+ if (account === "expired")
21
+ return {
22
+ status: "unavailable",
23
+ problem: {
24
+ kind: "expired",
25
+ expiresAt: new Date(Date.now() - 3600000).toISOString(),
26
+ refreshedBy: "claude",
27
+ },
28
+ };
20
29
  if (account === "throws")
21
30
  throw new Error("fixture failure");
22
31
  fetches++;
@@ -11,12 +11,21 @@ export default function contribute(server) {
11
11
  { account: "one" },
12
12
  { account: "bad", extra: true },
13
13
  { account: "throws" },
14
+ { account: "expired" },
14
15
  ];
15
- return inputs;
16
+ return inputs.map((input) => ({ key: String(input.account), input }));
16
17
  },
17
- identify: async (input) => ({ key: input.account }),
18
18
  fetch: async (input) => {
19
19
  const account = input.account;
20
+ if (account === "expired")
21
+ return {
22
+ status: "unavailable",
23
+ problem: {
24
+ kind: "expired",
25
+ expiresAt: new Date(Date.now() - 3600000).toISOString(),
26
+ refreshedBy: "claude",
27
+ },
28
+ };
20
29
  if (account === "throws")
21
30
  throw new Error("fixture failure");
22
31
  fetches++;
@@ -1,27 +1,24 @@
1
+ import type { Logger } from "pino";
1
2
  import { type ProviderUsage, type UsageReportEntry } from "@getpaseo/protocol/messages";
2
3
  export interface UsageSource {
3
4
  id: string;
4
5
  label: string;
5
6
  icon?: string;
6
- discover(): Promise<unknown[]>;
7
- identify(input: unknown): Promise<{
8
- key: string;
9
- label?: string;
10
- } | null>;
7
+ discover(): Promise<unknown>;
11
8
  fetch(input: unknown): Promise<unknown>;
12
9
  }
13
10
  /** Owns account identity, ordered logins for each account, and the five-minute fetch cache. */
14
11
  export declare class UsageSourceRegistry {
15
12
  private readonly now;
16
13
  private readonly ttlMs;
14
+ private readonly logger;
17
15
  private readonly sources;
18
16
  private readonly known;
19
17
  private readonly cache;
20
18
  private readonly pending;
21
- constructor(now?: () => number, ttlMs?: number);
19
+ constructor(now?: () => number, ttlMs?: number, logger?: Pick<Logger, "warn">);
22
20
  register(source: UsageSource): void;
23
21
  unregister(id: string): void;
24
- private identify;
25
22
  listReports(options?: {
26
23
  forceRefresh?: boolean;
27
24
  reportIds?: string[];
@@ -33,6 +30,7 @@ export declare class UsageSourceRegistry {
33
30
  }>;
34
31
  private fetchId;
35
32
  private writeCache;
36
- private errorEntry;
33
+ private fetchWithFallback;
34
+ private fetchLogin;
37
35
  }
38
36
  //# sourceMappingURL=index.d.ts.map
@@ -1,9 +1,11 @@
1
+ import { z } from "zod";
1
2
  import { UsageReportSchema, } from "@getpaseo/protocol/messages";
2
3
  /** Owns account identity, ordered logins for each account, and the five-minute fetch cache. */
3
4
  export class UsageSourceRegistry {
4
- constructor(now = Date.now, ttlMs = 300000) {
5
+ constructor(now = Date.now, ttlMs = 300000, logger = { warn: console.warn }) {
5
6
  this.now = now;
6
7
  this.ttlMs = ttlMs;
8
+ this.logger = logger;
7
9
  this.sources = new Map();
8
10
  this.known = new Map();
9
11
  this.cache = new Map();
@@ -23,56 +25,34 @@ export class UsageSourceRegistry {
23
25
  if (key.startsWith(`${id}:`))
24
26
  this.cache.delete(key);
25
27
  }
26
- async identify(source, input) {
27
- try {
28
- const account = await source.identify(input);
29
- if (account === null)
30
- return null;
31
- if (typeof account !== "object" ||
32
- (account.label !== undefined && typeof account.label !== "string"))
33
- throw new Error(`Invalid account identity from ${source.id}`);
34
- if (typeof account.key !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(account.key))
35
- throw new Error(`Invalid account key from ${source.id}`);
36
- return { id: `${source.id}:${account.key}`, label: account.label };
37
- }
38
- catch (error) {
39
- const id = `${source.id}:!error`;
40
- this.writeCache(id, this.errorEntry(source, id, error));
41
- return { id, error: true };
42
- }
43
- }
44
28
  async listReports(options = {}) {
45
29
  const ids = options.reportIds ?? (await this.discoverReportIds());
46
- return Promise.all([...new Set(ids)]
47
- .filter((id) => this.known.has(id) || this.cache.has(id))
48
- .map((id) => this.fetchId(id, options.forceRefresh)));
30
+ return Promise.all([...new Set(ids)].flatMap((id) => {
31
+ const known = this.known.get(id);
32
+ return known ? [this.fetchId(id, known, options.forceRefresh)] : [];
33
+ }));
49
34
  }
50
35
  async discoverReportIds() {
51
36
  const discovered = await Promise.all([...this.sources.values()].map(async (source) => {
52
37
  try {
53
- const inputs = await source.discover();
54
- if (!Array.isArray(inputs))
55
- throw new Error("Usage discovery must return an array");
56
- const accounts = await Promise.all(inputs.map((input) => this.identify(source, input)));
57
- // Group in discovery order so an account's logins keep the source's preference.
38
+ const accounts = z
39
+ .array(z.object({
40
+ key: z.string().regex(/^[A-Za-z0-9._-]{1,128}$/),
41
+ label: z.string().optional(),
42
+ input: z.json(),
43
+ }))
44
+ .parse(await source.discover());
58
45
  const knownReports = new Map();
59
46
  const ids = [];
60
- accounts.forEach((account, index) => {
61
- if (account === null)
62
- return;
63
- ids.push(account.id);
64
- if (account.error)
65
- return;
66
- const known = knownReports.get(account.id);
47
+ for (const account of accounts) {
48
+ const id = `${source.id}:${account.key}`;
49
+ ids.push(id);
50
+ const known = knownReports.get(id);
67
51
  if (known)
68
- known.inputs.push(inputs[index]);
52
+ known.inputs.push(account.input);
69
53
  else
70
- knownReports.set(account.id, {
71
- source,
72
- inputs: [inputs[index]],
73
- label: account.label,
74
- });
75
- });
54
+ knownReports.set(id, { source, inputs: [account.input], label: account.label });
55
+ }
76
56
  for (const key of this.known.keys()) {
77
57
  if (key.startsWith(`${source.id}:`))
78
58
  this.known.delete(key);
@@ -82,9 +62,12 @@ export class UsageSourceRegistry {
82
62
  return ids;
83
63
  }
84
64
  catch (error) {
85
- const id = `${source.id}:!error`;
86
- this.writeCache(id, this.errorEntry(source, id, error));
87
- return [id];
65
+ for (const key of this.known.keys()) {
66
+ if (key.startsWith(`${source.id}:`))
67
+ this.known.delete(key);
68
+ }
69
+ this.logger.warn({ sourceId: source.id, err: error }, "Usage source discovery failed");
70
+ return [];
88
71
  }
89
72
  }));
90
73
  return discovered.flat();
@@ -102,49 +85,31 @@ export class UsageSourceRegistry {
102
85
  ? `${entry.sourceLabel} (${entry.account.label})`
103
86
  : entry.sourceLabel,
104
87
  status: entry.report.status,
105
- planLabel: entry.report.planLabel ?? null,
106
- windows: entry.report.windows,
107
- balances: entry.report.balances ?? [],
108
- details: entry.report.details ?? [],
109
- error: entry.report.error ?? null,
88
+ planLabel: entry.report.status === "available" ? (entry.report.planLabel ?? null) : null,
89
+ windows: entry.report.status === "available" ? entry.report.windows : [],
90
+ balances: entry.report.status === "available" ? (entry.report.balances ?? []) : [],
91
+ details: entry.report.status === "available" ? (entry.report.details ?? []) : [],
92
+ error: legacyError(entry.report, this.now()),
110
93
  })),
111
94
  };
112
95
  }
113
- fetchId(id, forceRefresh = false) {
114
- const known = this.known.get(id);
96
+ fetchId(id, known, forceRefresh = false) {
115
97
  const cached = this.cache.get(id);
116
- if (!known)
117
- return Promise.resolve(cached.entry);
118
98
  if (!forceRefresh && cached && this.now() - cached.at < this.ttlMs)
119
99
  return Promise.resolve(cached.entry);
120
100
  const pending = this.pending.get(id);
121
101
  if (pending)
122
102
  return pending;
123
103
  const request = (async () => {
124
- let entry = {
104
+ const entry = {
125
105
  id,
126
106
  sourceId: known.source.id,
127
107
  sourceLabel: known.source.label,
128
108
  icon: known.source.icon,
129
109
  account: { label: known.label },
130
110
  fetchedAt: new Date(this.now()).toISOString(),
131
- report: { status: "unavailable", windows: [] },
111
+ report: await this.fetchWithFallback(known),
132
112
  };
133
- for (const input of known.inputs) {
134
- try {
135
- // A harness can switch accounts between discovery and a targeted refresh.
136
- const account = await known.source.identify(input);
137
- if (!account || `${known.source.id}:${account.key}` !== id)
138
- continue;
139
- const report = UsageReportSchema.parse(await known.source.fetch(input));
140
- entry.report = report;
141
- if (report.status === "available")
142
- break;
143
- }
144
- catch (error) {
145
- entry = this.errorEntry(known.source, id, error, known.label);
146
- }
147
- }
148
113
  this.writeCache(id, entry);
149
114
  return entry;
150
115
  })();
@@ -163,20 +128,50 @@ export class UsageSourceRegistry {
163
128
  }
164
129
  this.cache.set(id, { at, entry });
165
130
  }
166
- errorEntry(source, id, error, label, fetchedAt = new Date(this.now()).toISOString()) {
167
- return {
168
- id,
169
- sourceId: source.id,
170
- sourceLabel: source.label,
171
- icon: source.icon,
172
- account: { label },
173
- fetchedAt,
174
- report: {
175
- status: "error",
176
- windows: [],
177
- error: error instanceof Error ? error.message : String(error),
178
- },
179
- };
131
+ async fetchWithFallback(known) {
132
+ let report = await this.fetchLogin(known.source, known.inputs[0]);
133
+ for (const input of known.inputs.slice(1)) {
134
+ if (report.status === "available")
135
+ break;
136
+ report = await this.fetchLogin(known.source, input);
137
+ }
138
+ return report;
139
+ }
140
+ async fetchLogin(source, input) {
141
+ try {
142
+ return UsageReportSchema.parse(await source.fetch(input));
143
+ }
144
+ catch (error) {
145
+ return { status: "error", error: error instanceof Error ? error.message : String(error) };
146
+ }
180
147
  }
181
148
  }
149
+ // The legacy RPC has only an error string; the current app owns its own presentation.
150
+ // COMPAT(providerUsageList): added in v0.1.98, remove after 2027-03-26.
151
+ function legacyProblem(problem, now) {
152
+ if (problem.kind === "no_quota")
153
+ return problem.detail;
154
+ const remedy = problem.refreshedBy
155
+ ? `Run ${problem.refreshedBy} to refresh it.`
156
+ : "Sign in again.";
157
+ if (problem.kind === "rejected")
158
+ return `Login rejected (HTTP ${problem.status}). ${remedy}`;
159
+ const elapsed = Math.max(0, now - Date.parse(problem.expiresAt));
160
+ let ago = "just now";
161
+ if (elapsed >= 86400000)
162
+ ago = `${Math.floor(elapsed / 86400000)}d ago`;
163
+ else if (elapsed >= 3600000)
164
+ ago = `${Math.floor(elapsed / 3600000)}h ago`;
165
+ else if (elapsed >= 60000)
166
+ ago = `${Math.floor(elapsed / 60000)}m ago`;
167
+ return `Login expired ${ago}. ${remedy}`;
168
+ }
169
+ // COMPAT(providerUsageList): added in v0.1.98, remove after 2027-03-26.
170
+ function legacyError(report, now) {
171
+ if (report.status === "error")
172
+ return report.error;
173
+ if (report.status === "unavailable")
174
+ return legacyProblem(report.problem, now);
175
+ return null;
176
+ }
182
177
  //# sourceMappingURL=index.js.map