@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
package/src/config/schema.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { PATHS } from
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { PATHS } from "@/storage/paths.ts";
|
|
4
4
|
|
|
5
|
-
export type TimeWindow =
|
|
5
|
+
export type TimeWindow = "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "all";
|
|
6
6
|
|
|
7
7
|
export interface BudgetConfig {
|
|
8
8
|
daily: number | null;
|
|
9
9
|
weekly: number | null;
|
|
10
10
|
monthly: number | null;
|
|
11
|
-
currency:
|
|
11
|
+
currency: "USD" | "EUR" | "GBP";
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface AlertThresholds {
|
|
@@ -16,8 +16,8 @@ export interface AlertThresholds {
|
|
|
16
16
|
criticalPercent: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export type SparklineStyle =
|
|
20
|
-
export type SparklineOrientation =
|
|
19
|
+
export type SparklineStyle = "braille" | "block";
|
|
20
|
+
export type SparklineOrientation = "up" | "down";
|
|
21
21
|
|
|
22
22
|
export interface SparklineConfig {
|
|
23
23
|
style: SparklineStyle;
|
|
@@ -41,11 +41,11 @@ export interface AppConfig {
|
|
|
41
41
|
display: {
|
|
42
42
|
defaultTimeWindow: TimeWindow;
|
|
43
43
|
sidebarCollapsed: boolean;
|
|
44
|
-
timeFormat:
|
|
45
|
-
numberFormat:
|
|
44
|
+
timeFormat: "12h" | "24h";
|
|
45
|
+
numberFormat: "full" | "compact";
|
|
46
46
|
sparkline: SparklineConfig;
|
|
47
47
|
theme: string;
|
|
48
|
-
colorScheme:
|
|
48
|
+
colorScheme: "auto" | "light" | "dark";
|
|
49
49
|
};
|
|
50
50
|
notifications: {
|
|
51
51
|
toastsEnabled: boolean;
|
|
@@ -67,19 +67,19 @@ export const DEFAULT_CONFIG: AppConfig = {
|
|
|
67
67
|
pauseAutoRefresh: false,
|
|
68
68
|
staleThresholdMs: 300000,
|
|
69
69
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
theme: 'tokyo-night',
|
|
81
|
-
colorScheme: 'auto',
|
|
70
|
+
display: {
|
|
71
|
+
defaultTimeWindow: "1h",
|
|
72
|
+
sidebarCollapsed: false,
|
|
73
|
+
timeFormat: "24h",
|
|
74
|
+
numberFormat: "compact",
|
|
75
|
+
sparkline: {
|
|
76
|
+
style: "braille",
|
|
77
|
+
orientation: "up",
|
|
78
|
+
showBaseline: true,
|
|
82
79
|
},
|
|
80
|
+
theme: "tokyo-night",
|
|
81
|
+
colorScheme: "auto",
|
|
82
|
+
},
|
|
83
83
|
notifications: {
|
|
84
84
|
toastsEnabled: true,
|
|
85
85
|
soundEnabled: false,
|
|
@@ -88,7 +88,7 @@ export const DEFAULT_CONFIG: AppConfig = {
|
|
|
88
88
|
daily: null,
|
|
89
89
|
weekly: null,
|
|
90
90
|
monthly: null,
|
|
91
|
-
currency:
|
|
91
|
+
currency: "USD",
|
|
92
92
|
},
|
|
93
93
|
alerts: {
|
|
94
94
|
warningPercent: 80,
|
|
@@ -149,7 +149,7 @@ function deepMerge(target: AppConfig, source: Partial<AppConfig>): AppConfig {
|
|
|
149
149
|
|
|
150
150
|
export async function loadConfig(): Promise<AppConfig> {
|
|
151
151
|
try {
|
|
152
|
-
const content = await fs.readFile(PATHS.config.file,
|
|
152
|
+
const content = await fs.readFile(PATHS.config.file, "utf-8");
|
|
153
153
|
const loaded = JSON.parse(content) as Partial<AppConfig>;
|
|
154
154
|
return deepMerge(DEFAULT_CONFIG, loaded);
|
|
155
155
|
} catch {
|
|
@@ -159,7 +159,7 @@ export async function loadConfig(): Promise<AppConfig> {
|
|
|
159
159
|
|
|
160
160
|
export async function saveConfig(config: AppConfig): Promise<void> {
|
|
161
161
|
await fs.mkdir(path.dirname(PATHS.config.file), { recursive: true });
|
|
162
|
-
const tempFile = PATHS.config.file +
|
|
163
|
-
await fs.writeFile(tempFile, JSON.stringify(config, null, 2),
|
|
162
|
+
const tempFile = PATHS.config.file + ".tmp";
|
|
163
|
+
await fs.writeFile(tempFile, JSON.stringify(config, null, 2), "utf-8");
|
|
164
164
|
await fs.rename(tempFile, PATHS.config.file);
|
|
165
165
|
}
|
package/src/demo/simulator.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AgentSessionAggregate, AgentSessionStream } from
|
|
2
|
-
import type { ProviderUsageData } from
|
|
3
|
-
import type { UsageEventInsert } from
|
|
1
|
+
import type { AgentSessionAggregate, AgentSessionStream } from "@/agents/types.ts";
|
|
2
|
+
import type { ProviderUsageData } from "@/plugins/types/provider.ts";
|
|
3
|
+
import type { UsageEventInsert } from "@/storage/types.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Demo presets control the intensity of simulated activity.
|
|
@@ -8,7 +8,7 @@ import type { UsageEventInsert } from '@/storage/types.ts';
|
|
|
8
8
|
* - normal: Moderate activity, balanced simulation (default)
|
|
9
9
|
* - heavy: High activity, more sessions, faster token accumulation
|
|
10
10
|
*/
|
|
11
|
-
export type DemoPreset =
|
|
11
|
+
export type DemoPreset = "light" | "normal" | "heavy";
|
|
12
12
|
|
|
13
13
|
export interface DemoPresetConfig {
|
|
14
14
|
sessionCount: number;
|
|
@@ -53,8 +53,8 @@ export const DEMO_PRESETS: Record<DemoPreset, DemoPresetConfig> = {
|
|
|
53
53
|
export interface DemoSessionSeed {
|
|
54
54
|
sessionId: string;
|
|
55
55
|
sessionName?: string;
|
|
56
|
-
agentId:
|
|
57
|
-
agentName:
|
|
56
|
+
agentId: "opencode" | "claude-code" | "cursor";
|
|
57
|
+
agentName: "OpenCode" | "Claude Code" | "Cursor";
|
|
58
58
|
projectPath: string;
|
|
59
59
|
modelId: string;
|
|
60
60
|
providerId: string;
|
|
@@ -65,69 +65,69 @@ export interface DemoSessionSeed {
|
|
|
65
65
|
|
|
66
66
|
const DEFAULT_SESSIONS: DemoSessionSeed[] = [
|
|
67
67
|
{
|
|
68
|
-
sessionId:
|
|
69
|
-
sessionName:
|
|
70
|
-
agentId:
|
|
71
|
-
agentName:
|
|
72
|
-
projectPath:
|
|
73
|
-
modelId:
|
|
74
|
-
providerId:
|
|
68
|
+
sessionId: "demo-opencode-1",
|
|
69
|
+
sessionName: "Implement dashboard view with real-time updates",
|
|
70
|
+
agentId: "opencode",
|
|
71
|
+
agentName: "OpenCode",
|
|
72
|
+
projectPath: "/Users/demo/workspace/tokentop",
|
|
73
|
+
modelId: "claude-3-5-sonnet",
|
|
74
|
+
providerId: "anthropic",
|
|
75
75
|
baseTokens: 3200,
|
|
76
76
|
baseCost: 1.24,
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
|
-
sessionId:
|
|
80
|
-
sessionName:
|
|
81
|
-
agentId:
|
|
82
|
-
agentName:
|
|
83
|
-
projectPath:
|
|
84
|
-
modelId:
|
|
85
|
-
providerId:
|
|
79
|
+
sessionId: "demo-opencode-2",
|
|
80
|
+
sessionName: "Fix Kubernetes deployment configuration",
|
|
81
|
+
agentId: "opencode",
|
|
82
|
+
agentName: "OpenCode",
|
|
83
|
+
projectPath: "/Users/demo/workspace/infra",
|
|
84
|
+
modelId: "gpt-4.1",
|
|
85
|
+
providerId: "openai",
|
|
86
86
|
baseTokens: 2100,
|
|
87
87
|
baseCost: 0.92,
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
|
-
sessionId:
|
|
91
|
-
sessionName:
|
|
92
|
-
agentId:
|
|
93
|
-
agentName:
|
|
94
|
-
projectPath:
|
|
95
|
-
modelId:
|
|
96
|
-
providerId:
|
|
90
|
+
sessionId: "demo-claude-1",
|
|
91
|
+
sessionName: "Add authentication flow to mobile app",
|
|
92
|
+
agentId: "claude-code",
|
|
93
|
+
agentName: "Claude Code",
|
|
94
|
+
projectPath: "/Users/demo/workspace/mobile",
|
|
95
|
+
modelId: "claude-3-opus",
|
|
96
|
+
providerId: "anthropic",
|
|
97
97
|
baseTokens: 1800,
|
|
98
98
|
baseCost: 0.78,
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
|
-
sessionId:
|
|
102
|
-
sessionName:
|
|
103
|
-
agentId:
|
|
104
|
-
agentName:
|
|
105
|
-
projectPath:
|
|
106
|
-
modelId:
|
|
107
|
-
providerId:
|
|
101
|
+
sessionId: "demo-cursor-1",
|
|
102
|
+
sessionName: "Refactor API endpoints for better performance",
|
|
103
|
+
agentId: "cursor",
|
|
104
|
+
agentName: "Cursor",
|
|
105
|
+
projectPath: "/Users/demo/workspace/webapp",
|
|
106
|
+
modelId: "gemini-2.0-pro",
|
|
107
|
+
providerId: "google-gemini",
|
|
108
108
|
baseTokens: 2600,
|
|
109
109
|
baseCost: 0.64,
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
|
-
sessionId:
|
|
113
|
-
sessionName:
|
|
114
|
-
agentId:
|
|
115
|
-
agentName:
|
|
116
|
-
projectPath:
|
|
117
|
-
modelId:
|
|
118
|
-
providerId:
|
|
112
|
+
sessionId: "demo-opencode-old-1",
|
|
113
|
+
sessionName: "Migrate legacy API to REST v2",
|
|
114
|
+
agentId: "opencode",
|
|
115
|
+
agentName: "OpenCode",
|
|
116
|
+
projectPath: "/Users/demo/workspace/legacy-api",
|
|
117
|
+
modelId: "claude-3-5-sonnet",
|
|
118
|
+
providerId: "anthropic",
|
|
119
119
|
baseTokens: 5400,
|
|
120
120
|
baseCost: 2.18,
|
|
121
121
|
inactive: true,
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
|
-
sessionId:
|
|
125
|
-
sessionName:
|
|
126
|
-
agentId:
|
|
127
|
-
agentName:
|
|
128
|
-
projectPath:
|
|
129
|
-
modelId:
|
|
130
|
-
providerId:
|
|
124
|
+
sessionId: "demo-claude-old-1",
|
|
125
|
+
sessionName: "Update documentation with new API examples",
|
|
126
|
+
agentId: "claude-code",
|
|
127
|
+
agentName: "Claude Code",
|
|
128
|
+
projectPath: "/Users/demo/workspace/docs-site",
|
|
129
|
+
modelId: "gpt-4.1",
|
|
130
|
+
providerId: "openai",
|
|
131
131
|
baseTokens: 3800,
|
|
132
132
|
baseCost: 1.56,
|
|
133
133
|
inactive: true,
|
|
@@ -135,22 +135,22 @@ const DEFAULT_SESSIONS: DemoSessionSeed[] = [
|
|
|
135
135
|
];
|
|
136
136
|
|
|
137
137
|
const PROVIDER_LIMITS: Record<string, { label: string; windowMinutes: number }> = {
|
|
138
|
-
anthropic: { label:
|
|
139
|
-
openai: { label:
|
|
140
|
-
|
|
138
|
+
anthropic: { label: "Daily Tokens", windowMinutes: 1440 },
|
|
139
|
+
openai: { label: "Daily Tokens", windowMinutes: 1440 },
|
|
140
|
+
"google-gemini": { label: "Daily Tokens", windowMinutes: 1440 },
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
const EXTRA_PROVIDERS: Array<{ id: string; label: string; balance?: string }> = [
|
|
144
|
-
{ id:
|
|
145
|
-
{ id:
|
|
146
|
-
{ id:
|
|
147
|
-
{ id:
|
|
148
|
-
{ id:
|
|
149
|
-
{ id:
|
|
150
|
-
{ id:
|
|
151
|
-
{ id:
|
|
152
|
-
{ id:
|
|
153
|
-
{ id:
|
|
144
|
+
{ id: "codex", label: "ChatGPT Plus", balance: "$18.00" },
|
|
145
|
+
{ id: "github-copilot", label: "Copilot Pro", balance: "$26.00" },
|
|
146
|
+
{ id: "perplexity", label: "Perplexity Pro", balance: "$12.50" },
|
|
147
|
+
{ id: "antigravity", label: "Antigravity AI", balance: "$31.00" },
|
|
148
|
+
{ id: "minimax", label: "MiniMax", balance: "$22.00" },
|
|
149
|
+
{ id: "cohere", label: "Cohere Enterprise", balance: "$45.00" },
|
|
150
|
+
{ id: "mistral", label: "Mistral API", balance: "$15.00" },
|
|
151
|
+
{ id: "groq", label: "Groq Cloud", balance: "$8.50" },
|
|
152
|
+
{ id: "together", label: "Together AI", balance: "$20.00" },
|
|
153
|
+
{ id: "fireworks", label: "Fireworks AI", balance: "$12.00" },
|
|
154
154
|
];
|
|
155
155
|
|
|
156
156
|
function clamp(value: number, min: number, max: number): number {
|
|
@@ -212,7 +212,7 @@ export class DemoSimulator {
|
|
|
212
212
|
|
|
213
213
|
constructor(options: DemoSimulatorOptions = {}) {
|
|
214
214
|
const seed = options.seed ?? 1337;
|
|
215
|
-
this.preset = options.preset ??
|
|
215
|
+
this.preset = options.preset ?? "normal";
|
|
216
216
|
this.presetConfig = DEMO_PRESETS[this.preset];
|
|
217
217
|
this.rng = new DemoRng(seed);
|
|
218
218
|
const now = Date.now();
|
|
@@ -221,14 +221,14 @@ export class DemoSimulator {
|
|
|
221
221
|
const sessionCount = Math.min(this.presetConfig.sessionCount, DEFAULT_SESSIONS.length);
|
|
222
222
|
|
|
223
223
|
// Select sessions: include a mix of inactive history and active sessions per preset size.
|
|
224
|
-
const inactiveSessions = DEFAULT_SESSIONS.filter(s => s.inactive ?? false);
|
|
225
|
-
const activeSessions = DEFAULT_SESSIONS.filter(s => !(s.inactive ?? false));
|
|
224
|
+
const inactiveSessions = DEFAULT_SESSIONS.filter((s) => s.inactive ?? false);
|
|
225
|
+
const activeSessions = DEFAULT_SESSIONS.filter((s) => !(s.inactive ?? false));
|
|
226
226
|
const desiredInactive = Math.max(1, Math.round(sessionCount * 0.3));
|
|
227
227
|
let inactiveCount = Math.min(inactiveSessions.length, desiredInactive);
|
|
228
228
|
if (activeSessions.length > 0) {
|
|
229
229
|
inactiveCount = Math.min(inactiveCount, sessionCount - 1);
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
const activeCount = Math.min(activeSessions.length, sessionCount - inactiveCount);
|
|
232
232
|
if (activeCount + inactiveCount < sessionCount) {
|
|
233
233
|
inactiveCount = Math.min(inactiveSessions.length, sessionCount - activeCount);
|
|
234
234
|
}
|
|
@@ -250,7 +250,7 @@ export class DemoSimulator {
|
|
|
250
250
|
tokens: { input: inputTokens, output: outputTokens },
|
|
251
251
|
requestCount: Math.max(1, Math.floor(tokens / 800)),
|
|
252
252
|
costUsd: cost,
|
|
253
|
-
pricingSource:
|
|
253
|
+
pricingSource: "fallback",
|
|
254
254
|
},
|
|
255
255
|
];
|
|
256
256
|
|
|
@@ -263,23 +263,31 @@ export class DemoSimulator {
|
|
|
263
263
|
: now - this.rng.range(10_000, 50_000);
|
|
264
264
|
|
|
265
265
|
const nowDate = new Date(now);
|
|
266
|
-
const startOfDay = new Date(
|
|
266
|
+
const startOfDay = new Date(
|
|
267
|
+
nowDate.getFullYear(),
|
|
268
|
+
nowDate.getMonth(),
|
|
269
|
+
nowDate.getDate(),
|
|
270
|
+
).getTime();
|
|
267
271
|
const dayOfWeek = nowDate.getDay();
|
|
268
|
-
const startOfWeek = new Date(
|
|
272
|
+
const startOfWeek = new Date(
|
|
273
|
+
nowDate.getFullYear(),
|
|
274
|
+
nowDate.getMonth(),
|
|
275
|
+
nowDate.getDate() - dayOfWeek,
|
|
276
|
+
).getTime();
|
|
269
277
|
const startOfMonth = new Date(nowDate.getFullYear(), nowDate.getMonth(), 1).getTime();
|
|
270
278
|
|
|
271
279
|
const costInDay = lastActivityAt >= startOfDay ? cost : 0;
|
|
272
280
|
const costInWeek = lastActivityAt >= startOfWeek ? cost : 0;
|
|
273
281
|
const costInMonth = lastActivityAt >= startOfMonth ? cost : 0;
|
|
274
282
|
|
|
275
|
-
const baseSession: Omit<AgentSessionAggregate,
|
|
283
|
+
const baseSession: Omit<AgentSessionAggregate, "endedAt"> = {
|
|
276
284
|
sessionId: seedSession.sessionId,
|
|
277
285
|
agentId: seedSession.agentId,
|
|
278
286
|
agentName: seedSession.agentName,
|
|
279
287
|
projectPath: seedSession.projectPath,
|
|
280
288
|
startedAt,
|
|
281
289
|
lastActivityAt,
|
|
282
|
-
status: isInactive ?
|
|
290
|
+
status: isInactive ? "idle" : index % 3 === 0 ? "idle" : "active",
|
|
283
291
|
totals: {
|
|
284
292
|
input: inputTokens,
|
|
285
293
|
output: outputTokens,
|
|
@@ -297,12 +305,10 @@ export class DemoSimulator {
|
|
|
297
305
|
baseSession.sessionName = seedSession.sessionName;
|
|
298
306
|
}
|
|
299
307
|
|
|
300
|
-
return isInactive
|
|
301
|
-
? { ...baseSession, endedAt: lastActivityAt }
|
|
302
|
-
: baseSession;
|
|
308
|
+
return isInactive ? { ...baseSession, endedAt: lastActivityAt } : baseSession;
|
|
303
309
|
});
|
|
304
310
|
|
|
305
|
-
const sessionProviderIds = new Set(selectedSessions.map(s => s.providerId));
|
|
311
|
+
const sessionProviderIds = new Set(selectedSessions.map((s) => s.providerId));
|
|
306
312
|
this.fixedProviderIds = Array.from(sessionProviderIds);
|
|
307
313
|
|
|
308
314
|
const extraCount = Math.min(this.presetConfig.extraProviderCount, EXTRA_PROVIDERS.length);
|
|
@@ -317,7 +323,11 @@ export class DemoSimulator {
|
|
|
317
323
|
const providerTotals = new Map<string, { tokens: number; cost: number; requests: number }>();
|
|
318
324
|
for (const session of this.sessions) {
|
|
319
325
|
for (const stream of session.streams) {
|
|
320
|
-
const current = providerTotals.get(stream.providerId) ?? {
|
|
326
|
+
const current = providerTotals.get(stream.providerId) ?? {
|
|
327
|
+
tokens: 0,
|
|
328
|
+
cost: 0,
|
|
329
|
+
requests: 0,
|
|
330
|
+
};
|
|
321
331
|
const streamTokens = stream.tokens.input + stream.tokens.output;
|
|
322
332
|
providerTotals.set(stream.providerId, {
|
|
323
333
|
tokens: current.tokens + streamTokens,
|
|
@@ -329,12 +339,12 @@ export class DemoSimulator {
|
|
|
329
339
|
|
|
330
340
|
for (const providerId of this.fixedProviderIds) {
|
|
331
341
|
const totals = providerTotals.get(providerId) ?? { tokens: 0, cost: 0, requests: 0 };
|
|
332
|
-
const limit = PROVIDER_LIMITS[providerId] ?? { label:
|
|
342
|
+
const limit = PROVIDER_LIMITS[providerId] ?? { label: "Daily Tokens", windowMinutes: 1440 };
|
|
333
343
|
const usedPercent = clamp((totals.tokens / 50_000) * 100, 5, 98);
|
|
334
344
|
const limitReached = usedPercent > 95;
|
|
335
345
|
|
|
336
346
|
this.providerUsage.set(providerId, {
|
|
337
|
-
planType:
|
|
347
|
+
planType: "Pro",
|
|
338
348
|
limitReached,
|
|
339
349
|
limits: {
|
|
340
350
|
primary: {
|
|
@@ -355,14 +365,14 @@ export class DemoSimulator {
|
|
|
355
365
|
total: totals.cost,
|
|
356
366
|
input: totals.cost * 0.55,
|
|
357
367
|
output: totals.cost * 0.45,
|
|
358
|
-
currency:
|
|
368
|
+
currency: "USD",
|
|
359
369
|
},
|
|
360
|
-
source:
|
|
370
|
+
source: "estimated",
|
|
361
371
|
},
|
|
362
372
|
credits: {
|
|
363
373
|
hasCredits: true,
|
|
364
374
|
unlimited: false,
|
|
365
|
-
balance:
|
|
375
|
+
balance: "$42.50",
|
|
366
376
|
},
|
|
367
377
|
fetchedAt: now,
|
|
368
378
|
});
|
|
@@ -377,7 +387,7 @@ export class DemoSimulator {
|
|
|
377
387
|
limits: {
|
|
378
388
|
primary: {
|
|
379
389
|
usedPercent,
|
|
380
|
-
label:
|
|
390
|
+
label: "Monthly Tokens",
|
|
381
391
|
windowMinutes: 43200,
|
|
382
392
|
resetsAt: now + 10 * 24 * 60 * 60 * 1000,
|
|
383
393
|
},
|
|
@@ -391,14 +401,14 @@ export class DemoSimulator {
|
|
|
391
401
|
total: usedPercent * 0.08,
|
|
392
402
|
input: usedPercent * 0.04,
|
|
393
403
|
output: usedPercent * 0.04,
|
|
394
|
-
currency:
|
|
404
|
+
currency: "USD",
|
|
395
405
|
},
|
|
396
|
-
source:
|
|
406
|
+
source: "estimated",
|
|
397
407
|
},
|
|
398
408
|
credits: {
|
|
399
409
|
hasCredits: true,
|
|
400
410
|
unlimited: false,
|
|
401
|
-
balance: provider.balance ??
|
|
411
|
+
balance: provider.balance ?? "$25.00",
|
|
402
412
|
},
|
|
403
413
|
fetchedAt: now,
|
|
404
414
|
});
|
|
@@ -409,7 +419,11 @@ export class DemoSimulator {
|
|
|
409
419
|
const providerTotals = new Map<string, { tokens: number; cost: number; requests: number }>();
|
|
410
420
|
for (const session of this.sessions) {
|
|
411
421
|
for (const stream of session.streams) {
|
|
412
|
-
const current = providerTotals.get(stream.providerId) ?? {
|
|
422
|
+
const current = providerTotals.get(stream.providerId) ?? {
|
|
423
|
+
tokens: 0,
|
|
424
|
+
cost: 0,
|
|
425
|
+
requests: 0,
|
|
426
|
+
};
|
|
413
427
|
const streamTokens = stream.tokens.input + stream.tokens.output;
|
|
414
428
|
providerTotals.set(stream.providerId, {
|
|
415
429
|
tokens: current.tokens + streamTokens,
|
|
@@ -421,13 +435,13 @@ export class DemoSimulator {
|
|
|
421
435
|
|
|
422
436
|
for (const providerId of this.fixedProviderIds) {
|
|
423
437
|
const totals = providerTotals.get(providerId) ?? { tokens: 0, cost: 0, requests: 0 };
|
|
424
|
-
const limit = PROVIDER_LIMITS[providerId] ?? { label:
|
|
438
|
+
const limit = PROVIDER_LIMITS[providerId] ?? { label: "Daily Tokens", windowMinutes: 1440 };
|
|
425
439
|
const usedPercent = clamp((totals.tokens / 50_000) * 100, 5, 98);
|
|
426
440
|
const limitReached = usedPercent > 95;
|
|
427
441
|
|
|
428
442
|
const existing = this.providerUsage.get(providerId);
|
|
429
443
|
this.providerUsage.set(providerId, {
|
|
430
|
-
planType: existing?.planType ??
|
|
444
|
+
planType: existing?.planType ?? "Pro",
|
|
431
445
|
limitReached,
|
|
432
446
|
limits: {
|
|
433
447
|
primary: {
|
|
@@ -448,14 +462,14 @@ export class DemoSimulator {
|
|
|
448
462
|
total: totals.cost,
|
|
449
463
|
input: totals.cost * 0.55,
|
|
450
464
|
output: totals.cost * 0.45,
|
|
451
|
-
currency:
|
|
465
|
+
currency: "USD",
|
|
452
466
|
},
|
|
453
|
-
source:
|
|
467
|
+
source: "estimated",
|
|
454
468
|
},
|
|
455
469
|
credits: existing?.credits ?? {
|
|
456
470
|
hasCredits: true,
|
|
457
471
|
unlimited: false,
|
|
458
|
-
balance:
|
|
472
|
+
balance: "$42.50",
|
|
459
473
|
},
|
|
460
474
|
fetchedAt: now,
|
|
461
475
|
});
|
|
@@ -487,9 +501,9 @@ export class DemoSimulator {
|
|
|
487
501
|
total: newPercent * 0.08,
|
|
488
502
|
input: newPercent * 0.04,
|
|
489
503
|
output: newPercent * 0.04,
|
|
490
|
-
currency:
|
|
504
|
+
currency: "USD",
|
|
491
505
|
},
|
|
492
|
-
source:
|
|
506
|
+
source: "estimated",
|
|
493
507
|
},
|
|
494
508
|
fetchedAt: now,
|
|
495
509
|
});
|
|
@@ -514,16 +528,17 @@ export class DemoSimulator {
|
|
|
514
528
|
const sessionRng = tickRng.fork(sessionIndex);
|
|
515
529
|
const isIdle = sessionRng.next() < this.presetConfig.idleProbability;
|
|
516
530
|
if (isIdle) {
|
|
517
|
-
return { ...session, status:
|
|
531
|
+
return { ...session, status: "idle" as const };
|
|
518
532
|
}
|
|
519
533
|
|
|
520
534
|
const isBurst = sessionRng.next() < this.presetConfig.burstProbability;
|
|
521
535
|
const burstFactor = isBurst ? this.presetConfig.burstMultiplier : 1;
|
|
522
536
|
const activityFactor = sessionRng.range(0.4, 1.6) * burstFactor;
|
|
523
|
-
const tokensPerSec =
|
|
537
|
+
const tokensPerSec =
|
|
538
|
+
session.agentId === "opencode" ? 35 : session.agentId === "claude-code" ? 28 : 22;
|
|
524
539
|
const deltaTokens = Math.floor(tokensPerSec * activityFactor * activityMultiplier * dtSec);
|
|
525
540
|
if (deltaTokens === 0) {
|
|
526
|
-
return { ...session, status:
|
|
541
|
+
return { ...session, status: "idle" as const };
|
|
527
542
|
}
|
|
528
543
|
const inputDelta = Math.floor(deltaTokens * 0.6);
|
|
529
544
|
const outputDelta = deltaTokens - inputDelta;
|
|
@@ -542,9 +557,9 @@ export class DemoSimulator {
|
|
|
542
557
|
|
|
543
558
|
usageEvents.push({
|
|
544
559
|
timestamp: now,
|
|
545
|
-
source:
|
|
546
|
-
provider: updatedStreams[0]?.providerId ??
|
|
547
|
-
model: updatedStreams[0]?.modelId ??
|
|
560
|
+
source: "agent",
|
|
561
|
+
provider: updatedStreams[0]?.providerId ?? "anthropic",
|
|
562
|
+
model: updatedStreams[0]?.modelId ?? "claude-3-5-sonnet",
|
|
548
563
|
agentId: session.agentId,
|
|
549
564
|
sessionId: session.sessionId,
|
|
550
565
|
projectPath: session.projectPath ?? null,
|
|
@@ -554,12 +569,12 @@ export class DemoSimulator {
|
|
|
554
569
|
cacheWriteTokens: Math.floor(deltaTokens * 0.02),
|
|
555
570
|
costUsd: costDelta,
|
|
556
571
|
requestCount: Math.max(1, Math.floor(deltaTokens / 700)),
|
|
557
|
-
pricingSource:
|
|
572
|
+
pricingSource: "fallback",
|
|
558
573
|
});
|
|
559
574
|
|
|
560
575
|
return {
|
|
561
576
|
...session,
|
|
562
|
-
status:
|
|
577
|
+
status: "active" as const,
|
|
563
578
|
lastActivityAt: now,
|
|
564
579
|
totals: {
|
|
565
580
|
...session.totals,
|
|
@@ -593,17 +608,25 @@ export class DemoSimulator {
|
|
|
593
608
|
}
|
|
594
609
|
|
|
595
610
|
getProviderIds(): string[] {
|
|
596
|
-
return [...this.fixedProviderIds, ...this.fixedExtraProviders.map(p => p.id)];
|
|
611
|
+
return [...this.fixedProviderIds, ...this.fixedExtraProviders.map((p) => p.id)];
|
|
597
612
|
}
|
|
598
613
|
|
|
599
|
-
generateHistoricalCostDataByProvider(
|
|
614
|
+
generateHistoricalCostDataByProvider(
|
|
615
|
+
daysBack: number,
|
|
616
|
+
): Array<{ date: number; provider: string; cost: number; tokens: number; requests: number }> {
|
|
600
617
|
const totalDaily = this.generateHistoricalCostData(daysBack);
|
|
601
|
-
const result: Array<{
|
|
618
|
+
const result: Array<{
|
|
619
|
+
date: number;
|
|
620
|
+
provider: string;
|
|
621
|
+
cost: number;
|
|
622
|
+
tokens: number;
|
|
623
|
+
requests: number;
|
|
624
|
+
}> = [];
|
|
602
625
|
|
|
603
626
|
const providerShares = [
|
|
604
|
-
{ id:
|
|
605
|
-
{ id:
|
|
606
|
-
{ id:
|
|
627
|
+
{ id: "anthropic", share: 0.5 },
|
|
628
|
+
{ id: "openai", share: 0.25 },
|
|
629
|
+
{ id: "google-gemini", share: 0.15 },
|
|
607
630
|
];
|
|
608
631
|
|
|
609
632
|
const providerRng = this.rng.fork(88888);
|
|
@@ -636,15 +659,23 @@ export class DemoSimulator {
|
|
|
636
659
|
return result;
|
|
637
660
|
}
|
|
638
661
|
|
|
639
|
-
generateHistoricalCostDataByModel(
|
|
662
|
+
generateHistoricalCostDataByModel(
|
|
663
|
+
daysBack: number,
|
|
664
|
+
): Array<{ date: number; model: string; cost: number; tokens: number; requests: number }> {
|
|
640
665
|
const totalDaily = this.generateHistoricalCostData(daysBack);
|
|
641
|
-
const result: Array<{
|
|
666
|
+
const result: Array<{
|
|
667
|
+
date: number;
|
|
668
|
+
model: string;
|
|
669
|
+
cost: number;
|
|
670
|
+
tokens: number;
|
|
671
|
+
requests: number;
|
|
672
|
+
}> = [];
|
|
642
673
|
|
|
643
674
|
const modelShares = [
|
|
644
|
-
{ id:
|
|
645
|
-
{ id:
|
|
646
|
-
{ id:
|
|
647
|
-
{ id:
|
|
675
|
+
{ id: "claude-3-5-sonnet", share: 0.4 },
|
|
676
|
+
{ id: "claude-3-opus", share: 0.2 },
|
|
677
|
+
{ id: "gpt-4.1", share: 0.25 },
|
|
678
|
+
{ id: "gemini-2.0-pro", share: 0.15 },
|
|
648
679
|
];
|
|
649
680
|
|
|
650
681
|
const modelRng = this.rng.fork(77777);
|
|
@@ -677,14 +708,22 @@ export class DemoSimulator {
|
|
|
677
708
|
return result;
|
|
678
709
|
}
|
|
679
710
|
|
|
680
|
-
generateHistoricalCostDataByProject(
|
|
711
|
+
generateHistoricalCostDataByProject(
|
|
712
|
+
daysBack: number,
|
|
713
|
+
): Array<{ date: number; projectPath: string; cost: number; tokens: number; requests: number }> {
|
|
681
714
|
const totalDaily = this.generateHistoricalCostData(daysBack);
|
|
682
|
-
const result: Array<{
|
|
715
|
+
const result: Array<{
|
|
716
|
+
date: number;
|
|
717
|
+
projectPath: string;
|
|
718
|
+
cost: number;
|
|
719
|
+
tokens: number;
|
|
720
|
+
requests: number;
|
|
721
|
+
}> = [];
|
|
683
722
|
|
|
684
723
|
const projectShares = [
|
|
685
|
-
{ path:
|
|
686
|
-
{ path:
|
|
687
|
-
{ path:
|
|
724
|
+
{ path: "/Users/demo/workspace/tokentop", share: 0.45 },
|
|
725
|
+
{ path: "/Users/demo/workspace/webapp", share: 0.3 },
|
|
726
|
+
{ path: "/Users/demo/workspace/infra", share: 0.25 },
|
|
688
727
|
];
|
|
689
728
|
|
|
690
729
|
const projectRng = this.rng.fork(66666);
|
|
@@ -724,19 +763,19 @@ export class DemoSimulator {
|
|
|
724
763
|
const result: Array<{ date: number; cost: number }> = [];
|
|
725
764
|
|
|
726
765
|
const baseCost = this.presetConfig.activityMultiplier * 2.5;
|
|
727
|
-
|
|
766
|
+
|
|
728
767
|
for (let i = daysBack - 1; i >= 0; i--) {
|
|
729
768
|
const dayTimestamp = now - i * msPerDay;
|
|
730
769
|
const dayRng = historyRng.fork(i);
|
|
731
|
-
|
|
770
|
+
|
|
732
771
|
const isWeekend = new Date(dayTimestamp).getDay() % 6 === 0;
|
|
733
772
|
const weekendFactor = isWeekend ? 0.4 : 1.0;
|
|
734
|
-
|
|
773
|
+
|
|
735
774
|
const variance = dayRng.range(0.5, 1.5);
|
|
736
775
|
const spikeFactor = dayRng.next() < 0.1 ? dayRng.range(1.5, 2.5) : 1.0;
|
|
737
|
-
|
|
776
|
+
|
|
738
777
|
const cost = baseCost * variance * weekendFactor * spikeFactor;
|
|
739
|
-
|
|
778
|
+
|
|
740
779
|
result.push({ date: dayTimestamp, cost: Math.round(cost * 100) / 100 });
|
|
741
780
|
}
|
|
742
781
|
|