Expose turn token usage in the SDK (#4700)

It's present on the event, add it to the final result as well.
This commit is contained in:
pakrym-oai
2025-10-03 17:33:23 -07:00
committed by GitHub
parent a4f1c9d67e
commit 848058f05b
5 changed files with 46 additions and 11 deletions

View File

@@ -9,6 +9,7 @@ export type {
ItemCompletedEvent, ItemCompletedEvent,
ThreadError, ThreadError,
ThreadErrorEvent, ThreadErrorEvent,
Usage,
} from "./events"; } from "./events";
export type { export type {
ThreadItem, ThreadItem,

View File

@@ -1,5 +1,5 @@
import { CodexOptions } from "./codexOptions"; import { CodexOptions } from "./codexOptions";
import { ThreadEvent } from "./events"; import { ThreadEvent, Usage } from "./events";
import { CodexExec } from "./exec"; import { CodexExec } from "./exec";
import { ThreadItem } from "./items"; import { ThreadItem } from "./items";
import { ThreadOptions } from "./threadOptions"; import { ThreadOptions } from "./threadOptions";
@@ -8,6 +8,7 @@ import { ThreadOptions } from "./threadOptions";
export type Turn = { export type Turn = {
items: ThreadItem[]; items: ThreadItem[];
finalResponse: string; finalResponse: string;
usage: Usage | null;
}; };
/** Alias for `Turn` to describe the result of `run()`. */ /** Alias for `Turn` to describe the result of `run()`. */
@@ -85,14 +86,17 @@ export class Thread {
const generator = this.runStreamedInternal(input); const generator = this.runStreamedInternal(input);
const items: ThreadItem[] = []; const items: ThreadItem[] = [];
let finalResponse: string = ""; let finalResponse: string = "";
let usage: Usage | null = null;
for await (const event of generator) { for await (const event of generator) {
if (event.type === "item.completed") { if (event.type === "item.completed") {
if (event.item.type === "agent_message") { if (event.item.type === "agent_message") {
finalResponse = event.item.text; finalResponse = event.item.text;
} }
items.push(event.item); items.push(event.item);
} else if (event.type === "turn.completed") {
usage = event.usage;
} }
} }
return { items, finalResponse }; return { items, finalResponse, usage };
} }
} }

View File

@@ -3,6 +3,22 @@ import http from "node:http";
const DEFAULT_RESPONSE_ID = "resp_mock"; const DEFAULT_RESPONSE_ID = "resp_mock";
const DEFAULT_MESSAGE_ID = "msg_mock"; const DEFAULT_MESSAGE_ID = "msg_mock";
type ResponseCompletedUsage = {
input_tokens: number;
input_tokens_details: { cached_tokens: number } | null;
output_tokens: number;
output_tokens_details: { reasoning_tokens: number } | null;
total_tokens: number;
};
const DEFAULT_COMPLETED_USAGE: ResponseCompletedUsage = {
input_tokens: 42,
input_tokens_details: { cached_tokens: 12 },
output_tokens: 5,
output_tokens_details: null,
total_tokens: 47,
};
type SseEvent = { type SseEvent = {
type: string; type: string;
[key: string]: unknown; [key: string]: unknown;
@@ -157,17 +173,26 @@ export function assistantMessage(text: string, itemId: string = DEFAULT_MESSAGE_
}; };
} }
export function responseCompleted(responseId: string = DEFAULT_RESPONSE_ID): SseEvent { export function responseCompleted(
responseId: string = DEFAULT_RESPONSE_ID,
usage: ResponseCompletedUsage = DEFAULT_COMPLETED_USAGE,
): SseEvent {
const inputDetails = usage.input_tokens_details
? { ...usage.input_tokens_details }
: null;
const outputDetails = usage.output_tokens_details
? { ...usage.output_tokens_details }
: null;
return { return {
type: "response.completed", type: "response.completed",
response: { response: {
id: responseId, id: responseId,
usage: { usage: {
input_tokens: 0, input_tokens: usage.input_tokens,
input_tokens_details: null, input_tokens_details: inputDetails,
output_tokens: 0, output_tokens: usage.output_tokens,
output_tokens_details: null, output_tokens_details: outputDetails,
total_tokens: 0, total_tokens: usage.total_tokens,
}, },
}, },
}; };

View File

@@ -38,6 +38,11 @@ describe("Codex", () => {
}, },
]; ];
expect(result.items).toEqual(expectedItems); expect(result.items).toEqual(expectedItems);
expect(result.usage).toEqual({
cached_input_tokens: 12,
input_tokens: 42,
output_tokens: 5,
});
expect(thread.id).toEqual(expect.any(String)); expect(thread.id).toEqual(expect.any(String));
} finally { } finally {
await close(); await close();

View File

@@ -52,9 +52,9 @@ describe("Codex", () => {
{ {
type: "turn.completed", type: "turn.completed",
usage: { usage: {
cached_input_tokens: 0, cached_input_tokens: 12,
input_tokens: 0, input_tokens: 42,
output_tokens: 0, output_tokens: 5,
}, },
}, },
]); ]);