Skip to content

feat(discord): add /usage slash command backed by kiro-cli ACP usage query#1392

Merged
thepagent merged 4 commits into
mainfrom
feat/discord-usage-command
Jul 13, 2026
Merged

feat(discord): add /usage slash command backed by kiro-cli ACP usage query#1392
thepagent merged 4 commits into
mainfrom
feat/discord-usage-command

Conversation

@chaodu-agent

Copy link
Copy Markdown
Collaborator

Summary

Adds a Discord /usage slash command that reports the backend agent's account-level usage and billing: plan name, credits used vs plan limit, overage charges, and billing cycle reset date.

📊 Usage — KIRO POWER
Credits: 12781.64 / 10000 `██████████` 127% ⚠️
Overage charges: 111.27 USD
Billing cycle resets: 2026-08-01

How it works

kiro-cli exposes its /usage slash command over ACP via the _kiro.dev/commands/execute extension method (verified against kiro-cli 2.12.1). The response carries structured JSON (planName, usageBreakdowns[], overageCharges, …) rather than rendered text.

  • acp/protocol.rsUsageReport/UsageBreakdown types + parse_usage_report(). Only parses success: true responses; respects hasLimit: false (pooled/no-cap enterprise accounts hide the limit and progress bar, mirroring the kiro TUI behavior).
  • acp/connection.rs — stores agentInfo.name from initialize; new get_usage() sends the extension request with the adjacently-tagged TuiCommand shape {"command": "usage", "args": {}}.
  • acp/pool.rsSessionPool::get_usage(thread_id) plumbing (same pattern as set_config_option).
  • discord.rs — registers /usage globally, defers with an ephemeral response (the ACP round-trip can exceed Discord's 3s interaction deadline), then follows up with the formatted report.

Safety notes

  • _kiro.dev/* is a Kiro-specific extension namespace, not part of the ACP spec. A malformed or unrecognized command value is a serde deserialization error in kiro-cli that terminates the whole ACP connection (no JSON-RPC error is returned). get_usage() is therefore gated on the initialize-advertised agent name containing "kiro" and is never retried.
  • Non-kiro backends and threads without an active session get a clear ephemeral error instead.
  • Replies are ephemeral: usage/billing data is only shown to the invoking user.

Testing

  • 7 new unit tests: 4 for parse_usage_report (full report, no-limit sentinel hiding, failure→None, empty breakdowns) and 3 for format_usage_report (over-limit warning, no-limit consumption-only, under-limit bar rendering).
  • cargo test -p openab-core: 641 passed, 1 failed — secrets::tests::resolve_exec_nonzero_exit, verified pre-existing on clean main (f89e9fe), environment-specific, unrelated.
  • cargo clippy -p openab-core -- -D warnings: clean.
  • Wire shape verified live against kiro-cli acp 2.12.1 (initialize → session/new → commands/execute → structured usage JSON).

…query

Adds a Discord /usage slash command that reports the backend agent's
account-level usage and billing (plan, credits consumed vs limit,
overage charges, billing cycle reset).

- protocol.rs: UsageReport/UsageBreakdown types + parse_usage_report()
  for the response of kiro-cli's /usage command executed over ACP.
  Respects hasLimit=false (pooled/no-cap accounts hide the limit).
- connection.rs: store agentInfo.name from initialize; get_usage()
  sends the _kiro.dev/commands/execute extension request with the
  adjacently-tagged TuiCommand shape {command: "usage", args: {}}.
  Gated on the agent name containing "kiro" because a malformed or
  unrecognized shape is a deserialization error that terminates the
  ACP connection rather than returning a JSON-RPC error. Never retried.
- pool.rs: SessionPool::get_usage(thread_id) plumbing.
- discord.rs: register /usage globally, defer ephemeral response
  (ACP round-trip can exceed Discord's 3s deadline), render a compact
  report with progress bar and overage warning.

Non-kiro backends and threads without an active session get a clear
ephemeral error message.
@chaodu-agent chaodu-agent requested a review from thepagent as a code owner July 13, 2026 19:25
@chaodu-agent

This comment has been minimized.

Replace the plain-text reply with a CreateEmbed: plan name as title,
breakdown lines as description, billing cycle reset in the footer, and
a green/red sidebar depending on whether any breakdown is over its
plan limit. Still ephemeral — only the invoking user sees it.

Error paths remain plain-text ephemeral messages.
@chaodu-agent

This comment has been minimized.

Live payload from the pr1392 preview (B0, 2026-07-13) carries
limit=10000.0 but no hasLimit field; the unwrap_or(false) default
dropped the cap and progress bar for an account 130% over limit.
Treat a missing hasLimit as 'has a limit if a numeric limit is
present', keeping the explicit hasLimit=false pooled-account
sentinel behavior. Two regression tests added (one with the exact
captured payload).
@chaodu-agent

This comment has been minimized.

Discord clients render embed descriptions smaller than message
content. Put the report title+body in content (normal font) and keep
a minimal embed as the green/red color strip with the billing-cycle
footer. Pinned by usage_reply_body_in_content_not_embed.
@chaodu-agent

Copy link
Copy Markdown
Collaborator Author

Note

LGTM ✅ — Clean, well-guarded feature addition with solid test coverage and correct Discord interaction flow.

What This PR Does

Adds a Discord /usage slash command that queries account-level usage/billing from kiro-cli's internal _kiro.dev/commands/execute ACP extension and displays a formatted report (plan name, credit consumption, progress bar, overage charges, billing cycle reset) as an ephemeral embed.

How It Works

  1. protocol.rs — new UsageReport/UsageBreakdown types + parse_usage_report() parser that handles the success: true envelope, respects hasLimit: false for enterprise/pooled accounts, and gracefully infers limit presence when hasLimit is omitted entirely (observed live regression).
  2. connection.rs — stores agentInfo.name from initialize; get_usage() gates on the agent name containing "kiro" before sending the extension request (critical: a misformed request kills the whole ACP connection).
  3. pool.rsSessionPool::get_usage() plumbing (same pattern as set_config_option).
  4. discord.rs — registers /usage globally; defers with ephemeral response (ACP round-trip > 3s deadline); follows up with color-coded embed (red when over limit, green otherwise) + billing-cycle footer.

Findings

# Severity Finding Location
1 🟢 Agent-name gate prevents ACP connection kill on non-kiro backends connection.rs:656
2 🟢 Ephemeral defer pattern correctly handles Discord's 3s interaction deadline discord.rs:1688
3 🟢 hasLimit fallback logic handles real-world edge case where field is omitted protocol.rs:307-312
4 🟢 7 focused unit tests covering all rendering branches + edge cases protocol.rs / discord.rs
5 🟢 Report lives in content (normal font) not embed description (small font) — good UX decision discord.rs:2582-2593
Finding Details

🟢 F1: Agent-name gate

The safety note in the PR description correctly identifies the critical risk: a malformed _kiro.dev request is a serde deserialization error that terminates the ACP connection entirely. The .contains("kiro") check (case-insensitive) is a reasonable guard that prevents this code path from firing on non-kiro backends.

🟢 F2: Deferred ephemeral interaction

Follows the same proven pattern as the existing /auth command — defer first, followup after the ACP round-trip. Error paths all surface ⚠️ messages to the user rather than silently failing.

🟢 F3: hasLimit fallback

Real-world edge case: some kiro-cli versions omit hasLimit entirely while still reporting a numeric limit. The unwrap_or_else(|| b.get("limit")…is_some()) fallback correctly infers "has a limit if a numeric limit field is present". This avoids hiding the progress bar when a user is 130% over their cap.

🟢 F4: Test coverage

Tests cover: full report, no-limit enterprise, missing hasLimit with numeric limit (regression), missing both, failure response, empty breakdowns, over-limit formatting, consumption-only formatting, under-limit bar rendering, and embed vs content placement.

🟢 F5: UX — body in content

Discord embeds render description text at a smaller font. Putting the usage numbers in content and using the embed only for the color strip + billing footer is a thoughtful UX choice that improves readability.

Baseline Check
  • PR opened: 2026-07-13
  • Main already has: ACP session pool, config option plumbing, /auth slash command with deferred ephemeral pattern
  • Net-new value: Usage/billing query capability, _kiro.dev/commands/execute extension integration, UsageReport types, /usage Discord command + formatted output
What's Good (🟢)
  • Excellent documentation of the safety constraint (ACP connection kill on malformed extension requests) and the defensive coding that follows from it
  • The hasLimit missing-field regression is caught by a dedicated test — shows live-testing against real payloads
  • Progress bar clamped at 100% visually but percentage shows true value + warning emoji — good information density
  • Consistent with existing codebase patterns (set_config_option plumbing, /auth defer pattern)
  • PR description is exemplary: safety notes, wire shape verification, pre-existing test failure documented

5️⃣ Three Reasons We Might Not Need This PR

  1. kiro-cli already has a /usage TUI command — users can check their billing directly in kiro's own interface. However, Discord operators managing multiple bots benefit from checking usage without leaving the coordination channel.
  2. Extension namespace coupling_kiro.dev/* is not a stable API; any kiro-cli update could break the wire shape silently. The agent-name gate + no-retry policy + clear error messaging mitigates this adequately.
  3. Ephemeral-only limits utility — only the command invoker sees the response, so it can't be used for team-wide billing alerts. However, this is the correct privacy default for billing data; scheduled alerts are a separate feature.

@thepagent thepagent merged commit 4f98196 into main Jul 13, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants