Skip to content

Commit a55d055

Browse files
authored
Implement session usage updates (#92)
* Implement session usage updates * Reduce changes in package.json * Add token-usage-session-update-multiple.txt to git * Fix test data * Fix test data
1 parent 8db9cd8 commit a55d055

13 files changed

Lines changed: 295 additions & 106 deletions

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@
5050
"vitest": "^4.0.10"
5151
},
5252
"dependencies": {
53-
"@agentclientprotocol/sdk": "^0.12.0",
53+
"@agentclientprotocol/sdk": "^0.16.1",
5454
"vscode-jsonrpc": "^8.2.1",
5555
"open": "^11.0.0",
5656
"diff": "^8.0.3",
57-
"@openai/codex": "^0.114.0"
57+
"@openai/codex": "^0.118.0"
5858
}
5959
}

src/CodexAcpServer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {RateLimitsMap} from "./RateLimitsMap";
2424
import {ModelId} from "./ModelId";
2525
import {AgentMode} from "./AgentMode";
2626
import type {TokenCount} from "./TokenCount";
27+
import {toPromptUsage} from "./TokenCount";
2728
import {CodexCommands} from "./CodexCommands";
2829
import type {QuotaMeta} from "./QuotaMeta";
2930
import {logger} from "./Logger";
@@ -714,6 +715,7 @@ export class CodexAcpServer implements acp.Agent {
714715
logger.log("Prompt handled by a command");
715716
return {
716717
stopReason: "end_turn",
718+
usage: this.buildPromptUsage(sessionState.lastTokenUsage),
717719
_meta: this.buildQuotaMeta(sessionState),
718720
};
719721
}
@@ -751,6 +753,7 @@ export class CodexAcpServer implements acp.Agent {
751753
});
752754
return {
753755
stopReason: "cancelled",
756+
usage: this.buildPromptUsage(sessionState.lastTokenUsage),
754757
_meta: this.buildQuotaMeta(sessionState),
755758
};
756759
}
@@ -763,6 +766,7 @@ export class CodexAcpServer implements acp.Agent {
763766

764767
return {
765768
stopReason: "end_turn",
769+
usage: this.buildPromptUsage(sessionState.lastTokenUsage),
766770
_meta: this.buildQuotaMeta(sessionState),
767771
};
768772
} catch (err) {
@@ -793,6 +797,13 @@ export class CodexAcpServer implements acp.Agent {
793797
};
794798
}
795799

800+
private buildPromptUsage(lastTokenUsage: TokenCount | null): acp.Usage | null {
801+
if (lastTokenUsage == null) {
802+
return null;
803+
}
804+
return toPromptUsage(lastTokenUsage);
805+
}
806+
796807
private async runWithProcessCheck<T>(operation: () => Promise<T>): Promise<T> {
797808
try {
798809
return await operation();

src/CodexEventHandler.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ export class CodexEventHandler {
8787
this.sessionState.currentTurnId = null;
8888
return null;
8989
case "thread/tokenUsage/updated":
90-
this.handleTokenUsageUpdated(notification.params);
91-
return null;
90+
return this.createUsageUpdate(notification.params);
9291
case "item/commandExecution/outputDelta":
9392
return this.createCommandOutputDeltaEvent(notification.params);
9493
case "command/exec/outputDelta":
@@ -383,6 +382,22 @@ export class CodexEventHandler {
383382
this.sessionState.modelContextWindow = params.tokenUsage.modelContextWindow;
384383
}
385384

385+
private createUsageUpdate(params: ThreadTokenUsageUpdatedNotification): UpdateSessionEvent | null {
386+
this.handleTokenUsageUpdated(params);
387+
388+
const used = this.sessionState.totalTokenUsage?.totalTokens;
389+
const size = this.sessionState.modelContextWindow;
390+
if (used == null || size == null || size <= 0) {
391+
return null;
392+
}
393+
394+
return {
395+
sessionUpdate: "usage_update",
396+
used,
397+
size,
398+
};
399+
}
400+
386401
private handleRateLimitsUpdated(params: AccountRateLimitsUpdatedNotification): void {
387402
if (!this.sessionState.rateLimits) {
388403
this.sessionState.rateLimits = new Map();

src/TokenCount.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {Usage} from "@agentclientprotocol/sdk";
12
import type {TokenUsageBreakdown} from "./app-server/v2";
23

34
/**
@@ -33,3 +34,18 @@ export function toTokenCount(usage: TokenUsageBreakdown): TokenCount {
3334
reasoningOutputTokens: usage.reasoningOutputTokens,
3435
};
3536
}
37+
38+
/**
39+
* Maps our per-turn token breakdown to ACP PromptResponse usage fields.
40+
* Cached input tokens are reported as ACP cache reads, and reasoning output
41+
* tokens are exposed through ACP's thoughtTokens field.
42+
*/
43+
export function toPromptUsage(tokenCount: TokenCount): Usage {
44+
return {
45+
totalTokens: tokenCount.totalTokens,
46+
inputTokens: tokenCount.inputTokens,
47+
cachedReadTokens: tokenCount.cachedInputTokens,
48+
outputTokens: tokenCount.outputTokens,
49+
thoughtTokens: tokenCount.reasoningOutputTokens,
50+
};
51+
}

src/__tests__/CodexACPAgent/data/token-usage-cancelled.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
{
22
"stopReason": "cancelled",
3+
"usage": {
4+
"totalTokens": 1500,
5+
"inputTokens": 1200,
6+
"cachedReadTokens": 0,
7+
"outputTokens": 300,
8+
"thoughtTokens": 0
9+
},
310
"_meta": {
411
"quota": {
512
"token_count": {
@@ -23,4 +30,4 @@
2330
]
2431
}
2532
}
26-
}
33+
}

src/__tests__/CodexACPAgent/data/token-usage-end-turn.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
{
22
"stopReason": "end_turn",
3+
"usage": {
4+
"totalTokens": 2500,
5+
"inputTokens": 1500,
6+
"cachedReadTokens": 500,
7+
"outputTokens": 450,
8+
"thoughtTokens": 50
9+
},
310
"_meta": {
411
"quota": {
512
"token_count": {
@@ -23,4 +30,4 @@
2330
]
2431
}
2532
}
26-
}
33+
}

src/__tests__/CodexACPAgent/data/token-usage-multiple-updates.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
{
22
"stopReason": "end_turn",
3+
"usage": {
4+
"totalTokens": 1500,
5+
"inputTokens": 700,
6+
"cachedReadTokens": 500,
7+
"outputTokens": 200,
8+
"thoughtTokens": 100
9+
},
310
"_meta": {
411
"quota": {
512
"token_count": {
@@ -23,4 +30,4 @@
2330
]
2431
}
2532
}
26-
}
33+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
{
22
"stopReason": "end_turn",
3+
"usage": null,
34
"_meta": {
45
"quota": {
56
"token_count": null,
67
"model_usage": []
78
}
89
}
9-
}
10+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[
2+
{
3+
"method": "sessionUpdate",
4+
"args": [
5+
{
6+
"sessionId": "test-session-id",
7+
"update": {
8+
"sessionUpdate": "usage_update",
9+
"used": 1000,
10+
"size": 128000
11+
}
12+
}
13+
]
14+
},
15+
{
16+
"method": "sessionUpdate",
17+
"args": [
18+
{
19+
"sessionId": "test-session-id",
20+
"update": {
21+
"sessionUpdate": "usage_update",
22+
"used": 2000,
23+
"size": 128000
24+
}
25+
}
26+
]
27+
},
28+
{
29+
"method": "sessionUpdate",
30+
"args": [
31+
{
32+
"sessionId": "test-session-id",
33+
"update": {
34+
"sessionUpdate": "usage_update",
35+
"used": 3500,
36+
"size": 128000
37+
}
38+
}
39+
]
40+
}
41+
]

0 commit comments

Comments
 (0)