Skip to content

fix(zai): distinguish session/weekly/monthly TOKENS_LIMIT entries by unit - #181

Merged
hanrw merged 1 commit into
tddworks:mainfrom
Mitsi-ag:fix/zai-distinguish-token-limits
May 7, 2026
Merged

fix(zai): distinguish session/weekly/monthly TOKENS_LIMIT entries by unit#181
hanrw merged 1 commit into
tddworks:mainfrom
Mitsi-ag:fix/zai-distinguish-token-limits

Conversation

@Mitsi-ag

@Mitsi-ag Mitsi-ag commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Z.ai's GLM Coding Plan API returns multiple TOKENS_LIMIT entries that share the same type string and are distinguished only by an integer unit field. The previous parser mapped every TOKENS_LIMIT.session, so the second entry — the weekly token quota — silently collapsed with the 5-hour session quota.

The weekly cap is arguably the most important number for users on the GLM Coding Plan; the bug made ClaudeBar's Z.ai tab useless for tracking it.

Repro

Live API response from GET https://api.z.ai/api/monitor/usage/quota/limit:

Summary by CodeRabbit

  • New Features

    • Enhanced quota limit detection to support multiple quota types and units, enabling more accurate usage information tracking.
  • Tests

    • Added comprehensive test coverage for quota parsing, including backward compatibility and edge case scenarios.

…`unit`

Z.ai's GLM Coding Plan API returns multiple TOKENS_LIMIT entries that
share the same `type` string and are distinguished only by an integer
`unit` field. The previous parser mapped every TOKENS_LIMIT to
`.session`, so the second entry — the weekly token quota — silently
overwrote (or was overwritten by) the 5-hour session quota.

The weekly cap is the most important number for users on the GLM Coding
Plan; this bug made ClaudeBar useless for tracking it.
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR enhances ZaiUsageProbe's quota parsing with unit-awareness by adding an optional unit field to QuotaLimit and converting the quota-type mapping from type-only logic to a tuple-based switch on (type, unit). This enables discrimination of TOKENS_LIMIT quotas by unit values while preserving unknown unit configurations and maintaining backward compatibility.

Changes

Unit-Aware Quota Parsing

Layer / File(s) Summary
Data Shape
Sources/Infrastructure/Zai/ZaiUsageProbe.swift
QuotaLimit struct gains optional unit: Int? field to capture unit values from parsed quota data.
Core Parsing Logic
Sources/Infrastructure/Zai/ZaiUsageProbe.swift
Quota-type mapping expanded from type-only switch to tuple-based switch on (limit.type, limit.unit), adding cases for TIME_LIMIT and TOKENS_LIMIT units (3, 6, 7) with specific mappings to session, weekly, and model-specific labels; nil units default to session; unknown units preserved via modelSpecific.
Tests & Validation
Tests/InfrastructureTests/Zai/ZaiUsageProbeParsingTests.swift
Comprehensive unit-distinguishing tests validate TOKENS_LIMIT mappings across unit values, backward compatibility with nil units, real-world payload parsing, non-collapse of multiple quota tiers, and clamping of percentages to 0–100 range.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • tddworks/ClaudeBar#24: Both PRs extend ZaiUsageProbe's quota parsing—this PR adds unit-aware TOKENS_LIMIT mapping while the related PR adds nextResetTime/FlexibleDate parsing to the same component.

Poem

🐰 Quotas now speak in units bright,
Sessions, weeks, and tokens right,
A tuple switch brings order true,
Unknown units live on through,
Parsing flows with grace and care!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding unit-aware quota parsing to distinguish different TOKENS_LIMIT entries (session/weekly/monthly) by their unit field, directly addressing the bug described in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Sources/Infrastructure/Zai/ZaiUsageProbe.swift (1)

311-329: ⚡ Quick win

Add warning logs to detect API drift in Zai quota parsing.

The switch statement handles known quota types but silently swallows API changes in two paths:

  • case ("TOKENS_LIMIT", let unit?): Unknown units map to .modelSpecific("Tokens (unit \(unit))") without any log. If z.ai introduces a new quota tier, the team only learns through a strange UI label.
  • default: Unknown limit types silently continue with no trace.

Per coding guidelines, .warning() writes to both OSLog and ~/Library/Logs/ClaudeBar/ClaudeBar.log, so adding a single log line in each path surfaces API changes immediately without altering behavior.

Also consider changing case ("TIME_LIMIT", _) to case ("TIME_LIMIT", 5) to match the documented mapping (unit=5 → MCP); unexpected TIME_LIMIT units would then log via the observability path instead of silently assuming "MCP".

♻️ Proposed observability hooks
             switch (limit.type, limit.unit) {
-            case ("TIME_LIMIT", _):
+            case ("TIME_LIMIT", 5):
                 quotaType = .timeLimit("MCP")
             case ("TOKENS_LIMIT", 3):
                 quotaType = .session
             case ("TOKENS_LIMIT", 6):
                 quotaType = .weekly
             case ("TOKENS_LIMIT", 7):
                 quotaType = .modelSpecific("Monthly")
             case ("TOKENS_LIMIT", nil):
                 // Backward-compat: legacy responses with no `unit` field default to session.
                 quotaType = .session
             case ("TOKENS_LIMIT", let unit?):
                 // Unknown unit — preserve via modelSpecific so it isn't dropped/collapsed.
+                AppLog.probes.warning("Zai: Unknown TOKENS_LIMIT unit=\(unit) — preserving as modelSpecific. API may have changed.")
                 quotaType = .modelSpecific("Tokens (unit \(unit))")
             default:
-                // Skip unknown limit types
+                // Skip unknown limit types but log so we can spot API drift.
+                AppLog.probes.warning("Zai: Unknown limit type='\(limit.type)' unit=\(limit.unit.map(String.init) ?? "nil") — skipping.")
                 continue
             }

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d38c186e-a250-456d-a093-05b559eb12fe

📥 Commits

Reviewing files that changed from the base of the PR and between 12c1943 and a847ad7.

📒 Files selected for processing (2)
  • Sources/Infrastructure/Zai/ZaiUsageProbe.swift
  • Tests/InfrastructureTests/Zai/ZaiUsageProbeParsingTests.swift

@codecov

codecov Bot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 80.59%. Comparing base (ea81ed8) to head (a847ad7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
Sources/Infrastructure/Zai/ZaiUsageProbe.swift 95.45% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #181      +/-   ##
==========================================
+ Coverage   80.55%   80.59%   +0.04%     
==========================================
  Files         109      109              
  Lines        8085     8103      +18     
==========================================
+ Hits         6513     6531      +18     
  Misses       1572     1572              
Files with missing lines Coverage Δ
Sources/Infrastructure/Zai/ZaiUsageProbe.swift 86.77% <95.45%> (+1.06%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hanrw
hanrw merged commit e270318 into tddworks:main May 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants