Rebuild request payload on cross-protocol failover retries#18
Conversation
Agent-Logs-Url: https://github.com/crazyrob425/BlacklistedAIProxy/sessions/3024c79a-0f3e-4350-a430-1412955bb47e Co-authored-by: crazyrob425 <247058665+crazyrob425@users.noreply.github.com>
There was a problem hiding this comment.
AI Code Review by LlamaPReview
🎯 TL;DR & Recommendation
Recommendation: Approve with suggestions.
This PR adds request reconstruction for cross-protocol failover retries, ensuring correct provider conversion. However, the implementation introduces maintainability concerns and a speculative risk regarding the origin of originalRequestBody.
📄 Documentation Diagram
This diagram documents the refactored cross-protocol failover retry flow.
sequenceDiagram
participant Client
participant Handler
participant RebuildFn as rebuildRequestBodyForProtocolRetry
Client->>Handler: Original request
Handler->>Handler: Process & convert for initial provider
Note over Handler: PR #35;18: retry context carries originalRequestBody
Handler->>Handler: Retry triggered
alt Protocol change detected
Handler->>RebuildFn: rebuildRequestBodyForProtocolRetry(...)
RebuildFn->>RebuildFn: Clone & convert to new provider
RebuildFn-->>Handler: Rebuilt request body
else Same protocol
Handler->>Handler: Reuse current body
end
Handler->>Client: Response
🌟 Strengths
- Changes address the cross-protocol payload corruption issue, critical for reliability.
- Unit tests pass, indicating basic correctness.
| Priority | File | Category | Impact Summary | Anchors |
|---|---|---|---|---|
| P2 | src/utils/common.js | Bug | Potential double conversion on cross-protocol retries | |
| P2 | src/utils/common.js | Maintainability | Deep clone pattern may drop non-serializable fields | |
| P2 | src/utils/common.js | Maintainability | Duplicated retry rebuild logic increases maintenance debt |
🔍 Notable Themes
- Maintainability concerns: duplication of retry rebuild logic and use of JSON.stringify deep clone pattern.
💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.
|
|
||
| async function rebuildRequestBodyForProtocolRetry(originalRequestBody, currentRequestBody, fromProvider, targetToProvider, config) { | ||
| // 使用原始请求作为转换源,避免跨协议重试时叠加转换导致请求体损坏 | ||
| let rebuiltRequestBody = JSON.parse(JSON.stringify(originalRequestBody || {})); |
There was a problem hiding this comment.
P2 | Confidence: High
Using JSON.parse(JSON.stringify(…)) for deep cloning will silently strip undefined values, Symbol keys, functions, and convert Date objects to strings. While incoming request bodies are likely plain JSON‑compatible objects, this pattern introduces a latent risk if future enhancements add non‑serializable runtime fields (e.g., a function placeholder, undefined flags). Prefer a structured clone helper or a shallow copy that explicitly preserves required properties. The risk is low but grows as the codebase evolves.
| let rebuiltRequestBody = JSON.parse(JSON.stringify(originalRequestBody || {})); | |
| // Option 1: Use structuredClone if Node.js >= 17 (check runtime) | |
| let rebuiltRequestBody = structuredClone(originalRequestBody || {}); | |
| // Option 2: Manual copy of expected keys (more explicit) | |
| // let rebuiltRequestBody = { ...originalRequestBody }; |
| if (getProtocolPrefix(toProvider) !== getProtocolPrefix(retryToProvider)) { | ||
| retryRequestBody = await rebuildRequestBodyForProtocolRetry( | ||
| retryContext?.originalRequestBody, | ||
| requestBody, | ||
| fromProvider, | ||
| retryToProvider, | ||
| CONFIG | ||
| ); | ||
| } |
There was a problem hiding this comment.
P2 | Confidence: High
The same protocol‑check and rebuild logic is duplicated in both handleStreamRequest and handleUnaryRequest. This duplicates maintenance burden and increases the chance of divergence. Consider extracting a small helper (e.g., maybeRebuildForRetry) to encapsulate the condition and call. While acceptable for a small PR, it adds technical debt.
Code Suggestion:
async function maybeRebuildForRetry(requestBody, toProvider, retryToProvider, fromProvider, retryContext, CONFIG) {
if (getProtocolPrefix(toProvider) !== getProtocolPrefix(retryToProvider)) {
return await rebuildRequestBodyForProtocolRetry(
retryContext?.originalRequestBody,
requestBody,
fromProvider,
retryToProvider,
CONFIG
);
}
return requestBody;
}| // 当没有不同的健康凭证可用时,重试会自动停止 | ||
| const credentialSwitchMaxRetries = CONFIG.CREDENTIAL_SWITCH_MAX_RETRIES || 5; | ||
| const retryContext = { CONFIG, currentRetry: 0, maxRetries: credentialSwitchMaxRetries }; | ||
| const retryContext = { CONFIG, currentRetry: 0, maxRetries: credentialSwitchMaxRetries, originalRequestBody }; |
There was a problem hiding this comment.
P2 | Confidence: Medium
Speculative: The variable originalRequestBody is used to seed the retry context, but its origin is not shown in the diff. The PR description states it should be the “immutable original client payload”. However, the surrounding code creates the retry context right after processedRequestBody is computed (which already has provider-specific transformations applied). If originalRequestBody is actually the already-converted processedRequestBody, then when rebuildRequestBodyForProtocolRetry deep‑clones it and later calls convertData from the original source provider to a new target provider, the result will be a double‑conversion – first to the initial provider’s format, then to the new provider’s format. This would produce a malformed request. Without the variable definition (outside the visible diff), the correctness cannot be confirmed. Unit tests passing reduces the likelihood, but they may not cover cross‑protocol failovers.
Current in-request failover correctly reselects adapters, but retry recursion reused a request body already converted for the previous provider. That is safe for same-protocol retries and unsafe when fallback switches protocol (e.g., Gemini ↔ OpenAI/Claude).
What changed
handleStreamRequest(before recursive retry call)handleUnaryRequest(before recursive retry call)anyDataSent(no restart after downstream bytes are emitted).Request reconstruction logic
rebuildRequestBodyForProtocolRetry(...)insrc/utils/common.js.originalRequestBody__applySystemPromptFromFile, custom model parameters)Retry context propagation
originalRequestBody, enabling deterministic rebuild on recursive retries without re-reading request streams.