Deprecation policy
How field drops, observe_dropped_fields, and semver work when translation cannot map a vendor field.
Last updated: 2026-07-21
Issue: #103
Related: #152 (optional x-gateway-dropped-fields implementation)
This document defines how Inja LLM Gateway handles removed features and fields dropped during cross-dialect translation, so operators and agents are not surprised by silent data loss.
Acceptance criteria (#103)
| Criterion | Status |
|---|---|
| Passthrough never drops request/response JSON fields (except documented model rewrite / stream usage injection) | Policy + tests |
Decision on HTTP Warning vs x-gateway-dropped-fields |
Decided (Warning unused; header opt-in) |
| Metrics / hooks for drops | Opt-in observe_dropped_fields + usage dropped_fields |
| Semver rules when drop behavior changes | Documented |
Passthrough vs translation
| Path | Field policy |
|---|---|
| Passthrough (same family: OpenAI→openai/openai_compat, Anthropic→anthropic, Google→google) | Never drop request/response JSON fields. Only rewrite model (and stream usage injection where documented). Unknown OpenRouter/xAI extras must survive. |
Translation (cross family via canonical) |
May drop vendor-specific fields that have no canonical mapping. Drops must be covered by explicit tests (drop lists / goldens). |
Passthrough regressions that strip headers or body keys are bugs, not deprecations.
Owning tests (hermetic):
- OpenRouter extras:
proxy/openrouter_test.go(plugins/providerpreserved) - Reasoning content passthrough:
proxy/reasoning_passthrough_test.go - Media same-family:
proxy/media.gocomments + media tests - Translate drop list fixture:
testdata/fixtures/chat_translate/drops/common_drops.txt+proxy/translate_fixtures_test.go - Policy doc lock:
proxy/deprecation_policy_doc_test.go
Documented chat translation drops (today)
On OpenAI ↔ Anthropic ↔ Google chat translation, the following OpenAI-oriented knobs are not mapped (dropped on translate path only) — see also common_drops.txt:
n> 1 (policy: single choice; multi-choice rejected/dropped per fixtures)logprobs,top_logprobs,logit_biasstream_optionson non-OpenAI egress- Anthropic
cache_controlpreserved on Anthropic family (PT + translate IR); dropped cross-family — see Prompt caching (#108) - Other vendor-only extras without a canonical home
Thinking / tool / multimodal blocks that are mapped are listed in the README “Passthrough vs translation” table. New drops require a changelog Changed or Removed entry.
Warning headers (decision)
| Mechanism | Status |
|---|---|
HTTP Warning header |
Not used (ambiguous, often stripped by intermediaries) |
Custom x-gateway-dropped-fields |
Optional opt-in via observe_dropped_fields: true (#152) |
Hooks / usage dropped_fields |
Same opt-in; names only on the usage event. Prometheus counters still separate (#154). |
Rationale: translation drop lists are stable and tested; headers are off by default so SDKs are not spammed. Enable only when operators want runtime visibility.
When observe_dropped_fields: true:
- Response header
X-Gateway-Dropped-Fields: openai.logprobs,openai.service_tier,…(names only, never payloads). - Usage event field
dropped_fieldsmirrors the same names. - Passthrough paths set no header (they do not drop).
Also:
- Keep explicit drop-list tests next to translators (
common_drops.txt+ goldens). - Document behavioral changes in
CHANGELOG.md. - Prefer fail-closed errors for unsupported tool types / modalities over silent skip when fidelity matters.
Media / realtime
- Image/video/audio Extra maps: unknown keys may live in
Extraor be dropped per design-spec drop lists; goldens undertestdata/fixtures/are authoritative when present. - Realtime bridge (deferred): full OpenAI Realtime ↔ Google Live IR is not implemented. Cross-protocol attempts fail closed with
unsupported_realtime_bridge. Same-protocol passthrough only. - Realtime bridge drop list (unmapped until bridge ships): all cross-protocol event names, audio format conversion, tool/function remapping, VAD/session extras — never half-apply; never invent Anthropic WebSocket dialect.
Semver rules for drop behavior
| Change | Version |
|---|---|
| Start dropping a field that was previously preserved on a translation path | MAJOR (or MINOR only if field was never documented as supported — still changelog) |
| Preserve a field that was previously dropped | MINOR (additive fidelity) |
| Passthrough begins stripping a field/header | PATCH bugfix (restore) + regression test |
| Remove a public route or required config field | MAJOR |
Deprecation timeline for intentional removals:
- Announce in changelog under Changed (deprecated) with replacement.
- Keep working for at least one MINOR (preferably one MAJOR boundary for wire breaks).
- Remove in a MAJOR with Removed section.
Examples
OpenRouter plugins body: passthrough — plugins / provider must reach upstream unchanged (except model rewrite).
Image Extra: if canonical image IR cannot express a vendor quality enum value, document in drop list and tests; do not silently change meaning of another field.
Realtime unmapped event: drop-list test asserts the event is not re-encoded as a wrong type; bridge may close with a clear error rather than half-apply.
Related
- Changelog
- Compatibility matrix
- SDK matrix
- README.md HTTP API and translation tables
- Fixture:
testdata/fixtures/chat_translate/drops/common_drops.txt