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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import * as hermesCli from '../../services/hermes/hermes-cli'
|
||||
import { getConversationDetail, listConversationSummaries } from '../../services/hermes/conversations'
|
||||
import { listSessionSummaries, searchSessionSummaries } from '../../services/hermes/sessions-db'
|
||||
import { listSessionSummaries, searchSessionSummaries } from '../../db/hermes/sessions-db'
|
||||
import { deleteUsage, getUsage, getUsageBatch } from '../../db/hermes/usage-store'
|
||||
import { getModelContextLength } from '../../services/hermes/model-context'
|
||||
import { logger } from '../../services/logger'
|
||||
|
||||
function parseHumanOnly(value: unknown): boolean {
|
||||
@@ -84,9 +86,29 @@ export async function remove(ctx: any) {
|
||||
ctx.body = { error: 'Failed to delete session' }
|
||||
return
|
||||
}
|
||||
deleteUsage(ctx.params.id)
|
||||
ctx.body = { ok: true }
|
||||
}
|
||||
|
||||
export async function usageBatch(ctx: any) {
|
||||
const ids = (ctx.query.ids as string)
|
||||
if (!ids) {
|
||||
ctx.body = {}
|
||||
return
|
||||
}
|
||||
const idList = ids.split(',').filter(Boolean)
|
||||
ctx.body = getUsageBatch(idList)
|
||||
}
|
||||
|
||||
export async function usageSingle(ctx: any) {
|
||||
const result = getUsage(ctx.params.id)
|
||||
if (!result) {
|
||||
ctx.body = { input_tokens: 0, output_tokens: 0 }
|
||||
return
|
||||
}
|
||||
ctx.body = result
|
||||
}
|
||||
|
||||
export async function rename(ctx: any) {
|
||||
const { title } = ctx.request.body as { title?: string }
|
||||
if (!title || typeof title !== 'string') {
|
||||
@@ -102,3 +124,8 @@ export async function rename(ctx: any) {
|
||||
}
|
||||
ctx.body = { ok: true }
|
||||
}
|
||||
|
||||
export async function contextLength(ctx: any) {
|
||||
const profile = (ctx.query.profile as string) || undefined
|
||||
ctx.body = { context_length: getModelContextLength(profile) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user