Skip to content

Commit 8a8068c

Browse files
authored
feat(turns): extract turns from agent-tokens artifact in workflow_run_id mode (#20)
* feat(turns): extract turns from agent-tokens artifact in workflow_run_id mode * docs: document turns field in agent-tokens artifact format * test: add turns to inline CI test and lock file token extraction * test: add manual turns test workflow (workflow_dispatch via gh-aw)
1 parent 05aea40 commit 8a8068c

11 files changed

Lines changed: 1382 additions & 21 deletions

File tree

.github/aw/actions-lock.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
"version": "v8",
66
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
77
},
8+
"github/gh-aw-actions/setup@v0.67.4": {
9+
"repo": "github/gh-aw-actions/setup",
10+
"version": "v0.67.4",
11+
"sha": "9d6ae06250fc0ec536a0e5f35de313b35bad7246"
12+
},
813
"github/gh-aw/actions/setup@v0.55.0": {
914
"repo": "github/gh-aw/actions/setup",
1015
"version": "v0.55.0",

.github/workflows/agent-review.lock.yml

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

.github/workflows/agent-turns-test.lock.yml

Lines changed: 1255 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
name: "Agent: Turns Test"
3+
description: "Manually-triggered agent run to verify turns tracking end-to-end"
4+
5+
engine: claude
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
branch:
11+
description: "Branch to check out (defaults to current)"
12+
required: false
13+
default: ""
14+
15+
timeout-minutes: 10
16+
17+
permissions:
18+
contents: read
19+
pull-requests: read
20+
21+
tools:
22+
bash: ["cat", "head", "tail", "grep", "wc", "ls", "find", "echo"]
23+
github:
24+
toolsets: [repos]
25+
26+
safe-outputs:
27+
noop:
28+
max: 1
29+
---
30+
31+
# Turns Test
32+
33+
You are verifying that the AgentMeter action correctly captures agent turn counts.
34+
35+
## Your Task
36+
37+
1. List the files in the root of this repository using `ls`.
38+
2. Count the number of TypeScript source files in `src/` using `find`.
39+
3. Report your findings using `noop` with a brief summary of what you found.
40+
41+
Keep it short. This is a smoke-test to generate a real agent run with a known turn count.

.github/workflows/agentmeter.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
workflow_run:
55
workflows:
66
- "Agent: Code Review"
7+
- "Agent: Turns Test"
78
types:
89
- completed
910

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,17 @@ The action reads token counts from an `agent-tokens` artifact uploaded by the ag
256256
OUTPUT=$(echo "$RESULT_LINE" | jq -r '.usage.output_tokens // 0')
257257
CACHE_READ=$(echo "$RESULT_LINE" | jq -r '.usage.cache_read_input_tokens // 0')
258258
CACHE_WRITE=$(echo "$RESULT_LINE" | jq -r '.usage.cache_creation_input_tokens // 0')
259+
TURNS=$(echo "$RESULT_LINE" | jq -r '.num_turns // empty')
259260
else
260-
INPUT=0; OUTPUT=0; CACHE_READ=0; CACHE_WRITE=0
261+
INPUT=0; OUTPUT=0; CACHE_READ=0; CACHE_WRITE=0; TURNS=""
262+
fi
263+
if [ -n "$TURNS" ]; then
264+
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s,"turns":%s}\n' \
265+
"$INPUT" "$OUTPUT" "$CACHE_READ" "$CACHE_WRITE" "$TURNS" > /tmp/gh-aw/agent-tokens.json
266+
else
267+
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s}\n' \
268+
"$INPUT" "$OUTPUT" "$CACHE_READ" "$CACHE_WRITE" > /tmp/gh-aw/agent-tokens.json
261269
fi
262-
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s}\n' \
263-
"$INPUT" "$OUTPUT" "$CACHE_READ" "$CACHE_WRITE" > /tmp/gh-aw/agent-tokens.json
264270
265271
- name: Upload token data
266272
if: always()
@@ -282,8 +288,8 @@ If you're not using gh-aw, add these two steps to your agent job to write and up
282288
- name: Write token counts
283289
if: always()
284290
run: |
285-
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s}\n' \
286-
"$INPUT_TOKENS" "$OUTPUT_TOKENS" "$CACHE_READ_TOKENS" "$CACHE_WRITE_TOKENS" \
291+
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s,"turns":%s}\n' \
292+
"$INPUT_TOKENS" "$OUTPUT_TOKENS" "$CACHE_READ_TOKENS" "$CACHE_WRITE_TOKENS" "$TURNS" \
287293
> /tmp/agent-tokens.json
288294
289295
- uses: actions/upload-artifact@v4
@@ -293,7 +299,7 @@ If you're not using gh-aw, add these two steps to your agent job to write and up
293299
path: /tmp/agent-tokens.json
294300
```
295301

296-
Replace `$INPUT_TOKENS` etc. with however your agent exposes token counts (step outputs, env vars, parsed log lines).
302+
Replace `$INPUT_TOKENS` etc. with however your agent exposes token counts (step outputs, env vars, parsed log lines). `turns` is optional — omit it from the JSON if your agent doesn't expose an iteration count.
297303

298304
---
299305

__tests__/workflow-run.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,38 @@ describe('resolveWorkflowRun', () => {
301301
});
302302
});
303303

304+
it('extracts turns from artifact when present', async () => {
305+
const json =
306+
'{"input_tokens":1000,"output_tokens":200,"cache_read_tokens":500,"cache_write_tokens":100,"turns":42}';
307+
const zip = makeTokenZip(json);
308+
309+
const octokit = makeOctokit({
310+
artifacts: [{ name: 'agent-tokens', id: 999 }],
311+
artifactZip: zip,
312+
});
313+
mockGetOctokit.mockReturnValue(octokit as never);
314+
315+
const result = await resolveWorkflowRun(baseArgs);
316+
317+
expect(result.artifactTurns).toBe(42);
318+
});
319+
320+
it('sets artifactTurns to null when turns absent from artifact', async () => {
321+
const json =
322+
'{"input_tokens":1000,"output_tokens":200,"cache_read_tokens":500,"cache_write_tokens":100}';
323+
const zip = makeTokenZip(json);
324+
325+
const octokit = makeOctokit({
326+
artifacts: [{ name: 'agent-tokens', id: 999 }],
327+
artifactZip: zip,
328+
});
329+
mockGetOctokit.mockReturnValue(octokit as never);
330+
331+
const result = await resolveWorkflowRun(baseArgs);
332+
333+
expect(result.artifactTurns).toBeNull();
334+
});
335+
304336
it('defaults output_tokens to 0 when missing from artifact', async () => {
305337
const zip = makeTokenZip(
306338
'{"input_tokens":1000,"cache_read_tokens":500,"cache_write_tokens":100}'

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/run.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export async function run(): Promise<void> {
4949
// timestamps, trigger number, and agent-tokens artifact. This removes the need
5050
// for manual pre-steps in the caller's companion workflow.
5151
let workflowRunTokens: ReturnType<typeof resolveTokens>;
52+
let artifactTurns: number | null = null;
5253
let resolvedTriggerNumber = inputs.triggerNumber ?? ctx.triggerNumber;
5354
let resolvedTriggerEvent = inputs.triggerEvent || ctx.triggerType;
5455
let resolvedTriggerRef: string | null = null;
@@ -102,6 +103,7 @@ export async function run(): Promise<void> {
102103
// companion workflow and would misattribute if used as a fallback here.
103104
resolvedWorkflowName = runData.workflowName;
104105
workflowRunTokens = runData.tokens;
106+
artifactTurns = runData.artifactTurns;
105107
}
106108
}
107109

@@ -155,8 +157,11 @@ export async function run(): Promise<void> {
155157
? Math.max(0, Math.round((endMs - startMs) / 1000))
156158
: 0;
157159

160+
// Priority: explicit input → artifact (workflow_run_id mode) → extracted from agent_output
158161
const resolvedTurns =
159-
inputs.turns ?? (inputs.agentOutput ? extractTurnsFromOutput(inputs.agentOutput) : null);
162+
inputs.turns ??
163+
artifactTurns ??
164+
(inputs.agentOutput ? extractTurnsFromOutput(inputs.agentOutput) : null);
160165

161166
const result = await submitRun({
162167
apiKey: inputs.apiKey,

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ export interface AgentTokensArtifact {
154154
cache_read_tokens: number;
155155
/** Cache write tokens */
156156
cache_write_tokens: number;
157+
/** Number of agent turns/iterations (optional — populated by agent workflows that track it) */
158+
turns?: number;
157159
}
158160

159161
/**

0 commit comments

Comments
 (0)