feat(analyzers): add Grok session usage tracking - #229
Conversation
Parse Grok session history and turn usage from ~/.grok/sessions, including token, cache, reasoning, and tool statistics. Add official xAI API pricing with context-aware tiers so Grok costs remain comparable with other analyzers. Signed-off-by: jimyag <git@jimyag.com>
|
Warning Review limit reached
Next review available in: 37 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis change adds Grok session discovery and parsing, context-tiered pricing, application support, analyzer registration, usage aggregation, metadata extraction, and tests. ChangesGrok support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant GrokAnalyzer
participant GrokSessions
participant ConversationMessages
participant GrokPricing
GrokAnalyzer->>GrokSessions: Discover and read session files
GrokSessions-->>GrokAnalyzer: Return chat and usage records
GrokAnalyzer->>ConversationMessages: Build enriched messages
GrokAnalyzer->>GrokPricing: Calculate context-based costs
GrokPricing-->>GrokAnalyzer: Return estimated costs
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/analyzers/grok.rs (3)
299-318: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
session_metadatanever returns a session name.Both return paths produce
Nonefor the second tuple element. TheOption<String>is therefore dead, and thesession_name.is_none()check at line 365 is always true. Either read a title field fromsummary.jsonintoGrokSessionSummary, or change the return type toDateTime<Utc>and derive the name only from the first user message.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/analyzers/grok.rs` around lines 299 - 318, The session_metadata function never produces a session name, making its Option<String> result and downstream session_name.is_none() check ineffective. Read the title/name field from summary.json into GrokSessionSummary and return it from both metadata paths, preserving None only when no summary title exists.
346-349: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider reporting skipped malformed lines.
A parse failure discards the line without any signal. When
updates.jsonlandchat_history.jsonldisagree in length, the resulting usage misattribution is then hard to diagnose.crate::utils::warn_oncededuplicates messages, so a single warning per file stays quiet in normal operation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/analyzers/grok.rs` around lines 346 - 349, Update the JSON parsing match in the Grok analyzer to call crate::utils::warn_once when simd_json::from_slice fails, identifying the affected input file and malformed line, then continue skipping the record. Use warn_once so repeated parse failures per file remain deduplicated while preserving the existing successful-record path.
429-458: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAlign the WalkDir depth with the declared glob pattern.
get_data_glob_patternsdeclares exactly two directory levels below the sessions root.discover_data_sourcesandis_availablewalk every depth. The two surfaces therefore accept different file sets. Bound the walk to keep them consistent and to avoid traversing unrelated subtrees.♻️ Proposed fix to bound the traversal depth
- .flat_map(|dir| WalkDir::new(dir).into_iter()) + .flat_map(|dir| WalkDir::new(dir).min_depth(3).max_depth(3).into_iter())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/analyzers/grok.rs` around lines 429 - 458, Limit the WalkDir traversal in both discover_data_sources and is_available to the same depth represented by get_data_glob_patterns: exactly two directory levels below the sessions root, while still matching chat_history.jsonl files at that location. Apply the depth bound before filtering entries so unrelated deeper subtrees are not traversed.src/models.rs (1)
1537-1599: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a source URL to the xAI model pricing block.
The
grok-4.5andgrok-build-0.1rates and 200k context thresholds match xAI pricing documentation, but unlike neighboring vendor entries, this block has no source URL comment. Add one to keep future rate changes auditable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/models.rs` around lines 1537 - 1599, Add a source URL comment to the xAI model pricing block containing the grok-4.5 and grok-build-0.1 registrations. Place it alongside the existing “xAI Models” heading, using the official xAI pricing documentation URL and leaving both add_model! definitions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/analyzers/grok.rs`:
- Around line 251-254: Update src/analyzers/grok.rs lines 251-254 in the
apply_turn_usage grouping logic to align usage records explicitly rather than
relying on positional zip; skip empty groups without consuming usage, or
otherwise ensure turns lacking turn_completed do not shift usage onto later
turns. Also update the assistant-record handling at src/analyzers/grok.rs lines
392-396 to create an implicit leading assistant group when
assistant_groups.last_mut() is None, so leading assistant messages receive their
token and cost usage.
- Around line 265-282: Update the estimated-cost model lookup to search the
entire group for the first message whose model is present, rather than only
using group.first(). Keep the existing token and cost calculation flow unchanged
once that model is found, while preserving the no-model None behavior.
In `@src/models.rs`:
- Around line 2664-2671: Update the Tiered cache-cost handling in
calculate_context_cost and cache_cost_for_caching so both paths pass the same
cache creation and read token inputs to calculate_tiered_cache_cost. If Tiered
pricing intentionally ignores cache creation tokens because it has no write
rate, document that behavior and add an audit test using different
cache_creation_tokens values; otherwise propagate the creation-token value
through both paths.
---
Nitpick comments:
In `@src/analyzers/grok.rs`:
- Around line 299-318: The session_metadata function never produces a session
name, making its Option<String> result and downstream session_name.is_none()
check ineffective. Read the title/name field from summary.json into
GrokSessionSummary and return it from both metadata paths, preserving None only
when no summary title exists.
- Around line 346-349: Update the JSON parsing match in the Grok analyzer to
call crate::utils::warn_once when simd_json::from_slice fails, identifying the
affected input file and malformed line, then continue skipping the record. Use
warn_once so repeated parse failures per file remain deduplicated while
preserving the existing successful-record path.
- Around line 429-458: Limit the WalkDir traversal in both discover_data_sources
and is_available to the same depth represented by get_data_glob_patterns:
exactly two directory levels below the sessions root, while still matching
chat_history.jsonl files at that location. Apply the depth bound before
filtering entries so unrelated deeper subtrees are not traversed.
In `@src/models.rs`:
- Around line 1537-1599: Add a source URL comment to the xAI model pricing block
containing the grok-4.5 and grok-build-0.1 registrations. Place it alongside the
existing “xAI Models” heading, using the official xAI pricing documentation URL
and leaving both add_model! definitions unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fcb63fea-1c86-453c-ba7c-410bcb27b4a8
📒 Files selected for processing (6)
README.mdsrc/analyzers/grok.rssrc/analyzers/mod.rssrc/main.rssrc/models.rssrc/types.rs
Align turn usage with non-empty assistant groups, recover costs from later model IDs, and preserve leading assistant usage. Improve session metadata, malformed-record diagnostics, discovery bounds, and document tiered cache pricing semantics. Signed-off-by: jimyag <git@jimyag.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/analyzers/grok.rs (1)
170-201: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winSkip blank lines in
parse_turn_usagesbefore parsing.
parse_chat_history_fileskips blank lines withif line.trim().is_empty() { continue; }before callingsimd_json::from_slice.parse_turn_usageshas no equivalent check. A blank line inupdates.jsonlfails JSON parsing and triggers a "Failed to parse Grok updates file" warning even though the line is not malformed.Add the same blank-line skip used in the chat-history parser.
🩹 Proposed fix to skip blank lines
.filter_map(|(line_index, line)| { + if line.trim().is_empty() { + return None; + } let mut bytes = line.as_bytes().to_vec(); let record = match simd_json::from_slice::<GrokUpdateRecord>(&mut bytes) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/analyzers/grok.rs` around lines 170 - 201, Update parse_turn_usages to skip lines whose trimmed content is empty before converting the line to bytes and calling simd_json::from_slice, matching the existing behavior in parse_chat_history_file; continue parsing non-blank lines and preserve current warning behavior for malformed JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/analyzers/grok.rs`:
- Around line 86-91: Update GrokSessionSummary to deserialize generated_title
and use it as the primary session display name, falling back to session_summary
when generated_title is absent; preserve the existing optional/default handling.
---
Outside diff comments:
In `@src/analyzers/grok.rs`:
- Around line 170-201: Update parse_turn_usages to skip lines whose trimmed
content is empty before converting the line to bytes and calling
simd_json::from_slice, matching the existing behavior in
parse_chat_history_file; continue parsing non-blank lines and preserve current
warning behavior for malformed JSON.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 06e5401f-c2bc-4444-b35d-cb87be2e005b
📒 Files selected for processing (2)
src/analyzers/grok.rssrc/models.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/models.rs
Ignore blank update records before JSON parsing, and prefer generated session titles with session summaries as the fallback. Signed-off-by: jimyag <git@jimyag.com>
mike1858
left a comment
There was a problem hiding this comment.
Thank you! Welcome addition.

Summary
Add local Grok session analysis so Splitrail can report usage from
~/.grok/sessions.Key changes
chat_history.jsonland authoritativeupdates.jsonlturn usage.grok-4.5andgrok-build-0.1, including the 200k long-context tier and compatibility aliases.Verification
cargo fmt --all --quietcargo build --quietcargo clippy --quiet -- -D warningscargo doc --quietgit diff --checkcargo run --quiet -- statsoutput for the Grok session.cargo testwas not run per the repository instruction not to run task tests proactively.Notes
Pricing follows the official xAI API pricing documentation:
https://docs.x.ai/developers/pricing
Summary by CodeRabbit