Skip to content

feat(cli): expose pageable agent usage HTTP API - #15205

Merged
kirillk merged 2 commits into
mainfrom
feat/agent-usage-api
Oct 19, 2026
Merged

kirillk merged 2 commits into
mainfrom
feat/agent-usage-api

Conversation

@kirillk

@kirillk kirillk commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Issue

No public issue. This is the HTTP/SDK layer over the aggregation already merged from #15185.

Context

The core now computes per-agent usage (runs, direct step-finish cost, per-message active time, tokens, outcomes) from existing session/message/part rows, but only the server process can reach it. IDE clients have no way to show "how much time and money did this agent spend, and on which sessions" without re-implementing the semantics client-side, which would let JetBrains and VS Code drift apart.

This exposes that aggregation over the Kilo HTTP API so every client renders the same numbers, and regenerates the SDK so they can call it.

Implementation

Two new endpoints in a new Kilo-owned agent-usage group, using the same instance-context, workspace-routing, and authorization middleware as the other Kilo groups:

  • GET /kilocode/agent-usage/summary — one rollup per agent (agent, runs, sessions, cost, activeMs, tokens, errors, lastRunAt). Paging is deliberately ignored here.
  • GET /kilocode/agent-usage/runs — { items, next? }, newest first.

Paging is keyset, not offset, so pages stay correct while runs are being written. The cursor is an opaque base64url token encoding the last item's (startedAt, id), and it becomes a predicate in the aggregation SQL (startedAt < :s OR (startedAt = :s AND id < :i)) rather than a slice of a fully materialized list — a page costs one limit + 1 query, not a whole-project scan. The extra row only decides whether next is returned; there is no next on the last page. id is the run's first assistant message id, so two runs that start in the same millisecond still page individually.

Errors follow the existing declared-error convention: a malformed or non-decodable cursor is a 400 InvalidRequestError with field: "cursor" (not a silent reset), and limit is bounded by the query schema (1–100, default 50), so out-of-range values are rejected before the query runs. An unknown agent filter is an empty page with no next, not an error.

Nothing is recomputed in the HTTP layer: cost, active time, and outcome come straight from the aggregation, so propagated subagent cost is still never double-counted and idle time is still excluded. There is no new table, migration, config-schema, or mode change; removing the endpoints restores prior behavior exactly.

Screenshots / Video

N/A - no visual changes.

How to Test

Manual/local verification

Agent-run:

  • bun test ./test/kilocode/agent-usage ./test/kilocode/server/httpapi-agent-usage.test.ts from packages/opencode/ — 14 passed, 0 failed (51 assertions). Covers keyset paging, same-timestamp tiebreaking, cursor round-trip/rejection, per-agent summary rollups, a full paging sweep that inserts a new run between pages and asserts every pre-insert run appears exactly once, unknown-agent empty page, malformed cursor 400, and limit bounds 400.
  • bun run typecheck from packages/opencode/ — passed.
  • bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip — 336 pass, 0 fail, 0 missing (includes both new routes).
  • bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip — 336 pass, 0 fail; both routes return 401 without credentials.
  • bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip --shards 4 — all shards passed; both routes execute against the real handler.
  • bun run script/check-opencode-annotations.ts --worktree — passed.
  • bun run lint — 0 errors; no new warnings in the added files.
  • ./script/generate.ts — regenerated packages/sdk/openapi.json and src/v2/gen with agentUsage.summary/agentUsage.runs.

Reviewer test steps

  1. From packages/opencode/, run bun run dev serve against a project with some agent history.
  2. curl 'http://127.0.0.1:<port>/kilocode/agent-usage/summary?directory=<project>' and confirm one row per agent.
  3. curl 'http://127.0.0.1:<port>/kilocode/agent-usage/runs?directory=<project>&limit=1', then page with the returned next; confirm runs appear newest-first with no repeats.
  4. curl '.../kilocode/agent-usage/runs?cursor=not-a-cursor' and confirm a 400.

Blocked checks and substitute verification

No blocked checks.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

No changeset: additive internal API with no user-facing behavior change.

Get in Touch

Add GET /kilocode/agent-usage/summary and GET /kilocode/agent-usage/runs so IDE
clients can render one project-scoped agent usage history from the core
aggregation instead of recomputing cost and duration.

Runs page with an opaque cursor over (startedAt, id), applied as a keyset
predicate in the aggregation SQL so pages stay stable while new runs arrive.
Malformed cursors are rejected with a declared 400, and limit is bounded by
the query schema. Summary ignores paging and always returns one rollup per
agent.

Regenerates the SDK with typed agentUsage.summary and agentUsage.runs clients.
@github-actions github-actions Bot added area:cli CLI, TUI and core engine area:sdk SDK labels Oct 17, 2026
@kilo-code-bot

kilo-code-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The prior suggestion is resolved: cursor.encode now projects { startedAt, id } before serializing, so passing a full Run no longer leaks extra fields into the paging token, and the cursor round-trip test asserts the projected shape.

Files Reviewed (2 files)
  • packages/opencode/src/kilocode/agent-usage/index.ts
  • packages/opencode/test/kilocode/agent-usage/agent-usage.test.ts
Previous Review Summary (commit 3d118a2)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 3d118a2)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/kilocode/agent-usage/index.ts 95 cursor.encode serializes the whole Run object handed in by the handler (handlers/agent-usage.ts:38), so the next token embeds sessionID, title, model, cost, tokens, etc. beyond the documented (startedAt, id) key. Round-trips because decode drops extras, but projecting the two fields keeps the token minimal.
Files Reviewed (11 files)
  • packages/opencode/src/kilocode/agent-usage/index.ts - 1 issue
  • packages/opencode/src/kilocode/server/httpapi/groups/agent-usage.ts - no issues
  • packages/opencode/src/kilocode/server/httpapi/handlers/agent-usage.ts - no issues
  • packages/opencode/src/kilocode/server/httpapi/server.ts - no issues
  • packages/opencode/src/server/routes/instance/httpapi/api.ts - no issues
  • packages/opencode/test/kilocode/agent-usage/agent-usage.test.ts - no issues
  • packages/opencode/test/kilocode/server/httpapi-agent-usage.test.ts - no issues
  • packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts - no issues
  • packages/sdk/js/src/v2/gen/sdk.gen.ts - no issues (generated)
  • packages/sdk/js/src/v2/gen/types.gen.ts - no issues (generated)
  • packages/sdk/openapi.json - no issues (generated)

Notes: the keyset predicate (startedAt < :s OR (startedAt = :s AND id < :i)) matches the ORDER BY startedAt DESC, id DESC ordering, id (first assistant message of each run) is unique per run so ties page correctly, the cursor and limit are bound SQL parameters (no injection), and a malformed cursor is surfaced as a declared 400 rather than a silent reset. Both routes reuse the existing instance-context/workspace-routing/authorization middleware.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 39.9K · Output: 3.2K · Cached: 230.3K

Review guidance: REVIEW.md from base branch main

Encode only (startedAt, id) so the opaque next token stays minimal even when
callers pass a full run, and assert the token shape in the cursor test.

@fpliger fpliger 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.

✅

@kirillk
kirillk merged commit 87cc35c into main Oct 19, 2026
46 of 49 checks passed
@kirillk
kirillk deleted the feat/agent-usage-api branch October 19, 2026 16:19
@github-actions

Copy link
Copy Markdown
Contributor

Tracked for the opencode v2 rewrite in #15253 (core-v1-changes-port).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cli CLI, TUI and core engine area:sdk SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants