mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-12 20:07:31 +00:00
feat: add cache-aware token usage dashboard (#1338)
* feat: add cache-aware token usage dashboard Track normalized Claude, Codex, and ACP usage with incremental SQLite backfill. Exclude imported transcript history, rebuild usage after history rewrites, and expose an owner-only dashboard with cache-aware totals and breakdowns. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> * fix: preserve usage model and local dates via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> * fix: normalize cached usage and timezone buckets via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> --------- Co-authored-by: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -373,9 +373,9 @@ describe('codexLocalLauncher', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('tracks explicit and default reasoning effort from local turn context', async () => {
|
||||
it('tracks local turn context and stamps its model on usage', async () => {
|
||||
const transcriptPath = await writeTranscriptMeta('codex-turn-context.jsonl', 'codex-thread-effort');
|
||||
const { session, getModelReasoningEffort, getModelReasoningEffortUpdates } = createSessionStub('default');
|
||||
const { session, agentMessages, getModelReasoningEffort, getModelReasoningEffortUpdates } = createSessionStub('default');
|
||||
let releaseRunBarrier: (() => void) | undefined;
|
||||
harness.runBarrier = new Promise((resolve) => {
|
||||
releaseRunBarrier = resolve;
|
||||
@@ -393,13 +393,13 @@ describe('codexLocalLauncher', () => {
|
||||
type: 'turn_context',
|
||||
payload: { effort: 'max' }
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'event_msg',
|
||||
payload: { type: 'token_count', info: {} }
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'turn_context',
|
||||
payload: { model: 'gpt-5.4' }
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'event_msg',
|
||||
payload: { type: 'token_count', info: {} }
|
||||
})
|
||||
].join('\n') + '\n');
|
||||
await wait(700);
|
||||
@@ -409,6 +409,10 @@ describe('codexLocalLauncher', () => {
|
||||
|
||||
expect(getModelReasoningEffortUpdates()).toEqual(['max', null]);
|
||||
expect(getModelReasoningEffort()).toBeNull();
|
||||
expect(agentMessages).toContainEqual(expect.objectContaining({
|
||||
type: 'token_count',
|
||||
model: 'gpt-5.4'
|
||||
}));
|
||||
});
|
||||
|
||||
it('renders nested Code Mode plans and commands without their covered exec wrapper', async () => {
|
||||
@@ -645,7 +649,14 @@ describe('codexLocalLauncher', () => {
|
||||
[
|
||||
JSON.stringify({ type: 'session_meta', payload: { id: 'codex-thread-import' } }),
|
||||
JSON.stringify({ type: 'event_msg', payload: { type: 'user_message', message: 'old imported prompt' } }),
|
||||
JSON.stringify({ type: 'event_msg', payload: { type: 'agent_message', message: 'old imported message' } })
|
||||
JSON.stringify({ type: 'event_msg', payload: { type: 'agent_message', message: 'old imported message' } }),
|
||||
JSON.stringify({
|
||||
type: 'event_msg',
|
||||
payload: {
|
||||
type: 'token_count',
|
||||
info: { total_token_usage: { input_tokens: 100, output_tokens: 10 } }
|
||||
}
|
||||
})
|
||||
].join('\n') + '\n'
|
||||
);
|
||||
|
||||
@@ -694,7 +705,14 @@ describe('codexLocalLauncher', () => {
|
||||
transcriptPath,
|
||||
[
|
||||
JSON.stringify({ type: 'event_msg', payload: { type: 'user_message', message: 'new local prompt' } }),
|
||||
JSON.stringify({ type: 'event_msg', payload: { type: 'agent_message', message: 'new local response' } })
|
||||
JSON.stringify({ type: 'event_msg', payload: { type: 'agent_message', message: 'new local response' } }),
|
||||
JSON.stringify({
|
||||
type: 'event_msg',
|
||||
payload: {
|
||||
type: 'token_count',
|
||||
info: { total_token_usage: { input_tokens: 120, output_tokens: 12 } }
|
||||
}
|
||||
})
|
||||
].join('\n') + '\n'
|
||||
);
|
||||
await wait(700);
|
||||
@@ -713,6 +731,17 @@ describe('codexLocalLauncher', () => {
|
||||
message: 'new local response',
|
||||
id: expect.any(String)
|
||||
});
|
||||
const tokenMessages = agentMessages.filter((message) => (
|
||||
message as { type?: string }
|
||||
).type === 'token_count') as Array<Record<string, unknown>>;
|
||||
expect(tokenMessages).toHaveLength(2);
|
||||
expect(tokenMessages[0]).toMatchObject({ hapiUsageScope: 'imported-history' });
|
||||
expect(tokenMessages[0]).not.toHaveProperty('thread_id');
|
||||
expect(tokenMessages[1]).toMatchObject({
|
||||
threadId: 'codex-thread-import',
|
||||
thread_id: 'codex-thread-import',
|
||||
hapiUsageScope: 'managed'
|
||||
});
|
||||
});
|
||||
|
||||
it('replays semantic chat and tool events once and keeps a same-turn preface before its plan', async () => {
|
||||
|
||||
Reference in New Issue
Block a user