Skip to content

fix(antigravity-cli): attribute turns whose blob omits responseModel - #1051

Merged
junhoyeo merged 2 commits into
junhoyeo:mainfrom
haunchen:fix/antigravity-cli-model-fallback
Aug 6, 2026
Merged

fix(antigravity-cli): attribute turns whose blob omits responseModel#1051
junhoyeo merged 2 commits into
junhoyeo:mainfrom
haunchen:fix/antigravity-cli-model-fallback

Conversation

@haunchen

@haunchen haunchen commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Problem

tokscale submit aborted on every run against my local data:

Error: pricing is unavailable for submitted token usage: antigravity/unknown (x8)

Eight rows out of 634 token-bearing generations, spread over three of 222 conversations, blocked the entire submission. validate_priced_messages rejects the whole batch when any token-bearing message has neither an authoritative cost nor a pricing hit, so a single unattributable row is enough.

Root cause

parse_gen_metadata reads the model from chatModel.#19 (responseModel) and falls back to the literal string unknown. inferred_provider_from_model cannot place unknown, so the provider falls back to antigravity and the row becomes the unpriceable antigravity/unknown.

Antigravity CLI does not always write #19. Decoding every gen_metadata blob in my conversations directory with the module's own wire-format reader shows the field missing on some continuation and tool turns — the ones that also carry a large cacheRead and a tiny output. Those rows drop a whole group of chatModel fields (1, 2, 8, 10, 16, 19), not #19 alone.

The rows are not information-poor, though. #21 — the model display label — survives on every one of them, and sibling rows in the same database carry the machine id next to the identical label:

2b38a2ed…  idx 0-12  #19="gemini-3-flash-a"  #21="Gemini 3.5 Flash (High)"
           idx 13    #19=<missing>          #21="Gemini 3.5 Flash (High)"
758f622f…  idx 0-3   #19="gemini-3.6-flash"  #21="Gemini 3.6 Flash (High)"
           idx 4-7   #19=<missing>          #21="Gemini 3.6 Flash (High)"
affd2a9d…  idx 0-8   #19="gemini-3.6-flash"  #21="Gemini 3.6 Flash (High)"
           idx 9-11  #19=<missing>          #21="Gemini 3.6 Flash (High)"
           idx 12    #19="gemini-3.6-flash"  #21="Gemini 3.6 Flash (High)"

Fix

Index the conversation before parsing it, then resolve a missing #19 through that index:

  1. the row's own #19;
  2. the #19 seen elsewhere in the same file next to the row's #21 display label;
  3. the file's sole #19, when the row carries no label at all and every row that has one agrees;
  4. otherwise unknown, as before.

A label that identifies no model, or two different ones, resolves to nothing and the row still reports unknown rather than borrowing an id the evidence contradicts. Case 3 is the same idea as antigravity.rs's existing session_model fallback, which the CLI parser never had.

Display labels stay out of pricing. The value handed back is always a #19 machine id read from that same file; #21 is only ever a join key between rows of one conversation. This keeps the rule the alias table already states — labels get renamed (Gemini 3 FlashGemini 3.5 Flash (High)) and could be localized, so they must not become pricing keys. The data shows why a label-to-id table would be the wrong shape here anyway: Gemini 3.5 Flash (High) is the label for the wire string gemini-3-flash-a, and the two track each other only through the alias table.

Indexing the file up front rather than carrying the previous row's model forward also makes recovery independent of row order, and keeps it correct across a mid-conversation model switch — a label that never appeared next to a #19 resolves to nothing instead of inheriting whatever ran last.

Testing

Six unit tests cover recovery by label, recovery when the gap is the first row, alias resolution of a recovered id, a label the file never identified, a label claimed by two models, and the no-label single-model and multi-model cases. They build minimal proto fixtures with the existing enc_varint / enc_len helpers — no real databases are checked in.

cargo fmt --all -- --check is clean, and cargo test --workspace --all-features --no-fail-fast matches the pre-change baseline on every target (tokscale-core --lib goes from 1476 to 1482 for the new tests). My Windows checkout has some pre-existing failures unrelated to this change — the tui::cache legacy-path test, the timezone group in cli_tests, and one Clippy unneeded return in a #[cfg(target_os = "windows")] block in clients.rs — all of which reproduce identically on an unmodified tree at 4.10.0.

End to end against real local data: with the three affected conversations in place, an unmodified build reproduces antigravity/unknown (x8); with this change, tokscale --no-spinner submit --dry-run -c antigravity-cli succeeds, and so does a full-history dry run across every client.

Relation to #1035 and #1044

This removes one concrete cause of unpriced rows rather than changing what submit does when it meets one. #1035 lists two other unpriced ids that this does not touch, and the broader "don't abort the whole batch" request there and in #1021 is blocked on #1044 — so this is orthogonal to both and closes neither.


Summary by cubic

