Skip to content

feat(core,cli): add session and client+session group-by - #543

Merged
junhoyeo merged 2 commits into
junhoyeo:mainfrom
carlosflorencio:feat/group-by-session
May 24, 2026
Merged

feat(core,cli): add session and client+session group-by#543
junhoyeo merged 2 commits into
junhoyeo:mainfrom
carlosflorencio:feat/group-by-session

Conversation

@carlosflorencio

@carlosflorencio carlosflorencio commented May 12, 2026

Copy link
Copy Markdown
Contributor

Why

Tokscale already extracts session_id during parsing for dedup; this PR surfaces it as a group-by strategy on the CLI + JSON output so kandev can spawn tokscale --json --group-by client,session,model once and join each row against its own session id.

Downstream consumers - for example kdlbs/kandev, a multi-agent IDE runner - need to attribute cost to specific agent-CLI sessions, not just to dates or model rollups. Which this grouping we can match ACP sessions with their cost via tokscale.

What changed

  • Added GroupBy::Session (session,model) and GroupBy::ClientSession (client,session,model) to crates/tokscale-core/src/lib.rs:100, including FromStr/Display and aliases (session, session-model, client,session, client-session-model).
  • Added an optional session_id field to ModelUsage populated only for the new variants, and a new aggregate_by_session() + SessionContribution pair in crates/tokscale-core/src/aggregator.rs (mirrors the shape of aggregate_by_date / DailyContribution, sorted by last_seen desc).
  • CLI: --group-by session,model and --group-by client,session,model now emit a top-level sessionId key per JSON row, and the text-mode tables render Client/Session/Provider/Model/Cost columns.
  • TUI: the g picker exposes "Session + Model" and "Client + Session + Model" options.
  • README: documents both strategies and shows an example JSON payload.

Output sample

tokscale models --json --group-by session,model --opencode against the integration-test fixture:

{
  "groupBy": "session,model",
  "entries": [
    {
      "client": "opencode",
      "mergedClients": null,
      "sessionId": "session1",
      "model": "claude-sonnet-4",
      "provider": "anthropic",
      "input": 1800,
      "output": 800,
      "cacheRead": 350,
      "cacheWrite": 80,
      "reasoning": 0,
      "messageCount": 2,
      "cost": 0.08
    },
    {
      "client": "opencode",
      "mergedClients": null,
      "sessionId": "session2",
      "model": "gpt-4o",
      "provider": "openai",
      "input": 600,
      "output": 200,
      "cacheRead": 100,
      "cacheWrite": 20,
      "reasoning": 0,
      "messageCount": 1,
      "cost": 0.02
    }
  ]
}

The sessionId key is omitted entirely (via skip_serializing_if = Option::is_none) for every non-session group-by mode, so existing consumers see no schema change.

Tests

  • Unit: aggregate_by_session() over 10 messages / 3 sessions, top-client tiebreaker, serde round-trip for SessionContribution — in crates/tokscale-core/src/aggregator.rs.
  • Unit: aggregate_model_usage_entries under GroupBy::Session (merges across clients) and GroupBy::ClientSession (keeps clients separate), plus a defensive check that no other variant populates session_id — in crates/tokscale-core/src/lib.rs.
  • CLI integration: --group-by session,model and --group-by client,session,model against the OpenCode fixture, and a negative assertion in test_models_json_with_group_by_model — in crates/tokscale-cli/tests/cli_tests.rs.
  • cargo fmt && cargo clippy --all-targets -- -D warnings && cargo test clean (1258 passed).

Per-provider session parsers, pricing, scanner, and TUI layout were left untouched.

More improvements

  • Codex session_id today is the rollout filename stem (rollout-<ts>-<uuid>), not the canonical UUID from session_meta.payload.id. Worth normalizing in a follow-up so the value matches the ACP session new-response hash.
  • input currently includes cached input tokens. For session attribution it can be useful to split totalInput vs nonCachedInput, or just document the convention in the JSON schema. Applies to every group-by mode; happy to open a separate issue.

Will open follow up PRs for these if its okay.

@vercel

vercel Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
tokscale Ignored Ignored Preview May 24, 2026 2:57pm

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/tokscale-cli/src/tui/ui/dialog/group_by_picker.rs">

<violation number="1" location="crates/tokscale-cli/src/tui/ui/dialog/group_by_picker.rs:56">
P1: Adding Session and ClientSession group-by options causes the picker dialog to overflow: 6 options × 2 rows each = 12 list rows, but the dialog only provides ~9 lines for the list area (14 height - 2 borders - 3 fixed header/divider/hint rows). The last option and part of the second-to-last are clipped and invisible, with no scrolling logic to compensate. Increase the height cap to ~18 and/or add scroll offset logic matching source_picker.rs.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:255">
P2: Documentation inconsistency: `workspace+model` group-by is mentioned in the TUI picker description but missing from the Group-By Strategies table.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread crates/tokscale-cli/src/tui/ui/dialog/group_by_picker.rs
Comment thread README.md

@junhoyeo junhoyeo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

LGTM — clean additive group-by, no data leak across modes. Follow-up: normalize ms/s timestamp convention instead of magnitude heuristic.

Adds GroupBy::Session and GroupBy::ClientSession variants so users can group token usage by session_id from the CLI/JSON output. Downstream runners (e.g. kdlbs/kandev) need to attribute cost to specific agent-CLI sessions, and session_id is already extracted during parsing for dedup -- this surfaces it through aggregation + serialization.
Adding Session/ClientSession brought the picker to 6 options x 2 rows = 12 list rows, exceeding the previous height cap of 14 (only ~9 list rows after borders + header/divider/hint). Bump the cap to 18 to match source_picker so every option stays visible without scrolling, and add the existing Workspace + Model row to the README Group-By Strategies table so the keybinding description and the table agree.
@junhoyeo
junhoyeo force-pushed the feat/group-by-session branch from 422d520 to b8f85f5 Compare May 24, 2026 14:57
@junhoyeo
junhoyeo merged commit ba25408 into junhoyeo:main May 24, 2026
4 checks passed
@junhoyeo

Copy link
Copy Markdown
Owner

@carlosflorencio this has been merged to v3.0.0: https://github.com/junhoyeo/tokscale/releases/tag/v3.0.0 thanks for the contribution!

@carlosflorencio

Copy link
Copy Markdown
Contributor Author

Thanks @junhoyeo !

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