feat: implement Gemini to OpenAI chat stream conversion with state management and terminal handling

This commit is contained in:
CaIon
2026-07-27 20:17:20 +08:00
parent f3ab2cff36
commit a043eef559
10 changed files with 894 additions and 37 deletions
@@ -12,6 +12,7 @@ import (
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relaykit/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -86,6 +87,60 @@ func TestOaiResponsesToChatStreamHandlerConvertsSSEOrderAndUsage(t *testing.T) {
)
}
func TestOaiResponsesToChatStreamHandlerConvertsClaudeSSETerminalsAndUsage(t *testing.T) {
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 })
body := strings.Join([]string{
`data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-test","created_at":1710000000}}`,
`data: {"type":"response.output_text.delta","delta":"hello"}`,
`data: {"type":"response.completed","response":{"status":"completed","usage":{"input_tokens":2,"output_tokens":3,"total_tokens":5}}}`,
`data: [DONE]`,
``,
}, "\n")
c, recorder, resp, info := newResponsesChatTestContext(t, body, true)
info.RelayFormat = types.RelayFormatClaude
usage, err := OaiResponsesToChatStreamHandler(c, info, resp)
require.Nil(t, err)
require.NotNil(t, usage)
assert.Equal(t, 2, usage.PromptTokens)
assert.Equal(t, 3, usage.CompletionTokens)
assert.Equal(t, 5, usage.TotalTokens)
got := recorder.Body.String()
assert.Equal(t, "text/event-stream", recorder.Header().Get("Content-Type"))
assert.Equal(t, 1, strings.Count(got, "event: message_start\n"))
assert.Equal(t, 1, strings.Count(got, "event: content_block_stop\n"))
assert.Equal(t, 1, strings.Count(got, "event: message_delta\n"))
assert.Equal(t, 1, strings.Count(got, "event: message_stop\n"))
messageDeltaFrame := ""
for _, frame := range strings.Split(got, "\n\n") {
if strings.HasPrefix(frame, "event: message_delta\n") {
messageDeltaFrame = frame
break
}
}
require.NotEmpty(t, messageDeltaFrame)
assert.Contains(t, messageDeltaFrame, `"type":"message_delta"`)
assert.Contains(t, messageDeltaFrame, `"stop_reason":"end_turn"`)
assert.Contains(t, messageDeltaFrame, `"input_tokens":2`)
assert.Contains(t, messageDeltaFrame, `"output_tokens":3`)
requireOrderedSubstrings(t, got,
"event: message_start\n",
"event: content_block_stop\n",
"event: message_delta\n",
"event: message_stop\n",
)
}
func TestOaiResponsesToChatBufferedStreamHandlerReturnsJSONFromSSE(t *testing.T) {
oldMode := gin.Mode()
gin.SetMode(gin.TestMode)