@leo000001/opencode-quota-sidebar 3.0.0 → 3.0.2

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 (42) hide show
  1. package/CHANGELOG.md +14 -2
  2. package/CONTRIBUTING.md +4 -1
  3. package/README.md +210 -514
  4. package/README.zh-CN.md +337 -0
  5. package/SECURITY.md +2 -2
  6. package/assets/OpenCode-Quota-Sidebar.png +0 -0
  7. package/dist/cost.d.ts +3 -3
  8. package/dist/cost.js +258 -169
  9. package/dist/format.d.ts +4 -0
  10. package/dist/format.js +24 -10
  11. package/dist/providers/common.d.ts +6 -0
  12. package/dist/providers/common.js +32 -12
  13. package/dist/providers/core/anthropic.d.ts +1 -1
  14. package/dist/providers/core/anthropic.js +43 -39
  15. package/dist/providers/core/kimi_for_coding.d.ts +1 -1
  16. package/dist/providers/core/kimi_for_coding.js +44 -64
  17. package/dist/providers/core/minimax_cn_coding_plan.d.ts +2 -0
  18. package/dist/providers/core/minimax_cn_coding_plan.js +214 -0
  19. package/dist/providers/core/zhipu_coding_plan.d.ts +1 -1
  20. package/dist/providers/core/zhipu_coding_plan.js +41 -61
  21. package/dist/providers/index.d.ts +3 -3
  22. package/dist/providers/index.js +5 -5
  23. package/dist/providers/third_party/rightcode.d.ts +1 -1
  24. package/dist/providers/third_party/rightcode.js +41 -61
  25. package/dist/providers/third_party/xyai.d.ts +2 -0
  26. package/dist/providers/third_party/{xyai_vibe.js → xyai.js} +113 -79
  27. package/dist/quota.d.ts +2 -2
  28. package/dist/quota.js +24 -18
  29. package/dist/quota_render.d.ts +1 -1
  30. package/dist/quota_render.js +23 -17
  31. package/dist/storage_parse.js +1 -0
  32. package/dist/title.js +7 -7
  33. package/dist/title_apply.js +18 -1
  34. package/dist/tui.tsx +133 -36
  35. package/dist/tui_helpers.d.ts +16 -0
  36. package/dist/tui_helpers.js +146 -0
  37. package/dist/types.d.ts +2 -0
  38. package/package.json +9 -2
  39. package/quota-sidebar.config.example.json +45 -45
  40. package/dist/providers/third_party/buzz.d.ts +0 -2
  41. package/dist/providers/third_party/buzz.js +0 -156
  42. package/dist/providers/third_party/xyai_vibe.d.ts +0 -2
package/dist/types.d.ts CHANGED
@@ -113,6 +113,8 @@ export type SidebarPanelState = {
113
113
  version: 1;
114
114
  updatedAt: number;
115
115
  usage?: CachedSessionUsage;
116
+ /** Full TUI sidebar provider list for this session. */
117
+ panelQuotas?: QuotaSnapshot[];
116
118
  quotas?: QuotaSnapshot[];
117
119
  };
118
120
  /** Tracks incremental aggregation cursor for a session (P1). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo000001/opencode-quota-sidebar",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "OpenCode plugin that shows quota and token usage in TUI sidebar panels and compact session titles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,8 @@
30
30
  "CONTRIBUTING.md",
31
31
  "SECURITY.md",
32
32
  "README.md",
33
+ "README.zh-CN.md",
34
+ "assets/OpenCode-Quota-Sidebar.png",
33
35
  "quota-sidebar.config.example.json"
34
36
  ],
35
37
  "scripts": {
@@ -43,10 +45,15 @@
43
45
  },
44
46
  "keywords": [
45
47
  "opencode",
48
+ "opencode-plugin",
46
49
  "plugin",
47
50
  "quota",
48
51
  "sidebar",
49
- "token-usage"
52
+ "token-usage",
53
+ "tui",
54
+ "openai",
55
+ "copilot",
56
+ "anthropic"
50
57
  ],
51
58
  "license": "MIT",
52
59
  "repository": {
@@ -1,45 +1,45 @@
1
- {
2
- "sidebar": {
3
- "enabled": true,
4
- "width": 36,
5
- "titleMode": "auto",
6
- "multilineTitle": true,
7
- "showCost": true,
8
- "showQuota": true,
9
- "wrapQuotaLines": true,
10
- "includeChildren": true,
11
- "childrenMaxDepth": 6,
12
- "childrenMaxSessions": 128,
13
- "childrenConcurrency": 5,
14
- "desktopCompact": {
15
- "recentRequests": 50,
16
- "recentMinutes": 60
17
- }
18
- },
19
- "quota": {
20
- "refreshMs": 300000,
21
- "includeOpenAI": true,
22
- "includeCopilot": true,
23
- "includeAnthropic": true,
24
- "providers": {
25
- "rightcode": {
26
- "enabled": true
27
- },
28
- "xyai-vibe": {
29
- "enabled": false,
30
- "baseURL": "https://new.xychatai.com",
31
- "serviceType": "codex",
32
- "login": {
33
- "username": "your-account@example.com",
34
- "password": "your-password"
35
- }
36
- }
37
- },
38
- "refreshAccessToken": false,
39
- "requestTimeoutMs": 8000
40
- },
41
- "toast": {
42
- "durationMs": 12000
43
- },
44
- "retentionDays": 730
45
- }
1
+ {
2
+ "sidebar": {
3
+ "enabled": true,
4
+ "width": 36,
5
+ "titleMode": "auto",
6
+ "multilineTitle": true,
7
+ "showCost": true,
8
+ "showQuota": true,
9
+ "wrapQuotaLines": true,
10
+ "includeChildren": true,
11
+ "childrenMaxDepth": 6,
12
+ "childrenMaxSessions": 128,
13
+ "childrenConcurrency": 5,
14
+ "desktopCompact": {
15
+ "recentRequests": 50,
16
+ "recentMinutes": 60
17
+ }
18
+ },
19
+ "quota": {
20
+ "refreshMs": 300000,
21
+ "includeOpenAI": true,
22
+ "includeCopilot": true,
23
+ "includeAnthropic": true,
24
+ "providers": {
25
+ "rightcode": {
26
+ "enabled": true
27
+ },
28
+ "xyai": {
29
+ "enabled": false,
30
+ "baseURL": "https://new.xychatai.com",
31
+ "serviceType": "codex",
32
+ "login": {
33
+ "username": "your-account@example.com",
34
+ "password": "your-password"
35
+ }
36
+ }
37
+ },
38
+ "refreshAccessToken": false,
39
+ "requestTimeoutMs": 8000
40
+ },
41
+ "toast": {
42
+ "durationMs": 12000
43
+ },
44
+ "retentionDays": 730
45
+ }
@@ -1,2 +0,0 @@
1
- import type { QuotaProviderAdapter } from '../types.js';
2
- export declare const buzzAdapter: QuotaProviderAdapter;
@@ -1,156 +0,0 @@
1
- import { isRecord, swallow } from '../../helpers.js';
2
- import { asNumber, configuredProviderEnabled, fetchWithTimeout, sanitizeBaseURL, toIso, } from '../common.js';
3
- function isBuzzBaseURL(value) {
4
- const normalized = sanitizeBaseURL(value);
5
- if (!normalized)
6
- return false;
7
- try {
8
- const parsed = new URL(normalized);
9
- if (parsed.protocol !== 'https:')
10
- return false;
11
- return parsed.host === 'buzzai.cc' || parsed.host === 'www.buzzai.cc';
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
- function resolveApiKey(auth, providerOptions) {
18
- const optionKey = providerOptions?.apiKey;
19
- if (typeof optionKey === 'string' && optionKey)
20
- return optionKey;
21
- if (!auth)
22
- return undefined;
23
- if (auth.type === 'api' && typeof auth.key === 'string' && auth.key) {
24
- return auth.key;
25
- }
26
- if (auth.type === 'wellknown') {
27
- if (typeof auth.key === 'string' && auth.key)
28
- return auth.key;
29
- if (typeof auth.token === 'string' && auth.token)
30
- return auth.token;
31
- }
32
- if (auth.type === 'oauth' && typeof auth.access === 'string' && auth.access) {
33
- return auth.access;
34
- }
35
- return undefined;
36
- }
37
- function dashboardUrl(baseURL, pathname) {
38
- const normalized = sanitizeBaseURL(baseURL);
39
- if (normalized) {
40
- try {
41
- return new URL(pathname, normalized).toString();
42
- }
43
- catch {
44
- // Fall through to the stable default host below.
45
- }
46
- }
47
- return `https://buzzai.cc${pathname}`;
48
- }
49
- async function fetchBuzzQuota({ sourceProviderID, providerID, providerOptions, auth, config, }) {
50
- const checkedAt = Date.now();
51
- const runtimeProviderID = typeof sourceProviderID === 'string' && sourceProviderID
52
- ? sourceProviderID
53
- : providerID;
54
- const base = {
55
- providerID: runtimeProviderID,
56
- adapterID: 'buzz',
57
- label: 'Buzz',
58
- shortLabel: 'Buzz',
59
- sortOrder: 6,
60
- };
61
- const apiKey = resolveApiKey(auth, providerOptions);
62
- if (!apiKey) {
63
- return {
64
- ...base,
65
- status: 'unavailable',
66
- checkedAt,
67
- note: 'missing api key',
68
- };
69
- }
70
- const subscriptionEndpoint = dashboardUrl(providerOptions?.baseURL, '/v1/dashboard/billing/subscription');
71
- const usageEndpoint = dashboardUrl(providerOptions?.baseURL, '/v1/dashboard/billing/usage');
72
- const requestInit = {
73
- headers: {
74
- Accept: 'application/json',
75
- Authorization: `Bearer ${apiKey}`,
76
- 'User-Agent': 'opencode-quota-sidebar',
77
- },
78
- };
79
- const [subscriptionResponse, usageResponse] = await Promise.all([
80
- fetchWithTimeout(subscriptionEndpoint, requestInit, config.quota.requestTimeoutMs).catch(swallow('fetchBuzzQuota:subscription')),
81
- fetchWithTimeout(usageEndpoint, requestInit, config.quota.requestTimeoutMs).catch(swallow('fetchBuzzQuota:usage')),
82
- ]);
83
- if (!subscriptionResponse || !usageResponse) {
84
- return {
85
- ...base,
86
- status: 'error',
87
- checkedAt,
88
- note: 'network request failed',
89
- };
90
- }
91
- if (!subscriptionResponse.ok || !usageResponse.ok) {
92
- const note = [
93
- !subscriptionResponse.ok
94
- ? `subscription http ${subscriptionResponse.status}`
95
- : undefined,
96
- !usageResponse.ok ? `usage http ${usageResponse.status}` : undefined,
97
- ]
98
- .filter((value) => Boolean(value))
99
- .join(', ');
100
- return {
101
- ...base,
102
- status: 'error',
103
- checkedAt,
104
- note,
105
- };
106
- }
107
- const [subscriptionPayload, usagePayload] = await Promise.all([
108
- subscriptionResponse
109
- .json()
110
- .catch(swallow('fetchBuzzQuota:subscriptionJson')),
111
- usageResponse.json().catch(swallow('fetchBuzzQuota:usageJson')),
112
- ]);
113
- if (!isRecord(subscriptionPayload) || !isRecord(usagePayload)) {
114
- return {
115
- ...base,
116
- status: 'error',
117
- checkedAt,
118
- note: 'invalid response',
119
- };
120
- }
121
- const totalQuota = asNumber(subscriptionPayload.soft_limit_usd);
122
- const totalUsage = asNumber(usagePayload.total_usage);
123
- if (totalQuota === undefined || totalUsage === undefined) {
124
- return {
125
- ...base,
126
- status: 'error',
127
- checkedAt,
128
- note: 'missing billing fields',
129
- };
130
- }
131
- const accessUntil = asNumber(subscriptionPayload.access_until);
132
- const resetAt = accessUntil !== undefined && accessUntil > 0
133
- ? toIso(accessUntil)
134
- : undefined;
135
- const balance = totalQuota - totalUsage / 100;
136
- return {
137
- ...base,
138
- status: 'ok',
139
- checkedAt,
140
- resetAt,
141
- balance: {
142
- amount: balance,
143
- currency: '¥',
144
- },
145
- note: 'remaining balance = soft_limit_usd - total_usage / 100',
146
- };
147
- }
148
- export const buzzAdapter = {
149
- id: 'buzz',
150
- label: 'Buzz',
151
- shortLabel: 'Buzz',
152
- sortOrder: 6,
153
- matchScore: ({ providerOptions }) => isBuzzBaseURL(providerOptions?.baseURL) ? 100 : 0,
154
- isEnabled: (config) => configuredProviderEnabled(config.quota, 'buzz', true),
155
- fetch: fetchBuzzQuota,
156
- };
@@ -1,2 +0,0 @@
1
- import type { QuotaProviderAdapter } from '../types.js';
2
- export declare const xyaiVibeAdapter: QuotaProviderAdapter;