@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.
Files changed (158) 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 +32 -32
  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 +29 -23
  19. package/src/plugins/notifications/visual-flash.ts +21 -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 +66 -57
  34. package/src/plugins/registry.ts +47 -43
  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/repos/agentSessions.ts +33 -23
  73. package/src/storage/repos/providerSnapshots.ts +5 -5
  74. package/src/storage/repos/usageEvents.ts +40 -28
  75. package/src/storage/types.ts +5 -5
  76. package/src/tui/App.tsx +180 -137
  77. package/src/tui/components/CommandPalette.tsx +46 -34
  78. package/src/tui/components/DebugConsole.tsx +39 -33
  79. package/src/tui/components/DebugPanel.tsx +295 -177
  80. package/src/tui/components/FooterHints.tsx +2 -2
  81. package/src/tui/components/GhostProviderCard.tsx +37 -35
  82. package/src/tui/components/Header.tsx +68 -35
  83. package/src/tui/components/HelpOverlay.tsx +99 -29
  84. package/src/tui/components/InlineGauge.tsx +10 -7
  85. package/src/tui/components/InlineSparkline.tsx +17 -11
  86. package/src/tui/components/KpiStrip.tsx +128 -98
  87. package/src/tui/components/LimitGauge.tsx +96 -59
  88. package/src/tui/components/ModalBackdrop.tsx +4 -4
  89. package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
  90. package/src/tui/components/ProviderCard.tsx +232 -226
  91. package/src/tui/components/ProviderDetailPanel.tsx +133 -58
  92. package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
  93. package/src/tui/components/ProvidersList.tsx +215 -126
  94. package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
  95. package/src/tui/components/SessionsTable.tsx +313 -166
  96. package/src/tui/components/SettingsModal.tsx +231 -214
  97. package/src/tui/components/SidebarBreakdown.tsx +30 -16
  98. package/src/tui/components/Skeleton.tsx +24 -17
  99. package/src/tui/components/SmartSidebar.tsx +360 -258
  100. package/src/tui/components/Sparkline.tsx +64 -52
  101. package/src/tui/components/Spinner.tsx +3 -3
  102. package/src/tui/components/StatusBar.tsx +27 -27
  103. package/src/tui/components/ThemePicker.tsx +67 -66
  104. package/src/tui/components/Toast.tsx +9 -6
  105. package/src/tui/components/UsageGauge.tsx +10 -13
  106. package/src/tui/components/index.ts +5 -5
  107. package/src/tui/contexts/AgentSessionContext.tsx +193 -144
  108. package/src/tui/contexts/ConfigContext.tsx +15 -13
  109. package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
  110. package/src/tui/contexts/DemoModeContext.tsx +22 -15
  111. package/src/tui/contexts/DrawerContext.tsx +11 -9
  112. package/src/tui/contexts/InputContext.tsx +2 -2
  113. package/src/tui/contexts/LogContext.tsx +62 -47
  114. package/src/tui/contexts/PluginContext.tsx +232 -183
  115. package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
  116. package/src/tui/contexts/StorageContext.tsx +151 -117
  117. package/src/tui/contexts/ThemeContext.tsx +21 -16
  118. package/src/tui/contexts/TimeWindowContext.tsx +72 -61
  119. package/src/tui/contexts/ToastContext.tsx +10 -7
  120. package/src/tui/contexts/index.ts +9 -5
  121. package/src/tui/createApp.tsx +8 -12
  122. package/src/tui/debug/captureFrame.ts +25 -28
  123. package/src/tui/debug/snapshot.tsx +179 -162
  124. package/src/tui/driver/assertions.ts +30 -22
  125. package/src/tui/driver/cli.ts +231 -221
  126. package/src/tui/driver/coverage.ts +27 -44
  127. package/src/tui/driver/demo-snapshot.ts +33 -33
  128. package/src/tui/driver/diff.ts +26 -32
  129. package/src/tui/driver/driver.ts +43 -48
  130. package/src/tui/driver/index.ts +36 -40
  131. package/src/tui/driver/recorder.ts +37 -48
  132. package/src/tui/driver/test-workflow.ts +51 -51
  133. package/src/tui/driver/test.ts +25 -25
  134. package/src/tui/hooks/index.ts +24 -20
  135. package/src/tui/hooks/useAnimatedValue.ts +13 -18
  136. package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
  137. package/src/tui/hooks/useDashboardState.ts +26 -24
  138. package/src/tui/hooks/useEmaActivity.ts +82 -74
  139. package/src/tui/hooks/useEntranceAnimation.ts +11 -11
  140. package/src/tui/hooks/useExitAnimation.ts +3 -3
  141. package/src/tui/hooks/usePulse.ts +4 -4
  142. package/src/tui/hooks/useSafeRenderer.ts +5 -5
  143. package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
  144. package/src/tui/hooks/useValueFlash.ts +10 -14
  145. package/src/tui/index.tsx +29 -6
  146. package/src/tui/shutdown.ts +19 -0
  147. package/src/tui/utils/providerColor.ts +1 -1
  148. package/src/tui/views/Dashboard.tsx +127 -102
  149. package/src/tui/views/HistoricalTrendsView.tsx +579 -250
  150. package/src/tui/views/ProjectsView.tsx +466 -237
  151. package/src/tui/views/RealTimeDashboard.tsx +150 -111
  152. package/src/tui/views/SettingsView.tsx +222 -191
  153. package/src/tui/views/index.ts +2 -2
  154. package/src/types/opentui-test-utils.d.ts +7 -7
  155. package/src/utils/clipboard.ts +29 -29
  156. package/src/utils/currency.test.ts +53 -53
  157. package/src/utils/currency.ts +35 -32
  158. package/src/version.ts +1 -1
@@ -1,14 +1,14 @@
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
- const MINIMAX_USAGE_URL = 'https://platform.minimax.io/v1/api/openplatform/coding_plan/remains';
11
+ const MINIMAX_USAGE_URL = "https://platform.minimax.io/v1/api/openplatform/coding_plan/remains";
12
12
 
13
13
  interface MinimaxModelRemain {
14
14
  model_name?: string;
@@ -29,28 +29,28 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
29
29
  return {
30
30
  apiVersion: 2,
31
31
  id,
32
- type: 'provider',
32
+ type: "provider",
33
33
  name,
34
- version: '1.0.0',
34
+ version: "1.0.0",
35
35
 
36
36
  meta: {
37
37
  description: `${name} usage and quota tracking`,
38
- homepage: 'https://platform.minimax.io',
39
- brandColor: '#6366f1',
38
+ homepage: "https://platform.minimax.io",
39
+ brandColor: "#6366f1",
40
40
  },
41
41
 
42
42
  permissions: {
43
43
  network: {
44
44
  enabled: true,
45
- allowedDomains: ['platform.minimax.io'],
45
+ allowedDomains: ["platform.minimax.io"],
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: ['MINIMAX_API_KEY', 'MINIMAX_GROUP_ID'],
53
+ vars: ["MINIMAX_API_KEY", "MINIMAX_GROUP_ID"],
54
54
  },
55
55
  },
56
56
 
@@ -66,43 +66,47 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
66
66
  // 1. Try OpenCode auth (api type with key + groupId)
67
67
  const entry = await ctx.authSources.opencode.getProviderEntry(id);
68
68
  if (entry) {
69
- if (entry.type === 'api' && entry.key) {
69
+ if (entry.type === "api" && entry.key) {
70
70
  return {
71
71
  ok: true,
72
72
  credentials: {
73
73
  apiKey: entry.key,
74
74
  ...(entry.groupId && { groupId: entry.groupId }),
75
- source: 'opencode',
75
+ source: "opencode",
76
76
  },
77
77
  };
78
78
  }
79
- if (entry.type === 'wellknown' && (entry.token || entry.key)) {
79
+ if (entry.type === "wellknown" && (entry.token || entry.key)) {
80
80
  return {
81
81
  ok: true,
82
82
  credentials: {
83
83
  apiKey: (entry.token || entry.key)!,
84
84
  ...(entry.groupId && { groupId: entry.groupId }),
85
- source: 'opencode',
85
+ source: "opencode",
86
86
  },
87
87
  };
88
88
  }
89
89
  }
90
90
 
91
91
  // 2. Try env vars
92
- const apiKey = ctx.authSources.env.get('MINIMAX_API_KEY');
92
+ const apiKey = ctx.authSources.env.get("MINIMAX_API_KEY");
93
93
  if (apiKey) {
94
- const groupId = ctx.authSources.env.get('MINIMAX_GROUP_ID');
94
+ const groupId = ctx.authSources.env.get("MINIMAX_GROUP_ID");
95
95
  return {
96
96
  ok: true,
97
97
  credentials: {
98
98
  apiKey,
99
99
  ...(groupId && { groupId }),
100
- source: 'env',
100
+ source: "env",
101
101
  },
102
102
  };
103
103
  }
104
104
 
105
- return { ok: false, reason: 'missing', message: 'No MiniMax API key found. Configure MiniMax in OpenCode.' };
105
+ return {
106
+ ok: false,
107
+ reason: "missing",
108
+ message: "No MiniMax API key found. Configure MiniMax in OpenCode.",
109
+ };
106
110
  },
107
111
 
108
112
  isConfigured(credentials: Credentials): boolean {
@@ -117,42 +121,43 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
117
121
  if (!apiKey) {
118
122
  return {
119
123
  fetchedAt: Date.now(),
120
- error: 'API key required. Configure MiniMax in OpenCode.',
124
+ error: "API key required. Configure MiniMax in OpenCode.",
121
125
  };
122
126
  }
123
127
 
124
- const groupId = credentials.groupId ||
128
+ const groupId =
129
+ credentials.groupId ||
125
130
  (config.groupId as string | undefined) ||
126
131
  process.env.MINIMAX_GROUP_ID;
127
132
 
128
133
  if (!groupId) {
129
134
  return {
130
135
  fetchedAt: Date.now(),
131
- error: 'MiniMax groupId required. Set MINIMAX_GROUP_ID or configure in OpenCode.',
136
+ error: "MiniMax groupId required. Set MINIMAX_GROUP_ID or configure in OpenCode.",
132
137
  };
133
138
  }
134
139
 
135
140
  try {
136
141
  const url = `${MINIMAX_USAGE_URL}?GroupId=${encodeURIComponent(groupId)}`;
137
142
  const response = await http.fetch(url, {
138
- method: 'GET',
143
+ method: "GET",
139
144
  headers: {
140
- 'Authorization': `Bearer ${apiKey}`,
141
- 'Referer': 'https://platform.minimax.io/user-center/payment/coding-plan',
142
- 'Accept': 'application/json',
145
+ Authorization: `Bearer ${apiKey}`,
146
+ Referer: "https://platform.minimax.io/user-center/payment/coding-plan",
147
+ Accept: "application/json",
143
148
  },
144
149
  });
145
150
 
146
151
  if (!response.ok) {
147
- log.warn('Failed to fetch MiniMax usage', { status: response.status });
148
-
152
+ log.warn("Failed to fetch MiniMax usage", { status: response.status });
153
+
149
154
  if (response.status === 401 || response.status === 403) {
150
155
  return {
151
156
  fetchedAt: Date.now(),
152
- error: 'Authorization failed. Check your MiniMax API key.',
157
+ error: "Authorization failed. Check your MiniMax API key.",
153
158
  };
154
159
  }
155
-
160
+
156
161
  return {
157
162
  fetchedAt: Date.now(),
158
163
  error: `API error: ${response.status} ${response.statusText}`,
@@ -162,7 +167,7 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
162
167
  const data = (await response.json()) as MinimaxUsageResponse;
163
168
 
164
169
  if (!data || data.base_resp?.status_code !== 0) {
165
- const errorMsg = data?.base_resp?.status_msg || 'Unknown error';
170
+ const errorMsg = data?.base_resp?.status_msg || "Unknown error";
166
171
  return {
167
172
  fetchedAt: Date.now(),
168
173
  error: `MiniMax API error: ${errorMsg}`,
@@ -173,7 +178,7 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
173
178
  if (modelRemains.length === 0) {
174
179
  return {
175
180
  fetchedAt: Date.now(),
176
- planType: 'MiniMax Coding Plan',
181
+ planType: "MiniMax Coding Plan",
177
182
  allowed: true,
178
183
  };
179
184
  }
@@ -184,20 +189,18 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
184
189
  const remainsTime = model.remains_time ?? 0;
185
190
 
186
191
  const usedCount = totalCount > 0 ? totalCount - remainingCount : 0;
187
- const usedPercent = totalCount > 0
188
- ? Math.round((usedCount / totalCount) * 100)
189
- : 0;
192
+ const usedPercent = totalCount > 0 ? Math.round((usedCount / totalCount) * 100) : 0;
190
193
 
191
194
  const limitReached = usedCount >= totalCount && totalCount > 0;
192
195
 
193
196
  const result: ProviderUsageData = {
194
- planType: model.model_name || 'MiniMax Coding Plan',
197
+ planType: model.model_name || "MiniMax Coding Plan",
195
198
  allowed: !limitReached,
196
199
  limitReached,
197
200
  limits: {
198
201
  primary: {
199
202
  usedPercent,
200
- label: 'Requests',
203
+ label: "Requests",
201
204
  },
202
205
  },
203
206
  fetchedAt: Date.now(),
@@ -209,15 +212,18 @@ function createMinimaxPlugin(id: string, name: string): ProviderPlugin {
209
212
 
210
213
  return result;
211
214
  } catch (err) {
212
- log.error('Failed to fetch MiniMax usage', { error: err });
215
+ log.error("Failed to fetch MiniMax usage", { error: err });
213
216
  return {
214
217
  fetchedAt: Date.now(),
215
- error: err instanceof Error ? err.message : 'Unknown error',
218
+ error: err instanceof Error ? err.message : "Unknown error",
216
219
  };
217
220
  }
218
221
  },
219
222
  };
220
223
  }
221
224
 
222
- export const minimaxPlugin = createMinimaxPlugin('minimax', 'MiniMax');
223
- export const minimaxCodingPlanPlugin = createMinimaxPlugin('minimax-coding-plan', 'MiniMax Coding Plan');
225
+ export const minimaxPlugin = createMinimaxPlugin("minimax", "MiniMax");
226
+ export const minimaxCodingPlanPlugin = createMinimaxPlugin(
227
+ "minimax-coding-plan",
228
+ "MiniMax Coding Plan",
229
+ );
@@ -1,12 +1,12 @@
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
  interface UsageBucket {
12
12
  object: string;
@@ -51,26 +51,26 @@ interface CostsResponse {
51
51
 
52
52
  export const openaiApiPlugin: ProviderPlugin = {
53
53
  apiVersion: 2,
54
- id: 'openai-api',
55
- type: 'provider',
56
- name: 'OpenAI API',
57
- version: '1.0.0',
54
+ id: "openai-api",
55
+ type: "provider",
56
+ name: "OpenAI API",
57
+ version: "1.0.0",
58
58
 
59
59
  meta: {
60
- description: 'OpenAI Codex subscription usage tracking (OAuth)',
61
- homepage: 'https://openai.com/codex',
62
- brandColor: '#10a37f',
63
- providerAliases: ['openai'],
60
+ description: "OpenAI Codex subscription usage tracking (OAuth)",
61
+ homepage: "https://openai.com/codex",
62
+ brandColor: "#10a37f",
63
+ providerAliases: ["openai"],
64
64
  },
65
65
 
66
66
  permissions: {
67
67
  network: {
68
68
  enabled: true,
69
- allowedDomains: ['api.openai.com'],
69
+ allowedDomains: ["api.openai.com"],
70
70
  },
71
71
  env: {
72
72
  read: true,
73
- vars: ['OPENAI_API_KEY', 'OPENAI_ADMIN_KEY'],
73
+ vars: ["OPENAI_API_KEY", "OPENAI_ADMIN_KEY"],
74
74
  },
75
75
  },
76
76
 
@@ -82,33 +82,40 @@ export const openaiApiPlugin: ProviderPlugin = {
82
82
  },
83
83
 
84
84
  pricing: {
85
- modelsDevProviderId: 'openai',
85
+ modelsDevProviderId: "openai",
86
86
  },
87
87
 
88
88
  auth: {
89
89
  async discover(ctx: PluginContext): Promise<CredentialResult> {
90
- const entry = await ctx.authSources.opencode.getProviderEntry('openai');
90
+ const entry = await ctx.authSources.opencode.getProviderEntry("openai");
91
91
  if (entry) {
92
- if (entry.type === 'api' && entry.key) {
93
- return { ok: true, credentials: { apiKey: entry.key, source: 'opencode' } };
92
+ if (entry.type === "api" && entry.key) {
93
+ return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
94
94
  }
95
- if (entry.type === 'wellknown' && (entry.token || entry.key)) {
96
- return { ok: true, credentials: { apiKey: (entry.token || entry.key)!, source: 'opencode' } };
95
+ if (entry.type === "wellknown" && (entry.token || entry.key)) {
96
+ return {
97
+ ok: true,
98
+ credentials: { apiKey: (entry.token || entry.key)!, source: "opencode" },
99
+ };
97
100
  }
98
101
  }
99
102
 
100
103
  // 2. Try env vars (OPENAI_API_KEY, then OPENAI_ADMIN_KEY)
101
- const apiKey = ctx.authSources.env.get('OPENAI_API_KEY');
104
+ const apiKey = ctx.authSources.env.get("OPENAI_API_KEY");
102
105
  if (apiKey) {
103
- return { ok: true, credentials: { apiKey, source: 'env' } };
106
+ return { ok: true, credentials: { apiKey, source: "env" } };
104
107
  }
105
108
 
106
- const adminKey = ctx.authSources.env.get('OPENAI_ADMIN_KEY');
109
+ const adminKey = ctx.authSources.env.get("OPENAI_ADMIN_KEY");
107
110
  if (adminKey) {
108
- return { ok: true, credentials: { apiKey: adminKey, source: 'env' } };
111
+ return { ok: true, credentials: { apiKey: adminKey, source: "env" } };
109
112
  }
110
113
 
111
- return { ok: false, reason: 'missing', message: 'No OpenAI API key found. Set OPENAI_API_KEY or configure in OpenCode.' };
114
+ return {
115
+ ok: false,
116
+ reason: "missing",
117
+ message: "No OpenAI API key found. Set OPENAI_API_KEY or configure in OpenCode.",
118
+ };
112
119
  },
113
120
 
114
121
  isConfigured(credentials: Credentials): boolean {
@@ -122,7 +129,7 @@ export const openaiApiPlugin: ProviderPlugin = {
122
129
  if (!credentials.apiKey) {
123
130
  return {
124
131
  fetchedAt: Date.now(),
125
- error: 'API key required. Set OPENAI_API_KEY environment variable.',
132
+ error: "API key required. Set OPENAI_API_KEY environment variable.",
126
133
  };
127
134
  }
128
135
 
@@ -130,7 +137,7 @@ export const openaiApiPlugin: ProviderPlugin = {
130
137
  const now = Math.floor(Date.now() / 1000);
131
138
  const oneDayAgo = now - 86400;
132
139
 
133
- log.info('Fetching OpenAI usage', { keyPrefix: credentials.apiKey.slice(0, 10) });
140
+ log.info("Fetching OpenAI usage", { keyPrefix: credentials.apiKey.slice(0, 10) });
134
141
 
135
142
  try {
136
143
  const [usageResult, costsResult] = await Promise.allSettled([
@@ -139,19 +146,26 @@ export const openaiApiPlugin: ProviderPlugin = {
139
146
  ]);
140
147
 
141
148
  const result: ProviderUsageData = {
142
- planType: 'API',
149
+ planType: "API",
143
150
  allowed: true,
144
151
  fetchedAt: Date.now(),
145
152
  };
146
153
 
147
- if (usageResult.status === 'fulfilled' && usageResult.value) {
154
+ if (usageResult.status === "fulfilled" && usageResult.value) {
148
155
  const usage = usageResult.value;
149
- const totalInput = usage.data.reduce((sum, bucket) =>
150
- sum + bucket.results.reduce((s, r) => s + (r.input_tokens || 0), 0), 0);
151
- const totalOutput = usage.data.reduce((sum, bucket) =>
152
- sum + bucket.results.reduce((s, r) => s + (r.output_tokens || 0), 0), 0);
153
- const totalCached = usage.data.reduce((sum, bucket) =>
154
- sum + bucket.results.reduce((s, r) => s + (r.input_cached_tokens || 0), 0), 0);
156
+ const totalInput = usage.data.reduce(
157
+ (sum, bucket) => sum + bucket.results.reduce((s, r) => s + (r.input_tokens || 0), 0),
158
+ 0,
159
+ );
160
+ const totalOutput = usage.data.reduce(
161
+ (sum, bucket) => sum + bucket.results.reduce((s, r) => s + (r.output_tokens || 0), 0),
162
+ 0,
163
+ );
164
+ const totalCached = usage.data.reduce(
165
+ (sum, bucket) =>
166
+ sum + bucket.results.reduce((s, r) => s + (r.input_cached_tokens || 0), 0),
167
+ 0,
168
+ );
155
169
 
156
170
  result.tokens = {
157
171
  input: totalInput,
@@ -159,17 +173,23 @@ export const openaiApiPlugin: ProviderPlugin = {
159
173
  cacheRead: totalCached,
160
174
  };
161
175
 
162
- log.debug('OpenAI tokens (24h)', { input: totalInput, output: totalOutput, cached: totalCached });
163
- } else if (usageResult.status === 'rejected') {
164
- log.warn('Failed to fetch OpenAI usage', { error: usageResult.reason?.message });
176
+ log.debug("OpenAI tokens (24h)", {
177
+ input: totalInput,
178
+ output: totalOutput,
179
+ cached: totalCached,
180
+ });
181
+ } else if (usageResult.status === "rejected") {
182
+ log.warn("Failed to fetch OpenAI usage", { error: usageResult.reason?.message });
165
183
  }
166
184
 
167
- if (costsResult.status === 'fulfilled' && costsResult.value) {
185
+ if (costsResult.status === "fulfilled" && costsResult.value) {
168
186
  const costs = costsResult.value;
169
- const totalCost = costs.data.reduce((sum, bucket) =>
170
- sum + bucket.results.reduce((s, r) => s + (r.amount?.value || 0), 0), 0);
171
-
172
- const currency = costs.data[0]?.results[0]?.amount?.currency || 'USD';
187
+ const totalCost = costs.data.reduce(
188
+ (sum, bucket) => sum + bucket.results.reduce((s, r) => s + (r.amount?.value || 0), 0),
189
+ 0,
190
+ );
191
+
192
+ const currency = costs.data[0]?.results[0]?.amount?.currency || "USD";
173
193
 
174
194
  if (totalCost > 0) {
175
195
  result.cost = {
@@ -177,22 +197,22 @@ export const openaiApiPlugin: ProviderPlugin = {
177
197
  total: totalCost,
178
198
  currency,
179
199
  },
180
- source: 'api',
200
+ source: "api",
181
201
  };
182
202
  }
183
203
 
184
- log.debug('OpenAI costs (24h)', { total: totalCost, currency });
185
- } else if (costsResult.status === 'rejected') {
186
- log.warn('Failed to fetch OpenAI costs', { error: costsResult.reason?.message });
204
+ log.debug("OpenAI costs (24h)", { total: totalCost, currency });
205
+ } else if (costsResult.status === "rejected") {
206
+ log.warn("Failed to fetch OpenAI costs", { error: costsResult.reason?.message });
187
207
  }
188
208
 
189
209
  return result;
190
210
  } catch (err) {
191
211
  const msg = err instanceof Error ? err.message : String(err);
192
- log.error('OpenAI fetch error', { error: msg });
193
-
212
+ log.error("OpenAI fetch error", { error: msg });
213
+
194
214
  return {
195
- planType: 'API',
215
+ planType: "API",
196
216
  allowed: true,
197
217
  fetchedAt: Date.now(),
198
218
  error: `Failed to fetch usage: ${msg}`,
@@ -202,16 +222,16 @@ export const openaiApiPlugin: ProviderPlugin = {
202
222
  };
203
223
 
204
224
  async function fetchCompletionsUsage(
205
- http: ProviderFetchContext['http'],
225
+ http: ProviderFetchContext["http"],
206
226
  apiKey: string,
207
- startTime: number
227
+ startTime: number,
208
228
  ): Promise<UsageResponse | null> {
209
229
  const url = `https://api.openai.com/v1/organization/usage/completions?start_time=${startTime}&bucket_width=1d&limit=1`;
210
-
230
+
211
231
  const response = await http.fetch(url, {
212
232
  headers: {
213
- 'Authorization': `Bearer ${apiKey}`,
214
- 'Content-Type': 'application/json',
233
+ Authorization: `Bearer ${apiKey}`,
234
+ "Content-Type": "application/json",
215
235
  },
216
236
  });
217
237
 
@@ -226,16 +246,16 @@ async function fetchCompletionsUsage(
226
246
  }
227
247
 
228
248
  async function fetchCosts(
229
- http: ProviderFetchContext['http'],
249
+ http: ProviderFetchContext["http"],
230
250
  apiKey: string,
231
- startTime: number
251
+ startTime: number,
232
252
  ): Promise<CostsResponse | null> {
233
253
  const url = `https://api.openai.com/v1/organization/usage/costs?start_time=${startTime}&bucket_width=1d&limit=1`;
234
-
254
+
235
255
  const response = await http.fetch(url, {
236
256
  headers: {
237
- 'Authorization': `Bearer ${apiKey}`,
238
- 'Content-Type': 'application/json',
257
+ Authorization: `Bearer ${apiKey}`,
258
+ "Content-Type": "application/json",
239
259
  },
240
260
  });
241
261
 
@@ -1,73 +1,80 @@
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
  export const opencodeZenPlugin: ProviderPlugin = {
12
12
  apiVersion: 2,
13
- id: 'opencode-zen',
14
- type: 'provider',
15
- name: 'OpenCode Zen',
16
- version: '1.0.0',
13
+ id: "opencode-zen",
14
+ type: "provider",
15
+ name: "OpenCode Zen",
16
+ version: "1.0.0",
17
17
 
18
18
  meta: {
19
- description: 'OpenCode Zen - curated AI models for coding agents',
20
- homepage: 'https://opencode.ai/zen',
21
- brandColor: '#6366f1',
22
- providerAliases: ['opencode'],
19
+ description: "OpenCode Zen - curated AI models for coding agents",
20
+ homepage: "https://opencode.ai/zen",
21
+ brandColor: "#6366f1",
22
+ providerAliases: ["opencode"],
23
23
  },
24
24
 
25
25
  permissions: {
26
26
  network: {
27
27
  enabled: true,
28
- allowedDomains: ['opencode.ai'],
28
+ allowedDomains: ["opencode.ai"],
29
29
  },
30
30
  filesystem: {
31
31
  read: true,
32
- paths: ['~/.local/share/opencode'],
32
+ paths: ["~/.local/share/opencode"],
33
33
  },
34
34
  env: {
35
35
  read: true,
36
- vars: ['OPENCODE_API_KEY'],
36
+ vars: ["OPENCODE_API_KEY"],
37
37
  },
38
38
  },
39
39
 
40
40
  capabilities: {
41
41
  usageLimits: false,
42
42
  apiRateLimits: false,
43
- tokenUsage: true,
44
- actualCosts: true,
43
+ tokenUsage: false,
44
+ actualCosts: false,
45
45
  },
46
46
 
47
47
  pricing: {
48
- modelsDevProviderId: 'anthropic',
48
+ modelsDevProviderId: "anthropic",
49
49
  },
50
50
 
51
51
  auth: {
52
52
  async discover(ctx: PluginContext): Promise<CredentialResult> {
53
53
  // 1. Try OpenCode auth (getProviderEntry reads auth.json and config)
54
- const entry = await ctx.authSources.opencode.getProviderEntry('opencode');
54
+ const entry = await ctx.authSources.opencode.getProviderEntry("opencode");
55
55
  if (entry) {
56
- if (entry.type === 'api' && entry.key) {
57
- return { ok: true, credentials: { apiKey: entry.key, source: 'opencode' } };
56
+ if (entry.type === "api" && entry.key) {
57
+ return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
58
58
  }
59
- if (entry.type === 'wellknown' && (entry.token || entry.key)) {
60
- return { ok: true, credentials: { apiKey: (entry.token || entry.key)!, source: 'opencode' } };
59
+ if (entry.type === "wellknown" && (entry.token || entry.key)) {
60
+ return {
61
+ ok: true,
62
+ credentials: { apiKey: (entry.token || entry.key)!, source: "opencode" },
63
+ };
61
64
  }
62
65
  }
63
66
 
64
67
  // 2. Try env vars
65
- const apiKey = ctx.authSources.env.get('OPENCODE_API_KEY');
68
+ const apiKey = ctx.authSources.env.get("OPENCODE_API_KEY");
66
69
  if (apiKey) {
67
- return { ok: true, credentials: { apiKey, source: 'env' } };
70
+ return { ok: true, credentials: { apiKey, source: "env" } };
68
71
  }
69
72
 
70
- return { ok: false, reason: 'missing', message: 'No OpenCode API key found. Run /connect in OpenCode to set up Zen.' };
73
+ return {
74
+ ok: false,
75
+ reason: "missing",
76
+ message: "No OpenCode API key found. Run /connect in OpenCode to set up Zen.",
77
+ };
71
78
  },
72
79
 
73
80
  isConfigured(credentials: Credentials): boolean {
@@ -81,23 +88,23 @@ export const opencodeZenPlugin: ProviderPlugin = {
81
88
  if (!credentials.apiKey) {
82
89
  return {
83
90
  fetchedAt: Date.now(),
84
- error: 'API key required. Run /connect in OpenCode to set up Zen.',
91
+ error: "API key required. Run /connect in OpenCode to set up Zen.",
85
92
  };
86
93
  }
87
94
 
88
- log.info('OpenCode Zen configured', { keyPrefix: credentials.apiKey.slice(0, 10) });
95
+ log.info("OpenCode Zen configured", { keyPrefix: credentials.apiKey.slice(0, 10) });
89
96
 
90
97
  try {
91
- const response = await http.fetch('https://opencode.ai/zen/v1/models', {
98
+ const response = await http.fetch("https://opencode.ai/zen/v1/models", {
92
99
  headers: {
93
- 'Authorization': `Bearer ${credentials.apiKey}`,
100
+ Authorization: `Bearer ${credentials.apiKey}`,
94
101
  },
95
102
  });
96
-
103
+
97
104
  if (response.ok) {
98
- log.info('OpenCode Zen API accessible');
105
+ log.info("OpenCode Zen API accessible");
99
106
  return {
100
- planType: 'Pay-as-you-go',
107
+ planType: "Pay-as-you-go",
101
108
  allowed: true,
102
109
  fetchedAt: Date.now(),
103
110
  credits: {
@@ -108,15 +115,15 @@ export const opencodeZenPlugin: ProviderPlugin = {
108
115
  }
109
116
 
110
117
  return {
111
- planType: 'Pay-as-you-go',
118
+ planType: "Pay-as-you-go",
112
119
  allowed: true,
113
120
  fetchedAt: Date.now(),
114
121
  };
115
122
  } catch (err) {
116
- log.warn('Could not verify OpenCode Zen API access', { error: String(err) });
117
-
123
+ log.warn("Could not verify OpenCode Zen API access", { error: String(err) });
124
+
118
125
  return {
119
- planType: 'Pay-as-you-go',
126
+ planType: "Pay-as-you-go",
120
127
  allowed: true,
121
128
  fetchedAt: Date.now(),
122
129
  };