@tokentop/ttop 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +42 -38
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +36 -23
- package/src/plugins/notifications/visual-flash.ts +31 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +68 -57
- package/src/plugins/registry.ts +80 -71
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/persistence-service.ts +206 -0
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/retention.ts +146 -0
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +314 -183
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +320 -169
- package/src/tui/components/SettingsModal.tsx +593 -269
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +164 -151
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +238 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +95 -149
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +7 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +164 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +495 -243
- package/src/tui/views/RealTimeDashboard.tsx +176 -116
- package/src/tui/views/SettingsView.tsx +225 -189
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,48 +1,48 @@
|
|
|
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
|
/**
|
|
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:
|
|
28
|
-
type:
|
|
29
|
-
name:
|
|
30
|
-
version:
|
|
27
|
+
id: "perplexity",
|
|
28
|
+
type: "provider",
|
|
29
|
+
name: "Perplexity",
|
|
30
|
+
version: "1.0.0",
|
|
31
31
|
|
|
32
32
|
meta: {
|
|
33
|
-
description:
|
|
34
|
-
homepage:
|
|
35
|
-
brandColor:
|
|
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: [
|
|
41
|
+
allowedDomains: ["api.perplexity.ai"],
|
|
42
42
|
},
|
|
43
43
|
env: {
|
|
44
44
|
read: true,
|
|
45
|
-
vars: [
|
|
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(
|
|
59
|
+
const entry = await ctx.authSources.opencode.getProviderEntry("perplexity");
|
|
60
60
|
if (entry) {
|
|
61
|
-
if (entry.type ===
|
|
62
|
-
return { ok: true, credentials: { apiKey: entry.key, source:
|
|
61
|
+
if (entry.type === "api" && entry.key) {
|
|
62
|
+
return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
|
|
63
63
|
}
|
|
64
|
-
if (entry.type ===
|
|
65
|
-
return {
|
|
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(
|
|
73
|
+
const apiKey = ctx.authSources.env.get("PERPLEXITY_API_KEY");
|
|
71
74
|
if (apiKey) {
|
|
72
|
-
return { ok: true, credentials: { apiKey, source:
|
|
75
|
+
return { ok: true, credentials: { apiKey, source: "env" } };
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
return {
|
|
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:
|
|
96
|
+
error: "API key required. Set PERPLEXITY_API_KEY environment variable.",
|
|
90
97
|
};
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
log.info(
|
|
100
|
+
log.info("Checking Perplexity API", { keyPrefix: credentials.apiKey.slice(0, 8) });
|
|
94
101
|
|
|
95
102
|
try {
|
|
96
|
-
const response = await http.fetch(
|
|
97
|
-
method:
|
|
103
|
+
const response = await http.fetch("https://api.perplexity.ai/chat/completions", {
|
|
104
|
+
method: "POST",
|
|
98
105
|
headers: {
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
Authorization: `Bearer ${credentials.apiKey}`,
|
|
107
|
+
"Content-Type": "application/json",
|
|
101
108
|
},
|
|
102
109
|
body: JSON.stringify({
|
|
103
|
-
model:
|
|
104
|
-
messages: [{ role:
|
|
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:
|
|
119
|
+
error: "Invalid API key",
|
|
113
120
|
};
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
if (response.status === 402) {
|
|
117
124
|
return {
|
|
118
|
-
planType:
|
|
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:
|
|
131
|
+
balance: "$0.00",
|
|
125
132
|
},
|
|
126
|
-
error:
|
|
133
|
+
error: "Insufficient credits",
|
|
127
134
|
};
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
if (response.status === 429) {
|
|
131
138
|
return {
|
|
132
|
-
planType:
|
|
139
|
+
planType: "API",
|
|
133
140
|
allowed: true,
|
|
134
141
|
fetchedAt: Date.now(),
|
|
135
|
-
error:
|
|
142
|
+
error: "Rate limited",
|
|
136
143
|
};
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
if (response.ok) {
|
|
140
|
-
const data = await response.json().catch(() => null) as {
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
2
|
+
CredentialResult,
|
|
3
|
+
Credentials,
|
|
4
|
+
PluginContext,
|
|
5
|
+
ProviderAuth,
|
|
3
6
|
ProviderFetchContext,
|
|
7
|
+
ProviderPlugin,
|
|
4
8
|
ProviderUsageData,
|
|
5
9
|
UsageLimit,
|
|
6
|
-
|
|
7
|
-
PluginContext,
|
|
8
|
-
CredentialResult,
|
|
9
|
-
Credentials,
|
|
10
|
-
} from '../types/provider.ts';
|
|
10
|
+
} from "../types/provider.ts";
|
|
11
11
|
|
|
12
|
-
const ZAI_USAGE_URL =
|
|
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:
|
|
32
|
-
type:
|
|
33
|
-
name:
|
|
34
|
-
version:
|
|
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:
|
|
38
|
-
homepage:
|
|
39
|
-
brandColor:
|
|
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: [
|
|
45
|
+
allowedDomains: ["api.z.ai"],
|
|
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: ["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 =
|
|
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 ===
|
|
70
|
-
return { ok: true, credentials: { apiKey: entry.key, source:
|
|
71
|
+
if (entry.type === "api" && entry.key) {
|
|
72
|
+
return { ok: true, credentials: { apiKey: entry.key, source: "opencode" } };
|
|
71
73
|
}
|
|
72
|
-
if (entry.type ===
|
|
73
|
-
return {
|
|
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 ===
|
|
76
|
-
return { ok: true, credentials: { apiKey: entry.access, source:
|
|
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(
|
|
86
|
+
const apiKey = ctx.authSources.env.get("ZAI_API_KEY");
|
|
82
87
|
if (apiKey) {
|
|
83
|
-
return { ok: true, credentials: { apiKey, source:
|
|
88
|
+
return { ok: true, credentials: { apiKey, source: "env" } };
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
return {
|
|
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:
|
|
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:
|
|
117
|
+
method: "GET",
|
|
109
118
|
headers: {
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
Authorization: `Bearer ${token}`,
|
|
120
|
+
Accept: "application/json",
|
|
112
121
|
},
|
|
113
122
|
});
|
|
114
123
|
|
|
115
124
|
if (!response.ok) {
|
|
116
|
-
log.warn(
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
170
|
-
(l
|
|
171
|
-
|
|
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:
|
|
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(
|
|
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 :
|
|
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 ===
|
|
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 =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
227
|
+
if (!type) return "Quota";
|
|
217
228
|
switch (type) {
|
|
218
|
-
case
|
|
219
|
-
return
|
|
220
|
-
case
|
|
221
|
-
return
|
|
229
|
+
case "TIME_LIMIT":
|
|
230
|
+
return "Time";
|
|
231
|
+
case "TOKENS_LIMIT":
|
|
232
|
+
return "Tokens";
|
|
222
233
|
default:
|
|
223
|
-
return type.replace(/_/g,
|
|
234
|
+
return type.replace(/_/g, " ").toLowerCase();
|
|
224
235
|
}
|
|
225
236
|
}
|