@tokentop/ttop 0.2.2 → 0.3.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/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { pluginLifecycle } from
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { pluginLifecycle } from "@/plugins/lifecycle.ts";
|
|
11
|
+
import { notificationBus } from "@/plugins/notification-bus.ts";
|
|
12
|
+
import { createPluginContext } from "@/plugins/plugin-context-factory.ts";
|
|
13
|
+
import { safeInvoke, safeInvokeSync } from "@/plugins/plugin-host.ts";
|
|
14
|
+
import { pluginRegistry } from "@/plugins/registry.ts";
|
|
15
|
+
import { createPluginLogger, createSandboxedHttpClient } from "@/plugins/sandbox.ts";
|
|
16
|
+
import { installGlobalFetchGuard, runInPluginGuard } from "@/plugins/sandbox-guard.ts";
|
|
17
|
+
import type { NotificationPlugin } from "@/plugins/types/notification.ts";
|
|
18
|
+
import type { Credentials, ProviderPlugin, ProviderUsageData } from "@/plugins/types/provider.ts";
|
|
19
|
+
import type { ThemePlugin } from "@/plugins/types/theme.ts";
|
|
20
|
+
import { initPricingFromPlugins } from "@/pricing/index.ts";
|
|
21
|
+
import type { ProviderSnapshotInsert } from "@/storage/types.ts";
|
|
22
|
+
import { useConfig } from "./ConfigContext.tsx";
|
|
23
|
+
import { useDemoMode } from "./DemoModeContext.tsx";
|
|
24
|
+
import { useLogs } from "./LogContext.tsx";
|
|
25
|
+
import { useStorage } from "./StorageContext.tsx";
|
|
18
26
|
|
|
19
27
|
export interface UsageSnapshot {
|
|
20
28
|
timestamp: number;
|
|
@@ -46,16 +54,16 @@ const PluginContext = createContext<PluginContextValue | null>(null);
|
|
|
46
54
|
|
|
47
55
|
function getMaxUsagePercent(usage: ProviderUsageData): number | null {
|
|
48
56
|
if (!usage.limits) return null;
|
|
49
|
-
|
|
57
|
+
|
|
50
58
|
const items = usage.limits.items ?? [];
|
|
51
59
|
if (items.length > 0) {
|
|
52
60
|
const percents = items.map((item) => item.usedPercent).filter((p): p is number => p !== null);
|
|
53
61
|
return percents.length > 0 ? Math.max(...percents) : null;
|
|
54
62
|
}
|
|
55
|
-
|
|
63
|
+
|
|
56
64
|
const primary = usage.limits.primary?.usedPercent;
|
|
57
65
|
const secondary = usage.limits.secondary?.usedPercent;
|
|
58
|
-
|
|
66
|
+
|
|
59
67
|
if (primary !== null && primary !== undefined && secondary !== null && secondary !== undefined) {
|
|
60
68
|
return Math.max(primary, secondary);
|
|
61
69
|
}
|
|
@@ -88,7 +96,7 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
88
96
|
useEffect(() => {
|
|
89
97
|
async function initialize() {
|
|
90
98
|
installGlobalFetchGuard();
|
|
91
|
-
debug(
|
|
99
|
+
debug("Initializing plugin registry...", undefined, "plugins");
|
|
92
100
|
|
|
93
101
|
try {
|
|
94
102
|
if (!demoMode) {
|
|
@@ -102,44 +110,54 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
102
110
|
} else {
|
|
103
111
|
await pluginRegistry.loadBuiltinPlugins();
|
|
104
112
|
}
|
|
105
|
-
info(
|
|
113
|
+
info("Plugin registry initialized", undefined, "plugins");
|
|
106
114
|
} catch (err) {
|
|
107
|
-
logError(
|
|
115
|
+
logError("Failed to initialize plugin registry", { error: String(err) }, "plugins");
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
const providerPlugins = pluginRegistry.getAll(
|
|
111
|
-
const themePlugins = pluginRegistry.getAll(
|
|
112
|
-
const notificationPlugins = pluginRegistry.getAll(
|
|
118
|
+
const providerPlugins = pluginRegistry.getAll("provider");
|
|
119
|
+
const themePlugins = pluginRegistry.getAll("theme");
|
|
120
|
+
const notificationPlugins = pluginRegistry.getAll("notification");
|
|
113
121
|
|
|
114
122
|
initPricingFromPlugins(providerPlugins, {
|
|
115
|
-
|
|
123
|
+
"google-gemini": "google",
|
|
116
124
|
});
|
|
117
125
|
|
|
118
|
-
info(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
info(
|
|
127
|
+
`Loaded plugins`,
|
|
128
|
+
{
|
|
129
|
+
providers: providerPlugins.length,
|
|
130
|
+
themes: themePlugins.length,
|
|
131
|
+
notifications: notificationPlugins.length,
|
|
132
|
+
},
|
|
133
|
+
"plugins",
|
|
134
|
+
);
|
|
123
135
|
|
|
124
136
|
await pluginLifecycle.initializeAll();
|
|
125
137
|
await pluginLifecycle.startAll();
|
|
126
|
-
debug(
|
|
138
|
+
debug("Plugin lifecycle: all plugins initialized and started", undefined, "plugins");
|
|
127
139
|
|
|
128
|
-
debug(
|
|
140
|
+
debug("Discovering credentials...", undefined, "credentials");
|
|
129
141
|
|
|
130
142
|
const credentials = new Map<string, Credentials>();
|
|
131
143
|
if (!demoMode) {
|
|
132
|
-
await Promise.all(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
await Promise.all(
|
|
145
|
+
providerPlugins.map(async (p) => {
|
|
146
|
+
const ctx = createPluginContext(p.id, p.permissions);
|
|
147
|
+
const result = await safeInvoke(p.id, "auth.discover", () =>
|
|
148
|
+
runInPluginGuard(p.id, p.permissions, () => p.auth.discover(ctx)),
|
|
149
|
+
);
|
|
150
|
+
if (result.ok && result.value.ok && result.value.credentials) {
|
|
151
|
+
credentials.set(p.id, result.value.credentials);
|
|
152
|
+
} else if (!result.ok) {
|
|
153
|
+
logError(
|
|
154
|
+
`Credential discovery failed for ${p.id}`,
|
|
155
|
+
{ error: result.error.message },
|
|
156
|
+
"credentials",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
const providerStates = new Map<string, ProviderState>();
|
|
@@ -152,7 +170,9 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
152
170
|
if (demoMode) {
|
|
153
171
|
configured = true;
|
|
154
172
|
} else if (creds) {
|
|
155
|
-
const check = safeInvokeSync(plugin.id,
|
|
173
|
+
const check = safeInvokeSync(plugin.id, "auth.isConfigured", () =>
|
|
174
|
+
plugin.auth.isConfigured(creds),
|
|
175
|
+
);
|
|
156
176
|
configured = check.ok ? check.value : false;
|
|
157
177
|
}
|
|
158
178
|
providerStates.set(plugin.id, {
|
|
@@ -171,10 +191,14 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
171
191
|
}
|
|
172
192
|
}
|
|
173
193
|
|
|
174
|
-
info(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
info(
|
|
195
|
+
"Credential discovery complete",
|
|
196
|
+
{
|
|
197
|
+
configured: configuredIds,
|
|
198
|
+
unconfigured: unconfiguredIds,
|
|
199
|
+
},
|
|
200
|
+
"credentials",
|
|
201
|
+
);
|
|
178
202
|
|
|
179
203
|
if (demoMode && simulator) {
|
|
180
204
|
const snapshot = simulator.tick();
|
|
@@ -216,175 +240,204 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
216
240
|
const prevStr = JSON.stringify(prev[pluginId] ?? {});
|
|
217
241
|
const nextStr = JSON.stringify(next[pluginId] ?? {});
|
|
218
242
|
if (prevStr !== nextStr) {
|
|
219
|
-
debug(`Plugin config changed for ${pluginId}`, undefined,
|
|
243
|
+
debug(`Plugin config changed for ${pluginId}`, undefined, "plugins");
|
|
220
244
|
pluginLifecycle.notifyConfigChange(pluginId, next[pluginId] ?? {});
|
|
221
245
|
}
|
|
222
246
|
}
|
|
223
247
|
prevPluginConfigRef.current = { ...next };
|
|
224
248
|
}, [config.pluginConfig, isInitialized]);
|
|
225
249
|
|
|
226
|
-
const refreshProvider = useCallback(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
const refreshProvider = useCallback(
|
|
251
|
+
async (providerId: string) => {
|
|
252
|
+
const state = providers.get(providerId);
|
|
253
|
+
if (!state || !state.configured) {
|
|
254
|
+
debug(`Skipping refresh for ${providerId}: not configured`, undefined, "refresh");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
232
257
|
|
|
233
|
-
|
|
258
|
+
info(`Refreshing ${providerId}...`, undefined, "refresh");
|
|
234
259
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
setProviders((prev) => {
|
|
261
|
+
const next = new Map(prev);
|
|
262
|
+
const current = next.get(providerId);
|
|
263
|
+
if (current) {
|
|
264
|
+
next.set(providerId, { ...current, loading: true });
|
|
265
|
+
}
|
|
266
|
+
return next;
|
|
267
|
+
});
|
|
243
268
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
269
|
+
try {
|
|
270
|
+
if (demoMode && simulator) {
|
|
271
|
+
const snapshot = simulator.tick();
|
|
272
|
+
const usage = snapshot.providerUsage.get(providerId) ?? {
|
|
273
|
+
fetchedAt: Date.now(),
|
|
274
|
+
error: "Demo provider data missing",
|
|
275
|
+
};
|
|
251
276
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
277
|
+
if (storageReady) {
|
|
278
|
+
recordProviderSnapshots([
|
|
279
|
+
{
|
|
280
|
+
timestamp: Date.now(),
|
|
281
|
+
provider: providerId,
|
|
282
|
+
usedPercent: getMaxUsagePercent(usage),
|
|
283
|
+
limitReached: usage.limitReached ?? false,
|
|
284
|
+
resetsAt: usage.limits?.primary?.resetsAt ?? null,
|
|
285
|
+
rawJson: JSON.stringify(usage),
|
|
286
|
+
},
|
|
287
|
+
]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
setProviders((prev) => {
|
|
291
|
+
const next = new Map(prev);
|
|
292
|
+
const current = next.get(providerId);
|
|
293
|
+
const currentHistory = current?.history ?? [];
|
|
294
|
+
const snapshotEntry: UsageSnapshot = {
|
|
255
295
|
timestamp: Date.now(),
|
|
296
|
+
usedPercent: getMaxUsagePercent(usage),
|
|
297
|
+
limitReached: usage.limitReached,
|
|
298
|
+
};
|
|
299
|
+
next.set(providerId, {
|
|
300
|
+
...state,
|
|
301
|
+
usage,
|
|
302
|
+
loading: false,
|
|
303
|
+
lastFetchAt: Date.now(),
|
|
304
|
+
history: addToHistory(currentHistory, snapshotEntry),
|
|
305
|
+
});
|
|
306
|
+
return next;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const ctx = createPluginContext(providerId, state.plugin.permissions);
|
|
313
|
+
const discoverResult = await safeInvoke(providerId, "auth.discover", () =>
|
|
314
|
+
runInPluginGuard(providerId, state.plugin.permissions, () =>
|
|
315
|
+
state.plugin.auth.discover(ctx),
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (!discoverResult.ok) {
|
|
320
|
+
throw discoverResult.error;
|
|
321
|
+
}
|
|
322
|
+
const creds = discoverResult.value.ok ? discoverResult.value.credentials : undefined;
|
|
323
|
+
|
|
324
|
+
if (!creds) {
|
|
325
|
+
throw new Error("Credentials not found");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const http = createSandboxedHttpClient(providerId, state.plugin.permissions);
|
|
329
|
+
const logger = createPluginLogger(providerId);
|
|
330
|
+
|
|
331
|
+
const fetchResult = await safeInvoke(providerId, "fetchUsage", () =>
|
|
332
|
+
runInPluginGuard(providerId, state.plugin.permissions, () =>
|
|
333
|
+
state.plugin.fetchUsage({
|
|
334
|
+
credentials: creds,
|
|
335
|
+
http,
|
|
336
|
+
logger,
|
|
337
|
+
config: {},
|
|
338
|
+
signal: AbortSignal.timeout(30_000),
|
|
339
|
+
}),
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!fetchResult.ok) {
|
|
344
|
+
throw fetchResult.error;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const usage = fetchResult.value;
|
|
348
|
+
|
|
349
|
+
if (usage.error) {
|
|
350
|
+
warn(`${providerId} returned error: ${usage.error}`, undefined, "refresh");
|
|
351
|
+
} else {
|
|
352
|
+
info(
|
|
353
|
+
`${providerId} refreshed successfully`,
|
|
354
|
+
{
|
|
355
|
+
limitReached: usage.limitReached,
|
|
356
|
+
primaryUsage: usage.limits?.primary?.usedPercent,
|
|
357
|
+
},
|
|
358
|
+
"refresh",
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
if (storageReady) {
|
|
362
|
+
const now = Date.now();
|
|
363
|
+
const snapshotInsert: ProviderSnapshotInsert = {
|
|
364
|
+
timestamp: now,
|
|
256
365
|
provider: providerId,
|
|
257
366
|
usedPercent: getMaxUsagePercent(usage),
|
|
258
367
|
limitReached: usage.limitReached ?? false,
|
|
259
368
|
resetsAt: usage.limits?.primary?.resetsAt ?? null,
|
|
260
369
|
rawJson: JSON.stringify(usage),
|
|
261
|
-
}
|
|
262
|
-
|
|
370
|
+
};
|
|
371
|
+
recordProviderSnapshots([snapshotInsert]);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
notificationBus.checkProviderUsage(providerId, state.plugin.name, usage);
|
|
263
375
|
}
|
|
264
376
|
|
|
265
377
|
setProviders((prev) => {
|
|
266
378
|
const next = new Map(prev);
|
|
267
379
|
const current = next.get(providerId);
|
|
268
380
|
const currentHistory = current?.history ?? [];
|
|
269
|
-
|
|
381
|
+
|
|
382
|
+
const snapshot: UsageSnapshot = {
|
|
270
383
|
timestamp: Date.now(),
|
|
271
384
|
usedPercent: getMaxUsagePercent(usage),
|
|
272
385
|
limitReached: usage.limitReached,
|
|
273
386
|
};
|
|
387
|
+
|
|
274
388
|
next.set(providerId, {
|
|
275
389
|
...state,
|
|
276
390
|
usage,
|
|
277
391
|
loading: false,
|
|
278
392
|
lastFetchAt: Date.now(),
|
|
279
|
-
history: addToHistory(currentHistory,
|
|
393
|
+
history: addToHistory(currentHistory, snapshot),
|
|
280
394
|
});
|
|
281
395
|
return next;
|
|
282
396
|
});
|
|
397
|
+
} catch (err) {
|
|
398
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
399
|
+
logError(`Failed to refresh ${providerId}: ${errorMsg}`, undefined, "refresh");
|
|
283
400
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
throw new Error('Credentials not found');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const http = createSandboxedHttpClient(providerId, state.plugin.permissions);
|
|
302
|
-
const logger = createPluginLogger(providerId);
|
|
303
|
-
|
|
304
|
-
const fetchResult = await safeInvoke(providerId, 'fetchUsage', () =>
|
|
305
|
-
runInPluginGuard(providerId, state.plugin.permissions, () =>
|
|
306
|
-
state.plugin.fetchUsage({ credentials: creds, http, logger, config: {}, signal: AbortSignal.timeout(30_000) }),
|
|
307
|
-
),
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
if (!fetchResult.ok) {
|
|
311
|
-
throw fetchResult.error;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const usage = fetchResult.value;
|
|
315
|
-
|
|
316
|
-
if (usage.error) {
|
|
317
|
-
warn(`${providerId} returned error: ${usage.error}`, undefined, 'refresh');
|
|
318
|
-
} else {
|
|
319
|
-
info(`${providerId} refreshed successfully`, {
|
|
320
|
-
limitReached: usage.limitReached,
|
|
321
|
-
primaryUsage: usage.limits?.primary?.usedPercent,
|
|
322
|
-
}, 'refresh');
|
|
323
|
-
|
|
324
|
-
if (storageReady) {
|
|
325
|
-
const now = Date.now();
|
|
326
|
-
const snapshotInsert: ProviderSnapshotInsert = {
|
|
327
|
-
timestamp: now,
|
|
328
|
-
provider: providerId,
|
|
329
|
-
usedPercent: getMaxUsagePercent(usage),
|
|
330
|
-
limitReached: usage.limitReached ?? false,
|
|
331
|
-
resetsAt: usage.limits?.primary?.resetsAt ?? null,
|
|
332
|
-
rawJson: JSON.stringify(usage),
|
|
333
|
-
};
|
|
334
|
-
recordProviderSnapshots([snapshotInsert]);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
notificationBus.checkProviderUsage(providerId, state.plugin.name, usage);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
setProviders((prev) => {
|
|
341
|
-
const next = new Map(prev);
|
|
342
|
-
const current = next.get(providerId);
|
|
343
|
-
const currentHistory = current?.history ?? [];
|
|
344
|
-
|
|
345
|
-
const snapshot: UsageSnapshot = {
|
|
346
|
-
timestamp: Date.now(),
|
|
347
|
-
usedPercent: getMaxUsagePercent(usage),
|
|
348
|
-
limitReached: usage.limitReached,
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
next.set(providerId, {
|
|
352
|
-
...state,
|
|
353
|
-
usage,
|
|
354
|
-
loading: false,
|
|
355
|
-
lastFetchAt: Date.now(),
|
|
356
|
-
history: addToHistory(currentHistory, snapshot),
|
|
357
|
-
});
|
|
358
|
-
return next;
|
|
359
|
-
});
|
|
360
|
-
} catch (err) {
|
|
361
|
-
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
362
|
-
logError(`Failed to refresh ${providerId}: ${errorMsg}`, undefined, 'refresh');
|
|
363
|
-
|
|
364
|
-
setProviders((prev) => {
|
|
365
|
-
const next = new Map(prev);
|
|
366
|
-
const current = next.get(providerId);
|
|
367
|
-
next.set(providerId, {
|
|
368
|
-
...state,
|
|
369
|
-
usage: {
|
|
370
|
-
fetchedAt: Date.now(),
|
|
371
|
-
error: errorMsg,
|
|
372
|
-
},
|
|
373
|
-
loading: false,
|
|
374
|
-
lastFetchAt: Date.now(),
|
|
375
|
-
history: current?.history ?? [],
|
|
401
|
+
setProviders((prev) => {
|
|
402
|
+
const next = new Map(prev);
|
|
403
|
+
const current = next.get(providerId);
|
|
404
|
+
next.set(providerId, {
|
|
405
|
+
...state,
|
|
406
|
+
usage: {
|
|
407
|
+
fetchedAt: Date.now(),
|
|
408
|
+
error: errorMsg,
|
|
409
|
+
},
|
|
410
|
+
loading: false,
|
|
411
|
+
lastFetchAt: Date.now(),
|
|
412
|
+
history: current?.history ?? [],
|
|
413
|
+
});
|
|
414
|
+
return next;
|
|
376
415
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
[
|
|
419
|
+
providers,
|
|
420
|
+
debug,
|
|
421
|
+
info,
|
|
422
|
+
warn,
|
|
423
|
+
logError,
|
|
424
|
+
storageReady,
|
|
425
|
+
recordProviderSnapshots,
|
|
426
|
+
demoMode,
|
|
427
|
+
simulator,
|
|
428
|
+
],
|
|
429
|
+
);
|
|
381
430
|
|
|
382
431
|
const refreshAllProviders = useCallback(async () => {
|
|
383
432
|
const configuredProviders = Array.from(providers.entries())
|
|
384
433
|
.filter(([_, state]) => state.configured)
|
|
385
434
|
.map(([id]) => id);
|
|
386
435
|
|
|
387
|
-
info(
|
|
436
|
+
info(
|
|
437
|
+
`Refreshing ${configuredProviders.length} providers`,
|
|
438
|
+
{ providers: configuredProviders },
|
|
439
|
+
"refresh",
|
|
440
|
+
);
|
|
388
441
|
|
|
389
442
|
await Promise.all(configuredProviders.map(refreshProvider));
|
|
390
443
|
}, [providers, refreshProvider, info]);
|
|
@@ -398,17 +451,13 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
398
451
|
refreshAllProviders,
|
|
399
452
|
};
|
|
400
453
|
|
|
401
|
-
return
|
|
402
|
-
<PluginContext.Provider value={value}>
|
|
403
|
-
{children}
|
|
404
|
-
</PluginContext.Provider>
|
|
405
|
-
);
|
|
454
|
+
return <PluginContext.Provider value={value}>{children}</PluginContext.Provider>;
|
|
406
455
|
}
|
|
407
456
|
|
|
408
457
|
export function usePlugins(): PluginContextValue {
|
|
409
458
|
const context = useContext(PluginContext);
|
|
410
459
|
if (!context) {
|
|
411
|
-
throw new Error(
|
|
460
|
+
throw new Error("usePlugins must be used within PluginProvider");
|
|
412
461
|
}
|
|
413
462
|
return context;
|
|
414
463
|
}
|