Skip to content

expose per-message tokens/cost + a real 'list all base models' surface #153

Description

@adrianipopescu

What

Two closely related asks for the assistant subcommand, both blocking downstream tooling on 0.16.0:

  1. Re-expose per-message token counts and $ cost in assistant --stream --stream-output json events and in assistant thread get <id> output.
  2. Ship a first-class "list all base models available to this account" command that works without pre-existing custom assistants.

Context

I run a small OpenAI-compatible shim in front of kagi assistant --stream --stream-output json so that Bifrost / Open WebUI / anything speaking POST /v1/chat/completions can route through my Kagi Assistant. The shim needs three pieces of data out of the CLI:

  • Every call: prompt/completion token counts + upstream $ cost, so the shim can populate the OpenAI usage envelope and export a per-model cost counter for Grafana.
  • Startup: the full base-model catalog, so GET /v1/models returns the real slug set (not just ki_quick / ki_research / ki_deep_research).

On 0.8.0 the metadata was in message.metadata_html on the final stream event — parseable, though HTML — so I could reconstruct tokens and cost per call. On 0.16.0 those fields are gone from both --stream-output json and assistant thread get, so downstream callers see usage: {prompt_tokens: 0, completion_tokens: 0, total_tokens: 0} for every reply. I'm currently faking it with ceil(len_utf8 / 4), which is fine for trending but useless for reconciliation.

Repro (tokens)

$ echo hi | kagi assistant --model ki_quick --stream --stream-output json | tail -1 | jq
{
  "meta": {},
  "thread": { ... },
  "message": {
    "id": "...",
    "state": "done",
    "prompt": "hi",
    "reply_html": "...",
    "markdown": "Hi there! ...",
    "documents": [],
    "profile": { "model_name": "ki_quick", "model_version": "ki_quick-2026-05-22" }
  },
  "md_delta": "..."
}

No metadata_html, no usage, no tokens, no cost. assistant thread get <id> is the same story.

Repro (model catalog)

$ kagi assistant models
{"models":[]}

$ kagi assistant custom list
[]

assistant models documents itself as "List Assistant base-model slugs available to custom assistants". On my Ultimate account with no saved custom assistants, it returns an empty list — even though kagi assistant --model claude-4-5-sonnet (etc.) works. There's no other exposed way to enumerate the full model catalog, so shims either hard-code a list that goes stale or scrape the settings HTML.

Auth is fine (kagi auth check passes; assistant thread list returns the account's real threads; prompts stream successfully). This isn't a plan gap, it's a missing surface.

Suggested

Tokens + cost:

  • Add usage: {prompt_tokens, completion_tokens, total_tokens, cost_usd} to the final state:"done" stream event and to assistant thread get <id>'s messages. Kagi's own UI shows these numbers per message, so the API is producing them — CLI just needs to surface.
  • Even a minimal cost_usd per call would be enough for cost telemetry; token counts are a bonus.

Model catalog:

  • kagi assistant models --all (or a rename to kagi assistant base-models) that lists every model slug the account can use with --model, independent of whether custom assistants exist.
  • Include labels + optional per-model pricing if the API returns it, so shims don't have to maintain a separate table.

Workaround:

  • Shim: hard-coded fallback catalog (30-ish slugs), token estimation from char length, static per-model pricing table, /metrics Prometheus endpoint counting requests + estimated USD per model. Works but drifts as Kagi adds/renames models.

Happy to test a preview build or send a PR if you can point me at the code paths.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions