Skip to content

[ADD] 添加透传请求头功能以支持自定义请求头传递 - #2365

Closed
SilentFlower wants to merge 4782 commits into
QuantumNous:mainfrom
SilentFlower:feature/pass-through-headers
Closed

[ADD] 添加透传请求头功能以支持自定义请求头传递#2365
SilentFlower wants to merge 4782 commits into
QuantumNous:mainfrom
SilentFlower:feature/pass-through-headers

Conversation

@SilentFlower

@SilentFlower SilentFlower commented Dec 3, 2025

Copy link
Copy Markdown

✨ feat(api_request): 实现透传请求头逻辑并更新相关设置

因某些上游可能需要透传某些头,逐添加

image

Summary by CodeRabbit

  • New Features

    • Visual Override Editor: dual visual/JSON modes, templates, import/export, and quick-fill for header and parameter overrides.
    • UI controls to reset overrides to "no change".
  • Improvements

    • Header and parameter overrides now support richer templating and operations, including context-aware substitutions and passthroughs.
    • The final outbound request payload and headers are captured so previews and applied overrides reflect the actual data sent.

✏️ Tip: You can customize this high-level summary in your review settings.

creamlike1024 and others added 30 commits October 20, 2025 17:48
Comment out the debug log for MiniMax TTS Request.
…ort-stream-options

Ali channel support stream options
…ream

feat: openai tts support streaming realtime audio
…ream

feat: doubao tts support streaming realtime audio
multipart/form-data; boundary
…edit

修复豆包图像编辑(图生图)功能
…watermark

fix: correct bool value for watermark
Signed-off-by: iwu <iwu@tencent.com>
…orrect-symbol

feat: topUp show correct symbol
…rt-proxy

feat: aws client supports proxy settings
chore: Update AWS claude 4.5 haiku model's information
chore: Ignore .zed and debug binaries in .gitignore

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
web/src/components/common/ui/OverrideEditor.jsx (2)

89-110: Non-string value input can cause runtime error

This has been flagged in a previous review. The function assumes value is a string and calls value.trim() directly on line 91. If a caller passes an object (e.g., from a form library that stores parsed JSON), this will throw.

The same issue applies to handleOpenModal at line 250-251.


736-748: Invalid JSON allows switch to visual mode, causing silent data loss

This was flagged in a previous review. When switching from JSON to visual mode with invalid JSON, importFromJSON returns null but setEditMode(key) is still called, leaving the user on the visual tab with stale data.

🧹 Nitpick comments (1)
web/src/components/common/ui/OverrideEditor.jsx (1)

161-188: Consider moving serializeOperations outside component or wrapping with useCallback

serializeOperations is defined inline but used within the buildPreview callback without being listed in its dependency array. Currently safe since serializeOperations has no state dependencies, but this pattern is fragile for future maintenance.

Move serializeOperations outside the component to make the intent explicit:

+const serializeOperations = (ops) =>
+  ops
+    .filter((op) => op.path)
+    .map((op) => ({
+      path: op.path,
+      mode: op.mode,
+      value: parseMaybeJSON(op.value),
+      keep_origin: !!op.keep_origin,
+      from: op.from,
+      to: op.to,
+      logic: op.logic || 'OR',
+      conditions: (op.conditions || []).map((c) => ({
+        path: c.path,
+        mode: c.mode || 'full',
+        value: parseMaybeJSON(c.value),
+        invert: !!c.invert,
+        pass_missing_key: !!c.pass_missing_key,
+      })),
+    }));
+
 const OverrideEditor = ({ ... }) => {
   ...
-  const serializeOperations = (ops) =>
-    ops
-      .filter((op) => op.path)
-      ...
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6c3ea6 and fa0105e.

📒 Files selected for processing (1)
  • web/src/components/common/ui/OverrideEditor.jsx (1 hunks)
🔇 Additional comments (4)
web/src/components/common/ui/OverrideEditor.jsx (4)

112-127: LGTM!

Good defensive handling of the value prop in jsonText initialization, converting object values to JSON strings at line 126.


283-352: LGTM!

The operation and condition CRUD handlers use functional state updates with proper immutable patterns.


443-676: LGTM!

The visual editor renders operations and conditions with proper unique keys and well-structured UI using Semi components.


707-755: LGTM!

Good UX patterns: destructive action (clear) requires confirmation via Popconfirm, modal has responsive sizing, and content area handles overflow properly.

Comment on lines +371 to +410
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 12px',
backgroundColor: 'var(--semi-color-fill-0)',
borderRadius: 6,
cursor: 'pointer',
}}
onClick={handleOpenModal}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-1)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-0)';
}}
>
<div style={{ flex: 1, minWidth: 0 }}>
{hasConfig ? (
<Space wrap size='small'>
<Tag color='blue' size='small'>
{t('{{count}} 条规则', { count: configSummary.count })}
</Tag>
{configSummary.items.slice(0, 2).map((item, idx) => (
<Text key={idx} type='tertiary' size='small'>
{item.path}
{item.hasConditions && <span style={{ color: 'var(--semi-color-warning)' }}>*</span>}
</Text>
))}
{configSummary.items.length > 2 && (
<Text type='tertiary' size='small'>...</Text>
)}
</Space>
) : (
<Text type='tertiary' size='small'>{t('点击配置')}</Text>
)}
</div>
<IconEdit style={{ color: 'var(--semi-color-text-2)' }} />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add keyboard accessibility to the clickable trigger

The trigger div uses onClick but lacks keyboard support. Users navigating with keyboard cannot activate this control.

Add role, tabIndex, and keyboard handler:

         <div
           style={{
             display: 'flex',
             alignItems: 'center',
             gap: 8,
             padding: '8px 12px',
             backgroundColor: 'var(--semi-color-fill-0)',
             borderRadius: 6,
             cursor: 'pointer',
           }}
+          role="button"
+          tabIndex={0}
           onClick={handleOpenModal}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              e.preventDefault();
+              handleOpenModal();
+            }
+          }}
           onMouseEnter={(e) => {
📝 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.

Suggested change
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 12px',
backgroundColor: 'var(--semi-color-fill-0)',
borderRadius: 6,
cursor: 'pointer',
}}
onClick={handleOpenModal}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-1)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-0)';
}}
>
<div style={{ flex: 1, minWidth: 0 }}>
{hasConfig ? (
<Space wrap size='small'>
<Tag color='blue' size='small'>
{t('{{count}} 条规则', { count: configSummary.count })}
</Tag>
{configSummary.items.slice(0, 2).map((item, idx) => (
<Text key={idx} type='tertiary' size='small'>
{item.path}
{item.hasConditions && <span style={{ color: 'var(--semi-color-warning)' }}>*</span>}
</Text>
))}
{configSummary.items.length > 2 && (
<Text type='tertiary' size='small'>...</Text>
)}
</Space>
) : (
<Text type='tertiary' size='small'>{t('点击配置')}</Text>
)}
</div>
<IconEdit style={{ color: 'var(--semi-color-text-2)' }} />
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 12px',
backgroundColor: 'var(--semi-color-fill-0)',
borderRadius: 6,
cursor: 'pointer',
}}
role="button"
tabIndex={0}
onClick={handleOpenModal}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleOpenModal();
}
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-1)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-0)';
}}
>
<div style={{ flex: 1, minWidth: 0 }}>
{hasConfig ? (
<Space wrap size='small'>
<Tag color='blue' size='small'>
{t('{{count}} 条规则', { count: configSummary.count })}
</Tag>
{configSummary.items.slice(0, 2).map((item, idx) => (
<Text key={idx} type='tertiary' size='small'>
{item.path}
{item.hasConditions && <span style={{ color: 'var(--semi-color-warning)' }}>*</span>}
</Text>
))}
{configSummary.items.length > 2 && (
<Text type='tertiary' size='small'>...</Text>
)}
</Space>
) : (
<Text type='tertiary' size='small'>{t('点击配置')}</Text>
)}
</div>
<IconEdit style={{ color: 'var(--semi-color-text-2)' }} />
</div>
🤖 Prompt for AI Agents
In web/src/components/common/ui/OverrideEditor.jsx around lines 371 to 410, the
clickable div trigger only uses onClick and lacks keyboard accessibility; update
the div to behave like an accessible button by adding role="button" and
tabIndex={0}, and implement a keyboard handler (onKeyDown) that calls
handleOpenModal when Enter or Space is pressed (prevent default for Space to
avoid scrolling) so keyboard users can activate the control; keep the existing
mouse handlers and visual styles intact.

Comment on lines +517 to +541
{op.mode === 'move' && (
<Row gutter={8}>
<Col span={12}>
<div>
<Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>From</Text>
<Input
value={op.from}
onChange={(val) => updateTempOperation(op.id, 'from', val)}
placeholder={t('如 meta.old')}
size='small'
/>
</div>
</Col>
<Col span={12}>
<div>
<Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>To</Text>
<Input
value={op.to}
onChange={(val) => updateTempOperation(op.id, 'to', val)}
placeholder={t('如 meta.new')}
size='small'
/>
</div>
</Col>
</Row>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize "From" and "To" labels

These labels are hardcoded in English while other UI strings use t() for localization.

                   <Col span={12}>
                     <div>
-                      <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>From</Text>
+                      <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>{t('From')}</Text>
                       <Input
                         ...
                     </div>
                   </Col>
                   <Col span={12}>
                     <div>
-                      <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>To</Text>
+                      <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>{t('To')}</Text>
🤖 Prompt for AI Agents
In web/src/components/common/ui/OverrideEditor.jsx around lines 517 to 541 the
"From" and "To" Text labels are hardcoded in English; wrap those strings with
the translation function (t) like the other UI strings (e.g., t('From') and
t('To') or their appropriate i18n keys), keeping the existing props (type, size,
style) intact so layout and styling remain unchanged; ensure keys exist in the
localization files or add them if necessary.

Comment on lines +746 to +748
<Tabs.TabPane tab={t('可视化')} itemKey='visual' />
<Tabs.TabPane tab='JSON' itemKey='json' />
</Tabs>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize "JSON" tab label

The "JSON" tab label is hardcoded while the other tab uses t('可视化').

             <Tabs.TabPane tab={t('可视化')} itemKey='visual' />
-            <Tabs.TabPane tab='JSON' itemKey='json' />
+            <Tabs.TabPane tab={t('JSON')} itemKey='json' />
📝 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.

Suggested change
<Tabs.TabPane tab={t('可视化')} itemKey='visual' />
<Tabs.TabPane tab='JSON' itemKey='json' />
</Tabs>
<Tabs.TabPane tab={t('可视化')} itemKey='visual' />
<Tabs.TabPane tab={t('JSON')} itemKey='json' />
</Tabs>
🤖 Prompt for AI Agents
In web/src/components/common/ui/OverrideEditor.jsx around lines 746 to 748, the
second Tabs.TabPane uses a hardcoded 'JSON' label while the first tab uses the
i18n function t(...). Replace the hardcoded label with the i18n call (e.g.
t('JSON') or the project’s preferred key like t('json')) so both tabs are
localized, keeping the existing itemKey='json'; also update the locale resource
file with the appropriate translation key if it does not yet exist.

@seefs001

seefs001 commented Dec 7, 2025

Copy link
Copy Markdown
Collaborator

发现你还弄了UI,看起来挺酷的,我需要一些时间去看这个PR

@SilentFlower

Copy link
Copy Markdown
Author

发现你还弄了UI,看起来挺酷的,我需要一些时间去看这个PR

ui大概是这样了,但还没验证逻辑请求头请求体的逻辑改的有没有问题

@SilentFlower

Copy link
Copy Markdown
Author
image image image

@seefs001

seefs001 commented Dec 7, 2025

Copy link
Copy Markdown
Collaborator

image image image

了解,之后有精力了我会给这个上面加一些东西辅助操作,你要急用的话先自己build一份自用吧(

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
relay/common/override.go (2)

476-478: Acknowledged: API key exposure in template context.

This exposes api_key for templates like Bearer {{context.api_key}} (as shown in the header override templates). Since override configurations are admin-only settings stored at the channel/tag level, this is acceptable if access controls are properly enforced.

Ensure override configurations remain admin-only and cannot be influenced by end-user input.


516-533: Acknowledged: Client headers exposure includes sensitive values.

All client headers are exposed under client_headers.*, including potentially sensitive headers like Authorization, Cookie, and X-Api-Key. This enables pass-through functionality but increases exposure risk if template configurations are misconfigured.

Consider documenting this behavior and the security implications for administrators configuring header overrides.

🧹 Nitpick comments (4)
relay/relay_task.go (1)

52-57: Upstream request body capture is good, but consider logging failures and enforcing a consistent type

This block nicely keeps the upstream task payload available for header templates, but both GetTaskRequest and Marshal errors are silently ignored. That can make debugging header-override templates harder when the context key is unexpectedly missing.

Two concrete suggestions:

  1. Log unexpected failures instead of fully swallowing them, while still treating this as best-effort:
-	if taskReq, err := relaycommon.GetTaskRequest(c); err == nil {
-		if taskJSON, err := common.Marshal(taskReq); err == nil {
-			common.SetContextKey(c, constant.ContextKeyUpstreamRequestBody, string(taskJSON))
-		}
-	}
+	if taskReq, err := relaycommon.GetTaskRequest(c); err != nil {
+		common.SysLog("GetTaskRequest failed in RelayTaskSubmit: " + err.Error())
+	} else if taskJSON, err := common.Marshal(taskReq); err != nil {
+		common.SysLog("Marshal task request failed in RelayTaskSubmit: " + err.Error())
+	} else {
+		common.SetContextKey(c, constant.ContextKeyUpstreamRequestBody, string(taskJSON))
+	}
  1. Verify that the stored type (string JSON here) matches all other writers/readers of ContextKeyUpstreamRequestBody in the relay handlers and BuildOverrideContext. If any other code assumes []byte or map[string]any, aligning on one shared type (or adding a clear type switch at the read site) will avoid subtle panics or template-evaluation errors at runtime.
relay/websocket.go (1)

25-26: Verify and standardize the type used for ContextKeyUpstreamRequestBody

Initializing the upstream body with "{}" here is a sensible way to keep the override/template pipeline non‑nil for realtime channels. However, this also implicitly fixes the representation of ContextKeyUpstreamRequestBody as a JSON string.

To avoid subtle panics or extra conversions:

  • Please confirm that all other writers/readers of ContextKeyUpstreamRequestBody (HTTP relay handlers, middlewares, template logic) also treat this value consistently as a JSON string rather than []byte or map[string]any.
  • Consider adding small helpers such as SetUpstreamRequestBody(c, jsonStr string) / GetUpstreamRequestBody(c) (string, bool) in a shared place (e.g., relay/common) and using them here and elsewhere, so the representation is enforced in one spot.
web/src/components/table/channels/modals/EditTagModal.jsx (1)

81-168: Consider extracting shared templates to reduce duplication.

The paramOverrideTemplates and headerOverrideTemplates definitions are identical to those in EditChannelModal.jsx. This duplication increases maintenance burden—if templates need updating, both files must change.

Consider extracting these templates to a shared constants file:

// e.g., web/src/constants/overrideTemplates.js
export const getParamOverrideTemplates = (t) => [
  {
    label: t('按模型前缀设置温度'),
    data: { /* ... */ },
  },
  // ...
];

export const getHeaderOverrideTemplates = (t) => [
  // ...
];

Then import and use in both modals:

import { getParamOverrideTemplates, getHeaderOverrideTemplates } from '../../../../constants/overrideTemplates';

const paramOverrideTemplates = useMemo(() => getParamOverrideTemplates(t), [t]);
relay/common/override.go (1)

537-547: Silent error handling is acceptable here.

Marshal errors result in an empty string, which means template placeholders remain unrendered. This is a safe fallback. Consider adding debug logging for marshal failures to aid troubleshooting configuration issues.

 func marshalContext(ctx map[string]interface{}) string {
 	if ctx == nil || len(ctx) == 0 {
 		return ""
 	}
 	bytes, err := common.Marshal(ctx)
 	if err != nil {
+		common.SysLog("marshalContext failed: " + err.Error())
 		return ""
 	}
 	return string(bytes)
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa0105e and 2214769.

📒 Files selected for processing (6)
  • relay/audio_handler.go (2 hunks)
  • relay/common/override.go (4 hunks)
  • relay/relay_task.go (1 hunks)
  • relay/websocket.go (2 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (4 hunks)
  • web/src/components/table/channels/modals/EditTagModal.jsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/table/channels/modals/EditChannelModal.jsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-08T17:12:43.157Z
Learnt from: RedwindA
Repo: QuantumNous/new-api PR: 1537
File: relay/gemini_handler.go:330-342
Timestamp: 2025-08-08T17:12:43.157Z
Learning: In the new-api repository, the `GeminiEmbeddingHandler` function in `relay/gemini_handler.go` is designed specifically for native Gemini embedding requests and therefore does not require the `ConvertGeminiRequest` step that is used in the chat handler. The embedding requests are already in the native Gemini format and don't need conversion.

Applied to files:

  • relay/audio_handler.go
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.

Applied to files:

  • relay/relay_task.go
🧬 Code graph analysis (5)
relay/audio_handler.go (3)
common/json.go (1)
  • Marshal (21-23)
common/gin.go (1)
  • SetContextKey (62-64)
constant/context_key.go (1)
  • ContextKeyUpstreamRequestBody (36-36)
web/src/components/table/channels/modals/EditTagModal.jsx (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)
  • paramOverrideTemplates (132-177)
  • headerOverrideTemplates (179-219)
web/src/components/common/ui/OverrideEditor.jsx (1)
  • OverrideEditor (112-756)
relay/relay_task.go (4)
relay/common/relay_utils.go (1)
  • GetTaskRequest (62-72)
common/json.go (1)
  • Marshal (21-23)
common/gin.go (1)
  • SetContextKey (62-64)
constant/context_key.go (1)
  • ContextKeyUpstreamRequestBody (36-36)
relay/websocket.go (2)
common/gin.go (1)
  • SetContextKey (62-64)
constant/context_key.go (1)
  • ContextKeyUpstreamRequestBody (36-36)
relay/common/override.go (2)
relay/common/relay_info.go (1)
  • RelayInfo (81-128)
common/json.go (2)
  • Unmarshal (9-11)
  • Marshal (21-23)
🔇 Additional comments (8)
relay/websocket.go (1)

3-15: Imports for context storage look correct

The new common and constant imports are used by the context‑key write and align with existing package structure; no issues here.

relay/audio_handler.go (2)

9-9: LGTM!

The import addition is necessary for accessing constant.ContextKeyUpstreamRequestBody.


43-46: The review comment cannot be definitively evaluated without access to the codebase to verify the proposed changes, check for the availability of the suggested logging function, and confirm consistency patterns across similar handlers.

web/src/components/table/channels/modals/EditTagModal.jsx (2)

638-654: LGTM with minor consideration.

The OverrideEditor integration follows the same pattern as EditChannelModal.jsx. The formApi={formApiRef.current} may be null on initial render, but the OverrideEditor component handles this gracefully with optional chaining (formApi && typeof formApi.setValue === 'function').

The "不更改" reset option correctly sets the value to null, which the handleSave logic interprets as "no change" for this field.


656-672: LGTM!

The header override editor follows the same correct pattern as the param override editor, with appropriate type='header' and matching templates.

relay/common/override.go (3)

34-50: LGTM - Template context initialization.

The refactoring to pass templateContext through applyOperations is clean. The delegation pattern allows template rendering to use the same context as condition evaluation.


313-357: Correct rendering order and error propagation.

Templates are rendered before condition evaluation, which is the correct order. The rendered operation fields are then used consistently for both condition checks and the actual modifications. Error handling is properly preserved.


569-589: LGTM - Comprehensive template field rendering.

The function correctly renders templates in all relevant fields (Path, From, To, Value, and condition Path/Value). Only string values undergo template substitution, which is the correct behavior for numeric/boolean operation values.

Comment thread relay/common/override.go
Comment on lines +551 to +567
// renderTemplateString 在给定上下文中渲染模板占位符,未命中时返回空字符串
func renderTemplateString(raw, contextJSON string) string {
if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") {
return raw
}
return templatePattern.ReplaceAllStringFunc(raw, func(match string) string {
path := strings.TrimSpace(match[2 : len(match)-2])
if path == "" {
return ""
}
val := gjson.Get(contextJSON, path)
if !val.Exists() {
return ""
}
return val.String()
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing template values silently become empty strings.

When a template placeholder like {{client_headers.X-Missing-Header}} references a non-existent path, it renders to an empty string. This could cause unexpected behavior:

  • Empty header names in path field
  • Empty values being set where the original was expected

Consider preserving the original placeholder when the value is not found, or logging a warning:

 func renderTemplateString(raw, contextJSON string) string {
 	if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") {
 		return raw
 	}
 	return templatePattern.ReplaceAllStringFunc(raw, func(match string) string {
 		path := strings.TrimSpace(match[2 : len(match)-2])
 		if path == "" {
 			return ""
 		}
 		val := gjson.Get(contextJSON, path)
 		if !val.Exists() {
-			return ""
+			// Preserve original placeholder when value not found
+			return match
 		}
 		return val.String()
 	})
 }

Alternatively, if empty string is intentional, document this behavior clearly.

📝 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.

Suggested change
// renderTemplateString 在给定上下文中渲染模板占位符,未命中时返回空字符串
func renderTemplateString(raw, contextJSON string) string {
if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") {
return raw
}
return templatePattern.ReplaceAllStringFunc(raw, func(match string) string {
path := strings.TrimSpace(match[2 : len(match)-2])
if path == "" {
return ""
}
val := gjson.Get(contextJSON, path)
if !val.Exists() {
return ""
}
return val.String()
})
}
// renderTemplateString 在给定上下文中渲染模板占位符,未命中时返回空字符串
func renderTemplateString(raw, contextJSON string) string {
if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") {
return raw
}
return templatePattern.ReplaceAllStringFunc(raw, func(match string) string {
path := strings.TrimSpace(match[2 : len(match)-2])
if path == "" {
return ""
}
val := gjson.Get(contextJSON, path)
if !val.Exists() {
// Preserve original placeholder when value not found
return match
}
return val.String()
})
}
🤖 Prompt for AI Agents
In relay/common/override.go around lines 551 to 567, the current
renderTemplateString function silently replaces missing or empty template values
with empty strings; change it so missing values or empty paths return the
original placeholder (match) instead of "" and add a warning log when a path is
not found to aid debugging; specifically, when path == "" or val.Exists() is
false, return the original match and emit a logger warning indicating the
template key and contextJSON (or at least the key) so callers won’t end up with
empty header names/values unexpectedly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)

131-203: Shared override templates & header key casing

The paramOverrideTemplates/headerOverrideTemplates look good and match the structure used in EditTagModal, but they’re now duplicated in two files. Consider extracting these shared templates into a common module (e.g. a overrideTemplates.js) so future changes stay in sync between channel-level and tag-level UIs.

Also, please double‑check how client_headers is built on the backend: if header keys are normalized (e.g. all lower‑cased, as in many Node/HTTP stacks), templates like client_headers.X-Trace-Id and client_headers.session_id may never match. You may want to normalize these template paths to whatever casing the backend actually uses (e.g. client_headers.x-trace-id) for reliability.

Also applies to: 205-245


2789-2797: Channel param/header overrides lack front‑end JSON validation

The new OverrideEditor wiring for param_override and header_override correctly syncs through handleInputChange and formApiRef, but unlike tags (EditTagModal.handleSave), there’s no front‑end JSON validation here before submit.

Given these fields ultimately drive upstream request manipulation, it would be safer and more consistent to:

  • Reuse verifyJSON on localInputs.param_override / localInputs.header_override in submit, mirroring the logic in EditTagModal; and
  • Optionally trim empty strings to undefined when you intend “no override”.

This keeps malformed override JSON from reaching the backend and aligns behavior across channel/tag editors.

Also applies to: 2799-2807

web/src/components/table/channels/modals/EditTagModal.jsx (1)

20-52: OverrideEditor integration & template duplication

Bringing in useMemo and OverrideEditor here is aligned with the channel modal usage, and the template shapes for param/header overrides look correct for the OverrideEditor API.

Since these paramOverrideTemplates/headerOverrideTemplates are effectively identical to those in EditChannelModal.jsx, consider extracting them into a shared helper (e.g. overrideTemplates.js) and importing from both places. That will avoid drift if you later tweak the presets or add new ones.

Also applies to: 81-152, 154-194

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2214769 and 6a9ee90.

📒 Files selected for processing (2)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (4 hunks)
  • web/src/components/table/channels/modals/EditTagModal.jsx (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.

Applied to files:

  • web/src/components/table/channels/modals/EditChannelModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.

Applied to files:

  • web/src/components/table/channels/modals/EditChannelModal.jsx
🧬 Code graph analysis (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (5)
web/src/components/table/channels/modals/EditTagModal.jsx (3)
  • paramOverrideTemplates (81-152)
  • inputs (77-77)
  • formApiRef (78-78)
web/src/components/common/ui/OverrideEditor.jsx (1)
  • OverrideEditor (112-756)
web/src/pages/Setting/Chat/SettingsChats.jsx (1)
  • inputs (54-56)
web/src/components/table/tokens/modals/EditTokenModal.jsx (1)
  • formApiRef (62-62)
web/src/components/settings/personal/cards/NotificationSettings.jsx (1)
  • formApiRef (55-55)
web/src/components/table/channels/modals/EditTagModal.jsx (4)
web/src/components/table/channels/modals/EditChannelModal.jsx (3)
  • paramOverrideTemplates (132-203)
  • inputs (287-287)
  • formApiRef (299-299)
web/src/components/common/ui/OverrideEditor.jsx (1)
  • OverrideEditor (112-756)
web/src/pages/Setting/Chat/SettingsChats.jsx (1)
  • inputs (54-56)
web/src/components/table/tokens/modals/EditTokenModal.jsx (1)
  • formApiRef (62-62)
🔇 Additional comments (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

2971-2983: pass_through_body_enabled wiring looks consistent

Switching pass_through_body_enabled to use handleChannelSettingsChange keeps channelSettings, the form value, and the serialized setting JSON in sync, and submit still rebuilds setting from the top‑level flags. The behavior is consistent with the other channel extra settings (force_format, thinking_to_content, etc.).

web/src/components/table/channels/modals/EditTagModal.jsx (1)

664-672: “不更改” semantics with OverrideEditor are coherent

The OverrideEditor bindings for param_override and header_override plus the “不更改” links work well with handleSave:

  • value={inputs.xxx || ''} ensures the editor starts empty but still functions in both visual and JSON modes.
  • Clicking “不更改” sets the corresponding field to null, which handleSave correctly treats as “no change” because it only processes non‑undefined/non‑null values.
  • When a user later edits and confirms in OverrideEditor, the stringified JSON flows back through handleInputChange into both inputs and the form.

This design preserves the bulk‑edit semantics while giving explicit control over “leave unchanged” vs “clear/override”.

Also applies to: 673-680, 682-690, 691-698

@SilentFlower

Copy link
Copy Markdown
Author

image image image

了解,之后有精力了我会给这个上面加一些东西辅助操作,你要急用的话先自己build一份自用吧(

自测了下修了下问题,大概是gjson(?)会把头的首位自动大写,然后现在就加了个小写的副本进去,以免填路径透传的时候拿不到值。

我自用的那个session透传逻辑测了是没问题了,然后我加了下关联的那个issue的参数覆盖的模板,只跑了本地GO的TEST,没实际测对不对。

我不是太急,我build起来已经构建了一个镜像了,剩下的可能要你看看有没有BUG了。

image

@seefs001

seefs001 commented Dec 8, 2025

Copy link
Copy Markdown
Collaborator

@seefs001 需要注意 FetchUpstreamModels 也需要处理

@seefs001

seefs001 commented Dec 8, 2025

Copy link
Copy Markdown
Collaborator

@seefs001 需要注意 FetchUpstreamModels 也需要处理

我只是给我自己做个TODO,你怎么就给写上了(

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
controller/channel.go (1)

293-299: Consider the implications of joining multi-value headers.

Line 298 joins multiple header values with a comma. While this is acceptable for most standard authentication headers in the model fetch context, it may not be appropriate for all header types (e.g., Set-Cookie). Given the specific use case here, this is likely fine, but be aware of this limitation if the function is reused elsewhere.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9ee90 and 6c80409.

📒 Files selected for processing (2)
  • controller/channel.go (3 hunks)
  • controller/task.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controller/task.go (2)
logger/logger.go (1)
  • LogError (65-67)
common/sys_log.go (1)
  • SysLog (11-14)
🔇 Additional comments (5)
controller/task.go (1)

91-91: LGTM: Format specifier fix.

Correctly changes %d to %s for error message formatting. The previous code would have produced incorrect log output.

controller/channel.go (4)

14-14: LGTM!

The import alias for relay/common is appropriate and follows Go conventions.


227-233: LGTM!

Good refactoring to centralize header construction logic. The error handling is appropriate and the code is cleaner.


264-291: LGTM!

The simple header override mode is well-implemented with appropriate type checking and placeholder replacement.


306-310: Verify the security implications of passing request headers to the override context.

Line 310 passes c.Request.Header to BuildOverrideContext, which means user-provided headers can be referenced in template expressions within the header override configuration. Verify that:

  1. Template expansion has safeguards against header-based injection attacks
  2. Sensitive headers are filtered or escaped before being passed to template processing
  3. This pass-through behavior is intentionally designed and documented

Comment thread controller/channel.go
Comment on lines +321 to +325
finalHeaders := http.Header{}
for k, v := range newHeaders {
finalHeaders.Set(k, fmt.Sprintf("%v", v))
}
return finalHeaders, nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix unsafe type conversion for header values.

Line 323 uses fmt.Sprintf("%v", v) to convert override values to strings. This is problematic because JSON unmarshaling into map[string]interface{} can produce various types:

  • nil becomes "<nil>" (invalid header value)
  • Numbers become their string representation (might be acceptable)
  • Booleans become "true"/"false" (likely not intended)
  • Arrays/objects produce Go-style formatting (e.g., "[1 2 3]", definitely wrong)

Apply this diff to add proper type handling:

 finalHeaders := http.Header{}
 for k, v := range newHeaders {
-	finalHeaders.Set(k, fmt.Sprintf("%v", v))
+	strVal, ok := v.(string)
+	if !ok {
+		return nil, fmt.Errorf("header override result for %s is not a string (got %T)", k, v)
+	}
+	finalHeaders.Set(k, strVal)
 }
 return finalHeaders, nil

Alternatively, if non-string types are expected and should be converted, implement explicit conversion logic for each supported type.

Comment thread controller/task.go
Comment on lines 142 to 145
if !responseItems.IsSuccess() {
common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %d", channelId, len(taskIds), string(responseBody)))
common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %s", channelId, len(taskIds), string(responseBody)))
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Format fix correct, but logic issues in error handling.

The format specifier change from %d to %s is correct. However, there are logic issues in this error-handling block:

  1. Misleading log message: The message "成功获取到任务数" (successfully obtained task count) is contradictory in an error branch (!responseItems.IsSuccess()).

  2. Incorrect error propagation: Line 144 returns err, but err comes from the successful unmarshal at line 138. This means the function returns nil in an error condition, masking the failure.

Consider this fix:

 if !responseItems.IsSuccess() {
-    common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %s", channelId, len(taskIds), string(responseBody)))
-    return err
+    errMsg := fmt.Sprintf("渠道 #%d 获取任务失败, 请求任务数: %d, 响应: %s", channelId, len(taskIds), string(responseBody))
+    common.SysLog(errMsg)
+    return errors.New(errMsg)
 }
📝 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.

Suggested change
if !responseItems.IsSuccess() {
common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %d", channelId, len(taskIds), string(responseBody)))
common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %s", channelId, len(taskIds), string(responseBody)))
return err
}
if !responseItems.IsSuccess() {
errMsg := fmt.Sprintf("渠道 #%d 获取任务失败, 请求任务数: %d, 响应: %s", channelId, len(taskIds), string(responseBody))
common.SysLog(errMsg)
return errors.New(errMsg)
}
🤖 Prompt for AI Agents
In controller/task.go around lines 142-145, the error branch logs a misleading
success message and returns the wrong error variable; update the log to describe
the failure (e.g., "未完成的任务" / "failed to fetch tasks" and include responseBody
and channelId), and ensure the function returns a non-nil error from this
failure (for example return an error built from responseItems or fmt.Errorf with
context and responseBody) instead of returning the previously nil `err`; keep
the existing formatted values and include relevant context so the caller sees
the failure details.

@SilentFlower

Copy link
Copy Markdown
Author

@seefs001 需要注意 FetchUpstreamModels 也需要处理

我只是给我自己做个TODO,你怎么就给写上了(

🤣我还以为给我提的咧

@seefs001 seefs001 added the ready to merge It will eventually merge, requiring a final check. label Dec 26, 2025
@leonardxie

Copy link
Copy Markdown

蹲一下,headers透传功能实现了嘛

@leonardxie

Copy link
Copy Markdown

蹲蹲,这个功能未来还计划merge嘛

@seefs001

Copy link
Copy Markdown
Collaborator

蹲蹲,这个功能未来还计划merge嘛

会从这里提取一部分内容merge,现在还顾不上。

@seefs001 seefs001 removed the ready to merge It will eventually merge, requiring a final check. label Jan 24, 2026
@seefs001 seefs001 self-assigned this Jan 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.