@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.
Files changed (160) hide show
  1. package/README.md +2 -5
  2. package/package.json +8 -5
  3. package/src/agents/aggregator.ts +39 -19
  4. package/src/agents/costing.ts +11 -9
  5. package/src/agents/index.ts +7 -9
  6. package/src/agents/types.ts +2 -2
  7. package/src/cli.ts +28 -23
  8. package/src/config/schema.test.ts +42 -40
  9. package/src/config/schema.ts +26 -26
  10. package/src/demo/simulator.ts +161 -122
  11. package/src/plugins/agents/index.ts +2 -2
  12. package/src/plugins/auth-sources.ts +42 -38
  13. package/src/plugins/lifecycle.ts +42 -38
  14. package/src/plugins/loader.test.ts +49 -49
  15. package/src/plugins/loader.ts +67 -63
  16. package/src/plugins/notification-bus.ts +21 -16
  17. package/src/plugins/notifications/index.ts +2 -2
  18. package/src/plugins/notifications/terminal-bell.ts +36 -23
  19. package/src/plugins/notifications/visual-flash.ts +31 -19
  20. package/src/plugins/npm-installer.ts +23 -25
  21. package/src/plugins/plugin-context-factory.ts +10 -10
  22. package/src/plugins/plugin-host.ts +12 -5
  23. package/src/plugins/providers/anthropic.ts +64 -57
  24. package/src/plugins/providers/antigravity.ts +81 -69
  25. package/src/plugins/providers/codex.ts +42 -42
  26. package/src/plugins/providers/gemini.ts +106 -87
  27. package/src/plugins/providers/github-copilot.ts +49 -45
  28. package/src/plugins/providers/index.ts +10 -12
  29. package/src/plugins/providers/minimax.ts +50 -44
  30. package/src/plugins/providers/openai-api.ts +82 -62
  31. package/src/plugins/providers/opencode-zen.ts +46 -39
  32. package/src/plugins/providers/perplexity.ts +55 -46
  33. package/src/plugins/providers/zai.ts +68 -57
  34. package/src/plugins/registry.ts +80 -71
  35. package/src/plugins/sandbox-guard.test.ts +45 -48
  36. package/src/plugins/sandbox-guard.ts +20 -25
  37. package/src/plugins/sandbox.ts +26 -48
  38. package/src/plugins/themes/catppuccin-latte.ts +29 -29
  39. package/src/plugins/themes/catppuccin-mocha.ts +29 -29
  40. package/src/plugins/themes/claude-code.ts +29 -29
  41. package/src/plugins/themes/dracula.ts +29 -29
  42. package/src/plugins/themes/github-light.ts +29 -29
  43. package/src/plugins/themes/gruvbox-dark.ts +29 -29
  44. package/src/plugins/themes/gruvbox-light.ts +29 -29
  45. package/src/plugins/themes/index.ts +15 -15
  46. package/src/plugins/themes/kanagawa.ts +29 -29
  47. package/src/plugins/themes/nord.ts +29 -29
  48. package/src/plugins/themes/one-dark.ts +29 -29
  49. package/src/plugins/themes/opencode.ts +31 -31
  50. package/src/plugins/themes/rose-pine-dawn.ts +29 -29
  51. package/src/plugins/themes/rose-pine.ts +29 -29
  52. package/src/plugins/themes/solarized-light.ts +29 -29
  53. package/src/plugins/themes/tokyo-night.ts +29 -29
  54. package/src/plugins/types/agent.ts +5 -5
  55. package/src/plugins/types/base.ts +35 -24
  56. package/src/plugins/types/index.ts +9 -9
  57. package/src/plugins/types/notification.ts +12 -12
  58. package/src/plugins/types/provider.ts +12 -8
  59. package/src/plugins/types/theme.ts +31 -23
  60. package/src/plugins/update-checker.ts +6 -6
  61. package/src/pricing/estimator.test.ts +39 -44
  62. package/src/pricing/estimator.ts +7 -13
  63. package/src/pricing/fallback.test.ts +30 -34
  64. package/src/pricing/fallback.ts +54 -27
  65. package/src/pricing/index.ts +18 -12
  66. package/src/pricing/models-dev.ts +6 -6
  67. package/src/storage/database.ts +56 -46
  68. package/src/storage/db.ts +12 -12
  69. package/src/storage/index.ts +44 -45
  70. package/src/storage/migrations/index.ts +2 -2
  71. package/src/storage/paths.ts +13 -13
  72. package/src/storage/persistence-service.ts +206 -0
  73. package/src/storage/repos/agentSessions.ts +33 -23
  74. package/src/storage/repos/providerSnapshots.ts +5 -5
  75. package/src/storage/repos/usageEvents.ts +40 -28
  76. package/src/storage/retention.ts +146 -0
  77. package/src/storage/types.ts +5 -5
  78. package/src/tui/App.tsx +180 -137
  79. package/src/tui/components/CommandPalette.tsx +46 -34
  80. package/src/tui/components/DebugConsole.tsx +39 -33
  81. package/src/tui/components/DebugPanel.tsx +314 -183
  82. package/src/tui/components/FooterHints.tsx +2 -2
  83. package/src/tui/components/GhostProviderCard.tsx +37 -35
  84. package/src/tui/components/Header.tsx +68 -35
  85. package/src/tui/components/HelpOverlay.tsx +99 -29
  86. package/src/tui/components/InlineGauge.tsx +10 -7
  87. package/src/tui/components/InlineSparkline.tsx +17 -11
  88. package/src/tui/components/KpiStrip.tsx +128 -98
  89. package/src/tui/components/LimitGauge.tsx +96 -59
  90. package/src/tui/components/ModalBackdrop.tsx +4 -4
  91. package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
  92. package/src/tui/components/ProviderCard.tsx +232 -226
  93. package/src/tui/components/ProviderDetailPanel.tsx +133 -58
  94. package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
  95. package/src/tui/components/ProvidersList.tsx +215 -126
  96. package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
  97. package/src/tui/components/SessionsTable.tsx +320 -169
  98. package/src/tui/components/SettingsModal.tsx +593 -269
  99. package/src/tui/components/SidebarBreakdown.tsx +30 -16
  100. package/src/tui/components/Skeleton.tsx +24 -17
  101. package/src/tui/components/SmartSidebar.tsx +360 -258
  102. package/src/tui/components/Sparkline.tsx +64 -52
  103. package/src/tui/components/Spinner.tsx +3 -3
  104. package/src/tui/components/StatusBar.tsx +27 -27
  105. package/src/tui/components/ThemePicker.tsx +67 -66
  106. package/src/tui/components/Toast.tsx +9 -6
  107. package/src/tui/components/UsageGauge.tsx +10 -13
  108. package/src/tui/components/index.ts +5 -5
  109. package/src/tui/contexts/AgentSessionContext.tsx +164 -151
  110. package/src/tui/contexts/ConfigContext.tsx +15 -13
  111. package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
  112. package/src/tui/contexts/DemoModeContext.tsx +22 -15
  113. package/src/tui/contexts/DrawerContext.tsx +11 -9
  114. package/src/tui/contexts/InputContext.tsx +2 -2
  115. package/src/tui/contexts/LogContext.tsx +62 -47
  116. package/src/tui/contexts/PluginContext.tsx +238 -183
  117. package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
  118. package/src/tui/contexts/StorageContext.tsx +95 -149
  119. package/src/tui/contexts/ThemeContext.tsx +21 -16
  120. package/src/tui/contexts/TimeWindowContext.tsx +72 -61
  121. package/src/tui/contexts/ToastContext.tsx +10 -7
  122. package/src/tui/contexts/index.ts +9 -5
  123. package/src/tui/createApp.tsx +8 -12
  124. package/src/tui/debug/captureFrame.ts +25 -28
  125. package/src/tui/debug/snapshot.tsx +179 -162
  126. package/src/tui/driver/assertions.ts +30 -22
  127. package/src/tui/driver/cli.ts +231 -221
  128. package/src/tui/driver/coverage.ts +27 -44
  129. package/src/tui/driver/demo-snapshot.ts +33 -33
  130. package/src/tui/driver/diff.ts +26 -32
  131. package/src/tui/driver/driver.ts +43 -48
  132. package/src/tui/driver/index.ts +36 -40
  133. package/src/tui/driver/recorder.ts +37 -48
  134. package/src/tui/driver/test-workflow.ts +51 -51
  135. package/src/tui/driver/test.ts +25 -25
  136. package/src/tui/hooks/index.ts +24 -20
  137. package/src/tui/hooks/useAnimatedValue.ts +13 -18
  138. package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
  139. package/src/tui/hooks/useDashboardState.ts +26 -24
  140. package/src/tui/hooks/useEmaActivity.ts +82 -74
  141. package/src/tui/hooks/useEntranceAnimation.ts +11 -11
  142. package/src/tui/hooks/useExitAnimation.ts +7 -3
  143. package/src/tui/hooks/usePulse.ts +4 -4
  144. package/src/tui/hooks/useSafeRenderer.ts +5 -5
  145. package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
  146. package/src/tui/hooks/useValueFlash.ts +10 -14
  147. package/src/tui/index.tsx +29 -6
  148. package/src/tui/shutdown.ts +19 -0
  149. package/src/tui/utils/providerColor.ts +1 -1
  150. package/src/tui/views/Dashboard.tsx +164 -102
  151. package/src/tui/views/HistoricalTrendsView.tsx +579 -250
  152. package/src/tui/views/ProjectsView.tsx +495 -243
  153. package/src/tui/views/RealTimeDashboard.tsx +176 -116
  154. package/src/tui/views/SettingsView.tsx +225 -189
  155. package/src/tui/views/index.ts +2 -2
  156. package/src/types/opentui-test-utils.d.ts +7 -7
  157. package/src/utils/clipboard.ts +29 -29
  158. package/src/utils/currency.test.ts +53 -53
  159. package/src/utils/currency.ts +35 -32
  160. package/src/version.ts +1 -1
