@lijinzhao8/opencode-usage 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -69
- package/cli/index.js +34 -34
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# opencode-usage
|
|
2
2
|
|
|
3
|
-
OpenCode
|
|
3
|
+
OpenCode 插件:在 TUI 底部实时显示 API 用量和费用统计。
|
|
4
4
|
|
|
5
5
|
## 功能
|
|
6
6
|
|
|
7
7
|
- 实时显示 token 用量(输入/输出/缓存)
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
8
|
+
- 按模型计算费用,支持自定义费率
|
|
9
|
+
- 支持中转共享组(多个 Provider 共享费率配置)
|
|
10
|
+
- 支持时间段倍率(高峰时段加价)
|
|
11
|
+
- 显示每条消息的增量费用和会话累计总量
|
|
12
|
+
- 自带 Web 管理界面,可视化编辑费率配置
|
|
13
13
|
|
|
14
14
|
## 安装
|
|
15
15
|
|
|
@@ -17,47 +17,39 @@ OpenCode 插件:实时显示 API 用量和费用统计,完全复制 API-Prox
|
|
|
17
17
|
opencode plugin @lijinzhao8/opencode-usage@latest
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## 使用
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
### 启动管理界面
|
|
23
23
|
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
"plugin": [
|
|
27
|
-
"@lijinzhao8/opencode-usage@latest"
|
|
28
|
-
]
|
|
29
|
-
}
|
|
24
|
+
```bash
|
|
25
|
+
npx @lijinzhao8/opencode-usage
|
|
30
26
|
```
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
浏览器打开 http://localhost:3456 即可可视化编辑费率配置。
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
### 配置说明
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
32
|
+
配置文件为插件目录下的 `config.json`,也可通过管理界面编辑。
|
|
33
|
+
|
|
34
|
+
#### 中转共享组
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`,
|
|
56
|
-
},
|
|
57
|
-
];
|
|
36
|
+
多个 Provider 可以共享同一套费率配置:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"groups": [
|
|
41
|
+
{
|
|
42
|
+
"id": "grp_main",
|
|
43
|
+
"name": "主力组",
|
|
44
|
+
"enabled": true,
|
|
45
|
+
"provider_ids": ["api-proxy", "xiaomi-token-plan"],
|
|
46
|
+
"rates_text": "* input=0.15 output=0.60 cache_hit=0.03 cache_create=0.15"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
58
50
|
```
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
#### 费率格式(rates_text)
|
|
61
53
|
|
|
62
54
|
每行一个模型规则,格式:`model_name key=value ...`
|
|
63
55
|
|
|
@@ -70,13 +62,13 @@ DEFAULT_GROUPS = [
|
|
|
70
62
|
| `calls` | 按次计费次数 | 次 |
|
|
71
63
|
| `call_value` | 每次费用 | 美元 |
|
|
72
64
|
| `multiplier` | 全局倍率 | 倍数 |
|
|
73
|
-
| `time_mult` | 时间段倍率 |
|
|
65
|
+
| `time_mult` | 时间段倍率 | `HH:MM-HH:MM=倍率;...` |
|
|
74
66
|
|
|
75
67
|
`*` 匹配所有未明确列出的模型。
|
|
76
68
|
|
|
77
|
-
|
|
69
|
+
#### 时间段倍率
|
|
78
70
|
|
|
79
|
-
```
|
|
71
|
+
```
|
|
80
72
|
# 北京时间 22:00-02:00 加价 50%
|
|
81
73
|
time_mult=22:00-02:00=1.5
|
|
82
74
|
|
|
@@ -84,36 +76,10 @@ time_mult=22:00-02:00=1.5
|
|
|
84
76
|
time_mult=22:00-02:00=1.5;08:00-20:00=1.0
|
|
85
77
|
```
|
|
86
78
|
|
|
87
|
-
## 费用计算公式
|
|
88
|
-
|
|
89
|
-
完全复制 API-Proxy 的 `calculateAllowanceDebitUnits`:
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
normalInput = max(0, input - cacheHit - cacheCreate)
|
|
93
|
-
cost = (normalInput / 1M × rate.input
|
|
94
|
-
+ output / 1M × rate.output
|
|
95
|
-
+ cacheHit / 1M × rate.cache_hit
|
|
96
|
-
+ cacheCreate / 1M × rate.cache_create)
|
|
97
|
-
× multiplier × timeMultiplier
|
|
98
|
-
```
|
|
99
|
-
|
|
100
79
|
## 显示位置
|
|
101
80
|
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
## 开发
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
# 安装依赖
|
|
109
|
-
npm install
|
|
110
|
-
|
|
111
|
-
# 构建
|
|
112
|
-
bun run build
|
|
113
|
-
|
|
114
|
-
# 类型检查
|
|
115
|
-
bun run typecheck
|
|
116
|
-
```
|
|
81
|
+
- 侧边栏底部(session 视图)
|
|
82
|
+
- 首页底部(home 视图)
|
|
117
83
|
|
|
118
84
|
## License
|
|
119
85
|
|
package/cli/index.js
CHANGED
|
@@ -47,54 +47,54 @@ function getAdminHTML() {
|
|
|
47
47
|
<title>opencode-usage 管理界面</title>
|
|
48
48
|
<style>
|
|
49
49
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
50
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #
|
|
51
|
-
.header { background: #
|
|
52
|
-
.header h1 { font-size: 20px; color: #
|
|
53
|
-
.header .badge { background: #
|
|
50
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f0f4f8; color: #1a202c; line-height: 1.6; }
|
|
51
|
+
.header { background: #fff; border-bottom: 1px solid #d1dbe8; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
|
|
52
|
+
.header h1 { font-size: 20px; color: #2563eb; }
|
|
53
|
+
.header .badge { background: #2563eb; color: #fff; padding: 2px 10px; border-radius: 12px; font-size: 12px; }
|
|
54
54
|
.container { max-width: 1200px; margin: 0 auto; padding: 24px; }
|
|
55
|
-
.tabs { display: flex; gap: 0; margin-bottom: 24px; border-bottom: 2px solid #
|
|
56
|
-
.tab { padding: 10px 20px; cursor: pointer; color: #
|
|
57
|
-
.tab:hover { color: #
|
|
58
|
-
.tab.active { color: #
|
|
55
|
+
.tabs { display: flex; gap: 0; margin-bottom: 24px; border-bottom: 2px solid #d1dbe8; }
|
|
56
|
+
.tab { padding: 10px 20px; cursor: pointer; color: #6b7a8d; border-bottom: 2px solid transparent; margin-bottom: -2px; transition: all 0.2s; }
|
|
57
|
+
.tab:hover { color: #2563eb; }
|
|
58
|
+
.tab.active { color: #2563eb; border-bottom-color: #2563eb; font-weight: 600; }
|
|
59
59
|
.panel { display: none; }
|
|
60
60
|
.panel.active { display: block; }
|
|
61
|
-
.card { background: #
|
|
61
|
+
.card { background: #fff; border: 1px solid #d1dbe8; border-radius: 10px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
|
|
62
62
|
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
|
63
|
-
.card-title { font-size: 16px; font-weight: 600; color: #
|
|
63
|
+
.card-title { font-size: 16px; font-weight: 600; color: #1a202c; display: flex; align-items: center; gap: 8px; }
|
|
64
64
|
.card-title .dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
65
|
-
.card-title .dot.green { background: #
|
|
66
|
-
.card-title .dot.gray { background: #
|
|
65
|
+
.card-title .dot.green { background: #22c55e; }
|
|
66
|
+
.card-title .dot.gray { background: #9ca3af; }
|
|
67
67
|
.btn { padding: 6px 14px; border-radius: 6px; border: none; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.15s; }
|
|
68
|
-
.btn-primary { background: #
|
|
69
|
-
.btn-primary:hover { background: #
|
|
70
|
-
.btn-danger { background: #
|
|
71
|
-
.btn-danger:hover { background: #
|
|
72
|
-
.btn-ghost { background: transparent; color: #
|
|
73
|
-
.btn-ghost:hover { color: #
|
|
68
|
+
.btn-primary { background: #2563eb; color: #fff; }
|
|
69
|
+
.btn-primary:hover { background: #1d4ed8; }
|
|
70
|
+
.btn-danger { background: #ef4444; color: #fff; }
|
|
71
|
+
.btn-danger:hover { background: #dc2626; }
|
|
72
|
+
.btn-ghost { background: transparent; color: #6b7a8d; border: 1px solid #d1dbe8; }
|
|
73
|
+
.btn-ghost:hover { color: #2563eb; border-color: #2563eb; }
|
|
74
74
|
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
|
75
75
|
.form-group { margin-bottom: 14px; }
|
|
76
|
-
.form-group label { display: block; font-size: 13px; color: #
|
|
77
|
-
.form-group input, .form-group textarea, .form-group select { width: 100%; padding: 8px 12px; background: #
|
|
78
|
-
.form-group input:focus, .form-group textarea:focus { outline: none; border-color: #
|
|
76
|
+
.form-group label { display: block; font-size: 13px; color: #6b7a8d; margin-bottom: 4px; }
|
|
77
|
+
.form-group input, .form-group textarea, .form-group select { width: 100%; padding: 8px 12px; background: #f7f9fc; border: 1px solid #d1dbe8; border-radius: 6px; color: #1a202c; font-size: 14px; font-family: inherit; transition: border-color 0.15s; }
|
|
78
|
+
.form-group input:focus, .form-group textarea:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }
|
|
79
79
|
.form-group textarea { min-height: 100px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 13px; resize: vertical; }
|
|
80
80
|
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
81
81
|
.form-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
|
|
82
|
-
.tag { display: inline-block; background: #
|
|
83
|
-
.tag.blue { background: #
|
|
84
|
-
.divider { height: 1px; background: #
|
|
85
|
-
.empty { text-align: center; padding: 40px; color: #
|
|
86
|
-
.save-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #
|
|
87
|
-
.save-bar .status { color: #
|
|
82
|
+
.tag { display: inline-block; background: #e8eef5; color: #4b6584; padding: 2px 8px; border-radius: 4px; font-size: 12px; margin: 2px; }
|
|
83
|
+
.tag.blue { background: #dbeafe; color: #2563eb; }
|
|
84
|
+
.divider { height: 1px; background: #d1dbe8; margin: 16px 0; }
|
|
85
|
+
.empty { text-align: center; padding: 40px; color: #9ca3af; }
|
|
86
|
+
.save-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; border-top: 1px solid #d1dbe8; padding: 12px 24px; display: flex; justify-content: space-between; align-items: center; z-index: 100; box-shadow: 0 -1px 3px rgba(0,0,0,0.04); }
|
|
87
|
+
.save-bar .status { color: #22c55e; font-size: 13px; opacity: 0; transition: opacity 0.3s; }
|
|
88
88
|
.save-bar .status.show { opacity: 1; }
|
|
89
|
-
.help { font-size: 12px; color: #
|
|
90
|
-
.rate-preview { background: #
|
|
89
|
+
.help { font-size: 12px; color: #9ca3af; margin-top: 4px; }
|
|
90
|
+
.rate-preview { background: #f7f9fc; border: 1px solid #d1dbe8; border-radius: 6px; padding: 12px; font-family: 'SF Mono', monospace; font-size: 13px; color: #22c55e; white-space: pre-wrap; margin-top: 8px; }
|
|
91
91
|
.inline-flex { display: flex; align-items: center; gap: 8px; }
|
|
92
92
|
.switch { position: relative; width: 36px; height: 20px; }
|
|
93
93
|
.switch input { opacity: 0; width: 0; height: 0; }
|
|
94
|
-
.switch .slider { position: absolute; cursor: pointer; inset: 0; background: #
|
|
95
|
-
.switch .slider:before { content: ""; position: absolute; height: 14px; width: 14px; left: 3px; bottom: 3px; background: #
|
|
96
|
-
.switch input:checked + .slider { background: #
|
|
97
|
-
.switch input:checked + .slider:before { transform: translateX(16px);
|
|
94
|
+
.switch .slider { position: absolute; cursor: pointer; inset: 0; background: #d1dbe8; border-radius: 20px; transition: 0.2s; }
|
|
95
|
+
.switch .slider:before { content: ""; position: absolute; height: 14px; width: 14px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.15); }
|
|
96
|
+
.switch input:checked + .slider { background: #2563eb; }
|
|
97
|
+
.switch input:checked + .slider:before { transform: translateX(16px); }
|
|
98
98
|
body { padding-bottom: 60px; }
|
|
99
99
|
</style>
|
|
100
100
|
</head>
|
|
@@ -102,7 +102,7 @@ function getAdminHTML() {
|
|
|
102
102
|
|
|
103
103
|
<div class="header">
|
|
104
104
|
<h1>opencode-usage 管理界面</h1>
|
|
105
|
-
<span class="badge">v1.
|
|
105
|
+
<span class="badge">v1.1.2</span>
|
|
106
106
|
</div>
|
|
107
107
|
|
|
108
108
|
<div class="container">
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lijinzhao8/opencode-usage",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "OpenCode plugin
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "OpenCode plugin for real-time API usage and cost tracking in the TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|