Skip to content

feat: add Claude upstream stream aggregation#5025

Open
gtxx3600 wants to merge 7 commits into
QuantumNous:mainfrom
gtxx3600:feat/claude-upstream-stream
Open

feat: add Claude upstream stream aggregation#5025
gtxx3600 wants to merge 7 commits into
QuantumNous:mainfrom
gtxx3600:feat/claude-upstream-stream

Conversation

@gtxx3600
Copy link
Copy Markdown

@gtxx3600 gtxx3600 commented May 21, 2026

📝 变更描述 / Description

为 Claude 渠道增加“强制上游流式”配置:当下游请求是非流式时,new-api 与 Claude 上游之间可以改用流式通信,并在服务端聚合为下游期望的非流式响应。

这样做的原因是站点之间的连接经常经过 CDN 加速域名或负载均衡域名。非流式长耗时请求在上游长时间没有响应数据时,容易被 CDN 或负载均衡判定为空闲连接并断开;改为上游流式后,连接过程中会持续有 SSE 数据返回,可以降低这类中间层断连风险,同时保持下游接口仍是非流式响应。

本 PR 同时补充了前端渠道配置开关,并处理注意事项:部分渠道设置会使本开关失效,例如“请求体透传”。当请求体透传开启时,前端会自动关闭并禁用“强制上游流式”开关,保存时也会写入 force_upstream_stream: false,与后端门控保持一致。

另外单独修复 Claude OpenAI file content 转换,使 PDF/text file content 能正确转换到 Claude 消息结构。

已更新 docs/channel/other_setting.md,补充 force_upstream_streampass_through_body_enabled 以及两者互斥关系。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

new switch in channel configuration:
ScreenShot_2026-05-21_212104_195

my newapi log:

ScreenShot_2026-05-21_212528_467 ScreenShot_2026-05-21_212031_274

upstream newapi log:

ScreenShot_2026-05-21_212545_094 微信图片_20260521211950_67_109
git diff --check
go test ./...
cd web/default
bun run typecheck

补充说明:cd web/default && bun run format:check 当前会因仓库既有未格式化文件失败;本 PR 仅对触碰到的 default 前端文件运行了 prettier。

Summary by CodeRabbit

  • New Features

    • Added a "Force Upstream Streaming" channel option for Claude to enable upstream streaming and aggregate responses into a single JSON when downstream requests are non-streaming.
    • Claude responses now preserve stop-sequence markers.
  • Behavior

    • "Force Upstream Streaming" is disabled when request-body pass-through is enabled.
    • Improved media/file upload handling when converting OpenAI-style requests.
  • Tests

    • Added extensive unit tests for streaming, aggregation, media conversion, and related behaviors.
  • Docs

    • Updated documentation and multilingual translations for the new settings.

Review Change Stack

gtxx3600 and others added 2 commits May 21, 2026 20:18
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a force-upstream-stream channel setting, RelayInfo upstream-stream decision and JSON injection, handler/adaptor wiring to enable upstream streaming, Claude SSE aggregation into non-stream JSON responses, media-file conversion improvements, optional streaming timeout handling, UI schema + toggles + translations, tests, and docs.

Changes

Upstream streaming core feature

Layer / File(s) Summary
DTO schema updates
dto/channel_settings.go, dto/claude.go
Added ForceUpstreamStream to ChannelSettings and StopSequence fields / TODO for Claude DTOs.
RelayInfo decision and JSON helper
relay/common/relay_info.go, relay/common/relay_info_test.go
Added UpstreamStream boolean, ShouldUseUpstreamStream(), EnsureUpstreamStreamField() (injects "stream": true), and tests.
Handler integration (Claude & compatible)
relay/claude_handler.go, relay/compatible_handler.go
Handlers call EnsureUpstreamStreamField before upstream send and refine detection of SSE -> info.IsStream when UpstreamStream is already enabled.
Claude adaptor request/response wiring
relay/channel/claude/adaptor.go
Added imports, enable Claude upstream streaming during conversions, improved OpenAI→Claude conversion error handling, and branch to aggregate upstream SSE into non-stream responses.
Claude SSE aggregation & media handling
relay/channel/claude/relay-claude.go, relay/channel/claude/relay_claude_test.go
New helpers for MIME inference and OpenAI-file→Claude media conversion; implemented AggregateClaudeStreamResponse/claudeStreamAggregate to parse SSE events, aggregate deltas (text/thinking/tool-use), preserve stop_sequence/cache usage, compute usage, and return Claude or OpenAI formatted aggregated JSON. Extensive unit tests added.

Stream scanner timeout optimization

Layer / File(s) Summary
Optional streaming timeout ticker
relay/helper/stream_scanner.go, relay/helper/stream_scanner_test.go
Make streaming timeout ticker optional (created only when StreamingTimeout>0), guard ticker cleanup/reset, use optional timeout channel in select; add zero-timeout test and adjust pre-initialized StreamStatus expectation.

Classic web UI channel configuration

Layer / File(s) Summary
EditChannelModal changes and state wiring
web/classic/src/components/table/channels/modals/EditChannelModal.jsx
Add force_upstream_stream to defaults/state, enforce mutual exclusion with pass_through_body_enabled, handle load/reset/submit behavior, and add Claude-only advanced toggle.
Classic web i18n translations
web/classic/src/i18n/locales/*
Add translation entries for force-upstream-stream label and explanatory notes across classic locale files.

Default web UI channel configuration and i18n

Layer / File(s) Summary
Form schema, defaults, and transformations
web/default/src/features/channels/lib/channel-form.ts, web/default/src/features/channels/types.ts
Add force_upstream_stream to form schema/defaults and ChannelSettings type; enforce pass-through -> force=false in transformations.
Channel drawer UI and conditional clearing
web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
Drawer auto-expand detection, watch pass_through_body_enabled to clear force_upstream_stream, and render Claude-only toggle with conditional disable/description.
Default web i18n translations
web/default/src/i18n/locales/*
Add default-locale translation keys for disabled-state message, force-upstream-stream label, and Claude upstream-stream behavior across locales.

Docs and controller test

Layer / File(s) Summary
Docs update and controller test tweak
docs/channel/other_setting.md, controller/model_list_test.go
Document new extra settings and interactions; ensure model-list test DB setup and request context user group set in test.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Adaptor as Relay Claude Adaptor
  participant Upstream as Claude Upstream
  participant Agg as AggregateClaudeStreamResponse
  Client->>Adaptor: Non-stream request
  Adaptor->>Upstream: Upstream request (stream:true injected when UpstreamStream)
  Upstream-->>Adaptor: SSE stream (text/event-stream)
  Adaptor->>Agg: Pass HTTP response + RelayInfo
  Agg->>Agg: Parse SSE "data:" events, aggregate text/tool-use/stop_sequence
  Agg->>Adaptor: Aggregated JSON + usage
  Adaptor->>Client: Single JSON response (non-stream)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • seefs001

"A rabbit hops through streams of code,
I stitch a flag where toggles grow.
I gather data, join the flow,
Aggregate thoughts into one glow.
🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Claude upstream stream aggregation' accurately and clearly describes the main feature added in this PR—a new capability for Claude channels to aggregate upstream streaming responses.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
web/default/src/i18n/locales/zh.json (1)

1209-1209: ⚡ Quick win

Use semantic hierarchical i18n keys for these new entries.

These newly added translation keys are sentence-based; please switch to hierarchical semantic keys (for example under a channels.claude.* namespace) to keep i18n naming consistent and maintainable.

As per coding guidelines, web/default/src/i18n/**/*.{ts,tsx,json} should “Use hierarchical and semantically clear translation key names such as dashboard.overview.title and maintain naming consistency”.

Also applies to: 1791-1791, 4232-4232

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/zh.json` at line 1209, Replace the
sentence-based JSON key "Disabled while request body pass-through is enabled"
with a hierarchical semantic key under an appropriate namespace (e.g.,
channels.claude.requestBody.passThroughDisabled or
channels.claude.disabledRequestBodyPassThrough) and move the existing Chinese
value "请求体透传开启时,此开关会失效" to that new key; update any references to this string in
code to use the new key name and apply the same pattern for the other mentioned
entries (lines ~1791 and ~4232) to ensure consistent hierarchical i18n naming.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/classic/src/i18n/locales/ja.json`:
- Around line 1024-1026: The three new translation entries ("强制上游流式",
"请求体透传开启时,此开关会失效", "当下游请求为非流式时,此 Claude 渠道与上游之间使用流式通信") use 「上流」「下流」; update
their Japanese values to use the consistent terminology 「アップストリーム」「ダウンストリーム」
(e.g., change "上流ストリーミングを強制" → "アップストリームストリーミングを強制",
"リクエストボディのパススルーが有効な間、このスイッチは無効になります" keep same but ensure any occurrence of
上流/下流 is converted, and "下流リクエストが非ストリーミングの場合、この Claude
チャネルと上流の間でストリーミング通信を使用します" → use "ダウンストリーム" and "アップストリーム" accordingly) so the
file matches existing terminology.

In `@web/default/src/i18n/locales/vi.json`:
- Line 4232: Update the Vietnamese translation value for the JSON key "Use
streaming between this Claude channel and upstream for non-streaming downstream
requests" to replace the phrase "phía dưới" with the clearer technical term "hạ
nguồn" so the string reads: "Dùng truyền phát giữa kênh Claude này và thượng
nguồn cho các yêu cầu phía hạ nguồn không truyền phát" (or equivalent ordering
that uses "hạ nguồn"); edit the value in vi.json for that exact key.

---

Nitpick comments:
In `@web/default/src/i18n/locales/zh.json`:
- Line 1209: Replace the sentence-based JSON key "Disabled while request body
pass-through is enabled" with a hierarchical semantic key under an appropriate
namespace (e.g., channels.claude.requestBody.passThroughDisabled or
channels.claude.disabledRequestBodyPassThrough) and move the existing Chinese
value "请求体透传开启时,此开关会失效" to that new key; update any references to this string in
code to use the new key name and apply the same pattern for the other mentioned
entries (lines ~1791 and ~4232) to ensure consistent hierarchical i18n naming.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abb37541-a929-47a9-8811-7cc95a2647b6

📥 Commits

Reviewing files that changed from the base of the PR and between 006e801 and 7c721e0.

📒 Files selected for processing (29)
  • dto/channel_settings.go
  • dto/claude.go
  • relay/channel/claude/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/claude/relay_claude_test.go
  • relay/claude_handler.go
  • relay/common/relay_info.go
  • relay/common/relay_info_test.go
  • relay/compatible_handler.go
  • relay/helper/stream_scanner.go
  • relay/helper/stream_scanner_test.go
  • web/classic/src/components/table/channels/modals/EditChannelModal.jsx
  • web/classic/src/i18n/locales/en.json
  • web/classic/src/i18n/locales/fr.json
  • web/classic/src/i18n/locales/ja.json
  • web/classic/src/i18n/locales/ru.json
  • web/classic/src/i18n/locales/vi.json
  • web/classic/src/i18n/locales/zh-CN.json
  • web/classic/src/i18n/locales/zh-TW.json
  • web/classic/src/i18n/locales/zh.json
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/lib/channel-form.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json

Comment thread web/classic/src/i18n/locales/ja.json Outdated
Comment thread web/default/src/i18n/locales/vi.json Outdated
Copy link
Copy Markdown

@panjinhe panjinhe left a comment

Choose a reason for hiding this comment

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

Reviewed locally: diff checked, combined merge tested with related PRs, targeted tests passed.

Copy link
Copy Markdown

@panjinhe panjinhe left a comment

Choose a reason for hiding this comment

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

Reviewed locally: diff checked, combined merge tested with related PRs, targeted tests passed.

gtxx3600 and others added 2 commits May 21, 2026 21:32
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
web/default/src/i18n/locales/en.json (1)

1216-1216: 🏗️ Heavy lift

Use semantic hierarchical i18n keys instead of sentence-as-key entries.

The new entries still use literal English phrases as keys (e.g., Line 1216, Line 1800, Line 4266). Please migrate these additions to namespaced semantic keys (for example, channels.form.forceUpstreamStream.label, channels.form.forceUpstreamStream.disabledReason, channels.form.forceUpstreamStream.description) and keep naming consistent with the same convention across locales.

As per coding guidelines, "web/default/src/i18n/**/*.{ts,tsx,json}: Use hierarchical and semantically clear translation key names such as dashboard.overview.title and maintain naming consistency".

Also applies to: 1800-1800, 4266-4266

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/en.json` at line 1216, The JSON contains
sentence-as-key entries like "Disabled while request body pass-through is
enabled"; replace these with hierarchical semantic keys (e.g.,
channels.form.forceUpstreamStream.disabledReason) and move the English string to
the value side of that key; update similar entries at the other mentioned
locations (lines with literal keys) to follow the same convention and naming
pattern (for example channels.form.forceUpstreamStream.label and
channels.form.forceUpstreamStream.description) so all locales use consistent,
namespaced keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web/default/src/i18n/locales/en.json`:
- Line 1216: The JSON contains sentence-as-key entries like "Disabled while
request body pass-through is enabled"; replace these with hierarchical semantic
keys (e.g., channels.form.forceUpstreamStream.disabledReason) and move the
English string to the value side of that key; update similar entries at the
other mentioned locations (lines with literal keys) to follow the same
convention and naming pattern (for example
channels.form.forceUpstreamStream.label and
channels.form.forceUpstreamStream.description) so all locales use consistent,
namespaced keys.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2de7bf8e-eb3d-4d72-8542-677a5c02562e

📥 Commits

Reviewing files that changed from the base of the PR and between d685956 and 327a833.

📒 Files selected for processing (10)
  • controller/model_list_test.go
  • relay/common/relay_info.go
  • web/classic/src/i18n/locales/en.json
  • web/classic/src/i18n/locales/zh.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
✅ Files skipped from review due to trivial changes (5)
  • web/classic/src/i18n/locales/zh.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/zh.json
  • web/classic/src/i18n/locales/en.json

gtxx3600 and others added 2 commits May 26, 2026 19:18
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants