opencode-usage-stats 0.1.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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/api.d.ts +29 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +19 -0
- package/dist/api.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/usage.d.ts +32 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +125 -0
- package/dist/usage.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenUsage
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# OpenCodeUsage
|
|
2
|
+
|
|
3
|
+
An OpenCode plugin that surfaces local usage statistics (sessions, messages, tokens, cost, model/tool breakdown) and reserves a hook for cloud plan/remaining-quota information once OpenCode exposes a public API for it.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Add the plugin to your OpenCode config (`opencode.json` or `~/.config/opencode/opencode.json`):
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"$schema": "https://opencode.ai/config.json",
|
|
12
|
+
"plugin": ["opencode-usage-stats"]
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
OpenCode will install the npm package automatically on startup.
|
|
17
|
+
|
|
18
|
+
## Use
|
|
19
|
+
|
|
20
|
+
In any OpenCode session, ask the agent to show your usage:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Show my OpenCode usage
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The agent will call the custom `usage` tool and return a markdown summary.
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"plugin": ["opencode-usage-stats"],
|
|
33
|
+
"opencode-usage-stats": {
|
|
34
|
+
"enableCloudApi": false,
|
|
35
|
+
"cloudApiUrl": "https://console.opencode.ai/api/usage",
|
|
36
|
+
"apiKey": ""
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `enableCloudApi`: When `true`, the plugin attempts to fetch cloud plan/usage data. Currently this is a placeholder because OpenCode does not expose a public usage API.
|
|
42
|
+
- `cloudApiUrl`: Reserved URL for the future cloud usage endpoint.
|
|
43
|
+
- `apiKey`: Reserved API key for the future cloud usage endpoint.
|
|
44
|
+
|
|
45
|
+
## Data sources
|
|
46
|
+
|
|
47
|
+
- **C-1 (enabled by default)**: Aggregates local usage by reading the running OpenCode session list via the OpenCode SDK (`client.session.list()` / `client.session.messages()`).
|
|
48
|
+
- **C-2 (placeholder)**: Will call the configured cloud usage API when OpenCode releases a public endpoint. Until then, it is disabled and the plugin reports the limitation.
|
|
49
|
+
|
|
50
|
+
## Repository
|
|
51
|
+
|
|
52
|
+
https://github.com/QianYuan1437/OpenUsage
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OpenCodeUsageConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cloud usage response shape.
|
|
4
|
+
*
|
|
5
|
+
* This is intentionally generic because OpenCode does not currently expose a
|
|
6
|
+
* public REST endpoint for plan/usage data. When such an API becomes available,
|
|
7
|
+
* this interface can be tightened to match the real schema.
|
|
8
|
+
*/
|
|
9
|
+
export interface CloudUsageInfo {
|
|
10
|
+
plan?: string;
|
|
11
|
+
monthlyLimit?: number;
|
|
12
|
+
monthlyUsed?: number;
|
|
13
|
+
remaining?: number;
|
|
14
|
+
billingPeriod?: string;
|
|
15
|
+
currency?: string;
|
|
16
|
+
estimatedCost?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetch usage information from a cloud usage API.
|
|
20
|
+
*
|
|
21
|
+
* Currently this is a placeholder. The official OpenCode project does not
|
|
22
|
+
* expose a public REST endpoint for billing/usage/remaining quota. When it does,
|
|
23
|
+
* implement the real network call here and flip `enableCloudApi` to `true` in
|
|
24
|
+
* your OpenCode config.
|
|
25
|
+
*
|
|
26
|
+
* @returns Cloud usage data, or `null` if not available.
|
|
27
|
+
*/
|
|
28
|
+
export declare function fetchCloudUsage(_config: Required<OpenCodeUsageConfig>): Promise<CloudUsageInfo | null>;
|
|
29
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GACrC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch usage information from a cloud usage API.
|
|
3
|
+
*
|
|
4
|
+
* Currently this is a placeholder. The official OpenCode project does not
|
|
5
|
+
* expose a public REST endpoint for billing/usage/remaining quota. When it does,
|
|
6
|
+
* implement the real network call here and flip `enableCloudApi` to `true` in
|
|
7
|
+
* your OpenCode config.
|
|
8
|
+
*
|
|
9
|
+
* @returns Cloud usage data, or `null` if not available.
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchCloudUsage(_config) {
|
|
12
|
+
// Placeholder: C-2 is reserved but not enabled by default.
|
|
13
|
+
// Real implementation should:
|
|
14
|
+
// 1. Validate config.apiKey and config.cloudApiUrl
|
|
15
|
+
// 2. Make an authenticated request to the usage endpoint
|
|
16
|
+
// 3. Parse and return the response
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAsC;IAEtC,2DAA2D;IAC3D,8BAA8B;IAC9B,mDAAmD;IACnD,yDAAyD;IACzD,mCAAmC;IACnC,OAAO,IAAI,CAAA;AACb,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PluginOptions } from "@opencode-ai/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration shape for the OpenCodeUsage plugin.
|
|
4
|
+
*
|
|
5
|
+
* This is read from the OpenCode config under the `OpenCodeUsage` key,
|
|
6
|
+
* e.g.:
|
|
7
|
+
*
|
|
8
|
+
* ```json
|
|
9
|
+
* {
|
|
10
|
+
* "plugin": ["OpenCodeUsage"],
|
|
11
|
+
* "OpenCodeUsage": {
|
|
12
|
+
* "enableCloudApi": false,
|
|
13
|
+
* "cloudApiUrl": "https://console.opencode.ai/api/usage",
|
|
14
|
+
* "apiKey": ""
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export interface OpenCodeUsageConfig {
|
|
20
|
+
/** When true, attempt to call the cloud usage API (C-2). Default false (C-1). */
|
|
21
|
+
enableCloudApi?: boolean;
|
|
22
|
+
/** Base URL for the cloud usage API. Reserved for future OpenCode usage endpoints. */
|
|
23
|
+
cloudApiUrl?: string;
|
|
24
|
+
/** API key for the cloud usage API. Reserved for future use. */
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function resolveConfig(options?: PluginOptions): Required<OpenCodeUsageConfig>;
|
|
28
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,mBAAmB;IAClC,iFAAiF;IACjF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAOpF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function resolveConfig(options) {
|
|
2
|
+
const cfg = (options ?? {});
|
|
3
|
+
return {
|
|
4
|
+
enableCloudApi: cfg.enableCloudApi ?? false,
|
|
5
|
+
cloudApiUrl: cfg.cloudApiUrl ?? "https://console.opencode.ai/api/usage",
|
|
6
|
+
apiKey: cfg.apiKey ?? "",
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,aAAa,CAAC,OAAuB;IACnD,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAwB,CAAA;IAClD,OAAO;QACL,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,KAAK;QAC3C,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,uCAAuC;QACvE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;KACzB,CAAA;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
declare const OpenCodeUsage: Plugin;
|
|
3
|
+
export default OpenCodeUsage;
|
|
4
|
+
export { OpenCodeUsage };
|
|
5
|
+
export * from "./api.js";
|
|
6
|
+
export * from "./config.js";
|
|
7
|
+
export * from "./usage.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA8B,MAAM,qBAAqB,CAAA;AAM7E,QAAA,MAAM,aAAa,EAAE,MAuDpB,CAAA;eAEc,aAAa;AAC5B,OAAO,EAAE,aAAa,EAAE,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { fetchCloudUsage } from "./api.js";
|
|
3
|
+
import { resolveConfig } from "./config.js";
|
|
4
|
+
import { aggregateUsage, formatUsage } from "./usage.js";
|
|
5
|
+
const OpenCodeUsage = async (input, options) => {
|
|
6
|
+
const config = resolveConfig(options);
|
|
7
|
+
const { client } = input;
|
|
8
|
+
return {
|
|
9
|
+
tool: {
|
|
10
|
+
usage: tool({
|
|
11
|
+
description: "Show OpenCode usage statistics (sessions, messages, tokens, cost, model/tool breakdown). " +
|
|
12
|
+
"When OpenCode exposes a cloud usage API, this can also show plan limits and remaining quota.",
|
|
13
|
+
args: {
|
|
14
|
+
cloud: tool.schema
|
|
15
|
+
.boolean()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Force fetch cloud usage information if available."),
|
|
18
|
+
},
|
|
19
|
+
async execute(args) {
|
|
20
|
+
const wantsCloud = args.cloud ?? config.enableCloudApi;
|
|
21
|
+
if (wantsCloud) {
|
|
22
|
+
const cloud = await fetchCloudUsage(config);
|
|
23
|
+
if (cloud) {
|
|
24
|
+
return {
|
|
25
|
+
title: "OpenCode Cloud Usage",
|
|
26
|
+
output: [
|
|
27
|
+
"## OpenCode Cloud Usage",
|
|
28
|
+
"",
|
|
29
|
+
`- Plan: ${cloud.plan ?? "unknown"}`,
|
|
30
|
+
`- Billing Period: ${cloud.billingPeriod ?? "unknown"}`,
|
|
31
|
+
`- Monthly Limit: ${cloud.monthlyLimit ?? "unknown"}`,
|
|
32
|
+
`- Monthly Used: ${cloud.monthlyUsed ?? "unknown"}`,
|
|
33
|
+
`- Remaining: ${cloud.remaining ?? "unknown"}`,
|
|
34
|
+
`- Estimated Cost: ${cloud.estimatedCost !== undefined ? `$${cloud.estimatedCost.toFixed(4)}` : "unknown"}`,
|
|
35
|
+
].join("\n"),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const stats = await aggregateUsage(client);
|
|
41
|
+
return {
|
|
42
|
+
title: "OpenCode Usage",
|
|
43
|
+
output: formatUsage(stats),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
return {
|
|
49
|
+
title: "OpenCode Usage Error",
|
|
50
|
+
output: `Failed to gather OpenCode usage statistics.\n\n${message}\n\nMake sure OpenCode is running and the plugin has access to its local API.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export default OpenCodeUsage;
|
|
59
|
+
export { OpenCodeUsage };
|
|
60
|
+
export * from "./api.js";
|
|
61
|
+
export * from "./config.js";
|
|
62
|
+
export * from "./usage.js";
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExD,MAAM,aAAa,GAAW,KAAK,EAAE,KAAkB,EAAE,OAAuB,EAAE,EAAE;IAClF,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACrC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAExB,OAAO;QACL,IAAI,EAAE;YACJ,KAAK,EAAE,IAAI,CAAC;gBACV,WAAW,EACT,2FAA2F;oBAC3F,8FAA8F;gBAChG,IAAI,EAAE;oBACJ,KAAK,EAAE,IAAI,CAAC,MAAM;yBACf,OAAO,EAAE;yBACT,QAAQ,EAAE;yBACV,QAAQ,CAAC,mDAAmD,CAAC;iBACjE;gBACD,KAAK,CAAC,OAAO,CAAC,IAAI;oBAChB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,cAAc,CAAA;oBAEtD,IAAI,UAAU,EAAE,CAAC;wBACf,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAA;wBAC3C,IAAI,KAAK,EAAE,CAAC;4BACV,OAAO;gCACL,KAAK,EAAE,sBAAsB;gCAC7B,MAAM,EAAE;oCACN,yBAAyB;oCACzB,EAAE;oCACF,WAAW,KAAK,CAAC,IAAI,IAAI,SAAS,EAAE;oCACpC,qBAAqB,KAAK,CAAC,aAAa,IAAI,SAAS,EAAE;oCACvD,oBAAoB,KAAK,CAAC,YAAY,IAAI,SAAS,EAAE;oCACrD,mBAAmB,KAAK,CAAC,WAAW,IAAI,SAAS,EAAE;oCACnD,gBAAgB,KAAK,CAAC,SAAS,IAAI,SAAS,EAAE;oCAC9C,qBAAqB,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE;iCAC5G,CAAC,IAAI,CAAC,IAAI,CAAC;6BACb,CAAA;wBACH,CAAC;oBACH,CAAC;oBAED,IAAI,CAAC;wBACH,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAA;wBAC1C,OAAO;4BACL,KAAK,EAAE,gBAAgB;4BACvB,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;yBAC3B,CAAA;oBACH,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;wBACtE,OAAO;4BACL,KAAK,EAAE,sBAAsB;4BAC7B,MAAM,EAAE,kDAAkD,OAAO,+EAA+E;yBACjJ,CAAA;oBACH,CAAC;gBACH,CAAC;aACF,CAAC;SACH;KACF,CAAA;AACH,CAAC,CAAA;AAED,eAAe,aAAa,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { createOpencodeClient, Message, Part } from "@opencode-ai/sdk";
|
|
2
|
+
export interface ModelUsage {
|
|
3
|
+
messages: number;
|
|
4
|
+
inputTokens: number;
|
|
5
|
+
outputTokens: number;
|
|
6
|
+
cacheRead: number;
|
|
7
|
+
cacheWrite: number;
|
|
8
|
+
cost: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ToolUsage {
|
|
11
|
+
[tool: string]: number;
|
|
12
|
+
}
|
|
13
|
+
export interface UsageStats {
|
|
14
|
+
totalSessions: number;
|
|
15
|
+
totalMessages: number;
|
|
16
|
+
totalCost: number;
|
|
17
|
+
totalInputTokens: number;
|
|
18
|
+
totalOutputTokens: number;
|
|
19
|
+
totalReasoningTokens: number;
|
|
20
|
+
totalCacheRead: number;
|
|
21
|
+
totalCacheWrite: number;
|
|
22
|
+
modelUsage: Record<string, ModelUsage>;
|
|
23
|
+
toolUsage: ToolUsage;
|
|
24
|
+
}
|
|
25
|
+
export type OpencodeClient = ReturnType<typeof createOpencodeClient>;
|
|
26
|
+
export declare function aggregateMessages(messages: {
|
|
27
|
+
info: Message;
|
|
28
|
+
parts: Part[];
|
|
29
|
+
}[]): UsageStats;
|
|
30
|
+
export declare function aggregateUsage(client: OpencodeClient): Promise<UsageStats>;
|
|
31
|
+
export declare function formatUsage(stats: UsageStats): string;
|
|
32
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,IAAI,EAAW,MAAM,kBAAkB,CAAA;AAEpF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACtC,SAAS,EAAE,SAAS,CAAA;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAUpE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,EAAE,GAAG,UAAU,CAyD1F;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAsBhF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAqDrD"}
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
function isAssistantMessage(message) {
|
|
2
|
+
return "role" in message && message.role === "assistant";
|
|
3
|
+
}
|
|
4
|
+
function isToolPart(part) {
|
|
5
|
+
return part.type === "tool";
|
|
6
|
+
}
|
|
7
|
+
export function aggregateMessages(messages) {
|
|
8
|
+
const stats = {
|
|
9
|
+
totalSessions: 0,
|
|
10
|
+
totalMessages: messages.length,
|
|
11
|
+
totalCost: 0,
|
|
12
|
+
totalInputTokens: 0,
|
|
13
|
+
totalOutputTokens: 0,
|
|
14
|
+
totalReasoningTokens: 0,
|
|
15
|
+
totalCacheRead: 0,
|
|
16
|
+
totalCacheWrite: 0,
|
|
17
|
+
modelUsage: {},
|
|
18
|
+
toolUsage: {},
|
|
19
|
+
};
|
|
20
|
+
for (const { info, parts } of messages) {
|
|
21
|
+
if (!isAssistantMessage(info))
|
|
22
|
+
continue;
|
|
23
|
+
stats.totalCost += info.cost ?? 0;
|
|
24
|
+
const tokens = info.tokens;
|
|
25
|
+
if (tokens) {
|
|
26
|
+
stats.totalInputTokens += tokens.input ?? 0;
|
|
27
|
+
stats.totalOutputTokens += tokens.output ?? 0;
|
|
28
|
+
stats.totalReasoningTokens += tokens.reasoning ?? 0;
|
|
29
|
+
stats.totalCacheRead += tokens.cache?.read ?? 0;
|
|
30
|
+
stats.totalCacheWrite += tokens.cache?.write ?? 0;
|
|
31
|
+
}
|
|
32
|
+
const modelKey = `${info.providerID}/${info.modelID}`;
|
|
33
|
+
if (!stats.modelUsage[modelKey]) {
|
|
34
|
+
stats.modelUsage[modelKey] = {
|
|
35
|
+
messages: 0,
|
|
36
|
+
inputTokens: 0,
|
|
37
|
+
outputTokens: 0,
|
|
38
|
+
cacheRead: 0,
|
|
39
|
+
cacheWrite: 0,
|
|
40
|
+
cost: 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const model = stats.modelUsage[modelKey];
|
|
44
|
+
model.messages += 1;
|
|
45
|
+
if (tokens) {
|
|
46
|
+
model.inputTokens += tokens.input ?? 0;
|
|
47
|
+
model.outputTokens += tokens.output ?? 0;
|
|
48
|
+
model.cacheRead += tokens.cache?.read ?? 0;
|
|
49
|
+
model.cacheWrite += tokens.cache?.write ?? 0;
|
|
50
|
+
}
|
|
51
|
+
model.cost += info.cost ?? 0;
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (isToolPart(part)) {
|
|
54
|
+
stats.toolUsage[part.tool] = (stats.toolUsage[part.tool] ?? 0) + 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return stats;
|
|
59
|
+
}
|
|
60
|
+
export async function aggregateUsage(client) {
|
|
61
|
+
const sessionsResponse = await client.session.list();
|
|
62
|
+
const sessions = Array.isArray(sessionsResponse.data)
|
|
63
|
+
? sessionsResponse.data
|
|
64
|
+
: [];
|
|
65
|
+
const allMessages = [];
|
|
66
|
+
for (const session of sessions) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await client.session.messages({ path: { id: session.id } });
|
|
69
|
+
const messages = Array.isArray(response.data) ? response.data : [];
|
|
70
|
+
allMessages.push(...messages);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
// Skip sessions we cannot read.
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const stats = aggregateMessages(allMessages);
|
|
78
|
+
stats.totalSessions = sessions.length;
|
|
79
|
+
return stats;
|
|
80
|
+
}
|
|
81
|
+
export function formatUsage(stats) {
|
|
82
|
+
const totalTokens = stats.totalInputTokens +
|
|
83
|
+
stats.totalOutputTokens +
|
|
84
|
+
stats.totalReasoningTokens +
|
|
85
|
+
stats.totalCacheRead +
|
|
86
|
+
stats.totalCacheWrite;
|
|
87
|
+
const lines = [
|
|
88
|
+
"## OpenCode Usage",
|
|
89
|
+
"",
|
|
90
|
+
`- Sessions: ${stats.totalSessions.toLocaleString()}`,
|
|
91
|
+
`- Messages: ${stats.totalMessages.toLocaleString()}`,
|
|
92
|
+
`- Total Cost: $${stats.totalCost.toFixed(4)}`,
|
|
93
|
+
`- Total Tokens: ${totalTokens.toLocaleString()}`,
|
|
94
|
+
` - Input: ${stats.totalInputTokens.toLocaleString()}`,
|
|
95
|
+
` - Output: ${stats.totalOutputTokens.toLocaleString()}`,
|
|
96
|
+
` - Reasoning: ${stats.totalReasoningTokens.toLocaleString()}`,
|
|
97
|
+
` - Cache Read: ${stats.totalCacheRead.toLocaleString()}`,
|
|
98
|
+
` - Cache Write: ${stats.totalCacheWrite.toLocaleString()}`,
|
|
99
|
+
"",
|
|
100
|
+
];
|
|
101
|
+
const models = Object.entries(stats.modelUsage).sort(([, a], [, b]) => b.messages - a.messages);
|
|
102
|
+
if (models.length > 0) {
|
|
103
|
+
lines.push("### Model Usage");
|
|
104
|
+
lines.push("");
|
|
105
|
+
for (const [model, usage] of models) {
|
|
106
|
+
lines.push(`- ${model}`);
|
|
107
|
+
lines.push(` - Messages: ${usage.messages.toLocaleString()}`);
|
|
108
|
+
lines.push(` - Tokens: ${(usage.inputTokens + usage.outputTokens).toLocaleString()} (in ${usage.inputTokens.toLocaleString()} / out ${usage.outputTokens.toLocaleString()})`);
|
|
109
|
+
lines.push(` - Cost: $${usage.cost.toFixed(4)}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push("");
|
|
112
|
+
}
|
|
113
|
+
const tools = Object.entries(stats.toolUsage).sort(([, a], [, b]) => b - a);
|
|
114
|
+
if (tools.length > 0) {
|
|
115
|
+
lines.push("### Tool Usage");
|
|
116
|
+
lines.push("");
|
|
117
|
+
for (const [tool, count] of tools) {
|
|
118
|
+
lines.push(`- ${tool}: ${count.toLocaleString()}`);
|
|
119
|
+
}
|
|
120
|
+
lines.push("");
|
|
121
|
+
}
|
|
122
|
+
lines.push("_Cloud plan/quota information is not available because OpenCode does not expose a public usage API yet. Set `OpenCodeUsage.enableCloudApi: true` once it is supported._");
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-usage-stats",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An OpenCode plugin that surfaces local usage statistics and reserves a cloud usage API integration.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"opencode",
|
|
20
|
+
"opencode-plugin",
|
|
21
|
+
"usage",
|
|
22
|
+
"ai",
|
|
23
|
+
"agent"
|
|
24
|
+
],
|
|
25
|
+
"author": "OpenUsage",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/QianYuan1437/OpenUsage.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/QianYuan1437/OpenUsage/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/QianYuan1437/OpenUsage#readme",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@opencode-ai/plugin": "^1.18.0",
|
|
37
|
+
"@opencode-ai/sdk": "^1.18.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@opencode-ai/plugin": "^1.18.11",
|
|
41
|
+
"@opencode-ai/sdk": "^1.18.11",
|
|
42
|
+
"@types/node": "^26.1.2",
|
|
43
|
+
"typescript": "^7.0.2",
|
|
44
|
+
"vitest": "^4.1.10"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
}
|
|
49
|
+
}
|