opencode-copilot-usage-detector 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/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/dist/aggregator.d.ts +40 -0
- package/dist/aggregator.d.ts.map +1 -0
- package/dist/aggregator.js +254 -0
- package/dist/aggregator.js.map +1 -0
- package/dist/classifier.d.ts +50 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +304 -0
- package/dist/classifier.js.map +1 -0
- package/dist/copilot-budget.d.ts +57 -0
- package/dist/copilot-budget.d.ts.map +1 -0
- package/dist/copilot-budget.js +500 -0
- package/dist/copilot-budget.js.map +1 -0
- package/dist/debug.d.ts +7 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +49 -0
- package/dist/debug.js.map +1 -0
- package/dist/estimator.d.ts +92 -0
- package/dist/estimator.d.ts.map +1 -0
- package/dist/estimator.js +523 -0
- package/dist/estimator.js.map +1 -0
- package/dist/github-api.d.ts +11 -0
- package/dist/github-api.d.ts.map +1 -0
- package/dist/github-api.js +273 -0
- package/dist/github-api.js.map +1 -0
- package/dist/persistence.d.ts +28 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +246 -0
- package/dist/persistence.js.map +1 -0
- package/dist/tools.d.ts +20 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +300 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-03-21)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
- Token and request tracking per day/model with RPM monitoring
|
|
9
|
+
- 5-stage rate-limit classifier (burst, preview, daily)
|
|
10
|
+
- Adaptive limit estimator with confidence scoring and 14-day decay
|
|
11
|
+
- System prompt injection with budget status on every LLM turn
|
|
12
|
+
- `/budget` slash command (status, history, insights, errors, recompute)
|
|
13
|
+
- GitHub billing API integration for premium request tracking
|
|
14
|
+
- Preview model auto-detection
|
|
15
|
+
- Temporal pattern analysis and insight generation
|
|
16
|
+
- Model fallback detection
|
|
17
|
+
- Configurable threshold notifications (60%, 80%, 95%)
|
|
18
|
+
- Timezone-aware day boundaries
|
|
19
|
+
- JSONL auto-rotation (50MB / 90 days)
|
|
20
|
+
- Debug event logging mode
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 moodl
|
|
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,336 @@
|
|
|
1
|
+
# opencode-copilot-usage-detector
|
|
2
|
+
|
|
3
|
+
> **Experimental** — This plugin is in early development. Features may change, data formats may evolve, and there will be rough edges. Use at your own risk and please [report issues](https://github.com/moodl/opencode-copilot-usage-detector/issues).
|
|
4
|
+
|
|
5
|
+
An [OpenCode](https://opencode.ai) plugin that tracks GitHub Copilot token usage across sessions, empirically learns rate limits, and proactively informs you before you hit them.
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
GitHub Copilot doesn't publish concrete token/request limits for the coding assistant. There are multiple opaque limit tiers:
|
|
10
|
+
|
|
11
|
+
- **Monthly premium requests** with model-specific multipliers
|
|
12
|
+
- **Short-term burst limits** per time window
|
|
13
|
+
- **Preview model limits** that are separate and stricter
|
|
14
|
+
|
|
15
|
+
This plugin learns these limits from your own usage patterns and warns you as you approach them.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Token & request tracking** — Per-day, per-model usage with RPM monitoring
|
|
20
|
+
- **Adaptive limit learning** — Weighted averages with exponential recency decay and confidence scoring
|
|
21
|
+
- **Multi-dimensional hypothesis tracking** — Learns whether limits are token-based, request-based, or RPM-based
|
|
22
|
+
- **Preview model detection** — Automatically identifies models with separate, stricter limits
|
|
23
|
+
- **Rate-limit classification** — 5-stage classifier distinguishing burst, preview, and daily limits
|
|
24
|
+
- **System prompt injection** — Budget status in every LLM context (zero tool-call overhead)
|
|
25
|
+
- **Threshold notifications** — Configurable alerts at 60%, 80%, 95% of estimated limits
|
|
26
|
+
- **Temporal patterns** — Learns what time of day you typically hit limits and how model choice affects runway
|
|
27
|
+
- **Model fallback detection** — Detects when Copilot silently downgrades your model
|
|
28
|
+
- **GitHub billing API integration** — Fetches real premium request usage when auth is available
|
|
29
|
+
- **Full error catalog** — Logs all API errors for pattern analysis
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- [OpenCode](https://opencode.ai) v1.2.0 or later
|
|
34
|
+
- Node.js 18+
|
|
35
|
+
- GitHub Copilot subscription
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
### 1. Install the package
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd ~/.config/opencode
|
|
43
|
+
npm install git+https://github.com/moodl/opencode-copilot-usage-detector.git
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Register the plugin
|
|
47
|
+
|
|
48
|
+
Add it to your `~/.config/opencode/opencode.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"plugin": ["opencode-copilot-usage-detector"]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you already have other plugins, add it to the existing array:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"plugin": ["@tarquinen/opencode-dcp@latest", "opencode-copilot-usage-detector"]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Restart OpenCode
|
|
65
|
+
|
|
66
|
+
The plugin loads automatically on startup. Use `/budget` to verify it's working.
|
|
67
|
+
|
|
68
|
+
### Updating
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cd ~/.config/opencode
|
|
72
|
+
npm install git+https://github.com/moodl/opencode-copilot-usage-detector.git
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then restart OpenCode.
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
The plugin works automatically -- no action needed. It:
|
|
80
|
+
|
|
81
|
+
1. Tracks every LLM request via event hooks
|
|
82
|
+
2. Injects budget status into the system prompt
|
|
83
|
+
3. Notifies you in chat when approaching estimated limits
|
|
84
|
+
4. Logs everything to `~/.config/copilot-budget/observations.jsonl`
|
|
85
|
+
|
|
86
|
+
### `/budget` Command
|
|
87
|
+
|
|
88
|
+
| Command | Description |
|
|
89
|
+
|---------|-------------|
|
|
90
|
+
| `/budget` or `/budget status` | Current usage, estimates, and model breakdown |
|
|
91
|
+
| `/budget history` | Daily token usage for the last 14 days |
|
|
92
|
+
| `/budget insights` | Learned patterns, limit estimates, temporal analysis |
|
|
93
|
+
| `/budget errors` | Rate limit events and error catalog |
|
|
94
|
+
| `/budget recompute` | Force recompute all estimates from observations |
|
|
95
|
+
|
|
96
|
+
### Example: `/budget status`
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
## Copilot Budget Status — 2026-03-21
|
|
100
|
+
|
|
101
|
+
### Monthly Premium Requests
|
|
102
|
+
Premium requests this month: 147 / 300 (49% used, 153 remaining)
|
|
103
|
+
claude-opus-4.5: 89 requests
|
|
104
|
+
gpt-5.4-mini: 58 requests
|
|
105
|
+
*Last updated: 2026-03-21T14:30:00Z*
|
|
106
|
+
|
|
107
|
+
**Total tokens today:** 1.8M
|
|
108
|
+
**Total requests today:** 67
|
|
109
|
+
**Total cost today:** $0.0000
|
|
110
|
+
**Current RPM:** 3 req/min (peak: 7)
|
|
111
|
+
|
|
112
|
+
**Estimated daily token limit:** ~2.9M (82% confidence)
|
|
113
|
+
**Usage:** ~63%
|
|
114
|
+
**Limit type:** tokens
|
|
115
|
+
|
|
116
|
+
### Model Breakdown
|
|
117
|
+
|
|
118
|
+
| Model | Tokens | Requests | Category |
|
|
119
|
+
|------------------|--------|----------|----------|
|
|
120
|
+
| claude-opus-4.5 | 1.4M | 42 | stable |
|
|
121
|
+
| gpt-5.4-mini | 422K | 25 | stable |
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Example: System Prompt Injection
|
|
125
|
+
|
|
126
|
+
Every LLM response automatically sees this context (no tool call needed):
|
|
127
|
+
|
|
128
|
+
```xml
|
|
129
|
+
<copilot-budget>
|
|
130
|
+
Premium requests this month: 147 / 300 (49% used, 153 remaining)
|
|
131
|
+
Daily token usage: 1.8M tokens (67 requests)
|
|
132
|
+
Estimated daily limit: ~2.9M tokens (confidence: 82%)
|
|
133
|
+
Usage percentage: ~63%
|
|
134
|
+
Cost today: $0.0000
|
|
135
|
+
Current rate: 3 req/min (peak: 7)
|
|
136
|
+
|
|
137
|
+
Model breakdown:
|
|
138
|
+
claude-opus-4.5: 1.4M tokens / 42 requests (stable)
|
|
139
|
+
gpt-5.4-mini: 422K tokens / 25 requests (stable)
|
|
140
|
+
</copilot-budget>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Example: Threshold Notification
|
|
144
|
+
|
|
145
|
+
When approaching a learned limit, you'll see a chat message like:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
⚡ 80% of daily Copilot budget used (2.3M / ~2.9M est.)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Example: Rate Limit Alert
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
🔴 Rate limited! Day total: 2.8M tokens, 142 requests
|
|
155
|
+
Model: claude-opus-4.5 | Status: 429 | Class: hard_daily_limit
|
|
156
|
+
Run `/budget errors` for details.
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Example: `/budget insights`
|
|
160
|
+
|
|
161
|
+
After accumulating data over several days:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
## Copilot Budget Insights
|
|
165
|
+
|
|
166
|
+
**Data since:** 2026-03-01
|
|
167
|
+
**Days observed:** 21
|
|
168
|
+
**Days with limit hit:** 8
|
|
169
|
+
|
|
170
|
+
### Global Daily Budget
|
|
171
|
+
- Token estimate: ~2.9M (+/- 210K)
|
|
172
|
+
- Confidence: 82% (8 data points)
|
|
173
|
+
- Active limit type: tokens
|
|
174
|
+
|
|
175
|
+
### Model Categories
|
|
176
|
+
|
|
177
|
+
| Model | Category | Source | Confidence | Own Limit | Errors |
|
|
178
|
+
|------------------|----------|--------|------------|-----------|--------|
|
|
179
|
+
| claude-opus-4.5 | stable | auto | 95% | - | 5 |
|
|
180
|
+
| claude-opus-4.6 | preview | auto | 88% | ~400K | 4 |
|
|
181
|
+
| gpt-5.4-mini | stable | auto | 90% | - | 1 |
|
|
182
|
+
|
|
183
|
+
### Temporal Patterns
|
|
184
|
+
- Typical limit time: 16:30
|
|
185
|
+
- Std dev: +/- 75 min
|
|
186
|
+
- Reset type: daily_fixed
|
|
187
|
+
- Estimated reset: 00:00
|
|
188
|
+
|
|
189
|
+
### Generated Insights
|
|
190
|
+
- **[model_impact]** claude-opus-4.5-heavy days hit limits ~2.1h earlier
|
|
191
|
+
than mixed days (75% confidence, 8 data points)
|
|
192
|
+
- **[preview_detection]** claude-opus-4.6 has separate preview limit
|
|
193
|
+
(~400K tokens) (88% confidence, 4 data points)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
Optionally create `~/.config/copilot-budget/config.json`:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"debug": false,
|
|
203
|
+
"copilot_plan": "pro",
|
|
204
|
+
"known_preview_models": [],
|
|
205
|
+
"known_stable_models": [],
|
|
206
|
+
"notification_thresholds": [60, 80, 95],
|
|
207
|
+
"premium_request_multipliers": {
|
|
208
|
+
"claude-opus-4.5": 3.0,
|
|
209
|
+
"claude-sonnet-4.5": 1.0,
|
|
210
|
+
"gpt-5.4-mini": 0.33
|
|
211
|
+
},
|
|
212
|
+
"monthly_premium_allowance": 1000,
|
|
213
|
+
"timezone": "Europe/Berlin",
|
|
214
|
+
"quiet_mode": false
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
All fields are optional -- sensible defaults are used.
|
|
219
|
+
|
|
220
|
+
| Field | Description | Default |
|
|
221
|
+
|-------|-------------|---------|
|
|
222
|
+
| `debug` | Log all events to `debug-events.jsonl` | `false` |
|
|
223
|
+
| `copilot_plan` | Your Copilot plan (`free`, `pro`, `pro+`, `business`, `enterprise`) | `"pro"` |
|
|
224
|
+
| `known_preview_models` | Models to always treat as preview | `[]` |
|
|
225
|
+
| `known_stable_models` | Models to always treat as stable | `[]` |
|
|
226
|
+
| `notification_thresholds` | Percentage thresholds for chat warnings | `[60, 80, 95]` |
|
|
227
|
+
| `premium_request_multipliers` | Model cost multipliers for weighted tracking | `{}` |
|
|
228
|
+
| `monthly_premium_allowance` | Override monthly premium request allowance | `1000` |
|
|
229
|
+
| `timezone` | Timezone for day boundaries (e.g., `Europe/Berlin`, `America/New_York`) | `"UTC"` |
|
|
230
|
+
| `quiet_mode` | Suppress threshold notifications | `false` |
|
|
231
|
+
|
|
232
|
+
## Data Storage
|
|
233
|
+
|
|
234
|
+
All data is stored locally in `~/.config/copilot-budget/`:
|
|
235
|
+
|
|
236
|
+
| File | Description |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| `observations.jsonl` | Append-only event log (source of truth) |
|
|
239
|
+
| `estimates.json` | Derived limit model (can be deleted and regenerated) |
|
|
240
|
+
| `config.json` | User configuration |
|
|
241
|
+
| `debug-events.jsonl` | Debug event log (only when `debug: true`) |
|
|
242
|
+
|
|
243
|
+
The JSONL file auto-rotates at 50MB or when entries are older than 90 days. No data is sent anywhere -- everything stays on your machine.
|
|
244
|
+
|
|
245
|
+
## How It Learns
|
|
246
|
+
|
|
247
|
+
1. **Aggregation** -- Every LLM response's token counts are recorded per model per day
|
|
248
|
+
2. **Error detection** -- API errors (especially HTTP 429) are captured with full context including response headers
|
|
249
|
+
3. **Classification** -- A 5-stage classifier determines if an error is a burst limit, preview limit, or daily limit
|
|
250
|
+
4. **Estimation** -- Weighted averages with 14-day half-life produce limit estimates with confidence scores
|
|
251
|
+
5. **Insight generation** -- After accumulating data, the system identifies patterns (e.g., "opus-heavy days hit limits 2h earlier")
|
|
252
|
+
|
|
253
|
+
## Architecture
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
Plugin Entry (copilot-budget.ts)
|
|
257
|
+
+-- Config Hook (registers /budget command)
|
|
258
|
+
+-- Command Handler (/budget subcommands)
|
|
259
|
+
+-- Event Handler (message.updated, session.error)
|
|
260
|
+
+-- chat.params Hook (per-session model/provider capture)
|
|
261
|
+
+-- System Prompt Injection
|
|
262
|
+
+-- Session Compaction Context
|
|
263
|
+
+-- Budget Tool (for LLM tool-calling)
|
|
264
|
+
|
|
265
|
+
Aggregator (aggregator.ts)
|
|
266
|
+
+-- Token + Request counting (per-session, per-model)
|
|
267
|
+
+-- RPM tracking (sliding window)
|
|
268
|
+
+-- Day rollover + recovery detection
|
|
269
|
+
+-- Startup recovery from JSONL
|
|
270
|
+
|
|
271
|
+
Classifier (classifier.ts)
|
|
272
|
+
+-- Stage 1: Error message pattern matching
|
|
273
|
+
+-- Stage 2: Cross-model correlation
|
|
274
|
+
+-- Stage 3: Cancel-rate analysis
|
|
275
|
+
+-- Stage 4: Global budget comparison
|
|
276
|
+
+-- Stage 5: Recovery-time classification
|
|
277
|
+
+-- Delayed reclassification (10-min timer)
|
|
278
|
+
|
|
279
|
+
Estimator (estimator.ts)
|
|
280
|
+
+-- Global daily budget (weighted average)
|
|
281
|
+
+-- Per-model estimates
|
|
282
|
+
+-- Limit dimension hypotheses (tokens/requests/RPM)
|
|
283
|
+
+-- Model category detection (stable/preview)
|
|
284
|
+
+-- Temporal pattern analysis
|
|
285
|
+
+-- Multiplier hypothesis tracking
|
|
286
|
+
+-- Insight generation
|
|
287
|
+
|
|
288
|
+
GitHub API (github-api.ts)
|
|
289
|
+
+-- Premium request usage from billing API
|
|
290
|
+
+-- Multi-strategy auth (Copilot token, gh CLI, fallback)
|
|
291
|
+
+-- Periodic polling (15-min interval)
|
|
292
|
+
|
|
293
|
+
Persistence (persistence.ts)
|
|
294
|
+
+-- JSONL append + filtered read
|
|
295
|
+
+-- Atomic rotation (size/age)
|
|
296
|
+
+-- Estimates read/write
|
|
297
|
+
+-- Config management
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Development
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
git clone https://github.com/moodl/opencode-copilot-usage-detector.git
|
|
304
|
+
cd opencode-copilot-usage-detector
|
|
305
|
+
npm install
|
|
306
|
+
npm run build
|
|
307
|
+
npm test
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
After code changes:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npm run build
|
|
314
|
+
cd ~/.config/opencode
|
|
315
|
+
npm install /path/to/opencode-copilot-usage-detector
|
|
316
|
+
# Restart OpenCode
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Disclaimer
|
|
320
|
+
|
|
321
|
+
This project is **not affiliated with, endorsed by, or associated with GitHub, Microsoft, or OpenCode** in any way. It is an independent, community-built tool.
|
|
322
|
+
|
|
323
|
+
- This plugin observes your local usage patterns and API error responses. It does **not** access any private or undocumented APIs beyond the public GitHub billing endpoints (which require explicit user authorization).
|
|
324
|
+
- Rate limit estimates are **empirical approximations**, not official figures. GitHub may change limits at any time without notice.
|
|
325
|
+
- The authors assume **no responsibility** for any consequences of using this plugin, including but not limited to: account restrictions, incorrect estimates, missed rate limits, or any impact on your GitHub Copilot service.
|
|
326
|
+
- All data collected by this plugin is stored **locally on your machine** and is never transmitted to any external service.
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
[MIT](LICENSE) -- see [LICENSE](LICENSE) for full text.
|
|
331
|
+
|
|
332
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. See the license for the complete terms.
|
|
333
|
+
|
|
334
|
+
## Contributing
|
|
335
|
+
|
|
336
|
+
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DailyState, SessionState } from "./types.js";
|
|
2
|
+
export declare function setTimezone(tz: string): void;
|
|
3
|
+
export declare function getCurrentRPM(): number;
|
|
4
|
+
export declare function recoverFromJSONL(): void;
|
|
5
|
+
export interface AssistantTokens {
|
|
6
|
+
total: number;
|
|
7
|
+
input: number;
|
|
8
|
+
output: number;
|
|
9
|
+
reasoning: number;
|
|
10
|
+
cache: {
|
|
11
|
+
read: number;
|
|
12
|
+
write: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface IncomingMessage {
|
|
16
|
+
messageId: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
modelId: string;
|
|
19
|
+
providerId: string;
|
|
20
|
+
tokens: AssistantTokens;
|
|
21
|
+
cost: number;
|
|
22
|
+
finished: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function processAssistantMessage(msg: IncomingMessage): void;
|
|
25
|
+
export interface IncomingError {
|
|
26
|
+
sessionId: string | undefined;
|
|
27
|
+
errorName: string;
|
|
28
|
+
errorMessage: string;
|
|
29
|
+
errorRaw: string;
|
|
30
|
+
statusCode: number | undefined;
|
|
31
|
+
isRetryable: boolean;
|
|
32
|
+
responseHeaders: Record<string, string> | undefined;
|
|
33
|
+
responseBody: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
export declare function processErrorEvent(err: IncomingError, lastRequestedModel: string | null, lastRequestedProvider: string | null): string;
|
|
36
|
+
export declare function getDaily(): DailyState;
|
|
37
|
+
export declare function getSession(id: string): SessionState;
|
|
38
|
+
/** Reset all module-level state. For testing only. */
|
|
39
|
+
export declare function resetState(): void;
|
|
40
|
+
//# sourceMappingURL=aggregator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregator.d.ts","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,YAAY,EAAc,MAAM,YAAY,CAAA;AAoBlF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5C;AA6CD,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAoCD,wBAAgB,gBAAgB,IAAI,IAAI,CA8CvC;AAMD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CA6DlE;AAMD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,WAAW,EAAE,OAAO,CAAA;IACpB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;IACnD,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CACjC;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,kBAAkB,EAAE,MAAM,GAAG,IAAI,EACjC,qBAAqB,EAAE,MAAM,GAAG,IAAI,GACnC,MAAM,CAsCR;AAMD,wBAAgB,QAAQ,IAAI,UAAU,CAGrC;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAcnD;AAED,sDAAsD;AACtD,wBAAgB,UAAU,IAAI,IAAI,CAIjC"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { appendObservation, readTodayObservations } from "./persistence.js";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Globals
|
|
4
|
+
// ============================================================
|
|
5
|
+
let daily = createEmptyDay();
|
|
6
|
+
const sessions = new Map();
|
|
7
|
+
// Track the last processed message ID to avoid double-counting.
|
|
8
|
+
// message.updated fires multiple times for the same message as parts stream in.
|
|
9
|
+
const processedMessages = new Set();
|
|
10
|
+
// ============================================================
|
|
11
|
+
// Helpers
|
|
12
|
+
// ============================================================
|
|
13
|
+
let configuredTimezone = "UTC";
|
|
14
|
+
export function setTimezone(tz) {
|
|
15
|
+
configuredTimezone = tz;
|
|
16
|
+
}
|
|
17
|
+
function todayString() {
|
|
18
|
+
try {
|
|
19
|
+
// 'sv' locale gives YYYY-MM-DD format on full-ICU Node.js builds
|
|
20
|
+
const result = new Date().toLocaleDateString("sv", { timeZone: configuredTimezone });
|
|
21
|
+
// Validate output is actually YYYY-MM-DD (silent fallback on minimal-ICU builds)
|
|
22
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(result))
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Timezone not supported or locale unavailable
|
|
27
|
+
}
|
|
28
|
+
return new Date().toISOString().split("T")[0];
|
|
29
|
+
}
|
|
30
|
+
function createEmptyDay() {
|
|
31
|
+
return {
|
|
32
|
+
date: todayString(),
|
|
33
|
+
totalTokens: 0,
|
|
34
|
+
totalRequests: 0,
|
|
35
|
+
totalCost: 0,
|
|
36
|
+
byModel: {},
|
|
37
|
+
requestTimestamps: [],
|
|
38
|
+
peakRPM: 0,
|
|
39
|
+
limitHits: [],
|
|
40
|
+
blockedModels: [],
|
|
41
|
+
notifiedThresholds: new Set(),
|
|
42
|
+
lastLimitHitTs: null,
|
|
43
|
+
inLimitState: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function ensureModel(model) {
|
|
47
|
+
if (!daily.byModel[model]) {
|
|
48
|
+
daily.byModel[model] = { tokens: 0, requests: 0 };
|
|
49
|
+
}
|
|
50
|
+
return daily.byModel[model];
|
|
51
|
+
}
|
|
52
|
+
function pruneRpmWindow() {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
daily.requestTimestamps = daily.requestTimestamps.filter((ts) => now - ts < 60_000);
|
|
55
|
+
}
|
|
56
|
+
export function getCurrentRPM() {
|
|
57
|
+
pruneRpmWindow();
|
|
58
|
+
return daily.requestTimestamps.length;
|
|
59
|
+
}
|
|
60
|
+
// ============================================================
|
|
61
|
+
// Day rollover
|
|
62
|
+
// ============================================================
|
|
63
|
+
function checkDayRollover() {
|
|
64
|
+
const today = todayString();
|
|
65
|
+
if (daily.date !== today) {
|
|
66
|
+
// Write day_end event for previous day
|
|
67
|
+
const dayEnd = {
|
|
68
|
+
ts: daily.date + "T23:59:59",
|
|
69
|
+
type: "day_end",
|
|
70
|
+
day_cumulative_tokens: daily.totalTokens,
|
|
71
|
+
day_cumulative_requests: daily.totalRequests,
|
|
72
|
+
limit_hit: daily.limitHits.length > 0,
|
|
73
|
+
limit_hit_at_tokens: daily.limitHits.length > 0
|
|
74
|
+
? daily.limitHits[0].tokensAtHit
|
|
75
|
+
: null,
|
|
76
|
+
limit_hit_at_requests: daily.limitHits.length > 0
|
|
77
|
+
? daily.limitHits[0].requestsAtHit
|
|
78
|
+
: null,
|
|
79
|
+
models_used: { ...daily.byModel },
|
|
80
|
+
};
|
|
81
|
+
appendObservation(dayEnd);
|
|
82
|
+
// Reset for new day
|
|
83
|
+
daily = createEmptyDay();
|
|
84
|
+
processedMessages.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// ============================================================
|
|
88
|
+
// Startup recovery
|
|
89
|
+
// ============================================================
|
|
90
|
+
export function recoverFromJSONL() {
|
|
91
|
+
const today = todayString();
|
|
92
|
+
const events = readTodayObservations(today);
|
|
93
|
+
daily = createEmptyDay();
|
|
94
|
+
processedMessages.clear();
|
|
95
|
+
for (const event of events) {
|
|
96
|
+
if (event.type === "usage") {
|
|
97
|
+
const tokens = event.input_tokens +
|
|
98
|
+
event.output_tokens +
|
|
99
|
+
event.reasoning_tokens +
|
|
100
|
+
event.cache_read +
|
|
101
|
+
event.cache_write;
|
|
102
|
+
daily.totalTokens += tokens;
|
|
103
|
+
daily.totalRequests += 1;
|
|
104
|
+
daily.totalCost += event.cost;
|
|
105
|
+
const m = ensureModel(event.model);
|
|
106
|
+
m.tokens += tokens;
|
|
107
|
+
m.requests += 1;
|
|
108
|
+
}
|
|
109
|
+
if (event.type === "limit_hit") {
|
|
110
|
+
daily.limitHits.push({
|
|
111
|
+
ts: event.ts,
|
|
112
|
+
model: event.model,
|
|
113
|
+
class: event.class,
|
|
114
|
+
tokensAtHit: event.day_cumulative_tokens,
|
|
115
|
+
requestsAtHit: event.day_cumulative_requests,
|
|
116
|
+
rpmAtHit: event.requests_last_minute,
|
|
117
|
+
});
|
|
118
|
+
daily.inLimitState = true;
|
|
119
|
+
daily.lastLimitHitTs = new Date(event.ts).getTime();
|
|
120
|
+
}
|
|
121
|
+
if (event.type === "model_blocked") {
|
|
122
|
+
daily.blockedModels.push({
|
|
123
|
+
ts: event.ts,
|
|
124
|
+
model: event.model,
|
|
125
|
+
errorMessage: event.error_message,
|
|
126
|
+
statusCode: event.status_code,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (event.type === "recovery") {
|
|
130
|
+
daily.inLimitState = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function processAssistantMessage(msg) {
|
|
135
|
+
checkDayRollover();
|
|
136
|
+
// Only process finished messages, and only once
|
|
137
|
+
if (!msg.finished)
|
|
138
|
+
return;
|
|
139
|
+
if (processedMessages.has(msg.messageId))
|
|
140
|
+
return;
|
|
141
|
+
processedMessages.add(msg.messageId);
|
|
142
|
+
// Skip zero-token messages (e.g. empty responses)
|
|
143
|
+
const totalTokens = msg.tokens.input + msg.tokens.output + msg.tokens.reasoning +
|
|
144
|
+
msg.tokens.cache.read + msg.tokens.cache.write;
|
|
145
|
+
if (totalTokens === 0)
|
|
146
|
+
return;
|
|
147
|
+
// Update daily state
|
|
148
|
+
daily.totalTokens += totalTokens;
|
|
149
|
+
daily.totalRequests += 1;
|
|
150
|
+
daily.totalCost += msg.cost;
|
|
151
|
+
const m = ensureModel(msg.modelId);
|
|
152
|
+
m.tokens += totalTokens;
|
|
153
|
+
m.requests += 1;
|
|
154
|
+
// RPM tracking
|
|
155
|
+
daily.requestTimestamps.push(Date.now());
|
|
156
|
+
pruneRpmWindow();
|
|
157
|
+
daily.peakRPM = Math.max(daily.peakRPM, daily.requestTimestamps.length);
|
|
158
|
+
// If we were in a limit state and now got a successful response, that's a recovery
|
|
159
|
+
if (daily.inLimitState) {
|
|
160
|
+
const minutesSinceLimit = daily.lastLimitHitTs
|
|
161
|
+
? (Date.now() - daily.lastLimitHitTs) / 60_000
|
|
162
|
+
: 0;
|
|
163
|
+
appendObservation({
|
|
164
|
+
ts: new Date().toISOString(),
|
|
165
|
+
type: "recovery",
|
|
166
|
+
model: msg.modelId,
|
|
167
|
+
day_cumulative_tokens: daily.totalTokens,
|
|
168
|
+
minutes_since_limit: Math.round(minutesSinceLimit),
|
|
169
|
+
});
|
|
170
|
+
daily.inLimitState = false;
|
|
171
|
+
}
|
|
172
|
+
// Write observation
|
|
173
|
+
const event = {
|
|
174
|
+
ts: new Date().toISOString(),
|
|
175
|
+
type: "usage",
|
|
176
|
+
session: msg.sessionId,
|
|
177
|
+
model: msg.modelId,
|
|
178
|
+
provider: msg.providerId,
|
|
179
|
+
input_tokens: msg.tokens.input,
|
|
180
|
+
output_tokens: msg.tokens.output,
|
|
181
|
+
reasoning_tokens: msg.tokens.reasoning,
|
|
182
|
+
cache_read: msg.tokens.cache.read,
|
|
183
|
+
cache_write: msg.tokens.cache.write,
|
|
184
|
+
cost: msg.cost,
|
|
185
|
+
day_cumulative_tokens: daily.totalTokens,
|
|
186
|
+
day_cumulative_requests: daily.totalRequests,
|
|
187
|
+
requests_last_minute: getCurrentRPM(),
|
|
188
|
+
request_ok: true,
|
|
189
|
+
};
|
|
190
|
+
appendObservation(event);
|
|
191
|
+
}
|
|
192
|
+
export function processErrorEvent(err, lastRequestedModel, lastRequestedProvider) {
|
|
193
|
+
checkDayRollover();
|
|
194
|
+
const rpm = getCurrentRPM();
|
|
195
|
+
const ts = new Date().toISOString();
|
|
196
|
+
appendObservation({
|
|
197
|
+
ts,
|
|
198
|
+
type: "limit_hit",
|
|
199
|
+
session: err.sessionId ?? "unknown",
|
|
200
|
+
model: lastRequestedModel ?? "unknown",
|
|
201
|
+
provider: lastRequestedProvider ?? "unknown",
|
|
202
|
+
day_cumulative_tokens: daily.totalTokens,
|
|
203
|
+
day_cumulative_requests: daily.totalRequests,
|
|
204
|
+
requests_last_minute: rpm,
|
|
205
|
+
error_name: err.errorName,
|
|
206
|
+
error_message: err.errorMessage,
|
|
207
|
+
error_raw: err.errorRaw,
|
|
208
|
+
status_code: err.statusCode,
|
|
209
|
+
is_retryable: err.isRetryable,
|
|
210
|
+
response_headers: err.responseHeaders,
|
|
211
|
+
response_body: err.responseBody,
|
|
212
|
+
class: "unknown",
|
|
213
|
+
});
|
|
214
|
+
daily.limitHits.push({
|
|
215
|
+
ts,
|
|
216
|
+
model: lastRequestedModel ?? "unknown",
|
|
217
|
+
class: "unknown",
|
|
218
|
+
tokensAtHit: daily.totalTokens,
|
|
219
|
+
requestsAtHit: daily.totalRequests,
|
|
220
|
+
rpmAtHit: rpm,
|
|
221
|
+
});
|
|
222
|
+
daily.inLimitState = true;
|
|
223
|
+
daily.lastLimitHitTs = Date.now();
|
|
224
|
+
return ts;
|
|
225
|
+
}
|
|
226
|
+
// ============================================================
|
|
227
|
+
// Getters for other modules
|
|
228
|
+
// ============================================================
|
|
229
|
+
export function getDaily() {
|
|
230
|
+
checkDayRollover();
|
|
231
|
+
return daily;
|
|
232
|
+
}
|
|
233
|
+
export function getSession(id) {
|
|
234
|
+
let s = sessions.get(id);
|
|
235
|
+
if (!s) {
|
|
236
|
+
s = {
|
|
237
|
+
id,
|
|
238
|
+
tokensThisSession: 0,
|
|
239
|
+
requestsThisSession: 0,
|
|
240
|
+
currentModel: null,
|
|
241
|
+
currentProvider: null,
|
|
242
|
+
notifiedThresholds: new Set(),
|
|
243
|
+
};
|
|
244
|
+
sessions.set(id, s);
|
|
245
|
+
}
|
|
246
|
+
return s;
|
|
247
|
+
}
|
|
248
|
+
/** Reset all module-level state. For testing only. */
|
|
249
|
+
export function resetState() {
|
|
250
|
+
daily = createEmptyDay();
|
|
251
|
+
sessions.clear();
|
|
252
|
+
processedMessages.clear();
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=aggregator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"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"}
|