Skip to content

Add Kimi Coding Plan provider - #1

Merged
sargunv merged 1 commit into
sargunv:mainfrom
sargunv-bot:feat/kimi-coding-plan
Jul 19, 2026
Merged

Add Kimi Coding Plan provider#1
sargunv merged 1 commit into
sargunv:mainfrom
sargunv-bot:feat/kimi-coding-plan

Conversation

@sargunv-bot

Copy link
Copy Markdown
Contributor

Summary

  • add first-class Kimi Coding Plan usage via the first-party https://api.kimi.com/coding/v1/usages endpoint
  • discover ~/.kimi/credentials/kimi-code.json / KIMI_SHARE_DIR, refresh expiring OAuth credentials with Kimi CLI-compatible locking and atomic unknown-field-preserving writes, and retry usage once after a 401
  • normalize weekly and additional quota windows and expose Kimi in the service, CLI, GTK, SwiftUI, and Plasma frontends
  • document setup and pin the researched MoonshotAI/kimi-cli source to 4a550effdfcb29a25a5d325bf935296cc50cd417 (1.49.0)

Test plan

  • mise exec -- cargo test --workspace --all-features --exclude braindrain-gui
  • mise exec -- cargo clippy --workspace --all-targets --all-features --exclude braindrain-gui -- -D warnings
  • mise exec -- cargo fmt --all -- --check
  • mise exec -- dprint check
  • smoke-tested braindrain providers and braindrain info kimi with an isolated KIMI_SHARE_DIR

The full local mise run check reaches the Linux GUI package but this host does not have the required GLib 2.88 development package; all non-GUI workspace targets pass, and CI covers the configured project matrix.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added support for tracking Kimi Code subscription usage via its provider.
    • Recognizes kimi, kimi-code, and kimi-coding-plan aliases and normalizes them to the Kimi provider.
    • Improved provider display names to show “Kimi Code” across Linux, macOS, and Plasma.
    • Added OAuth-based credential handling with automatic token refresh and usage quota/time window reporting.
  • Documentation
    • Added README instructions for setting up and running Kimi Coding Plan checks.

Walkthrough

Adds a Kimi Code provider for subscription usage, including OAuth credentials, token refresh, usage parsing, service registration, frontend labels, tests, workspace wiring, and setup documentation.

Changes

Kimi Code provider

