feat: improve channel override ui/ux - #3009
Conversation
…exible header operations
…ust JSON fallback
# Conflicts: # relay/channel/api_request_test.go # relay/common/override_test.go # web/src/components/table/channels/modals/EditChannelModal.jsx
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughReplaces ApplyParamOverride usage with ApplyParamOverrideWithRelayInfo across handlers; significantly expands the param-override subsystem (new modes, header/runtime override handling, pruning, sync_fields, structured return_error); enriches RelayInfo with headers, retry/last-error and runtime header overrides; wires per-rule channel-affinity param templates and UI editors; updates middleware, tests, and header override normalization. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(52,120,246,0.5)
participant Client
end
rect rgba(34,197,94,0.5)
participant Middleware
end
rect rgba(236,72,153,0.5)
participant Relay
end
rect rgba(249,115,22,0.5)
participant ParamOverrideEngine
end
rect rgba(107,114,128,0.5)
participant Upstream
end
Client->>Middleware: HTTP request (headers, body)
Middleware->>Relay: Setup RelayInfo (RequestHeaders, Channel overrides, ParamTemplate)
Relay->>ParamOverrideEngine: ApplyParamOverrideWithRelayInfo(jsonData, RelayInfo)
ParamOverrideEngine-->>Relay: modified jsonData OR ParamOverrideReturnError
alt override returned API error
Relay->>Client: respond with translated API error (newAPIErrorFromParamOverride)
else success
Relay->>Upstream: forward modified request (headers/body)
Upstream-->>Client: upstream response
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
relay/common/override.go (1)
116-130:⚠️ Potential issue | 🟠 MajorInvalid
operationsconfig is silently treated as legacy override.When
operationsexists but parse fails (e.g., Line [201]-Line [204]),tryParseOperationsreturnsok=false, andApplyParamOverridefalls back toapplyOperationsLegacyon Line [129]. This masks misconfiguration and mutates payload in an unintended way instead of failing fast.🛠️ Proposed fix
-func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) { +func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) { if len(paramOverride) == 0 { return jsonData, nil } - // 尝试断言为操作格式 - if operations, ok := tryParseOperations(paramOverride); ok { + operations, ok, parseErr := tryParseOperations(paramOverride) + if parseErr != nil { + return nil, parseErr + } + if ok { // 使用新方法 result, err := applyOperations(string(jsonData), operations, conditionContext) return []byte(result), err } // 直接使用旧方法 return applyOperationsLegacy(jsonData, paramOverride) } -func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation, bool) { +func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation, bool, error) { // 检查是否包含 "operations" 字段 if opsValue, exists := paramOverride["operations"]; exists { if opsSlice, ok := opsValue.([]interface{}); ok { var operations []ParamOperation for _, op := range opsSlice { if opMap, ok := op.(map[string]interface{}); ok { operation := ParamOperation{} @@ if conditions, exists := opMap["conditions"]; exists { parsedConditions, err := parseConditionOperations(conditions) if err != nil { - return nil, false + return nil, false, fmt.Errorf("invalid operations.conditions: %w", err) } operation.Conditions = append(operation.Conditions, parsedConditions...) } operations = append(operations, operation) } else { - return nil, false + return nil, false, fmt.Errorf("operation must be object") } } - return operations, true + return operations, true, nil } + return nil, false, fmt.Errorf("operations must be array") } - return nil, false + return nil, false, nil }Also applies to: 161-218
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/common/override.go` around lines 116 - 130, The function ApplyParamOverride currently falls back to applyOperationsLegacy when tryParseOperations fails, silently masking malformed "operations" configs; update ApplyParamOverride to detect if paramOverride contains the "operations" key and, when tryParseOperations returns ok==false, return a descriptive error instead of falling back to applyOperationsLegacy; specifically, in ApplyParamOverride check for _, exists := paramOverride["operations"] (or equivalent), and if exists && !ok return an error mentioning invalid operations payload, otherwise proceed to call applyOperations (on success) or applyOperationsLegacy (when no "operations" key).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/channel/api_request.go`:
- Around line 176-179: The code sets headerOverrideSource to
info.RuntimeHeadersOverride when info.UseRuntimeHeadersOverride is true, but if
RuntimeHeadersOverride is nil that silently discards info.HeadersOverride;
change the selection to nil-guard and fallback: when
info.UseRuntimeHeadersOverride is true and info.RuntimeHeadersOverride is
non-nil use it, otherwise fall back to info.HeadersOverride (and ensure
headerOverrideSource is never left nil before iterating). Update the logic
around headerOverrideSource (the variables info.HeadersOverride,
info.RuntimeHeadersOverride and flag UseRuntimeHeadersOverride) to perform this
guarded selection so static overrides are preserved when runtime overrides are
nil.
In `@web/package.json`:
- Line 13: Update the axios dependency in package.json from "1.12.0" to a
patched release (at least "1.13.5") to address CVE-2026-25639; modify the
"axios" entry so the version string is "1.13.5" (or a later compatible semver
range) and run npm/yarn install to update lockfile and verify builds/tests that
reference axios in the project.
In `@web/src/components/table/channels/modals/EditChannelModal.jsx`:
- Around line 707-760: In applyParamOverrideTemplate, parseParamOverrideInput()
is called unconditionally which causes 'fill' operations to fail when current
JSON is invalid; modify applyParamOverrideTemplate so it checks applyMode (and
templateType for 'legacy') first and returns the fill content via
handleInputChange('param_override', JSON.stringify(...)) without calling
parseParamOverrideInput(), and only call parseParamOverrideInput() when
applyMode !== 'fill' and you need to merge with existing content (used in the
legacy merge branch and operations-merge branch) so parsing is only attempted
when actually required.
---
Outside diff comments:
In `@relay/common/override.go`:
- Around line 116-130: The function ApplyParamOverride currently falls back to
applyOperationsLegacy when tryParseOperations fails, silently masking malformed
"operations" configs; update ApplyParamOverride to detect if paramOverride
contains the "operations" key and, when tryParseOperations returns ok==false,
return a descriptive error instead of falling back to applyOperationsLegacy;
specifically, in ApplyParamOverride check for _, exists :=
paramOverride["operations"] (or equivalent), and if exists && !ok return an
error mentioning invalid operations payload, otherwise proceed to call
applyOperations (on success) or applyOperationsLegacy (when no "operations"
key).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
controller/channel-test.gocontroller/relay.gorelay/channel/api_request.gorelay/channel/api_request_test.gorelay/chat_completions_via_responses.gorelay/claude_handler.gorelay/common/override.gorelay/common/override_test.gorelay/common/relay_info.gorelay/compatible_handler.gorelay/embedding_handler.gorelay/gemini_handler.gorelay/image_handler.gorelay/param_override_error.gorelay/rerank_handler.gorelay/responses_handler.goweb/package.jsonweb/src/components/table/channels/modals/EditChannelModal.jsxweb/src/components/table/channels/modals/ParamOverrideEditorModal.jsx
| "@visactor/vchart": "~1.8.8", | ||
| "@visactor/vchart-semi-theme": "~1.8.8", | ||
| "axios": "1.13.5", | ||
| "axios": "1.12.0", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n web/package.json | head -20Repository: QuantumNous/new-api
Length of output: 768
🏁 Script executed:
cd web && git log -p --follow -S "axios" -- package.json | head -100Repository: QuantumNous/new-api
Length of output: 45
🌐 Web query:
axios 1.13.5 security fix DoS mergeConfig __proto__
💡 Result:
Axios v1.13.5 includes a security fix for CVE-2026-25639 / GHSA-43fc-jf86-j433: a Denial of Service (DoS) crash triggered when mergeConfig processes a config object that has an own __proto__ property (commonly introduced via JSON.parse('{"__proto__": {...}}')). In vulnerable versions, mergeMap['__proto__'] resolves via the prototype chain to Object.prototype and is then invoked as a function, throwing TypeError: merge is not a function and potentially crashing a Node.js process. [1][2]
Affected: axios <= 1.13.4 (also <= 0.30.2)
Patched: 1.13.5 (also 0.30.3) [1][3]
What changed in the fix (PR #7369 / commit 28c7215):
mergeConfignow skips keys__proto__,constructor, andprototype.- It also ensures
mergeMap[prop]is only used whenpropis an own property ofmergeMap(avoids prototype-chain lookups). [2][4]
Action: upgrade to axios 1.13.5+ (or 0.30.3+ if you’re on that line). [1][3]
Sources
- GitHub Security Advisory GHSA-43fc-jf86-j433 (axios)
- Axios v1.13.5 release notes
- NVD entry for CVE-2026-25639
- Fix commit (axios/axios@28c7215)
🌐 Web query:
axios 1.12.0 1.13.5 release notes changelog
💡 Result:
Axios v1.12.0 (released 2025-09-11) — release notes / changelog
Bug fixes: fetch-adapter Content-Type for Node FormData; enforce maxContentLength for data: URLs; package exports; URL param encoding change; type-guard change for isCancel; plus release/build housekeeping. [2]
Features: surface low-level network error details via cause; fetch adapter env config (fetch, Request, Response); JSON.parse reviver support; extend AxiosResponse to allow custom headers type. [2]
Security context: 1.12.0 is the patched version for GHSA-4hjh-wcwx-xvwj (DoS via large data: URL / lack of size check). [3]
Axios v1.13.5 (released 2026-02-08) — release notes / changelog
Highlights: security fix for DoS via __proto__ key in mergeConfig; fix missing AxiosError.status regression (on/after v1.13.3). [1]
Other changes: input validation for isAbsoluteURL; dependency bumps/refactors; docs + CI maintenance items. [1]
GitHub “Full Changelog” compare links (official):
v1.12.0: https://github.com/axios/axios/compare/v1.11.0...v1.12.0
v1.13.5: https://github.com/axios/axios/compare/v1.13.4...v1.13.5
Upgrade axios to a patched version to fix DoS vulnerability.
At Line 13, the pinned version 1.12.0 is vulnerable to CVE-2026-25639 (DoS crash via __proto__ key in mergeConfig), which was patched in 1.13.5. Upgrade to 1.13.5 or later.
🔧 Proposed change
- "axios": "1.12.0",
+ "axios": "1.13.5",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "axios": "1.12.0", | |
| "axios": "1.13.5", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/package.json` at line 13, Update the axios dependency in package.json
from "1.12.0" to a patched release (at least "1.13.5") to address
CVE-2026-25639; modify the "axios" entry so the version string is "1.13.5" (or a
later compatible semver range) and run npm/yarn install to update lockfile and
verify builds/tests that reference axios in the project.
| const applyParamOverrideTemplate = ( | ||
| templateType = 'operations', | ||
| applyMode = 'fill', | ||
| ) => { | ||
| try { | ||
| const parsedCurrent = parseParamOverrideInput(); | ||
| if (templateType === 'legacy') { | ||
| if (applyMode === 'fill') { | ||
| handleInputChange( | ||
| 'param_override', | ||
| JSON.stringify(PARAM_OVERRIDE_LEGACY_TEMPLATE, null, 2), | ||
| ); | ||
| return; | ||
| } | ||
| const currentLegacy = | ||
| parsedCurrent && | ||
| typeof parsedCurrent === 'object' && | ||
| !Array.isArray(parsedCurrent) && | ||
| !Array.isArray(parsedCurrent.operations) | ||
| ? parsedCurrent | ||
| : {}; | ||
| const merged = { | ||
| ...PARAM_OVERRIDE_LEGACY_TEMPLATE, | ||
| ...currentLegacy, | ||
| }; | ||
| handleInputChange('param_override', JSON.stringify(merged, null, 2)); | ||
| return; | ||
| } | ||
|
|
||
| if (applyMode === 'fill') { | ||
| handleInputChange( | ||
| 'param_override', | ||
| JSON.stringify(PARAM_OVERRIDE_OPERATIONS_TEMPLATE, null, 2), | ||
| ); | ||
| return; | ||
| } | ||
| const currentOperations = | ||
| parsedCurrent && | ||
| typeof parsedCurrent === 'object' && | ||
| !Array.isArray(parsedCurrent) && | ||
| Array.isArray(parsedCurrent.operations) | ||
| ? parsedCurrent.operations | ||
| : []; | ||
| const merged = { | ||
| operations: [ | ||
| ...currentOperations, | ||
| ...PARAM_OVERRIDE_OPERATIONS_TEMPLATE.operations, | ||
| ], | ||
| }; | ||
| handleInputChange('param_override', JSON.stringify(merged, null, 2)); | ||
| } catch (error) { | ||
| showError(error.message || t('模板应用失败')); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Template “fill” should bypass current JSON parsing.
On Line [712], parseParamOverrideInput() runs before checking applyMode. With invalid current JSON, the fill actions on Line [736] and Line [713] fail instead of replacing content. Fill should work regardless of current validity.
💡 Proposed fix
const applyParamOverrideTemplate = (
templateType = 'operations',
applyMode = 'fill',
) => {
try {
- const parsedCurrent = parseParamOverrideInput();
+ const parsedCurrent =
+ applyMode === 'fill' ? null : parseParamOverrideInput();
if (templateType === 'legacy') {
if (applyMode === 'fill') {
handleInputChange(
'param_override',
JSON.stringify(PARAM_OVERRIDE_LEGACY_TEMPLATE, null, 2),
);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const applyParamOverrideTemplate = ( | |
| templateType = 'operations', | |
| applyMode = 'fill', | |
| ) => { | |
| try { | |
| const parsedCurrent = parseParamOverrideInput(); | |
| if (templateType === 'legacy') { | |
| if (applyMode === 'fill') { | |
| handleInputChange( | |
| 'param_override', | |
| JSON.stringify(PARAM_OVERRIDE_LEGACY_TEMPLATE, null, 2), | |
| ); | |
| return; | |
| } | |
| const currentLegacy = | |
| parsedCurrent && | |
| typeof parsedCurrent === 'object' && | |
| !Array.isArray(parsedCurrent) && | |
| !Array.isArray(parsedCurrent.operations) | |
| ? parsedCurrent | |
| : {}; | |
| const merged = { | |
| ...PARAM_OVERRIDE_LEGACY_TEMPLATE, | |
| ...currentLegacy, | |
| }; | |
| handleInputChange('param_override', JSON.stringify(merged, null, 2)); | |
| return; | |
| } | |
| if (applyMode === 'fill') { | |
| handleInputChange( | |
| 'param_override', | |
| JSON.stringify(PARAM_OVERRIDE_OPERATIONS_TEMPLATE, null, 2), | |
| ); | |
| return; | |
| } | |
| const currentOperations = | |
| parsedCurrent && | |
| typeof parsedCurrent === 'object' && | |
| !Array.isArray(parsedCurrent) && | |
| Array.isArray(parsedCurrent.operations) | |
| ? parsedCurrent.operations | |
| : []; | |
| const merged = { | |
| operations: [ | |
| ...currentOperations, | |
| ...PARAM_OVERRIDE_OPERATIONS_TEMPLATE.operations, | |
| ], | |
| }; | |
| handleInputChange('param_override', JSON.stringify(merged, null, 2)); | |
| } catch (error) { | |
| showError(error.message || t('模板应用失败')); | |
| } | |
| }; | |
| const applyParamOverrideTemplate = ( | |
| templateType = 'operations', | |
| applyMode = 'fill', | |
| ) => { | |
| try { | |
| const parsedCurrent = | |
| applyMode === 'fill' ? null : parseParamOverrideInput(); | |
| if (templateType === 'legacy') { | |
| if (applyMode === 'fill') { | |
| handleInputChange( | |
| 'param_override', | |
| JSON.stringify(PARAM_OVERRIDE_LEGACY_TEMPLATE, null, 2), | |
| ); | |
| return; | |
| } | |
| const currentLegacy = | |
| parsedCurrent && | |
| typeof parsedCurrent === 'object' && | |
| !Array.isArray(parsedCurrent) && | |
| !Array.isArray(parsedCurrent.operations) | |
| ? parsedCurrent | |
| : {}; | |
| const merged = { | |
| ...PARAM_OVERRIDE_LEGACY_TEMPLATE, | |
| ...currentLegacy, | |
| }; | |
| handleInputChange('param_override', JSON.stringify(merged, null, 2)); | |
| return; | |
| } | |
| if (applyMode === 'fill') { | |
| handleInputChange( | |
| 'param_override', | |
| JSON.stringify(PARAM_OVERRIDE_OPERATIONS_TEMPLATE, null, 2), | |
| ); | |
| return; | |
| } | |
| const currentOperations = | |
| parsedCurrent && | |
| typeof parsedCurrent === 'object' && | |
| !Array.isArray(parsedCurrent) && | |
| Array.isArray(parsedCurrent.operations) | |
| ? parsedCurrent.operations | |
| : []; | |
| const merged = { | |
| operations: [ | |
| ...currentOperations, | |
| ...PARAM_OVERRIDE_OPERATIONS_TEMPLATE.operations, | |
| ], | |
| }; | |
| handleInputChange('param_override', JSON.stringify(merged, null, 2)); | |
| } catch (error) { | |
| showError(error.message || t('模板应用失败')); | |
| } | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/table/channels/modals/EditChannelModal.jsx` around lines
707 - 760, In applyParamOverrideTemplate, parseParamOverrideInput() is called
unconditionally which causes 'fill' operations to fail when current JSON is
invalid; modify applyParamOverrideTemplate so it checks applyMode (and
templateType for 'legacy') first and returns the fill content via
handleInputChange('param_override', JSON.stringify(...)) without calling
parseParamOverrideInput(), and only call parseParamOverrideInput() when
applyMode !== 'fill' and you need to merge with existing content (used in the
legacy merge branch and operations-merge branch) so parsing is only attempted
when actually required.
… first rule match
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
setting/operation_setting/channel_affinity_setting.go (1)
37-59: Prevent backend/frontend template drift.These header lists and rule templates are duplicated in
web/src/constants/channel-affinity-template.constants.js. Consider sourcing defaults from one authoritative place (e.g., backend-provided defaults) to avoid silent divergence later.Also applies to: 82-110
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@setting/operation_setting/channel_affinity_setting.go` around lines 37 - 59, The duplicated header lists codexCliPassThroughHeaders and claudeCliPassThroughHeaders are causing template drift between backend and frontend; centralize these defaults by exporting a single source of truth from the backend and updating the frontend to consume it (or load them from a shared config/endpoint) instead of hardcoding; modify the code around codexCliPassThroughHeaders and claudeCliPassThroughHeaders to load values from the new backend-provided constant or API (and remove the duplicate arrays), and ensure the corresponding frontend constant in channel-affinity-template.constants.js references that backend source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/pages/Setting/Operation/SettingsChannelAffinity.jsx`:
- Around line 177-191: parseOptionalObjectJson currently returns dynamic Chinese
strings using the label; change it to return fixed i18n keys (and optional
params) instead of interpolated messages so translators remain stable.
Specifically, update parseOptionalObjectJson (and places using verifyJSON) to
return something like { ok: false, key: 'settings.json_invalid' } or { ok:false,
key: 'settings.json_must_be_object' , params: { label } } and then update the
caller that wraps results with t(...) to call t(result.key, result.params)
(using useTranslation in the component) rather than t(result.message). This
preserves the label as a param for interpolation while ensuring message keys are
static for translation.
---
Nitpick comments:
In `@setting/operation_setting/channel_affinity_setting.go`:
- Around line 37-59: The duplicated header lists codexCliPassThroughHeaders and
claudeCliPassThroughHeaders are causing template drift between backend and
frontend; centralize these defaults by exporting a single source of truth from
the backend and updating the frontend to consume it (or load them from a shared
config/endpoint) instead of hardcoding; modify the code around
codexCliPassThroughHeaders and claudeCliPassThroughHeaders to load values from
the new backend-provided constant or API (and remove the duplicate arrays), and
ensure the corresponding frontend constant in
channel-affinity-template.constants.js references that backend source.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
middleware/distributor.goservice/channel_affinity.goservice/channel_affinity_template_test.gosetting/operation_setting/channel_affinity_setting.goweb/src/components/table/channels/modals/ParamOverrideEditorModal.jsxweb/src/constants/channel-affinity-template.constants.jsweb/src/constants/index.jsweb/src/pages/Setting/Operation/SettingsChannelAffinity.jsx
| const parseOptionalObjectJson = (jsonString, label) => { | ||
| const raw = (jsonString || '').trim(); | ||
| if (!raw) return { ok: true, value: null }; | ||
| if (!verifyJSON(raw)) { | ||
| return { ok: false, message: `${label} JSON 格式不正确` }; | ||
| } | ||
| try { | ||
| const parsed = JSON.parse(raw); | ||
| if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { | ||
| return { ok: false, message: `${label} 必须是 JSON 对象` }; | ||
| } | ||
| return { ok: true, value: parsed }; | ||
| } catch (error) { | ||
| return { ok: false, message: `${label} JSON 格式不正确` }; | ||
| } |
There was a problem hiding this comment.
Use static i18n keys for validation errors.
Line 181, Line 186, and Line 190 compose messages dynamically (${label}...) and Line 703 sends them into t(...). Prefer fixed message keys/constants to keep translations stable and maintainable.
♻️ Suggested adjustment
+const MSG_PARAM_TEMPLATE_JSON_INVALID = '参数覆盖模板 JSON 格式不正确';
+const MSG_PARAM_TEMPLATE_JSON_OBJECT = '参数覆盖模板 必须是 JSON 对象';
+
-const parseOptionalObjectJson = (jsonString, label) => {
+const parseOptionalObjectJson = (jsonString) => {
const raw = (jsonString || '').trim();
if (!raw) return { ok: true, value: null };
if (!verifyJSON(raw)) {
- return { ok: false, message: `${label} JSON 格式不正确` };
+ return { ok: false, message: MSG_PARAM_TEMPLATE_JSON_INVALID };
}
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
- return { ok: false, message: `${label} 必须是 JSON 对象` };
+ return { ok: false, message: MSG_PARAM_TEMPLATE_JSON_OBJECT };
}
return { ok: true, value: parsed };
} catch (error) {
- return { ok: false, message: `${label} JSON 格式不正确` };
+ return { ok: false, message: MSG_PARAM_TEMPLATE_JSON_INVALID };
}
};
...
-const paramTemplateValidation = parseOptionalObjectJson(
- paramTemplateDraft,
- '参数覆盖模板',
-);
+const paramTemplateValidation = parseOptionalObjectJson(paramTemplateDraft);Based on learnings: Applies to web/src/**/*.{ts,tsx} : Use useTranslation() hook and call t('中文key') in React components for translation lookup.
Also applies to: 698-704
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/pages/Setting/Operation/SettingsChannelAffinity.jsx` around lines 177
- 191, parseOptionalObjectJson currently returns dynamic Chinese strings using
the label; change it to return fixed i18n keys (and optional params) instead of
interpolated messages so translators remain stable. Specifically, update
parseOptionalObjectJson (and places using verifyJSON) to return something like {
ok: false, key: 'settings.json_invalid' } or { ok:false, key:
'settings.json_must_be_object' , params: { label } } and then update the caller
that wraps results with t(...) to call t(result.key, result.params) (using
useTranslation in the component) rather than t(result.message). This preserves
the label as a param for interpolation while ensuring message keys are static
for translation.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
relay/common/override.go (1)
118-131:⚠️ Potential issue | 🟠 MajorDo not silently fall back to legacy mode when
operationsparsing fails.When condition parsing fails (Line 203),
tryParseOperationsreturnsfalse, andApplyParamOverridefalls back toapplyOperationsLegacy(Line 130). That turns an invalid override config into a payload merge instead of a hard failure, which can bypass intended override behavior.Proposed fix
-func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) { +func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) { if len(paramOverride) == 0 { return jsonData, nil } - - // 尝试断言为操作格式 - if operations, ok := tryParseOperations(paramOverride); ok { - // 使用新方法 - result, err := applyOperations(string(jsonData), operations, conditionContext) - return []byte(result), err - } + if opsValue, exists := paramOverride["operations"]; exists { + operations, err := tryParseOperationsValue(opsValue) + if err != nil { + return nil, err + } + result, err := applyOperations(string(jsonData), operations, conditionContext) + return []byte(result), err + } // 直接使用旧方法 return applyOperationsLegacy(jsonData, paramOverride) }-func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation, bool) { - if opsValue, exists := paramOverride["operations"]; exists { +func tryParseOperationsValue(opsValue interface{}) ([]ParamOperation, error) { + if opsValue != nil { if opsSlice, ok := opsValue.([]interface{}); ok { var operations []ParamOperation for _, op := range opsSlice { if opMap, ok := op.(map[string]interface{}); ok { operation := ParamOperation{} @@ - if mode, ok := opMap["mode"].(string); ok { + if mode, ok := opMap["mode"].(string); ok { operation.Mode = mode } else { - return nil, false // mode 是必需的 + return nil, fmt.Errorf("operation mode is required") } @@ if conditions, exists := opMap["conditions"]; exists { parsedConditions, err := parseConditionOperations(conditions) if err != nil { - return nil, false + return nil, err } operation.Conditions = append(operation.Conditions, parsedConditions...) } @@ - return nil, false + return nil, fmt.Errorf("operation item must be object") } } - return operations, true + return operations, nil } + return nil, fmt.Errorf("operations must be array") } - - return nil, false + return nil, fmt.Errorf("operations value is required") }Also applies to: 203-206
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/common/override.go` around lines 118 - 131, The ApplyParamOverride function currently silently falls back to applyOperationsLegacy when tryParseOperations fails; change this so a failed parse returns a clear error instead of treating the override as legacy mode. Update ApplyParamOverride to detect a false ok from tryParseOperations and return a descriptive error (including the original paramOverride or reason) rather than calling applyOperationsLegacy; alternatively adjust tryParseOperations to return (operations, error) and propagate that error into ApplyParamOverride so callers see a hard failure instead of a silent payload merge (referencing ApplyParamOverride, tryParseOperations, applyOperations, and applyOperationsLegacy).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/channel/api_request_test.go`:
- Around line 148-156: The test never supplies the "X-Codex-Beta-Features"
header so the exclusion branch for that header in the pass_headers check isn't
exercised; update the test to set
ctx.Request.Header.Set("X-Codex-Beta-Features", "some-value") and include the
same key/value in info.RequestHeaders (the RelayInfo struct) before calling the
code that uses pass_headers, then assert that the header is excluded as expected
(or that code paths 177-178 and 184-185 behave accordingly) to validate the
exclusion logic.
In `@relay/common/override.go`:
- Around line 668-680: moveHeaderInContext currently deletes the source header
only from override maps (via deleteHeaderOverrideInContext) so
getHeaderValueFromContext can still read the original value from
request_headers/request_headers_raw; update moveHeaderInContext to, after
calling copyHeaderInContext and when fromHeader != toHeader, also remove the
source header keys from any request header storage (request_headers and
request_headers_raw) in the context so the header is truly moved. Ensure you use
the same header normalization as copyHeaderInContext/getHeaderValueFromContext
when deleting, and apply the same fix to the other move-header codepaths that
mirror this logic (the other move_header blocks that call
copyHeaderInContext/deleteHeaderOverrideInContext) so all request-sourced
headers are removed from request_* maps when moved.
---
Outside diff comments:
In `@relay/common/override.go`:
- Around line 118-131: The ApplyParamOverride function currently silently falls
back to applyOperationsLegacy when tryParseOperations fails; change this so a
failed parse returns a clear error instead of treating the override as legacy
mode. Update ApplyParamOverride to detect a false ok from tryParseOperations and
return a descriptive error (including the original paramOverride or reason)
rather than calling applyOperationsLegacy; alternatively adjust
tryParseOperations to return (operations, error) and propagate that error into
ApplyParamOverride so callers see a hard failure instead of a silent payload
merge (referencing ApplyParamOverride, tryParseOperations, applyOperations, and
applyOperationsLegacy).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
relay/channel/api_request_test.gorelay/common/override.gorelay/common/override_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- relay/common/override_test.go
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
relay/common/override.go (1)
758-770:⚠️ Potential issue | 🟠 Major
move_headerstill behaves likecopy_headerfor request-sourced headers.After move, the source header is removed from override maps only. It remains readable from
request_headers/request_headers_raw, so subsequent operations can still consume it.💡 Proposed fix
func moveHeaderInContext(context map[string]interface{}, fromHeader, toHeader string, keepOrigin bool) error { @@ if strings.EqualFold(fromHeader, toHeader) { return nil } - return deleteHeaderOverrideInContext(context, fromHeader) + if err := deleteHeaderOverrideInContext(context, fromHeader); err != nil { + return err + } + deleteHeaderFromRequestContext(context, fromHeader) + return nil } + +func deleteHeaderFromRequestContext(context map[string]interface{}, headerName string) { + for key := range ensureMapKeyInContext(context, paramOverrideContextRequestHeadersRaw) { + if strings.EqualFold(strings.TrimSpace(key), headerName) { + delete(ensureMapKeyInContext(context, paramOverrideContextRequestHeadersRaw), key) + } + } + normalized := normalizeHeaderContextKey(headerName) + if normalized != "" { + delete(ensureMapKeyInContext(context, paramOverrideContextRequestHeaders), normalized) + } +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/common/override.go` around lines 758 - 770, moveHeaderInContext currently only removes the source header from override maps (via deleteHeaderOverrideInContext) so headers coming from request_headers/request_headers_raw remain readable; update moveHeaderInContext to also remove the source header from request-sourced maps when a true move is performed: after copyHeaderInContext and the EqualFold check, delete the header key from context["request_headers"] and context["request_headers_raw"] (handle case-insensitive keys or both raw/normalized forms as used elsewhere) so the header is fully moved; reference moveHeaderInContext, copyHeaderInContext, deleteHeaderOverrideInContext, request_headers and request_headers_raw.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/common/override.go`:
- Around line 732-734: When setting header_override in override.go,
ensureMapKeyInContext returns rawHeaders but you must deduplicate existing keys
case-insensitively before inserting headerName/headerValue; scan rawHeaders for
an existingKey where strings.EqualFold(existingKey, headerName) and if found and
existingKey != headerName delete rawHeaders[existingKey] then set
rawHeaders[headerName] = headerValue. This preserves deterministic overwrite
semantics for subsequent reads/merges while still using ensureMapKeyInContext
and paramOverrideContextHeaderOverride.
- Around line 282-285: The current code silently falls back to legacy override
behavior when parseConditionOperations(conditions) errors, allowing the raw
operations payload to be injected; change this to fail fast by returning the
parse error (or a wrapped error) instead of returning nil, false so callers
cannot continue into legacy processing—specifically update the handling around
parseConditionOperations/parsedConditions to return the error to the caller (or
propagate it via the function's error return) and ensure any code path that
would inject the original operations payload is skipped when parsing fails.
---
Duplicate comments:
In `@relay/common/override.go`:
- Around line 758-770: moveHeaderInContext currently only removes the source
header from override maps (via deleteHeaderOverrideInContext) so headers coming
from request_headers/request_headers_raw remain readable; update
moveHeaderInContext to also remove the source header from request-sourced maps
when a true move is performed: after copyHeaderInContext and the EqualFold
check, delete the header key from context["request_headers"] and
context["request_headers_raw"] (handle case-insensitive keys or both
raw/normalized forms as used elsewhere) so the header is fully moved; reference
moveHeaderInContext, copyHeaderInContext, deleteHeaderOverrideInContext,
request_headers and request_headers_raw.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
relay/channel/api_request.gorelay/channel/api_request_test.gorelay/common/override.gorelay/common/override_test.gorelay/common/relay_info.go
🚧 Files skipped from review as they are similar to previous changes (2)
- relay/channel/api_request.go
- relay/channel/api_request_test.go
| parsedConditions, err := parseConditionOperations(conditions) | ||
| if err != nil { | ||
| return nil, false | ||
| } |
There was a problem hiding this comment.
Do not silently downgrade invalid operations to legacy override behavior.
When condition parsing fails here, control falls back to legacy mode, which can inject the raw operations payload into the request body instead of failing fast.
💡 Proposed fix
func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) {
if len(paramOverride) == 0 {
return jsonData, nil
}
+ _, hasOperations := paramOverride["operations"]
// 尝试断言为操作格式
if operations, ok := tryParseOperations(paramOverride); ok {
// 使用新方法
result, err := applyOperations(string(jsonData), operations, conditionContext)
return []byte(result), err
}
+ if hasOperations {
+ return nil, fmt.Errorf("invalid param_override.operations format")
+ }
// 直接使用旧方法
return applyOperationsLegacy(jsonData, paramOverride)
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@relay/common/override.go` around lines 282 - 285, The current code silently
falls back to legacy override behavior when parseConditionOperations(conditions)
errors, allowing the raw operations payload to be injected; change this to fail
fast by returning the parse error (or a wrapped error) instead of returning nil,
false so callers cannot continue into legacy processing—specifically update the
handling around parseConditionOperations/parsedConditions to return the error to
the caller (or propagate it via the function's error return) and ensure any code
path that would inject the original operations payload is skipped when parsing
fails.
| rawHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverride) | ||
| rawHeaders[headerName] = headerValue | ||
|
|
There was a problem hiding this comment.
Deduplicate case-insensitive header keys when setting header_override.
This write path can leave both X-Trace and x-trace in the raw map, which breaks deterministic overwrite semantics for later reads/merges.
💡 Proposed fix
rawHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverride)
- rawHeaders[headerName] = headerValue
+ setHeaderOverrideEntry(rawHeaders, headerName, headerValue)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@relay/common/override.go` around lines 732 - 734, When setting
header_override in override.go, ensureMapKeyInContext returns rawHeaders but you
must deduplicate existing keys case-insensitively before inserting
headerName/headerValue; scan rawHeaders for an existingKey where
strings.EqualFold(existingKey, headerName) and if found and existingKey !=
headerName delete rawHeaders[existingKey] then set rawHeaders[headerName] =
headerValue. This preserves deterministic overwrite semantics for subsequent
reads/merges while still using ensureMapKeyInContext and
paramOverrideContextHeaderOverride.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
relay/channel/api_request.go (1)
181-196:⚠️ Potential issue | 🟠 MajorRegex passthrough can miss matching headers due case mismatch.
Rule keys are normalized to lowercase, but regex matching is done against the original header name (
X-Trace-Idstyle). This breaks many lowercase regex rules unless callers explicitly add case-insensitive regex flags.💡 Proposed fix
for name := range c.Request.Header { + nameLower := strings.ToLower(strings.TrimSpace(name)) if shouldSkipPassthroughHeader(name) { continue } if !passAll { matched := false for _, re := range passthroughRegex { - if re.MatchString(name) { + if re.MatchString(nameLower) { matched = true break } } @@ - headerOverride[strings.ToLower(strings.TrimSpace(name))] = value + headerOverride[nameLower] = value }#!/bin/bash # Verify that regex rules are lowercased, while matching still uses raw header names. rg -n 'for k := range headerOverrideSource|strings.ToLower\(k\)|MatchString\(name\)|strings.ToLower\(strings.TrimSpace\(name\)\)' relay/channel/api_request.goExpected: matches showing key normalization and
MatchString(name)in the same flow.Also applies to: 223-224, 236-236
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/channel/api_request.go` around lines 181 - 196, The regex passthrough keys are normalized to lowercase (headerOverrideSource loop, headerPassthroughRegexPrefix/headerPassthroughRegexPrefixV2 -> pattern) but matching still uses the raw header name (MatchString(name)), causing case-mismatches; fix by normalizing the header name before regex matching (e.g., run strings.ToLower(strings.TrimSpace(name)) prior to calling the compiled regex's MatchString) or ensure the compiled pattern is case-insensitive (prepend (?i) when building the regex from pattern) wherever pattern is used (including the other occurrences around the same flow/variables).
♻️ Duplicate comments (4)
relay/common/override.go (3)
198-201:⚠️ Potential issue | 🟠 MajorGuard runtime-header mode when runtime map is nil.
When
UseRuntimeHeadersOverrideis true andRuntimeHeadersOverrideis nil, this returns an empty effective map and silently drops static channel header overrides.💡 Proposed fix
func GetEffectiveHeaderOverride(info *RelayInfo) map[string]interface{} { if info == nil { return map[string]interface{}{} } - if info.UseRuntimeHeadersOverride { + if info.UseRuntimeHeadersOverride && info.RuntimeHeadersOverride != nil { return sanitizeHeaderOverrideMap(info.RuntimeHeadersOverride) } return sanitizeHeaderOverrideMap(getHeaderOverrideMap(info)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/common/override.go` around lines 198 - 201, If UseRuntimeHeadersOverride is true but RuntimeHeadersOverride is nil the current branch returns sanitizeHeaderOverrideMap(nil) and silently drops static overrides; update the conditional in the code that checks info.UseRuntimeHeadersOverride to also guard for a non-nil info.RuntimeHeadersOverride (i.e. only call sanitizeHeaderOverrideMap(info.RuntimeHeadersOverride) when RuntimeHeadersOverride != nil), otherwise fall back to sanitizeHeaderOverrideMap(getHeaderOverrideMap(info)) so static channel overrides are preserved; reference the symbols UseRuntimeHeadersOverride, RuntimeHeadersOverride, sanitizeHeaderOverrideMap, and getHeaderOverrideMap to locate and change the branch logic.
727-739:⚠️ Potential issue | 🟠 Major
move_headerstill behaves likecopy_headerfor request-sourced headers.The source key is removed only from
header_override. Subsequent reads still resolve it fromrequest_headers, so later operations can keep consuming a header that should have been moved.💡 Proposed fix
func moveHeaderInContext(context map[string]interface{}, fromHeader, toHeader string, keepOrigin bool) error { @@ if strings.EqualFold(fromHeader, toHeader) { return nil } - return deleteHeaderOverrideInContext(context, fromHeader) + if err := deleteHeaderOverrideInContext(context, fromHeader); err != nil { + return err + } + delete(ensureMapKeyInContext(context, paramOverrideContextRequestHeaders), fromHeader) + return nil }Also applies to: 966-983
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/common/override.go` around lines 727 - 739, The moveHeaderInContext currently only deletes the key from header_override, leaving the original value in request_headers so consumers still see it; update moveHeaderInContext (and/or deleteHeaderOverrideInContext) so that when moving (i.e., keepOrigin == false and fromHeader != toHeader) you also remove the normalized fromHeader key from the request_headers map in context (look up context["request_headers"] as map[string]interface{} and delete the key if present) after copyHeaderInContext succeeds, ensuring the header is truly moved rather than still readable from request_headers; keep the current behavior when keepOrigin == true.
116-129:⚠️ Potential issue | 🟠 MajorFail fast when
operationsexists but parsing fails.If
operationsis present and condition parsing fails, this returns(nil, false)and falls back to legacy merge, which can inject rawoperationsinto the request instead of rejecting invalid config.💡 Proposed fix
-func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) { +func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, conditionContext map[string]interface{}) ([]byte, error) { if len(paramOverride) == 0 { return jsonData, nil } - // 尝试断言为操作格式 - if operations, ok := tryParseOperations(paramOverride); ok { + operations, ok, parseErr := tryParseOperations(paramOverride) + if parseErr != nil { + return nil, parseErr + } + if ok { result, err := applyOperations(string(jsonData), operations, conditionContext) return []byte(result), err } return applyOperationsLegacy(jsonData, paramOverride) } -func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation, bool) { +func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation, bool, error) { opsValue, exists := paramOverride["operations"] if !exists { - return nil, false + return nil, false, nil } @@ - if err != nil { - return nil, false + if err != nil { + return nil, true, fmt.Errorf("invalid operations.conditions: %w", err) }Also applies to: 263-266
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/common/override.go` around lines 116 - 129, The current flow in ApplyParamOverride falls back to legacy merge when tryParseOperations returns false, which lets malformed "operations" slip through; change logic to first check if the "operations" key exists in paramOverride (e.g., _, hasOps := paramOverride["operations"]), then if hasOps call tryParseOperations and if parsing returns failure/invalid, return an error immediately (do not call applyOperationsLegacy); only call applyOperations when parsing succeeds, and otherwise when "operations" key is absent continue to use applyOperationsLegacy; apply the same presence+fail-fast behavior for the other occurrence referenced (lines ~263-266) so malformed operations are rejected rather than merged.relay/channel/api_request_test.go (1)
148-156:⚠️ Potential issue | 🟡 Minor
X-Codex-Beta-Featuresexclusion path is still not exercised.This test never supplies
X-Codex-Beta-Features, so the “excluded/not forwarded” assertions pass even if exclusion logic regresses.💡 Proposed fix
ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil) ctx.Request.Header.Set("Originator", "Codex CLI") ctx.Request.Header.Set("Session_id", "sess-123") + ctx.Request.Header.Set("X-Codex-Beta-Features", "beta-flag") @@ RequestHeaders: map[string]string{ "Originator": "Codex CLI", "Session_id": "sess-123", + "X-Codex-Beta-Features": "beta-flag", },Also applies to: 177-186
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/channel/api_request_test.go` around lines 148 - 156, The test currently never sets the "X-Codex-Beta-Features" header so the exclusion code path isn't exercised; update the test to set ctx.Request.Header.Set("X-Codex-Beta-Features", "<some-feature>") and also include the same key/value in the info.RequestHeaders map used to build relaycommon.RelayInfo, then assert the expected exclusion/forwarding behavior (i.e., that the header is handled according to the exclusion logic) for both the block around ctx.Request.Header and the similar block at lines 177-186 so the exclusion path is actually tested.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@relay/channel/api_request.go`:
- Around line 181-196: The regex passthrough keys are normalized to lowercase
(headerOverrideSource loop,
headerPassthroughRegexPrefix/headerPassthroughRegexPrefixV2 -> pattern) but
matching still uses the raw header name (MatchString(name)), causing
case-mismatches; fix by normalizing the header name before regex matching (e.g.,
run strings.ToLower(strings.TrimSpace(name)) prior to calling the compiled
regex's MatchString) or ensure the compiled pattern is case-insensitive (prepend
(?i) when building the regex from pattern) wherever pattern is used (including
the other occurrences around the same flow/variables).
---
Duplicate comments:
In `@relay/channel/api_request_test.go`:
- Around line 148-156: The test currently never sets the "X-Codex-Beta-Features"
header so the exclusion code path isn't exercised; update the test to set
ctx.Request.Header.Set("X-Codex-Beta-Features", "<some-feature>") and also
include the same key/value in the info.RequestHeaders map used to build
relaycommon.RelayInfo, then assert the expected exclusion/forwarding behavior
(i.e., that the header is handled according to the exclusion logic) for both the
block around ctx.Request.Header and the similar block at lines 177-186 so the
exclusion path is actually tested.
In `@relay/common/override.go`:
- Around line 198-201: If UseRuntimeHeadersOverride is true but
RuntimeHeadersOverride is nil the current branch returns
sanitizeHeaderOverrideMap(nil) and silently drops static overrides; update the
conditional in the code that checks info.UseRuntimeHeadersOverride to also guard
for a non-nil info.RuntimeHeadersOverride (i.e. only call
sanitizeHeaderOverrideMap(info.RuntimeHeadersOverride) when
RuntimeHeadersOverride != nil), otherwise fall back to
sanitizeHeaderOverrideMap(getHeaderOverrideMap(info)) so static channel
overrides are preserved; reference the symbols UseRuntimeHeadersOverride,
RuntimeHeadersOverride, sanitizeHeaderOverrideMap, and getHeaderOverrideMap to
locate and change the branch logic.
- Around line 727-739: The moveHeaderInContext currently only deletes the key
from header_override, leaving the original value in request_headers so consumers
still see it; update moveHeaderInContext (and/or deleteHeaderOverrideInContext)
so that when moving (i.e., keepOrigin == false and fromHeader != toHeader) you
also remove the normalized fromHeader key from the request_headers map in
context (look up context["request_headers"] as map[string]interface{} and delete
the key if present) after copyHeaderInContext succeeds, ensuring the header is
truly moved rather than still readable from request_headers; keep the current
behavior when keepOrigin == true.
- Around line 116-129: The current flow in ApplyParamOverride falls back to
legacy merge when tryParseOperations returns false, which lets malformed
"operations" slip through; change logic to first check if the "operations" key
exists in paramOverride (e.g., _, hasOps := paramOverride["operations"]), then
if hasOps call tryParseOperations and if parsing returns failure/invalid, return
an error immediately (do not call applyOperationsLegacy); only call
applyOperations when parsing succeeds, and otherwise when "operations" key is
absent continue to use applyOperationsLegacy; apply the same presence+fail-fast
behavior for the other occurrence referenced (lines ~263-266) so malformed
operations are rejected rather than merged.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
relay/channel/api_request.gorelay/channel/api_request_test.gorelay/common/override.gorelay/common/override_test.gorelay/common/relay_info.goservice/channel_affinity_template_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- service/channel_affinity_template_test.go
…am-override feat: improve channel override ui/ux
参数覆盖增加更多操作项,支持请求体和请求头同时操作。
引入新的UI编辑界面。
渠道亲和规则支持绑定参数覆盖,可适用于claude code/codex cli自动透传请求头。
支持对anthropic-beta类似的请求头进行深度的操作,预设模版
Summary by CodeRabbit
New Features
Bug Fixes
Tests