feat(proxy): add 200+error body detection and extra inputs rectifier#2433
feat(proxy): add 200+error body detection and extra inputs rectifier#2433NKU100 wants to merge 9 commits intofarion1231:mainfrom
Conversation
… fields When a provider returns "Extra inputs are not permitted" errors, the rectifier extracts the unsupported field names from the error message, caches them per-provider (1-hour TTL), strips the fields, retries the request, and pre-filters subsequent requests from the same provider. Includes settings panel toggle (en/zh/ja) and retry guard to prevent infinite loops.
Remove unused `extracted_fields` field and `insert` method, use char array pattern in `rsplit` to satisfy clippy::manual_pattern_char_comparison.
…elds Non-streaming 200 responses lacking expected keys (content/choices/output/ candidates) are now reclassified as UpstreamError, triggering rectifiers and failover. When the extra-inputs rectifier fires but cannot extract field names from the error message, it falls back to stripping known Anthropic-only fields (context_management, anthropic_beta, output_config) present in the request body.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b68a192c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| obj.contains_key("content") | ||
| || obj.contains_key("choices") | ||
| || obj.contains_key("output") | ||
| || obj.contains_key("candidates") |
There was a problem hiding this comment.
Accept prompt-blocked Gemini responses as valid
is_valid_response_body only treats objects containing content/choices/output/candidates as success, so a valid Gemini generateContent 200 response that only carries promptFeedback (blocked prompt, no candidates) is reclassified as UpstreamError. In that case this code triggers unnecessary retry/failover instead of returning the provider’s safety-block result to callers, which changes behavior for normal filtered prompts.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
这个不太好修啊,不知道补上generateContent能不能覆盖所有场景
| let entries = self.entries.read().await; | ||
| entries | ||
| .iter() | ||
| .filter(|(k, v)| k.starts_with(&prefix) && !v.is_expired()) | ||
| .map(|(k, _)| k[prefix.len()..].to_string()) | ||
| .collect() |
There was a problem hiding this comment.
Evict expired cache entries when reading blocked fields
get_blocked_fields filters out expired entries but never removes them from entries, so the cache grows monotonically in long-running processes as new provider/field pairs are learned. This violates the intended 1-hour TTL behavior in practice (expiration affects matching, not retention) and can cause avoidable memory growth over time.
Useful? React with 👍 / 👎.
…ache entries - Add "promptFeedback" to is_valid_response_body so Gemini safety- blocked responses are forwarded instead of misclassified as errors - Lazily evict expired entries in ExtraInputsCache::get_blocked_fields to prevent unbounded memory growth in long-running processes
Replace the key-presence allowlist in is_valid_response_body with discriminator-field matching per wire format: - Anthropic Messages: type == "message" - OpenAI Chat Completions: object == "chat.completion" - OpenAI Responses API: object == "response" - Gemini: candidates / promptFeedback (no discriminator in protocol) This avoids misclassifying 200 bodies that happen to contain strings like "content" or "choices" as errors, and correctly rejects error envelopes (type == "error") and SSE chunk types.
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@claude review |
|
I'll analyze this and get back to you. |
|
@claude review (Retrying — first attempt hit a git fetch race in claude-code-action.) |
|
I'll analyze this and get back to you. |
|
@claude review |
|
I'll analyze this and get back to you. |
|
@claude review |
|
Claude finished @farion1231's task in 4m 50s —— View job Claude PR Review
FindingsImportant —
But the Gemini route in .route("/v1beta/*path", post(handlers::handle_gemini))
.route("/gemini/v1beta/*path", post(handlers::handle_gemini))So it also forwards non-
Concrete failure path: Gemini CLI (and similar clients) call Two minimal-surface fixes:
Notes on prior Codex feedbackBoth items are already resolved on this branch, looks good:
Otherwise the rectifier module is well-scoped, the per-provider keying is correct, the pre-filter ordering sits cleanly before the existing body filter, and the discriminator-based body classifier is a nice tightening over the original key-set check. |
…per path The hyper client does not auto-decompress responses, so gzip-compressed 200 bodies (e.g. from Xiaomi mimo API) were misidentified as invalid by is_valid_response_body, triggering unnecessary retries. Add manual decompression using response_processor::decompress_body before the validity check, matching what the reqwest path already does.
…ection Instead of whitelisting valid response shapes per protocol (Anthropic type=="message", OpenAI object=="chat.completion", Gemini candidates/ promptFeedback, ...), detect the `error` key in JSON body. Any 200 response containing `error` is reclassified as UpstreamError; everything else is trusted. This eliminates the need for per-protocol patches when new endpoints or providers are added.
MiFE proxy may strip gzip wrapping but leave content-encoding: gzip header intact. decompress_body then fails with "invalid gzip header", and the misleading header is forwarded to clients causing ZlibError. Check for gzip magic bytes (1f 8b) before attempting decompression. If absent, return the body as-is with Ok, which triggers content-encoding header stripping in read_decoded_body.
…back strategy The old description only mentioned matching "Extra inputs are not permitted" errors. The actual implementation also falls back to checking Anthropic-specific fields (context_management, anthropic_beta, output_config) in the request body. Update zh/en/ja descriptions to reflect this broader detection behavior.
|
Updated PR description to reflect the latest implementation changes since the original submission:
|

Summary
context_management,anthropic_beta,output_config) when error messages don't specify which fields are unsupportederrorkey and reclassify them asUpstreamError, enabling rectifiers and failover to triggerTest plan
cargo clippy -- -D warningspassescargo fmt --checkpasses{"error":...}— error-key detection → extra inputs rectifier chain triggers correctly