Summary
Kimi Code's ACP adapter does not report token usage to ACP clients. The PromptResponse.usage field is left empty and no usage_update events are emitted on turn.ended. This causes downstream ACP clients (e.g., Multica, OpenCode, Claude Code running kimi as backend) to have zero visibility into token consumption and cost.
Impact
-
Breaking for external orchestration platforms. Multica's runtime usage command shows zero rows for Kimi runtime, while Claude/OpenAI runtimes on the same daemon return full per-model breakdowns. Cost calibration autopilots that read usage data permanently skip Kimi agents.
-
The data exists internally. Kimi Code's internal SDK tracks usage via session.getUsage() and serializes it to wire.jsonl (inputOther/output/inputCacheRead/inputCacheCreation). It just never crosses the ACP boundary.
-
Old Python kimi-cli had the same bug. kimi-cli#2394 reported the identical issue in the legacy Python codebase. This is a known gap.
Root Cause
In packages/acp-adapter/src/session.ts, the turn.ended handler resolves with only stopReason:
// Current (v0.26.0):
resolve({ stopReason: ... })
// No PromptResponse.usage filled
// No usage_update event emitted
The ACP protocol supports two paths for usage reporting:
PromptResponse.usage — maps directly to TurnResponse and is consumed by Multica's kimi adapter (kimi.go L305–L385).
session/update with usage_update — parsed by the shared ACP client with camelCase/snake_case compatibility (hermes.go L988–L1058).
Either path, if filled, would be picked up by existing downstream code immediately—no changes needed in Multica or other ACP consumers.
Proposed Fix
In the turn.ended handler (or a new prompt.ended handler), call session.getUsage() and populate PromptResponse.usage:
const usage = session.getUsage()
resolve({
stopReason: ...,
usage: {
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
// cache fields if tracked:
// cacheReadInputTokens: usage.inputCacheRead,
// cacheCreationInputTokens: usage.inputCacheCreation,
}
})
Alternatively (or additionally), emit a usage_update via session/update notification.
Steps to Reproduce
- Run kimi-code v0.26.0 in ACP mode (
kimi acp)
- Complete any turn (simple prompt or tool-calling turn, doesn't matter)
- Observe the
prompt response — usage field is absent or empty
- Compare with Claude Code / OpenCode in ACP mode — both fill
usage on every turn
Environment
- kimi-code: 0.26.0 (Nix-installed on macOS ARM64, Node.js v24.18.0)
- ACP protocol version: 1.4
- Model: k3 (also reproducible with kimi-for-coding)
Related
Summary
Kimi Code's ACP adapter does not report token usage to ACP clients. The
PromptResponse.usagefield is left empty and nousage_updateevents are emitted onturn.ended. This causes downstream ACP clients (e.g., Multica, OpenCode, Claude Code running kimi as backend) to have zero visibility into token consumption and cost.Impact
Breaking for external orchestration platforms. Multica's
runtime usagecommand shows zero rows for Kimi runtime, while Claude/OpenAI runtimes on the same daemon return full per-model breakdowns. Cost calibration autopilots that read usage data permanently skip Kimi agents.The data exists internally. Kimi Code's internal SDK tracks usage via
session.getUsage()and serializes it towire.jsonl(inputOther/output/inputCacheRead/inputCacheCreation). It just never crosses the ACP boundary.Old Python kimi-cli had the same bug. kimi-cli#2394 reported the identical issue in the legacy Python codebase. This is a known gap.
Root Cause
In
packages/acp-adapter/src/session.ts, theturn.endedhandler resolves with onlystopReason:The ACP protocol supports two paths for usage reporting:
PromptResponse.usage— maps directly toTurnResponseand is consumed by Multica's kimi adapter (kimi.go L305–L385).session/updatewithusage_update— parsed by the shared ACP client with camelCase/snake_case compatibility (hermes.go L988–L1058).Either path, if filled, would be picked up by existing downstream code immediately—no changes needed in Multica or other ACP consumers.
Proposed Fix
In the
turn.endedhandler (or a newprompt.endedhandler), callsession.getUsage()and populatePromptResponse.usage:Alternatively (or additionally), emit a
usage_updateviasession/updatenotification.Steps to Reproduce
kimi acp)promptresponse —usagefield is absent or emptyusageon every turnEnvironment
Related