Files
orca/src/main/codex/codex-context-usage.ts
T
Kelvin AmoabaandBrennan Benson 885d8a0046 feat(native-chat): show context usage in Codex chats (#28084)
* feat(native-chat): show context usage in Codex chats

Codex turn-row writes become journal revisions so the reading survives a turn end.

* test(codex): run the context-usage journal suite under Node

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-10-13 16:47:07 -07:00

73 lines
3.1 KiB
TypeScript

// What a Codex thread reports about its context window, and where that lands in the journal.
import type { AgentSessionContextUsage } from '../../shared/agent-session-context-usage'
import { parseAgentJournalItemKey } from '../../shared/agent-session-journal-item-key'
import { writeAgentJournalTurnRow } from '../native-chat/agent-session-timeline/agent-journal-turn-row-revision'
import type { StructuredAgentSessionEventSink } from '../native-chat/agent-session-wire/structured-agent-session-event-sink'
import { readRecord } from './codex-item-field-readers'
import type { CodexJournalActiveTurns } from './codex-structured-journal-translation-turn-state'
import { readCodexTurnId } from './codex-structured-thread-facts'
import type { CodexRowAttribution } from './codex-subagent-linkage'
function positiveCount(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null
}
/** Codex's own reading of its context on a `thread/tokenUsage/updated` frame, so a report with no
* breakdown; null when it measures nothing. `last.totalTokens` is the size, and the only count
* Codex sets when it restates one after a compaction; `total` is the thread's running spend. */
export function readCodexContextUsage(
params: unknown,
capturedAt: number
): AgentSessionContextUsage | null {
const usage = readRecord(readRecord(params).tokenUsage)
// Every request reads at least the instructions, so zero is no measurement.
const usedTokens = positiveCount(readRecord(usage.last).totalTokens)
if (usedTokens === null) {
return null
}
const windowTokens = positiveCount(usage.modelContextWindow)
if (windowTokens === null) {
// The reading the journal already holds may be measured against another model's window.
return { used: { kind: 'unknown', capturedAt } }
}
return {
used: {
kind: 'report',
// The frame names no model.
model: 'unknown',
usedTokens,
windowTokens,
percentage: Math.round((usedTokens / windowTokens) * 100),
categories: [],
capturedAt
}
}
}
/** Writes the session's own frame to the open turn's row, else the newest, where the frame Codex
* replays on resume belongs. */
export function recordCodexContextUsage(input: {
sink: StructuredAgentSessionEventSink
activeTurns: Pick<CodexJournalActiveTurns, 'current' | 'has'>
attributionFor: CodexRowAttribution
threadId: string
params: unknown
capturedAt: number
}): void {
const contextUsage = readCodexContextUsage(input.params, input.capturedAt)
if (!contextUsage) {
return
}
const turnId = readCodexTurnId(input.params) ?? input.activeTurns.current(input.threadId)
const openTurnId = turnId && input.activeTurns.has(input.threadId, turnId) ? turnId : null
const scope = openTurnId ? input.attributionFor(input.threadId, openTurnId).turnScope : null
const identity = scope?.kind === 'turn' ? parseAgentJournalItemKey(scope.turnItemId) : null
writeAgentJournalTurnRow(
input.sink,
identity ? { identity } : { newest: true },
{ contextUsage },
{ publish: true }
)
}