@dijkspicy/opencode-stats-reporter 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 +198 -0
- package/README.md +135 -0
- package/bin/opencode-stats-reporter.js +7 -0
- package/contrib/opencode-session-reporter/README.md +92 -0
- package/contrib/opencode-session-reporter/package.json +16 -0
- package/contrib/opencode-session-reporter/plugin-server.ts +14 -0
- package/contrib/opencode-session-reporter/reporter.test.ts +378 -0
- package/contrib/opencode-session-reporter/session-usage-reporter.ts +610 -0
- package/package.json +30 -0
- package/panel/config.example.js +35 -0
- package/panel/css/style.css +615 -0
- package/panel/index.html +398 -0
- package/panel/js/api.js +154 -0
- package/panel/js/app.js +1167 -0
- package/panel/js/charts.js +241 -0
- package/panel/js/config.js +125 -0
- package/panel/js/format.js +156 -0
- package/panel/js/sessions.js +412 -0
- package/src/cli.js +164 -0
- package/src/config.js +57 -0
- package/src/identity.js +34 -0
- package/src/paths.js +37 -0
- package/src/proxy.js +31 -0
- package/src/server.js +164 -0
- package/src/session-api.js +97 -0
- package/src/store.js +246 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, but not limited to, the implied warranties of
|
|
148
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, or
|
|
149
|
+
NON-INFRINGEMENT. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your use of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You under any legal theory, whether in contract, strict
|
|
158
|
+
liability, or tort (including negligence) arising in any way out of
|
|
159
|
+
the use of this software, even if advised of the possibility of such
|
|
160
|
+
damage.
|
|
161
|
+
|
|
162
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
163
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
164
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
165
|
+
or other liability obligations and/or rights consistent with this
|
|
166
|
+
License. However, in accepting such obligations, You may act only
|
|
167
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
168
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
169
|
+
defend, and hold each Contributor harmless for any liability
|
|
170
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
171
|
+
of your accepting any such warranty or additional liability.
|
|
172
|
+
|
|
173
|
+
END OF TERMS AND CONDITIONS
|
|
174
|
+
|
|
175
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
176
|
+
|
|
177
|
+
To apply the Apache License to your work, attach the following
|
|
178
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
179
|
+
replaced with your own identifying information. (Don't include
|
|
180
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
181
|
+
comment syntax for the file format. We also recommend that a
|
|
182
|
+
file or class name and description of purpose be included on the
|
|
183
|
+
same "printed page" as the copyright notice for easier
|
|
184
|
+
identification within third-party archives.
|
|
185
|
+
|
|
186
|
+
Copyright [yyyy] [name of copyright owner]
|
|
187
|
+
|
|
188
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
189
|
+
you may not use this file except in compliance with the License.
|
|
190
|
+
You may obtain a copy of the License at
|
|
191
|
+
|
|
192
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
193
|
+
|
|
194
|
+
Unless required by applicable law or agreed to in writing, software
|
|
195
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
196
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
197
|
+
See the License for the specific language governing permissions and
|
|
198
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# BlueRegion Usage
|
|
2
|
+
|
|
3
|
+
面向 LLM API 网关最终用户的**轻量级个人运营小系统**:用户只需持有自己的 API Key,即可查询个人 Token 用量、趋势、模型分布等数据,并通过一个零构建的静态 HTML 面板可视化呈现。
|
|
4
|
+
|
|
5
|
+
整套方案以「复用 API 网关 + 函数服务 + 监控数据链路」为默认形态——核心用量看板**零新增基础设施**即可跑通。可选的**会话级用量**扩展会引入一个自建会话服务与持久化存储(SQLite),属可选组件,不部署不影响既有功能。
|
|
6
|
+
|
|
7
|
+
## 快速开始
|
|
8
|
+
|
|
9
|
+
1. 下载或克隆本仓库;
|
|
10
|
+
2. **双击打开 [`panel/index.html`](panel/index.html)——这就是看板入口**,纯静态页面,无需构建、无需启动任何服务;
|
|
11
|
+
3. 首次打开会进入引导视图,填入你的**网关地址**(`gatewayBaseUrl`)和**个人 API Key**,勾选「记住配置」即进入看板;
|
|
12
|
+
- 也可以预先复制 `panel/config.example.js` 为 `panel/config.js` 并填写同样两项,之后双击 `index.html` 直达看板;
|
|
13
|
+
- `panel/config.js` 含你的私有凭证,已被 `.gitignore` 忽略,请勿提交或截图分享。
|
|
14
|
+
|
|
15
|
+
> 提示:通过 `http://` 打开页面时 API Key 会以明文随请求传输,建议使用 `file://` 直接双击或 `https://` 托管访问。
|
|
16
|
+
|
|
17
|
+
## 本地一键启动(npm CLI,可选)
|
|
18
|
+
|
|
19
|
+
不想自建函数与网关路由时,用 npm 包在本地拉起完整看板:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i -g @dijkspicy/opencode-stats-reporter
|
|
23
|
+
opencode-stats-reporter config set --gateway https://<你的网关> --key <你的 API Key>
|
|
24
|
+
opencode-stats-reporter web start # 打开 http://127.0.0.1:8787
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- 本地服务同时做三件事:托管静态面板、把 `/v1/usage/*` 与 `/v1/models` **反代**到你的网关(Key 只保存在服务端)、并在本地 SQLite 存储会话用量;
|
|
28
|
+
- 浏览器只与 `localhost` 通信,因此**没有 CORS 问题,上游 Key 也不会进入浏览器**;
|
|
29
|
+
- 会话上报插件,**推荐用 opencode 自带的插件命令安装**(走 npm,随包更新):
|
|
30
|
+
```bash
|
|
31
|
+
opencode plugin @dijkspicy/opencode-stats-reporter -g
|
|
32
|
+
```
|
|
33
|
+
离线/本地文件方式作为替代:`opencode-stats-reporter plugin install`。之后在 opencode 进程环境设置 `SESSION_USAGE_ENDPOINT=http://127.0.0.1:8787/v1/session-usage/report`(本地模式无需 `SESSION_USAGE_API_KEY`,且不要用 `--pure` 启动);
|
|
34
|
+
- 默认仅监听 `127.0.0.1`;数据落在 `~/.local/share/opencode-stats-reporter/`,不会上传第三方;
|
|
35
|
+
- **托管模式**:`opencode-stats-reporter web start --mode gateway` 时,会话接口要求网关注入的身份(`X-Forward-Consumer`),并按消费者做租户隔离;本地模式则为单一本地用户;
|
|
36
|
+
- 要求 Node ≥ 24(使用内置 `node:sqlite`,零外部依赖)。
|
|
37
|
+
|
|
38
|
+
## 为什么做这个项目
|
|
39
|
+
|
|
40
|
+
OpenAI、Anthropic 等大厂的用量查询均要求管理级 Key(Admin Key),普通推理 Key 无法查询自己的用量。"**用自己的 API Key 查自己的用量**"是 OpenRouter、LiteLLM、new-api 等网关验证过的标杆能力,也是个人开发者最朴素的需求。本项目把这套能力以最小成本落地,并开源给生态伙伴共同完善。
|
|
41
|
+
|
|
42
|
+
## 总体架构
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
用户浏览器(静态 HTML 面板,config.js 配置自己的 API Key)
|
|
46
|
+
│ Authorization: Bearer <用户自己的 API Key>
|
|
47
|
+
▼
|
|
48
|
+
API 网关(独立路由,key-auth 认证 + 限流,鉴权通过后注入消费者 ID)
|
|
49
|
+
▼ 仅携带消费者 ID,用户凭证不离开网关鉴权层
|
|
50
|
+
函数服务 Webserver 函数
|
|
51
|
+
▼ Prometheus HTTP API(Instant Query,平台内部服务间授权,无需凭证)
|
|
52
|
+
托管 Prometheus(网关访问日志聚合后的用量指标)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **身份即凭证,凭证止于网关**:网关 key-auth 校验通过后,将消费者 ID 注入请求传给函数;函数仅以消费者 ID 作为指标查询的 consumer 过滤值,用户天然只能查到自己的数据。用户 API Key 全程不进入函数服务——对函数而言,这只是一次普通的网关调用。
|
|
56
|
+
- **全链路零凭证流转**:函数查询监控数据源走云平台内部服务间授权(VeFaaS ↔ VMP),无需配置、传递任何访问凭证,也没有凭证可能泄露。
|
|
57
|
+
- 当前实现基于火山引擎(VeFaaS 函数服务 + APIG 网关 + VMP 托管 Prometheus),思路可平移到任意"网关 + 函数 + Prometheus 兼容数据源"的组合。
|
|
58
|
+
|
|
59
|
+
## 目录结构
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
functions/
|
|
63
|
+
└── ops/ # /v1/usage/* 个人用量查询 API(Python,VeFaaS)
|
|
64
|
+
|
|
65
|
+
src/ bin/ package.json
|
|
66
|
+
# opencode-stats-reporter npm 包:Node 会话服务(本地一体化 / 网关托管双模式)
|
|
67
|
+
contrib/
|
|
68
|
+
└── opencode-session-reporter/ # opencode 会话用量上报插件
|
|
69
|
+
test/ # Node 侧测试(store / server / cli)
|
|
70
|
+
panel/ # 零构建静态可视化面板(原生 JS + ECharts CDN)
|
|
71
|
+
├── index.html # ★ 看板入口:双击打开即用(引导视图 + 面板视图)
|
|
72
|
+
├── config.js # 用户私有配置(gitignore,由 config.example.js 复制而来)
|
|
73
|
+
├── config.example.js
|
|
74
|
+
├── css/ js/
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 面板功能
|
|
78
|
+
|
|
79
|
+
- 今日 / 本周 / 本月(UTC+8 日历对齐)三周期切换,KPI 环比徽章(较昨日 / 上周 / 上月)
|
|
80
|
+
- Credit 用量估算:按日分段计量(每日总 Tokens × 当日生效系数 ÷ 百万),跨系数变更自动分段;系数缺失自动降级
|
|
81
|
+
- 输入 Tokens 按 OpenAI 规范拆分:缓存未命中 / 缓存命中 / 输出分别展示
|
|
82
|
+
- Credit 限额提醒:可配置每日 / 每周 / 每月限额(config.js 或页面内「配置限额」),Credit 卡显示离限额差额(未超绿 / 超出红);本月 pacing 进度条按「月限额÷当月天数×第几天」衡量消耗进度,超额橙色告警(规格见 openspec/specs/credit-quota/spec.md)
|
|
83
|
+
- 每日趋势、今日分时、模型分布、供应商分布图表,模型明细表排序 + CSV 导出
|
|
84
|
+
- 手动刷新(10s 冷却)+ 每 5min 自动刷新(页面隐藏时暂停),浅色 / 深色 / 跟随系统主题,纯静态零构建
|
|
85
|
+
- 会话级用量视图(可选):由 opencode 上报插件采集会话 token,面板展示会话列表 / 详情与「网关权威总量 − 会话上报合计 = 未归属用量」(会话数据为**客户端上报口径**,可能延迟或遗漏,非网关权威值)
|
|
86
|
+
|
|
87
|
+
## 配套 /v1/models 端点(需自建)
|
|
88
|
+
|
|
89
|
+
面板除 `/v1/usage/*` 外还调用网关上现有的 `GET /v1/models`(OpenAI 兼容模型列表),用于连通性检测与 Credit 计量。本项目使用的定制版与具体模型目录、计量系数耦合,未包含在本仓库。如需完整体验,请自建返回如下形状的端点:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"object": "list",
|
|
94
|
+
"consumer": "api-key-xxxxxxxx",
|
|
95
|
+
"data": [
|
|
96
|
+
{"id": "your-model", "object": "model", "created": 1700000000, "owned_by": "you",
|
|
97
|
+
"credit": 1.23,
|
|
98
|
+
"credit_history": [{"from": "2026-08-01", "credit": 1.23}]}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `consumer`:回显当前消费者 ID(网关在 key-auth 通过后注入身份头,函数回显即可);
|
|
104
|
+
- `credit` / `credit_history`(可选):模型计量系数及其版本历史(区间语义 `[from, 下一条 from)`),面板据此按日分段估算 Credit 用量。
|
|
105
|
+
|
|
106
|
+
缺少 `consumer` / `credit` 字段时面板自动降级(用量查询不受影响,连通徽标与 Credit 展示降级为 `--`)。
|
|
107
|
+
|
|
108
|
+
## Roadmap
|
|
109
|
+
|
|
110
|
+
- [x] `/v1/usage/*` 个人用量查询函数(已实现并上线运行,内置 mock 模式可无依赖本地开发)
|
|
111
|
+
- [x] `panel/` 零构建静态可视化面板(已实现,与 usage API 联调通过)
|
|
112
|
+
- [x] 会话级用量服务 `functions/session/`(SQLite)+ opencode 上报插件 + 面板会话视图(本期新增)
|
|
113
|
+
- [ ] 会话数据的 retention / 归档,与团队化(多消费者)视图
|
|
114
|
+
- [ ] `/v1/quota`、`/v1/announcements` 等更多个人运营接口
|
|
115
|
+
- [ ] 其他网关 / 函数平台 / Prometheus 兼容数据源的适配
|
|
116
|
+
|
|
117
|
+
## 部署前提
|
|
118
|
+
|
|
119
|
+
1. 支持 Webserver 模式的服务:用量查询函数(`functions/ops`,Python)以 `python3 vefaas_*.py` 启动,监听 `VEFAAS_PORT`;会话服务(`src/`,Node ≥ 24)本地用 `opencode-stats-reporter web start`,托管部署用 `web start --mode gateway`(默认仅回环,数据落在可配置目录);
|
|
120
|
+
2. API 网关:为服务配置路由转发,用量类接口绑定 key-auth 类认证插件与限流插件;**托管模式下会话接口同样走 key-auth**(网关校验后注入 `X-Forward-Consumer` 作为租户键);
|
|
121
|
+
3. Prometheus 兼容数据源:存有带 `consumer` 标签的用量指标(本方案指标由网关访问日志经日志服务定时 SQL 聚合写入);函数到数据源采用云平台内部服务间授权(如火山 VeFaaS ↔ VMP),无需配置访问凭证。
|
|
122
|
+
|
|
123
|
+
## 安全约定
|
|
124
|
+
|
|
125
|
+
- 本仓库**不包含、也不接受**任何真实凭证(API Key、AK/SK、域名、实例 ID)。方案本身运行时也不依赖任何凭证配置:用户凭证止于网关鉴权层,函数到数据源走平台内部服务间授权。
|
|
126
|
+
- `panel/config.js`(用户私有配置)已在 `.gitignore` 中忽略,请勿强制提交。
|
|
127
|
+
- 提交前建议运行 `gitleaks` 等工具自查。
|
|
128
|
+
|
|
129
|
+
## 参与贡献
|
|
130
|
+
|
|
131
|
+
欢迎生态伙伴一起完善:更多个人运营接口、面板体验优化、其他网关/函数平台的适配。请通过 Issue 交流想法、PR 提交代码。
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
[Apache-2.0](LICENSE)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# opencode-session-reporter
|
|
2
|
+
|
|
3
|
+
opencode 客户端插件:在会话空闲时读取各会话(含子代理会话)的 token 用量,聚合为快照后经本地有界队列 + 指数退避**非阻塞**上报到 session-usage 服务端点。载荷**不含任何消费者身份字段**——身份完全由网关(key-auth 注入 `X-Forward-Consumer`)决定。
|
|
4
|
+
|
|
5
|
+
对应规格:`openspec/changes/add-session-usage-tracking/specs/session-usage-reporter/spec.md`
|
|
6
|
+
|
|
7
|
+
## 安装方式
|
|
8
|
+
|
|
9
|
+
把插件文件复制为 opencode 本地插件即可(无需 install、无运行时依赖,`@opencode-ai/*` 仅作类型声明):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 全局(对本机所有项目生效)
|
|
13
|
+
mkdir -p ~/.config/opencode/plugins
|
|
14
|
+
cp session-usage-reporter.ts ~/.config/opencode/plugins/
|
|
15
|
+
|
|
16
|
+
# 或项目级(只对该项目生效)
|
|
17
|
+
mkdir -p .opencode/plugins
|
|
18
|
+
cp session-usage-reporter.ts .opencode/plugins/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- 已在 opencode **1.18.33** 上核实插件 loader 行为并适配:本地 `.ts` 插件的**所有运行时导出必须都是函数**(本文件已满足;loader 甚至会把每个函数导出当插件工厂各调用一次,纯辅助导出已做防御,收到插件输入时返回空对象)。
|
|
22
|
+
- 注意:以 `--pure` 启动 opencode 会禁用全部外部插件(包括本插件)。
|
|
23
|
+
- 一个 opencode server 实例加载一份插件实例;插件按事件里的 `sessionID` 处理对应会话,无需多份。
|
|
24
|
+
|
|
25
|
+
## 环境变量
|
|
26
|
+
|
|
27
|
+
| 变量 | 必填 | 说明 |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `SESSION_USAGE_ENDPOINT` | 是(未设置则插件完全惰性) | 上报端点 URL。插件对它发 `POST`,`content-type: application/json`,body 见下。 |
|
|
30
|
+
| `SESSION_USAGE_API_KEY` | 否 | 上报请求的网关 Key(插件发 `Authorization: Bearer <key>`)。网关 key-auth 通过后注入 `X-Forward-Consumer`,服务端据此确定租户。未配置则不带鉴权头。 |
|
|
31
|
+
|
|
32
|
+
> **身份来源**:载荷本身**不含**任何消费者身份字段。若 `SESSION_USAGE_ENDPOINT` 指向网关后的服务,必须配 `SESSION_USAGE_API_KEY`,否则请求会被网关 401 拒绝。
|
|
33
|
+
|
|
34
|
+
`install_id` 保存在 `~/.local/state/opencode-session-reporter/install_id`(首次自动生成 UUID 并持久化;目录不可写时退化为进程内临时 id)。
|
|
35
|
+
|
|
36
|
+
## 行为
|
|
37
|
+
|
|
38
|
+
- **触发时机**:bus 事件 `session.idle`,或 `session.status` 且 `status.type === "idle"`(两种空闲信号都处理)。
|
|
39
|
+
- **采集**:经 SDK 读取 `session.messages`(仅统计 `role === "assistant"` 且 `info.time.completed` 已设置的**已完成**消息,天然去重不双计),再经 `session.children` 递归子会话。
|
|
40
|
+
- **父子独立**:每个会话(含子代理会话)各自生成一条快照;**父会话快照绝不并入子会话用量**。
|
|
41
|
+
- **口径原样分列**:`tokens_input / tokens_output / tokens_reasoning / tokens_cache_read / tokens_cache_write` 直接取自 `info.tokens`,**客户端不做任何相加或换算**(opencode 语义:`output` 不含 `reasoning`,`input` 不含 `cache.read`;`cost` 恒为 0,不采信、不上报)。合计由服务端派生。
|
|
42
|
+
- **非阻塞与失败隔离**:hook 只触发后台采集并入队,**hook 内绝不等待网络**;采集/上报全程 try/catch,任何异常只记日志,不向会话执行路径抛出。
|
|
43
|
+
- **有界队列 + 重试**:内存队列上限 500 条快照,溢出丢最旧;上报失败按指数退避(`1s × 2^n`,封顶 5 分钟,乘 `[0.5, 1.5)` 随机抖动)重试,失败批次原样保留、不丢数据;单次请求用 `AbortSignal.timeout`(10s)限时。
|
|
44
|
+
- **退出刷新**:`dispose` 钩子在进程退出时以约 **3 秒**预算尽力发送队列,超时即放弃,绝不阻塞退出。
|
|
45
|
+
- **幂等友好**:同一快照重复发送无害;服务端按 `(consumer, install_id, session_id)` upsert,并用 `revision` / `last_activity_at` 单调守卫防乱序覆盖。
|
|
46
|
+
|
|
47
|
+
**修订号(revision)选型**:取「该会话已完成助手消息条数」。追加式会话中随消息增长单调递增;重复上报同值无害;会话压缩/删消息导致回退时,服务端单调守卫会拒绝旧修订,数据不会倒退。
|
|
48
|
+
|
|
49
|
+
**上报载荷**(每次 flush 把当前积压的快照合并为一个请求,同会话多快照按最高修订合并):
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"schema_version": 1,
|
|
54
|
+
"install_id": "0b3f7e6a-....",
|
|
55
|
+
"reporter": { "client": "opencode", "version": "0.1.0" },
|
|
56
|
+
"sessions": [
|
|
57
|
+
{
|
|
58
|
+
"session_id": "ses_...",
|
|
59
|
+
"revision": 12,
|
|
60
|
+
"model": "zhipu/glm-4.6",
|
|
61
|
+
"agent": "build",
|
|
62
|
+
"tokens_input": 10234,
|
|
63
|
+
"tokens_output": 4521,
|
|
64
|
+
"tokens_reasoning": 812,
|
|
65
|
+
"tokens_cache_read": 90211,
|
|
66
|
+
"tokens_cache_write": 1320,
|
|
67
|
+
"message_count": 12,
|
|
68
|
+
"session_created_at": 1759000000000,
|
|
69
|
+
"last_activity_at": 1759003600123
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`model` 取该会话最后一条已完成助手消息的 `providerID/modelID`;`agent` 取其 `mode`(无则为 `null`)。载荷中**不存在** consumer/identity 类字段。
|
|
76
|
+
|
|
77
|
+
## 测试
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bun test reporter.test.ts # 或 bun run reporter.test.ts
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
自包含 mock 测试(mock client + mock fetch),无网络、无需真实 opencode:覆盖已完成消息过滤、父子独立计量、载荷结构与无身份字段、队列上限丢最旧、失败批次保留 + 退避调度、成功清空队列、dispose 成功/失败路径、退避曲线、install_id 稳定性。
|
|
84
|
+
|
|
85
|
+
## 局限
|
|
86
|
+
|
|
87
|
+
- **队列在内存**:进程崩溃(非正常 dispose 退出)会丢失未上报的积压;队列满后丢最旧。
|
|
88
|
+
- **会话级快照**:无 per-message / 按模型 / 按时间细分;一个会话只记录最后一条消息的 model/agent,多模型会话归属到最后的模型。
|
|
89
|
+
- **消息被压缩/删除**时 `message_count` 可能回退,revision 随之回退——依赖服务端单调守卫保留较高修订。
|
|
90
|
+
- **不携带身份与鉴权**:请求无凭据,完全依赖网关注入消费者身份(与设计 D4/D9 一致);端点本身须由网关/服务端做租户隔离与限流。
|
|
91
|
+
- **与 opencode 版本耦合**:消息/事件/插件 loader 形状按 1.18.33 核实,字段均为防御式读取;升级 opencode 后建议重跑测试。
|
|
92
|
+
- 每台机器一个 `install_id`;同一消费者跨机器的会话默认按 `install_id` 分列。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-session-reporter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "opencode 客户端插件:在会话空闲时聚合各会话(含子代理会话)的 token 用量快照,经有界队列 + 指数退避非阻塞上报;载荷不含消费者身份字段。",
|
|
7
|
+
"main": "session-usage-reporter.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "bun test reporter.test.ts",
|
|
10
|
+
"test:direct": "bun run reporter.test.ts"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@opencode-ai/plugin": "^1.18.33",
|
|
14
|
+
"@opencode-ai/sdk": "^1.18.33"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm 形态插件入口(供 `opencode plugin @dijkspicy/opencode-stats-reporter` 使用)。
|
|
3
|
+
*
|
|
4
|
+
* opencode 加载 npm 插件时按 v1 形态解析:default 导出必须是对象 { id, server },
|
|
5
|
+
* 其中 server 为 Plugin 函数(hooks 工厂)。与本地文件形态
|
|
6
|
+
* (contrib/opencode-session-reporter/session-usage-reporter.ts,裸函数 + 多命名导出)
|
|
7
|
+
* 的区别仅在于导出形状,行为完全一致。
|
|
8
|
+
*/
|
|
9
|
+
import { SessionUsageReporter } from "./session-usage-reporter.ts"
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
id: "opencode-stats-reporter",
|
|
13
|
+
server: SessionUsageReporter,
|
|
14
|
+
}
|