Skip to content

fix issue #867#881

Closed
Cuttttay wants to merge 3 commits intoWei-Shaw:mainfrom
Cuttttay:main
Closed

fix issue #867#881
Cuttttay wants to merge 3 commits intoWei-Shaw:mainfrom
Cuttttay:main

Conversation

@Cuttttay
Copy link

@Cuttttay Cuttttay commented Mar 9, 2026

修改文件:backend/internal/service/openai_gateway_messages.go
问题根因:isStream 变量身兼两职——同时控制"上游请求是否流式"和"客户端响应是否流式"。OAuth 账号为满足上游要求强制将其设为 true,导致即使客户端发送 stream: false,响应仍以 SSE 格式返回。
修复思路:拆分两个变量的职责。 1.clientWantsStream:只记录客户端的原始意图,全程不变
2.isStream:只控制上游请求格式,OAuth 时可被覆盖为 true
响应阶段改用 clientWantsStream 做分支判断,并新增 handleAnthropicStreamToNonStreamingResponse,处理"上游 SSE → 客户端 JSON"的转换(读完 SSE 流后组装成单个 JSON 对象返回)。

Copilot AI review requested due to automatic review settings March 9, 2026 03:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes issue #867 where a single isStream flag was incorrectly used to control both upstream streaming (SSE) and downstream client streaming, causing OAuth accounts (which force upstream SSE) to return SSE even when the client requested stream:false.

Changes:

  • Split streaming intent into clientWantsStream (downstream behavior) and isStream (upstream request behavior, OAuth may force to true).
  • Update response handling to branch on clientWantsStream, adding an SSE→JSON aggregation path when upstream must stream but client does not.
  • Add handleAnthropicStreamToNonStreamingResponse to read the upstream Responses SSE stream and assemble a single Anthropic Messages JSON response.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +526 to +532
// Assemble content blocks in index order.
var content []apicompat.AnthropicContentBlock
for idx := 0; idx < len(blocks); idx++ {
b, ok := blocks[idx]
if !ok {
continue
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

When assembling content from blocks, iterating idx := 0; idx < len(blocks); idx++ can drop blocks if indices are non-contiguous or don’t start at 0 (e.g., keys {0,2} => len=2, index 2 never visited). Track the max index or collect/sort the map keys and iterate in order to ensure all blocks are included.

Copilot uses AI. Check for mistakes.

if s.responseHeaderFilter != nil {
responseheaders.WriteFilteredHeaders(c.Writer.Header(), resp.Header, s.responseHeaderFilter)
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

WriteFilteredHeaders forwards upstream Content-Type by default (and upstream is text/event-stream in this path). Gin’s c.JSON won’t overwrite an existing Content-Type, so clients may receive a JSON body with text/event-stream header. Explicitly Set the downstream Content-Type to application/json; charset=utf-8 (ideally after copying headers, or ensure the filter doesn’t forward Content-Type here).

Suggested change
}
}
// Ensure JSON responses are served with the correct Content-Type, even if
// the upstream forwarded a different Content-Type (e.g., text/event-stream).
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +172
switch {
case clientWantsStream:
// Client wants SSE; upstream is also streaming — pass through directly.
result, handleErr = s.handleAnthropicStreamingResponse(resp, c, originalModel, mappedModel, startTime)
} else {
case isStream:
// Upstream is streaming (OAuth forced) but client wants a single JSON
// object — collect the SSE and assemble the non-streaming response.
result, handleErr = s.handleAnthropicStreamToNonStreamingResponse(resp, c, originalModel, mappedModel, startTime)
default:
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

This introduces a new behavioral branch (upstream SSE → client JSON via handleAnthropicStreamToNonStreamingResponse) that isn’t covered by tests. There are existing tests for OAuth SSE→JSON conversion in openai_gateway_service_test.go; adding a similar unit test for ForwardAsAnthropic/handleAnthropicStreamToNonStreamingResponse would help prevent regressions (e.g., client stream:false with OAuth account returns a single JSON body and correct Content-Type/usage).

Copilot uses AI. Check for mistakes.
@Wei-Shaw
Copy link
Owner

Wei-Shaw commented Mar 9, 2026

25178cd

@Wei-Shaw Wei-Shaw closed this Mar 9, 2026
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.

3 participants