@howaboua/opencode-usage-plugin 0.0.2 → 0.0.4
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 -0
- package/dist/providers/proxy/config.d.ts.map +1 -1
- package/dist/providers/proxy/config.js +4 -1
- package/dist/providers/proxy/format.d.ts.map +1 -1
- package/dist/providers/proxy/format.js +54 -14
- package/dist/providers/proxy/index.d.ts.map +1 -1
- package/dist/providers/proxy/index.js +88 -26
- package/dist/providers/proxy/types.d.ts +96 -77
- package/dist/providers/proxy/types.d.ts.map +1 -1
- package/dist/ui/status.d.ts.map +1 -1
- package/dist/ui/status.js +33 -13
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Track AI provider rate limits and quotas in real-time.
|
|
|
9
9
|
- **Inline status** – Results appear directly in your chat, no context switching
|
|
10
10
|
- **Zero setup** – Auto-detects providers from your existing config
|
|
11
11
|
|
|
12
|
+
<img width="1300" height="900" alt="image" src="https://github.com/user-attachments/assets/cd49e450-f4b6-4314-b236-b3a92bffdb88" />
|
|
13
|
+
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
14
16
|
Add to your `opencode.json`:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAI1C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAI1C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAwD5D"}
|
|
@@ -38,7 +38,10 @@ export async function loadProxyConfig() {
|
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
40
|
const content = await file.text();
|
|
41
|
-
|
|
41
|
+
// Remove comments first (both // and /* */)
|
|
42
|
+
const withoutComments = content.replace(/(\".*?\"|\'.*?\')|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g1) => g1 ?? "");
|
|
43
|
+
// Remove trailing commas before closing brackets/braces
|
|
44
|
+
const cleanJson = withoutComments.replace(/,(\s*[}\]])/g, "$1");
|
|
42
45
|
const config = JSON.parse(cleanJson);
|
|
43
46
|
if (!config.endpoint) {
|
|
44
47
|
throw new Error('Config must contain "endpoint" field');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA8B,MAAM,SAAS,CAAA;AAwHxE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAoC7D"}
|
|
@@ -4,10 +4,29 @@
|
|
|
4
4
|
const GROUP_MAPPING = {
|
|
5
5
|
"claude": "claude",
|
|
6
6
|
"g3-pro": "g3-pro",
|
|
7
|
-
"g3-flash": "g3-
|
|
7
|
+
"g3-flash": "g3-flash",
|
|
8
|
+
"g25-flash": "25-flash",
|
|
9
|
+
"g25-lite": "25-lite",
|
|
8
10
|
"pro": "g3-pro",
|
|
9
|
-
"3-flash": "g3-
|
|
11
|
+
"3-flash": "g3-flash",
|
|
12
|
+
"25-flash": "25-flash",
|
|
13
|
+
"25-lite": "25-lite"
|
|
10
14
|
};
|
|
15
|
+
// Display name ordering priority
|
|
16
|
+
const GROUP_ORDER = ["claude", "g3-pro", "g3-flash", "25-flash", "25-lite"];
|
|
17
|
+
function sortGroupNames(groups) {
|
|
18
|
+
return Array.from(groups.keys()).sort((a, b) => {
|
|
19
|
+
const aIndex = GROUP_ORDER.indexOf(a);
|
|
20
|
+
const bIndex = GROUP_ORDER.indexOf(b);
|
|
21
|
+
if (aIndex !== -1 && bIndex !== -1)
|
|
22
|
+
return aIndex - bIndex;
|
|
23
|
+
if (aIndex !== -1)
|
|
24
|
+
return -1;
|
|
25
|
+
if (bIndex !== -1)
|
|
26
|
+
return 1;
|
|
27
|
+
return a.localeCompare(b);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
11
30
|
function formatBar(remainingPercent) {
|
|
12
31
|
const clamped = Math.max(0, Math.min(100, remainingPercent));
|
|
13
32
|
const size = 20;
|
|
@@ -48,26 +67,44 @@ function aggregateCredentialsByTier(credentials) {
|
|
|
48
67
|
};
|
|
49
68
|
for (const cred of credentials) {
|
|
50
69
|
const tier = normalizeTier(cred.tier);
|
|
51
|
-
const
|
|
52
|
-
for (const [name,
|
|
70
|
+
const groupUsage = cred.group_usage ?? {};
|
|
71
|
+
for (const [name, groupData] of Object.entries(groupUsage)) {
|
|
53
72
|
if (!(name in GROUP_MAPPING))
|
|
54
73
|
continue;
|
|
55
74
|
const mappedName = GROUP_MAPPING[name];
|
|
75
|
+
// Find the best window from group_usage
|
|
76
|
+
const windows = groupData.windows || {};
|
|
77
|
+
let bestWindow = null;
|
|
78
|
+
// Priority order for windows
|
|
79
|
+
const windowPriority = ["daily", "5h", "1h", "15m"];
|
|
80
|
+
for (const windowName of windowPriority) {
|
|
81
|
+
if (windows[windowName]) {
|
|
82
|
+
bestWindow = windows[windowName];
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Fallback to any available window
|
|
87
|
+
if (!bestWindow && Object.keys(windows).length > 0) {
|
|
88
|
+
bestWindow = Object.values(windows)[0];
|
|
89
|
+
}
|
|
90
|
+
if (!bestWindow)
|
|
91
|
+
continue;
|
|
56
92
|
const existing = result[tier].get(mappedName);
|
|
57
93
|
if (existing) {
|
|
58
|
-
existing.remaining +=
|
|
59
|
-
existing.max +=
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
94
|
+
existing.remaining += bestWindow.remaining;
|
|
95
|
+
existing.max += bestWindow.limit || 0;
|
|
96
|
+
if (bestWindow.reset_at) {
|
|
97
|
+
const newResetTime = new Date(bestWindow.reset_at * 1000).toISOString();
|
|
98
|
+
if (!existing.resetTime || new Date(newResetTime) > new Date(existing.resetTime)) {
|
|
99
|
+
existing.resetTime = newResetTime;
|
|
63
100
|
}
|
|
64
101
|
}
|
|
65
102
|
}
|
|
66
103
|
else {
|
|
67
104
|
result[tier].set(mappedName, {
|
|
68
|
-
remaining:
|
|
69
|
-
max:
|
|
70
|
-
resetTime:
|
|
105
|
+
remaining: bestWindow.remaining,
|
|
106
|
+
max: bestWindow.limit || bestWindow.remaining,
|
|
107
|
+
resetTime: bestWindow.reset_at ? new Date(bestWindow.reset_at * 1000).toISOString() : null,
|
|
71
108
|
});
|
|
72
109
|
}
|
|
73
110
|
}
|
|
@@ -84,13 +121,16 @@ export function formatProxyLimits(data) {
|
|
|
84
121
|
}
|
|
85
122
|
for (const [providerName, provider] of Object.entries(data.providers)) {
|
|
86
123
|
lines.push(`${providerName}:`);
|
|
87
|
-
const
|
|
124
|
+
const credentialsArray = Object.values(provider.credentials ?? {});
|
|
125
|
+
const tierData = aggregateCredentialsByTier(credentialsArray);
|
|
88
126
|
for (const [tierName, quotas] of Object.entries(tierData)) {
|
|
89
127
|
if (quotas.size === 0)
|
|
90
128
|
continue;
|
|
91
129
|
const tierLabel = tierName === "paid" ? "Paid" : "Free";
|
|
92
130
|
lines.push(` ${tierLabel}:`);
|
|
93
|
-
|
|
131
|
+
const sortedNames = sortGroupNames(quotas);
|
|
132
|
+
for (const groupName of sortedNames) {
|
|
133
|
+
const quota = quotas.get(groupName);
|
|
94
134
|
const remainingPct = quota.max > 0 ? (quota.remaining / quota.max) * 100 : 0;
|
|
95
135
|
const resetSuffix = quota.remaining === 0 ? formatResetTime(quota.resetTime) : "";
|
|
96
136
|
lines.push(` ${groupName}: ${formatBar(remainingPct)} ${quota.remaining}/${quota.max}${resetSuffix}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAiK5C,eAAO,MAAM,aAAa,EAAE,aAwB3B,CAAA"}
|
|
@@ -10,32 +10,90 @@ export { formatProxyLimits } from "./format";
|
|
|
10
10
|
const GROUP_MAPPING = {
|
|
11
11
|
"claude": "claude",
|
|
12
12
|
"g3-pro": "g3-pro",
|
|
13
|
-
"g3-flash": "g3-
|
|
13
|
+
"g3-flash": "g3-flash",
|
|
14
|
+
"g25-flash": "25-flash",
|
|
15
|
+
"g25-lite": "25-lite",
|
|
14
16
|
"pro": "g3-pro",
|
|
15
|
-
"3-flash": "g3-
|
|
17
|
+
"3-flash": "g3-flash",
|
|
18
|
+
"25-flash": "25-flash",
|
|
19
|
+
"25-lite": "25-lite"
|
|
16
20
|
};
|
|
21
|
+
// Display name ordering priority
|
|
22
|
+
const GROUP_ORDER = ["claude", "g3-pro", "g3-flash", "25-flash", "25-lite"];
|
|
23
|
+
function sortQuotaGroups(groups) {
|
|
24
|
+
return groups.sort((a, b) => {
|
|
25
|
+
const aIndex = GROUP_ORDER.indexOf(a.name);
|
|
26
|
+
const bIndex = GROUP_ORDER.indexOf(b.name);
|
|
27
|
+
// Both in priority list: sort by index
|
|
28
|
+
if (aIndex !== -1 && bIndex !== -1)
|
|
29
|
+
return aIndex - bIndex;
|
|
30
|
+
// Only a in priority list: a comes first
|
|
31
|
+
if (aIndex !== -1)
|
|
32
|
+
return -1;
|
|
33
|
+
// Only b in priority list: b comes first
|
|
34
|
+
if (bIndex !== -1)
|
|
35
|
+
return 1;
|
|
36
|
+
// Neither in priority list: alphabetical
|
|
37
|
+
return a.name.localeCompare(b.name);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
17
40
|
function normalizeTier(tier) {
|
|
18
41
|
if (!tier)
|
|
19
42
|
return "free";
|
|
20
43
|
return tier.includes("free") ? "free" : "paid";
|
|
21
44
|
}
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Extract quota groups from group_usage data
|
|
47
|
+
* New API structure: group_usage[groupName].windows[windowName]
|
|
48
|
+
*/
|
|
49
|
+
function parseQuotaGroupsFromCredential(groupUsage) {
|
|
50
|
+
if (!groupUsage)
|
|
24
51
|
return [];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
const result = new Map();
|
|
53
|
+
for (const [groupName, groupData] of Object.entries(groupUsage)) {
|
|
54
|
+
const mappedName = GROUP_MAPPING[groupName];
|
|
55
|
+
if (!mappedName)
|
|
56
|
+
continue;
|
|
57
|
+
// Find the window with the best data (prefer daily, then 5h, then any)
|
|
58
|
+
const windows = groupData.windows || {};
|
|
59
|
+
let bestWindow = null;
|
|
60
|
+
// Priority order for windows
|
|
61
|
+
const windowPriority = ["daily", "5h", "1h", "15m"];
|
|
62
|
+
for (const windowName of windowPriority) {
|
|
63
|
+
if (windows[windowName]) {
|
|
64
|
+
bestWindow = windows[windowName];
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Fallback to any available window
|
|
69
|
+
if (!bestWindow && Object.keys(windows).length > 0) {
|
|
70
|
+
bestWindow = Object.values(windows)[0];
|
|
71
|
+
}
|
|
72
|
+
if (!bestWindow)
|
|
73
|
+
continue;
|
|
74
|
+
const existing = result.get(mappedName);
|
|
75
|
+
if (existing) {
|
|
76
|
+
existing.remaining += bestWindow.remaining;
|
|
77
|
+
existing.max += bestWindow.limit || 0;
|
|
78
|
+
// Use the latest reset time
|
|
79
|
+
if (bestWindow.reset_at) {
|
|
80
|
+
const newResetTime = new Date(bestWindow.reset_at * 1000).toISOString();
|
|
81
|
+
if (!existing.resetTime || new Date(newResetTime) > new Date(existing.resetTime)) {
|
|
82
|
+
existing.resetTime = newResetTime;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
result.set(mappedName, {
|
|
88
|
+
name: mappedName,
|
|
89
|
+
remaining: bestWindow.remaining,
|
|
90
|
+
max: bestWindow.limit || bestWindow.remaining,
|
|
91
|
+
remainingPct: bestWindow.limit ? Math.round((bestWindow.remaining / bestWindow.limit) * 100) : 0,
|
|
92
|
+
resetTime: bestWindow.reset_at ? new Date(bestWindow.reset_at * 1000).toISOString() : null,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return Array.from(result.values());
|
|
39
97
|
}
|
|
40
98
|
function aggregateByTier(credentials) {
|
|
41
99
|
const tiers = {
|
|
@@ -44,7 +102,7 @@ function aggregateByTier(credentials) {
|
|
|
44
102
|
};
|
|
45
103
|
for (const cred of credentials) {
|
|
46
104
|
const tier = normalizeTier(cred.tier);
|
|
47
|
-
const groups = parseQuotaGroupsFromCredential(cred.
|
|
105
|
+
const groups = parseQuotaGroupsFromCredential(cred.group_usage);
|
|
48
106
|
for (const group of groups) {
|
|
49
107
|
const existing = tiers[tier].get(group.name);
|
|
50
108
|
if (existing) {
|
|
@@ -66,23 +124,27 @@ function aggregateByTier(credentials) {
|
|
|
66
124
|
}
|
|
67
125
|
const result = [];
|
|
68
126
|
if (tiers.paid.size > 0) {
|
|
69
|
-
result.push({ tier: "paid", quotaGroups: Array.from(tiers.paid.values()) });
|
|
127
|
+
result.push({ tier: "paid", quotaGroups: sortQuotaGroups(Array.from(tiers.paid.values())) });
|
|
70
128
|
}
|
|
71
129
|
if (tiers.free.size > 0) {
|
|
72
|
-
result.push({ tier: "free", quotaGroups: Array.from(tiers.free.values()) });
|
|
130
|
+
result.push({ tier: "free", quotaGroups: sortQuotaGroups(Array.from(tiers.free.values())) });
|
|
73
131
|
}
|
|
74
132
|
return result;
|
|
75
133
|
}
|
|
76
134
|
function parseProviders(data) {
|
|
77
135
|
if (!data.providers)
|
|
78
136
|
return [];
|
|
79
|
-
return Object.entries(data.providers).map(([name, provider]) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
137
|
+
return Object.entries(data.providers).map(([name, provider]) => {
|
|
138
|
+
// Convert credentials object to array
|
|
139
|
+
const credentialsArray = Object.values(provider.credentials || {});
|
|
140
|
+
return {
|
|
141
|
+
name,
|
|
142
|
+
tiers: aggregateByTier(credentialsArray),
|
|
143
|
+
};
|
|
144
|
+
});
|
|
83
145
|
}
|
|
84
146
|
function parseProxyQuota(data) {
|
|
85
|
-
const summary = data.
|
|
147
|
+
const summary = data.summary;
|
|
86
148
|
return {
|
|
87
149
|
providers: parseProviders(data),
|
|
88
150
|
totalCredentials: summary?.total_credentials ?? 0,
|
|
@@ -13,96 +13,116 @@ export type ProxyConfig = {
|
|
|
13
13
|
};
|
|
14
14
|
/** Token statistics from the proxy */
|
|
15
15
|
export type TokenStats = {
|
|
16
|
-
input_cached
|
|
17
|
-
input_uncached
|
|
18
|
-
input_cache_pct
|
|
19
|
-
output
|
|
16
|
+
input_cached?: number;
|
|
17
|
+
input_uncached?: number;
|
|
18
|
+
input_cache_pct?: number;
|
|
19
|
+
output?: number;
|
|
20
|
+
prompt_tokens?: number;
|
|
21
|
+
completion_tokens?: number;
|
|
22
|
+
thinking_tokens?: number;
|
|
23
|
+
output_tokens?: number;
|
|
24
|
+
prompt_tokens_cache_read?: number;
|
|
25
|
+
prompt_tokens_cache_write?: number;
|
|
26
|
+
total_tokens?: number;
|
|
27
|
+
request_count?: number;
|
|
28
|
+
success_count?: number;
|
|
29
|
+
failure_count?: number;
|
|
30
|
+
approx_cost?: number;
|
|
20
31
|
};
|
|
21
|
-
/**
|
|
22
|
-
export type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
/** Window-based quota information */
|
|
33
|
+
export type WindowQuota = {
|
|
34
|
+
limit?: number;
|
|
35
|
+
remaining: number;
|
|
36
|
+
reset_at?: number | null;
|
|
37
|
+
request_count?: number;
|
|
38
|
+
success_count?: number;
|
|
39
|
+
failure_count?: number;
|
|
40
|
+
total_used?: number;
|
|
41
|
+
total_remaining?: number;
|
|
42
|
+
total_max?: number;
|
|
43
|
+
remaining_pct?: number;
|
|
44
|
+
};
|
|
45
|
+
/** Model group usage information */
|
|
46
|
+
export type GroupUsageWindow = {
|
|
47
|
+
[windowName: string]: WindowQuota;
|
|
48
|
+
};
|
|
49
|
+
export type GroupUsage = {
|
|
50
|
+
windows: GroupUsageWindow;
|
|
51
|
+
totals: TokenStats;
|
|
52
|
+
fair_cycle_exhausted?: boolean;
|
|
53
|
+
fair_cycle_reason?: string | null;
|
|
54
|
+
cooldown_remaining?: number | null;
|
|
55
|
+
cooldown_source?: string | null;
|
|
56
|
+
custom_cap?: number | null;
|
|
57
|
+
};
|
|
58
|
+
/** Model usage information */
|
|
59
|
+
export type ModelUsage = {
|
|
60
|
+
windows: GroupUsageWindow;
|
|
61
|
+
totals: TokenStats;
|
|
62
|
+
};
|
|
63
|
+
/** Tier availability info */
|
|
64
|
+
export type TierAvailability = {
|
|
65
|
+
total: number;
|
|
66
|
+
available: number;
|
|
67
|
+
};
|
|
68
|
+
/** Tier window info */
|
|
69
|
+
export type TierWindow = {
|
|
70
|
+
total_used: number;
|
|
71
|
+
total_remaining: number;
|
|
72
|
+
total_max: number;
|
|
73
|
+
remaining_pct: number;
|
|
74
|
+
tier_availability: Record<string, TierAvailability>;
|
|
75
|
+
};
|
|
76
|
+
/** Model group tiers */
|
|
77
|
+
export type GroupTiers = {
|
|
78
|
+
[tierName: string]: {
|
|
29
79
|
priority: number;
|
|
30
80
|
total: number;
|
|
31
|
-
}
|
|
32
|
-
total_remaining_pct: number;
|
|
33
|
-
total_requests_max: number;
|
|
34
|
-
total_requests_remaining: number;
|
|
35
|
-
total_requests_used: number;
|
|
81
|
+
};
|
|
36
82
|
};
|
|
37
|
-
/**
|
|
38
|
-
export type
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
success_count: number;
|
|
42
|
-
failure_count: number;
|
|
43
|
-
prompt_tokens: number;
|
|
44
|
-
prompt_tokens_cached: number;
|
|
45
|
-
completion_tokens: number;
|
|
46
|
-
approx_cost: number;
|
|
47
|
-
window_start_ts: number | null;
|
|
48
|
-
quota_reset_ts: number | null;
|
|
49
|
-
baseline_remaining_fraction: number | null;
|
|
50
|
-
baseline_fetched_at: number | null;
|
|
51
|
-
quota_max_requests: number;
|
|
52
|
-
quota_display: string;
|
|
83
|
+
/** Fair cycle summary */
|
|
84
|
+
export type FairCycleSummary = {
|
|
85
|
+
exhausted_count: number;
|
|
86
|
+
total_count: number;
|
|
53
87
|
};
|
|
54
|
-
/** Model group
|
|
55
|
-
export type
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
requests_max: number;
|
|
62
|
-
requests_remaining: number;
|
|
63
|
-
requests_used: number;
|
|
64
|
-
reset_time_iso: string | null;
|
|
88
|
+
/** Model group aggregation */
|
|
89
|
+
export type ModelGroupAggregation = {
|
|
90
|
+
tiers: GroupTiers;
|
|
91
|
+
windows: {
|
|
92
|
+
[windowName: string]: TierWindow;
|
|
93
|
+
};
|
|
94
|
+
fair_cycle_summary: FairCycleSummary;
|
|
65
95
|
};
|
|
66
|
-
/** Credential information */
|
|
67
|
-
export type
|
|
68
|
-
|
|
96
|
+
/** Credential information from new API */
|
|
97
|
+
export type CredentialData = {
|
|
98
|
+
stable_id: string;
|
|
99
|
+
accessor_masked?: string;
|
|
69
100
|
full_path: string;
|
|
70
|
-
|
|
71
|
-
|
|
101
|
+
identifier: string;
|
|
102
|
+
email?: string | null;
|
|
72
103
|
tier?: string;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
models: Record<string, ModelQuota>;
|
|
82
|
-
model_groups?: Record<string, ModelGroup>;
|
|
104
|
+
priority?: number;
|
|
105
|
+
active_requests?: number;
|
|
106
|
+
status: string;
|
|
107
|
+
totals: TokenStats;
|
|
108
|
+
model_usage?: Record<string, ModelUsage>;
|
|
109
|
+
group_usage?: Record<string, GroupUsage>;
|
|
110
|
+
last_used_at?: number;
|
|
111
|
+
first_used_at?: number;
|
|
83
112
|
};
|
|
84
|
-
/** Provider information */
|
|
113
|
+
/** Provider information from new API */
|
|
85
114
|
export type Provider = {
|
|
115
|
+
provider: string;
|
|
86
116
|
credential_count: number;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
total_requests: number;
|
|
91
|
-
tokens: TokenStats;
|
|
92
|
-
approx_cost: number | null;
|
|
93
|
-
credentials: Credential[];
|
|
94
|
-
quota_groups?: Record<string, QuotaGroup>;
|
|
95
|
-
global?: {
|
|
96
|
-
approx_cost: number | null;
|
|
97
|
-
tokens: TokenStats;
|
|
98
|
-
total_requests: number;
|
|
99
|
-
};
|
|
117
|
+
rotation_mode?: string;
|
|
118
|
+
credentials: Record<string, CredentialData>;
|
|
119
|
+
quota_groups?: Record<string, ModelGroupAggregation>;
|
|
100
120
|
};
|
|
101
|
-
/** Summary statistics */
|
|
121
|
+
/** Summary statistics from new API */
|
|
102
122
|
export type Summary = {
|
|
103
|
-
total_providers
|
|
123
|
+
total_providers?: number;
|
|
104
124
|
total_credentials: number;
|
|
105
|
-
active_credentials
|
|
125
|
+
active_credentials: number;
|
|
106
126
|
exhausted_credentials?: number;
|
|
107
127
|
total_requests: number;
|
|
108
128
|
tokens: TokenStats;
|
|
@@ -112,7 +132,6 @@ export type Summary = {
|
|
|
112
132
|
export type ProxyResponse = {
|
|
113
133
|
providers: Record<string, Provider>;
|
|
114
134
|
summary: Summary;
|
|
115
|
-
global_summary?: Summary;
|
|
116
135
|
data_source: string;
|
|
117
136
|
timestamp: number;
|
|
118
137
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oEAAoE;AACpE,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,KAAK,CAAC,EAAE,OAAO,CAAA;KAChB,CAAA;CACF,CAAA;AAED,sCAAsC;AACtC,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/types.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAwChB;AA0GD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhB"}
|
package/dist/ui/status.js
CHANGED
|
@@ -2,25 +2,45 @@
|
|
|
2
2
|
* Renders usage snapshots into readable status text.
|
|
3
3
|
*/
|
|
4
4
|
export async function sendStatusMessage(options) {
|
|
5
|
-
|
|
5
|
+
// 1. Send to Companion via Bus
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
const bus = options.client.bus;
|
|
8
|
+
if (bus) {
|
|
9
|
+
try {
|
|
10
|
+
await bus.publish({
|
|
11
|
+
topic: "companion.projection",
|
|
12
|
+
body: {
|
|
13
|
+
key: "usage",
|
|
14
|
+
kind: "markdown",
|
|
15
|
+
content: options.text,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
}
|
|
21
|
+
// 2. Send plain message to TUI
|
|
22
|
+
await options.client.session
|
|
6
23
|
.prompt({
|
|
7
24
|
path: { id: options.sessionID },
|
|
8
25
|
body: {
|
|
9
26
|
noReply: true,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
parts: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: options.text,
|
|
31
|
+
ignored: true,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
13
34
|
},
|
|
14
35
|
})
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
})
|
|
23
|
-
.catch(() => { });
|
|
36
|
+
.catch(async () => {
|
|
37
|
+
// 3. Fallback: Toast
|
|
38
|
+
await options.client.tui
|
|
39
|
+
.showToast({
|
|
40
|
+
body: { title: "Usage Status", message: options.text, variant: "info" },
|
|
41
|
+
})
|
|
42
|
+
.catch(() => { });
|
|
43
|
+
});
|
|
24
44
|
}
|
|
25
45
|
function formatBar(remainingPercent) {
|
|
26
46
|
const clamped = Math.max(0, Math.min(100, remainingPercent));
|
package/package.json
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howaboua/opencode-usage-plugin",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "opencode plugin for tracking AI provider usage, rate limits, and quotas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"opencode",
|
|
15
|
+
"opencode-plugin",
|
|
16
|
+
"plugin",
|
|
17
|
+
"usage",
|
|
18
|
+
"tracking",
|
|
19
|
+
"rate-limit"
|
|
20
|
+
],
|
|
10
21
|
"license": "MIT",
|
|
11
22
|
"peerDependencies": {
|
|
12
23
|
"@opencode-ai/plugin": "^1.0.0"
|