Skip to content

Hidden LLM calls (session title / message summaries) are billed by the provider but absent from session cost #46371

Closed as not planned

Description

@greedyivan

Reviving #7175 (auto-closed as not planned after 90 days of inactivity) with billing-level evidence, since the gap is still live and measurable.

What happens: title generation, summary titles, and body summaries make real LLM calls through LLM.stream() but never persist token usage or cost on any message (confirmed empirically in #7175 by @fedealliani and still the case on current versions).

Billing-level confirmation. Reconciling one day of usage against the provider's invoice export (SQLite walk over root + subagent sessions vs billed tokens):

  • Billed requests: 204. Sessions account for 203 (root + 3 subagents, cache_read matched to the exact token).
  • The 204th request is the session-title generation call: 752 input / 498 output tokens, billed, present in no session — it ran at session creation (agent=title, small=true in the logs), before the first user message.

Individually tiny (~$0.003 on a deepseek-v4-pro session), but it's a systematic understatement: every session pays it, and it compounds with the subagent rollup gap (#45417) — for cost analytics on top of opencode data (our use case, and fedealliani's multi-tenant one) any unrecorded call breaks reconciliation.

Suggested direction (happy to send a PR): persist usage/cost for hidden LLM calls — either as a synthetic session-scoped record or accumulated into session.cost with the message count untouched. The usage numbers are already available at the call site since LLM.stream() receives the provider's usage payload.

Related: #45417 (subagent cost rollup — same reconciliation turned up both gaps).

Activity

  1. argszero commented on Aug 31, 2026

    @argszero
    Contributor

    I'd like to work on this.

    I traced the gap in the code. Title generation (packages/core/src/session/title.ts) does publish a SessionEvent.UsageRecorded with source: "title" on step finish, so the token/cost are recorded as events. The problem is on the aggregation side: packages/core/src/session/stats.ts only folds UsageRecorded events into session cost when json_extract(data, '$.source') = 'compaction' (line ~296). The source: "title" records are therefore excluded from the session/stats cost totals, which is exactly the understatement described here.

    The fix is small and scoped to the stats aggregation: include the title source alongside compaction so these hidden title-generation calls show up in session cost/usage.

    I'll implement this against v2 with a regression test and open a PR linked to this issue.

  2. greedyivan commented on Sep 1, 2026

    @greedyivan
    Author

    @argszero Thanks for tracing the aggregation side — the "title" filter exclusion is
    exactly the gap, and it's in #46544 with credit.

    One heads-up on where #46544 went wider: /generate never publishing usage is actually
    documented in the session.generate contract (#38558 — "Durability: no … usage", non-goals
    "unless we explicitly promote them"). #46544 proposes that promotion with a billing argument
    (the call is billed, so cost reconciliation can't see it), adding the event emission next
    to your filter fix plus regression tests for both sides.

    If you'd rather keep the stats change as yours, I'll drop that piece — and if maintainers
    prefer the contract as-is, the title-only part stands on its own.

  3. argszero commented on Sep 1, 2026

    @argszero
    Contributor

    @greedyivan Thank you for picking this up in #46544 and for the credit. I've reviewed the diff — the stats.ts filter change exactly matches the gap I traced (the source: "title" exclusion), and the generate usage recording plus the regression tests on both sides are the right way to handle the #38558 contract promotion.

    I'm deferring my implementation to #46544 — this is the cleaner home for the fix, and it saves a duplicate PR. No need to drop the stats piece; the combined PR is stronger.

    I'll keep the issue linked to #46544. Happy to review/test if maintainers want a second pair of eyes.

  4. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the careful reconciliation against the invoice, it makes the gap very clear.

    From your numbers in #45417 (opencode 1.18.25) and the code paths you cite, this report is about OpenCode v1 (the opencode-ai package). v1 now only gets fixes for critical bugs: data loss, security problems, or crashes with no workaround. A small accounting gap like this is unlikely to be fixed there.

    OpenCode v2 (the @opencode/cli package, versions 2.x) already works the way you suggest. Title generation records its token usage and cost as a session-level usage event (source: "title"). That cost is added to session.cost without creating a message, and background compaction calls are recorded the same way. If you upgrade to v2, your reconciliation should cover the title call.

    Related issues:

    If you see the same problem on v2, or think this is critical for v1, please reply here with the version and details.

  5. opencodejr commented on Oct 29, 2026

    @opencodejr

    Closing this as not planned since there's been no reply. This report is about OpenCode v1 (1.18.25), which only gets fixes for critical bugs. Missing cost for the title call doesn't qualify, since it only affects accounting. OpenCode v2 (@opencode/cli) already records the cost of title generation and background compaction as session-level usage and adds it to session.cost, so upgrading should close this gap in your reconciliation. For the subagent cost gap, see #45417. If you see the same problem on v2, please open a new issue with the version and your reconciliation details. Thanks again for the thorough analysis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions