@tokentop/ttop 0.2.2 → 0.4.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 +42 -38
- 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 +36 -23
- package/src/plugins/notifications/visual-flash.ts +31 -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 +68 -57
- package/src/plugins/registry.ts +80 -71
- 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/persistence-service.ts +206 -0
- 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/retention.ts +146 -0
- 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 +314 -183
- 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 +320 -169
- package/src/tui/components/SettingsModal.tsx +593 -269
- 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 +164 -151
- 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 +238 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +95 -149
- 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 +7 -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 +164 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +495 -243
- package/src/tui/views/RealTimeDashboard.tsx +176 -116
- package/src/tui/views/SettingsView.tsx +225 -189
- 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,29 @@
|
|
|
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 { AgentPlugin } from "@/plugins/types/agent.ts";
|
|
18
|
+
import type { NotificationPlugin } from "@/plugins/types/notification.ts";
|
|
19
|
+
import type { Credentials, ProviderPlugin, ProviderUsageData } from "@/plugins/types/provider.ts";
|
|
20
|
+
import type { ThemePlugin } from "@/plugins/types/theme.ts";
|
|
21
|
+
import { initPricingFromPlugins } from "@/pricing/index.ts";
|
|
22
|
+
import type { ProviderSnapshotInsert } from "@/storage/types.ts";
|
|
23
|
+
import { useConfig } from "./ConfigContext.tsx";
|
|
24
|
+
import { useDemoMode } from "./DemoModeContext.tsx";
|
|
25
|
+
import { useLogs } from "./LogContext.tsx";
|
|
26
|
+
import { useStorage } from "./StorageContext.tsx";
|
|
18
27
|
|
|
19
28
|
export interface UsageSnapshot {
|
|
20
29
|
timestamp: number;
|
|
@@ -35,6 +44,7 @@ export interface ProviderState {
|
|
|
35
44
|
|
|
36
45
|
interface PluginContextValue {
|
|
37
46
|
providers: Map<string, ProviderState>;
|
|
47
|
+
agents: AgentPlugin[];
|
|
38
48
|
themes: ThemePlugin[];
|
|
39
49
|
notifications: NotificationPlugin[];
|
|
40
50
|
isInitialized: boolean;
|
|
@@ -46,16 +56,16 @@ const PluginContext = createContext<PluginContextValue | null>(null);
|
|
|
46
56
|
|
|
47
57
|
function getMaxUsagePercent(usage: ProviderUsageData): number | null {
|
|
48
58
|
if (!usage.limits) return null;
|
|
49
|
-
|
|
59
|
+
|
|
50
60
|
const items = usage.limits.items ?? [];
|
|
51
61
|
if (items.length > 0) {
|
|
52
62
|
const percents = items.map((item) => item.usedPercent).filter((p): p is number => p !== null);
|
|
53
63
|
return percents.length > 0 ? Math.max(...percents) : null;
|
|
54
64
|
}
|
|
55
|
-
|
|
65
|
+
|
|
56
66
|
const primary = usage.limits.primary?.usedPercent;
|
|
57
67
|
const secondary = usage.limits.secondary?.usedPercent;
|
|
58
|
-
|
|
68
|
+
|
|
59
69
|
if (primary !== null && primary !== undefined && secondary !== null && secondary !== undefined) {
|
|
60
70
|
return Math.max(primary, secondary);
|
|
61
71
|
}
|
|
@@ -79,6 +89,7 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
79
89
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
80
90
|
const [providers, setProviders] = useState<Map<string, ProviderState>>(new Map());
|
|
81
91
|
const [themes, setThemes] = useState<ThemePlugin[]>([]);
|
|
92
|
+
const [agents, setAgents] = useState<AgentPlugin[]>([]);
|
|
82
93
|
const [notifications, setNotifications] = useState<NotificationPlugin[]>([]);
|
|
83
94
|
const { demoMode, simulator } = useDemoMode();
|
|
84
95
|
const { debug, info, warn, error: logError } = useLogs();
|
|
@@ -88,7 +99,7 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
88
99
|
useEffect(() => {
|
|
89
100
|
async function initialize() {
|
|
90
101
|
installGlobalFetchGuard();
|
|
91
|
-
debug(
|
|
102
|
+
debug("Initializing plugin registry...", undefined, "plugins");
|
|
92
103
|
|
|
93
104
|
try {
|
|
94
105
|
if (!demoMode) {
|
|
@@ -102,44 +113,55 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
102
113
|
} else {
|
|
103
114
|
await pluginRegistry.loadBuiltinPlugins();
|
|
104
115
|
}
|
|
105
|
-
info(
|
|
116
|
+
info("Plugin registry initialized", undefined, "plugins");
|
|
106
117
|
} catch (err) {
|
|
107
|
-
logError(
|
|
118
|
+
logError("Failed to initialize plugin registry", { error: String(err) }, "plugins");
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
const providerPlugins = pluginRegistry.getAll(
|
|
111
|
-
const
|
|
112
|
-
const
|
|
121
|
+
const providerPlugins = pluginRegistry.getAll("provider");
|
|
122
|
+
const agentPlugins = pluginRegistry.getAll("agent");
|
|
123
|
+
const themePlugins = pluginRegistry.getAll("theme");
|
|
124
|
+
const notificationPlugins = pluginRegistry.getAll("notification");
|
|
113
125
|
|
|
114
126
|
initPricingFromPlugins(providerPlugins, {
|
|
115
|
-
|
|
127
|
+
"google-gemini": "google",
|
|
116
128
|
});
|
|
117
129
|
|
|
118
|
-
info(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
info(
|
|
131
|
+
`Loaded plugins`,
|
|
132
|
+
{
|
|
133
|
+
providers: providerPlugins.length,
|
|
134
|
+
themes: themePlugins.length,
|
|
135
|
+
notifications: notificationPlugins.length,
|
|
136
|
+
},
|
|
137
|
+
"plugins",
|
|
138
|
+
);
|
|
123
139
|
|
|
124
140
|
await pluginLifecycle.initializeAll();
|
|
125
141
|
await pluginLifecycle.startAll();
|
|
126
|
-
debug(
|
|
142
|
+
debug("Plugin lifecycle: all plugins initialized and started", undefined, "plugins");
|
|
127
143
|
|
|
128
|
-
debug(
|
|
144
|
+
debug("Discovering credentials...", undefined, "credentials");
|
|
129
145
|
|
|
130
146
|
const credentials = new Map<string, Credentials>();
|
|
131
147
|
if (!demoMode) {
|
|
132
|
-
await Promise.all(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
await Promise.all(
|
|
149
|
+
providerPlugins.map(async (p) => {
|
|
150
|
+
const ctx = createPluginContext(p.id, p.permissions);
|
|
151
|
+
const result = await safeInvoke(p.id, "auth.discover", () =>
|
|
152
|
+
runInPluginGuard(p.id, p.permissions, () => p.auth.discover(ctx)),
|
|
153
|
+
);
|
|
154
|
+
if (result.ok && result.value.ok && result.value.credentials) {
|
|
155
|
+
credentials.set(p.id, result.value.credentials);
|
|
156
|
+
} else if (!result.ok) {
|
|
157
|
+
logError(
|
|
158
|
+
`Credential discovery failed for ${p.id}`,
|
|
159
|
+
{ error: result.error.message },
|
|
160
|
+
"credentials",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
const providerStates = new Map<string, ProviderState>();
|
|
@@ -152,7 +174,9 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
152
174
|
if (demoMode) {
|
|
153
175
|
configured = true;
|
|
154
176
|
} else if (creds) {
|
|
155
|
-
const check = safeInvokeSync(plugin.id,
|
|
177
|
+
const check = safeInvokeSync(plugin.id, "auth.isConfigured", () =>
|
|
178
|
+
plugin.auth.isConfigured(creds),
|
|
179
|
+
);
|
|
156
180
|
configured = check.ok ? check.value : false;
|
|
157
181
|
}
|
|
158
182
|
providerStates.set(plugin.id, {
|
|
@@ -171,10 +195,14 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
171
195
|
}
|
|
172
196
|
}
|
|
173
197
|
|
|
174
|
-
info(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
info(
|
|
199
|
+
"Credential discovery complete",
|
|
200
|
+
{
|
|
201
|
+
configured: configuredIds,
|
|
202
|
+
unconfigured: unconfiguredIds,
|
|
203
|
+
},
|
|
204
|
+
"credentials",
|
|
205
|
+
);
|
|
178
206
|
|
|
179
207
|
if (demoMode && simulator) {
|
|
180
208
|
const snapshot = simulator.tick();
|
|
@@ -197,6 +225,7 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
197
225
|
notificationBus.registerPlugins(notificationPlugins);
|
|
198
226
|
|
|
199
227
|
setProviders(providerStates);
|
|
228
|
+
setAgents(agentPlugins);
|
|
200
229
|
setThemes(themePlugins);
|
|
201
230
|
setNotifications(notificationPlugins);
|
|
202
231
|
setIsInitialized(true);
|
|
@@ -216,181 +245,211 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
216
245
|
const prevStr = JSON.stringify(prev[pluginId] ?? {});
|
|
217
246
|
const nextStr = JSON.stringify(next[pluginId] ?? {});
|
|
218
247
|
if (prevStr !== nextStr) {
|
|
219
|
-
debug(`Plugin config changed for ${pluginId}`, undefined,
|
|
248
|
+
debug(`Plugin config changed for ${pluginId}`, undefined, "plugins");
|
|
220
249
|
pluginLifecycle.notifyConfigChange(pluginId, next[pluginId] ?? {});
|
|
221
250
|
}
|
|
222
251
|
}
|
|
223
252
|
prevPluginConfigRef.current = { ...next };
|
|
224
253
|
}, [config.pluginConfig, isInitialized]);
|
|
225
254
|
|
|
226
|
-
const refreshProvider = useCallback(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
255
|
+
const refreshProvider = useCallback(
|
|
256
|
+
async (providerId: string) => {
|
|
257
|
+
const state = providers.get(providerId);
|
|
258
|
+
if (!state || !state.configured) {
|
|
259
|
+
debug(`Skipping refresh for ${providerId}: not configured`, undefined, "refresh");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
232
262
|
|
|
233
|
-
|
|
263
|
+
info(`Refreshing ${providerId}...`, undefined, "refresh");
|
|
234
264
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
265
|
+
setProviders((prev) => {
|
|
266
|
+
const next = new Map(prev);
|
|
267
|
+
const current = next.get(providerId);
|
|
268
|
+
if (current) {
|
|
269
|
+
next.set(providerId, { ...current, loading: true });
|
|
270
|
+
}
|
|
271
|
+
return next;
|
|
272
|
+
});
|
|
243
273
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
274
|
+
try {
|
|
275
|
+
if (demoMode && simulator) {
|
|
276
|
+
const snapshot = simulator.tick();
|
|
277
|
+
const usage = snapshot.providerUsage.get(providerId) ?? {
|
|
278
|
+
fetchedAt: Date.now(),
|
|
279
|
+
error: "Demo provider data missing",
|
|
280
|
+
};
|
|
251
281
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
282
|
+
if (storageReady) {
|
|
283
|
+
recordProviderSnapshots([
|
|
284
|
+
{
|
|
285
|
+
timestamp: Date.now(),
|
|
286
|
+
provider: providerId,
|
|
287
|
+
usedPercent: getMaxUsagePercent(usage),
|
|
288
|
+
limitReached: usage.limitReached ?? false,
|
|
289
|
+
resetsAt: usage.limits?.primary?.resetsAt ?? null,
|
|
290
|
+
rawJson: JSON.stringify(usage),
|
|
291
|
+
},
|
|
292
|
+
]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
setProviders((prev) => {
|
|
296
|
+
const next = new Map(prev);
|
|
297
|
+
const current = next.get(providerId);
|
|
298
|
+
const currentHistory = current?.history ?? [];
|
|
299
|
+
const snapshotEntry: UsageSnapshot = {
|
|
255
300
|
timestamp: Date.now(),
|
|
301
|
+
usedPercent: getMaxUsagePercent(usage),
|
|
302
|
+
limitReached: usage.limitReached,
|
|
303
|
+
};
|
|
304
|
+
next.set(providerId, {
|
|
305
|
+
...state,
|
|
306
|
+
usage,
|
|
307
|
+
loading: false,
|
|
308
|
+
lastFetchAt: Date.now(),
|
|
309
|
+
history: addToHistory(currentHistory, snapshotEntry),
|
|
310
|
+
});
|
|
311
|
+
return next;
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const ctx = createPluginContext(providerId, state.plugin.permissions);
|
|
318
|
+
const discoverResult = await safeInvoke(providerId, "auth.discover", () =>
|
|
319
|
+
runInPluginGuard(providerId, state.plugin.permissions, () =>
|
|
320
|
+
state.plugin.auth.discover(ctx),
|
|
321
|
+
),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!discoverResult.ok) {
|
|
325
|
+
throw discoverResult.error;
|
|
326
|
+
}
|
|
327
|
+
const creds = discoverResult.value.ok ? discoverResult.value.credentials : undefined;
|
|
328
|
+
|
|
329
|
+
if (!creds) {
|
|
330
|
+
throw new Error("Credentials not found");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const http = createSandboxedHttpClient(providerId, state.plugin.permissions);
|
|
334
|
+
const logger = createPluginLogger(providerId);
|
|
335
|
+
|
|
336
|
+
const fetchResult = await safeInvoke(providerId, "fetchUsage", () =>
|
|
337
|
+
runInPluginGuard(providerId, state.plugin.permissions, () =>
|
|
338
|
+
state.plugin.fetchUsage({
|
|
339
|
+
credentials: creds,
|
|
340
|
+
http,
|
|
341
|
+
logger,
|
|
342
|
+
config: {},
|
|
343
|
+
signal: AbortSignal.timeout(30_000),
|
|
344
|
+
}),
|
|
345
|
+
),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
if (!fetchResult.ok) {
|
|
349
|
+
throw fetchResult.error;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const usage = fetchResult.value;
|
|
353
|
+
|
|
354
|
+
if (usage.error) {
|
|
355
|
+
warn(`${providerId} returned error: ${usage.error}`, undefined, "refresh");
|
|
356
|
+
} else {
|
|
357
|
+
info(
|
|
358
|
+
`${providerId} refreshed successfully`,
|
|
359
|
+
{
|
|
360
|
+
limitReached: usage.limitReached,
|
|
361
|
+
primaryUsage: usage.limits?.primary?.usedPercent,
|
|
362
|
+
},
|
|
363
|
+
"refresh",
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
if (storageReady) {
|
|
367
|
+
const now = Date.now();
|
|
368
|
+
const snapshotInsert: ProviderSnapshotInsert = {
|
|
369
|
+
timestamp: now,
|
|
256
370
|
provider: providerId,
|
|
257
371
|
usedPercent: getMaxUsagePercent(usage),
|
|
258
372
|
limitReached: usage.limitReached ?? false,
|
|
259
373
|
resetsAt: usage.limits?.primary?.resetsAt ?? null,
|
|
260
374
|
rawJson: JSON.stringify(usage),
|
|
261
|
-
}
|
|
262
|
-
|
|
375
|
+
};
|
|
376
|
+
recordProviderSnapshots([snapshotInsert]);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
notificationBus.checkProviderUsage(providerId, state.plugin.name, usage);
|
|
263
380
|
}
|
|
264
381
|
|
|
265
382
|
setProviders((prev) => {
|
|
266
383
|
const next = new Map(prev);
|
|
267
384
|
const current = next.get(providerId);
|
|
268
385
|
const currentHistory = current?.history ?? [];
|
|
269
|
-
|
|
386
|
+
|
|
387
|
+
const snapshot: UsageSnapshot = {
|
|
270
388
|
timestamp: Date.now(),
|
|
271
389
|
usedPercent: getMaxUsagePercent(usage),
|
|
272
390
|
limitReached: usage.limitReached,
|
|
273
391
|
};
|
|
392
|
+
|
|
274
393
|
next.set(providerId, {
|
|
275
394
|
...state,
|
|
276
395
|
usage,
|
|
277
396
|
loading: false,
|
|
278
397
|
lastFetchAt: Date.now(),
|
|
279
|
-
history: addToHistory(currentHistory,
|
|
398
|
+
history: addToHistory(currentHistory, snapshot),
|
|
280
399
|
});
|
|
281
400
|
return next;
|
|
282
401
|
});
|
|
402
|
+
} catch (err) {
|
|
403
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
404
|
+
logError(`Failed to refresh ${providerId}: ${errorMsg}`, undefined, "refresh");
|
|
283
405
|
|
|
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 ?? [],
|
|
406
|
+
setProviders((prev) => {
|
|
407
|
+
const next = new Map(prev);
|
|
408
|
+
const current = next.get(providerId);
|
|
409
|
+
next.set(providerId, {
|
|
410
|
+
...state,
|
|
411
|
+
usage: {
|
|
412
|
+
fetchedAt: Date.now(),
|
|
413
|
+
error: errorMsg,
|
|
414
|
+
},
|
|
415
|
+
loading: false,
|
|
416
|
+
lastFetchAt: Date.now(),
|
|
417
|
+
history: current?.history ?? [],
|
|
418
|
+
});
|
|
419
|
+
return next;
|
|
376
420
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
[
|
|
424
|
+
providers,
|
|
425
|
+
debug,
|
|
426
|
+
info,
|
|
427
|
+
warn,
|
|
428
|
+
logError,
|
|
429
|
+
storageReady,
|
|
430
|
+
recordProviderSnapshots,
|
|
431
|
+
demoMode,
|
|
432
|
+
simulator,
|
|
433
|
+
],
|
|
434
|
+
);
|
|
381
435
|
|
|
382
436
|
const refreshAllProviders = useCallback(async () => {
|
|
383
437
|
const configuredProviders = Array.from(providers.entries())
|
|
384
438
|
.filter(([_, state]) => state.configured)
|
|
385
439
|
.map(([id]) => id);
|
|
386
440
|
|
|
387
|
-
info(
|
|
441
|
+
info(
|
|
442
|
+
`Refreshing ${configuredProviders.length} providers`,
|
|
443
|
+
{ providers: configuredProviders },
|
|
444
|
+
"refresh",
|
|
445
|
+
);
|
|
388
446
|
|
|
389
447
|
await Promise.all(configuredProviders.map(refreshProvider));
|
|
390
448
|
}, [providers, refreshProvider, info]);
|
|
391
449
|
|
|
392
450
|
const value: PluginContextValue = {
|
|
393
451
|
providers,
|
|
452
|
+
agents,
|
|
394
453
|
themes,
|
|
395
454
|
notifications,
|
|
396
455
|
isInitialized,
|
|
@@ -398,17 +457,13 @@ export function PluginProvider({ children, cliPlugins }: PluginProviderProps) {
|
|
|
398
457
|
refreshAllProviders,
|
|
399
458
|
};
|
|
400
459
|
|
|
401
|
-
return
|
|
402
|
-
<PluginContext.Provider value={value}>
|
|
403
|
-
{children}
|
|
404
|
-
</PluginContext.Provider>
|
|
405
|
-
);
|
|
460
|
+
return <PluginContext.Provider value={value}>{children}</PluginContext.Provider>;
|
|
406
461
|
}
|
|
407
462
|
|
|
408
463
|
export function usePlugins(): PluginContextValue {
|
|
409
464
|
const context = useContext(PluginContext);
|
|
410
465
|
if (!context) {
|
|
411
|
-
throw new Error(
|
|
466
|
+
throw new Error("usePlugins must be used within PluginProvider");
|
|
412
467
|
}
|
|
413
468
|
return context;
|
|
414
469
|
}
|