Skip to content

ACP session/prompt result omits token usage — ACP hosts can't meter token costs #812

Description

@maxivillus

Summary

The ACP server (src/cli/acp.rs) runs jcode as an ACP (Agent Communication Protocol) backend, but the session/prompt response only contains stopReason. Provider token usage that the daemon already emits as ServerEvent::TokenUsage is dropped, so ACP hosts that meter token consumption (cost dashboards, quotas, billing attribution) report 0 tokens for every jcode-driven task.

Context

jcode's daemon protocol emits token accounting per provider request:

ServerEvent::TokenUsage {
    input: u64,
    output: u64,
    cache_read_input: Option<u64>,
    cache_creation_input: Option<u64>,
}

The ACP layer (AcpRuntime::run_prompt in src/cli/acp.rs) consumes events via session.read_event() but ignores TokenUsage, and finishes the RPC with:

{ "stopReason": "end_turn" }

ACP hosts in the hermes family (e.g. Multica) read token counts from the session/prompt result — top-level usage with inputTokens/outputTokens/cachedReadTokens/cacheWriteTokens, or _meta.usage — and persist them per task. With jcode those hosts see an empty usage map, so cost dashboards stay at zero.

Reproduction

  1. Run jcode acp.
  2. initializesession/newsession/prompt.
  3. Inspect the JSON-RPC result: no usage field regardless of how many tokens the provider streamed.

Proposed change (implemented and verified locally)

Aggregate ServerEvent::TokenUsage over the turn in run_prompt and include it in the result, plus _meta.modelId from the last ServerEvent::ModelChanged for usage attribution:

{
  "stopReason": "end_turn",
  "usage": {
    "inputTokens": 5777,
    "outputTokens": 121,
    "cachedReadTokens": 3072,
    "cacheWriteTokens": 0
  },
  "_meta": { "modelId": "deepseek-v4-flash" }
}

Key points:

  • Omit the usage block when all counters are zero (keeps the response unchanged for providers without usage).
  • cachedReadTokens maps from cache_read_input, cacheWriteTokens from cache_creation_input.
  • Track the last ModelChanged during the turn for _meta.modelId; leave it absent when none arrived.

Environment

  • jcode v0.68.0, Linux x86_64, DeepSeek provider (OpenAI-compatible chat completions).
  • Verified end-to-end: Multica's hermes-family ACP client now records input_tokens / output_tokens / cache_read_tokens for jcode agents.

Metadata

Metadata

Assignees

No one assigned

    Labels

    autonomous: likelyProbably hands-off: clearly worth fixing, agent can do it, minor judgment needed.bugSomething isn't workingtriage: reproducibleClear repro + clear fix path

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions