@leo000001/opencode-quota-sidebar 3.0.1 → 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 (41) 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.js +10 -3
  10. package/dist/providers/common.d.ts +6 -0
  11. package/dist/providers/common.js +32 -12
  12. package/dist/providers/core/anthropic.d.ts +1 -1
  13. package/dist/providers/core/anthropic.js +43 -39
  14. package/dist/providers/core/kimi_for_coding.d.ts +1 -1
  15. package/dist/providers/core/kimi_for_coding.js +44 -64
  16. package/dist/providers/core/minimax_cn_coding_plan.d.ts +2 -0
  17. package/dist/providers/core/minimax_cn_coding_plan.js +214 -0
  18. package/dist/providers/core/zhipu_coding_plan.d.ts +1 -1
  19. package/dist/providers/core/zhipu_coding_plan.js +41 -61
  20. package/dist/providers/index.d.ts +3 -3
  21. package/dist/providers/index.js +5 -5
  22. package/dist/providers/third_party/rightcode.d.ts +1 -1
  23. package/dist/providers/third_party/rightcode.js +41 -61
  24. package/dist/providers/third_party/xyai.d.ts +2 -0
  25. package/dist/providers/third_party/{xyai_vibe.js → xyai.js} +113 -79
  26. package/dist/quota.d.ts +2 -2
  27. package/dist/quota.js +24 -18
  28. package/dist/quota_render.d.ts +1 -1
  29. package/dist/quota_render.js +23 -17
  30. package/dist/storage_parse.js +1 -0
  31. package/dist/title.js +7 -7
  32. package/dist/title_apply.js +18 -1
  33. package/dist/tui.tsx +2 -1
  34. package/dist/tui_helpers.d.ts +2 -1
  35. package/dist/tui_helpers.js +6 -1
  36. package/dist/types.d.ts +2 -0
  37. package/package.json +9 -2
  38. package/quota-sidebar.config.example.json +45 -45
  39. package/dist/providers/third_party/buzz.d.ts +0 -2
  40. package/dist/providers/third_party/buzz.js +0 -156
  41. package/dist/providers/third_party/xyai_vibe.d.ts +0 -2
package/dist/quota.js CHANGED
@@ -1,14 +1,14 @@
1
- import fs from 'node:fs/promises';
2
- import { isRecord, swallow } from './helpers.js';
3
- import { createDefaultProviderRegistry } from './providers/index.js';
4
- import { sanitizeBaseURL } from './providers/common.js';
1
+ import fs from "node:fs/promises";
2
+ import { isRecord, swallow } from "./helpers.js";
3
+ import { createDefaultProviderRegistry } from "./providers/index.js";
4
+ import { sanitizeBaseURL } from "./providers/common.js";
5
5
  function resolveContext(providerID, providerOptions) {
6
6
  return { providerID, providerOptions };
7
7
  }
8
8
  function authCandidates(providerID, normalizedProviderID, adapterID) {
9
9
  const candidates = new Set([providerID]);
10
- if (adapterID === 'github-copilot') {
11
- candidates.add('github-copilot-enterprise');
10
+ if (adapterID === "github-copilot") {
11
+ candidates.add("github-copilot-enterprise");
12
12
  }
13
13
  candidates.add(normalizedProviderID);
14
14
  candidates.add(adapterID);
@@ -34,11 +34,12 @@ export function quotaSort(left, right) {
34
34
  export function listDefaultQuotaProviderIDs() {
35
35
  // Keep default report behavior stable for built-in subscription providers.
36
36
  return [
37
- 'openai',
38
- 'kimi-for-coding',
39
- 'zhipuai-coding-plan',
40
- 'github-copilot',
41
- 'anthropic',
37
+ "openai",
38
+ "kimi-for-coding",
39
+ "zhipuai-coding-plan",
40
+ "minimax-cn-coding-plan",
41
+ "github-copilot",
42
+ "anthropic",
42
43
  ];
43
44
  }
44
45
  export function createQuotaRuntime() {
@@ -58,10 +59,15 @@ export function createQuotaRuntime() {
58
59
  if (adapter?.id && normalizedProviderID !== providerID) {
59
60
  keyBase = `${adapter.id}:${providerID}`;
60
61
  }
61
- // RightCode variants intentionally keep provider-specific labels (RC-openai,
62
- // RC-foo). Preserve that identity in cache keys so snapshots don't collide.
63
- if (adapter?.id === 'rightcode' &&
64
- normalizedProviderID.startsWith('rightcode-')) {
62
+ // Some third-party adapters intentionally preserve provider-specific labels
63
+ // (for example RC-openai or an XYAI alias) even when they share one adapter.
64
+ // Keep the original provider identity in cache keys so same-host aliases with
65
+ // different auth/config entries do not overwrite each other.
66
+ if ((adapter?.id === "rightcode" &&
67
+ normalizedProviderID.startsWith("rightcode-")) ||
68
+ (adapter?.id === "xyai" &&
69
+ providerID !== "xyai" &&
70
+ providerID !== "xyai-vibe")) {
65
71
  keyBase = normalizedProviderID;
66
72
  }
67
73
  return baseURL ? `${keyBase}@${baseURL}` : keyBase;
@@ -110,16 +116,16 @@ export function quotaCacheKey(providerID, providerOptions) {
110
116
  }
111
117
  export async function loadAuthMap(authPath) {
112
118
  const parsed = await fs
113
- .readFile(authPath, 'utf8')
119
+ .readFile(authPath, "utf8")
114
120
  .then((value) => JSON.parse(value))
115
- .catch(swallow('loadAuthMap'));
121
+ .catch(swallow("loadAuthMap"));
116
122
  if (!isRecord(parsed))
117
123
  return {};
118
124
  return Object.entries(parsed).reduce((acc, [key, value]) => {
119
125
  if (!isRecord(value))
120
126
  return acc;
121
127
  const type = value.type;
122
- if (type !== 'oauth' && type !== 'api' && type !== 'wellknown')
128
+ if (type !== "oauth" && type !== "api" && type !== "wellknown")
123
129
  return acc;
124
130
  acc[key] = value;
125
131
  return acc;
@@ -1,4 +1,4 @@
1
- import type { QuotaSnapshot } from './types.js';
1
+ import type { QuotaSnapshot } from "./types.js";
2
2
  export declare function canonicalProviderID(providerID: string): string;
3
3
  export declare function displayShortLabel(providerID: string): string;
4
4
  export declare function quotaDisplayLabel(quota: QuotaSnapshot): string;
@@ -1,16 +1,22 @@
1
1
  const PROVIDER_SHORT_LABELS = {
2
- openai: 'OpenAI',
3
- 'kimi-for-coding': 'Kimi',
4
- 'zhipuai-coding-plan': 'Zhipu',
5
- 'github-copilot': 'Copilot',
6
- anthropic: 'Anthropic',
7
- rightcode: 'RC',
2
+ openai: "OpenAI",
3
+ "kimi-for-coding": "Kimi",
4
+ "zhipuai-coding-plan": "Zhipu",
5
+ "minimax-cn-coding-plan": "MiniMax",
6
+ "github-copilot": "Copilot",
7
+ anthropic: "Anthropic",
8
+ rightcode: "RC",
9
+ xyai: "XYAI",
8
10
  };
9
11
  export function canonicalProviderID(providerID) {
10
- if (providerID.startsWith('github-copilot'))
11
- return 'github-copilot';
12
- if (providerID === 'zhipuai-coding-plan')
13
- return 'zhipuai-coding-plan';
12
+ if (providerID.startsWith("github-copilot"))
13
+ return "github-copilot";
14
+ if (providerID === "zhipuai-coding-plan")
15
+ return "zhipuai-coding-plan";
16
+ if (providerID === "minimax-cn-coding-plan")
17
+ return "minimax-cn-coding-plan";
18
+ if (providerID === "xyai-vibe")
19
+ return "xyai";
14
20
  return providerID;
15
21
  }
16
22
  export function displayShortLabel(providerID) {
@@ -18,8 +24,8 @@ export function displayShortLabel(providerID) {
18
24
  const direct = PROVIDER_SHORT_LABELS[canonical];
19
25
  if (direct)
20
26
  return direct;
21
- if (canonical.startsWith('rightcode-')) {
22
- return `RC-${canonical.slice('rightcode-'.length)}`;
27
+ if (canonical.startsWith("rightcode-")) {
28
+ return `RC-${canonical.slice("rightcode-".length)}`;
23
29
  }
24
30
  return providerID;
25
31
  }
@@ -34,13 +40,13 @@ export function quotaDisplayLabel(quota) {
34
40
  return displayShortLabel(quota.providerID);
35
41
  }
36
42
  function quotaKey(quota) {
37
- if (quota.adapterID === 'rightcode')
43
+ if (quota.adapterID === "rightcode")
38
44
  return `rightcode:${quota.providerID}`;
39
45
  return `${quota.adapterID || quota.providerID}:${quota.providerID}`;
40
46
  }
41
47
  function quotaScore(quota) {
42
48
  let score = 0;
43
- if (quota.status === 'ok')
49
+ if (quota.status === "ok")
44
50
  score += 10;
45
51
  if (quota.windows && quota.windows.length > 0) {
46
52
  score += 5 + quota.windows.length;
@@ -53,13 +59,13 @@ function quotaScore(quota) {
53
59
  }
54
60
  export function collapseQuotaSnapshots(quotas) {
55
61
  const grouped = new Map();
56
- const hasRightCodeBase = quotas.some((quota) => quota.adapterID === 'rightcode' && quotaDisplayLabel(quota) === 'RC');
62
+ const hasRightCodeBase = quotas.some((quota) => quota.adapterID === "rightcode" && quotaDisplayLabel(quota) === "RC");
57
63
  for (const quota of quotas) {
58
64
  // If both RC (balance) and RC-variant (subscription) exist,
59
65
  // treat balance as owned by RC.
60
66
  const normalizedQuota = hasRightCodeBase &&
61
- quota.adapterID === 'rightcode' &&
62
- quotaDisplayLabel(quota).startsWith('RC-')
67
+ quota.adapterID === "rightcode" &&
68
+ quotaDisplayLabel(quota).startsWith("RC-")
63
69
  ? { ...quota, balance: undefined }
64
70
  : quota;
65
71
  const key = quotaKey(normalizedQuota);
@@ -192,6 +192,7 @@ function parseSidebarPanel(value) {
192
192
  version: 1,
193
193
  updatedAt,
194
194
  usage: parseCachedUsage(value.usage),
195
+ panelQuotas: parseQuotaSnapshots(value.panelQuotas),
195
196
  quotas: parseQuotaSnapshots(value.quotas),
196
197
  };
197
198
  }
package/dist/title.js CHANGED
@@ -57,28 +57,28 @@ function isCoreDecoratedDetail(line) {
57
57
  function isQuotaDecoratedDetail(line) {
58
58
  if (!line)
59
59
  return false;
60
- if (/^(OAI|Cop|Ant|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:\?|unsupported|unavailable|error|(?:\d+h|D|W|M)\d{1,3}|D[\d.,]+\/[\d.,]+|B(?:[¥$-])?[\d.,]+))+$/i.test(line)) {
60
+ if (/^(OAI|Cop|Ant|Kimi|XYAI|RC(?:-[^\s]+)?)(?:\s+(?:\?|unsupported|unavailable|error|(?:\d+h|D|W|M)\d{1,3}|D[\d.,]+\/[\d.,]+|B(?:[¥$-])?[\d.,]+))+$/i.test(line)) {
61
61
  return true;
62
62
  }
63
- if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)\s*$/.test(line)) {
63
+ if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|RC(?:-[^\s]+)?)\s*$/.test(line)) {
64
64
  return true;
65
65
  }
66
66
  if (/^(?:(?:Daily\s+\$[\d.,]+\/\$[\d.,]+|\$[\d.,]+\/\$[\d.,]+)(?:\s+(?:Rst|Exp\+?)\s+[-:\d]+)?|(?:\d+[hdw]|Weekly|Monthly)\s+\d{1,3}%(?:\s+Rst\s+[-:\d]+)?|Balance\s+\$[\d.,]+|Remaining\s+\?|(?:error|unsupported|unavailable))$/.test(line)) {
67
67
  return true;
68
68
  }
69
- if (/^(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3})(?:\s+(?:R|E\+?)\d[\d:.-]*)?$/.test(line)) {
69
+ if (/^(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|(?:S7d|O7d|OA7d|Co7d)\d{1,3})(?:\s+(?:R|E\+?)\d[\d:.-]*)?$/.test(line)) {
70
70
  return true;
71
71
  }
72
- if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:(?:Daily\s+\$[\d.,]+\/\$[\d.,]+|\$[\d.,]+\/\$[\d.,]+)(?:\s+(?:Rst|Exp\+?)\s+[-:\d]+)?|(?:\d+[hdw]|Weekly|Monthly)\s+\d{1,3}%(?:\s+Rst\s+[-:\d]+)?|(?:error|unsupported|unavailable)))$/.test(line)) {
72
+ if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|RC(?:-[^\s]+)?)(?:\s+(?:(?:Daily\s+\$[\d.,]+\/\$[\d.,]+|\$[\d.,]+\/\$[\d.,]+)(?:\s+(?:Rst|Exp\+?)\s+[-:\d]+)?|(?:\d+[hdw]|Weekly|Monthly)\s+\d{1,3}%(?:\s+Rst\s+[-:\d]+)?|(?:error|unsupported|unavailable)))$/.test(line)) {
73
73
  return true;
74
74
  }
75
- if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3})(?:\s+(?:R|E\+?)\d[\d:.-]*)?)$/.test(line)) {
75
+ if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|RC(?:-[^\s]+)?)(?:\s+(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|(?:S7d|O7d|OA7d|Co7d)\d{1,3})(?:\s+(?:R|E\+?)\d[\d:.-]*)?)$/.test(line)) {
76
76
  return true;
77
77
  }
78
- if (/^(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3}|(?:R|E\+?)\d[\d:.-]*))(?:\s+(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3}|(?:R|E\+?)\d[\d:.-]*)))*$/.test(line)) {
78
+ if (/^(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|(?:S7d|O7d|OA7d|Co7d)\d{1,3}|(?:R|E\+?)\d[\d:.-]*))(?:\s+(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|(?:S7d|O7d|OA7d|Co7d)\d{1,3}|(?:R|E\+?)\d[\d:.-]*)))*$/.test(line)) {
79
79
  return true;
80
80
  }
81
- if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3}|(?:R|E\+?)\d[\d:.-]*)))*$/.test(line)) {
81
+ if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|RC(?:-[^\s]+)?)(?:\s+(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|(?:S7d|O7d|OA7d|Co7d)\d{1,3}|(?:R|E\+?)\d[\d:.-]*)))*$/.test(line)) {
82
82
  return true;
83
83
  }
84
84
  return false;
@@ -15,6 +15,16 @@ export function createTitleApplicator(deps) {
15
15
  balance: quota.balance ? { ...quota.balance } : undefined,
16
16
  windows: quota.windows?.map((win) => ({ ...win })),
17
17
  }));
18
+ const sameProviderIDs = (left, right) => {
19
+ if (left.length !== right.length)
20
+ return false;
21
+ const rightSet = new Set(right);
22
+ for (const value of left) {
23
+ if (!rightSet.has(value))
24
+ return false;
25
+ }
26
+ return true;
27
+ };
18
28
  const applyTitle = async (sessionID) => {
19
29
  if (!deps.config.sidebar.enabled)
20
30
  return false;
@@ -82,16 +92,23 @@ export function createTitleApplicator(deps) {
82
92
  const usage = await deps.summarizeSessionUsageForDisplay(sessionID, deps.config.sidebar.includeChildren);
83
93
  const view = deps.getTitleView?.(sessionID) ??
84
94
  resolveTitleView({ config: deps.config });
95
+ const panelQuotaProviders = Array.from(new Set(Object.keys(usage.providers)));
85
96
  const quotaProviders = Array.from(new Set(view === 'compact'
86
97
  ? selectDesktopCompactProviderIDs(usage, deps.config)
87
- : Object.keys(usage.providers)));
98
+ : panelQuotaProviders));
88
99
  const quotas = deps.config.sidebar.showQuota && quotaProviders.length > 0
89
100
  ? await deps.getQuotaSnapshots(quotaProviders)
90
101
  : [];
102
+ const panelQuotas = deps.config.sidebar.showQuota && panelQuotaProviders.length > 0
103
+ ? sameProviderIDs(quotaProviders, panelQuotaProviders)
104
+ ? quotas
105
+ : await deps.getQuotaSnapshots(panelQuotaProviders)
106
+ : [];
91
107
  sessionState.sidebarPanel = {
92
108
  version: 1,
93
109
  updatedAt: Date.now(),
94
110
  usage: toCachedSessionUsage(usage),
111
+ panelQuotas: cloneQuotas(collapseQuotaSnapshots(panelQuotas)),
95
112
  quotas: cloneQuotas(collapseQuotaSnapshots(quotas)),
96
113
  };
97
114
  stateMutated = true;
package/dist/tui.tsx CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  quotaGroupsSummary,
14
14
  quotaGroupsUseBullets,
15
15
  renderSidebarQuotaGroups,
16
+ sidebarPanelQuotaSnapshots,
16
17
  type SidebarQuotaGroup,
17
18
  } from './tui_helpers.js'
18
19
  import {
@@ -109,7 +110,7 @@ async function loadSidebarPanel(
109
110
  ? renderSidebarUsageLines(usage, panelConfig(config))
110
111
  : []
111
112
  const quotaGroups = renderSidebarQuotaGroups(
112
- session?.sidebarPanel?.quotas || [],
113
+ sidebarPanelQuotaSnapshots(session?.sidebarPanel),
113
114
  panelConfig(config),
114
115
  )
115
116
 
@@ -1,4 +1,4 @@
1
- import type { QuotaSidebarConfig, QuotaSnapshot } from './types.js';
1
+ import type { QuotaSidebarConfig, QuotaSnapshot, SidebarPanelState } from './types.js';
2
2
  export type SidebarQuotaTone = 'success' | 'warning' | 'error' | 'muted';
3
3
  export type SidebarQuotaGroup = {
4
4
  providerID: string;
@@ -9,6 +9,7 @@ export type SidebarQuotaGroup = {
9
9
  continuationLines: string[];
10
10
  };
11
11
  export declare function renderSidebarQuotaGroups(quotas: QuotaSnapshot[], config: QuotaSidebarConfig): SidebarQuotaGroup[];
12
+ export declare function sidebarPanelQuotaSnapshots(panel?: SidebarPanelState): QuotaSnapshot[];
12
13
  export declare function fallbackQuotaGroupsFromTitle(title: string, width: number): SidebarQuotaGroup[];
13
14
  export declare function quotaGroupsUseBullets(groups: SidebarQuotaGroup[]): boolean;
14
15
  export declare function quotaGroupsAreCollapsible(groups: SidebarQuotaGroup[]): boolean;
@@ -69,7 +69,9 @@ function fallbackQuotaTone(detail) {
69
69
  return 'error';
70
70
  if (/\bB-/.test(safe))
71
71
  return 'error';
72
- const percents = [...safe.matchAll(/\b(?:\d+[hdw]|[DWM]|S7d)(\d{1,3})\b/gi)]
72
+ const percents = [
73
+ ...safe.matchAll(/\b(?:\d+[hdw]|[DWM]|S7d|O7d|OA7d|Co7d)(\d{1,3})\b/gi),
74
+ ]
73
75
  .map((match) => Number(match[1]))
74
76
  .filter((value) => Number.isFinite(value));
75
77
  if (percents.length === 0)
@@ -104,6 +106,9 @@ export function renderSidebarQuotaGroups(quotas, config) {
104
106
  };
105
107
  });
106
108
  }
109
+ export function sidebarPanelQuotaSnapshots(panel) {
110
+ return panel?.panelQuotas || panel?.quotas || [];
111
+ }
107
112
  export function fallbackQuotaGroupsFromTitle(title, width) {
108
113
  const parts = (title || '')
109
114
  .split(' | ')
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.1",
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;