Skip to content

Commit 3b74d7d

Browse files
authored
docs: add status-values.md and update README status input (#16)
* docs: add status-values.md, update README status input description Made-with: Cursor * fix: include cache writes in cache hit rate denominator cache_writes are tokens processed fresh to populate the cache and belong in the denominator alongside input_tokens. Previous formula ignored them, inflating the hit rate (e.g. 100% instead of ~69%). New formula: reads / (reads + writes + input) Made-with: Cursor * docs: add product screenshots to README Made-with: Cursor * docs: add product screenshot images Made-with: Cursor * chore: remove author
1 parent 3733cf3 commit 3b74d7d

9 files changed

Lines changed: 146 additions & 6 deletions

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ Add this action after your AI agent step to:
2020

2121
The action **never fails your workflow** — all API calls and comment posts use `core.warning()` for errors, not `core.setFailed()`.
2222

23+
<p align="center">
24+
<img src="images/screenshot-agentmeter-github-comment.png" alt="AgentMeter GitHub PR comment showing cost summary" width="700" />
25+
<br/><sub>AgentMeter posts a cost summary directly on the PR.</sub>
26+
</p>
27+
28+
<p align="center">
29+
<img src="images/screenshot-agentmeter-run-detail.png" alt="Run detail with token breakdown" width="48%" />
30+
&nbsp;
31+
<img src="images/screenshot-agentmeter-runs.png" alt="Runs feed dashboard" width="48%" />
32+
</p>
33+
<p align="center">
34+
<sub>Full token breakdown per run &nbsp;·&nbsp; All runs in one dashboard</sub>
35+
</p>
36+
2337
---
2438

2539
## Quickstart
@@ -281,7 +295,7 @@ Replace `$INPUT_TOKENS` etc. with however your agent exposes token counts (step
281295
| `api_key` | ✅ | — | Your AgentMeter API key (`am_sk_…`). Get it from [agentmeter.app/dashboard/settings](https://agentmeter.app/dashboard/settings). |
282296
| `model` | ❌ | `''` | The AI model used (e.g. `claude-sonnet-4-5`). Used for per-token cost display. |
283297
| `engine` | ❌ | `claude` | The AI engine (`claude`, `codex`). |
284-
| `status` | ❌ | `success` | Run status: `success`, `failed`, `timed_out`, `cancelled`, `needs_human`. |
298+
| `status` | ❌ | `success` | Run outcome. In companion `workflow_run` mode this is resolved automatically from the triggering workflow's conclusion. In inline mode pass `${{ steps.agent.outcome }}` or a custom value like `needs_human`. See [docs/status-values.md](docs/status-values.md). |
285299
| `agent_output` | ❌ | `''` | Raw stdout from the agent step. Used to auto-extract token counts from JSON. |
286300
| `input_tokens` | ❌ | `''` | Explicit input token count. Overrides extraction from `agent_output`. |
287301
| `output_tokens` | ❌ | `''` | Explicit output token count. |

__tests__/comment.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,43 @@ describe('buildCommentBody', () => {
163163
expect(body).toContain('approximate');
164164
});
165165

166+
it('calculates cache hit rate using reads / (reads + writes + input)', () => {
167+
const body = buildCommentBody({
168+
apiPricing: testPricing,
169+
existingBody: null,
170+
runData: {
171+
...baseRun,
172+
tokens: {
173+
inputTokens: 50,
174+
outputTokens: 2172,
175+
cacheWriteTokens: 55569,
176+
cacheReadTokens: 124794,
177+
isApproximate: false,
178+
},
179+
},
180+
});
181+
// 124794 / (50 + 55569 + 124794) = 124794 / 180413 ≈ 69%
182+
expect(body).toContain('69% cache hit rate');
183+
});
184+
185+
it('does not show cache hit rate when cacheReadTokens is 0', () => {
186+
const body = buildCommentBody({
187+
apiPricing: testPricing,
188+
existingBody: null,
189+
runData: {
190+
...baseRun,
191+
tokens: {
192+
inputTokens: 1000,
193+
outputTokens: 500,
194+
cacheWriteTokens: 0,
195+
cacheReadTokens: 0,
196+
isApproximate: false,
197+
},
198+
},
199+
});
200+
expect(body).not.toContain('cache hit rate');
201+
});
202+
166203
it('skips token details when tokens are not provided', () => {
167204
const body = buildCommentBody({
168205
apiPricing: testPricing,

action.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
name: 'AgentMeter'
22
description: 'Track token usage and cost for AI agent runs in GitHub Actions'
3-
author: 'Foo.software'
43
branding:
54
icon: 'zap'
65
color: 'green'

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.

docs/status-values.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Status Values
2+
3+
Documents how the `status` input works in the AgentMeter Action and what values are valid.
4+
5+
---
6+
7+
## How status is determined
8+
9+
### Companion workflow mode (`workflow_run` trigger)
10+
11+
When `workflow_run_id` is set, status is resolved **automatically** from the GitHub Actions conclusion of the triggering workflow. The user does not need to pass `status` — the action reads it from the `workflow_run` event payload and normalizes it internally via `normalizeConclusion()`.
12+
13+
```yaml
14+
- uses: foo-software/agentmeter-action@main
15+
with:
16+
api_key: ${{ secrets.AGENTMETER_API_KEY }}
17+
workflow_run_id: ${{ github.event.workflow_run.id }}
18+
# status is resolved automatically — no need to set it
19+
```
20+
21+
### Inline mode (direct / same-workflow)
22+
23+
When running inline (no `workflow_run_id`), the user passes `status` explicitly. It defaults to `'success'` if omitted.
24+
25+
```yaml
26+
- uses: foo-software/agentmeter-action@main
27+
if: always()
28+
with:
29+
api_key: ${{ secrets.AGENTMETER_API_KEY }}
30+
status: ${{ steps.agent.outcome }}
31+
```
32+
33+
---
34+
35+
## Built-in GitHub conclusion mappings
36+
37+
`normalizeConclusion()` maps GitHub's standard conclusion strings to AgentMeter's internal status enum:
38+
39+
| GitHub conclusion | AgentMeter status | Notes |
40+
|---|---|---|
41+
| `success` | `success` | |
42+
| `failure` | `failed` | |
43+
| `timed_out` | `timed_out` | |
44+
| `cancelled` | `cancelled` | |
45+
| `skipped` | *(not ingested)* | Run is skipped entirely — nothing is sent to the API |
46+
47+
**Source of truth:** `normalizeConclusion()` in `src/workflow-run.ts`.
48+
49+
---
50+
51+
## Custom statuses
52+
53+
Any value **not** in the mapping table above is passed through to the API unchanged. This is intentional — unrecognized values are preserved so custom statuses are not silently replaced with `failed`.
54+
55+
### `needs_human`
56+
57+
The primary custom status. Use it when an agent run completes but requires human review before the result can be acted on (e.g. low-confidence output, a tool call was blocked, or the agent explicitly flagged escalation).
58+
59+
**Example — conditionally set based on an agent output flag:**
60+
61+
```yaml
62+
- uses: anthropics/claude-code-action@v1
63+
id: agent
64+
with:
65+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
66+
prompt: "..."
67+
68+
- uses: foo-software/agentmeter-action@main
69+
if: always()
70+
with:
71+
api_key: ${{ secrets.AGENTMETER_API_KEY }}
72+
model: claude-sonnet-4-5
73+
status: ${{ steps.agent.outputs.needs_human == 'true' && 'needs_human' || job.status }}
74+
```
75+
76+
When `steps.agent.outputs.needs_human` is `'true'`, the run is recorded as `needs_human`. Otherwise it falls back to the job's actual status (`success` or `failure`).
77+
78+
---
79+
80+
## All valid AgentMeter status values
81+
82+
| Value | Set via action | Description |
83+
|---|---|---|
84+
| `success` | ✅ | Agent run completed successfully |
85+
| `failed` | ✅ | Agent run failed |
86+
| `timed_out` | ✅ | Agent run exceeded its time limit |
87+
| `cancelled` | ✅ | Agent run was cancelled |
88+
| `needs_human` | ✅ | Run completed but requires human review |
89+
| `running` | ❌ internal only | Run is currently in progress — not settable via action |
269 KB
Loading
135 KB
Loading
272 KB
Loading

src/comment.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,11 @@ function buildTokenDetails({
155155
const { tokens, model, turns } = run;
156156
if (!tokens) return null;
157157

158+
// Cache hit rate = reads / (reads + writes + input). Cache writes are tokens processed
159+
// fresh to populate the cache — they belong in the denominator alongside input tokens.
160+
const totalPromptTokens = tokens.cacheReadTokens + tokens.cacheWriteTokens + tokens.inputTokens;
158161
const cacheHitRate =
159-
tokens.cacheReadTokens + tokens.inputTokens > 0
160-
? Math.round((tokens.cacheReadTokens / (tokens.cacheReadTokens + tokens.inputTokens)) * 100)
161-
: 0;
162+
totalPromptTokens > 0 ? Math.round((tokens.cacheReadTokens / totalPromptTokens) * 100) : 0;
162163

163164
const pricing = getPricing({ apiPricing, model });
164165
const perM = (count: number, pricePerM: number | null | undefined): string => {

0 commit comments

Comments
 (0)