@tokentop/ttop 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ProviderPlugin,
|
|
3
|
-
ProviderFetchContext,
|
|
4
|
-
ProviderUsageData,
|
|
5
|
-
ProviderAuth,
|
|
6
|
-
PluginContext,
|
|
7
2
|
CredentialResult,
|
|
8
3
|
Credentials,
|
|
9
|
-
|
|
4
|
+
PluginContext,
|
|
5
|
+
ProviderAuth,
|
|
6
|
+
ProviderFetchContext,
|
|
7
|
+
ProviderPlugin,
|
|
8
|
+
ProviderUsageData,
|
|
9
|
+
} from "../types/provider.ts";
|
|
10
10
|
|
|
11
|
-
const MINIMAX_USAGE_URL =
|
|
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:
|
|
32
|
+
type: "provider",
|
|
33
33
|
name,
|
|
34
|
-
version:
|
|
34
|
+
version: "1.0.0",
|
|
35
35
|
|
|
36
36
|
meta: {
|
|
37
37
|
description: `${name} usage and quota tracking`,
|
|
38
|
-
homepage:
|
|
39
|
-
brandColor:
|
|
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: [
|
|
45
|
+
allowedDomains: ["platform.minimax.io"],
|
|
46
46
|
},
|
|
47
47
|
filesystem: {
|
|
48
48
|
read: true,
|
|
49
|
-
paths: [
|
|
49
|
+
paths: ["~/.local/share/opencode"],
|
|
50
50
|
},
|
|
51
51
|
env: {
|
|
52
52
|
read: true,
|
|
53
|
-
vars: [
|
|
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 ===
|
|
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:
|
|
75
|
+
source: "opencode",
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
if (entry.type ===
|
|
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:
|
|
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(
|
|
92
|
+
const apiKey = ctx.authSources.env.get("MINIMAX_API_KEY");
|
|
93
93
|
if (apiKey) {
|
|
94
|
-
const groupId = ctx.authSources.env.get(
|
|
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:
|
|
100
|
+
source: "env",
|
|
101
101
|
},
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
return {
|
|
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:
|
|
124
|
+
error: "API key required. Configure MiniMax in OpenCode.",
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
const 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:
|
|
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:
|
|
143
|
+
method: "GET",
|
|
139
144
|
headers: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
|
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:
|
|
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 ||
|
|
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:
|
|
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 ||
|
|
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:
|
|
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(
|
|
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 :
|
|
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(
|
|
223
|
-
export const minimaxCodingPlanPlugin = createMinimaxPlugin(
|
|
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
|
-
|
|
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:
|
|
55
|
-
type:
|
|
56
|
-
name:
|
|
57
|
-
version:
|
|
54
|
+
id: "openai-api",
|
|
55
|
+
type: "provider",
|
|
56
|
+
name: "OpenAI API",
|
|
57
|
+
version: "1.0.0",
|
|
58
58
|
|
|
59
59
|
meta: {
|
|
60
|
-
description:
|
|
61
|
-
homepage:
|
|
62
|
-
brandColor:
|
|
63
|
-
providerAliases: [
|
|
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: [
|
|
69
|
+
allowedDomains: ["api.openai.com"],
|
|
70
70
|
},
|
|
71
71
|
env: {
|
|
72
72
|
read: true,
|
|
73
|
-
vars: [
|
|
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:
|
|
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(
|
|
90
|
+
const entry = await ctx.authSources.opencode.getProviderEntry("openai");
|
|
91
91
|
if (entry) {
|
|
92
|
-
if (entry.type ===
|
|
93
|
-
return { ok: true, credentials: { apiKey: entry.key, source:
|
|
92
|
+
if (entry.type === "api" && entry.key) {
|
|
93
|
+
return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
|
|
94
94
|
}
|
|
95
|
-
if (entry.type ===
|
|
96
|
-
return {
|
|
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(
|
|
104
|
+
const apiKey = ctx.authSources.env.get("OPENAI_API_KEY");
|
|
102
105
|
if (apiKey) {
|
|
103
|
-
return { ok: true, credentials: { apiKey, source:
|
|
106
|
+
return { ok: true, credentials: { apiKey, source: "env" } };
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
const adminKey = ctx.authSources.env.get(
|
|
109
|
+
const adminKey = ctx.authSources.env.get("OPENAI_ADMIN_KEY");
|
|
107
110
|
if (adminKey) {
|
|
108
|
-
return { ok: true, credentials: { apiKey: adminKey, source:
|
|
111
|
+
return { ok: true, credentials: { apiKey: adminKey, source: "env" } };
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
return {
|
|
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:
|
|
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(
|
|
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:
|
|
149
|
+
planType: "API",
|
|
143
150
|
allowed: true,
|
|
144
151
|
fetchedAt: Date.now(),
|
|
145
152
|
};
|
|
146
153
|
|
|
147
|
-
if (usageResult.status ===
|
|
154
|
+
if (usageResult.status === "fulfilled" && usageResult.value) {
|
|
148
155
|
const usage = usageResult.value;
|
|
149
|
-
const totalInput = usage.data.reduce(
|
|
150
|
-
sum + bucket.results.reduce((s, r) => s + (r.input_tokens || 0), 0),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
sum + bucket.results.reduce((s, r) => s + (r.
|
|
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(
|
|
163
|
-
|
|
164
|
-
|
|
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 ===
|
|
185
|
+
if (costsResult.status === "fulfilled" && costsResult.value) {
|
|
168
186
|
const costs = costsResult.value;
|
|
169
|
-
const totalCost = costs.data.reduce(
|
|
170
|
-
sum + bucket.results.reduce((s, r) => s + (r.amount?.value || 0), 0),
|
|
171
|
-
|
|
172
|
-
|
|
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:
|
|
200
|
+
source: "api",
|
|
181
201
|
};
|
|
182
202
|
}
|
|
183
203
|
|
|
184
|
-
log.debug(
|
|
185
|
-
} else if (costsResult.status ===
|
|
186
|
-
log.warn(
|
|
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(
|
|
193
|
-
|
|
212
|
+
log.error("OpenAI fetch error", { error: msg });
|
|
213
|
+
|
|
194
214
|
return {
|
|
195
|
-
planType:
|
|
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[
|
|
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
|
-
|
|
214
|
-
|
|
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[
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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:
|
|
14
|
-
type:
|
|
15
|
-
name:
|
|
16
|
-
version:
|
|
13
|
+
id: "opencode-zen",
|
|
14
|
+
type: "provider",
|
|
15
|
+
name: "OpenCode Zen",
|
|
16
|
+
version: "1.0.0",
|
|
17
17
|
|
|
18
18
|
meta: {
|
|
19
|
-
description:
|
|
20
|
-
homepage:
|
|
21
|
-
brandColor:
|
|
22
|
-
providerAliases: [
|
|
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: [
|
|
28
|
+
allowedDomains: ["opencode.ai"],
|
|
29
29
|
},
|
|
30
30
|
filesystem: {
|
|
31
31
|
read: true,
|
|
32
|
-
paths: [
|
|
32
|
+
paths: ["~/.local/share/opencode"],
|
|
33
33
|
},
|
|
34
34
|
env: {
|
|
35
35
|
read: true,
|
|
36
|
-
vars: [
|
|
36
|
+
vars: ["OPENCODE_API_KEY"],
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
|
|
40
40
|
capabilities: {
|
|
41
41
|
usageLimits: false,
|
|
42
42
|
apiRateLimits: false,
|
|
43
|
-
tokenUsage:
|
|
44
|
-
actualCosts:
|
|
43
|
+
tokenUsage: false,
|
|
44
|
+
actualCosts: false,
|
|
45
45
|
},
|
|
46
46
|
|
|
47
47
|
pricing: {
|
|
48
|
-
modelsDevProviderId:
|
|
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(
|
|
54
|
+
const entry = await ctx.authSources.opencode.getProviderEntry("opencode");
|
|
55
55
|
if (entry) {
|
|
56
|
-
if (entry.type ===
|
|
57
|
-
return { ok: true, credentials: { apiKey: entry.key, source:
|
|
56
|
+
if (entry.type === "api" && entry.key) {
|
|
57
|
+
return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
|
|
58
58
|
}
|
|
59
|
-
if (entry.type ===
|
|
60
|
-
return {
|
|
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(
|
|
68
|
+
const apiKey = ctx.authSources.env.get("OPENCODE_API_KEY");
|
|
66
69
|
if (apiKey) {
|
|
67
|
-
return { ok: true, credentials: { apiKey, source:
|
|
70
|
+
return { ok: true, credentials: { apiKey, source: "env" } };
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
return {
|
|
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:
|
|
91
|
+
error: "API key required. Run /connect in OpenCode to set up Zen.",
|
|
85
92
|
};
|
|
86
93
|
}
|
|
87
94
|
|
|
88
|
-
log.info(
|
|
95
|
+
log.info("OpenCode Zen configured", { keyPrefix: credentials.apiKey.slice(0, 10) });
|
|
89
96
|
|
|
90
97
|
try {
|
|
91
|
-
const response = await http.fetch(
|
|
98
|
+
const response = await http.fetch("https://opencode.ai/zen/v1/models", {
|
|
92
99
|
headers: {
|
|
93
|
-
|
|
100
|
+
Authorization: `Bearer ${credentials.apiKey}`,
|
|
94
101
|
},
|
|
95
102
|
});
|
|
96
|
-
|
|
103
|
+
|
|
97
104
|
if (response.ok) {
|
|
98
|
-
log.info(
|
|
105
|
+
log.info("OpenCode Zen API accessible");
|
|
99
106
|
return {
|
|
100
|
-
planType:
|
|
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:
|
|
118
|
+
planType: "Pay-as-you-go",
|
|
112
119
|
allowed: true,
|
|
113
120
|
fetchedAt: Date.now(),
|
|
114
121
|
};
|
|
115
122
|
} catch (err) {
|
|
116
|
-
log.warn(
|
|
117
|
-
|
|
123
|
+
log.warn("Could not verify OpenCode Zen API access", { error: String(err) });
|
|
124
|
+
|
|
118
125
|
return {
|
|
119
|
-
planType:
|
|
126
|
+
planType: "Pay-as-you-go",
|
|
120
127
|
allowed: true,
|
|
121
128
|
fetchedAt: Date.now(),
|
|
122
129
|
};
|