feat(api-proxy): ordered fallback models on 5xx, timeout, or model_not_supported - #9356
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Anthropic detection is order-dependent, and newly buffered error responses have no memory bound.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds ordered, guarded model fallback retries to the API proxy, addressing #9353.
Changes:
- Adds fallback selection, request rewriting, failure detection, and guard enforcement.
- Records fallback telemetry and the serving model.
- Wires configuration, schemas, documentation, Docker packaging, and tests.
| File | Description |
|---|---|
src/types/api-proxy-model-options.ts |
Defines fallback model options. |
src/services/api-proxy-env-config.ts |
Exports fallback configuration. |
src/services/api-proxy-env-config.test.ts |
Tests environment mapping. |
src/schema.test.ts |
Tests fallback schema validation. |
src/config-mapper.ts |
Maps file configuration. |
src/config-file.ts |
Types file configuration. |
src/config-file-mapping.test.ts |
Tests configuration mapping. |
src/commands/build-config.ts |
Builds runtime configuration. |
src/awf-config-schema.json |
Updates runtime schema. |
schemas/token-usage.schema.json |
Defines fallback telemetry. |
docs/awf-config.schema.json |
Updates canonical config schema. |
docs/awf-config-spec.md |
Documents fallback semantics. |
docs/api-proxy-sidecar.md |
Adds sidecar usage guidance. |
containers/api-proxy/upstream-token.js |
Passes fallback metadata. |
containers/api-proxy/upstream-retry.js |
Handles buffered fallback responses. |
containers/api-proxy/upstream-response.js |
Routes eligible responses through fallback. |
containers/api-proxy/upstream-http.test.js |
Tests guarded dispatch behavior. |
containers/api-proxy/upstream-http.js |
Dispatches rewritten fallback requests. |
containers/api-proxy/token-tracker-http.unit.test.js |
Tests fallback records. |
containers/api-proxy/token-tracker-http.js |
Persists fallback metadata. |
containers/api-proxy/server.model-fallback-chain.test.js |
Tests provider integrations. |
containers/api-proxy/proxy-request.js |
Applies existing guards to candidates. |
containers/api-proxy/model-fallback-chain.test.js |
Tests fallback utilities. |
containers/api-proxy/model-fallback-chain.js |
Implements fallback selection and rewriting. |
containers/api-proxy/Dockerfile |
Packages the new module. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (shouldBufferForFallback) { | ||
| const bufferedChunks = []; | ||
| proxyRes.on('data', (chunk) => { | ||
| responseBytes += chunk.length; | ||
| bufferedChunks.push(chunk); | ||
| }); |
| /\b(?:unknown|invalid|unsupported) model\b/i, | ||
| /\bmodel\b[^"\n]{0,120}?\b(?:does not exist|not found|is not supported|not supported|is not available|not available|has been deprecated|is deprecated|is retired)\b/i, | ||
| /\bmodels\/[^\s"]+ is not found\b/i, | ||
| /"type"\s*:\s*"not_found_error"[^}]*"message"\s*:\s*"model:/i, |
|
@copilot Fix the code for all comments in this review thread. When a review comment includes a suggested change, apply the suggestion exactly. Do not make changes beyond what is described in the linked review thread. |
Addressed both review findings in |
|
✅ Copilot review passed with no inline comments. @copilot Add the |
Documentation PreviewDocumentation has been built for this PR. To view locally:
Built from commit 68de66f |
|
✅ Security Guard completed successfully! Security review complete for PR #9356 (feat: ordered fallback models). All security-critical changes analyzed: ✅ Buffer overflow prevention: 64KB limit on fallback body inspection with streaming fallback for oversized responses No security weakening detected. PR passes security review.
|
|
✅ Build Test Suite completed successfully!
|
|
❌ Smoke Copilot BYOK AOAI (api-key) reports failed. AOAI BYOK (api-key) mode investigation needed...
|
|
🛡️ Smoke Copilot Network Isolation confirmed the egress allowlist is enforced. ✅
|
|
✅ Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓
|
|
❌ Smoke Copilot BYOK AOAI (Entra) reports failed. AOAI BYOK (Entra) mode investigation needed...
|
|
🔌 Smoke Services — All services reachable! ✅
|
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟
|
|
✅ Smoke Claude passed
|
|
Smoke Cloud Hypervisor reports failed. Cloud Hypervisor + Copilot failed.
|
|
❌ Smoke Gemini reports failed. Facets need polishing...
|
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.
|
|
📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅
|
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤
|
✅ Smoke Test: Copilot BYOK (Direct) ModeStatus: PASS
Mode: Direct BYOK via
|
|
EGRESS_RESULT allow=pass deny=pass
|
Smoke Test: Claude Engine Validation
Overall result: PASS
|
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
|
OTEL smoke test results
|
|
Smoke Copilot — PASS ✅
Author:
|
|
Smoke services: ✅ Redis PONG · ✅ pg_isready · ✅ SELECT 1 → PASS
|
|
Smoke test results
|
Chroot Version Comparison
Result: Not all tests passed (Node.js version mismatch), so the
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — PASS Note: the first Java run failed with
|


engine.modelaccepts a single model, and retries re-run that same model. When the model fails with a 5xx, a timeout, ormodel_not_supported, the job ends with no output. This PR adds an optional ordered fallback chain to the API proxy: on a model-specific failure, the proxy rewrites the request's model and re-sends it.Trigger policy (
model-fallback-chain.js)upstream_timeout)upstream_connection_error)model_not_supported,model_not_found, "not accessible via the … endpoint", Anthropicnot_found_errorwithmodel:, Geminimodels/X is not found(reported asmodel_not_supported)modelfield for OpenAI, Anthropic, and Copilot. Gemini carries the model in the URL, so the/models/<model>:<method>path segment is rewritten instead. A redundant<provider>/prefix on an entry is stripped.getCurrentGuardChecks. Without this, the fallback chain could bypassdisallowedModels.Request/response plumbing
upstream-http.js: works out the next candidate before dispatch. The response handler receives anonModelFallbackcallback only when a fallback actually exists. As a result, error bodies are buffered only when a switch is possible; with no chain configured, or once it is exhausted, 5xx responses still stream straight to the client.proxyReqerror before any response arrives is treated as eligible.upstream-response.js/upstream-retry.js: 5xx and 404 responses get a new buffered path. For 400, the fallback runs as a new step inhandle400WithRetry, after the existing Copilot transient retry and alias-candidate retry. The shared tail that writes a buffered error response was extracted tosendBufferedUpstreamResponse.Observability
model_fallbackwarn log withfrom_model,to_model,requested_model,attempt,reason, andstatus.modelnow holds the model that actually served the request. A newmodel_fallbackobject (requested_model,model,attempt,reason,status) is added to the record and toschemas/token-usage.schema.json, so gh-aw can report the served model inGH_AW_INFO_MODEL.CLI / config
apiProxy.fallbackModelsflows through the config schema,config-file.ts, the mapper,build-config.ts, and the types, and reaches the proxy asAWF_FALLBACK_MODELS(JSON array).COPYlist.Not covered
Tests
server.model-fallback-chain.test.jscovers OpenAI, Anthropic, and Copilot (5xx, model-specific error, full-chain walk, no fallback on 401/403/429 or a generic 400, connection error, no-op when unconfigured) plus Gemini path rewriting.Docs:
docs/awf-config-spec.md§12.7 anddocs/api-proxy-sidecar.md.