Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/CodexCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,10 @@ export class CodexCommands {
}
const total = this.formatTokenCount(usage.totalTokens);
const input = this.formatTokenCount(usage.inputTokens);
const cachedInput = this.formatTokenCount(usage.cachedInputTokens);
const cacheRead = this.formatTokenCount(usage.cachedInputTokens);
const cacheWrite = this.formatTokenCount(usage.cacheWriteInputTokens);
const output = this.formatTokenCount(usage.outputTokens);
return `${total} total (${input} input + ${cachedInput} cached input, ${output} output)`;
return `${total} total (${input} input + ${cacheRead} cache read + ${cacheWrite} cache write, ${output} output)`;
}

private formatContextWindow(usage: TokenCount | null, contextWindow: number | null): string {
Expand Down
21 changes: 13 additions & 8 deletions src/TokenCount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,51 @@ import type {TokenUsageBreakdown} from "./app-server/v2";
* Token usage information for a turn.
* This interface decouples our API from Codex's internal types.
*
* [totalTokens]: total number of tokens used (the sum of all other fields)
* [inputTokens]: number of non-cached input tokens
* [cachedInputTokens]: number of cached input tokens
* [totalTokens]: ordinary input + cache reads + cache writes + output
* [inputTokens]: number of ordinary input tokens
* [cachedInputTokens]: number of cache-read input tokens
* [cacheWriteInputTokens]: number of cache-write input tokens
* [outputTokens]: number of output tokens (including reasoning output tokens)
* [reasoningOutputTokens]: number of reasoning output tokens
* [reasoningOutputTokens]: reasoning subset of output tokens
*/
export interface TokenCount {
totalTokens: number;
inputTokens: number;
cachedInputTokens: number;
cacheWriteInputTokens: number;
outputTokens: number;
reasoningOutputTokens: number;
}

/**
* Maps Codex's TokenUsageBreakdown to our TokenCount interface.
* This explicit mapping ensures compile-time errors if Codex changes their types.
* Note: Codex includes cached input tokens in the input token count, so they are subtracted here.
* Note: Codex includes cache reads and writes in the input token count, so they are subtracted here.
*/
export function toTokenCount(usage: TokenUsageBreakdown): TokenCount {
const cacheWriteInputTokens = usage.cacheWriteInputTokens ?? 0;

return {
totalTokens: usage.totalTokens,
inputTokens: usage.inputTokens - usage.cachedInputTokens,
inputTokens: usage.inputTokens - usage.cachedInputTokens - cacheWriteInputTokens,
cachedInputTokens: usage.cachedInputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: usage.outputTokens,
reasoningOutputTokens: usage.reasoningOutputTokens,
};
}

/**
* Maps our per-turn token breakdown to ACP PromptResponse usage fields.
* Cached input tokens are reported as ACP cache reads, and reasoning output
* tokens are exposed through ACP's thoughtTokens field.
* Cache reads and writes are reported through their corresponding ACP fields,
* and reasoning output tokens are exposed through ACP's thoughtTokens field.
*/
export function toPromptUsage(tokenCount: TokenCount): Usage {
return {
totalTokens: tokenCount.totalTokens,
inputTokens: tokenCount.inputTokens,
cachedReadTokens: tokenCount.cachedInputTokens,
cachedWriteTokens: tokenCount.cacheWriteInputTokens,
outputTokens: tokenCount.outputTokens,
thoughtTokens: tokenCount.reasoningOutputTokens,
};
Expand Down
15 changes: 14 additions & 1 deletion src/__tests__/CodexACPAgent/CodexAcpClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1624,8 +1624,17 @@ describe('ACP server test', { timeout: 40_000 }, () => {
const {mockFixture, sessionState} = setupPromptFixture({
lastTokenUsage: {
totalTokens: 41_500,
inputTokens: 40_000,
inputTokens: 39_000,
cachedInputTokens: 1_000,
cacheWriteInputTokens: 1_000,
outputTokens: 500,
reasoningOutputTokens: 100,
},
totalTokenUsage: {
totalTokens: 41_500,
inputTokens: 39_000,
cachedInputTokens: 1_000,
cacheWriteInputTokens: 1_000,
outputTokens: 500,
reasoningOutputTokens: 100,
},
Expand All @@ -1641,6 +1650,9 @@ describe('ACP server test', { timeout: 40_000 }, () => {
expect(mockFixture.getAcpConnectionDump([])).toContain(
"**Context window:** 16% used (41.5K used / 258.4K)",
);
expect(mockFixture.getAcpConnectionDump([])).toContain(
"**Token usage:** 41.5K total (39.0K input + 1.0K cache read + 1.0K cache write, 500 output)",
);
});

it('resets the previous context usage before a model turn', async () => {
Expand All @@ -1649,6 +1661,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
totalTokens: 41_500,
inputTokens: 40_000,
cachedInputTokens: 1_000,
cacheWriteInputTokens: 0,
outputTokens: 500,
reasoningOutputTokens: 100,
},
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/CodexACPAgent/data/token-usage-cancelled.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"totalTokens": 1500,
"inputTokens": 1200,
"cachedReadTokens": 0,
"cachedWriteTokens": 0,
"outputTokens": 300,
"thoughtTokens": 0
},
Expand All @@ -13,6 +14,7 @@
"totalTokens": 1500,
"inputTokens": 1200,
"cachedInputTokens": 0,
"cacheWriteInputTokens": 0,
"outputTokens": 300,
"reasoningOutputTokens": 0
},
Expand All @@ -23,6 +25,7 @@
"totalTokens": 1500,
"inputTokens": 1200,
"cachedInputTokens": 0,
"cacheWriteInputTokens": 0,
"outputTokens": 300,
"reasoningOutputTokens": 0
}
Expand Down
15 changes: 9 additions & 6 deletions src/__tests__/CodexACPAgent/data/token-usage-end-turn.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@
"stopReason": "end_turn",
"usage": {
"totalTokens": 2500,
"inputTokens": 1500,
"inputTokens": 1250,
"cachedReadTokens": 500,
"outputTokens": 450,
"cachedWriteTokens": 250,
"outputTokens": 500,
"thoughtTokens": 50
},
"_meta": {
"quota": {
"token_count": {
"totalTokens": 2500,
"inputTokens": 1500,
"inputTokens": 1250,
"cachedInputTokens": 500,
"outputTokens": 450,
"cacheWriteInputTokens": 250,
"outputTokens": 500,
"reasoningOutputTokens": 50
},
"model_usage": [
{
"model": "model-id",
"token_count": {
"totalTokens": 2500,
"inputTokens": 1500,
"inputTokens": 1250,
"cachedInputTokens": 500,
"outputTokens": 450,
"cacheWriteInputTokens": 250,
"outputTokens": 500,
"reasoningOutputTokens": 50
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"totalTokens": 1500,
"inputTokens": 700,
"cachedReadTokens": 500,
"outputTokens": 200,
"cachedWriteTokens": 0,
"outputTokens": 300,
"thoughtTokens": 100
},
"_meta": {
Expand All @@ -13,7 +14,8 @@
"totalTokens": 1500,
"inputTokens": 700,
"cachedInputTokens": 500,
"outputTokens": 200,
"cacheWriteInputTokens": 0,
"outputTokens": 300,
"reasoningOutputTokens": 100
},
"model_usage": [
Expand All @@ -23,7 +25,8 @@
"totalTokens": 1500,
"inputTokens": 700,
"cachedInputTokens": 500,
"outputTokens": 200,
"cacheWriteInputTokens": 0,
"outputTokens": 300,
"reasoningOutputTokens": 100
}
}
Expand Down
33 changes: 16 additions & 17 deletions src/__tests__/CodexACPAgent/token-usage-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@ describe('Token Usage Events', () => {
totalTokens: 5000,
inputTokens: 4000,
cachedInputTokens: 1000,
cacheWriteInputTokens: 0,
outputTokens: 900,
cacheWriteInputTokens: 500,
outputTokens: 1000,
reasoningOutputTokens: 100,
},
last: {
totalTokens: 2500,
inputTokens: 2000,
cachedInputTokens: 500,
cacheWriteInputTokens: 0,
outputTokens: 450,
cacheWriteInputTokens: 250,
outputTokens: 500,
reasoningOutputTokens: 50,
},
modelContextWindow: 128000,
Expand All @@ -87,24 +87,23 @@ describe('Token Usage Events', () => {
);
});

it('should include token_count in PromptResponse on cancelled', async () => {
it('should include token_count on cancelled with legacy token usage', async () => {
const tokenUsageNotification = createTokenUsageNotification(sessionId, {
// Older Codex binaries can omit cacheWriteInputTokens at runtime.
total: {
totalTokens: 3000,
inputTokens: 2500,
cachedInputTokens: 0,
cacheWriteInputTokens: 0,
outputTokens: 500,
reasoningOutputTokens: 0,
},
} as TokenUsageBreakdown,
last: {
totalTokens: 1500,
inputTokens: 1200,
cachedInputTokens: 0,
cacheWriteInputTokens: 0,
outputTokens: 300,
reasoningOutputTokens: 0,
},
} as TokenUsageBreakdown,
modelContextWindow: 128000,
});

Expand Down Expand Up @@ -146,8 +145,8 @@ describe('Token Usage Events', () => {
modelContextWindow: 128000,
}),
createTokenUsageNotification(sessionId, {
total: { totalTokens: 3500, inputTokens: 2800, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 600, reasoningOutputTokens: 100 },
last: { totalTokens: 1500, inputTokens: 1200, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 200, reasoningOutputTokens: 100 },
total: { totalTokens: 3500, inputTokens: 2800, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 700, reasoningOutputTokens: 100 },
last: { totalTokens: 1500, inputTokens: 1200, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 300, reasoningOutputTokens: 100 },
modelContextWindow: 128000,
}),
];
Expand Down Expand Up @@ -201,16 +200,16 @@ describe('Token Usage Events', () => {
totalTokens: 5000,
inputTokens: 4000,
cachedInputTokens: 1000,
cacheWriteInputTokens: 0,
outputTokens: 900,
cacheWriteInputTokens: 500,
outputTokens: 1000,
reasoningOutputTokens: 100,
},
last: {
totalTokens: 2500,
inputTokens: 2000,
cachedInputTokens: 500,
cacheWriteInputTokens: 0,
outputTokens: 450,
cacheWriteInputTokens: 250,
outputTokens: 500,
reasoningOutputTokens: 50,
},
modelContextWindow: 128000,
Expand All @@ -233,8 +232,8 @@ describe('Token Usage Events', () => {
modelContextWindow: 128000,
}),
createTokenUsageNotification(sessionId, {
total: { totalTokens: 3500, inputTokens: 2800, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 600, reasoningOutputTokens: 100 },
last: { totalTokens: 1500, inputTokens: 1200, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 200, reasoningOutputTokens: 100 },
total: { totalTokens: 3500, inputTokens: 2800, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 700, reasoningOutputTokens: 100 },
last: { totalTokens: 1500, inputTokens: 1200, cachedInputTokens: 500, cacheWriteInputTokens: 0, outputTokens: 300, reasoningOutputTokens: 100 },
modelContextWindow: 128000,
}),
])();
Expand Down