From 6f69c6980211158f9861c84155c431c3033ae5ab Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:14:50 +0800 Subject: [PATCH] feat: add token usage tracking, context display, and dynamic context length (#132) * fix: specify TS_NODE_PROJECT for dev:server script ts-node/register resolves tsconfig from the entry file upward, finding the root solution-style tsconfig.json (no compilerOptions). This causes target to default to ES3, breaking MapIterator spread syntax (TS2802). Set TS_NODE_PROJECT env var to point to the server tsconfig which targets ES2024. Co-Authored-By: Claude Opus 4.6 * feat: add token usage tracking, context display, and dynamic context length - Intercept SSE proxy to capture run.completed events and persist token usage (input_tokens, output_tokens) per session to SQLite/JSON store - Display context usage bar in ChatInput showing used/total/remaining tokens - Resolve actual context length from Hermes models_dev_cache.json based on the active profile's default model (fallback 200K), with 5min in-memory cache - Move sessions-db.ts to db/hermes/ for unified database layer - Add usage store with SQLite + JSON fallback (auto-migration via ensureTable) - Fix proxy SSE path regex to match rewritten upstream path - Fix route ordering: /sessions/usage before /sessions/:id to avoid 404 - Fetch per-session usage on session enter instead of batch - Add unit tests for usage-store, db index, and proxy SSE interception Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- packages/client/src/api/hermes/chat.ts | 5 + packages/client/src/api/hermes/sessions.ts | 23 ++ .../src/components/hermes/chat/ChatInput.vue | 135 +++++++-- .../src/components/hermes/chat/ChatPanel.vue | 65 ----- packages/client/src/i18n/locales/de.ts | 1 + packages/client/src/i18n/locales/en.ts | 1 + packages/client/src/i18n/locales/es.ts | 1 + packages/client/src/i18n/locales/fr.ts | 1 + packages/client/src/i18n/locales/ja.ts | 1 + packages/client/src/i18n/locales/ko.ts | 1 + packages/client/src/i18n/locales/pt.ts | 1 + packages/client/src/i18n/locales/zh.ts | 1 + packages/client/src/stores/hermes/chat.ts | 29 +- .../server/src/controllers/hermes/sessions.ts | 29 +- .../{services => db}/hermes/sessions-db.ts | 2 +- packages/server/src/db/hermes/usage-store.ts | 75 +++++ packages/server/src/db/index.ts | 136 +++++++++ packages/server/src/index.ts | 8 +- .../server/src/routes/hermes/proxy-handler.ts | 169 +++++++++-- packages/server/src/routes/hermes/sessions.ts | 3 + .../src/services/hermes/model-context.ts | 106 +++++++ tests/server/db-index.test.ts | 116 ++++++++ tests/server/proxy-handler.test.ts | 262 +++++++++++++++++- tests/server/sessions-db.test.ts | 8 +- tests/server/sessions-routes.test.ts | 9 + tests/server/usage-store.test.ts | 159 +++++++++++ 26 files changed, 1203 insertions(+), 144 deletions(-) rename packages/server/src/{services => db}/hermes/sessions-db.ts (99%) create mode 100644 packages/server/src/db/hermes/usage-store.ts create mode 100644 packages/server/src/db/index.ts create mode 100644 packages/server/src/services/hermes/model-context.ts create mode 100644 tests/server/db-index.test.ts create mode 100644 tests/server/usage-store.test.ts diff --git a/packages/client/src/api/hermes/chat.ts b/packages/client/src/api/hermes/chat.ts index a6f51cc..d882553 100644 --- a/packages/client/src/api/hermes/chat.ts +++ b/packages/client/src/api/hermes/chat.ts @@ -28,6 +28,11 @@ export interface RunEvent { preview?: string timestamp?: number error?: string + usage?: { + input_tokens: number + output_tokens: number + total_tokens: number + } } export async function startRun(body: StartRunRequest): Promise { diff --git a/packages/client/src/api/hermes/sessions.ts b/packages/client/src/api/hermes/sessions.ts index 240477b..786c8ef 100644 --- a/packages/client/src/api/hermes/sessions.ts +++ b/packages/client/src/api/hermes/sessions.ts @@ -94,3 +94,26 @@ export async function renameSession(id: string, title: string): Promise return false } } + +export async function fetchSessionUsage(ids: string[]): Promise> { + if (ids.length === 0) return {} + const params = new URLSearchParams() + params.set('ids', ids.join(',')) + return request(`/api/hermes/sessions/usage?${params}`) +} + +export async function fetchSessionUsageSingle(id: string): Promise<{ input_tokens: number; output_tokens: number } | null> { + try { + return await request<{ input_tokens: number; output_tokens: number }>(`/api/hermes/sessions/${id}/usage`) + } catch { + return null + } +} + +export async function fetchContextLength(profile?: string): Promise { + const params = new URLSearchParams() + if (profile) params.set('profile', profile) + const query = params.toString() + const res = await request<{ context_length: number }>(`/api/hermes/sessions/context-length${query ? `?${query}` : ''}`) + return res.context_length +} diff --git a/packages/client/src/components/hermes/chat/ChatInput.vue b/packages/client/src/components/hermes/chat/ChatInput.vue index f3e1fcd..dd3ba77 100644 --- a/packages/client/src/components/hermes/chat/ChatInput.vue +++ b/packages/client/src/components/hermes/chat/ChatInput.vue @@ -1,8 +1,11 @@