Skip to content

fix(openai): support streaming image relay and image edit for images API - #4608

Merged
Calcium-Ion merged 22 commits into
QuantumNous:mainfrom
gaoren002:pr/openai-image-stream-keepalive-20260504130050
Jun 8, 2026
Merged

fix(openai): support streaming image relay and image edit for images API #4608
Calcium-Ion merged 22 commits into
QuantumNous:mainfrom
gaoren002:pr/openai-image-stream-keepalive-20260504130050

Conversation

@gaoren002

@gaoren002 gaoren002 commented May 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ 提交说明 / PR Notice

Important

  • 本描述已按代码逻辑人工整理,未直接粘贴原始排查记录。

📝 变更描述 / Description

本 PR 修复 OpenAI 图片生成/编辑在 stream=true 场景下没有按流式响应处理的问题。

此前 ImageRequest.IsStream() 固定返回 false,即使客户端传入 stream=true,图片接口仍会走非流式响应路径。对于耗时较长的图片生成/编辑请求,下游在上游完成前收不到任何响应数据,容易被 CDN / Cloudflare 等入口超时断开。

本次改动让图片请求正确识别 stream 参数,并在 OpenAI 图片生成与图片编辑接口中增加 SSE 转发处理:上游返回的 event: / data: 行会原样写回客户端,兼容 image_generation.partial_image、完成事件和 [DONE],同时从流式数据中提取 usage,保持计费链路可用。

另外,图片编辑接口使用 multipart/form-data。校验阶段如果直接调用 c.MultipartForm(),会消费原始请求体,后续转发到 OpenAI 时可能导致 multipart 内容不可复用。本 PR 改为使用可复用的 multipart 解析方式,并回填 c.Request.MultipartForm / c.Request.PostForm,确保校验后仍能完整重建并转发图片、mask 和普通表单字段。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 修复图片流式请求未按流处理,以及图片编辑 multipart body 被提前消费的问题
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 已搜索现有 Issues 与 PRs;OpenAI Images API 长耗时请求被 Cloudflare 524 切断 stream=true 未按 Images SSE 处理且缺少结果取回机制 #4478 是精确相关 issue,未发现同等修复已合并到当前 main
  • Bug fix 说明: 此 PR 修复的是请求参数已存在但未被流式链路识别、以及 multipart 校验后无法可靠转发的问题。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 仅包含 OpenAI 图片流式转发和图片编辑 multipart 复用相关改动。
  • 本地验证: 已在本地运行相关 Go 测试并通过。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

本地测试命令:

go test ./model ./controller ./relay/helper ./relay/channel/openai ./dto

测试结果:

ok  github.com/QuantumNous/new-api/model
ok  github.com/QuantumNous/new-api/controller
ok  github.com/QuantumNous/new-api/relay/helper
ok  github.com/QuantumNous/new-api/relay/channel/openai
ok  github.com/QuantumNous/new-api/dto

PR 分支:

pr/openai-image-stream-keepalive-20260504130050

提交:

84051a50a fix(openai): support streaming image relay
728a45c20 fix(openai): keep image edit multipart body reusable

Summary by CodeRabbit

  • New Features

    • Dedicated image SSE streaming path with JSON-as-stream fallback and improved usage normalization.
  • Bug Fixes

    • Incoming image request "stream" flag is honored end-to-end.
    • Multipart image-edit uploads preserve and replay form fields/files when original form data is missing.
    • OpenAI error responses now surface an additional status field.
    • Stream scanner buffer sizing and stream-status initialization behavior improved.
  • Tests

    • Added tests for streaming, multipart replay/parsing, SSE wrapping, usage mapping, and invalid stream values.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

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

ImageRequest now honors JSON/multipart stream; multipart edits reuse a parsed form and preserve stream; DoResponse dispatches streaming image responses to a new SSE proxy that forwards events, captures/normalizes usage, and applies post-processing; tests cover parsing, multipart replay, and SSE behaviors.

Changes

OpenAI Images Streaming Support

