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:
@@ -9,6 +9,7 @@ export type {
|
|||||||
ItemCompletedEvent,
|
ItemCompletedEvent,
|
||||||
ThreadError,
|
ThreadError,
|
||||||
ThreadErrorEvent,
|
ThreadErrorEvent,
|
||||||
|
Usage,
|
||||||
} from "./events";
|
} from "./events";
|
||||||
export type {
|
export type {
|
||||||
ThreadItem,
|
ThreadItem,
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user