Skip to content

[FEATURE]: /usage — dedicated session token & cost report (alias /cost) #41915

Open

Description

@afonsoft

Requested feature

Add a /usage slash command (alias /cost) producing a dedicated report of session token consumption and financial cost.

Why

Today cost/token info is only a compact inline line in the sidebar footer (packages/tui/src/feature-plugins/sidebar/context.tsx). A dedicated report would give per-message and cumulative accounting.

Proposed behavior

  • /usage opens a report: per-message tokens (input/output/reasoning/cache), cumulative totals, and estimated cost { amount, currency } (data already tracked, see packages/opencode/src/acp/usage.ts).
  • Showables: per session, per day, or since a date; exportable as markdown.
  • Optionally aggregate across all providers used.

Benefits

  • Financial/cost visibility (LLM spend) without external tooling.
  • Analogous to /cost in other CLIs; complements /context for window size vs /usage for spend.

Activity

  1. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Please review these issues to see if your request has already been addressed or is being tracked elsewhere.

  2. Oloompa commented on Aug 15, 2026

    @Oloompa

    It would be nice to also have reports in native application in order to be able to compare periods usage and analyse impact of configuration decisions

  3. tekram commented on Aug 28, 2026

    @tekram

    I would like to contribute a native TUI version of this, rather than a plugin. A useful first slice would be a compact daily usage block in the existing session sidebar, complementing the native current-session line:

    Today
    $5.58 spent  ·  1.8M tokens
    
    Models
    bedrock/zai.glm-4.7       $1.05 · 1.7M
    bedrock/kimi-k2.5         $0.04 · 65.6K
    ollama/qwen3-coder:30b    $0.00 · 537K
    

    Requirements:

    • Keep the existing current-session context/cost display unchanged.
    • Aggregate all assistant usage for the local calendar day, including zero-cost local providers such as Ollama.
    • Break down by provider/model with cost and token totals.
    • Reset automatically at local midnight.
    • Use the existing native stats aggregation/data path and reactive session events, not a polling plugin.

    I have a working local prototype and can prepare a focused PR with aggregation tests and TUI coverage.

  4. kaanchinar commented on Sep 23, 2026

    @kaanchinar

    It would be pretty useful if this command were added. In addition to pay-as-you-go providers, subscription-based ones should also be supported. I use kimi-code and antigravity-cli from time to time alongside opencode, and their usage report information is much easier to check. Especially when using OpenCode Go or other subscriptions connected to OpenCode, it becomes too annoying to check the 5-hour or weekly limits from the browser.

    I have actually created a small plugin to add this usage information to the TUI and the /usage command, but it currently only shows Kimi.ai and OpenCode Go usage details since I don't have any other subscriptions. (Feel free to send PRs to my plugin if you have other AI providers or pay-as-you-go formats.) https://github.com/kaanchinar/opencode-usage-report

  5. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed request, @afonsoft! You're asking for a dedicated report of a session's spend: tokens per message (input/output/reasoning/cache), running totals and cost, viewable per session, per day or since a date, and exportable as markdown. Maintainers will consider it.

    A few notes on what v2 already has:

    • /usage is already a built-in command in v2. It opens the OpenCode Console usage-limits dialog, so a built-in report would probably need a different name, such as /cost.
    • /stats and opencode stats --days N --cost (or --full) already give totals with an input/output/reasoning/cache breakdown over a time range.

    Until then, a small TUI plugin can give you the per-message session report now. It adds a /cost command (also in the command palette). The command lists every assistant message in the current session and its subagents, with tokens and cost, plus a total. Press ctrl+e in the dialog to write the report as usage-<sessionID>.md in the project directory. It type-checks against the current plugin API, but I haven't tested it in a live TUI.

    Save it as ~/.config/opencode/plugins/cost/tui.ts. OpenCode discovers plugins there automatically, so restart the TUI after saving.

    import { Plugin } from "@opencode/plugin/tui"
    import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode/client"
    
    const usd = (value: number) => `$${value.toFixed(4)}`
    const num = (value: number) => value.toLocaleString("en-US")
    
    type Row = { sessionID: string; message: SessionMessageAssistant }
    
    function rows(context: Plugin.Context, sessionID: string): Row[] {
      return context.data.session
        .family(sessionID)
        .flatMap((id) =>
          context.data.session.message
            .list(id)
            .filter((m: SessionMessageInfo): m is SessionMessageAssistant => m.type === "assistant" && !!m.tokens)
            .map((message) => ({ sessionID: id, message })),
        )
        .sort((a, b) => a.message.time.created - b.message.time.created)
    }
    
    function totals(list: Row[]) {
      return list.reduce(
        (sum, { message: m }) => ({
          input: sum.input + (m.tokens?.input ?? 0),
          output: sum.output + (m.tokens?.output ?? 0),
          reasoning: sum.reasoning + (m.tokens?.reasoning ?? 0),
          read: sum.read + (m.tokens?.cache.read ?? 0),
          write: sum.write + (m.tokens?.cache.write ?? 0),
          cost: sum.cost + (m.cost ?? 0),
        }),
        { input: 0, output: 0, reasoning: 0, read: 0, write: 0, cost: 0 },
      )
    }
    
    function markdown(title: string, list: Row[]) {
      const t = totals(list)
      return [
        `# Usage: ${title}`,
        "",
        "| Time | Model | Input | Output | Reasoning | Cache read | Cache write | Cost (USD) |",
        "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |",
        ...list.map(({ message: m }) =>
          [
            "",
            new Date(m.time.created).toISOString(),
            `${m.model.providerID}/${m.model.id}`,
            num(m.tokens!.input),
            num(m.tokens!.output),
            num(m.tokens!.reasoning),
            num(m.tokens!.cache.read),
            num(m.tokens!.cache.write),
            usd(m.cost ?? 0),
            "",
          ].join(" | ").trim(),
        ),
        `| **Total** | | ${num(t.input)} | ${num(t.output)} | ${num(t.reasoning)} | ${num(t.read)} | ${num(t.write)} | ${usd(t.cost)} |`,
        "",
      ].join("\n")
    }
    
    export default Plugin.define({
      id: "local.cost",
      setup(context) {
        context.keymap.layer(() => ({
          mode: "global",
          commands: [
            {
              id: "local.cost.show",
              title: "Session cost report",
              group: "Session",
              palette: true,
              slash: { name: "cost" },
              async run() {
                const route = context.ui.router.current()
                if (route.type !== "session") {
                  context.ui.toast.show({ message: "Open a session first", variant: "warning" })
                  return
                }
                const family = context.data.session.family(route.sessionID)
                await Promise.all(family.map((id) => context.data.session.message.sync(id)))
                const list = rows(context, route.sessionID)
                const t = totals(list)
                const title = context.data.session.get(route.sessionID)?.title ?? route.sessionID
                await context.ui.dialog.select({
                  title: `Usage · ${usd(t.cost)} · ${num(t.input + t.output + t.reasoning + t.read + t.write)} tokens`,
                  options: [
                    {
                      title: "Total",
                      value: -1,
                      category: "Summary",
                      description: `in ${num(t.input)} · out ${num(t.output)} · reasoning ${num(t.reasoning)} · cache r/w ${num(t.read)}/${num(t.write)}`,
                      footer: usd(t.cost),
                    },
                    ...list.map(({ sessionID, message: m }, index) => ({
                      title: `${new Date(m.time.created).toLocaleTimeString()} ${m.model.id}${sessionID === route.sessionID ? "" : " (subagent)"}`,
                      value: index,
                      category: "Messages",
                      description: `in ${num(m.tokens!.input)} · out ${num(m.tokens!.output)} · reasoning ${num(m.tokens!.reasoning)} · cache r/w ${num(m.tokens!.cache.read)}/${num(m.tokens!.cache.write)}`,
                      footer: usd(m.cost ?? 0),
                    })),
                  ],
                  actions: [
                    {
                      bind: "ctrl+e",
                      title: "export markdown",
                      selection: "none",
                      onTrigger: () => {
                        const dir = context.location?.directory ?? process.cwd()
                        const file = `${dir}/usage-${route.sessionID}.md`
                        void Bun.write(file, markdown(title, list)).then(() =>
                          context.ui.toast.show({ message: `Wrote ${file}`, variant: "success" }),
                        )
                      },
                    },
                  ],
                })
              },
            },
          ],
        }))
      },
    })

    Costs are whatever the provider reports, so providers that report $0 will show $0 here too.

    Related issues:

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

Metadata

Metadata

Assignees

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