@howaboua/opencode-usage-plugin 0.0.1
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/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/hooks/command.d.ts +13 -0
- package/dist/hooks/command.d.ts.map +1 -0
- package/dist/hooks/command.js +53 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/proxy.d.ts +14 -0
- package/dist/hooks/proxy.d.ts.map +1 -0
- package/dist/hooks/proxy.js +31 -0
- package/dist/hooks/session.d.ts +8 -0
- package/dist/hooks/session.d.ts.map +1 -0
- package/dist/hooks/session.js +16 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/providers/base.d.ts +13 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +5 -0
- package/dist/providers/codex/headers.d.ts +15 -0
- package/dist/providers/codex/headers.d.ts.map +1 -0
- package/dist/providers/codex/headers.js +25 -0
- package/dist/providers/codex/index.d.ts +12 -0
- package/dist/providers/codex/index.d.ts.map +1 -0
- package/dist/providers/codex/index.js +66 -0
- package/dist/providers/codex/response.d.ts +75 -0
- package/dist/providers/codex/response.d.ts.map +1 -0
- package/dist/providers/codex/response.js +59 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/proxy/config.d.ts +6 -0
- package/dist/providers/proxy/config.d.ts.map +1 -0
- package/dist/providers/proxy/config.js +52 -0
- package/dist/providers/proxy/fetch.d.ts +6 -0
- package/dist/providers/proxy/fetch.d.ts.map +1 -0
- package/dist/providers/proxy/fetch.js +30 -0
- package/dist/providers/proxy/format.d.ts +6 -0
- package/dist/providers/proxy/format.d.ts.map +1 -0
- package/dist/providers/proxy/format.js +102 -0
- package/dist/providers/proxy/index.d.ts +11 -0
- package/dist/providers/proxy/index.d.ts.map +1 -0
- package/dist/providers/proxy/index.js +116 -0
- package/dist/providers/proxy/types.d.ts +119 -0
- package/dist/providers/proxy/types.d.ts.map +1 -0
- package/dist/providers/proxy/types.js +4 -0
- package/dist/state.d.ts +18 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +13 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/proxy-limits.d.ts +16 -0
- package/dist/tools/proxy-limits.d.ts.map +1 -0
- package/dist/tools/proxy-limits.js +33 -0
- package/dist/tools/usage.d.ts +10 -0
- package/dist/tools/usage.d.ts.map +1 -0
- package/dist/tools/usage.js +12 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/status.d.ts +22 -0
- package/dist/ui/status.d.ts.map +1 -0
- package/dist/ui/status.js +142 -0
- package/dist/usage/fetch.d.ts +11 -0
- package/dist/usage/fetch.d.ts.map +1 -0
- package/dist/usage/fetch.js +80 -0
- package/dist/usage/index.d.ts +2 -0
- package/dist/usage/index.d.ts.map +1 -0
- package/dist/usage/index.js +1 -0
- package/dist/usage/registry.d.ts +19 -0
- package/dist/usage/registry.d.ts.map +1 -0
- package/dist/usage/registry.js +30 -0
- package/dist/utils/headers.d.ts +8 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +33 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/paths.d.ts +7 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +24 -0
- package/package.json +28 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the Usage Tracking Plugin.
|
|
3
|
+
*/
|
|
4
|
+
export declare const PlanTypes: readonly ["guest", "free", "go", "plus", "pro", "free_workspace", "team", "business", "education", "quorum", "k12", "enterprise", "edu"];
|
|
5
|
+
export type PlanType = (typeof PlanTypes)[number];
|
|
6
|
+
export interface RateLimitWindow {
|
|
7
|
+
usedPercent: number;
|
|
8
|
+
windowMinutes: number | null;
|
|
9
|
+
resetsAt: number | null;
|
|
10
|
+
}
|
|
11
|
+
export interface CreditsSnapshot {
|
|
12
|
+
hasCredits: boolean;
|
|
13
|
+
unlimited: boolean;
|
|
14
|
+
balance: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ProxyQuotaGroup {
|
|
17
|
+
name: string;
|
|
18
|
+
remaining: number;
|
|
19
|
+
max: number;
|
|
20
|
+
remainingPct: number;
|
|
21
|
+
resetTime?: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface ProxyTierInfo {
|
|
24
|
+
tier: "paid" | "free";
|
|
25
|
+
quotaGroups: ProxyQuotaGroup[];
|
|
26
|
+
}
|
|
27
|
+
export interface ProxyProviderInfo {
|
|
28
|
+
name: string;
|
|
29
|
+
tiers: ProxyTierInfo[];
|
|
30
|
+
}
|
|
31
|
+
export interface ProxyQuota {
|
|
32
|
+
providers: ProxyProviderInfo[];
|
|
33
|
+
totalCredentials: number;
|
|
34
|
+
activeCredentials: number;
|
|
35
|
+
dataSource: string;
|
|
36
|
+
}
|
|
37
|
+
export interface UsageSnapshot {
|
|
38
|
+
timestamp: number;
|
|
39
|
+
provider: string;
|
|
40
|
+
planType: PlanType | null;
|
|
41
|
+
primary: RateLimitWindow | null;
|
|
42
|
+
secondary: RateLimitWindow | null;
|
|
43
|
+
codeReview: RateLimitWindow | null;
|
|
44
|
+
credits: CreditsSnapshot | null;
|
|
45
|
+
proxyQuota?: ProxyQuota;
|
|
46
|
+
updatedAt: number;
|
|
47
|
+
}
|
|
48
|
+
export interface UsageEntry {
|
|
49
|
+
id: string;
|
|
50
|
+
timestamp: number;
|
|
51
|
+
provider: string;
|
|
52
|
+
model: string;
|
|
53
|
+
sessionID: string;
|
|
54
|
+
agent?: string;
|
|
55
|
+
inputTokens: number;
|
|
56
|
+
outputTokens: number;
|
|
57
|
+
cacheReadTokens?: number;
|
|
58
|
+
cacheWriteTokens?: number;
|
|
59
|
+
reasoningTokens?: number;
|
|
60
|
+
cost?: number;
|
|
61
|
+
requestID?: string;
|
|
62
|
+
statusCode?: number;
|
|
63
|
+
latency?: number;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,SAAS,0IAcZ,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,WAAW,EAAE,eAAe,EAAE,CAAA;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,eAAe,GAAG,IAAI,CAAA;IACjC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAE/B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the Usage Tracking Plugin.
|
|
3
|
+
*/
|
|
4
|
+
export const PlanTypes = [
|
|
5
|
+
"guest",
|
|
6
|
+
"free",
|
|
7
|
+
"go",
|
|
8
|
+
"plus",
|
|
9
|
+
"pro",
|
|
10
|
+
"free_workspace",
|
|
11
|
+
"team",
|
|
12
|
+
"business",
|
|
13
|
+
"education",
|
|
14
|
+
"quorum",
|
|
15
|
+
"k12",
|
|
16
|
+
"enterprise",
|
|
17
|
+
"edu",
|
|
18
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderUsageStatus, sendStatusMessage } from "./status";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders usage snapshots into readable status text.
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
5
|
+
import type { UsageSnapshot } from "../types";
|
|
6
|
+
import type { UsageState } from "../state";
|
|
7
|
+
type UsageClient = PluginInput["client"];
|
|
8
|
+
export declare function sendStatusMessage(options: {
|
|
9
|
+
client: UsageClient;
|
|
10
|
+
state: UsageState;
|
|
11
|
+
sessionID: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
export declare function renderUsageStatus(options: {
|
|
15
|
+
client: UsageClient;
|
|
16
|
+
state: UsageState;
|
|
17
|
+
sessionID: string;
|
|
18
|
+
snapshots: UsageSnapshot[];
|
|
19
|
+
filter?: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +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,CAqBhB;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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders usage snapshots into readable status text.
|
|
3
|
+
*/
|
|
4
|
+
export async function sendStatusMessage(options) {
|
|
5
|
+
const sent = await options.client.session
|
|
6
|
+
.prompt({
|
|
7
|
+
path: { id: options.sessionID },
|
|
8
|
+
body: {
|
|
9
|
+
noReply: true,
|
|
10
|
+
agent: options.state.agent,
|
|
11
|
+
model: options.state.model,
|
|
12
|
+
parts: [{ type: "text", text: options.text, ignored: true }],
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
.then(() => true)
|
|
16
|
+
.catch(() => false);
|
|
17
|
+
if (sent)
|
|
18
|
+
return;
|
|
19
|
+
await options.client.tui
|
|
20
|
+
.showToast({
|
|
21
|
+
body: { title: "Usage Status", message: options.text, variant: "info" },
|
|
22
|
+
})
|
|
23
|
+
.catch(() => { });
|
|
24
|
+
}
|
|
25
|
+
function formatBar(remainingPercent) {
|
|
26
|
+
const clamped = Math.max(0, Math.min(100, remainingPercent));
|
|
27
|
+
const size = 15;
|
|
28
|
+
const filled = Math.round((clamped / 100) * size);
|
|
29
|
+
const empty = size - filled;
|
|
30
|
+
return `${"█".repeat(filled)}${"░".repeat(empty)}`;
|
|
31
|
+
}
|
|
32
|
+
function formatPlanType(planType) {
|
|
33
|
+
return planType
|
|
34
|
+
.replace(/_/g, " ")
|
|
35
|
+
.split(" ")
|
|
36
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
37
|
+
.join(" ");
|
|
38
|
+
}
|
|
39
|
+
function formatResetTime(resetAt) {
|
|
40
|
+
const now = Math.floor(Date.now() / 1000);
|
|
41
|
+
const diff = resetAt - now;
|
|
42
|
+
if (diff <= 0)
|
|
43
|
+
return "now";
|
|
44
|
+
if (diff < 60)
|
|
45
|
+
return `${diff}s`;
|
|
46
|
+
if (diff < 3600)
|
|
47
|
+
return `${Math.ceil(diff / 60)}m`;
|
|
48
|
+
if (diff < 86400)
|
|
49
|
+
return `${Math.round(diff / 3600)}h`;
|
|
50
|
+
return `${Math.round(diff / 86400)}d`;
|
|
51
|
+
}
|
|
52
|
+
function formatResetSuffix(resetAt) {
|
|
53
|
+
if (!resetAt)
|
|
54
|
+
return "";
|
|
55
|
+
return ` (resets in ${formatResetTime(resetAt)})`;
|
|
56
|
+
}
|
|
57
|
+
function formatResetSuffixISO(isoString) {
|
|
58
|
+
try {
|
|
59
|
+
const resetAt = Math.floor(new Date(isoString).getTime() / 1000);
|
|
60
|
+
return ` (resets in ${formatResetTime(resetAt)})`;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function formatProxySnapshot(snapshot) {
|
|
67
|
+
const proxy = snapshot.proxyQuota;
|
|
68
|
+
if (!proxy)
|
|
69
|
+
return ["→ [proxy] No data"];
|
|
70
|
+
const lines = ["→ [Google] Mirrowel Proxy"];
|
|
71
|
+
for (const provider of proxy.providers) {
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push(` ${provider.name}:`);
|
|
74
|
+
for (const tierInfo of provider.tiers) {
|
|
75
|
+
const tierLabel = tierInfo.tier === "paid" ? "Paid" : "Free";
|
|
76
|
+
lines.push(` ${tierLabel}:`);
|
|
77
|
+
for (const group of tierInfo.quotaGroups) {
|
|
78
|
+
const resetSuffix = group.resetTime ? formatResetSuffixISO(group.resetTime) : "";
|
|
79
|
+
const label = `${group.name}:`.padEnd(9);
|
|
80
|
+
lines.push(` ${label} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${resetSuffix}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
function formatSnapshot(snapshot) {
|
|
87
|
+
if (snapshot.provider === "proxy" && snapshot.proxyQuota) {
|
|
88
|
+
return formatProxySnapshot(snapshot);
|
|
89
|
+
}
|
|
90
|
+
const plan = snapshot.planType ? ` (${formatPlanType(snapshot.planType)})` : "";
|
|
91
|
+
const lines = [`→ [${snapshot.provider.toUpperCase()}]${plan}`];
|
|
92
|
+
const primary = snapshot.primary;
|
|
93
|
+
if (primary) {
|
|
94
|
+
const remainingPct = 100 - primary.usedPercent;
|
|
95
|
+
const label = "Hourly:".padEnd(13);
|
|
96
|
+
lines.push(` ${label} ${formatBar(remainingPct)} ${remainingPct.toFixed(0)}% left${formatResetSuffix(primary.resetsAt)}`);
|
|
97
|
+
}
|
|
98
|
+
const secondary = snapshot.secondary;
|
|
99
|
+
if (secondary) {
|
|
100
|
+
const remainingPct = 100 - secondary.usedPercent;
|
|
101
|
+
const label = "Weekly:".padEnd(13);
|
|
102
|
+
lines.push(` ${label} ${formatBar(remainingPct)} ${remainingPct.toFixed(0)}% left${formatResetSuffix(secondary.resetsAt)}`);
|
|
103
|
+
}
|
|
104
|
+
const codeReview = snapshot.codeReview;
|
|
105
|
+
if (codeReview) {
|
|
106
|
+
const remainingPct = 100 - codeReview.usedPercent;
|
|
107
|
+
const label = "Code Review:".padEnd(13);
|
|
108
|
+
lines.push(` ${label} ${formatBar(remainingPct)} ${remainingPct.toFixed(0)}% left${formatResetSuffix(codeReview.resetsAt)}`);
|
|
109
|
+
}
|
|
110
|
+
if (snapshot.credits?.hasCredits) {
|
|
111
|
+
lines.push(` Credits: ${snapshot.credits.balance}`);
|
|
112
|
+
}
|
|
113
|
+
return lines;
|
|
114
|
+
}
|
|
115
|
+
export async function renderUsageStatus(options) {
|
|
116
|
+
if (options.snapshots.length === 0) {
|
|
117
|
+
const filterMsg = options.filter ? ` for "${options.filter}"` : "";
|
|
118
|
+
await sendStatusMessage({
|
|
119
|
+
client: options.client,
|
|
120
|
+
state: options.state,
|
|
121
|
+
sessionID: options.sessionID,
|
|
122
|
+
text: `▣ Usage | No data received${filterMsg}.`,
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const lines = ["▣ Usage Status", ""];
|
|
127
|
+
options.snapshots.forEach((snapshot, index) => {
|
|
128
|
+
const snapshotLines = formatSnapshot(snapshot);
|
|
129
|
+
for (const line of snapshotLines)
|
|
130
|
+
lines.push(line);
|
|
131
|
+
if (index < options.snapshots.length - 1) {
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("---");
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
await sendStatusMessage({
|
|
137
|
+
client: options.client,
|
|
138
|
+
state: options.state,
|
|
139
|
+
sessionID: options.sessionID,
|
|
140
|
+
text: lines.join("\n"),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads auth data and fetches live usage snapshots from providers.
|
|
3
|
+
* Supports filtering by provider alias.
|
|
4
|
+
*/
|
|
5
|
+
import type { UsageSnapshot } from "../types";
|
|
6
|
+
import type { AuthRecord } from "./registry";
|
|
7
|
+
export declare const providerAliases: Record<string, string>;
|
|
8
|
+
export declare function resolveProviderFilter(filter?: string): string | undefined;
|
|
9
|
+
export declare function fetchUsageSnapshots(filter?: string): Promise<UsageSnapshot[]>;
|
|
10
|
+
export declare function loadAuths(): Promise<AuthRecord>;
|
|
11
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAe5C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQlD,CAAA;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIzE;AAED,wBAAsB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAiCnF;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CASrD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads auth data and fetches live usage snapshots from providers.
|
|
3
|
+
* Supports filtering by provider alias.
|
|
4
|
+
*/
|
|
5
|
+
import z from "zod";
|
|
6
|
+
import { providers } from "../providers";
|
|
7
|
+
import { getAuthFilePath } from "../utils";
|
|
8
|
+
import { resolveProviderAuths } from "./registry";
|
|
9
|
+
const authEntrySchema = z
|
|
10
|
+
.object({
|
|
11
|
+
type: z.string().optional(),
|
|
12
|
+
access: z.string().optional(),
|
|
13
|
+
refresh: z.string().optional(),
|
|
14
|
+
enterpriseUrl: z.string().optional(),
|
|
15
|
+
accountId: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
.passthrough();
|
|
18
|
+
const authRecordSchema = z.record(z.string(), authEntrySchema);
|
|
19
|
+
export const providerAliases = {
|
|
20
|
+
codex: "codex",
|
|
21
|
+
openai: "codex",
|
|
22
|
+
gpt: "codex",
|
|
23
|
+
proxy: "proxy",
|
|
24
|
+
agy: "proxy",
|
|
25
|
+
antigravity: "proxy",
|
|
26
|
+
gemini: "proxy",
|
|
27
|
+
};
|
|
28
|
+
export function resolveProviderFilter(filter) {
|
|
29
|
+
if (!filter)
|
|
30
|
+
return undefined;
|
|
31
|
+
const normalized = filter.toLowerCase().trim();
|
|
32
|
+
return providerAliases[normalized];
|
|
33
|
+
}
|
|
34
|
+
export async function fetchUsageSnapshots(filter) {
|
|
35
|
+
const targetProvider = resolveProviderFilter(filter);
|
|
36
|
+
const auths = await loadAuths();
|
|
37
|
+
const entries = resolveProviderAuths(auths, null);
|
|
38
|
+
const snapshots = [];
|
|
39
|
+
// Fetch from auth-based providers
|
|
40
|
+
const fetches = entries
|
|
41
|
+
.filter((entry) => !targetProvider || entry.providerID === targetProvider)
|
|
42
|
+
.map(async (entry) => {
|
|
43
|
+
const provider = providers[entry.providerID];
|
|
44
|
+
if (!provider?.fetchUsage)
|
|
45
|
+
return;
|
|
46
|
+
const snapshot = await provider.fetchUsage(entry.auth).catch(() => null);
|
|
47
|
+
if (snapshot)
|
|
48
|
+
snapshots.push(snapshot);
|
|
49
|
+
});
|
|
50
|
+
// Always include proxy if no filter or filter matches proxy
|
|
51
|
+
if (!targetProvider || targetProvider === "proxy") {
|
|
52
|
+
const proxyProvider = providers["proxy"];
|
|
53
|
+
if (proxyProvider?.fetchUsage) {
|
|
54
|
+
fetches.push(proxyProvider
|
|
55
|
+
.fetchUsage(undefined)
|
|
56
|
+
.then((snapshot) => {
|
|
57
|
+
if (snapshot)
|
|
58
|
+
snapshots.push(snapshot);
|
|
59
|
+
})
|
|
60
|
+
.catch(() => { }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await Promise.race([Promise.all(fetches), timeout(5000)]);
|
|
64
|
+
return snapshots;
|
|
65
|
+
}
|
|
66
|
+
export async function loadAuths() {
|
|
67
|
+
const authPath = getAuthFilePath();
|
|
68
|
+
const data = await Bun.file(authPath)
|
|
69
|
+
.json()
|
|
70
|
+
.catch(() => ({}));
|
|
71
|
+
if (!data || typeof data !== "object")
|
|
72
|
+
return {};
|
|
73
|
+
const parsed = authRecordSchema.safeParse(data);
|
|
74
|
+
if (!parsed.success)
|
|
75
|
+
return {};
|
|
76
|
+
return parsed.data;
|
|
77
|
+
}
|
|
78
|
+
function timeout(ms) {
|
|
79
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/usage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { fetchUsageSnapshots, resolveProviderFilter } from "./fetch";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves provider auth entries for usage snapshots.
|
|
3
|
+
*/
|
|
4
|
+
import type { CodexAuth } from "../providers/codex";
|
|
5
|
+
export type AuthEntry = {
|
|
6
|
+
type?: string;
|
|
7
|
+
access?: string;
|
|
8
|
+
refresh?: string;
|
|
9
|
+
enterpriseUrl?: string;
|
|
10
|
+
accountId?: string;
|
|
11
|
+
};
|
|
12
|
+
export type AuthRecord = Record<string, AuthEntry>;
|
|
13
|
+
type ProviderAuthEntry = {
|
|
14
|
+
providerID: "codex";
|
|
15
|
+
auth: CodexAuth;
|
|
16
|
+
};
|
|
17
|
+
export declare function resolveProviderAuths(auths: AuthRecord, usageToken: string | null): ProviderAuthEntry[];
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/usage/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAElD,KAAK,iBAAiB,GAClB;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAqB5C,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,EAAE,CActG"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves provider auth entries for usage snapshots.
|
|
3
|
+
*/
|
|
4
|
+
const providerDescriptors = [
|
|
5
|
+
{
|
|
6
|
+
id: "codex",
|
|
7
|
+
authKeys: ["codex", "openai"],
|
|
8
|
+
requiresOAuth: true,
|
|
9
|
+
buildAuth: (entry) => ({
|
|
10
|
+
access: entry.access,
|
|
11
|
+
accountId: entry.accountId,
|
|
12
|
+
}),
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
export function resolveProviderAuths(auths, usageToken) {
|
|
16
|
+
const entries = [];
|
|
17
|
+
for (const descriptor of providerDescriptors) {
|
|
18
|
+
const matched = descriptor.authKeys.find((key) => Boolean(auths[key]));
|
|
19
|
+
if (!matched)
|
|
20
|
+
continue;
|
|
21
|
+
const auth = auths[matched];
|
|
22
|
+
if (!auth)
|
|
23
|
+
continue;
|
|
24
|
+
if (descriptor.requiresOAuth && auth.type && auth.type !== "oauth")
|
|
25
|
+
continue;
|
|
26
|
+
const built = descriptor.buildAuth(auth, usageToken);
|
|
27
|
+
entries.push({ providerID: descriptor.id, auth: built });
|
|
28
|
+
}
|
|
29
|
+
return entries;
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses numeric and boolean headers from usage responses.
|
|
3
|
+
* Keeps header parsing consistent across providers.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseNumberHeader(headers: Headers, name: string): number | null;
|
|
6
|
+
export declare function parseIntegerHeader(headers: Headers, name: string): number | null;
|
|
7
|
+
export declare function parseBooleanHeader(headers: Headers, name: string): boolean | null;
|
|
8
|
+
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/utils/headers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAOjF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses numeric and boolean headers from usage responses.
|
|
3
|
+
* Keeps header parsing consistent across providers.
|
|
4
|
+
*/
|
|
5
|
+
export function parseNumberHeader(headers, name) {
|
|
6
|
+
const value = headers.get(name);
|
|
7
|
+
if (!value)
|
|
8
|
+
return null;
|
|
9
|
+
const parsed = Number.parseFloat(value);
|
|
10
|
+
if (Number.isNaN(parsed))
|
|
11
|
+
return null;
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
export function parseIntegerHeader(headers, name) {
|
|
15
|
+
const value = headers.get(name);
|
|
16
|
+
if (!value)
|
|
17
|
+
return null;
|
|
18
|
+
const parsed = Number.parseInt(value, 10);
|
|
19
|
+
if (Number.isNaN(parsed))
|
|
20
|
+
return null;
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
export function parseBooleanHeader(headers, name) {
|
|
24
|
+
const value = headers.get(name);
|
|
25
|
+
if (!value)
|
|
26
|
+
return null;
|
|
27
|
+
const normalized = value.toLowerCase();
|
|
28
|
+
if (normalized === "true" || normalized === "1")
|
|
29
|
+
return true;
|
|
30
|
+
if (normalized === "false" || normalized === "0")
|
|
31
|
+
return false;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AACrF,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves filesystem locations for OpenCode data files.
|
|
3
|
+
* Centralizes platform-specific paths so callers stay simple and consistent.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getAppDataPath(): string;
|
|
6
|
+
export declare function getAuthFilePath(): string;
|
|
7
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,wBAAgB,cAAc,IAAI,MAAM,CAiBvC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves filesystem locations for OpenCode data files.
|
|
3
|
+
* Centralizes platform-specific paths so callers stay simple and consistent.
|
|
4
|
+
*/
|
|
5
|
+
import { homedir, platform } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
export function getAppDataPath() {
|
|
8
|
+
const plat = platform();
|
|
9
|
+
const home = homedir();
|
|
10
|
+
if (plat === "darwin") {
|
|
11
|
+
return join(home, "Library", "Application Support", "opencode");
|
|
12
|
+
}
|
|
13
|
+
if (plat === "win32") {
|
|
14
|
+
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
|
|
15
|
+
}
|
|
16
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
17
|
+
if (xdgData) {
|
|
18
|
+
return join(xdgData, "opencode");
|
|
19
|
+
}
|
|
20
|
+
return join(home, ".local", "share", "opencode");
|
|
21
|
+
}
|
|
22
|
+
export function getAuthFilePath() {
|
|
23
|
+
return join(getAppDataPath(), "auth.json");
|
|
24
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@howaboua/opencode-usage-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "OpenCode plugin for tracking AI provider usage, rate limits, and quotas",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
9
|
+
"keywords": ["opencode", "opencode-plugin", "plugin", "usage", "tracking", "rate-limit"],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@opencode-ai/plugin": "^1.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@opencode-ai/plugin": "^1.0.0",
|
|
16
|
+
"@types/bun": "^1.2.0",
|
|
17
|
+
"@types/node": "^22.0.0",
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"build": "npm run clean && tsc",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
}
|
|
28
|
+
}
|