Skip to content

Commit 88e345c

Browse files
authored
Merge pull request #48 from onllm-dev/fix/openrouter-frontend
fix: OpenRouter frontend + MiniMax zero-quota filter
2 parents 80308f1 + aa485f3 commit 88e345c

7 files changed

Lines changed: 232 additions & 18 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,13 @@ Set `COPILOT_TOKEN` in your `.env` with a GitHub Personal Access Token (classic)
241241

242242
Set `MINIMAX_API_KEY` in your `.env` with your MiniMax Coding Plan API key. Get your key from the [MiniMax Console](https://platform.minimax.io). onWatch tracks the shared quota pool across all MiniMax models (M2, M2.1, M2.5) with 5-hour rolling window reset cycles. Full walkthrough: [MiniMax Setup Guide](docs/MINIMAX_SETUP.md).
243243

244+
### How do I track my OpenRouter usage?
245+
246+
Set `OPENROUTER_API_KEY` in your `.env` with your OpenRouter API key. Get one from [openrouter.ai/keys](https://openrouter.ai/keys). onWatch polls the OpenRouter auth key endpoint to track total credits usage, daily/weekly/monthly spending, credit limits, and remaining balance. Works with both free and paid accounts.
247+
244248
### Does onWatch work with Cline, Roo Code, Kilo Code, or Claude Code?
245249

246-
Yes. onWatch monitors the API provider (Synthetic, Z.ai, Anthropic, Codex, GitHub Copilot, MiniMax, Gemini CLI, or Antigravity), not the coding tool. Any tool that uses a Synthetic, Z.ai, Anthropic, Codex, Copilot, MiniMax, Gemini CLI, or Antigravity API key -- including Cline, Roo Code, Kilo Code, Claude Code, Codex CLI, Cursor, GitHub Copilot, MiniMax Coding Plan, Gemini CLI, Antigravity, and others -- will have its usage tracked automatically.
250+
Yes. onWatch monitors the API provider (Synthetic, Z.ai, Anthropic, Codex, GitHub Copilot, MiniMax, OpenRouter, Gemini CLI, or Antigravity), not the coding tool. Any tool that uses a Synthetic, Z.ai, Anthropic, Codex, Copilot, MiniMax, OpenRouter, Gemini CLI, or Antigravity API key -- including Cline, Roo Code, Kilo Code, Claude Code, Codex CLI, Cursor, GitHub Copilot, MiniMax Coding Plan, OpenRouter, Gemini CLI, Antigravity, and others -- will have its usage tracked automatically.
247251

248252
### Does onWatch send any data to external servers?
249253

internal/tracker/minimax_tracker.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ func (t *MiniMaxTracker) SetOnReset(fn func(modelName string)) {
5959
// Process processes one snapshot and updates/reset-cycles per model.
6060
func (t *MiniMaxTracker) Process(snapshot *api.MiniMaxSnapshot) error {
6161
for _, model := range snapshot.Models {
62+
// Skip models with no quota allocation
63+
if model.Total == 0 && model.Used == 0 {
64+
continue
65+
}
6266
if err := t.processModel(model, snapshot.CapturedAt); err != nil {
6367
return fmt.Errorf("minimax tracker: %s: %w", model.ModelName, err)
6468
}

internal/web/handlers.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,16 +2005,9 @@ func (h *Handler) historyOpenRouter(w http.ResponseWriter, r *http.Request) {
20052005
"capturedAt": snapshot.CapturedAt.Format(time.RFC3339),
20062006
"usage": snapshot.Usage,
20072007
"usageDaily": snapshot.UsageDaily,
2008-
"isFreeTier": snapshot.IsFreeTier,
20092008
}
2010-
if snapshot.Limit != nil {
2011-
entry["limit"] = *snapshot.Limit
2012-
if *snapshot.Limit > 0 {
2013-
entry["percent"] = (snapshot.Usage / *snapshot.Limit) * 100
2014-
}
2015-
}
2016-
if snapshot.LimitRemaining != nil {
2017-
entry["remaining"] = *snapshot.LimitRemaining
2009+
if snapshot.Limit != nil && *snapshot.Limit > 0 {
2010+
entry["percent"] = (snapshot.Usage / *snapshot.Limit) * 100
20182011
}
20192012
histResp = append(histResp, entry)
20202013
}
@@ -7088,6 +7081,10 @@ func (h *Handler) buildMiniMaxCurrent() map[string]interface{} {
70887081

70897082
quotas := make([]map[string]interface{}, 0, len(latest.Models))
70907083
for _, m := range latest.Models {
7084+
// Skip models with no quota allocation (total=0 and used=0)
7085+
if m.Total == 0 && m.Used == 0 {
7086+
continue
7087+
}
70917088
quotas = append(quotas, buildQuota(m, m.ModelName))
70927089
}
70937090

@@ -7468,6 +7465,9 @@ func (h *Handler) historyMiniMax(w http.ResponseWriter, r *http.Request) {
74687465
}
74697466
} else {
74707467
for _, model := range snap.Models {
7468+
if model.Total == 0 && model.Used == 0 {
7469+
continue
7470+
}
74717471
entry[model.ModelName] = model.UsedPercent
74727472
}
74737473
}
@@ -8893,12 +8893,17 @@ func (h *Handler) loggingHistoryMiniMax(w http.ResponseWriter, r *http.Request)
88938893
quotaNames := []string{}
88948894
for _, snap := range snapshots {
88958895
for _, model := range snap.Models {
8896+
if model.Total == 0 && model.Used == 0 {
8897+
continue
8898+
}
88968899
quotaNames = append(quotaNames, model.ModelName)
88978900
}
88988901
break
88998902
}
89008903
if len(quotaNames) == 0 {
8901-
quotaNames, _ = h.store.QueryAllMiniMaxModelNames()
8904+
allNames, _ := h.store.QueryAllMiniMaxModelNames()
8905+
// Filter will happen at series level; use all names as fallback
8906+
quotaNames = allNames
89028907
}
89038908

89048909
capturedAt := make([]time.Time, 0, len(snapshots))
@@ -8909,6 +8914,9 @@ func (h *Handler) loggingHistoryMiniMax(w http.ResponseWriter, r *http.Request)
89098914
ids = append(ids, snap.ID)
89108915
row := make(map[string]loggingHistoryCrossQuota, len(snap.Models))
89118916
for _, model := range snap.Models {
8917+
if model.Total == 0 && model.Used == 0 {
8918+
continue
8919+
}
89128920
row[model.ModelName] = loggingHistoryCrossQuota{
89138921
Name: model.ModelName,
89148922
Value: float64(model.Used),

internal/web/menubar.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ func (h *Handler) buildMenubarProviders(settings *menubar.Settings, includeHidde
294294
}
295295
}
296296
}
297+
if h.config != nil && h.config.HasProvider("openrouter") && h.providerDashboardVisible("openrouter", visibility) {
298+
payload := h.buildOpenRouterCurrent()
299+
if card := normalizeProviderCard("openrouter", "OpenRouter", "", payload, normalized.WarningPercent, normalized.CriticalPercent); card != nil {
300+
providers = append(providers, *card)
301+
if captured := parseCapturedAt(payload); captured.After(latest) {
302+
latest = captured
303+
}
304+
}
305+
}
297306
if h.config != nil && h.config.HasProvider("gemini") && h.providerDashboardVisible("gemini", visibility) {
298307
payload := h.buildGeminiCurrent()
299308
if card := normalizeProviderCard("gemini", "Gemini", "", payload, normalized.WarningPercent, normalized.CriticalPercent); card != nil {
@@ -555,7 +564,7 @@ func normalizeQuotas(payload map[string]interface{}, warningPercent, criticalPer
555564
}
556565

557566
if len(rawQuotas) == 0 {
558-
for _, key := range []string{"subscription", "search", "toolCalls", "tokensLimit", "timeLimit", "sharedQuota"} {
567+
for _, key := range []string{"subscription", "search", "toolCalls", "tokensLimit", "timeLimit", "sharedQuota", "credits"} {
559568
if quotaMap, ok := payload[key].(map[string]interface{}); ok {
560569
rawQuotas = append(rawQuotas, quotaMap)
561570
}

0 commit comments

Comments
 (0)