Layer / File(s) Summary
Workspace and provider identity
Cargo.toml, crates/core/src/lib.rs, crates/providers-kimi/Cargo.toml
Registers the provider crate and dependencies, and adds the canonical kimi provider identifier.
Credential resolution and usage requests
crates/providers-kimi/src/lib.rs
Resolves API keys and credential files, refreshes OAuth tokens with locking and atomic persistence, and fetches usage data with bearer authentication.
Usage models and validation
crates/providers-kimi/src/lib.rs
Parses Kimi usage responses into rate windows and tests parsing, credential compatibility, API requests, token rotation, and error handling.
Service routing and presentation
crates/service/*, apps/linux/src/app.rs, apps/macos/Sources/BrainDrainApp/ProviderListModel.swift, apps/plasma/package/contents/ui/main.qml, README.md
Adds Kimi discovery, aliases, info/check flows, frontend display names, tests, and setup documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Service
  participant KimiProvider
  participant OAuth
  participant UsageAPI
  CLI->>Service: check kimi
  Service->>KimiProvider: refresh usage
  KimiProvider->>OAuth: refresh token when needed
  OAuth-->>KimiProvider: refreshed credentials
  KimiProvider->>UsageAPI: fetch subscription usage
  UsageAPI-->>KimiProvider: usage windows
  KimiProvider-->>Service: UsageSnapshot
  Service-->>CLI: rendered provider usage
Loading

Suggested reviewers: sargunv

Poem

I’m a rabbit with tokens, refreshed in the night,
Kimi windows now hop into sight.
Locked files stay tidy, APIs hum bright,
“Kimi Code” labels make every path right.
Usage blooms softly—what a delightful byte!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding support for the Kimi Coding Plan provider.
Description check ✅ Passed The description is directly related to the changeset and matches the implemented Kimi provider, integrations, and tests.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@sargunv-bot
sargunv-bot force-pushed the feat/kimi-coding-plan branch 2 times, most recently from c29313f to 7519585 Compare July 19, 2026 12:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@Cargo.toml`:
- Line 30: Replace the outdated fs2 dependency with maintained fs4 in
Cargo.toml, then update all dependent lock-call usage to match fs4’s API rather
than assuming drop-in compatibility. Verify the project builds and preserves
existing filesystem locking behavior.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 2a1850db-2d6e-425f-863d-9054dbc61705

📥 Commits

Reviewing files that changed from the base of the PR and between 93d17a8 and 7519585.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml
  • README.md
  • apps/linux/src/app.rs
  • apps/macos/Sources/BrainDrainApp/ProviderListModel.swift
  • apps/plasma/package/contents/ui/main.qml
  • crates/core/src/lib.rs
  • crates/providers-kimi/Cargo.toml
  • crates/providers-kimi/src/lib.rs
  • crates/service/Cargo.toml
  • crates/service/src/lib.rs

Comment thread Cargo.toml Outdated
Read and safely refresh first-party Kimi Code OAuth credentials, fetch and normalize subscription usage, and expose Kimi across the service and desktop frontends.
@sargunv-bot
sargunv-bot force-pushed the feat/kimi-coding-plan branch from 7519585 to 0460dd5 Compare July 19, 2026 18:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@crates/providers-kimi/src/lib.rs`:
- Around line 684-702: Update the duration method to use the fallible Duration
constructor when converting value * multiplier, returning None if the input is
non-finite or overflows instead of panicking. Preserve the existing
non-positive-value handling and time-unit multiplier selection.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: a9125afe-0bde-4ee6-bc0f-65b70c50a066

📥 Commits

Reviewing files that changed from the base of the PR and between 7519585 and 0460dd5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml
  • README.md
  • apps/linux/src/app.rs
  • apps/macos/Sources/BrainDrainApp/ProviderListModel.swift
  • apps/plasma/package/contents/ui/main.qml
  • crates/core/src/lib.rs
  • crates/providers-kimi/Cargo.toml
  • crates/providers-kimi/src/lib.rs
  • crates/service/Cargo.toml
  • crates/service/src/lib.rs

Comment on lines +684 to +702
fn duration(&self) -> Option<Duration> {
let value = self.duration.as_ref().and_then(value_to_f64)?;
if value <= 0.0 {
return None;
}
let multiplier = match self
.time_unit
.as_deref()
.unwrap_or("")
.to_ascii_uppercase()
.as_str()
{
unit if unit.contains("MINUTE") => 60.0,
unit if unit.contains("HOUR") => 3_600.0,
unit if unit.contains("DAY") => 86_400.0,
_ => 1.0,
};
Some(Duration::from_secs_f64(value * multiplier))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Rust Duration try_from_secs_f64 stabilized version

💡 Result:

The Duration::try_from_secs_f64 method was stabilized in Rust 1.66.0 [1]. It is the checked version of from_secs_f64, providing a way to convert a floating-point value of seconds into a Duration without panicking [2][3]. Instead of panicking on negative, non-finite (e.g., NaN, infinity), or overflowing input, it returns a Result<Duration, TryFromFloatSecsError> [2][1]. The stabilization was finalized via pull request #102271 [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== workspace manifests ==\n'
git ls-files | rg '(^|/)(Cargo\.toml|rust-toolchain(\.toml)?|README\.md)$'

printf '\n== rust-version / toolchain hints ==\n'
for f in Cargo.toml rust-toolchain.toml rust-toolchain; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  fi
done

printf '\n== target file around duration() ==\n'
cat -n crates/providers-kimi/src/lib.rs | sed -n '670,710p'

Repository: sargunv/braindrain

Length of output: 4089


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== value_to_f64 and nearby helpers ==\n'
rg -n "fn value_to_f64|value_to_f64\\(" crates/providers-kimi/src/lib.rs
cat -n crates/providers-kimi/src/lib.rs | sed -n '1,220p'

printf '\n== all uses of KimiWindow::duration / label ==\n'
rg -n "KimiWindow::duration|\\.duration\\(" crates/providers-kimi/src/lib.rs

Repository: sargunv/braindrain

Length of output: 9999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n crates/providers-kimi/src/lib.rs | sed -n '835,875p'

Repository: sargunv/braindrain

Length of output: 1570


Avoid panicking on malformed duration values. value_to_f64() can produce non-finite or very large inputs from the API, and Duration::from_secs_f64(value * multiplier) will panic on NaN/∞/overflow. Use the fallible constructor here instead.

Proposed change
-        Some(Duration::from_secs_f64(value * multiplier))
+        Duration::try_from_secs_f64(value * multiplier).ok()
📝 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.

Suggested change
fn duration(&self) -> Option<Duration> {
let value = self.duration.as_ref().and_then(value_to_f64)?;
if value <= 0.0 {
return None;
}
let multiplier = match self
.time_unit
.as_deref()
.unwrap_or("")
.to_ascii_uppercase()
.as_str()
{
unit if unit.contains("MINUTE") => 60.0,
unit if unit.contains("HOUR") => 3_600.0,
unit if unit.contains("DAY") => 86_400.0,
_ => 1.0,
};
Some(Duration::from_secs_f64(value * multiplier))
}
fn duration(&self) -> Option<Duration> {
let value = self.duration.as_ref().and_then(value_to_f64)?;
if value <= 0.0 {
return None;
}
let multiplier = match self
.time_unit
.as_deref()
.unwrap_or("")
.to_ascii_uppercase()
.as_str()
{
unit if unit.contains("MINUTE") => 60.0,
unit if unit.contains("HOUR") => 3_600.0,
unit if unit.contains("DAY") => 86_400.0,
_ => 1.0,
};
Duration::try_from_secs_f64(value * multiplier).ok()
}
🤖 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 `@crates/providers-kimi/src/lib.rs` around lines 684 - 702, Update the duration
method to use the fallible Duration constructor when converting value *
multiplier, returning None if the input is non-finite or overflows instead of
panicking. Preserve the existing non-positive-value handling and time-unit
multiplier selection.

@sargunv
sargunv merged commit 778bb1f into sargunv:main Jul 19, 2026
1 check 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