Skip to content

feat(kimi): estimate session cost from aggregate token usage#889

Merged
wesm merged 2 commits into
kenn-io:mainfrom
Junt184:kimi-cost-estimate
Jun 26, 2026
Merged

feat(kimi): estimate session cost from aggregate token usage#889
wesm merged 2 commits into
kenn-io:mainfrom
Junt184:kimi-cost-estimate

Conversation

@Junt184

@Junt184 Junt184 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Kimi sessions previously showed no cost and never appeared in the usage
views. Kimi wire logs record token counts only as session-level
aggregates (the StatusUpdate path), so individual messages carry no
per-message token_usage and no model identifier. The cost engine
priced 0 tokens per message and produced $0 for the whole session.

This change emits a single session-level ParsedUsageEvent when a Kimi
session exposes only aggregate output tokens, and routes parser-emitted
usage events through processKimi (which previously dropped them).

The cost it produces is explicitly an estimate, not exact billing:

  • Prices come from the LiteLLM catalog (a community-maintained price
    list), not from Kimi/Moonshot directly.
  • The wire logs do not record which model served each turn, and Kimi
    sells several models concurrently at different rates (e.g.
    kimi-k2.7-code, kimi-k2.6, kimi-k2.5), so the per-turn model is
    unknown. The event defaults to moonshot/kimi-k2.6, a recent model
    with a cleanly matchable catalog entry. k2.7 is currently priced only
    under a cloudflare-namespaced key that the provider-conflict rule
    rejects for a moonshot/ model, and it carries the same 0.95/4.0 rate
    as k2.6, so k2.6 is the more robust proxy at an identical price. Bump
    the constant once the catalog gains a cleanly matchable k2.7 entry.
  • Only output tokens are available from these aggregates, so input and
    cache tokens are not priced. The figure is a lower bound that tracks
    output volume rather than a full invoice.

Sessions whose messages already carry per-message token_usage (the
native step.end protocol) are still priced message-by-message and are
skipped here to avoid double counting.

Reviewers may want to look at defaultKimiModel in
internal/parser/kimi.go for the estimation rationale and the
emission/skip logic at the end of ParseKimiSession.

@Junt184 Junt184 force-pushed the kimi-cost-estimate branch from e2469f1 to b726588 Compare June 26, 2026 09:38
Kimi wire logs carry token counts only as session-level aggregates
(StatusUpdate), so individual messages have no per-message token_usage
and no model identifier. The usage/cost engine priced 0 tokens and Kimi
sessions never appeared in the cost views.

Emit one session-level ParsedUsageEvent when a Kimi session exposes only
aggregate output tokens, defaulting the model to a recent, cleanly
matchable Kimi catalog entry (moonshot/kimi-k2.6) so the cost engine can
produce an estimate. The estimate is a lower bound: prices come from the
LiteLLM catalog rather than Kimi directly, the wire logs do not record
which model served each turn, and only output tokens are exposed by the
aggregates (input and cache tokens are not priced). Sessions whose
messages already carry per-message token_usage (native step.end
protocol) are priced message-by-message and skipped to avoid double
counting.

processKimi previously dropped parser-emitted usage events; pass them
through so the events reach the store.
@Junt184 Junt184 force-pushed the kimi-cost-estimate branch from b726588 to c7ae53b Compare June 26, 2026 09:57
@roborev-ci

roborev-ci Bot commented Jun 26, 2026

Copy link
Copy Markdown

roborev: Combined Review (c7ae53b)

Medium issues found; no High or Critical issues reported.

Medium

  • internal/parser/kimi.go:808 - dataVersion was not bumped after adding persisted Kimi usage events. processKimi skips unchanged files whose stored data version is current, so existing synced Kimi sessions will not be reparsed or backfilled and will still show no estimated cost until the source file changes or a forced resync occurs.
    Fix: Bump dataVersion in internal/db/db.go and update the corresponding version guard test/comment so existing archives resync.

  • internal/parser/kimi.go:530 - currentModel now defaults to defaultKimiModel, and step.end assigns that before checking whether the event has its own model. Native per-message usage rows with an explicit event.model but no prior config.update can be stored and priced as the proxy model instead of the actual model.
    Fix: Prefer event.Get("model").Str in the step.end path before falling back to currentModel or defaultKimiModel, and add coverage for that case.


Panel: ci_default_security | Synthesis: codex, 8s | Members: codex_default (codex/default, done, 4m4s), codex_security (codex/security, done, 26s) | Total: 4m38s

Kimi usage event persistence needs a parser data-version bump so unchanged existing archives are reparsed and backfilled through the normal sync path. Without the bump, sessions already stamped current would keep showing no estimated cost until their source file changed.\n\nNative step.end records can also carry the exact model even when no prior config.update exists. Preserving that explicit event model avoids pricing per-message usage rows with the proxy fallback model.
@roborev-ci

roborev-ci Bot commented Jun 26, 2026

Copy link
Copy Markdown

roborev: Combined Review (03e3bd9)

No issues found.


Panel: ci_default_security | Synthesis: codex | Members: codex_default (codex/default, done, 4m57s), codex_security (codex/security, done, 14s) | Total: 5m11s

@wesm wesm merged commit 6f3b6ba into kenn-io:main Jun 26, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants