Skip to content

PromptResponse.usage reports only the turn's final request (lastTokenUsage) instead of session-cumulative totalTokenUsage #447

Description

@panghy

Summary

PromptResponse.usage is built from sessionState.lastTokenUsage — the token usage of the final model request of the turn — rather than the session-cumulative sessionState.totalTokenUsage that the adapter already tracks. The result is neither the cumulative-session totals the ACP schema describes nor a per-turn sum: multi-request turns (tool-calling loops) systematically under-report output tokens, and inputTokens reflects the context fill of the last request only.

Where (v1.6.2, tag 9780d31)

  • buildPromptUsage (src/CodexAcpServer.ts:2750-2755) builds the prompt usage from sessionState.lastTokenUsage; it is attached to every prompt response (end_turn / cancelled / terminal-failure — CodexAcpServer.ts:2387, 2587, 2706, 2723).
  • CodexEventHandler.ts:1224-1228 stores both lastTokenUsage = toTokenCount(tokenUsage.last) and totalTokenUsage = toTokenCount(tokenUsage.total) from each thread/tokenUsage/updated notification — the cumulative counter is right there.
  • totalTokenUsage is only ever used to format the /status slash-command text (src/CodexCommands.ts:405); it never reaches any ACP wire surface.

Upstream codex-rs maintains the cumulative sum natively: append_last_usage does total.add_assign(last); last_token_usage = last (protocol/src/protocol.rs:2125-2128, rust-v0.148.0), projected 1:1 into thread/tokenUsage/updated as tokenUsage.total / tokenUsage.last.

Why it matters

The ACP SDK 1.3.0 schema documents Usage fields as session-cumulative: "Total input tokens across all turns", "Sum of all token types across session" (dist/schema/types.gen.d.ts:3050-3085). There is an open debate on whether the session-usage RFD should be clarified to per-turn instead (agentclientprotocol/claude-agent-acp#390) — but last-request semantics matches neither reading: it is not the cumulative session total and not a per-turn sum either, so any client aggregation strategy undercounts.

Concrete downstream impact: intentd (intent-hq/intent) ingests PromptResponse.usage with the schema's cumulative-REPLACE semantics, so codex sessions are undercounted in its token accounting — tracked in intent-hq/intent#3795.

Suggested fix

Send sessionState.totalTokenUsage (already tracked) in PromptResponse.usage. If the RFD lands on per-turn semantics instead, a per-turn sum can be derived by diffing tokenUsage.total snapshots at turn boundaries — either is strictly better than last-request.

Related smaller gaps (same surface, noted while auditing)

  • toTokenCount (src/TokenCount.ts:27-36) drops cacheWriteInputTokens, so cachedWriteTokens is never sent even though codex-rs reports cache_write_input_tokens.
  • codex-rs fill_to_context_window() (protocol.rs:2130-2144) synthesizes usage with only total_tokens set and all breakdown fields zero; after the adapter's transforms such a report carries all-zero breakdowns, which clients reasonably read as "no usage".

Happy to provide the full audit trail (line-cited) if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions