together_ai, cerebras, nvidia_nim, lm_studio, llamafile, lambda_ai, nebius,
novita, wandb, featherless_ai, nscale, hyperbolic, volcengine. One family
subpackage (the google_genai one-family precedent): per-provider supported
list truths as pure gates (v1 raises UnsupportedParamsError unless
drop_params on anything off the list, so exclusions are typed fallbacks),
frozen CompatProfile deltas (mct rename, user emission, together's rf-text
drop, cerebras capability-gated reasoning_effort over the load-bearing
cerebras/{model} map key), openai_compat assemble_body for the body and
parse_response verbatim for responses; the {provider}/{wire_model}
re-prefix stays the seam's preset arm. Family raw guard adds the explicit
stream:false arm (the SDK serializes the key; absent-vs-false is lost in
the IR). baseten is dropped from the wave: its streams ride the dedicated
legacy handle_baseten_chunk wrapper branch, not the openai dialect, so it
stays an unregistered typed v1 fallback
M1: the use_xai_oauth guard arm is documented as defense-in-depth (it is
unreachable through _raw_openai_body: litellm param, never in
non_default_params); the REAL protection is now a HARD seam obligation in
CLAUDE.md (route the kwarg or fall back pre-deps, pinned at completion()
level before flag-on) and test_use_xai_oauth_guard_reachability_facts pins
the classification facts the analysis rests on.
M2: usage token coercion now mirrors v1's int(x or 0) exactly — numeric
strings and bools fold, uncoercible values are typed boundary errors (loud
where v1 raises out of chunk_parser / Usage validation, never a silent 0);
two-sided corpus row numeric_string_usage_coerced plus loud-on-both-sides
rows for response and stream, and a fold-equality row against v1's own
static methods.
M3: one make_parse_line(parse_event) factory in openai_compat/stream.py;
openai/azure/xai all compose it (copies 1-3 deleted) before the long-tail
waves template off this port.
M4: test_supported_list_mirrors_track_v1_at_head pins supports_stop /
supports_reasoning against XAIChatConfig.get_supported_openai_params for
EVERY xai chat model in the map at HEAD, plus the params v2 emits
unconditionally staying in v1's supported list.
F1 (verifier, refutes a report claim): v1 FORWARDS a stream refusal that
rides a role/content delta (only refusal-only deltas are swallowed); the
parser now forwards it verbatim when the wire key is present; pinned by
the refusal_rides_content_deltas two-sided row. The false 'wrapper drops
refusal' claim is corrected in code comments and the report.
F2 (verifier): folded usage attaches ONLY to the choices:[] tail; v1's
wrapper strips usage from every emitted content/finish chunk; pinned by
usage_withheld_on_content_chunks (the fold still runs, so uncoercible
values stay loud).
N1: _contains_strict_key fails CLOSED at the recursion cap. N2: inbound
fold docstrings cover the xai dialect. N4: Block import at module top.
N5: the xai/-prefix capability trap is asserted, not just narrated.
Differential report regenerated: 336 IDENTICAL / 78 FALLBACK / 0 divergent.
Zero recorded xai fixtures exist anywhere (the characterization branch has
none), so the corpus under characterization_xai/ pins v1 IN-PROCESS at HEAD
(provenance in its README; regenerate with generate_xai_snapshots) and the
gates are two-sided: v1-at-HEAD must equal the committed snapshot (drift)
AND v2 must equal it byte-for-byte. Requests run v1 AS EXECUTED
(get_optional_params('xai') -> extra_body pop -> transform_request) so the
seven R2 raise rows assert v1's UnsupportedParamsError in-process beside
v2's typed fallback. Responses run the LIVE XAIChatConfig.transform_response
(httpx path) pinning the finish_reason '' chain (stop WITH tool_calls), the
reasoning fold + idempotency, websearch billing fields, citations, and the
bare-model no-prefix row. Streams replay raw SSE data lines through
XAIChatCompletionStreamingHandler + CustomStreamWrapper('xai') (the line
seam: the chunk_parser rewrites sit below the parsed-chunk seam) with the
usage tail pinned as the inherited openai-port seam contract.
DIFFERENTIAL_REPORT.md regenerated: 0 divergent rows.
Append-only rows in all four tables (serializer, parser, dialect 'openai',
raw guard). xai is deliberately NOT same-family: v1's transform touches the
body (strict strip, non-user name strip), so a verbatim fast-path forward
would diverge. CLAUDE.md maps providers/xai, the xai fallback surfaces, and
the unwired-seam handoff notes (bare wire model, tail synthesis).
The serializer is the openai_compat body over xai's own gates: v1's
supported-list truth is RAISE-unless-drop_params (the mct rename arm is
dead code), so every raise row is a typed fallback; reasoning_effort gates
through deps over the xai/{model} map key; user rides verbatim; the
function-level tool strict strip mirrors filter_value_from_dict with deeper
strict keys failing closed in the raw guard. The guard also owns the R5
reroutes (web_search_options Responses bridge, use_xai_oauth PKCE) plus
explicit stream:false, and runs the shared openai guard with a user-only
message-name fallback (v1 strips non-user names, so the IR drop IS v1).
The response parser is the openai parser plus the xai usage post-steps
(reasoning fold, total normalize, num_sources_used -> web_search_requests);
the finish_reason '' chain needs no arm because v1's own fix function is
empirically dead and both sides share the live map_finish_reason. The
stream parser pins the httpx dict path: per-chunk usage fold, no
extras/system_fingerprint survival, reasoning rename, tool type 'function'
default, and the choices:[] usage tail passes through folded for the seam
contract. assemble_body is the gate-free entry point azure N4 deferred to
this phase; openai_compat callers are unchanged.
The cache-marker token bound is computed over the IR, but the inbound
parse drops the OpenAI message 'name' field (v1's generateContent
transform ignores it on the wire), so name bytes were invisible to the
bound while v1's is_prompt_caching_valid_prompt token-counts them:
100 one-char marked messages with 80-char names measured v1=4603 tokens
(>= 1024, context cache created) against a v2 bound of 900 (served,
uncached). New providers/google_genai/guard.py falls back before parse
when any message carries 'name' AND a cache_control marker is present
(name bytes cannot be bounded post-IR), registered in _RAW_GUARDS for
vertex_ai and gemini; vertex_anthropic needs no row (v1 context caching
is gemini-only and the anthropic wire ignores name on both sides). The
verifier's attack script now finds no unsound shape. Corpus: a
name_beside_marker fallback row (fails with the guard unregistered,
verified by stash) and a message_name_without_marker quirk row pinning
that both sides drop bare 'name' identically. Differential regenerated:
297 IDENTICAL / 55 FALLBACK / 0 divergent.
- N1: the ambient-network rule gains a urllib.request.$F(...) call-site
pattern, closing the bare-import evasion (probe-verified: fires on
import urllib + urllib.request.urlopen; urllib.parse stays free).
- N2: the rebuilt-Choices cast is the precise Optional[Dict[str, Any]],
not Any.
- N4: a seam-side drift test pins the two hand-maintained mirrors
(ir.THOUGHT_SIGNATURE_SEPARATOR == factory's; DUMMY_THOUGHT_SIGNATURE
== _get_dummy_thought_signature()).
- N6: _route_to_v2 passes the REAL enabled_providers() set so route()
owns the membership decision too; no half of the fork lives inline.
provider_specific_fields comes off a PlainJson body; cast at the
boundary like the file's sibling adapters (pre-existing on the openai
branch, surfaced while unifying the seam).
- openai N1: the raw guard keeps scanning past non-object messages and
tool_calls instead of aborting the whole scan (locally conservative;
parse still rejects the non-object with a boundary error).
- azure N2: the cache_control scan returns True (fall back) at its
recursion cap; exhaustion never admits a request.
- azure N6 + verifier S6: the corpus moderation strip asserts the value
is null and the extra_body strip asserts emptiness, so a future
non-null payload breaks the gate instead of vanishing.
- google N1: dead deepcopies removed before the schema strip passes
(both reconstruct every node).
- google N4: sampling_entries reads the three sampling params explicitly
(no getattr Any-leak, no del-ed parameters); signature drops the
unused deps/target.
- google N6: the dict-item ignore in schema.py replaced by an in-loop
isinstance narrow.
- google N2: the remaining unjustified mutation waivers carry the
build-then-freeze justification line.
The shared _raw_openai_body adopts the google branch's fixed semantics
across ALL forks: every caller-set OpenAI param rides into the parse and
unknowns (n, seed, penalties, logprobs, modalities, ...) become typed
boundary errors that fall back to v1, instead of the _BODY_FIELDS
whitelist silently dropping them while v2 served a thinner body (v1
raises or maps those params; dropping diverged). The google send module
aliases the shared builder. _send_v2_google now sends through the
engine's injected HttpPort (send_prepared + HttpxJsonPort), the same
skeleton as the anthropic fork; copy #3 of the hand-rolled httpx client,
manual status check and manual json parse is gone, and the magic 600.0
fallback timeout is a named constant. Bedrock's send stays separate by
necessity (SigV4 signs after the body is final). mypy clean.
M1: _google_endpoint is async and resolves vertex credentials through
v1's _ensure_access_token_async on both arms, so the acompletion path
never blocks the caller's event loop on a token refresh; the test stub
covers the async chokepoint (get_access_token_async) too.
M2 (route half): both completion() forks now go through dispatch.route
via _route_to_v2, mirroring the anthropic fork; the dead inline
allowlist re-derivation is gone. (The shared-send-skeleton half rides
the _raw_openai_body unification commit.)
M3: GoogleTarget is a real Literal["vertex_ai", "gemini"]; provider_key
is GoogleProviderKey (subset of dispatch.Provider); every resulting
type: ignore in the send module is deleted.
M4: both seam modules fully annotated against v1's types
(VertexAIModelRoute, VERTEX_CREDENTIALS_TYPES, AllMessageValues,
LiteLLMLoggingObj, ModelResponse, PreparedRequest); the
_build_usage_gemini setattr loop is bounded to
CompletionTokensDetailsWrapper's declared fields while keeping the
only-set-when-reported serialization contract. mypy clean.
M6: the bare except->False around _supports_factory is gone; a lookup
failure is loud instead of silently flipping the structured-output
fork. supports_response_schema stays as-is: v1's own helper documents
"Does not raise error. Defaults to False", so v1 takes the same branch
on lookup failure.
M8 (separator half): one in-package declaration of the __thought__
separator (ir.py, documented as the mirror of factory.py's canonical);
google providers and the inbound response import it; the seam imports
v1's factory constant directly. N3: dead GOOGLE_PROVIDER_KEYS deleted.
deps.api_version=None conflated two worlds: v1's genuinely-unparseable
STRING branch (passthrough is pinned v1 behavior) and "the integrator
never threaded the field" (in v1 the version reaching map_openai_params
is always a string from the default chain). The tool_choice and
response_format gates now return a typed fallback naming the wiring bug
when api_version is None; real unparseable strings keep the passthrough.
The unparseable differential row passes the literal "" (the harness no
longer normalizes "" to None), and two new fallback rows pin the
None-is-a-wiring-bug contract. deps.py documents the seam handoff.
Three branches had grown the same fabricated-placeholder match arm: a
wire/composite dialect receiving a per-block thinking delta silently
emitted an anthropic-shaped body. _thinking_delta_body now takes
BlockDialect (anthropic | bedrock_converse) so the impossible arms are
unrepresentable in the body builder; the step narrows through
_as_block_dialect and returns a TranslationError for wire/composite
dialects, which the fold/chunk_stream surface (step's result type now
carries the error channel). The same loudness applies to the other two
pairings: wire_chunk events under non-wire dialects and composite chunk
events under non-gemini dialects (previously silently swallowed or
mis-emitted). google M5's second half: _gemini_chunk_step rebuilds state
via dataclasses.replace, so stream_id and future fields are never
silently reset. Regression tests cover every mismatched pairing plus the
fold surfacing.
The IR's canonical re-dump made every real replayed tool history (compact
OpenAI spacing) fall back to v1; the corpus could only go green on
json.dumps-spaced arguments production never sends. ToolUse gains a
defaulted arguments_raw: Option[str] holding the wire string; the inbound
parse sets it for openai-format tool_calls, the openai_compat serializer
re-emits the bytes verbatim (re-dump only for non-wire origins), and the
guard's spacing check is gone (byte fidelity over canonicalization; the
change can only shrink the fallback surface since the guard previously
rejected everything it newly admits). New IDENTICAL rows: the compact
real-traffic shape, odd spacing, and blank argument strings; the spacing
EXPECTED_FALLBACK row is removed. Malformed-JSON arguments still fall
back to v1's repair path.
serialize_response fell through to the anthropic assembly when an
openai-dialect ChatResponse carried no wire body, silently serving a
wrong-shaped body if a future openai-dialect parser forgets to set
ChatResponse.wire (azure/azure_ai now extend exactly this path). The
function returns Body | TranslationError; both pipeline call sites lift
the error into the Result, and a regression test pins the loud error.
The accumulator escaped into a callee that appended to it through an
out-parameter, waved through with the annotation meant for the opposite
case. _user_message is now pure (returns zero-or-one user message; call
sites concatenate), and the remaining genuinely-local accumulators carry
the one-line justification the convention requires.
M4 (signed off at integration per the PR #30138 tenet clause): a quirky
compat finish value fired a typed error AFTER the request was sent and
billed, where no v1 fallback exists; v1 is deliberately lenient there
(map_finish_reason + native stash). The native string now rides the wire
body/chunk verbatim and BOTH sides normalize through the live
Choices/StreamingChoices map_finish_reason, so v2 inherits v1's lenient
mapping with no mirror table. Loud errors stay for semantics-changing
shapes (n>1, legacy function_call output/delta/finish, audio, logprobs).
New differential rows pin mapped ("eos") and unmapped finish strings on
the response path and an "eos" stream replayed through the SDK's lenient
construct_type (the production seam; model_validate would reject what
the SDK serves).
M1: the frozenset-membership narrowing that needed `type: ignore` in the
strict zone is gone; the semantic IR finish narrows through a literal
match (_semantic_finish), no suppressions left in the package.
detection_model kept an empty-string base_model, so base_model: '' in a
deployment's litellm_params disabled the o-series/gpt-5 family and
response_format gates (fail-open: v2 served max_tokens where v1 routes
AzureOpenAIO1Config and emits max_completion_tokens). One-token fix to
'deps.base_model or model'; the docstring's 'exactly like v1' claim is
now true. Pinning corpus row uses base_model='' with the non-prefix
family substring deployment name prod-o3-mini per verifier-azure (the
shared openai prefix gate cannot catch it); row fails without the fix.
The old gate compared CHAR count to gemini's 1024-TOKEN cache minimum on
the false premise that one token spans at least one character; BPE tokens
floor at one BYTE, so 700 CJK or 400 emoji chars passed the gate while
v1's token_counter could reach 1024 and fire the cachedContents network
call (silent caching loss + wire-body divergence, flag-on). The gate now
computes a conservative upper bound on whatever v1 could count: UTF-8
byte length of every text/thinking/tool_result/tool_use-args surface
across the WHOLE request (a superset of v1's continuous cached block,
which spans first-to-last marked message inclusive), plus a fixed
per-message margin for token_counter's overhead, and fails closed on ANY
media block when a marker is present (v1 charges 250 tokens per image at
zero text bytes). Quirk rows pin the typed fallback for CJK, emoji, and
unmarked-image-beside-marker shapes (all three fail against the old
gate), and a sub-limit CJK quirk pins that small international markers
still serialize identically to v1.
_to_model_response_openai only handled the response.model-is-None arm;
v1's completion branch (convert_dict_to_response.py:699-711) also rewrites
a pre-set 'provider/model' to 'provider/{wire model}', and v1's openai
handler pre-sets exactly that for every non-openai compat consumer. The
differential was structurally blind (always passed a fresh ModelResponse),
so the corpus now pins both pre-set shapes: prefixed preset re-prefixed to
the wire model, slash-free preset kept verbatim. The prefixed row fails
without the seam fix (verified by stashing it).
The merged google provider uses urllib.parse.urlparse (pure URL string
parsing, no I/O) and the rule-hardener's bare 'import urllib' pattern
matches every submodule from-import semantically, so the gate flagged it.
Ban the actual client module instead; verified the rule still fires on
import urllib.request, from urllib import request, and
from urllib.request import urlopen, and not on urllib.parse.
The merged step() carries both branches' new event arms (wire_chunk +
chunk), tipping C901 to 11. Pull the two tool arms into one helper via an
or-pattern case; the helper narrows on the two-literal tag so no
placeholder arm is needed.
Clean merge (append-only Makefile/semgrep/ruff additions plus the two
gate scripts). CLAUDE.md freshness and file-size gates now run in
make lint-translation; verified against the merged tree in the next
baseline pass.
Both-add unions: pipeline registry rows (azure/azure_ai/azure_ai_anthropic
beside the google rows), ChunkDialect literal now carries azure + gemini,
the thinking-delta placeholder arm keeps gemini split from the
anthropic|openai|azure arm (all placeholder arms are removed by the
ChunkDialect split fix later in this branch). CLAUDE.md scope/tree
hand-unioned for ten providers. DIFFERENTIAL_REPORT.md regenerated on the
merged tree (0 divergent). RESPONSE_FORMAT_SPOOF_MODEL rename merged clean
(identical hunk on both branches).
Append-only registry rows (serializer/parser/dialect openai) plus a
_RAW_GUARDS table the pipeline consults before parse for same-family
providers. Request gate: 17 identical rows against v1 in-process
(map_openai_params + transform_request) and 17 asserted typed
fallbacks. Response gate: 5 identical rows against
convert_to_model_response_object (the live SDK-path normalizer) plus
loud-error rows for unreachable shapes. Stream gate: byte-identical
replays through the real CustomStreamWrapper over SDK chunks, an
SSE-line decode equivalence check, and the pinned usage-tail seam
contract. DIFFERENTIAL_REPORT.md regenerated: 0 divergent rows.
vertex_ai routes through v1's get_vertex_ai_model_route (gemini -> v2
vertex_ai, claude partner route -> v2 vertex_anthropic, everything else
stays v1); gemini (AI Studio) gets its own fork. The vertex OAuth token and
URL come from v1's own envelope helpers in the seam, the raw body carries
EVERY caller-set OpenAI param so unknown shapes fall back typed instead of
being dropped, and ambient globals the pure serializers cannot honor
(vertex_ai_safety_settings, custom_prompt_dict, modify_params) force v1.
parse_response mirrors convert_to_model_response_object (the SDK-path
normalizer; transform_response is dead code there) and rides the
normalized chat-completion body on ChatResponse.wire; the new openai
response dialect emits it unchanged. parse_event normalizes SSE chunks
to the SDK-dump shape and the openai chunk dialect reproduces the
wrapper's stateful decode (first-chunk role, role-strip
provider_specific_fields, empty-delta suppression, wire id pinning,
preserve_upstream extras, choices=[] usage passthrough). The seam
gains the openai usage style (verbatim Usage(**raw)) and wire
id/created/system_fingerprint adoption.
Body assembly = {model, messages, **params} with the original
max-tokens key re-emitted, recursive cache_control strip on tool
parameters (v1 filter_value_from_dict parity incl. the recursion cap),
and fail-closed gates: o-series/gpt-5 families, top_k/thinking/
reasoning_effort, the user param's model-list gate, and
response_format on gpt-4/gpt-3.5-turbo-16k.
Review fixes on the inherited work: typed map/seq narrowing in guard.py
(pyright strict), four new guard reasons for shapes the IR cannot
round-trip (empty tools/stop lists, empty assistant content list,
non-function tool_calls, tool_calls without string arguments), a
wire_chunk placeholder arm in the chunk fold, and lint formatting.
vertex_gemini / gemini / vertex_anthropic requests, responses, and streams:
snapshot == v1-at-HEAD == v2 (canonical JSON), plus a quirk corpus pinning
the 3-way structured-output fork, gemini-3 defaults and id forwarding,
multi-system parts, top_k passthrough on both routes, and the fail-closed
shapes (large cache markers, AI-Studio https media, xhigh effort, blocked
responses, mid-stream error objects).