-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: expose token usage as step outputs #1189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c1a5a3b
78bb3c5
afd55cc
c3ce4da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { readFile, writeFile, access } from "fs/promises"; | |
| import { dirname, join } from "path"; | ||
| import { query } from "@anthropic-ai/claude-agent-sdk"; | ||
| import type { | ||
| SDKAssistantMessage, | ||
| SDKMessage, | ||
| SDKResultMessage, | ||
| SDKUserMessage, | ||
|
|
@@ -14,6 +15,11 @@ export type ClaudeRunResult = { | |
| sessionId?: string; | ||
| conclusion: "success" | "failure"; | ||
| structuredOutput?: string; | ||
| inputTokens?: number; | ||
| outputTokens?: number; | ||
| cacheReadTokens?: number; | ||
| cacheWriteTokens?: number; | ||
| numTurns?: number; | ||
| }; | ||
|
|
||
| const EXECUTION_FILE = `${process.env.RUNNER_TEMP}/claude-execution-output.json`; | ||
|
|
@@ -156,6 +162,10 @@ export async function runClaudeWithSdk( | |
|
|
||
| const messages: SDKMessage[] = []; | ||
| let resultMessage: SDKResultMessage | undefined; | ||
| let totalInputTokens = 0; | ||
| let totalOutputTokens = 0; | ||
| let totalCacheReadTokens = 0; | ||
| let totalCacheWriteTokens = 0; | ||
|
|
||
| try { | ||
| for await (const message of query({ prompt, options: sdkOptions })) { | ||
|
|
@@ -169,6 +179,14 @@ export async function runClaudeWithSdk( | |
| if (message.type === "result") { | ||
| resultMessage = message as SDKResultMessage; | ||
| } | ||
|
|
||
| if (message.type === "assistant") { | ||
| const usage = (message as SDKAssistantMessage).message.usage; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this casting is used above this line, maybe moving out and using the same casting for both branches seems cleaner.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The two casts are for different types in different branches of the same discriminated union -- |
||
| totalInputTokens += usage.input_tokens ?? 0; | ||
| totalOutputTokens += usage.output_tokens ?? 0; | ||
| totalCacheReadTokens += usage.cache_read_input_tokens ?? 0; | ||
| totalCacheWriteTokens += usage.cache_creation_input_tokens ?? 0; | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.error("SDK execution error:", error); | ||
|
|
@@ -205,6 +223,12 @@ export async function runClaudeWithSdk( | |
| const isSuccess = resultMessage.subtype === "success"; | ||
| result.conclusion = isSuccess ? "success" : "failure"; | ||
|
|
||
| result.inputTokens = totalInputTokens; | ||
| result.outputTokens = totalOutputTokens; | ||
| result.cacheReadTokens = totalCacheReadTokens; | ||
| result.cacheWriteTokens = totalCacheWriteTokens; | ||
| result.numTurns = resultMessage.num_turns; | ||
|
|
||
| // Handle structured output | ||
| if (hasJsonSchema) { | ||
| if ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -294,6 +294,24 @@ async function run() { | |
| core.setOutput("structured_output", claudeResult.structuredOutput); | ||
| } | ||
| core.setOutput("conclusion", claudeResult.conclusion); | ||
| if (claudeResult.inputTokens !== undefined) { | ||
| core.setOutput("input_tokens", String(claudeResult.inputTokens)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we really need to transform intro string?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes -- |
||
| } | ||
| if (claudeResult.outputTokens !== undefined) { | ||
| core.setOutput("output_tokens", String(claudeResult.outputTokens)); | ||
| } | ||
| if (claudeResult.cacheReadTokens !== undefined) { | ||
| core.setOutput("cache_read_tokens", String(claudeResult.cacheReadTokens)); | ||
| } | ||
| if (claudeResult.cacheWriteTokens !== undefined) { | ||
| core.setOutput( | ||
| "cache_write_tokens", | ||
| String(claudeResult.cacheWriteTokens), | ||
| ); | ||
| } | ||
| if (claudeResult.numTurns !== undefined) { | ||
| core.setOutput("turns", String(claudeResult.numTurns)); | ||
| } | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error); | ||
| // Only mark as prepare failure if we haven't completed the prepare phase | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's your opinion about having just one const object and updating its props?
I feel it seems more structured but your approach is already fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm suggesting something like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appreciate the suggestion! The flat variables feel more conventional here -- the rest of the codebase uses the same pattern (e.g.
resultMessage,commentId,claudeBranchare all individualletdeclarations rather than grouped objects). A nested object would also require introducing a new type just for internal accumulation, without adding much clarity given these four values flow into separate fields onClaudeRunResultright after.