@aliou/pi-synthetic 0.15.0 → 0.16.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-synthetic",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { AuthStorage, ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
2
  import {
3
3
  configLoader,
4
4
  emitSyntheticConfigUpdated,
@@ -12,6 +12,18 @@ import {
12
12
  type SyntheticFeatureId,
13
13
  seedSyntheticConfigIfMissing,
14
14
  } from "../../config";
15
+ import { getSyntheticApiKey } from "../../lib/env";
16
+ import { QuotaStore } from "../../services/quota-store";
17
+ import {
18
+ parseQuotaHeader,
19
+ type QuotasResponse,
20
+ SYNTHETIC_QUOTAS_READ_EVENT,
21
+ SYNTHETIC_QUOTAS_REQUEST_EVENT,
22
+ SYNTHETIC_QUOTAS_UPDATED_EVENT,
23
+ type SyntheticQuotasReadPayload,
24
+ type SyntheticQuotasRequestPayload,
25
+ } from "../../types/quotas";
26
+ import { fetchQuotas } from "../../utils/quotas";
15
27
  import { SYNTHETIC_MODELS } from "./models";
16
28
 
17
29
  export function buildSyntheticProviderModels(includeProxiedModels: boolean) {
@@ -71,6 +83,54 @@ export default async function (pi: ExtensionAPI) {
71
83
  getLoadedFeatures: () => loadedFeatures,
72
84
  });
73
85
 
86
+ const quotaStore = new QuotaStore();
87
+ let currentAuthStorage: AuthStorage | undefined;
88
+
89
+ async function fetchQuotasFromAuth(): Promise<QuotasResponse | undefined> {
90
+ if (!currentAuthStorage) return undefined;
91
+ const apiKey = await getSyntheticApiKey(currentAuthStorage);
92
+ if (!apiKey) return undefined;
93
+ const result = await fetchQuotas(apiKey);
94
+ return result.success ? result.data.quotas : undefined;
95
+ }
96
+
97
+ quotaStore.subscribe((snapshot) => {
98
+ pi.events.emit(SYNTHETIC_QUOTAS_UPDATED_EVENT, {
99
+ quotas: snapshot.quotas,
100
+ source: snapshot.source,
101
+ updatedAt: snapshot.updatedAt,
102
+ });
103
+ });
104
+
105
+ pi.on("after_provider_response", (event, ctx) => {
106
+ if (ctx.model?.provider !== "synthetic") return;
107
+ const quotas = parseQuotaHeader(event.headers);
108
+ if (quotas) quotaStore.ingest(quotas, "header");
109
+ });
110
+
111
+ pi.events.on(SYNTHETIC_QUOTAS_REQUEST_EVENT, async (data: unknown) => {
112
+ const payload = data as SyntheticQuotasRequestPayload | undefined;
113
+ const snapshot = await quotaStore.refreshFromApi(fetchQuotasFromAuth);
114
+ if (payload?.respond) {
115
+ payload.respond(snapshot);
116
+ }
117
+ });
118
+
119
+ pi.events.on(SYNTHETIC_QUOTAS_READ_EVENT, (data: unknown) => {
120
+ const { respond } = data as SyntheticQuotasReadPayload;
121
+ respond(quotaStore.getSnapshot());
122
+ });
123
+
124
+ pi.on("session_before_switch", () => {
125
+ quotaStore.clear();
126
+ currentAuthStorage = undefined;
127
+ });
128
+
129
+ pi.on("session_shutdown", () => {
130
+ quotaStore.clear();
131
+ currentAuthStorage = undefined;
132
+ });
133
+
74
134
  pi.on("session_start", async (_event, ctx) => {
75
135
  const messages = pendingMessages.splice(0).map((m) => `- ${m}`);
76
136
  if (messages.length > 0) {
@@ -81,7 +141,13 @@ export default async function (pi: ExtensionAPI) {
81
141
  }
82
142
 
83
143
  loadedFeatures.clear();
144
+ quotaStore.clear();
145
+ currentAuthStorage = ctx.modelRegistry.authStorage;
84
146
  pi.events.emit(SYNTHETIC_EXTENSIONS_REQUEST_EVENT, undefined);
85
147
  emitSyntheticConfigUpdated(pi);
148
+
149
+ if (ctx.model?.provider === "synthetic") {
150
+ await quotaStore.refreshFromApi(fetchQuotasFromAuth);
151
+ }
86
152
  });
87
153
  }
@@ -1,4 +1,7 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@mariozechner/pi-coding-agent";
2
5
  import {
3
6
  configLoader,
4
7
  SYNTHETIC_CONFIG_UPDATED_EVENT,
@@ -6,48 +9,100 @@ import {
6
9
  SYNTHETIC_EXTENSIONS_REQUEST_EVENT,
7
10
  type SyntheticConfigUpdatedPayload,
8
11
  } from "../../config";
9
- import { clearAlertState, triggerCheck } from "./notifier";
12
+ import { QuotaWarningNotifier } from "../../services/quota-warnings";
13
+ import {
14
+ SYNTHETIC_QUOTAS_READ_EVENT,
15
+ SYNTHETIC_QUOTAS_REQUEST_EVENT,
16
+ type SyntheticQuotasReadPayload,
17
+ type SyntheticQuotasRequestPayload,
18
+ type SyntheticQuotasSnapshotPayload,
19
+ } from "../../types/quotas";
10
20
 
11
21
  export default async function (pi: ExtensionAPI) {
12
22
  await configLoader.load();
13
23
 
14
24
  let enabled = configLoader.getConfig().quotaWarnings;
15
- let currentModel: { provider: string; id: string } | undefined;
16
- let currentContext: Parameters<typeof triggerCheck>[0] | undefined;
25
+
26
+ const notifier = new QuotaWarningNotifier();
27
+
28
+ function requestQuotas(
29
+ respond?: (snapshot: SyntheticQuotasSnapshotPayload | undefined) => void,
30
+ ): void {
31
+ pi.events.emit(SYNTHETIC_QUOTAS_REQUEST_EVENT, {
32
+ respond,
33
+ } satisfies SyntheticQuotasRequestPayload);
34
+ }
35
+
36
+ function readQuotas(
37
+ respond: (snapshot: SyntheticQuotasSnapshotPayload | undefined) => void,
38
+ ): void {
39
+ pi.events.emit(SYNTHETIC_QUOTAS_READ_EVENT, {
40
+ respond,
41
+ } satisfies SyntheticQuotasReadPayload);
42
+ }
43
+
44
+ function evaluateFromStoreOrRefresh(ctx: ExtensionContext): void {
45
+ if (!enabled || ctx.model?.provider !== "synthetic") return;
46
+ readQuotas((snapshot) => {
47
+ if (snapshot) {
48
+ notifier.evaluate(
49
+ snapshot.quotas,
50
+ snapshot.source === "header",
51
+ (message, level) => {
52
+ ctx.ui.notify(message, level);
53
+ },
54
+ );
55
+ } else {
56
+ requestQuotas((refreshed) => {
57
+ if (!refreshed) return;
58
+ notifier.evaluate(
59
+ refreshed.quotas,
60
+ refreshed.source === "header",
61
+ (message, level) => {
62
+ ctx.ui.notify(message, level);
63
+ },
64
+ );
65
+ });
66
+ }
67
+ });
68
+ }
17
69
 
18
70
  pi.events.on(SYNTHETIC_CONFIG_UPDATED_EVENT, (data: unknown) => {
19
71
  enabled = (data as SyntheticConfigUpdatedPayload).config.quotaWarnings;
20
72
 
21
73
  if (!enabled) {
22
- clearAlertState();
74
+ notifier.clearAlertState();
23
75
  return;
24
76
  }
25
77
 
26
- if (currentContext && currentModel?.provider === "synthetic") {
27
- clearAlertState();
28
- triggerCheck(currentContext, currentModel, false);
29
- }
78
+ notifier.clearAlertState();
79
+ // In config updates we don't have ctx, so we just clear. The next lifecycle event will refresh.
30
80
  });
31
81
 
32
- pi.on("session_start", async (_event, ctx) => {
33
- currentContext = ctx;
34
- currentModel = ctx.model;
35
- if (!enabled || ctx.model?.provider !== "synthetic") return;
36
- clearAlertState();
37
- triggerCheck(ctx, ctx.model, false);
82
+ pi.on("session_start", (_event, ctx) => {
83
+ notifier.clearAlertState();
84
+ evaluateFromStoreOrRefresh(ctx);
38
85
  });
39
86
 
40
- pi.on("agent_end", async (_event, ctx) => {
41
- currentContext = ctx;
42
- currentModel = ctx.model;
43
- if (!enabled || ctx.model?.provider !== "synthetic") return;
44
- triggerCheck(ctx, ctx.model, true);
87
+ pi.on("model_select", (_event, ctx) => {
88
+ notifier.clearAlertState();
89
+ evaluateFromStoreOrRefresh(ctx);
90
+ });
91
+
92
+ pi.on("agent_end", (_event, ctx) => {
93
+ evaluateFromStoreOrRefresh(ctx);
94
+ });
95
+
96
+ pi.on("turn_end", (_event, ctx) => {
97
+ evaluateFromStoreOrRefresh(ctx);
98
+ });
99
+
100
+ pi.on("session_before_switch", () => {
101
+ notifier.clearAlertState();
45
102
  });
46
103
 
47
- pi.on("session_shutdown", async () => {
48
- currentContext = undefined;
49
- currentModel = undefined;
50
- clearAlertState();
104
+ pi.on("session_shutdown", () => {
105
+ notifier.clearAlertState();
51
106
  });
52
107
 
53
108
  pi.events.on(SYNTHETIC_EXTENSIONS_REQUEST_EVENT, () => {
@@ -1,4 +1,4 @@
1
- import type { AuthStorage, ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
2
  import {
3
3
  configLoader,
4
4
  SYNTHETIC_CONFIG_UPDATED_EVENT,
@@ -6,9 +6,13 @@ import {
6
6
  SYNTHETIC_EXTENSIONS_REQUEST_EVENT,
7
7
  type SyntheticConfigUpdatedPayload,
8
8
  } from "../../config";
9
- import { getSyntheticApiKey } from "../../lib/env";
10
- import type { QuotasResponse } from "../../types/quotas";
11
- import { fetchQuotas, formatResetTime } from "../../utils/quotas";
9
+ import {
10
+ type QuotasResponse,
11
+ SYNTHETIC_QUOTAS_REQUEST_EVENT,
12
+ SYNTHETIC_QUOTAS_UPDATED_EVENT,
13
+ type SyntheticQuotasUpdatedPayload,
14
+ } from "../../types/quotas";
15
+ import { formatResetTime } from "../../utils/quotas";
12
16
 
13
17
  interface RateWindow {
14
18
  label: string;
@@ -24,14 +28,6 @@ interface UsageSnapshot {
24
28
  lastSuccessAt?: number;
25
29
  }
26
30
 
27
- interface SubCoreSettingsPayload {
28
- settings?: {
29
- behavior?: {
30
- refreshInterval: number;
31
- };
32
- };
33
- }
34
-
35
31
  function toUsageSnapshot(quotas: QuotasResponse): UsageSnapshot {
36
32
  const windows: RateWindow[] = [];
37
33
 
@@ -105,64 +101,42 @@ function toUsageSnapshot(quotas: QuotasResponse): UsageSnapshot {
105
101
  };
106
102
  }
107
103
 
108
- async function emitCurrentUsage(
109
- pi: ExtensionAPI,
110
- authStorage: AuthStorage,
111
- ): Promise<void> {
112
- const apiKey = await getSyntheticApiKey(authStorage);
113
- if (!apiKey) return;
114
- const result = await fetchQuotas(apiKey);
115
- if (!result.success) return;
116
- pi.events.emit("sub-core:update-current", {
117
- state: {
118
- provider: "synthetic",
119
- usage: toUsageSnapshot(result.data.quotas),
120
- },
121
- });
122
- }
123
-
124
104
  export function registerSubBarIntegration(pi: ExtensionAPI): void {
125
- let interval: NodeJS.Timeout | undefined;
126
- let refreshMs = 60000;
127
105
  let subCoreReady = false;
128
106
  let currentProvider: string | undefined;
129
- let currentAuthStorage: AuthStorage | undefined;
130
107
  let enabled = configLoader.getConfig().subBarIntegration;
131
108
 
132
109
  function isSynthetic(): boolean {
133
110
  return enabled && currentProvider === "synthetic";
134
111
  }
135
112
 
136
- function stop(): void {
137
- if (interval) {
138
- clearInterval(interval);
139
- interval = undefined;
140
- }
113
+ function emitUsage(quotas: QuotasResponse): void {
114
+ pi.events.emit("sub-core:update-current", {
115
+ state: {
116
+ provider: "synthetic",
117
+ usage: toUsageSnapshot(quotas),
118
+ },
119
+ });
141
120
  }
142
121
 
143
- function startPolling(authStorage: AuthStorage): void {
144
- stop();
145
- currentAuthStorage = authStorage;
146
- void emitCurrentUsage(pi, authStorage);
147
- const ms = Math.max(10000, refreshMs);
148
- interval = setInterval(() => {
149
- if (isSynthetic() && currentAuthStorage) {
150
- void emitCurrentUsage(pi, currentAuthStorage);
151
- }
152
- }, ms);
153
- interval.unref?.();
122
+ function requestQuotas(): void {
123
+ pi.events.emit(SYNTHETIC_QUOTAS_REQUEST_EVENT, undefined);
154
124
  }
155
125
 
126
+ // Receive quota updates from the provider extension
127
+ pi.events.on(SYNTHETIC_QUOTAS_UPDATED_EVENT, (data: unknown) => {
128
+ if (!isSynthetic() || !subCoreReady) return;
129
+ const { quotas } = data as SyntheticQuotasUpdatedPayload;
130
+ emitUsage(quotas);
131
+ });
132
+
156
133
  pi.events.on(SYNTHETIC_CONFIG_UPDATED_EVENT, (data: unknown) => {
157
134
  enabled = (data as SyntheticConfigUpdatedPayload).config.subBarIntegration;
158
135
 
159
- if (!enabled) {
160
- stop();
161
- return;
162
- }
136
+ if (!enabled) return;
163
137
 
164
- if (subCoreReady && currentAuthStorage && currentProvider === "synthetic") {
165
- startPolling(currentAuthStorage);
138
+ if (subCoreReady && currentProvider === "synthetic") {
139
+ requestQuotas();
166
140
  }
167
141
  });
168
142
 
@@ -170,48 +144,24 @@ export function registerSubBarIntegration(pi: ExtensionAPI): void {
170
144
  subCoreReady = true;
171
145
  });
172
146
 
173
- pi.events.on("sub-core:settings:updated", (data: unknown) => {
174
- const payload = data as SubCoreSettingsPayload;
175
- if (payload.settings?.behavior?.refreshInterval) {
176
- refreshMs = payload.settings.behavior.refreshInterval * 1000;
177
- if (interval && isSynthetic() && currentAuthStorage) {
178
- startPolling(currentAuthStorage);
179
- }
180
- }
147
+ pi.on("session_start", async (_event, ctx) => {
148
+ currentProvider = ctx.model?.provider;
181
149
  });
182
150
 
183
- pi.on("session_start", async (_event, ctx) => {
151
+ pi.on("model_select", async (_event, ctx) => {
184
152
  currentProvider = ctx.model?.provider;
185
- currentAuthStorage = ctx.modelRegistry.authStorage;
186
153
 
187
154
  if (subCoreReady && isSynthetic()) {
188
- const apiKey = await getSyntheticApiKey(currentAuthStorage);
189
- if (apiKey) {
190
- startPolling(currentAuthStorage);
191
- }
155
+ requestQuotas();
192
156
  }
193
157
  });
194
158
 
195
- pi.on("model_select", async (event, ctx) => {
196
- currentProvider = event.model?.provider;
197
- currentAuthStorage = ctx.modelRegistry.authStorage;
198
-
199
- if (subCoreReady && isSynthetic()) {
200
- const apiKey = await getSyntheticApiKey(currentAuthStorage);
201
- if (apiKey) {
202
- startPolling(currentAuthStorage);
203
- } else {
204
- stop();
205
- }
206
- } else {
207
- stop();
208
- }
159
+ pi.on("session_before_switch", (_event, ctx) => {
160
+ currentProvider = ctx.model?.provider;
209
161
  });
210
162
 
211
163
  pi.on("session_shutdown", () => {
212
164
  currentProvider = undefined;
213
- currentAuthStorage = undefined;
214
- stop();
215
165
  });
216
166
  }
217
167
 
@@ -9,9 +9,15 @@ import {
9
9
  SYNTHETIC_EXTENSIONS_REQUEST_EVENT,
10
10
  type SyntheticConfigUpdatedPayload,
11
11
  } from "../../config";
12
- import { getSyntheticApiKey } from "../../lib/env";
13
- import type { QuotasResponse } from "../../types/quotas";
14
- import { fetchQuotas, formatResetTime } from "../../utils/quotas";
12
+ import {
13
+ type QuotasResponse,
14
+ SYNTHETIC_QUOTAS_READ_EVENT,
15
+ SYNTHETIC_QUOTAS_REQUEST_EVENT,
16
+ type SyntheticQuotasReadPayload,
17
+ type SyntheticQuotasRequestPayload,
18
+ type SyntheticQuotasSnapshotPayload,
19
+ } from "../../types/quotas";
20
+ import { formatResetTime } from "../../utils/quotas";
15
21
  import {
16
22
  assessWindow,
17
23
  getSeverityColor,
@@ -20,7 +26,6 @@ import {
20
26
  } from "../../utils/quotas-severity";
21
27
 
22
28
  const EXTENSION_ID = "synthetic-usage";
23
- const REFRESH_INTERVAL_MS = 60_000;
24
29
 
25
30
  type WindowStatus = {
26
31
  label: string;
@@ -73,175 +78,95 @@ function formatStatus(ctx: ExtensionContext, windows: WindowStatus[]): string {
73
78
  return parts.join(" ");
74
79
  }
75
80
 
76
- function createStatusRefresher() {
77
- let refreshTimer: ReturnType<typeof setInterval> | undefined;
78
- let activeContext: ExtensionContext | undefined;
79
- let isRefreshInFlight = false;
80
- let queuedRefresh = false;
81
- let lastSnapshot: WindowStatus[] | undefined;
81
+ export default async function (pi: ExtensionAPI) {
82
+ await configLoader.load();
82
83
 
83
- async function updateFooterStatus(ctx: ExtensionContext): Promise<void> {
84
- if (!ctx.hasUI) return;
85
- if (isRefreshInFlight) {
86
- queuedRefresh = true;
87
- return;
88
- }
89
- isRefreshInFlight = true;
90
- try {
91
- const apiKey = await getSyntheticApiKey(ctx.modelRegistry.authStorage);
92
- if (!apiKey) {
93
- lastSnapshot = undefined;
94
- ctx.ui.setStatus(EXTENSION_ID, undefined);
95
- return;
96
- }
97
- const result = await fetchQuotas(apiKey);
98
- if (!result.success) {
99
- ctx.ui.setStatus(
100
- EXTENSION_ID,
101
- ctx.ui.theme.fg("warning", "usage unavailable"),
102
- );
103
- return;
104
- }
105
- const windows = parseSnapshot(result.data.quotas);
106
- lastSnapshot = windows;
107
- if (windows.length === 0) {
108
- ctx.ui.setStatus(EXTENSION_ID, undefined);
109
- return;
110
- }
111
- ctx.ui.setStatus(EXTENSION_ID, formatStatus(ctx, windows));
112
- } catch {
113
- ctx.ui.setStatus(
114
- EXTENSION_ID,
115
- ctx.ui.theme.fg("warning", "usage unavailable"),
116
- );
117
- } finally {
118
- isRefreshInFlight = false;
119
- if (queuedRefresh) {
120
- queuedRefresh = false;
121
- void updateFooterStatus(ctx);
122
- }
123
- }
124
- }
84
+ let enabled = configLoader.getConfig().usageStatus;
125
85
 
126
- function refreshFor(ctx: ExtensionContext): Promise<void> {
127
- activeContext = ctx;
128
- return updateFooterStatus(ctx);
86
+ function requestQuotas(
87
+ respond: (snapshot: SyntheticQuotasSnapshotPayload | undefined) => void,
88
+ ): void {
89
+ pi.events.emit(SYNTHETIC_QUOTAS_REQUEST_EVENT, {
90
+ respond,
91
+ } satisfies SyntheticQuotasRequestPayload);
129
92
  }
130
93
 
131
- function startAutoRefresh(): void {
132
- if (refreshTimer) clearInterval(refreshTimer);
133
- refreshTimer = setInterval(() => {
134
- if (!activeContext) return;
135
- void updateFooterStatus(activeContext);
136
- }, REFRESH_INTERVAL_MS);
137
- refreshTimer.unref?.();
94
+ function readQuotas(
95
+ respond: (snapshot: SyntheticQuotasSnapshotPayload | undefined) => void,
96
+ ): void {
97
+ pi.events.emit(SYNTHETIC_QUOTAS_READ_EVENT, {
98
+ respond,
99
+ } satisfies SyntheticQuotasReadPayload);
138
100
  }
139
101
 
140
- function stopAutoRefresh(ctx?: ExtensionContext): void {
141
- if (refreshTimer) {
142
- clearInterval(refreshTimer);
143
- refreshTimer = undefined;
102
+ function renderSnapshot(
103
+ ctx: ExtensionContext,
104
+ snapshot: SyntheticQuotasSnapshotPayload | undefined,
105
+ ): void {
106
+ if (!ctx.hasUI) return;
107
+ if (!snapshot) {
108
+ ctx.ui.setStatus(
109
+ EXTENSION_ID,
110
+ ctx.ui.theme.fg("dim", "loading usage..."),
111
+ );
112
+ return;
144
113
  }
145
- ctx?.ui.setStatus(EXTENSION_ID, undefined);
146
- }
147
114
 
148
- async function setLoadingStatus(ctx: ExtensionContext): Promise<void> {
149
- if (!ctx.hasUI) return;
150
- const apiKey = await getSyntheticApiKey(
151
- ctx.modelRegistry.authStorage,
152
- ).catch(() => undefined);
153
- if (!apiKey) {
115
+ const windows = parseSnapshot(snapshot.quotas);
116
+ if (windows.length === 0) {
154
117
  ctx.ui.setStatus(EXTENSION_ID, undefined);
155
118
  return;
156
119
  }
157
- ctx.ui.setStatus(EXTENSION_ID, ctx.ui.theme.fg("dim", "loading usage..."));
158
- }
159
120
 
160
- function renderFromLastSnapshot(ctx: ExtensionContext): boolean {
161
- if (!ctx.hasUI || !lastSnapshot) return false;
162
- ctx.ui.setStatus(EXTENSION_ID, formatStatus(ctx, lastSnapshot));
163
- return true;
121
+ ctx.ui.setStatus(EXTENSION_ID, formatStatus(ctx, windows));
164
122
  }
165
123
 
166
- return {
167
- refreshFor,
168
- startAutoRefresh,
169
- stopAutoRefresh,
170
- setLoadingStatus,
171
- renderFromLastSnapshot,
172
- };
173
- }
174
-
175
- export default async function (pi: ExtensionAPI) {
176
- await configLoader.load();
124
+ function clearStatus(ctx: ExtensionContext): void {
125
+ if (!ctx.hasUI) return;
126
+ ctx.ui.setStatus(EXTENSION_ID, undefined);
127
+ }
177
128
 
178
- const refresher = createStatusRefresher();
179
- let enabled = configLoader.getConfig().usageStatus;
180
- let currentContext: ExtensionContext | undefined;
181
- let currentProvider: string | undefined;
129
+ function renderFromStoreOrRefresh(ctx: ExtensionContext): void {
130
+ if (!enabled || ctx.model?.provider !== "synthetic") {
131
+ clearStatus(ctx);
132
+ return;
133
+ }
134
+ readQuotas((snapshot) => {
135
+ if (snapshot) {
136
+ renderSnapshot(ctx, snapshot);
137
+ } else {
138
+ renderSnapshot(ctx, undefined); // show loading
139
+ requestQuotas((refreshed) => renderSnapshot(ctx, refreshed));
140
+ }
141
+ });
142
+ }
182
143
 
183
144
  pi.events.on(SYNTHETIC_CONFIG_UPDATED_EVENT, (data: unknown) => {
184
145
  enabled = (data as SyntheticConfigUpdatedPayload).config.usageStatus;
146
+ });
185
147
 
186
- if (!enabled) {
187
- refresher.stopAutoRefresh(currentContext);
188
- return;
189
- }
190
-
191
- if (currentContext && currentProvider === "synthetic") {
192
- refresher.startAutoRefresh();
193
- void refresher.refreshFor(currentContext);
194
- }
148
+ pi.on("session_start", (_event, ctx) => {
149
+ renderFromStoreOrRefresh(ctx);
195
150
  });
196
151
 
197
- pi.on("session_start", async (_event, ctx) => {
198
- currentContext = ctx;
199
- currentProvider = ctx.model?.provider;
200
- if (!enabled || ctx.model?.provider !== "synthetic") return;
201
- refresher.startAutoRefresh();
202
- await refresher.setLoadingStatus(ctx);
203
- await refresher.refreshFor(ctx);
152
+ pi.on("model_select", (_event, ctx) => {
153
+ renderFromStoreOrRefresh(ctx);
204
154
  });
205
155
 
206
- pi.on("turn_end", (_event, ctx) => {
207
- currentContext = ctx;
208
- currentProvider = ctx.model?.provider;
209
- if (!enabled || ctx.model?.provider !== "synthetic") return;
210
- void refresher.refreshFor(ctx);
156
+ pi.on("agent_end", (_event, ctx) => {
157
+ renderFromStoreOrRefresh(ctx);
211
158
  });
212
159
 
213
- pi.on("session_start", (event, ctx) => {
214
- // Handle session switches (model_select handles mid-session provider changes)
215
- if (
216
- event.reason === "new" ||
217
- event.reason === "resume" ||
218
- event.reason === "fork"
219
- ) {
220
- currentContext = ctx;
221
- currentProvider = ctx.model?.provider;
222
- if (enabled && ctx.model?.provider === "synthetic") {
223
- void refresher.refreshFor(ctx);
224
- } else {
225
- refresher.stopAutoRefresh(ctx);
226
- }
227
- }
160
+ pi.on("turn_end", (_event, ctx) => {
161
+ renderFromStoreOrRefresh(ctx);
228
162
  });
229
163
 
230
- pi.on("model_select", (_event, ctx) => {
231
- currentContext = ctx;
232
- currentProvider = ctx.model?.provider;
233
- if (enabled && ctx.model?.provider === "synthetic") {
234
- refresher.startAutoRefresh();
235
- void refresher.refreshFor(ctx);
236
- } else {
237
- refresher.stopAutoRefresh(ctx);
238
- }
164
+ pi.on("session_before_switch", (_event, ctx) => {
165
+ clearStatus(ctx);
239
166
  });
240
167
 
241
168
  pi.on("session_shutdown", (_event, ctx) => {
242
- currentContext = undefined;
243
- currentProvider = undefined;
244
- refresher.stopAutoRefresh(ctx);
169
+ clearStatus(ctx);
245
170
  });
246
171
 
247
172
  pi.events.on(SYNTHETIC_EXTENSIONS_REQUEST_EVENT, () => {