Layer / File(s) Summary
DTO Behavior
dto/openai_image.go, dto/openai_image_test.go
ImageRequest gains Stream bool \json:"stream,omitempty"`andIsStream()now returnsi.Stream. Added test verifying JSON stream: true`.
Request Validation
relay/helper/valid_request.go, relay/helper/openai_image_request_test.go
Multipart edits parsing uses common.ParseMultipartFormReusable, assigns c.Request.MultipartForm/c.Request.PostForm, parses optional stream via strconv.ParseBool, and errors on invalid values; tests for valid/invalid stream and body preservation.
Adaptor Multipart Conversion
relay/channel/openai/adaptor.go, relay/channel/openai/image_edit_test.go
ConvertImageRequest reuses parsed multipart form when available or uses reusable parser; converted multipart preserves stream, other form fields, and file parts; tests validate replayed multipart contents.
Relay Routing
relay/channel/openai/adaptor.go
For image generation/edit relay modes, DoResponse now calls OpenaiImageStreamHandler when info.IsStream is true, otherwise OpenaiHandlerWithUsage.
Streaming Handler Core
relay/channel/openai/relay-openai.go
Added OpenaiImageStreamHandler, OpenaiImageJSONAsStreamHandler, normalizeOpenAIUsage, and SSE writer helpers to proxy SSE data: events, normalize usage, detect upstream error events, and apply usage post-processing. Includes bufio scanner usage and OpenAI error status mapping.
Image-stream Tests
relay/channel/openai/image_stream_test.go
Adds tests covering SSE forwarding, oversized SSE lines, JSON-to-SSE wrapping, JSON error fallbacks, upstream event: error handling, and usage normalization mapping.
Stream Infrastructure
relay/helper/stream_scanner.go, relay/helper/stream_scanner_test.go
Exported GetScannerBufferSize() and updated scanner usage; stream-status test updated to expect replacement/reset behavior.
Error mapping / types
dto/openai_response.go, types/error.go
GetOpenAIError now extracts error["status"] into OpenAIError.Status; types.OpenAIError gains Status field.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NewAPI as "NewAPI Relay"
  participant Upstream as "OpenAI Upstream"

  Client->>NewAPI: POST /v1/images (stream=true / multipart)
  NewAPI->>Upstream: proxy request
  Upstream-->>NewAPI: text/event-stream (event/data SSE lines)
  NewAPI->>Client: forward SSE lines (event/data), flush each
  NewAPI->>NewAPI: parse data -> dto.SimpleResponse -> normalize usage
  Upstream-->>NewAPI: data: [DONE] / EOF
  NewAPI->>NewAPI: applyUsagePostProcessing(lastData) -> record usage
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • seefs001

Poem

"I'm a rabbit with bytes in my paws,
I hop your multipart and mind the laws,
I forward SSE, count tokens too,
preserve stream bits and file parts true,
now long image jobs can finish without pause."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% 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 PR title accurately reflects the main change: adding support for streaming image relay and image edit functionality for the OpenAI Images API.
Linked Issues check ✅ Passed The PR comprehensively implements all coding requirements from issue #4478: ImageRequest.Stream field support, IsStream() method behavior, SSE forwarding with proper headers, multipart form reusability, usage extraction, JSON→SSE fallback, and stream error recording.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue objectives. Stream buffer size was exported (supporting streaming infrastructure), test renames reflect new behavior, and OpenAIError.Status field supports error status extraction—all necessary for complete streaming image relay implementation.

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

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

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

@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

🤖 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/openai/image_stream_test.go`:
- Around line 16-21: The test TestOpenaiImageStreamHandlerForwardsSSEAndUsage
sets global Gin mode via gin.SetMode(gin.TestMode) but doesn't restore the
previous mode; update the test to capture the current mode (e.g., prevMode :=
gin.Mode()), call gin.SetMode(gin.TestMode) as done now, and add a t.Cleanup
closure that restores gin.SetMode(prevMode) alongside the existing
constant.StreamingTimeout restore so the global Gin mode is reverted after the
test.
🪄 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: 3b13492d-e5c4-4394-8191-5886936a74d5

📥 Commits

Reviewing files that changed from the base of the PR and between dac55f0 and 728a45c.

📒 Files selected for processing (9)
  • dto/openai_image.go
  • dto/openai_image_test.go
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go

Comment thread relay/channel/openai/image_stream_test.go
@gaoren002
gaoren002 force-pushed the pr/openai-image-stream-keepalive-20260504130050 branch 2 times, most recently from 2d27fba to c98a5b4 Compare May 4, 2026 13:29
@gaoren002

Copy link
Copy Markdown
Contributor Author

本次修复重点考虑了计费逻辑,应该能较完满解决问题。
但是只能在上游返回 usage 的情况下保持现有图片 token 计费语义
无 usage 的异常/中断场景仍沿用现有兜底行为

@gaoren002
gaoren002 force-pushed the pr/openai-image-stream-keepalive-20260504130050 branch from c98a5b4 to 55f3767 Compare May 4, 2026 13:40

@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

🧹 Nitpick comments (1)
relay/helper/openai_image_request_test.go (1)

18-52: ⚡ Quick win

LGTM — the happy-path multipart stream test is well-structured

The body-reusability assertion at lines 44–46 correctly verifies that ParseMultipartFormReusable restores c.Request.Body after first read. The subsequent ParseMultipartFormReusable(c) call at line 48 correctly relies on c.Request.MultipartForm having been pre-populated by valid_request.go (line 155), which prevents re-reading the already-drained body.

Optional: The error branch at valid_request.go lines 164–166 (malformed stream value → return error) has no test coverage. Consider adding:

func TestGetAndValidOpenAIImageRequestMultipartStreamInvalidValue(t *testing.T) {
    gin.SetMode(gin.TestMode)
    var body bytes.Buffer
    writer := multipart.NewWriter(&body)
    require.NoError(t, writer.WriteField("model", "gpt-image-1"))
    require.NoError(t, writer.WriteField("stream", "notabool"))
    require.NoError(t, writer.Close())

    recorder := httptest.NewRecorder()
    c, _ := gin.CreateTestContext(recorder)
    c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/edits", &body)
    c.Request.Header.Set("Content-Type", writer.FormDataContentType())

    _, err := GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesEdits)
    require.Error(t, err)
    require.Contains(t, err.Error(), "invalid stream value")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/helper/openai_image_request_test.go` around lines 18 - 52, Add a
negative test that covers the malformed "stream" value path in
GetAndValidOpenAIImageRequest: create a multipart request with model
"gpt-image-1" and stream set to "notabool", set Content-Type to the writer's
FormDataContentType, call GetAndValidOpenAIImageRequest(c,
relayconstant.RelayModeImagesEdits) and assert it returns an error containing
"invalid stream value" (e.g., name the test
TestGetAndValidOpenAIImageRequestMultipartStreamInvalidValue and mirror the
existing multipart setup/recorder/gin context used in
TestGetAndValidOpenAIImageRequestMultipartStream).
🤖 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/openai/relay-openai.go`:
- Around line 604-620: OpenaiImageStreamHandler currently forces SSE headers for
every response; instead guard by checking resp.StatusCode and
resp.Header["Content-Type"] (or resp.Header.Get("Content-Type")) for an SSE
content type and a successful status before calling
helper.SetEventStreamHeaders; if the response is non-200 or not SSE (e.g.,
OpenAI JSON error), do NOT set SSE headers—read the body, return the original
status and a types.NewOpenAIError (including the response body/error text) so
the client gets the real HTTP error, and keep defer
service.CloseResponseBodyGracefully(resp) and existing handling of
info.StreamStatus unchanged.

---

Nitpick comments:
In `@relay/helper/openai_image_request_test.go`:
- Around line 18-52: Add a negative test that covers the malformed "stream"
value path in GetAndValidOpenAIImageRequest: create a multipart request with
model "gpt-image-1" and stream set to "notabool", set Content-Type to the
writer's FormDataContentType, call GetAndValidOpenAIImageRequest(c,
relayconstant.RelayModeImagesEdits) and assert it returns an error containing
"invalid stream value" (e.g., name the test
TestGetAndValidOpenAIImageRequestMultipartStreamInvalidValue and mirror the
existing multipart setup/recorder/gin context used in
TestGetAndValidOpenAIImageRequestMultipartStream).
🪄 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: 8859bf41-d1cd-493f-921d-35f7672b79b3

📥 Commits

Reviewing files that changed from the base of the PR and between 728a45c and c98a5b4.

📒 Files selected for processing (6)
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (1)
  • relay/channel/openai/image_edit_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/helper/stream_scanner.go

Comment thread relay/channel/openai/relay-openai.go Outdated

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

♻️ Duplicate comments (2)
relay/channel/openai/image_stream_test.go (1)

19-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore Gin mode in test cleanup to avoid global-state leakage.

At Line 19, the test mutates global Gin mode but only restores constant.StreamingTimeout. Please also restore the previous Gin mode in t.Cleanup.

Suggested fix
 func TestOpenaiImageStreamHandlerForwardsSSEAndUsage(t *testing.T) {
-	gin.SetMode(gin.TestMode)
+	oldMode := gin.Mode()
+	gin.SetMode(gin.TestMode)
+	t.Cleanup(func() { gin.SetMode(oldMode) })
 
 	oldTimeout := constant.StreamingTimeout
 	constant.StreamingTimeout = 30
 	t.Cleanup(func() { constant.StreamingTimeout = oldTimeout })
#!/bin/bash
# Verify gin mode mutation is paired with cleanup restore in this test file.
rg -n -C2 'gin\.SetMode\(gin\.TestMode\)|gin\.Mode\(\)|t\.Cleanup\(func\(\)\s*\{\s*gin\.SetMode' relay/channel/openai/image_stream_test.go
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/openai/image_stream_test.go` around lines 19 - 23, The test
sets global Gin mode via gin.SetMode(gin.TestMode) but only restores
constant.StreamingTimeout in t.Cleanup, causing global-state leakage; update the
cleanup to capture the original Gin mode with gin.Mode() before calling
gin.SetMode and restore it inside the existing t.Cleanup alongside restoring
constant.StreamingTimeout so both constant.StreamingTimeout and Gin's mode
(captured via gin.Mode()) are reset after the test.
relay/channel/openai/relay-openai.go (1)

616-617: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve upstream non-SSE/error responses before forcing SSE output.

At Line 616, helper.SetEventStreamHeaders(c) is unconditional. If upstream returns non-2xx or JSON error, this path can rewrite the response contract and mask real error semantics.

Suggested fix
 func OpenaiImageStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
 	if resp == nil || resp.Body == nil {
 		logger.LogError(c, "invalid image stream response")
 		return nil, types.NewOpenAIError(fmt.Errorf("invalid response"), types.ErrorCodeBadResponse, http.StatusInternalServerError)
 	}
-	defer service.CloseResponseBodyGracefully(resp)
-
-	usage := &dto.Usage{}
-	var lastStreamData []byte
-
-	helper.SetEventStreamHeaders(c)
+	contentType := strings.ToLower(resp.Header.Get("Content-Type"))
+	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices ||
+		!strings.Contains(contentType, "text/event-stream") {
+		// keep non-stream/error behavior intact
+		return OpenaiHandlerWithUsage(c, info, resp)
+	}
+	defer service.CloseResponseBodyGracefully(resp)
+
+	usage := &dto.Usage{}
+	var lastStreamData []byte
+
+	helper.SetEventStreamHeaders(c)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/openai/relay-openai.go` around lines 616 - 617,
helper.SetEventStreamHeaders(c) is being called unconditionally which forces SSE
headers even when the upstream response is non-2xx or contains a JSON error;
change the flow so that before calling helper.SetEventStreamHeaders(c) you check
the upstream status/response (use info.StreamStatus and any upstream response
body/status) and only set SSE headers when the upstream returns a successful
stream (e.g., 2xx and not an error JSON). In practice, move or gate the
helper.SetEventStreamHeaders(c) call behind a conditional that verifies
info.StreamStatus indicates a streaming success and avoid mutating headers when
upstream returned an error status or non-SSE payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@relay/channel/openai/image_stream_test.go`:
- Around line 19-23: The test sets global Gin mode via gin.SetMode(gin.TestMode)
but only restores constant.StreamingTimeout in t.Cleanup, causing global-state
leakage; update the cleanup to capture the original Gin mode with gin.Mode()
before calling gin.SetMode and restore it inside the existing t.Cleanup
alongside restoring constant.StreamingTimeout so both constant.StreamingTimeout
and Gin's mode (captured via gin.Mode()) are reset after the test.

In `@relay/channel/openai/relay-openai.go`:
- Around line 616-617: helper.SetEventStreamHeaders(c) is being called
unconditionally which forces SSE headers even when the upstream response is
non-2xx or contains a JSON error; change the flow so that before calling
helper.SetEventStreamHeaders(c) you check the upstream status/response (use
info.StreamStatus and any upstream response body/status) and only set SSE
headers when the upstream returns a successful stream (e.g., 2xx and not an
error JSON). In practice, move or gate the helper.SetEventStreamHeaders(c) call
behind a conditional that verifies info.StreamStatus indicates a streaming
success and avoid mutating headers when upstream returned an error status or
non-SSE payload.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcf2b9f7-9e88-48f3-9a4d-0169ef31bc39

📥 Commits

Reviewing files that changed from the base of the PR and between c98a5b4 and 55f3767.

📒 Files selected for processing (7)
  • dto/openai_image_test.go
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (3)
  • dto/openai_image_test.go
  • relay/channel/openai/image_edit_test.go
  • relay/helper/valid_request.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go

@gaoren002
gaoren002 force-pushed the pr/openai-image-stream-keepalive-20260504130050 branch 2 times, most recently from cfc9a34 to f706730 Compare May 4, 2026 13:55

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

🧹 Nitpick comments (1)
relay/channel/openai/image_stream_test.go (1)

51-61: ⚡ Quick win

data: [DONE] forwarding is not asserted.

The test verifies that SSE event lines and the usage payload are forwarded, but it never checks that the stream-termination sentinel (data: [DONE]) is present in the recorded body. The handler forwards all lines unconditionally, so this is a coverage gap rather than a production defect — but a targeted regression could silently remove [DONE] forwarding without tripping any existing assertion.

✅ Proposed addition
 require.Contains(t, recorder.Body.String(), `data: {"usage":{"input_tokens":3,"output_tokens":4,"total_tokens":7,"input_tokens_details":{"image_tokens":2,"text_tokens":1}}}`)
+require.Contains(t, recorder.Body.String(), `data: [DONE]`)
 require.Equal(t, "text/event-stream", recorder.Header().Get("Content-Type"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/openai/image_stream_test.go` around lines 51 - 61, The test for
OpenaiImageStreamHandler misses asserting that the SSE stream terminator "data:
[DONE]" is forwarded; update the test in
relay/channel/openai/image_stream_test.go to also assert that
recorder.Body.String() (or equivalent recorded output) contains the literal
`data: [DONE]`, ensuring the handler's forwarding of the stream-termination
sentinel is covered along with the existing checks for partial_image events and
usage payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@relay/channel/openai/image_stream_test.go`:
- Around line 51-61: The test for OpenaiImageStreamHandler misses asserting that
the SSE stream terminator "data: [DONE]" is forwarded; update the test in
relay/channel/openai/image_stream_test.go to also assert that
recorder.Body.String() (or equivalent recorded output) contains the literal
`data: [DONE]`, ensuring the handler's forwarding of the stream-termination
sentinel is covered along with the existing checks for partial_image events and
usage payloads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d47a1817-cb81-45ea-86f1-80384ff32ed3

📥 Commits

Reviewing files that changed from the base of the PR and between 55f3767 and cfc9a34.

📒 Files selected for processing (7)
  • dto/openai_image_test.go
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (2)
  • relay/channel/openai/image_edit_test.go
  • relay/helper/stream_scanner.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • relay/helper/openai_image_request_test.go
  • dto/openai_image_test.go
  • relay/helper/valid_request.go
  • relay/channel/openai/relay-openai.go

@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

🤖 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/openai/relay-openai.go`:
- Around line 594-599: The normalization block copies several token subfields
from usage.InputTokensDetails to usage.PromptTokensDetails but omits
CachedCreationTokens, so add copying of that field as well: when
usage.InputTokensDetails != nil assign
usage.PromptTokensDetails.CachedCreationTokens =
usage.InputTokensDetails.CachedCreationTokens alongside the existing assignments
(i.e., update the block that references usage.InputTokensDetails and
usage.PromptTokensDetails to include CachedCreationTokens).
🪄 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: dc2ddfb7-4a53-4df2-b3fc-fde827b24322

📥 Commits

Reviewing files that changed from the base of the PR and between cfc9a34 and f706730.

📒 Files selected for processing (8)
  • dto/openai_image_test.go
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (3)
  • relay/channel/openai/image_stream_test.go
  • relay/helper/valid_request.go
  • dto/openai_image_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • relay/channel/openai/image_edit_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/openai_image_request_test.go

Comment thread relay/channel/openai/relay-openai.go
@gaoren002

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

@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

🤖 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/openai/image_edit_test.go`:
- Around line 75-118: The test
TestConvertImageEditRequestParsesReusableMultipartWhenFormIsMissing needs to
exercise the "stream" path as well: include the "stream" field in the multipart
body (e.g., add writer.WriteField("stream", "true")) and set the
dto.ImageRequest.Stream flag (e.g., request.Stream = true) before calling
(&Adaptor{}).ConvertImageRequest so the fallback reusable-multipart parsing
covers the stream branch.
🪄 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: 41d4fe8d-d518-4a7c-99e8-6a600afa91c6

📥 Commits

Reviewing files that changed from the base of the PR and between cfc9a34 and f706730.

📒 Files selected for processing (8)
  • dto/openai_image_test.go
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (2)
  • dto/openai_image_test.go
  • relay/helper/openai_image_request_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/valid_request.go

Comment thread relay/channel/openai/image_edit_test.go
@seefs001

seefs001 commented May 5, 2026

Copy link
Copy Markdown
Collaborator

需要考虑一下增加stream的版本和没有增加stream之间的套娃,也就是当前端按流处理但是返回的端实际上是按非流处理的情况。

@gaoren002
gaoren002 force-pushed the pr/openai-image-stream-keepalive-20260504130050 branch from f706730 to 71a66dc Compare May 7, 2026 04:32

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

🧹 Nitpick comments (1)
relay/channel/openai/image_stream_test.go (1)

18-63: 💤 Low value

OpenaiImageStreamHandler test looks good overall — one minor coverage gap.

The SSE body at line 31 sends usage as a bare data: line (no preceding event: line). If the actual OpenAI streaming API also delivers the usage event with an explicit event: response.completed or similar prefix, the test would not exercise that code path. Consider adding a second sub-case (or a second data: event with an event: prefix on the usage payload) to confirm the handler correctly extracts usage regardless of whether an event: line precedes the usage data: line.

🤖 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 `@relay/channel/openai/image_stream_test.go` around lines 18 - 63, The test
TestOpenaiImageStreamHandlerForwardsSSEAndUsage only covers a usage payload sent
as a bare `data:` line; add a second sub-case (or extend the test body) that
sends the usage payload with a preceding `event:` line (e.g., `event:
response.completed` followed by the `data: {...usage...}`) so
OpenaiImageStreamHandler is exercised for the code path that parses usage when
an `event:` prefix exists; update assertions to assert identical usage
extraction and SSE forwarding behavior for this variant as well.
🤖 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 `@relay/channel/openai/image_stream_test.go`:
- Around line 18-63: The test TestOpenaiImageStreamHandlerForwardsSSEAndUsage
only covers a usage payload sent as a bare `data:` line; add a second sub-case
(or extend the test body) that sends the usage payload with a preceding `event:`
line (e.g., `event: response.completed` followed by the `data: {...usage...}`)
so OpenaiImageStreamHandler is exercised for the code path that parses usage
when an `event:` prefix exists; update assertions to assert identical usage
extraction and SSE forwarding behavior for this variant as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 99a50d2e-ead7-4e65-9be3-54b151979831

📥 Commits

Reviewing files that changed from the base of the PR and between f706730 and 71a66dc.

📒 Files selected for processing (9)
  • dto/openai_image.go
  • dto/openai_image_test.go
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/image_edit_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/helper/openai_image_request_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (1)
  • relay/helper/openai_image_request_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • dto/openai_image_test.go
  • relay/helper/valid_request.go
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/relay-openai.go
  • dto/openai_image.go
  • relay/helper/stream_scanner.go

@gaoren002

Copy link
Copy Markdown
Contributor Author

需要考虑一下增加stream的版本和没有增加stream之间的套娃,也就是当前端按流处理但是返回的端实际上是按非流处理的情况。

已补充处理“客户端按流式请求,但上游/下游旧版本实际返回非流 JSON”的套娃场景。

这次调整点:

  • 如果图片请求进入 info.IsStream 路径,但上游 2xx 响应不是 text/event-stream,不再直接回退为普通 JSON 响应。
  • 新增 JSON fallback 包装逻辑:将 OpenAI Images 普通 JSON 响应转换为 SSE:
    • event: image_generation.completed
    • data: {...}
    • data: [DONE]
  • 保留 url / b64_json / revised_prompt,有 usage 时也会带上 usage。
  • 非 2xx 错误响应仍走原有错误处理,不扩大本次变更范围。
  • 真实 SSE 上游响应仍保持原样透传。

可以避免前面已经按 SSE/ping 提交响应头后,后面又返回裸 JSON,导致客户端语义不一致或解析异常的问题。

新增测试:

  • TestOpenaiImageStreamHandlerWrapsJSONResponse

已验证:

go test ./relay/channel/openai -run 'TestOpenaiImageStreamHandler|TestNormalizeOpenAIUsage'

@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

🤖 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 `@relay/channel/openai/relay-openai.go`:
- Around line 707-744: The first-response time is being recorded after emitting
all image events and the final DONE in this OpenAI image streaming path; move
the call to info.SetFirstResponseTime() so it executes before emitting the first
event (i.e., place it immediately before the for _, image := range
imageResp.Data loop), and keep the rest of the info updates
(info.ReceivedResponseCount, StreamStatus) after the loop as-is; reference the
existing symbols info.SetFirstResponseTime(), info.ReceivedResponseCount, and
the for _, image := range imageResp.Data loop when making the change.
- Around line 629-668: Replace the bufio.Scanner usage that reads resp.Body with
a bufio.Reader and use ReadBytes('\n') or ReadString('\n') to stream lines so
you don't hit the fixed-size Scanner buffer limit; locate the loop that
currently uses scanner := bufio.NewScanner(resp.Body) and scanner.Scan(), change
it to create a reader (bufio.NewReader(resp.Body)) and read each line with
reader.ReadBytes('\n') (or ReadString), then keep the existing handling logic
for lines (checking strings.HasPrefix("data:"), updating
info.SetFirstResponseTime(), lastStreamData handling, usage
unmarshalling/normalizeOpenAIUsage, writing to c.Writer, helper.FlushWriter and
calling info.StreamStatus.SetEndReason(...) on errors) so behavior remains the
same but without the 64MB truncation risk.
🪄 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: f016d749-fddc-427c-8405-f778b782d87a

📥 Commits

Reviewing files that changed from the base of the PR and between e79107a and 9a9609d.

📒 Files selected for processing (2)
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/openai/image_stream_test.go

Comment thread relay/channel/openai/relay-openai.go Outdated
Comment thread relay/channel/openai/relay-openai.go
@gaoren002

Copy link
Copy Markdown
Contributor Author

改动内容:

  • new-api-image-stream-pr/relay/channel/openai/relay-openai.go:图片流 SSE 遇到 event: error、type=upstream_error/error 或 error 字段时,
    记录到 StreamStatus,最终日志不再是 eof ok,而是带 stream_status.status=error。
  • new-api-image-stream-pr/relay/channel/openai/image_stream_test.go:补了 partial image 后接 upstream error 的单测。

Comment thread relay/channel/openai/relay-openai.go Outdated
return &usageResp.Usage, nil
}

func openAIErrorStatusCode(oaiError *types.OpenAIError, fallback int) int {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

加这个openAIErrorStatusCode代码是为了什么呢?

@gaoren002 gaoren002 May 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

不好意思佬,下午在忙,这里是基于我实际观察选择补充的代码。这里是为了处理上游 HTTP 200 但 body.error 非空的情况。图片链路的传输,容易遇到CF的120s超时,除了用流和partial_image作为解决方案,我观察到很多地方采用一种先发status 200,然后在body中持续传输空字节来规避超时的方法,但是如果后续出错status已经无法更改。我发现newapi的现有代码也有这种,status为200但body里面放了error的情况并不少见,因此引入了这种做法。例如:relay/channel/jimeng/image.go relay/channel/zhipu_4v/image.go 具体到这个函数就是在status为200的情况下从body里的error里面pick出对应的errorcode

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

原则上是给非流的时候用的

@Calcium-Ion Calcium-Ion May 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

必须严格使用上游状态码,这部分代码是不能接受的,状态码为200的情况下,虽然body error status为错误状态,但是无法确认上游是否已经扣费,这种情况下进行重试,极有可能导致大额亏损

@seefs001 seefs001 removed their assignment May 29, 2026
@Calcium-Ion

Calcium-Ion commented May 31, 2026

Copy link
Copy Markdown
Member

这个 PR 是个很好的功能,感谢贡献。我稍后整理确认后会合并。

有一点建议:这个 PR 里修改了较多与主题关联不强的内容,例如 responses 相关代码、stream scanner 相关调整等,整体涉及文件偏多,会增加维护者的审核负担。这可能也是 PR 一直搁置、没有维护者及时跟进的原因之一。

后续提交类似功能时,建议尽量把变更范围控制在当前功能本身:核心功能、必要的兼容处理、对应测试放在一个 PR;额外重构或无关修复可以拆成单独 PR。这样更容易 review,也更容易尽快合并

还有一点小建议:当前提交记录里不少 commit author 是 Codex codex@local,这会让提交历史看起来不太正式,也不利于后续追踪贡献者和排查问题。建议之后使用自己的 GitHub 用户名和邮箱提交,AI 可以辅助写代码,但最终提交身份最好保持为贡献者本人。

@gaoren002
gaoren002 requested a review from Calcium-Ion June 3, 2026 14:31
@gaoren002

Copy link
Copy Markdown
Contributor Author

完了,又被遗忘了 @seefs001 @Calcium-Ion @t0ng7u @songquanpeng

@Calcium-Ion
Calcium-Ion merged commit d2576dd into QuantumNous:main Jun 8, 2026
1 check passed
ottocsb added a commit to ottocsb/new-api that referenced this pull request Jun 9, 2026
* fix(openai): support streaming image relay and image edit for images API  (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>

* fix: 适配上游合并测试文件 import 路径为本 fork module(newapi)

上游 QuantumNous#4608 新增的 openai 图片相关测试文件使用 github.com/QuantumNous/new-api 路径,
本 fork module 名为 newapi,统一替换为 newapi/ 前缀以恢复编译。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: gaoren002 <83566620+gaoren002@users.noreply.github.com>
Co-authored-by: CaIon <i@caion.me>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ottocsb added a commit to ottocsb/new-api that referenced this pull request Jun 9, 2026
PR#3 以 squash 方式合并了上游 QuantumNous#4608(OpenAI 图片流式中继与编辑修复),
实际代码已在 main 中,但 squash 切断了到上游提交 d2576dd 的祖先链路,
导致比较工具按 SHA 仍认为该上游提交未合并。
此处用 -s ours 仅补记父链(不改动任何文件内容),恢复正常的上游合并记录。
52assert added a commit to 52assert/new-api that referenced this pull request Jun 10, 2026
…codes

* origin/main: (45 commits)
  fix(openai): support streaming image relay and image edit for images API  (QuantumNous#4608)
  perf(web): improve dialog sizing and footer layout
  feat(web): add shared dialog wrapper
  perf(web): simplify public page hero copy
  perf(model-pricing): move pricing tabs into page title
  feat(json-editor): add reusable JSON code editor
  perf(model-pricing): improve JSON pricing editor layout
  perf(model-pricing): reduce duplicate model name display
  fix: support six-decimal steps in model pricing editor
  fix: respect theme for multiselect combobox popover
  fix: reuse stream scanner buffer in channel handlers (QuantumNous#5225)
  fix: 收窄 OpenAI o 系列模型适配范围 (QuantumNous#5293)
  fix(i18n): clarify thinking adapter copy (QuantumNous#5242)
  fix: limit anonymous request body (QuantumNous#5244)
  fix(relay): fix Anthropic-compatible compatibility for GLM (avoid chunked encoding) (QuantumNous#5307)
  feat: 支持配置渠道被禁用后是否清空渠道粘性 (QuantumNous#5306)
  fix: add relay idle connection timeout config (QuantumNous#5309)
  feat(web): show user id on profile page
  perf(model-pricing): refine visual editor actions
  refactor(model-pricing): split visual pricing editor modules
  ...

# Conflicts:
#	router/api-router.go
#	web/default/src/features/usage-logs/components/usage-logs-mobile-card.tsx
endercat-alu pushed a commit to endercat-alu/new-api that referenced this pull request Jun 10, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
(cherry picked from commit d2576dd)
tongkaiteng pushed a commit to tongkaiteng/new-api that referenced this pull request Jun 12, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
OuYang-HX pushed a commit to OuYang-HX/new-api that referenced this pull request Jun 13, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
YeMao11 pushed a commit to YeMao11/new-api that referenced this pull request Jun 16, 2026
Upstream changes (50+ commits, v1.0.0-rc.11):
- data-table perf: row selection memo, column pinning, badge display
- Fixes: channel test dialog (QuantumNous#5517), CC Switch model selector (QuantumNous#5515),
  API key form options (QuantumNous#5512), cell overflow (QuantumNous#5510), kimi k2.6 temp (QuantumNous#5390),
  Anthropic-compatible GLM chunked encoding (QuantumNous#5307), streaming image relay (QuantumNous#4608)
- Feat: audit auth method tracking (QuantumNous#5462), channel affinity clear toggle (QuantumNous#5306),
  relay idle timeout config (QuantumNous#5309), 6-decimal pricing precision (QuantumNous#5332)
- Classic frontend: Rsbuild support, Semi React 19 adapter
- Shared dialog wrapper, JSON code editor, debounce channel search

Conflict resolved: web/bun.lock (accepted upstream, will regenerate)

Co-Authored-By: Claude <noreply@anthropic.com>
YeMao11 pushed a commit to YeMao11/new-api that referenced this pull request Jun 16, 2026
Merge upstream v1.0.0-rc.11 (50+ commits):
- data-table perf: row selection memo, column pinning, badge display
- Fixes: channel test dialog (QuantumNous#5517), CC Switch (QuantumNous#5515), API key (QuantumNous#5512),
  kimi k2.6 temp (QuantumNous#5390), GLM chunked encoding (QuantumNous#5307), streaming image (QuantumNous#4608)
- Feat: audit auth tracking (QuantumNous#5462), channel affinity toggle (QuantumNous#5306),
  relay idle timeout (QuantumNous#5309), 6-decimal pricing (QuantumNous#5332)
- Shared dialog wrapper, JSON code editor, classic Rsbuild support

SEO optimization:
- robots.txt: 10 AI crawler blocks + 22 path disallows + crawl-delay
- sitemap.xml: 7 public URLs with 6-language hreflang annotations
- index.html: hreflang tags, og:locale:alternate, og:image, canonical,
  5 structured data types (Organization, SoftwareApplication, FAQPage,
  WebSite, SearchAction), expanded keywords (gateway, agent router,
  aggregation, orchestration)
- i18n/config.ts: sync <html lang> with active language for SEO

Co-Authored-By: Claude <noreply@anthropic.com>
ruanhangjian pushed a commit to ruanhangjian/new-api that referenced this pull request Jul 11, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
noah-wung pushed a commit to noah-wung/new-api that referenced this pull request Jul 17, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
zhaodechao2008 pushed a commit to zhaodechao2008/new-api that referenced this pull request Jul 27, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
330079598 pushed a commit to 330079598/new-api that referenced this pull request Aug 19, 2026
…API (QuantumNous#4608)

* fix(openai): support streaming image relay

* fix(openai): keep image edit multipart body reusable

* test(openai): cover image stream usage details

* test(openai): cover image edit fallback stream field

* fix(openai): wrap image json fallback as stream

* fix(relay): support OpenAI image streaming

* fix(openai): record image stream upstream error events

* fix(openai): harden image stream relay

* fix(openai): return image JSON errors

* fix(relay): reset stream status per scanner run

* fix(relay): drop upstream credit passthrough

* fix(openai): keep image errors minimal

* fix(openai): keep image error status from response

---------

Co-authored-by: CaIon <i@caion.me>
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.

OpenAI Images API 长耗时请求被 Cloudflare 524 切断 stream=true 未按 Images SSE 处理且缺少结果取回机制

6 participants