Skip to content

Conversation

@rere43
Copy link

@rere43 rere43 commented Jan 12, 2026

…ystem

  • Add new CliProxyApiQuota segment to display quota usage from CLI Proxy API

    • Supports multiple auth types (antigravity, gemini-cli)
    • Shows quota percentage for Opus, Gemini 3 Pro, and Gemini 3 Flash models
    • Configurable via TUI with alias and color customization
    • Uses caching to minimize API calls
  • Enhance theme system with forward-compatible segment merging

    • Add merge_missing_segments() to auto-add new segments to existing configs
    • Centralize default segment config in presets.rs (reduces code duplication)
    • New segments are disabled by default to avoid breaking existing setups
  • Add open_with_value() method to NameInputComponent for pre-filled text inputs

Summary by Sourcery

Add a new CLI Proxy API quota statusline segment with configurable options and forward-compatible theme support.

New Features:

  • Introduce a CLI Proxy API Quota segment that displays quota usage for selected models using the CLI Proxy API.
  • Add a TUI options editor for configuring CLI Proxy API quota aliases, colors, and separators.
  • Support raw ANSI-colored text rendering in segments via metadata flags.
  • Allow opening text input fields pre-populated with an initial value in the name input component.

Enhancements:

  • Centralize default CLI Proxy API Quota segment configuration in theme presets and include it (disabled by default) across all built-in themes.
  • Automatically merge missing segments from built-in themes into user theme files to keep older configurations forward compatible.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 12, 2026

Reviewer's Guide

Implements a new CLI Proxy API Quota statusline segment with configurable aliases/colors and caching, wires it through core, preview, and TUI editors, and enhances the theme system to share the segment definition across all presets with forward-compatible segment merging and improved text input handling.

Sequence diagram for editing CLI Proxy API quota alias via TUI

sequenceDiagram
    actor User
    participant App
    participant CliProxyApiQuotaOptionsComponent as CliProxyOptions
    participant NameInputComponent as NameInput
    participant Config
    participant PreviewComponent as Preview

    User->>App: Navigate to CliProxyApiQuota segment
    User->>App: Press Tab to Options field
    User->>App: Press Enter
    App->>CliProxyOptions: open()
    App->>App: status_message = "Editing CPA Quota options"

    loop Navigate options
        User->>CliProxyOptions: Up/Down keys (via App event loop)
        CliProxyOptions-->>App: move_selection(delta)
    end

    User->>App: Press Enter on Alias field
    App->>CliProxyOptions: selected_field()
    CliProxyOptions-->>App: Alias(CliProxyApiQuotaModelKind)
    App->>Config: read current segment.options[alias_key]
    App->>NameInput: open_with_value(title, placeholder, current_alias)
    App->>App: text_input_target = CliProxyApiQuotaAlias(model)

    User->>NameInput: Type new alias
    User->>App: Press Enter
    App->>NameInput: get_input()
    NameInput-->>App: value
    App->>App: apply_text_input(value)
    App->>Config: update segment.options[alias_key] = value
    App->>App: status_message = "Updated <model> alias"
    App->>Preview: update_preview(&config)
    App->>NameInput: close()
    App->>App: text_input_target = None

    User->>App: Press Esc
    App->>CliProxyOptions: close()
Loading

Sequence diagram for rendering CLI Proxy API quota segment with caching

sequenceDiagram
    participant StatusLineGenerator as StatusLineGen
    participant CliProxyApiQuotaSegment as CPAQuotaSeg
    participant Config
    participant FileSystem as FS

    StatusLineGen->>CPAQuotaSeg: collect(&InputData)

    CPAQuotaSeg->>Config: Config::load()
    Config-->>CPAQuotaSeg: Config (includes segments)
    CPAQuotaSeg->>CPAQuotaSeg: find CliProxyApiQuota SegmentConfig
    CPAQuotaSeg->>CPAQuotaSeg: read options (host,key,cache_duration,auth_type,separator)

    CPAQuotaSeg->>FS: load_cache()
    FS-->>CPAQuotaSeg: Option<CliProxyApiQuotaCache>
    alt valid cache
        CPAQuotaSeg->>CPAQuotaSeg: is_cache_valid(cache, cache_duration)
        CPAQuotaSeg-->>CPAQuotaSeg: use cached quotas
    else missing or expired cache
        CPAQuotaSeg->>CPAQuotaSeg: fetch_all_quotas(host,key,auth_type)
        CPAQuotaSeg-->>CPAQuotaSeg: quotas
        alt quotas not empty
            CPAQuotaSeg->>FS: save_cache(CliProxyApiQuotaCache)
        else fetch failed
            CPAQuotaSeg->>CPAQuotaSeg: fallback to cached quotas or empty
        end
    end

    alt quotas empty
        CPAQuotaSeg-->>StatusLineGen: None
    else quotas present
        CPAQuotaSeg->>CPAQuotaSeg: format_tracked_output(quotas, options, separator)
        CPAQuotaSeg-->>StatusLineGen: Some(SegmentData{primary, metadata[raw_text=true]})
    end

    StatusLineGen->>StatusLineGen: render_segment(config, data - respects raw_text flag)
Loading

Class diagram for CLI Proxy API quota segment and options

classDiagram
    direction LR

    class CliProxyApiQuotaSegment {
        +new() CliProxyApiQuotaSegment
        +collect(input: InputData) Option~SegmentData~
        +id() SegmentId
        -normalize_model_text(text: &str) String
        -tracked_model_for(model_id: &str, display_name: &str) Option~TrackedModel~
        -tracked_model_for_quota(quota: &ModelQuota) Option~TrackedModel~
        -get_alias(options: &HashMap~String, Value~, model: TrackedModel) String
        -get_color(options: &HashMap~String, Value~, model: TrackedModel) AnsiColor
        -apply_foreground_color(text: &str, color: &AnsiColor) String
        -format_tracked_output(quotas: &Vec~ModelQuota~, options: &HashMap~String, Value~, separator: &str) String
        -get_cache_path() Option~PathBuf~
        -load_cache() Option~CliProxyApiQuotaCache~
        -save_cache(cache: &CliProxyApiQuotaCache) void
        -is_cache_valid(cache: &CliProxyApiQuotaCache, cache_duration: u64) bool
        -get_auth_files(host: &str, key: &str) Option~Vec~AuthFile~~
        -api_call(host: &str, key: &str, auth_index: &str, method: &str, url: &str, data: &str, extra_headers: Option~HashMap~&str,&str~~) Option~ApiCallResponse~
        -get_antigravity_quota(host: &str, key: &str, auth_index: &str) Vec~ModelQuota~
        -extract_project_from_name(name: &str) Option~String~
        -get_gemini_cli_quota(host: &str, key: &str, auth_index: &str, project: &str) Vec~ModelQuota~
        -fetch_all_quotas(host: &str, key: &str, auth_type_filter: &str) Vec~ModelQuota~
    }

    class Segment {
        <<trait>>
        +collect(input: InputData) Option~SegmentData~
        +id() SegmentId
    }

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

    class TrackedModel {
        <<enum>>
        Opus
        Gemini3Pro
        Gemini3Flash
        +alias_key() &str
        +color_key() &str
        +default_alias() &str
        +default_color() AnsiColor
    }

    class CliProxyApiQuotaCache {
        +quotas: Vec~ModelQuota~
        +cached_at: String
    }

    class ModelQuota {
        +model_id: String
        +display_name: String
        +remaining_fraction: f64
        +auth_type: String
    }

    class AuthFile {
        +auth_type: String
        +auth_index: String
        +label: Option~String~
        +name: Option~String~
        +disabled: Option~bool~
    }

    class AuthFilesResponse {
        +files: Vec~AuthFile~
    }

    class ApiCallResponse {
        +body: Option~String~
        +error: Option~String~
    }

    class AntigravityModelsResponse {
        +models: Option~HashMap~String,ModelInfo~>
    }

    class ModelInfo {
        +display_name: Option~String~
        +quota_info: Option~QuotaInfo~
    }

    class QuotaInfo {
        +remaining_fraction: Option~f64~
    }

    class GeminiQuotaResponse {
        +buckets: Option~Vec~GeminiBucket~~
    }

    class GeminiBucket {
        +model_id: Option~String~
        +remaining_fraction: Option~f64~
    }

    class CliProxyApiQuotaOptionsComponent {
        +is_open: bool
        -selected: usize
        +new() CliProxyApiQuotaOptionsComponent
        +open() void
        +close() void
        +move_selection(delta: i32) void
        +selected_field() CliProxyApiQuotaOptionField
        +render(f: Frame, area: Rect, config: Config, selected_segment: usize) void
        -fields() &[CliProxyApiQuotaOptionField]
        -get_alias(options: &HashMap~String,Value~, model: CliProxyApiQuotaModelKind) String
        -get_color(options: &HashMap~String,Value~, model: CliProxyApiQuotaModelKind) Option~AnsiColor~
        -color_to_desc(color: &Option~AnsiColor~) String
        -to_ratatui_color(color: &AnsiColor) Color
    }

    class CliProxyApiQuotaModelKind {
        <<enum>>
        Opus
        Gemini3Pro
        Gemini3Flash
        +display_name() &str
        +alias_key() &str
        +color_key() &str
        +default_alias() &str
    }

    class CliProxyApiQuotaOptionField {
        <<enum>>
        Alias(CliProxyApiQuotaModelKind)
        Color(CliProxyApiQuotaModelKind)
        Separator
    }

    class App {
        -config: Config
        -cli_proxy_api_quota_options: CliProxyApiQuotaOptionsComponent
        -text_input_target: Option~TextInputTarget~
        -color_picker_target: Option~ColorPickerTarget~
        +apply_text_input(value: String) void
        +apply_selected_color(color: AnsiColor) void
        +open_cli_proxy_api_quota_option_editor() void
    }

    class TextInputTarget {
        <<enum>>
        SaveThemeName
        CliProxyApiQuotaAlias(CliProxyApiQuotaModelKind)
        CliProxyApiQuotaSeparator
    }

    class ColorPickerTarget {
        <<enum>>
        CliProxyApiQuotaModelColor(CliProxyApiQuotaModelKind)
    }

    class ThemePresets {
        +get_theme(theme_name: &str) Config
        +builtin_theme(theme_name: &str) Option~Config~
        +merge_missing_segments(config: Config, baseline: &Config) Config
        +default_cli_proxy_api_quota_segment() SegmentConfig
    }

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

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

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

    Segment <|.. CliProxyApiQuotaSegment
    CliProxyApiQuotaSegment --> SegmentData
    CliProxyApiQuotaSegment --> CliProxyApiQuotaCache
    CliProxyApiQuotaSegment --> ModelQuota
    CliProxyApiQuotaSegment --> TrackedModel
    CliProxyApiQuotaSegment --> AuthFilesResponse
    CliProxyApiQuotaSegment --> ApiCallResponse
    CliProxyApiQuotaSegment --> AntigravityModelsResponse
    CliProxyApiQuotaSegment --> GeminiQuotaResponse

    CliProxyApiQuotaOptionsComponent --> CliProxyApiQuotaModelKind
    CliProxyApiQuotaOptionsComponent --> CliProxyApiQuotaOptionField
    CliProxyApiQuotaOptionsComponent --> Config
    CliProxyApiQuotaOptionsComponent --> SegmentConfig

    App --> CliProxyApiQuotaOptionsComponent
    App --> TextInputTarget
    App --> ColorPickerTarget
    App --> Config

    ThemePresets --> SegmentConfig
    ThemePresets --> Config
    SegmentConfig --> SegmentId
    Config --> SegmentConfig

    CliProxyApiQuotaSegment --> SegmentId
Loading

File-Level Changes

Change Details Files
Add CLI Proxy API Quota statusline segment with cached quota retrieval and raw-colored output support.
  • Introduce CliProxyApiQuotaSegment that loads its own segment options, calls the CLI Proxy management API for antigravity and gemini-cli auth types, aggregates per-model remainingFraction into percentages for Opus/Gemini 3 Pro/Gemini 3 Flash, and caches results on disk with TTL-based invalidation.
  • Define multiple response/DTO types for auth-files, antigravity models, and Gemini quota APIs, including helpers to normalize model identifiers and map them to tracked models.
  • Implement ANSI foreground-color rendering for per-model labels and mark the segment output as raw_text in SegmentData metadata so downstream rendering doesn’t re-style or strip escape codes.
  • Wire CliProxyApiQuotaSegment into the segment registry and statusline generator, including handling of raw_text metadata in StatusLineGenerator::render_segment for both background-colored and non-background segments.
src/core/segments/cli_proxy_api_quota.rs
src/core/segments/mod.rs
src/core/statusline.rs
src/config/types.rs
Add configurable CLI Proxy API Quota segment to all themes and make theme loading forward-compatible via baseline segment merging.
  • Create a shared default_cli_proxy_api_quota_segment factory in ThemePresets with default icon, colors, and options (host, key, cache_duration, auth_type, separator).
  • Include the default CLI Proxy API Quota segment in every builtin theme’s segment list so the segment exists (disabled) across presets.
  • Refactor builtin theme selection into ThemePresets::builtin_theme and change get_theme to use it with a fallback to the default theme.
  • When loading a theme from disk, compute a baseline builtin config and merge any missing segments from it into the loaded config via merge_missing_segments so older theme files automatically gain new segments without changing existing ones.
src/ui/themes/presets.rs
Extend the TUI to configure CLI Proxy API Quota segment options (aliases, colors, separator) via dedicated popups and targeted inputs.
  • Add CliProxyApiQuotaOptionsComponent, with model kind and option field enums, to render a modal listing per-model alias/color and the separator, showing current values and color swatches, and handle selection movement.
  • Integrate CliProxyApiQuotaOptionsComponent into App state and event loop: opening it when the options field is selected for the CliProxyApiQuota segment, handling Esc/Enter/up/down, and rendering it above the main UI.
  • Introduce TextInputTarget and ColorPickerTarget enums in App to route the result of the shared name_input and color_picker popups to specific CliProxyApiQuota options or theme-saving logic.
  • Implement App::open_cli_proxy_api_quota_option_editor and App::apply_text_input to open name_input prefilled with current alias/separator values and write back updated options into the selected CliProxyApiQuota segment, updating preview and status messages; similarly extend apply_selected_color to update per-model color options when ColorPickerTarget is set.
src/ui/app.rs
src/ui/components/cli_proxy_api_quota_options.rs
Enhance preview rendering and segment list/labels to support the new CLI Proxy API Quota segment and show realistic colored mock output.
  • Add a mock SegmentData case for SegmentId::CliProxyApiQuota in PreviewComponent that builds an ANSI-colored string showing fixed percentages for the three tracked models, driven by segment options for aliases, colors, and separator, and sets raw_text=true metadata.
  • Add SegmentId::CliProxyApiQuota to various TUI label switches (status messages, settings, segment list) so the new segment is named correctly in the UI.
src/ui/components/preview.rs
src/ui/components/segment_list.rs
src/ui/components/settings.rs
src/ui/app.rs
Improve the name input component to support pre-filled values for editing existing options.
  • Add NameInputComponent::open_with_value to open the popup with an initial text value and reuse title/placeholder handling, used for editing CPA quota aliases and separator.
  • Ensure name_input close logic in App clears associated TextInputTarget and routes Enter presses through apply_text_input instead of directly saving a theme.
src/ui/components/name_input.rs
src/ui/app.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

@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 TrackedModel logic in core/segments/cli_proxy_api_quota.rs and CliProxyApiQuotaModelKind plus alias/color key helpers in the TUI options component are essentially duplicated; consider extracting a single shared type/API for model kinds, alias/color keys, and defaults to avoid drift.
  • The CliProxyApiQuotaSegment::collect implementation loads Config::load() from disk and performs synchronous HTTP requests on each call, which may block statusline rendering; it would be safer to pass the segment options in from the caller and/or isolate the network/cache refresh on a slower background path with tighter timeouts.
  • ANSI foreground-color formatting logic for the quota output is duplicated between CliProxyApiQuotaSegment and the preview segment builder in PreviewComponent; extracting a small shared helper for applying foreground colors would reduce the chance of inconsistencies (e.g., reset codes) between preview and runtime rendering.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `TrackedModel` logic in `core/segments/cli_proxy_api_quota.rs` and `CliProxyApiQuotaModelKind` plus alias/color key helpers in the TUI options component are essentially duplicated; consider extracting a single shared type/API for model kinds, alias/color keys, and defaults to avoid drift.
- The `CliProxyApiQuotaSegment::collect` implementation loads `Config::load()` from disk and performs synchronous HTTP requests on each call, which may block statusline rendering; it would be safer to pass the segment options in from the caller and/or isolate the network/cache refresh on a slower background path with tighter timeouts.
- ANSI foreground-color formatting logic for the quota output is duplicated between `CliProxyApiQuotaSegment` and the preview segment builder in `PreviewComponent`; extracting a small shared helper for applying foreground colors would reduce the chance of inconsistencies (e.g., reset codes) between preview and runtime rendering.

## Individual Comments

