Every chat session now runs through the Hermes bridge, so the three chat chains become feature-equivalent instead of three partially-maintained dispatch paths. Keeping the coding-agent and in-house agent runtimes alive meant every bridge change had to be mirrored into two more runtimes that had already drifted. Historical rows are read, not migrated: sessions stored with the retired source normalize to cli on load, workflow graphs naming the retired agents keep loading and run on the bridge, and the visible-source check plus SOURCE_LABELS still recognize the old value so those sessions stay listed. The public surface follows the code: the OpenAPI generator and the MCP chat-run tool schema drop the six retired entry fields so external callers are not told about runtimes that no longer exist. harness-check gains a RETIRED_RUNTIME scan so the residual check is a mechanical gate instead of a manual grep, with the two surviving read-side compat sites recorded with reasons. Refs: docs/phases/phase_003.md
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
import { DatabaseSync } from 'node:sqlite'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
let db: DatabaseSync
|
|
|
|
vi.mock('../../packages/server/src/db/index', () => ({
|
|
isSqliteAvailable: () => true,
|
|
getDb: () => db,
|
|
jsonSet: vi.fn(),
|
|
jsonGet: vi.fn(),
|
|
jsonGetAll: vi.fn(),
|
|
jsonDelete: vi.fn(),
|
|
}))
|
|
|
|
describe('usage store agent breakdown', () => {
|
|
beforeEach(() => {
|
|
db = new DatabaseSync(':memory:')
|
|
db.exec(`
|
|
CREATE TABLE session_usage (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
session_id TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT '',
|
|
agent TEXT NOT NULL DEFAULT '',
|
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
|
api_calls INTEGER NOT NULL DEFAULT 0,
|
|
model TEXT NOT NULL DEFAULT '',
|
|
profile TEXT NOT NULL DEFAULT 'default',
|
|
created_at INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
`)
|
|
})
|
|
|
|
afterEach(() => db.close())
|
|
|
|
it('groups ledger rows by their explicit agent type', async () => {
|
|
const now = Date.now()
|
|
const insert = db.prepare(`
|
|
INSERT INTO session_usage (
|
|
session_id, source, agent, input_tokens, output_tokens,
|
|
cache_read_tokens, model, profile, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`)
|
|
insert.run('session-workflow', 'workflow', 'workflow', 100, 10, 40, 'workflow-model', 'default', now)
|
|
insert.run('session-global', 'global_agent', 'global_agent', 80, 20, 30, 'global-model', 'default', now)
|
|
insert.run('hermes-1', 'hermes', 'hermes', 30, 3, 6, 'hermes-model', 'default', now)
|
|
|
|
const { getLocalUsageStats } = await import('../../packages/server/src/db/hermes/usage-store')
|
|
expect(getLocalUsageStats('default', 7).by_agent).toEqual([
|
|
{ agent: 'workflow', input_tokens: 100, output_tokens: 10, cache_read_tokens: 40, cache_write_tokens: 0, reasoning_tokens: 0, sessions: 1 },
|
|
{ agent: 'global_agent', input_tokens: 80, output_tokens: 20, cache_read_tokens: 30, cache_write_tokens: 0, reasoning_tokens: 0, sessions: 1 },
|
|
{ agent: 'hermes', input_tokens: 30, output_tokens: 3, cache_read_tokens: 6, cache_write_tokens: 0, reasoning_tokens: 0, sessions: 1 },
|
|
])
|
|
})
|
|
})
|