[codex] add provider coverage parity#133
Conversation
|
Warning Review limit reached
More reviews will be available in 49 minutes and 23 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughThis PR adds support for 12 new AI agent providers (Codebuff, Droid, Kimi, Kilo, Mux, Crush, Goose, Antigravity, Zed, Kiro, Trae, Synthetic) to the TokenLeak registry. Each provider implementation parses local cache or database files, extracts token usage within a date range, and normalizes to a common record format. Provider classes are exported, wired into CLI/MCP, assigned UI colors, tested comprehensively, and documented. ChangesMulti-provider expansion
🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 8
🤖 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 `@packages/registry/src/providers/hermes.ts`:
- Line 125: rowToRecord is still dropping rows with zero tokens even when
signalPredicate now includes non-zero-cost rows, so update rowToRecord (and any
downstream filtering it uses) to preserve records that have cost > 0 regardless
of token count: change the token-count-based discard logic in rowToRecord to
check for cost (e.g., keep if row.cost && row.cost > 0) before skipping, ensure
the returned Record includes the cost field so cost-only sessions survive
query-time inclusion, and remove or adjust any other filters that assume zero
tokens means drop.
In `@packages/registry/src/providers/kilo.ts`:
- Around line 29-31: The resolveDbPath function currently returns
process.env['TOKENLEAK_KILO_DIR'] verbatim which breaks if callers pass a
directory; change resolveDbPath to detect when TOKENLEAK_KILO_DIR (or the
optional dbPath arg) is a directory and if so append the expected DB filename
(use the basename from DEFAULT_DB_PATH) and return the joined path; implement
detection using path and fs (e.g., fs.existsSync +
fs.statSync(...).isDirectory() or fallback to checking trailing path separator)
and normalize with path.resolve/path.join so DB open later receives a full file
path.
In `@packages/registry/src/providers/kiro.ts`:
- Around line 103-107: The current DB load uses JSON.parse on each row's history
inside the flatMap, which lets one malformed history throw and abort the entire
load; update the logic around the rows iteration (where rows is produced and
processed into parseKiroSessionObject) to try/catch JSON.parse and/or
objectValue per row, skip rows whose history fails to parse, and continue
parsing valid rows; reference the same symbols: keep using db.query(...).all(),
objectValue(JSON.parse(...)), stringValue(row['id']), and parseKiroSessionObject
but wrap the JSON.parse/objectValue/parseKiroSessionObject call in a per-row
try/catch, optionally logging the row id or error, and return [] for errored
rows so other rows are still ingested.
In `@packages/registry/src/providers/provider-parity.test.ts`:
- Around line 549-554: The test checks a floating-point sum with exact equality
(expect(data.totalCost).toBe(0.036)) which is brittle; change the assertion to a
tolerant matcher such as Jest's toBeCloseTo (e.g.,
expect(data.totalCost).toBeCloseTo(0.036, 3)) so the MuxProvider.load result's
totalCost is compared with a tolerance rather than strict IEEE-754 equality.
Target the assertion for data.totalCost in the provider-parity.test (the
MuxProvider.load result) and adjust the precision as appropriate.
In `@packages/registry/src/providers/synthetic.ts`:
- Around line 35-37: The hf: branch returns the whole string when there is no
slash (e.g., "hf:deepseek-r1"), so update the branch that checks if
(lower.startsWith('hf:')) to extract the last segment and then strip the "hf:"
prefix if present: compute const last = lower.split('/').at(-1) ?? ''; if
(last.startsWith('hf:')) return last.slice(3); else return last; this ensures
both slash-separated IDs and single-segment hf: IDs normalize correctly
(referencing the existing lower variable and the hf: branch in
providers/synthetic.ts).
In `@packages/renderers/src/svg/wrapped-single-page.ts`:
- Line 85: The dark-mode color for the "kimi" provider in the color palette
object is inconsistent; update the "kimi" entry in wrapped-single-page.ts so its
dark value is "`#111827`" (matching the other renderers) instead of "`#9ca3af`" to
ensure uniform rendering across wrapped-live-template and theme mappings.
In `@README.md`:
- Line 3: The intro sentence lists the provider name as "Kimi" but other
references use "Kimi CLI"; update the string in the intro (the list of providers
in README.md) to "Kimi CLI" so it matches the provider table and details
sections (search for the literal "Kimi" in that provider list and replace it
with "Kimi CLI").
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0af5362b-7065-427c-894e-1b86970a434a
📒 Files selected for processing (24)
README.mdpackages/cli/src/cli.test.tspackages/cli/src/cli.tspackages/mcp/src/server.tspackages/registry/src/index.tspackages/registry/src/providers/antigravity.tspackages/registry/src/providers/codebuff.tspackages/registry/src/providers/crush.tspackages/registry/src/providers/droid.tspackages/registry/src/providers/goose.tspackages/registry/src/providers/hermes.tspackages/registry/src/providers/index.tspackages/registry/src/providers/kilo.tspackages/registry/src/providers/kimi.tspackages/registry/src/providers/kiro.tspackages/registry/src/providers/mux.tspackages/registry/src/providers/provider-parity.test.tspackages/registry/src/providers/synthetic.tspackages/registry/src/providers/trae.tspackages/registry/src/providers/zed.tspackages/renderers/src/live/wrapped-live-template.tspackages/renderers/src/svg/wrapped-single-page.tspackages/tui/src/lib/data.tspackages/tui/src/lib/theme.ts
| sessionId: `${dbPath}:${row.id}`, | ||
| projectId: dirname(dbPath).split(/[\\/]/).at(-1), |
There was a problem hiding this comment.
Avoid leaking absolute filesystem paths in sessionId.
Line 99 includes full dbPath, which can expose local usernames and host-specific paths in exported/reporting data.
💡 Proposed fix
function rowToRecord(dbPath: string, row: CrushSession, range: DateRange): LocalUsageRecord | null {
+ const dbId = dirname(dbPath).split(/[\\/]/).slice(-2).join('/');
const timestamp = timestampToIso(row.updated_at || row.created_at);
const date = timestamp ? extractDate(timestamp) : null;
if (!timestamp || !date || !isInRange(date, range)) return null;
@@
- sessionId: `${dbPath}:${row.id}`,
+ sessionId: `${dbId}:${row.id}`,
projectId: dirname(dbPath).split(/[\\/]/).at(-1),
};
}| function resolveDbPath(dbPath?: string): string { | ||
| return dbPath ?? process.env['TOKENLEAK_KILO_DIR'] ?? DEFAULT_DB_PATH; | ||
| } |
There was a problem hiding this comment.
Handle directory-style TOKENLEAK_KILO_DIR values correctly.
Line 30 returns the override verbatim. If users provide a directory path, Line 86 passes availability, but DB open later fails and returns empty data.
💡 Proposed fix
function resolveDbPath(dbPath?: string): string {
- return dbPath ?? process.env['TOKENLEAK_KILO_DIR'] ?? DEFAULT_DB_PATH;
+ const candidate = dbPath ?? process.env['TOKENLEAK_KILO_DIR'] ?? DEFAULT_DB_PATH;
+ return candidate.endsWith('.db') ? candidate : join(candidate, 'kilo.db');
}Also applies to: 85-87
🤖 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 `@packages/registry/src/providers/kilo.ts` around lines 29 - 31, The
resolveDbPath function currently returns process.env['TOKENLEAK_KILO_DIR']
verbatim which breaks if callers pass a directory; change resolveDbPath to
detect when TOKENLEAK_KILO_DIR (or the optional dbPath arg) is a directory and
if so append the expected DB filename (use the basename from DEFAULT_DB_PATH)
and return the joined path; implement detection using path and fs (e.g.,
fs.existsSync + fs.statSync(...).isDirectory() or fallback to checking trailing
path separator) and normalize with path.resolve/path.join so DB open later
receives a full file path.
| const rows = db.query('SELECT id, history FROM conversations_v2').all() as Array<Record<string, unknown>>; | ||
| return rows.flatMap((row) => { | ||
| const history = objectValue(JSON.parse(String(row['history'] ?? '{}'))); | ||
| return history ? parseKiroSessionObject(history, stringValue(row['id']) ?? 'kiro-sqlite', range) : []; | ||
| }); |
There was a problem hiding this comment.
Malformed SQLite history in one row drops the entire Kiro DB load.
A single bad history JSON currently aborts parsing for all rows in that database. Handle parse failures per row so valid rows are still ingested.
Suggested fix
- return rows.flatMap((row) => {
- const history = objectValue(JSON.parse(String(row['history'] ?? '{}')));
- return history ? parseKiroSessionObject(history, stringValue(row['id']) ?? 'kiro-sqlite', range) : [];
- });
+ return rows.flatMap((row) => {
+ try {
+ const history = objectValue(JSON.parse(String(row['history'] ?? '{}')));
+ return history
+ ? parseKiroSessionObject(history, stringValue(row['id']) ?? 'kiro-sqlite', range)
+ : [];
+ } catch {
+ return [];
+ }
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const rows = db.query('SELECT id, history FROM conversations_v2').all() as Array<Record<string, unknown>>; | |
| return rows.flatMap((row) => { | |
| const history = objectValue(JSON.parse(String(row['history'] ?? '{}'))); | |
| return history ? parseKiroSessionObject(history, stringValue(row['id']) ?? 'kiro-sqlite', range) : []; | |
| }); | |
| const rows = db.query('SELECT id, history FROM conversations_v2').all() as Array<Record<string, unknown>>; | |
| return rows.flatMap((row) => { | |
| try { | |
| const history = objectValue(JSON.parse(String(row['history'] ?? '{}'))); | |
| return history | |
| ? parseKiroSessionObject(history, stringValue(row['id']) ?? 'kiro-sqlite', range) | |
| : []; | |
| } catch { | |
| return []; | |
| } | |
| }); |
🤖 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 `@packages/registry/src/providers/kiro.ts` around lines 103 - 107, The current
DB load uses JSON.parse on each row's history inside the flatMap, which lets one
malformed history throw and abort the entire load; update the logic around the
rows iteration (where rows is produced and processed into
parseKiroSessionObject) to try/catch JSON.parse and/or objectValue per row, skip
rows whose history fails to parse, and continue parsing valid rows; reference
the same symbols: keep using db.query(...).all(), objectValue(JSON.parse(...)),
stringValue(row['id']), and parseKiroSessionObject but wrap the
JSON.parse/objectValue/parseKiroSessionObject call in a per-row try/catch,
optionally logging the row id or error, and return [] for errored rows so other
rows are still ingested.
| const data = await new MuxProvider(root).load(RANGE); | ||
|
|
||
| expect(data.provider).toBe('mux'); | ||
| expect(data.totalTokens).toBe(20); | ||
| expect(data.totalCost).toBe(0.036); | ||
| expect(data.events?.[0]?.projectId).toBe('workspace-a'); |
There was a problem hiding this comment.
Use a tolerant matcher for totalCost float assertions
Line 553 uses exact equality for a summed decimal (expect(data.totalCost).toBe(0.036);), which can be brittle under IEEE-754 rounding. Use a tolerance-based assertion instead.
Suggested fix
- expect(data.totalCost).toBe(0.036);
+ expect(data.totalCost).toBeCloseTo(0.036, 6);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const data = await new MuxProvider(root).load(RANGE); | |
| expect(data.provider).toBe('mux'); | |
| expect(data.totalTokens).toBe(20); | |
| expect(data.totalCost).toBe(0.036); | |
| expect(data.events?.[0]?.projectId).toBe('workspace-a'); | |
| const data = await new MuxProvider(root).load(RANGE); | |
| expect(data.provider).toBe('mux'); | |
| expect(data.totalTokens).toBe(20); | |
| expect(data.totalCost).toBeCloseTo(0.036, 6); | |
| expect(data.events?.[0]?.projectId).toBe('workspace-a'); |
🤖 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 `@packages/registry/src/providers/provider-parity.test.ts` around lines 549 -
554, The test checks a floating-point sum with exact equality
(expect(data.totalCost).toBe(0.036)) which is brittle; change the assertion to a
tolerant matcher such as Jest's toBeCloseTo (e.g.,
expect(data.totalCost).toBeCloseTo(0.036, 3)) so the MuxProvider.load result's
totalCost is compared with a tolerance rather than strict IEEE-754 equality.
Target the assertion for data.totalCost in the provider-parity.test (the
MuxProvider.load result) and adjust the precision as appropriate.
| if (lower.startsWith('hf:')) { | ||
| return lower.split('/').at(-1) ?? lower.slice(3); | ||
| } |
There was a problem hiding this comment.
hf: model normalization fails for single-segment IDs.
For values like hf:deepseek-r1, the current branch keeps the hf: prefix, which can break model normalization consistency.
Suggested fix
function normalizeSyntheticModel(model: string): string {
const lower = model.toLowerCase();
if (lower.startsWith('hf:')) {
- return lower.split('/').at(-1) ?? lower.slice(3);
+ const withoutPrefix = lower.slice(3);
+ return withoutPrefix.split('/').at(-1) ?? withoutPrefix;
}🤖 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 `@packages/registry/src/providers/synthetic.ts` around lines 35 - 37, The hf:
branch returns the whole string when there is no slash (e.g., "hf:deepseek-r1"),
so update the branch that checks if (lower.startsWith('hf:')) to extract the
last segment and then strip the "hf:" prefix if present: compute const last =
lower.split('/').at(-1) ?? ''; if (last.startsWith('hf:')) return last.slice(3);
else return last; this ensures both slash-separated IDs and single-segment hf:
IDs normalize correctly (referencing the existing lower variable and the hf:
branch in providers/synthetic.ts).
| cursor: { dark: '#7c5cbf', light: '#6a4aaa' }, | ||
| codebuff: { dark: '#2563eb', light: '#1d4ed8' }, | ||
| droid: { dark: '#f97316', light: '#c2410c' }, | ||
| kimi: { dark: '#9ca3af', light: '#374151' }, |
There was a problem hiding this comment.
Kimi provider color inconsistency detected.
The dark-mode color for kimi is #9ca3af here, but wrapped-live-template.ts (Line 65) and theme.ts (Line 33) both use #111827. This inconsistency will cause the Kimi provider to render with different colors in live HTML output vs. single-page SVG vs. TUI dashboard, creating a confusing user experience.
🎨 Proposed fix to align with other renderers
- kimi: { dark: '`#9ca3af`', light: '`#374151`' },
+ kimi: { dark: '`#111827`', light: '`#374151`' },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| kimi: { dark: '#9ca3af', light: '#374151' }, | |
| kimi: { dark: '`#111827`', light: '`#374151`' }, |
🤖 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 `@packages/renderers/src/svg/wrapped-single-page.ts` at line 85, The dark-mode
color for the "kimi" provider in the color palette object is inconsistent;
update the "kimi" entry in wrapped-single-page.ts so its dark value is "`#111827`"
(matching the other renderers) instead of "`#9ca3af`" to ensure uniform rendering
across wrapped-live-template and theme mappings.
| # Tokenleak | ||
|
|
||
| See where your AI tokens actually go. Tokenleak reads usage data from **Claude Code**, **Codex**, **Cursor**, **Gemini**, **GitHub Copilot**, **Amp**, **Qwen**, **Roo Code**, **Kilo Code**, **OpenClaw**, **Hermes**, **Pi (`pi-mono`)**, and **OpenCode**, then renders terminal dashboards, heatmaps, compare reports, explain/focus reports, and shareable image cards from the CLI. | ||
| See where your AI tokens actually go. Tokenleak reads usage data from **Claude Code**, **Codex**, **Cursor**, **Gemini**, **GitHub Copilot**, **Amp**, **Codebuff**, **Droid**, **Qwen**, **Roo Code**, **Kilo Code**, **Kimi**, **Kilo CLI**, **Mux**, **Crush**, **OpenClaw**, **Hermes**, **Goose**, **Antigravity**, **Zed Agent**, **Kiro**, **Trae**, **Synthetic**, **Pi (`pi-mono`)**, and **OpenCode**, then renders terminal dashboards, heatmaps, compare reports, explain/focus reports, and shareable image cards from the CLI. |
There was a problem hiding this comment.
Inconsistent provider name: "Kimi" should be "Kimi CLI".
The intro line lists "Kimi" but the provider table (line 24), the provider details section (line 652), and all other references consistently use "Kimi CLI" as the display name.
📝 Proposed fix
-See where your AI tokens actually go. Tokenleak reads usage data from **Claude Code**, **Codex**, **Cursor**, **Gemini**, **GitHub Copilot**, **Amp**, **Codebuff**, **Droid**, **Qwen**, **Roo Code**, **Kilo Code**, **Kimi**, **Kilo CLI**, **Mux**, **Crush**, **OpenClaw**, **Hermes**, **Goose**, **Antigravity**, **Zed Agent**, **Kiro**, **Trae**, **Synthetic**, **Pi (`pi-mono`)**, and **OpenCode**, then renders terminal dashboards, heatmaps, compare reports, explain/focus reports, and shareable image cards from the CLI.
+See where your AI tokens actually go. Tokenleak reads usage data from **Claude Code**, **Codex**, **Cursor**, **Gemini**, **GitHub Copilot**, **Amp**, **Codebuff**, **Droid**, **Qwen**, **Roo Code**, **Kilo Code**, **Kimi CLI**, **Kilo CLI**, **Mux**, **Crush**, **OpenClaw**, **Hermes**, **Goose**, **Antigravity**, **Zed Agent**, **Kiro**, **Trae**, **Synthetic**, **Pi (`pi-mono`)**, and **OpenCode**, then renders terminal dashboards, heatmaps, compare reports, explain/focus reports, and shareable image cards from the CLI.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| See where your AI tokens actually go. Tokenleak reads usage data from **Claude Code**, **Codex**, **Cursor**, **Gemini**, **GitHub Copilot**, **Amp**, **Codebuff**, **Droid**, **Qwen**, **Roo Code**, **Kilo Code**, **Kimi**, **Kilo CLI**, **Mux**, **Crush**, **OpenClaw**, **Hermes**, **Goose**, **Antigravity**, **Zed Agent**, **Kiro**, **Trae**, **Synthetic**, **Pi (`pi-mono`)**, and **OpenCode**, then renders terminal dashboards, heatmaps, compare reports, explain/focus reports, and shareable image cards from the CLI. | |
| See where your AI tokens actually go. Tokenleak reads usage data from **Claude Code**, **Codex**, **Cursor**, **Gemini**, **GitHub Copilot**, **Amp**, **Codebuff**, **Droid**, **Qwen**, **Roo Code**, **Kilo Code**, **Kimi CLI**, **Kilo CLI**, **Mux**, **Crush**, **OpenClaw**, **Hermes**, **Goose**, **Antigravity**, **Zed Agent**, **Kiro**, **Trae**, **Synthetic**, **Pi (`pi-mono`)**, and **OpenCode**, then renders terminal dashboards, heatmaps, compare reports, explain/focus reports, and shareable image cards from the CLI. |
🤖 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 `@README.md` at line 3, The intro sentence lists the provider name as "Kimi"
but other references use "Kimi CLI"; update the string in the intro (the list of
providers in README.md) to "Kimi CLI" so it matches the provider table and
details sections (search for the literal "Kimi" in that provider list and
replace it with "Kimi CLI").
Summary
Validation
bun test packages/registry/src/providers/provider-parity.test.tsbun test packages/cli/src/cli.test.tsbun run checkbun run testbun run buildSummary by CodeRabbit
Release Notes
New Features
TOKENLEAK_*_DIRenvironment variables for enhanced configuration flexibility.Documentation