Fixes unpriced rows in antigravity-cli by recovering missing model IDs, so tokscale submit no longer aborts on antigravity/unknown. Adds a safety guard so the “single-model” fallback is used only when every label in the file is identified.

  • Bug Fixes
    • Indexes each conversation to recover missing chatModel.#19 via its #21 display label from the same file; if a row has no label, uses the file’s sole #19 only when every label resolves; otherwise returns unknown. #21 is optional and rows with neither field are handled.
    • Uses display labels only as join keys; pricing always uses the recovered machine ID, and alias resolution still applies.
    • Order-independent and safe across model switches; ambiguous or unidentified labels, or rows with no model fields, return unknown rather than guessing.
    • Adds unit tests for label-based recovery, first-row gaps, alias resolution, ambiguous/unidentified labels, and safe single-/multi-model fallbacks.

Written for commit 73d5937. Summary will update on new commits.

Review in cubic

Antigravity CLI leaves `chatModel.junhoyeo#19` (responseModel) out of some generations — continuation and tool turns, which also carry a large `cacheRead` and a tiny output — while still writing the `junhoyeo#21` display label. Those rows fell back to the literal `unknown`, which `inferred_provider_from_model` cannot place, so they surfaced as `antigravity/unknown`. One unpriceable row aborts the whole submission, so eight such rows across three conversations blocked every submit on this machine.

The rows are not information-poor: sibling rows in the same database carry the machine id next to the identical `junhoyeo#21` label. Index the conversation before parsing it, then resolve a missing `junhoyeo#19` through that index — by display label when the row has one, or through the conversation's sole model when it has neither field. A label that identifies no model, or two, resolves to nothing and the row still reports `unknown` rather than borrowing an id the evidence contradicts.

The recovered value is always a `junhoyeo#19` machine id read from the same file, so display labels stay out of pricing keys — they get renamed (`Gemini 3 Flash` → `Gemini 3.5 Flash (High)`) and could be localized.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
tokscale Ignored Ignored Preview Aug 5, 2026 6:20am

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/tokscale-core/src/sessions/antigravity_cli.rs
Comment thread crates/tokscale-core/src/sessions/antigravity_cli.rs Outdated
… went unidentified

`SessionModels::sole_model` served rows carrying neither `junhoyeo#19` nor `junhoyeo#21` whenever the file named exactly one model. That counted named ids, not models: a conversation where one row carries a display label no row ever identifies ran at least two models while naming only one, and an unlabelled row there could be either. It inherited the single named id and billed a model switch under the wrong model.

Gate the fallback on every label in the file resolving to a machine id, so those rows stay `unknown` — the same rule the label path already followed, now applied to the unlabelled one.

Also mark `junhoyeo#21` optional in the field table. It was present on every row observed so far, but the parser has never required it and the `sole_model` path exists precisely for rows that carry neither field.
@haunchen

haunchen commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Both valid, fixed in 73d5937.

The second one is a real hole and it was my own rule applied inconsistently. sole_model counted named ids, not models: a conversation where one row carries a display label no row ever identifies ran at least two models while naming only one, so an unlabelled row there could be either — and it inherited the single named id. The labelled path already refused to guess in exactly that situation; the unlabelled one did not. The fallback is now gated on every label in the file resolving to a machine id, with a test covering the named-Flash / unidentified-Pro / no-fields case.

On the first: #21 is now marked optional too. It was present on every row I observed, including all the ones missing #19, but the parser has never required it and the sole_model path exists precisely for rows carrying neither field — so the table implying otherwise was misleading. The prose now says both things.

@channprj

channprj commented Aug 6, 2026

Copy link
Copy Markdown

Looks good!

@junhoyeo
junhoyeo merged commit 416721e into junhoyeo:main Aug 6, 2026
20 checks passed
@junhoyeo

junhoyeo commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Shipped in v4.11.0https://github.com/junhoyeo/tokscale/releases/tag/v4.11.0

@haunchen @channprj thanks. Recovering the model from the chatModel #21 display label — after noticing that Antigravity drops a whole field group (1, 2, 8, 10, 16, 19) on continuation and tool turns while #21 survives on every one — was a genuinely good piece of reverse engineering, and the decoding evidence in the description made it easy to trust.

Worth calling out how you handled the review: the P1 about a conversation containing a model switch billing under the wrong model was a real hole, and 73d5937d withholds the single-model fallback when any label is unresolved rather than papering over it. That's the right instinct.

One gap that came out of triaging #1035 afterwards: this covers the CLI parser only. sessions/antigravity.rs:61 still has the bare unknown fallback for the IDE-backed path, so IDE-sourced rows are unaffected. I've asked the reporters there which path their data comes from before filing it separately — if you already know the IDE format well enough to say whether the same #21 trick applies, that'd be useful.

Also relevant now: post-#1053 an unattributable row is excluded with a warning instead of aborting the submission, so a regression here would be quiet rather than loud.

If tokscale has been useful to you, a ⭐ on the repo helps other people find it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants