-
-
Notifications
You must be signed in to change notification settings - Fork 308
fix: deduplication fallback to message ID when request ID is missing #775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Some API proxies may not include requestId in the usage data. Previously, entries without requestId were not deduplicated at all, leading to duplicate counting. This change makes the deduplication logic fall back to using only messageId when requestId is not available, ensuring proper deduplication for all API proxy types.
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/ccusage/src/data-loader.ts (1)
4263-4277: Test updated correctly, but consider adding integration test.The unit test properly validates the new fallback behavior. However, I don't see an integration test that verifies deduplication actually works when entries have the same
messageIdbut norequestId.Suggested integration test to validate the deduplication fallback
Consider adding a test like this in the
deduplication functionalitydescribe block:it('should deduplicate entries with same message ID when request ID is missing', async () => { await using fixture = await createFixture({ projects: { project1: { session1: { 'file1.jsonl': JSON.stringify({ timestamp: '2025-01-10T10:00:00Z', message: { id: 'msg_123', usage: { input_tokens: 100, output_tokens: 50, }, }, // No requestId costUSD: 0.001, }), }, session2: { 'file2.jsonl': JSON.stringify({ timestamp: '2025-01-15T10:00:00Z', message: { id: 'msg_123', // Same messageId usage: { input_tokens: 100, output_tokens: 50, }, }, // No requestId costUSD: 0.001, }), }, }, }, }); const data = await loadDailyUsageData({ claudePath: fixture.path, mode: 'display', }); // Should only have one entry due to messageId-based deduplication expect(data).toHaveLength(1); expect(data[0]?.date).toBe('2025-01-10'); expect(data[0]?.inputTokens).toBe(100); expect(data[0]?.totalCost).toBe(0.001); });This would validate that the fallback deduplication mechanism works end-to-end for API proxies that omit
requestId.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/ccusage/src/data-loader.ts
🧰 Additional context used
📓 Path-based instructions (7)
apps/ccusage/src/**/*.ts
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/src/**/*.ts: Write tests in-source usingif (import.meta.vitest != null)blocks instead of separate test files
Use Vitest globals (describe,it,expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Usefs-fixturewithcreateFixture()to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities fromsrc/logger.tsinstead
Files:
apps/ccusage/src/data-loader.ts
apps/ccusage/**/*.ts
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/**/*.ts: NEVER useawait import()dynamic imports anywhere (especially in tests)
Prefer@praha/byethrowResult type for error handling instead of try-catch
Use.tsextensions for local imports (e.g.,import { foo } from './utils.ts')
Files:
apps/ccusage/src/data-loader.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use ESLint for linting and formatting with tab indentation and double quotes
No console.log allowed except where explicitly disabled with eslint-disable; use logger.ts instead
Use file paths with Node.js path utilities for cross-platform compatibility
Use variables starting with lowercase (camelCase) for variable names
Can use UPPER_SNAKE_CASE for constants
Files:
apps/ccusage/src/data-loader.ts
**/*.ts{,x}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict mode and bundler module resolution
Files:
apps/ccusage/src/data-loader.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Use.tsextensions for local file imports (e.g.,import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
UseResult.try()for wrapping operations that may throw (JSON parsing, etc.)
UseResult.isFailure()for checking errors (more readable than!Result.isSuccess())
Use early return pattern (if (Result.isFailure(result)) continue;) instead of ternary operators when checking Results
Keep traditional try-catch only for file I/O with complex error handling or legacy code that's hard to refactor
Always useResult.isFailure()andResult.isSuccess()type guards for better code clarity
Use uppercase (PascalCase) for type names
Only export constants, functions, and types that are actually used by other modules - internal constants used only within the same file should NOT be exported
In-source testing pattern: write tests directly in source files usingif (import.meta.vitest != null)blocks
CRITICAL: DO NOT useawait import()dynamic imports anywhere in the codebase - this causes tree-shaking issues
CRITICAL: Never use dynamic imports withawait import()in vitest test blocks - this is particularly problematic for test execution
Vitest globals (describe,it,expect) are enabled and available without imports since globals are configured
Create mock data usingfs-fixturewithcreateFixture()for Claude data directory simulation in tests
All test files must use current Claude 4 models (claude-sonnet-4-20250514, claude-opus-4-20250514), not outdated Claude 3 models
Model names in tests must exactly match LiteLLM's pricing database entries
Files:
apps/ccusage/src/data-loader.ts
**/data-loader.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Silently skip malformed JSONL lines during parsing in data loading operations
Files:
apps/ccusage/src/data-loader.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Claude model naming convention:
claude-{model-type}-{generation}-{date}(e.g.,claude-sonnet-4-20250514, NOTclaude-4-sonnet-20250514)
Files:
apps/ccusage/src/data-loader.ts
🔇 Additional comments (1)
apps/ccusage/src/data-loader.ts (1)
521-539: LGTM! Deduplication fallback implemented correctly.The logic properly handles the three cases:
- No
messageId: returnsnull(cannot deduplicate)- Both
messageIdandrequestId: returns combined hash for precise deduplicationmessageIdonly: returnsmessageIdalone (new fallback for API proxies withoutrequestId)This fix enables proper deduplication for all API proxy types, preventing duplicate counting when
requestIdis omitted.
Summary
Some API proxies may not include
requestIdin the usage data. Previously, entries withoutrequestIdwere not deduplicated at all (returnednullfromcreateUniqueHash), leading to duplicate counting.This change makes the deduplication logic fall back to using only
messageIdwhenrequestIdis not available, ensuring proper deduplication for all API proxy types.Changes
createUniqueHashto returnmessageIdwhenrequestIdis missing (instead ofnull)Test Plan
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.