### Comment 1
<location> `src/core/segments/cli_proxy_api_quota.rs:499-508` </location>
<code_context>
+    fn collect(&self, _input: &InputData) -> Option<SegmentData> {
</code_context>

<issue_to_address>
**issue (performance):** Avoid unnecessary Config load and network work when the segment is disabled, and consider the impact of synchronous HTTP/FS calls with relatively long timeouts.

This `collect` implementation always reads the full `Config` and may perform HTTP requests (5s/10s timeouts) plus JSON (de)serialization on every call. A few concrete refinements:

1. Since you already locate `CliProxyApiQuota` via `find(|s| s.id == SegmentId::CliProxyApiQuota)`, you can early-return when the segment is missing or `!segment.enabled` to skip all IO/networking in that case.

2. On cache misses or expired cache, the synchronous HTTP calls can block prompt rendering for several seconds. You could:
   * Lower the default timeouts or make them configurable.
   * Introduce a short-lived negative cache/backoff after failures instead of retrying every invocation.
   * Reuse config passed via `InputData` (if available) to avoid re-reading it from disk on each call.
</issue_to_address>

### Comment 2
<location> `src/ui/components/preview.rs:211-220` </location>
<code_context>
+                                .unwrap_or(default)
+                        };
+
+                        let apply = |text: &str, c: &crate::config::AnsiColor| -> String {
+                            let prefix = match c {
+                                crate::config::AnsiColor::Color16 { c16 } => {
+                                    let code = if *c16 < 8 { 30 + c16 } else { 90 + (c16 - 8) };
+                                    format!("\x1b[{}m", code)
+                                }
+                                crate::config::AnsiColor::Color256 { c256 } => {
+                                    format!("\x1b[38;5;{}m", c256)
+                                }
+                                crate::config::AnsiColor::Rgb { r, g, b } => {
+                                    format!("\x1b[38;2;{};{};{}m", r, g, b)
+                                }
+                            };
</code_context>

<issue_to_address>
**suggestion:** Factor out duplicated ANSI color escape generation logic shared with CliProxyApiQuotaSegment.

This closure duplicates the foreground escape logic in `CliProxyApiQuotaSegment::apply_foreground_color`, which risks subtle divergence if color variants or reset behavior change. Please extract a shared helper (e.g., a small utility in `crate::config`) that converts `AnsiColor` to the correct escape sequence and use it in both places to keep behavior consistent and avoid duplication.

Suggested implementation:

```rust
                        let apply = |text: &str, c: &crate::config::AnsiColor| -> String {

```

```rust
                            let prefix = crate::config::ansi_color_escape_sequence(c);
                            format!("{prefix}{text}\x1b[0m")
                        };

```

To fully implement the refactor and eliminate duplication:

1. In the `crate::config` module (likely `src/config.rs` or similar), add a shared helper:

```rust
pub fn ansi_color_escape_sequence(color: &AnsiColor) -> String {
    match color {
        AnsiColor::Color16 { c16 } => {
            let code = if *c16 < 8 { 30 + c16 } else { 90 + (c16 - 8) };
            format!("\x1b[{}m", code)
        }
        AnsiColor::Color256 { c256 } => {
            format!("\x1b[38;5;{}m", c256)
        }
        AnsiColor::Rgb { r, g, b } => {
            format!("\x1b[38;2;{};{};{}m", r, g, b)
        }
    }
}
```

   - Adjust the function name/signature if your config module structure differs.
   - Ensure `AnsiColor` is in scope (`use crate::config::AnsiColor;` or similar).

2. In `CliProxyApiQuotaSegment::apply_foreground_color` (where the original duplicated logic lives), replace the inline `match` on `AnsiColor` with a call to `crate::config::ansi_color_escape_sequence(color)` and keep/reset behavior (`\x1b[0m`) consistent with how it's done in `preview.rs`.

3. If you prefer the helper to return only the prefix (without reset), adjust the `preview.rs` `apply` closure and `CliProxyApiQuotaSegment::apply_foreground_color` accordingly so both add the reset in a consistent place.
</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.

…ystem

- Add new CliProxyApiQuota segment to display quota usage from CLI Proxy API
  - Supports multiple auth types (antigravity, gemini-cli)
  - Shows quota percentage for Opus, Gemini 3 Pro, and Gemini 3 Flash models
  - Configurable via TUI with alias and color customization
  - Uses caching to minimize API calls

- Enhance theme system with forward-compatible segment merging
  - Add merge_missing_segments() to auto-add new segments to existing configs
  - Centralize default segment config in presets.rs (reduces code duplication)
  - New segments are disabled by default to avoid breaking existing setups

- Code quality improvements (addressing Sourcery AI feedback):
  - Unify TrackedModel type across segment and TUI components (eliminates duplication)
  - Add collect_with_options() to avoid loading Config from disk on each call
  - Extract apply_foreground_color() as shared ANSI color helper
  - Add open_with_value() method to NameInputComponent for pre-filled text inputs
@rere43 rere43 force-pushed the feat/antigravity-quota branch from 9b3daaf to 60e1c9c Compare January 12, 2026 02:54
@rere43
Copy link
Author

rere43 commented Jan 12, 2026

close since not considering cliproxyapi

@rere43 rere43 closed this Jan 12, 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