@@ -1,48 +1,48 @@
1
1
  import type {
2
- ProviderPlugin,
3
- ProviderFetchContext,
4
- ProviderUsageData,
5
- ProviderAuth,
6
- PluginContext,
7
2
  CredentialResult,
8
3
  Credentials,
9
- } from '../types/provider.ts';
4
+ PluginContext,
5
+ ProviderAuth,
6
+ ProviderFetchContext,
7
+ ProviderPlugin,
8
+ ProviderUsageData,
9
+ } from "../types/provider.ts";
10
10
 
11
11
  /**
12
12
  * Perplexity Shadow API Discovery (January 2026):
13
- *
13
+ *
14
14
  * Internal endpoints that power the billing dashboard (require session cookies, not API key):
15
15
  * - GET /rest/pplx-api/v2/groups → { orgs: [{ api_org_id, display_name, usage_tier }] }
16
16
  * - GET /rest/pplx-api/v2/groups/{orgId} → { customerInfo: { balance, auto_top_up_amount, ... } }
17
17
  * - GET /rest/pplx-api/v2/groups/{orgId}/usage-analytics?time_bucket=day&time_range=past_month
18
18
  * → Array of meter data (api_requests, input_tokens, output_tokens, etc.) with costs
19
19
  * - GET /rest/pplx-api/v2/groups/{orgId}/invoices → Invoice history
20
- *
20
+ *
21
21
  * Unfortunately, these endpoints use web session auth, not API key auth.
22
22
  * GitHub issue #266 requests a public usage API endpoint.
23
23
  */
