Skip to content

feat: add MiniMax token plan usage segment#102

Closed
yearth wants to merge 3 commits intoHaleclipse:masterfrom
yearth:pr-101
Closed

feat: add MiniMax token plan usage segment#102
yearth wants to merge 3 commits intoHaleclipse:masterfrom
yearth:pr-101

Conversation

@yearth
Copy link
Copy Markdown

@yearth yearth commented Mar 22, 2026

Add support for checking MiniMax API token plan usage (weekly and 5-hour interval quotas) directly in the statusline.

Features:

  • New MiniMaxTokenPlan segment that queries the MiniMax API
  • Displays remaining quota percentages with visual circle icons
  • Caches API responses for 5 minutes to reduce API calls
  • Configurable via MINIMAX_API_KEY environment variable
  • Added to all built-in themes (cometix, default, minimal, gruvbox, nord, powerline-dark, powerline-light, powerline-rose-pine, powerline-tokyo-night)

Configuration options:

  • cache_duration: Cache validity in seconds (default: 300)
  • timeout: API request timeout in seconds (default: 10)

Summary by Sourcery

Add a MiniMax token plan statusline segment that displays cached MiniMax API quota usage and integrate it into all built-in themes and UI settings.

New Features:

  • Introduce a MiniMaxTokenPlan segment that queries the MiniMax API and shows remaining weekly and 5-hour interval token quotas in the statusline.

Enhancements:

  • Wire the MiniMax token plan segment into statusline collection, theme presets, and UI settings/segment lists, including themed icons and colors across all built-in themes.
  • Add preview/mock data support for the MiniMax token plan segment to the preview component for consistent UI behavior.

Add support for checking MiniMax API token plan usage (weekly and
5-hour interval quotas) directly in the statusline.

Features:
- New MiniMaxTokenPlan segment that queries the MiniMax API
- Displays remaining quota percentages with visual circle icons
- Caches API responses for 5 minutes to reduce API calls
- Configurable via MINIMAX_API_KEY environment variable
- Added to all built-in themes (cometix, default, minimal, gruvbox,
  nord, powerline-dark, powerline-light, powerline-rose-pine,
  powerline-tokyo-night)

Configuration options:
- cache_duration: Cache validity in seconds (default: 300)
- timeout: API request timeout in seconds (default: 10)

Co-Authored-By: Claude <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 22, 2026

Reviewer's Guide

Adds a new MiniMax token plan statusline segment that queries the MiniMax API, computes remaining weekly and 5‑hour quotas with caching, wires it into the segment system, and exposes it across all built‑in themes and UI components.

Sequence diagram for MiniMax token plan segment collection and caching

sequenceDiagram
    actor User
    participant App
    participant Statusline
    participant MiniMaxTokenPlanSegment
    participant FileSystem
    participant MiniMaxAPI

    User->>App: Render statusline
    App->>Statusline: collect_all_segments()
    Statusline->>MiniMaxTokenPlanSegment: collect(input)

    MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: read MINIMAX_API_KEY
    alt API key missing
        MiniMaxTokenPlanSegment-->>Statusline: None
        Statusline-->>App: Skip MiniMaxTokenPlan
        App-->>User: Statusline without MiniMax token plan
    else API key present
        MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: load Config
        MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: read cache_duration, timeout
        MiniMaxTokenPlanSegment->>FileSystem: load_cache()
        FileSystem-->>MiniMaxTokenPlanSegment: CacheData?
        alt Cache valid
            MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: use cached weekly_remaining_pct, interval_remaining_pct
        else No cache or expired
            MiniMaxTokenPlanSegment->>MiniMaxAPI: GET /coding_plan/remains
            alt API success
                MiniMaxAPI-->>MiniMaxTokenPlanSegment: ApiResponse(model_remains)
                MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: find model MiniMax_M2_7
                MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: compute remaining percentages
                MiniMaxTokenPlanSegment->>FileSystem: save_cache(CacheData)
            else API failure
                alt Cached data available
                    MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: fallback to cached CacheData
                else No cache
                    MiniMaxTokenPlanSegment-->>Statusline: None
                    Statusline-->>App: Skip MiniMax token plan
                    App-->>User: Statusline without MiniMax token plan
                end
            end
        end
        MiniMaxTokenPlanSegment->>MiniMaxTokenPlanSegment: build primary string with circle icons
        MiniMaxTokenPlanSegment-->>Statusline: SegmentData{primary, metadata}
        Statusline-->>App: Include MiniMax token plan segment
        App-->>User: Statusline with MiniMax token plan usage
    end
Loading

Class diagram for MiniMax token plan segment and related types

classDiagram
    class Segment {
        <<trait>>
        +collect(input_data) SegmentData?
        +id() SegmentId
    }

    class MiniMaxTokenPlanSegment {
        +new() MiniMaxTokenPlanSegment
        +collect(input_data) SegmentData?
        +id() SegmentId
        -get_circle_icon(remaining_pct u32) String
        -get_cache_path() PathBuf?
        -load_cache() CacheData?
        -save_cache(cache CacheData)
        -is_cache_valid(cache CacheData, cache_duration u64) bool
        -fetch_api(api_key String, timeout_secs u64) ApiResponse?
    }

    class SegmentId {
        <<enum>>
        ContextWindow
        Cost
        Directory
        Git
        Model
        OutputStyle
        Session
        Update
        MiniMaxTokenPlan
    }

    class SegmentData {
        +primary String
        +secondary String
        +metadata HashMap~String, String~
    }

    class ApiResponse {
        +model_remains Vec~ModelRemain~
    }

    class ModelRemain {
        +model_name String
        +current_interval_total_count u64
        +current_interval_usage_count u64
        +current_weekly_total_count u64
        +current_weekly_usage_count u64
    }

    class CacheData {
        +weekly_remaining_pct u32
        +interval_remaining_pct u32
        +cached_at String
    }

    class Config {
        +segments Vec~SegmentConfig~
        +load() Result~Config, Error~
    }

    class SegmentConfig {
        +id SegmentId
        +enabled bool
        +icon IconConfig
        +colors ColorConfig
        +styles TextStyleConfig
        +options HashMap~String, Value~
    }

    Segment <|.. MiniMaxTokenPlanSegment
    MiniMaxTokenPlanSegment --> SegmentId
    MiniMaxTokenPlanSegment --> SegmentData
    MiniMaxTokenPlanSegment --> ApiResponse
    ApiResponse --> ModelRemain
    MiniMaxTokenPlanSegment --> CacheData
    MiniMaxTokenPlanSegment --> Config
    Config --> SegmentConfig
    SegmentConfig --> SegmentId
    SegmentData --> SegmentId
Loading

File-Level Changes

Change Details Files
Introduce MiniMaxTokenPlan segment implementation that queries MiniMax API, caches results, and formats remaining quota display.
  • Define MiniMaxTokenPlanSegment struct implementing the Segment trait and registering its SegmentId.
  • Implement MiniMax API response types, cache data structure, and logic to compute weekly and 5‑hour interval remaining percentages for model MiniMax-M2.7.
  • Add filesystem-based JSON cache with timestamp, cache validation using chrono, and fallback behavior when API requests fail.
  • Add circle-slice icon mapping based on remaining percentage and build the primary statusline string plus metadata map.
src/core/segments/minimax_token_plan.rs
src/core/segments/mod.rs
src/config/types.rs
src/core/statusline.rs
Integrate the MiniMaxTokenPlan segment into the UI, presets, and theme system with default options.
  • Add minimax_token_plan_segment() factory functions to each theme, configuring icon, colors, and default cache_duration/timeout options.
  • Include the new minimax_token_plan_segment() in all ThemePresets so it appears in each built-in theme configuration (disabled by default).
  • Extend UI components (app, settings, segment list, preview) to recognize SegmentId::MiniMaxTokenPlan, provide a human-readable name, and mock preview data including metadata fields for remaining percentages.
src/ui/themes/theme_nord.rs
src/ui/themes/theme_powerline_dark.rs
src/ui/themes/theme_powerline_light.rs
src/ui/themes/theme_powerline_rose_pine.rs
src/ui/themes/theme_powerline_tokyo_night.rs
src/ui/themes/theme_cometix.rs
src/ui/themes/theme_default.rs
src/ui/themes/theme_gruvbox.rs
src/ui/themes/theme_minimal.rs
src/ui/themes/presets.rs
src/ui/components/preview.rs
src/ui/app.rs
src/ui/components/segment_list.rs
src/ui/components/settings.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The timeout option for the MiniMax token plan segment is currently unused in fetch_api (parameter is _timeout_secs); either wire this into the ureq call (e.g., via a request/agent timeout) or remove the option/parameter to avoid confusion.
  • In MiniMaxTokenPlanSegment::collect, loading the full Config from disk on every render just to read segment options may introduce unnecessary I/O; consider wiring the segment options through InputData (similar to other segments) so you can avoid reloading the config each time.
  • The MiniMax model name "MiniMax-M2.7" is hardcoded when selecting model_remains; if different models/plans are used, it might be more flexible to make this model identifier configurable via segment options or environment variables.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `timeout` option for the MiniMax token plan segment is currently unused in `fetch_api` (parameter is `_timeout_secs`); either wire this into the `ureq` call (e.g., via a request/agent timeout) or remove the option/parameter to avoid confusion.
- In `MiniMaxTokenPlanSegment::collect`, loading the full `Config` from disk on every render just to read segment options may introduce unnecessary I/O; consider wiring the segment options through `InputData` (similar to other segments) so you can avoid reloading the config each time.
- The MiniMax model name `"MiniMax-M2.7"` is hardcoded when selecting `model_remains`; if different models/plans are used, it might be more flexible to make this model identifier configurable via segment options or environment variables.

## Individual Comments

### Comment 1
<location path="src/core/segments/minimax_token_plan.rs" line_range="118-126" />
<code_context>
+        }
+
+        // Load config from file to get segment options
+        let config = crate::config::Config::load().ok()?;
+        let segment_config = config
+            .segments
+            .iter()
+            .find(|s| s.id == SegmentId::MiniMaxTokenPlan);
+
+        let cache_duration = segment_config
+            .and_then(|sc| sc.options.get("cache_duration"))
+            .and_then(|v| v.as_u64())
+            .unwrap_or(300);
+
</code_context>
<issue_to_address>
**suggestion (performance):** Loading the full config on every collect call may be unnecessarily expensive.

Each `collect` call reloads `Config` from disk just to read `cache_duration` and `timeout`, which can add noticeable overhead if this runs often. Consider caching these options on the segment (e.g., via `OnceLock` or similar) or passing them in from an already-loaded config instead of reloading each time.

Suggested implementation:

```rust
}

use std::sync::OnceLock;

#[derive(Clone, Copy, Debug)]
struct MiniMaxSegmentOptions {
    cache_duration: u64,
    timeout: u64,
}

static MINIMAX_SEGMENT_OPTIONS: OnceLock<MiniMaxSegmentOptions> = OnceLock::new();

fn get_segment_options() -> MiniMaxSegmentOptions {
    *MINIMAX_SEGMENT_OPTIONS.get_or_init(|| {
        let config = crate::config::Config::load().ok();

        let (cache_duration, timeout) = config
            .as_ref()
            .and_then(|cfg| {
                cfg.segments
                    .iter()
                    .find(|s| s.id == SegmentId::MiniMaxTokenPlan)
            })
            .map(|sc| {
                let cache_duration = sc
                    .options
                    .get("cache_duration")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(300);

                let timeout = sc
                    .options
                    .get("timeout")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(10);

                (cache_duration, timeout)
            })
            .unwrap_or((300, 10));

        MiniMaxSegmentOptions {
            cache_duration,
            timeout,
        }
    })
}

impl Segment for MiniMaxTokenPlanSegment {

```

```rust
        // Load config options once and reuse them across collect calls
        let options = get_segment_options();
        let cache_duration = options.cache_duration;
        let timeout = options.timeout;

#[derive(Debug, Deserialize)]

```

1. Ensure the necessary imports exist at the top of the file (or the relevant module scope):
   - `use crate::config::{Config, SegmentId};` (or equivalent paths if they differ in your codebase).
   - If `Config` and `SegmentId` are already imported, remove any duplicate `use` lines.
2. Replace the hard-coded default values `300` and `10` with the existing defaults used elsewhere in this file (or in your config layer) if they differ, so behavior remains consistent with the pre-PR implementation.
3. Anywhere later in `collect` where `timeout` was previously defined directly (e.g., a local `timeout` variable before the PR), remove or update that code to rely on the `timeout` from `options` to avoid duplicate/conflicting definitions.
</issue_to_address>

### Comment 2
<location path="src/core/segments/minimax_token_plan.rs" line_range="147-149" />
<code_context>
+            match self.fetch_api(&api_key, timeout) {
+                Some(response) => {
+                    let model = response
+                        .model_remains
+                        .iter()
+                        .find(|m| m.model_name == "MiniMax-M2.7")?;
+
+                    let weekly_remaining_pct = if model.current_weekly_total_count > 0 {
</code_context>
<issue_to_address>
**suggestion:** Hard-coding the model name may limit flexibility and robustness.

This lookup relies on the exact string "MiniMax-M2.7", so a plan change, model rename, or additional models would cause this to return `None`. Consider making the target model configurable (e.g., via segment options) and/or using a sensible default if the preferred model isn’t found.

Suggested implementation:

```rust
            match self.fetch_api(&api_key, timeout) {
                Some(response) => {
                    // Prefer a configurable model name, falling back to a sensible default.
                    let preferred_model_name = self
                        .preferred_model_name()
                        .unwrap_or_else(|| "MiniMax-M2.7".to_string());

                    let model = response
                        .model_remains
                        .iter()
                        .find(|m| m.model_name == preferred_model_name)
                        // Fallback: if the preferred model isn't found, use the first available model.
                        .or_else(|| response.model_remains.iter().next())?;

```

To fully support configurability you’ll need to:
1. Add a `preferred_model_name(&self) -> Option<String>` helper (or adjust the call above to match your existing configuration access pattern). For example, this helper might read from segment options or configuration:
   - If you already have a generic options struct, implement `preferred_model_name()` to read something like `self.options.preferred_model_name`.
   - Otherwise, add a new field (e.g., `preferred_model_name: Option<String>`) to the segment struct and implement the helper to return it.
2. Wire this configuration up from the segment construction site (e.g., from JSON/YAML config, environment, or wherever segment options are currently provided) so users can override the model name.
3. Ensure the `model_remains` collection is non-empty before calling `.iter().next()` or rely on the existing `?` to propagate `None` as appropriate for your error-handling semantics.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

yearth and others added 2 commits March 22, 2026 19:07
- Wire timeout_secs through to ureq::Agent via timeout_global()
- Cache segment config to avoid repeated disk I/O on each render
- Extract shared minimax_token_plan_options() helper in presets.rs

Co-Authored-By: Claude <noreply@anthropic.com>
Add preferred_model option to segment config, defaulting to "MiniMax-M2.7".
Fallback to first available model if preferred model is not found.

Co-Authored-By: Claude <noreply@anthropic.com>
@Haleclipse
Copy link
Copy Markdown
Owner

We will not create a separate Usage Segment for any single provider.

We will consider aggregating a unified data request extension.

yearth pushed a commit to yearth/CCometixLine that referenced this pull request Mar 23, 2026
Replaces the per-provider MiniMaxTokenPlan approach with a general-purpose
ExternalUsage segment that dispatches to pluggable UsageProvider impls.

Design:
- `UsageProvider` trait: name() + fetch(options) -> Vec<UsageMetric>
- `ExternalUsageSegment`: handles caching, circle-icon formatting, config
- `MinimaxProvider`: queries MiniMax coding_plan/remains API
- Shared `Cache` utility extracted to cache.rs

Adding a new provider only requires implementing UsageProvider and
registering it in providers/mod.rs — no changes to segment core or types.

Default config (disabled, provider=minimax):
  options.provider = "minimax"
  options.auth_env = "MINIMAX_API_KEY"
  options.cache_duration = 300
  options.timeout = 10

Supersedes Haleclipse#101 and Haleclipse#102 (MiniMaxTokenPlan).
yearth added a commit to yearth/CCometixLine that referenced this pull request Mar 23, 2026
Replaces the per-provider MiniMaxTokenPlan approach with a general-purpose
ExternalUsage segment that dispatches to pluggable UsageProvider impls.

Design:
- `UsageProvider` trait: name() + fetch(options) -> Vec<UsageMetric>
- `ExternalUsageSegment`: handles caching, circle-icon formatting, config
- `MinimaxProvider`: queries MiniMax coding_plan/remains API
- Shared `Cache` utility extracted to cache.rs

Adding a new provider only requires implementing UsageProvider and
registering it in providers/mod.rs — no changes to segment core or types.

Default config (disabled, provider=minimax):
  options.provider = "minimax"
  options.auth_env = "MINIMAX_API_KEY"
  options.cache_duration = 300
  options.timeout = 10

Supersedes Haleclipse#101 and Haleclipse#102 (MiniMaxTokenPlan).
@yearth
Copy link
Copy Markdown
Author

yearth commented Mar 23, 2026

Superseded by #104, which implements a provider-based extensibility mechanism as suggested.

@yearth yearth closed this Mar 23, 2026
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