Repository navigation
fix(usage): record verified Claude ACP per-prompt counters - #1048
Conversation
…ition The manual add form stores only the package spec: the launch command is derived from it and the version label falls back to `custom`. Check that shape through `build_meta`, so a change to either derivation cannot silently stop recording usage for these agents.
Document what version 9 changes in the version list the earlier bumps use, and why 8 is skipped. Also restore the alphabetical order of the `acp` modules and rewrap the dependency note on the two unstable ACP features.
claude-agent-acp relays a message typed in another client of the session (Remote Control) as a `user_message_chunk`, which can arrive while codeg's prompt is still open. It closes the reply so far, so when nothing followed it the turn end found no open part, no steer, and an earlier reply in the turn, and its usage went nowhere: the prompt's whole recorded spend was dropped. A turn end with no open part now puts its usage and model on the last reply of its own prompt in every case, as it already did after a trailing steer. ZCode's turn metrics, which ride on the same turn end, gain the same fix, which the schema 9 note now covers.
A turn end records the model selector's current value. For claude-agent-acp that is an alias such as `opus` or `default`, so every turn whose prompt-response usage is now recorded showed up under the alias: the by-model breakdown listed it verbatim and the API-price estimate counted its tokens as unpriced. Since 0.71.0 the adapter's response splits the prompt's own tally by model id in `_meta.quota.model_usage`, leaving out work the agent started on its own and models with nothing to report. When exactly one model did all of that work, the turn is named by it. When several did, the main loop's model is unknown and the selector value stands. The loop test harness can now answer a prompt with a whole response, not only its usage.
A custom agent on the official Claude adapter runs Claude Code itself. When it shares Claude Code's config directory, each of its sessions is also a native Claude Code session under the same id, and importing local sessions added a second, Claude Code conversation for it. Now that codeg records the adapter's per-prompt usage, that session was counted twice in Token Usage. The import now leaves such a session out, neither inserting it nor bringing back a deleted copy, when a live conversation of a custom agent whose current registration selects the adapter's counters holds the session, or continues it after the adapter could not load it again; the import picker lists it as already imported. Sessions of other adapters, whose usage codeg does not record, import as before. Conversations imported before this change are left as they are. After a plan approval that clears the context, the adapter carries on in a new Claude Code session that no conversation holds, so that session still imports.
|
@Adam-Dalloul thanks for splitting this out of #1029. It's the recording-only piece we hoped for, and several parts of it are careful work:
I went through it and pushed five commits on top of your 009250a to this branch (a fast-forward, no force-push). If you push more changes, pulling first (
Details below, then three smaller notes on the rest of the PR. None of the notes block it. 1. Usage dropped when a relayed message closes the replyclaude-agent-acp forwards a message someone types in another client of the session (Remote Control, from claude.ai or the mobile app) as a Now, when no part is open at the turn end and the prompt has a reply, the turn end's usage and model go on the prompt's last reply whether or not the turn was steered; before, only a steered turn did that (acp_native.rs#L535-L556). A prompt with no reply at all still takes your empty-turn path. ZCode's turn metrics ride on the same turn end, so the fix applies to them as well. Your schema 9 bump covers the change (I extended its note), so there's no new bump. 2. The recorded model is usually the selector's aliasBefore this commit, a turn end recorded the model selector's current value. For claude-agent-acp that's usually one of the picker's aliases, such as The prompt response's 3. Double count with a shared Claude config directoryA custom agent on the official adapter runs Claude Code itself, and for an ordinary new session the adapter uses the ACP session id as Claude's own session id (acp-agent.ts#L11467-L11483, #L11907-L11910). So when the custom agent resolves to the same Claude config directory as the built-in Claude Code (say, neither sets its own The import now leaves such a session out (import_service.rs#L537-L546): it adds no Claude Code row and doesn't restore a deleted one. The picker lists the session as already imported (conversations.rs#L828-L834). A session is left out when a live conversation of a custom agent holds it and that agent's current registration selects the adapter's counters by your What it doesn't cover:
4. The other two commits
Smaller notes (non-blocking)
Tests
If you see any of this differently, I'm happy to change it. |
Custom agents using the official Claude ACP adapter currently lose the usage returned with completed prompts. Record those per-prompt counters with the turn so they remain available after reopening the conversation.
This is the recording-only split from #1029. Native history recovery remains a separate design discussion in #1047.
@agentclientprotocol/claude-agent-acpexecutable is accepted here; display names and custom IDs do not select accounting semantics.The official Claude response reports main-loop usage, not a complete billing ledger. This does not recover historical native files, subagent/autonomous/compaction spend, or stopped/cancelled prompts, even when their later responses include usage. It does not infer token counts from context occupancy or combine native and ACP totals.
Validation: production server/MCP/CLI compile and all-target Clippy pass. Focused tests cover the real typed ACP prompt-response recording path, unsupported cumulative counters, ZCode overlap, paid empty turns, message IDs, transcript projection, and token usage sync. The relevant test groups passed (4 contract/projection, 2 protocol recording, 28 transcript, 42 ACP parser, 46 ZCode, and 52 token/service cases; groups overlap).
An additional isolated HTTP fixture exercised the ordinary custom-agent launch and real usage report: verified Claude counters persisted after reopening, an unrecognized producer was excluded, and both missing and old projection markers triggered an incremental request to rebuild unchanged conversations. The ACP responses were deterministic fixtures; no provider generation or real sign-in was performed.