24
24
 
25
25
  export const perplexityPlugin: ProviderPlugin = {
26
26
  apiVersion: 2,
27
- id: 'perplexity',
28
- type: 'provider',
29
- name: 'Perplexity',
30
- version: '1.0.0',
27
+ id: "perplexity",
28
+ type: "provider",
29
+ name: "Perplexity",
30
+ version: "1.0.0",
31
31
 
32
32
  meta: {
33
- description: 'Perplexity AI API usage (credit-based)',
34
- homepage: 'https://www.perplexity.ai',
35
- brandColor: '#20b2aa',
33
+ description: "Perplexity AI API usage (credit-based)",
34
+ homepage: "https://www.perplexity.ai",
35
+ brandColor: "#20b2aa",
36
36
  },
37
37
 
38
38
  permissions: {
39
39
  network: {
40
40
  enabled: true,
41
- allowedDomains: ['api.perplexity.ai'],
41
+ allowedDomains: ["api.perplexity.ai"],
42
42
  },
43
43
  env: {
44
44
  read: true,
45
- vars: ['PERPLEXITY_API_KEY'],
45
+ vars: ["PERPLEXITY_API_KEY"],
46
46
  },
47
47
  },
48
48
 
@@ -56,23 +56,30 @@ export const perplexityPlugin: ProviderPlugin = {
56
56
  auth: {
57
57
  async discover(ctx: PluginContext): Promise<CredentialResult> {
58
58
  // 1. Try OpenCode auth first (wellknown type, has token or key)
59
- const entry = await ctx.authSources.opencode.getProviderEntry('perplexity');
59
+ const entry = await ctx.authSources.opencode.getProviderEntry("perplexity");
60
60
  if (entry) {
61
- if (entry.type === 'api' && entry.key) {
62
- return { ok: true, credentials: { apiKey: entry.key, source: 'opencode' } };
61
+ if (entry.type === "api" && entry.key) {
62
+ return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
63
63
  }
64
- if (entry.type === 'wellknown' && (entry.token || entry.key)) {
65
- return { ok: true, credentials: { apiKey: (entry.token || entry.key)!, source: 'opencode' } };
64
+ if (entry.type === "wellknown" && (entry.token || entry.key)) {
65
+ return {
66
+ ok: true,
67
+ credentials: { apiKey: (entry.token || entry.key)!, source: "opencode" },
68
+ };
66
69
  }
67
70
  }
68
71
 
69
72
  // 2. Try env vars
70
- const apiKey = ctx.authSources.env.get('PERPLEXITY_API_KEY');
73
+ const apiKey = ctx.authSources.env.get("PERPLEXITY_API_KEY");
71
74
  if (apiKey) {
72
- return { ok: true, credentials: { apiKey, source: 'env' } };
75
+ return { ok: true, credentials: { apiKey, source: "env" } };
73
76
  }
74
77
 
75
- return { ok: false, reason: 'missing', message: 'No Perplexity API key found. Set PERPLEXITY_API_KEY or configure in OpenCode.' };
78
+ return {
79
+ ok: false,
80
+ reason: "missing",
81
+ message: "No Perplexity API key found. Set PERPLEXITY_API_KEY or configure in OpenCode.",
82
+ };
76
83
  },
77
84
 
78
85
  isConfigured(credentials: Credentials): boolean {
@@ -86,22 +93,22 @@ export const perplexityPlugin: ProviderPlugin = {
86
93
  if (!credentials.apiKey) {
87
94
  return {
88
95
  fetchedAt: Date.now(),
89
- error: 'API key required. Set PERPLEXITY_API_KEY environment variable.',
96
+ error: "API key required. Set PERPLEXITY_API_KEY environment variable.",
90
97
  };
91
98
  }
92
99
 
93
- log.info('Checking Perplexity API', { keyPrefix: credentials.apiKey.slice(0, 8) });
100
+ log.info("Checking Perplexity API", { keyPrefix: credentials.apiKey.slice(0, 8) });
94
101
 
95
102
  try {
96
- const response = await http.fetch('https://api.perplexity.ai/chat/completions', {
97
- method: 'POST',
103
+ const response = await http.fetch("https://api.perplexity.ai/chat/completions", {
104
+ method: "POST",
98
105
  headers: {
99
- 'Authorization': `Bearer ${credentials.apiKey}`,
100
- 'Content-Type': 'application/json',
106
+ Authorization: `Bearer ${credentials.apiKey}`,
107
+ "Content-Type": "application/json",
101
108
  },
102
109
  body: JSON.stringify({
103
- model: 'sonar',
104
- messages: [{ role: 'user', content: 'ping' }],
110
+ model: "sonar",
111
+ messages: [{ role: "user", content: "ping" }],
105
112
  max_tokens: 1,
106
113
  }),
107
114
  });
@@ -109,39 +116,41 @@ export const perplexityPlugin: ProviderPlugin = {
109
116
  if (response.status === 401) {
110
117
  return {
111
118
  fetchedAt: Date.now(),
112
- error: 'Invalid API key',
119
+ error: "Invalid API key",
113
120
  };
114
121
  }
115
122
 
116
123
  if (response.status === 402) {
117
124
  return {
118
- planType: 'API',
125
+ planType: "API",
119
126
  allowed: false,
120
127
  fetchedAt: Date.now(),
121
128
  credits: {
122
129
  hasCredits: false,
123
130
  unlimited: false,
124
- balance: '$0.00',
131
+ balance: "$0.00",
125
132
  },
126
- error: 'Insufficient credits',
133
+ error: "Insufficient credits",
127
134
  };
128
135
  }
129
136
 
130
137
  if (response.status === 429) {
131
138
  return {
132
- planType: 'API',
139
+ planType: "API",
133
140
  allowed: true,
134
141
  fetchedAt: Date.now(),
135
- error: 'Rate limited',
142
+ error: "Rate limited",
136
143
  };
137
144
  }
138
145
 
139
146
  if (response.ok) {
140
- const data = await response.json().catch(() => null) as { usage?: { prompt_tokens?: number; completion_tokens?: number } } | null;
147
+ const data = (await response.json().catch(() => null)) as {
148
+ usage?: { prompt_tokens?: number; completion_tokens?: number };
149
+ } | null;
141
150
  const usage = data?.usage;
142
-
151
+
143
152
  return {
144
- planType: 'API',
153
+ planType: "API",
145
154
  allowed: true,
146
155
  fetchedAt: Date.now(),
147
156
  credits: {
@@ -157,17 +166,17 @@ export const perplexityPlugin: ProviderPlugin = {
157
166
  };
158
167
  }
159
168
 
160
- const errorText = await response.text().catch(() => '');
169
+ const errorText = await response.text().catch(() => "");
161
170
  return {
162
- planType: 'API',
171
+ planType: "API",
163
172
  allowed: true,
164
173
  fetchedAt: Date.now(),
165
174
  error: `API error: ${response.status} ${errorText.slice(0, 100)}`,
166
175
  };
167
176
  } catch (err) {
168
177
  const msg = err instanceof Error ? err.message : String(err);
169
- log.error('Perplexity fetch error', { error: msg });
170
-
178
+ log.error("Perplexity fetch error", { error: msg });
179
+
171
180
  return {
172
181
  fetchedAt: Date.now(),
173
182
  error: `Connection error: ${msg}`,
@@ -1,15 +1,15 @@
1
1
  import type {
2
- ProviderPlugin,
2
+ CredentialResult,
3
+ Credentials,
4
+ PluginContext,
5
+ ProviderAuth,
3
6
  ProviderFetchContext,
7
+ ProviderPlugin,
4
8
  ProviderUsageData,
5
9
  UsageLimit,
6
- ProviderAuth,
7
- PluginContext,
8
- CredentialResult,
9
- Credentials,
10
- } from '../types/provider.ts';
10
+ } from "../types/provider.ts";
11
11
 
12
- const ZAI_USAGE_URL = 'https://api.z.ai/api/monitor/usage/quota/limit';
12
+ const ZAI_USAGE_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
13
13
 
14
14
  interface ZaiLimitEntry {
15
15
  type?: string;
@@ -28,29 +28,29 @@ interface ZaiUsageResponse {
28
28
 
29
29
  export const zaiCodingPlanPlugin: ProviderPlugin = {
30
30
  apiVersion: 2,
31
- id: 'zai-coding-plan',
32
- type: 'provider',
33
- name: 'Z.ai Coding Plan',
34
- version: '1.0.0',
31
+ id: "zai-coding-plan",
32
+ type: "provider",
33
+ name: "Z.ai Coding Plan",
34
+ version: "1.0.0",
35
35
 
36
36
  meta: {
37
- description: 'Z.ai coding plan usage and quota tracking',
38
- homepage: 'https://z.ai',
39
- brandColor: '#10b981',
37
+ description: "Z.ai coding plan usage and quota tracking",
38
+ homepage: "https://z.ai",
39
+ brandColor: "#10b981",
40
40
  },
41
41
 
42
42
  permissions: {
43
43
  network: {
44
44
  enabled: true,
45
- allowedDomains: ['api.z.ai'],
45
+ allowedDomains: ["api.z.ai"],
46
46
  },
47
47
  filesystem: {
48
48
  read: true,
49
- paths: ['~/.local/share/opencode'],
49
+ paths: ["~/.local/share/opencode"],
50
50
  },
51
51
  env: {
52
52
  read: true,
53
- vars: ['ZAI_API_KEY'],
53
+ vars: ["ZAI_API_KEY"],
54
54
  },
55
55
  },
56
56
 
@@ -64,26 +64,35 @@ export const zaiCodingPlanPlugin: ProviderPlugin = {
64
64
  auth: {
65
65
  async discover(ctx: PluginContext): Promise<CredentialResult> {
66
66
  // 1. Try OpenCode auth (api, wellknown, or oauth)
67
- const entry = await ctx.authSources.opencode.getProviderEntry('zai-coding-plan');
67
+ const entry =
68
+ (await ctx.authSources.opencode.getProviderEntry("zhipuai-coding-plan")) ||
69
+ (await ctx.authSources.opencode.getProviderEntry("zai-coding-plan"));
68
70
  if (entry) {
69
- if (entry.type === 'api' && entry.key) {
70
- return { ok: true, credentials: { apiKey: entry.key, source: 'opencode' } };
71
+ if (entry.type === "api" && entry.key) {
72
+ return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
71
73
  }
72
- if (entry.type === 'wellknown' && (entry.token || entry.key)) {
73
- return { ok: true, credentials: { apiKey: (entry.token || entry.key)!, source: 'opencode' } };
74
+ if (entry.type === "wellknown" && (entry.token || entry.key)) {
75
+ return {
76
+ ok: true,
77
+ credentials: { apiKey: (entry.token || entry.key)!, source: "opencode" },
78
+ };
74
79
  }
75
- if (entry.type === 'oauth' && entry.access) {
76
- return { ok: true, credentials: { apiKey: entry.access, source: 'opencode' } };
80
+ if (entry.type === "oauth" && entry.access) {
81
+ return { ok: true, credentials: { apiKey: entry.access, source: "opencode" } };
77
82
  }
78
83
  }
79
84
 
80
85
  // 2. Try env vars
81
- const apiKey = ctx.authSources.env.get('ZAI_API_KEY');
86
+ const apiKey = ctx.authSources.env.get("ZAI_API_KEY");
82
87
  if (apiKey) {
83
- return { ok: true, credentials: { apiKey, source: 'env' } };
88
+ return { ok: true, credentials: { apiKey, source: "env" } };
84
89
  }
85
90
 
86
- return { ok: false, reason: 'missing', message: 'No Z.ai API key found. Configure Z.ai in OpenCode.' };
91
+ return {
92
+ ok: false,
93
+ reason: "missing",
94
+ message: "No Z.ai API key found. Configure Z.ai in OpenCode.",
95
+ };
87
96
  },
88
97
 
89
98
  isConfigured(credentials: Credentials): boolean {
@@ -99,29 +108,29 @@ export const zaiCodingPlanPlugin: ProviderPlugin = {
99
108
  if (!token) {
100
109
  return {
101
110
  fetchedAt: Date.now(),
102
- error: 'API key required. Configure Z.ai in OpenCode.',
111
+ error: "API key required. Configure Z.ai in OpenCode.",
103
112
  };
104
113
  }
105
114
 
106
115
  try {
107
116
  const response = await http.fetch(ZAI_USAGE_URL, {
108
- method: 'GET',
117
+ method: "GET",
109
118
  headers: {
110
- 'Authorization': `Bearer ${token}`,
111
- 'Accept': 'application/json',
119
+ Authorization: `Bearer ${token}`,
120
+ Accept: "application/json",
112
121
  },
113
122
  });
114
123
 
115
124
  if (!response.ok) {
116
- log.warn('Failed to fetch Z.ai usage', { status: response.status });
117
-
125
+ log.warn("Failed to fetch Z.ai usage", { status: response.status });
126
+
118
127
  if (response.status === 401 || response.status === 403) {
119
128
  return {
120
129
  fetchedAt: Date.now(),
121
- error: 'Authorization failed. Check your Z.ai API key.',
130
+ error: "Authorization failed. Check your Z.ai API key.",
122
131
  };
123
132
  }
124
-
133
+
125
134
  return {
126
135
  fetchedAt: Date.now(),
127
136
  error: `API error: ${response.status} ${response.statusText}`,
@@ -133,7 +142,7 @@ export const zaiCodingPlanPlugin: ProviderPlugin = {
133
142
  if (!data || data.success !== true) {
134
143
  return {
135
144
  fetchedAt: Date.now(),
136
- planType: 'Z.ai',
145
+ planType: "Z.ai",
137
146
  allowed: true,
138
147
  };
139
148
  }
@@ -142,18 +151,18 @@ export const zaiCodingPlanPlugin: ProviderPlugin = {
142
151
  if (limits.length === 0) {
143
152
  return {
144
153
  fetchedAt: Date.now(),
145
- planType: 'Z.ai',
154
+ planType: "Z.ai",
146
155
  allowed: true,
147
156
  };
148
157
  }
149
158
 
150
159
  const parsedLimits = limits
151
- .map(limit => ({
160
+ .map((limit) => ({
152
161
  type: limit.type,
153
162
  usedPercent: resolveUsedPercent(limit),
154
163
  remaining: limit.remaining,
155
164
  }))
156
- .filter(l => l.usedPercent !== null);
165
+ .filter((l) => l.usedPercent !== null);
157
166
 
158
167
  let primary: UsageLimit | undefined;
159
168
  for (const limit of parsedLimits) {
@@ -166,13 +175,14 @@ export const zaiCodingPlanPlugin: ProviderPlugin = {
166
175
  }
167
176
  }
168
177
 
169
- const limitReached = parsedLimits.some(l =>
170
- (l.usedPercent !== null && l.usedPercent >= 100) ||
171
- (typeof l.remaining === 'number' && l.remaining <= 0)
178
+ const limitReached = parsedLimits.some(
179
+ (l) =>
180
+ (l.usedPercent !== null && l.usedPercent >= 100) ||
181
+ (typeof l.remaining === "number" && l.remaining <= 0),
172
182
  );
173
183
 
174
184
  const result: ProviderUsageData = {
175
- planType: 'Z.ai Coding Plan',
185
+ planType: "Z.ai Coding Plan",
176
186
  allowed: !limitReached,
177
187
  limitReached,
178
188
  fetchedAt: Date.now(),
@@ -184,26 +194,27 @@ export const zaiCodingPlanPlugin: ProviderPlugin = {
184
194
 
185
195
  return result;
186
196
  } catch (err) {
187
- log.error('Failed to fetch Z.ai usage', { error: err });
197
+ log.error("Failed to fetch Z.ai usage", { error: err });
188
198
  return {
189
199
  fetchedAt: Date.now(),
190
- error: err instanceof Error ? err.message : 'Unknown error',
200
+ error: err instanceof Error ? err.message : "Unknown error",
191
201
  };
192
202
  }
193
203
  },
194
204
  };
195
205
 
196
206
  function resolveUsedPercent(limit: ZaiLimitEntry): number | null {
197
- if (typeof limit.percentage === 'number') {
207
+ if (typeof limit.percentage === "number") {
198
208
  return Math.round(Math.max(0, Math.min(100, limit.percentage)));
199
209
  }
200
210
 
201
211
  const total = limit.usage;
202
- const used = typeof limit.currentValue === 'number'
203
- ? limit.currentValue
204
- : (total !== undefined && typeof limit.remaining === 'number')
205
- ? Math.max(0, total - limit.remaining)
206
- : undefined;
212
+ const used =
213
+ typeof limit.currentValue === "number"
214
+ ? limit.currentValue
215
+ : total !== undefined && typeof limit.remaining === "number"
216
+ ? Math.max(0, total - limit.remaining)
217
+ : undefined;
207
218
 
208
219
  if (total && used !== undefined && total > 0) {
209
220
  return Math.round((used / total) * 100);
@@ -213,13 +224,13 @@ function resolveUsedPercent(limit: ZaiLimitEntry): number | null {
213
224
  }
214
225
 
215
226
  function formatLimitType(type?: string): string {
216
- if (!type) return 'Quota';
227
+ if (!type) return "Quota";
217
228
  switch (type) {
218
- case 'TIME_LIMIT':
219
- return 'Time';
220
- case 'TOKENS_LIMIT':
221
- return 'Tokens';
229
+ case "TIME_LIMIT":
230
+ return "Time";
231
+ case "TOKENS_LIMIT":
232
+ return "Tokens";
222
233
  default:
223
- return type.replace(/_/g, ' ').toLowerCase();
234
+ return type.replace(/_/g, " ").toLowerCase();
224
235
  }
225
236
  }