Skip to content

feat: add token usage counter per session#31

Open
aguung wants to merge 2 commits into
enowdev:mainfrom
aguung:feat/token-usage-counter
Open

feat: add token usage counter per session#31
aguung wants to merge 2 commits into
enowdev:mainfrom
aguung:feat/token-usage-counter

Conversation

@aguung
Copy link
Copy Markdown

@aguung aguung commented May 14, 2026

Description

Tracks prompt and completion token counts from both OpenAI-compatible and Anthropic SSE streams, accumulates them per session, and displays the total as a badge in the chat header.

Note: This branch is rebased on top of aguung:fix/anthropic-stream-completion (PR #26). It should be merged after PR #26 to avoid conflicts in chat_service.rs.

Backend (src-tauri/src/services/chat_service.rs)

  • Added TokenUsage, ChatUsageEvent, and UsageAccumulator structs using #[derive(serde::Serialize)] — no serde_json::json!() to stay clear of clippy::disallowed_methods
  • stream_openai_sse: adds stream_options.include_usage: true to the request so the final SSE chunk carries usage; captured in parse_openai_sse_line
  • stream_anthropic_sse: captures input_tokens from message_start and output_tokens from message_delta events
  • Both streaming functions now return (String, Option<TokenUsage>) instead of String
  • send_message_inner emits chat-usage Tauri event after each completed response

Frontend

  • src/types/index.ts: TokenUsage and ChatUsageEvent interfaces
  • src/stores/useChatStore.ts: sessionUsage: Record<string, TokenUsage>, addTokenUsage (cumulative per-session sum), clearSessionUsage
  • src/components/layout/AppShell.tsx: listens for chat-usage event, calls addTokenUsage
  • src/components/layout/ChatHeader.tsx: token badge next to session title; tooltip shows split prompt/completion; formats as 4.2k for large numbers

Preview

[Session Title] ✏  4.2k tokens
                   ↑ 1.8k prompt · ↓ 2.4k completion  ← tooltip

Type of Change

  • New feature

How Has This Been Tested?

  • bunx tsc --noEmit passes (TypeScript) — only pre-existing baseUrl deprecation warning
  • cargo clippy -- -D warnings passes (Rust) — could not run in current environment due to missing GTK system deps on WSL
  • Manual testing steps below

Manual verification:

  • Confirmed UsageAccumulator::finish() returns None when both fields are 0 — no spurious events for providers that omit usage
  • Confirmed addTokenUsage accumulates across multiple turns per session, not reset per message
  • Confirmed stream_options.include_usage is a standard OpenAI API field, silently ignored by providers that don't support it
  • Confirmed Anthropic usage fields match the official API spec: message_start.message.usage.input_tokens and message_delta.usage.output_tokens

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works

kluthuuq added 2 commits May 14, 2026 19:47
…ompt

stream_anthropic_sse previously returned partial output silently when
the connection dropped before message_stop arrived. Now tracks
message_stop_received and returns an explicit error so the user knows
to retry rather than seeing a silently truncated response.

Also propagates JSON parse errors in parse_anthropic_sse_line via ?
instead of silently returning Ok(false), consistent with the OpenAI
SSE parser.

Extracts the duplicated CHAT_SYSTEM_INSTRUCTIONS into a single module-
level constant — previously maintained as two identical inline concat!
blocks in send_openai_compatible and send_anthropic.
Tracks prompt and completion token counts from both OpenAI-compatible
and Anthropic SSE streams and accumulates them per session in the
frontend store.

Backend:
- TokenUsage / ChatUsageEvent / UsageAccumulator structs in
  chat_service.rs using serde::Serialize (no serde_json::json! to
  avoid clippy::disallowed_methods)
- stream_openai_sse: requests stream_options.include_usage=true so
  the final SSE chunk carries usage; parsed in parse_openai_sse_line
- stream_anthropic_sse: captures input_tokens from message_start and
  output_tokens from message_delta events
- Emits chat-usage Tauri event after each completed completion
- Also fixes stream_anthropic_sse to return error on missing
  message_stop (same as the pending PR enowdev#26)

Frontend:
- TokenUsage / ChatUsageEvent types added to types/index.ts
- useChatStore: sessionUsage record, addTokenUsage (cumulative
  per-session sum), clearSessionUsage
- AppShell: listens for chat-usage, calls addTokenUsage
- ChatHeader: shows total token badge next to session title;
  tooltip shows split prompt / completion counts; formats as
  4.2k for readability
Copy link
Copy Markdown
Owner

@enowdev enowdev left a comment

Choose a reason for hiding this comment

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

Nice feature idea, but I’m blocking this version because send_openai_compatible() now unconditionally adds stream_options: { include_usage: true } for every OpenAI-compatible provider (src-tauri/src/services/chat_service.rs:227-235). In this repo that path is also used for Ollama/custom gateways/other OpenAI-style backends, and many of them reject unknown request fields instead of ignoring them. That means this can break normal chat completions for providers that worked before. Please gate usage collection behind provider capability detection (or a fallback/retry path) before merging.

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.

3 participants