fix(drift): classify the new Gemini families and resolve Live AUDIO modality from the provider - #392
Merged
Merged
Conversation
…eclared capability
The Gemini /models canary has been red since 2026-08-27 on three unclassified
families. Classified all three EXCLUDE from the provider's declared
capabilities, never from the id string:
gemini-3.5-transcribe input Audio -> output "Text, Word annotations"
(GA 2026-08-26, non-streaming speech-to-text)
gemini-3.5-transcribe-live the Live-API half of the same model. Declares
bidiGenerateContent — proven by the Live leg's
discovery, which filters on that method ALONE and
selected it — and cannot emit AUDIO, proven by
Google refusing the session with code=1007 "The
requested combination of response modalities
(AUDIO) is not supported by the model".
gemini-omni-1.1-flash input "Video with audio" -> output "Video with
audio" (GA 2026-08-27, conversational video
generation and editing)
None emits a chat completion, so none can be text-generation drift. The
substring would have argued the OPPOSITE for the last one: "omni" names
OpenAI's text-capable line, so a name rule would have INCLUDED a video model.
Includes the deliberate excludeFamilies.gemini re-pin, the resolved
drift-proposals notes (superseding bot PRs #388/#389), the three ids in the
offline live /models wave, and behavioural coverage through the real
enumerate->normalize->subtract pipeline — so dropping an entry reddens on what
the classification MEANS, not only on a moved checksum.
… not the listing The Gemini Live leg rested on a premise Google falsified: "every model exposing bidiGenerateContent is a native-audio model that supports only AUDIO". On 2026-08-26 Google shipped gemini-3.5-transcribe-live, a streaming speech-to-text model that declares bidiGenerateContent and emits only TEXT. Discovery filters on that declared method alone — correctly, since the model NAME carries no capability signal — so the listing handed it to the probe, the probe asked for AUDIO, and the provider refused: code=1007 reason="The requested combination of response modalities (AUDIO) is not supported by the model. models/gemini-3.5-transcribe-live" reddening the whole leg on every run from 2026-08-27 (run 33296393200). The fix is neither a name filter nor a hardcoded pin — both are mistakes this leg has already made. Response modality is a per-model capability the /models listing does not express, so the only authority on it is the provider, and now the leg ASKS: resolveLiveModelCandidates keeps the whole ordered candidate list and driveGeminiLiveAudio walks it, treating a 1007 modality refusal as "disqualify this candidate, advance to the next". A listing whose Live models ALL refuse AUDIO is an honest skip — there is no AUDIO turn to grade — not drift. Every other failure (handshake infra status, mute provider, timeout) propagates unchanged, with a negative-control test pinning that. Proven against the existing local TLS fake provider, which now enforces the real PER-MODEL rule, driving the REAL probe through the REAL transport and framing: red on the verbatim 1007 above, green on the advance to a model that serves AUDIO. NOT proven here: the live endpoint's behaviour, which needs a real GOOGLE_API_KEY.
commit: |
contextablemark
approved these changes
Aug 30, 2026
contextablemark
left a comment
Contributor
There was a problem hiding this comment.
Reviewed the model-family classifications, candidate-resolution path, failure propagation, regression coverage, and the live PR drift artifacts. No blocking findings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the two Gemini legs of the
Drift Testsworkflow, red on every run since 2026-08-27 (latest: run 33296393200). Supersedes drift-bot PRs #388 and #389 — see below.What was wrong
Google shipped three model families the registry had never seen, and one of them also broke the Gemini Live probe.
gemini-3.5-transcribeAudio (up to 1 hour)→ outputText, Word annotationsgemini-3.5-transcribe-livebidiGenerateContent; emits TEXT, refuses AUDIOgemini-omni-1.1-flashVideo with audio→ outputVideo with audioNone of the three emits a chat completion, so none can be text-generation drift.
Classified on capability, never on the id string. For
gemini-omni-1.1-flashthe substring argues the opposite: "omni" names OpenAI's text-capable line, so a name rule would have INCLUDED a video-generation model. The deciding evidence is the provider's declared input/output signature — the same instrument used forgemini-3.7-flash-video-understanding-eap, and the one whose absence caused the historicalnative-audiomisclassification.gemini-3.5-transcribe-livecarries two independently observed capability facts:bidiGenerateContent— the Live leg'sfetchLiveCapableModelsfilters on that method alone with no name heuristic, and it selected this id.code=1007 reason="The requested combination of response modalities (AUDIO) is not supported by the model. models/gemini-3.5-transcribe-live".So it is a bidi Live surface that emits TEXT: a streaming transcriber, not a native-audio model.
The Live leg's bug (cause DETERMINED, not assumed)
ws-gemini-live.drift.tsdocumented the premise "every model exposingbidiGenerateContentis a native-audio model that supports onlyAUDIO". Fact 2 above falsifies it. The listing offered the transcriber first, the probe asked for AUDIO, and the provider refused — so the leg reported provider-refusal noise instead of drift on every run.The fix is neither a name filter nor a hardcoded pin — both are mistakes this leg has already made. Response modality is a per-model capability
/modelsdoes not express, so the only authority is the provider, and now the leg asks:resolveLiveModelCandidateskeeps the whole ordered candidate list anddriveGeminiLiveAudiowalks it, treating a 1007 modality refusal as disqualify this candidate, advance to the next. A listing whose Live models all refuse AUDIO is an honest skip — there is no AUDIO turn to grade — not drift. Every other failure propagates unchanged (negative-control test included).Red-green proof
Leg 1 — classification, through the real
enumerate→normalize→subtractpipelinenpx vitest run --config vitest.config.drift.ts src/__tests__/drift/models.drift.ts -t "Gemini: every live family is classified"RED (
EXIT=1) — byte-matches the CI critical list:GREEN (
EXIT=0):Same pair on the behavioural specs (
text-drift.test.ts): REDTests 3 failed | 6 passed (9)(expected false to be trueonisClassifiedFamilyfor each family) → GREENTests 56 passed (56)acrosstext-drift+logic-pin+model-registry.Leg 2 — Live modality, through the REAL probe, transport and framing
npx vitest run src/__tests__/drift/ws-gemini-live-modality.test.ts -t "a bidiGenerateContent model that refuses AUDIO"Driven against this repo's existing local TLS fake provider, now enforcing the real per-model rule (the rule lives in the "provider", not in the expectations, so the test cannot satisfy itself). RED produced by reverting the advance to the pre-fix behaviour:
RED (
EXIT=1) — reproduces the CI failure verbatim:GREEN (
EXIT=0):The green case asserts the advance happened on the wire (the fake provider received both setups, in listing order), that a real audio turn followed (
audioPartCount > 0,textPartCount === 0, oneturnComplete), and that the refusal was surfaced rather than swallowed.No provider credentials exist in the environment this was developed in —
GEMINI_API_KEY,GOOGLE_API_KEYandGOOGLE_GENERATIVE_AI_API_KEYare all absent (presence checked only; no value was read). So neither the live Gemini/modelsleg nor the live Gemini Live WebSocket leg was exercised against Google.unclassifiedFamiliespipeline over the exact ids the live canary reported, and that pipeline is the whole of the failing check.drift-reportartifact. This is the same standard the previous, mirror-image refusal was landed under (see the head ofws-gemini-live-modality.test.ts). That the live endpoint accepts the advance can only be confirmed by a scheduled run with a realGOOGLE_API_KEY.GOOGLE_API_KEYin CI is what closes that gap; the nightlyDrift Testsrun is the confirmation.Relationship to #388 / #389
Both are drift-bot needs-human-decision PRs holding
Decision: pendingnote stubs — #389 is a strict superset of #388 (it addsgemini-omni-1.1-flash). Neither carries a code change, and as their own bodies explain, merging them applies nothing:includeFamilies/excludeFamiliesare checksum-pinned and drift-sync's gate-1 allowlist cannot touchlogic-pin.test.ts, so a follow-up run reportsgate-failed, notok-applied.This PR is the by-hand procedure those bodies prescribe, in one reviewed change: the classification, both re-pins, the resolved notes (same filenames, now
Status: RESOLVEDwith the capability rationale — an EXCLUDE has no automated path and must be recorded in prose), and the offline/modelswave. #388 and #389 should be closed when this merges.Test status
vitest runEXIT=0— 178 files, 5347 passedvitest run --config vitest.config.drift.tsEXIT=0— 134 passed, 57 skipped (live legs skip without keys)eslint .EXIT=0prettier --check .EXIT=0tsdownbuildEXIT=0Note for reviewers: the repo's
tsconfig.jsonexcludessrc/__tests__, sotsc --noEmit -p tsconfig.jsontypechecks none of the files this PR touches. The changed test files were typechecked explicitly under the same compiler options; that pass caught a real error (WSResultused across a module boundary without being exported) that the project typecheck reported clean.Out of scope
The AG-UI leg of the same failing run is a separate concern owned elsewhere and is untouched here.
CI on this PR — read the gating, not the job names
Two things this PR's own checks CANNOT tell you:
driftjob isskippingonpull_request. It is gated to the scheduled run, so the Gemini/modelsand Gemini Live legs this PR fixes do not execute here. A green PR runs a subset. Confirmation is the next scheduledDrift Testsrun onmain(or a manualworkflow_dispatch), withGOOGLE_API_KEYpresent.agui-schema-driftfails, and it is PRE-EXISTING. The same job isfailureon the nightly main run33296393200(26AGUIEventTypecriticals —SUBAGENT_STARTEDand friends). This PR touches no AG-UI file; that concern is owned separately.Every other check is green: static quality (eslint, prettier, commitlint, exports), the full Node test matrix (20/22/24), the Python matrix, Sentinel, and preview.