mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
Merge pull request #7038 from zenggang/contribute/gpt55-lite-compat
fix(openai): remove Responses Lite markers after OAuth mapping to GPT-5.5
This commit is contained in:
@@ -1523,6 +1523,9 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
|
||||
setOpenAICodexRoutingHintFromBody(req.Header, account, body)
|
||||
logOpenAIRoutingDiagnosticsFromBody(ctx, account, "http", req.Header, body, "not_applicable")
|
||||
|
||||
if err := applyMappedGPT55LiteCompatibility(req, account, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -732,6 +732,9 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
|
||||
setOpenAICodexRoutingHintFromBody(req.Header, account, body)
|
||||
logOpenAIRoutingDiagnosticsFromBody(ctx, account, "http_passthrough", req.Header, body, "not_applicable")
|
||||
|
||||
if err := applyMappedGPT55LiteCompatibility(req, account, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// The account mapping changes the model, not the upstream capability header.
|
||||
// Apply at the final HTTP request boundary, after account/model resolution.
|
||||
// Never mutate the ingress headers/body: failover may select a native account.
|
||||
func applyMappedGPT55LiteCompatibility(req *http.Request, account *Account, body []byte) error {
|
||||
if req == nil || account == nil || !account.IsOpenAIOAuthLike() {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(gjson.GetBytes(body, "model").String()) != "gpt-5.5" {
|
||||
return nil
|
||||
}
|
||||
if !isOpenAIResponsesLiteHeader(req.Header.Get(responsesLiteHeader)) && !isOpenAIResponsesLiteWebSocketPayload(body) {
|
||||
return nil
|
||||
}
|
||||
// The Codex non-Lite endpoint accepts additional_tools, namespaces and
|
||||
// reasoning.context=all_turns. Preserve them and all history/tool results.
|
||||
if isOpenAIResponsesLiteWebSocketPayload(body) {
|
||||
var err error
|
||||
body, err = sjson.DeleteBytes(body, "client_metadata."+responsesLiteWSMetadataKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove mapped GPT-5.5 Lite metadata: %w", err)
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
savedBody := append([]byte(nil), body...)
|
||||
req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(savedBody)), nil }
|
||||
}
|
||||
req.Header.Del(responsesLiteHeader)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestMappedGPT55LiteCompatibility(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
id int64
|
||||
model string
|
||||
mapped bool
|
||||
wantLite bool
|
||||
}{
|
||||
{"mapped", 12, "gpt-5.5", true, false},
|
||||
{"account13Mapped", 13, "gpt-5.5", true, false},
|
||||
{"account14Mapped", 14, "gpt-5.5", true, false},
|
||||
{"account13Native", 13, "gpt-6-astra", false, true},
|
||||
{"account14Native", 14, "gpt-6-astra", false, true},
|
||||
{"nativeSol", 12, "gpt-5.6-sol", true, true},
|
||||
{"noMapping", 12, "gpt-5.5", false, false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
a := &Account{ID: tc.id, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Credentials: map[string]any{}}
|
||||
if tc.mapped {
|
||||
a.Credentials["model_mapping"] = map[string]any{"gpt-5.6-sol": "gpt-5.5"}
|
||||
}
|
||||
body := []byte(`{"model":"` + tc.model + `","input":[{"type":"additional_tools","role":"developer","tools":[]},{"type":"function_call_output","call_id":"call1","output":"12345678901234567890"}],"reasoning":{"context":"all_turns"},"client_metadata":{"ws_request_header_x_openai_internal_codex_responses_lite":"true","keep":"yes"}}`)
|
||||
r, err := http.NewRequest("POST", "http://localhost", bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
r.Header.Set(responsesLiteHeader, "true")
|
||||
require.NoError(t, applyMappedGPT55LiteCompatibility(r, a, body))
|
||||
require.Equal(t, tc.wantLite, isOpenAIResponsesLiteHeader(r.Header.Get(responsesLiteHeader)))
|
||||
got, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
if tc.wantLite {
|
||||
require.Equal(t, body, got)
|
||||
} else {
|
||||
require.False(t, isOpenAIResponsesLiteWebSocketPayload(got))
|
||||
require.Equal(t, gjson.GetBytes(body, "input").Raw, gjson.GetBytes(got, "input").Raw)
|
||||
require.Equal(t, "all_turns", gjson.GetBytes(got, "reasoning.context").String())
|
||||
require.Equal(t, "yes", gjson.GetBytes(got, "client_metadata.keep").String())
|
||||
require.Equal(t, int64(len(got)), r.ContentLength)
|
||||
replay, err := r.GetBody()
|
||||
require.NoError(t, err)
|
||||
again, err := io.ReadAll(replay)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, got, again)
|
||||
}
|
||||
require.True(t, isOpenAIResponsesLiteWebSocketPayload(body))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMappedGPT55LiteBuildersPreserveIngressForFailover(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
for _, passthrough := range []bool{false, true} {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = httptest.NewRequest("POST", "/v1/responses", nil)
|
||||
c.Request.Header.Set(responsesLiteHeader, "true")
|
||||
s := &OpenAIGatewayService{cfg: &config.Config{}}
|
||||
a := &Account{ID: 12, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Credentials: map[string]any{"model_mapping": map[string]any{"gpt-5.6-sol": "gpt-5.5"}}}
|
||||
body := []byte(`{"model":"gpt-5.5","stream":true,"input":[]}`)
|
||||
var r *http.Request
|
||||
var err error
|
||||
if passthrough {
|
||||
r, err = s.buildUpstreamRequestOpenAIPassthrough(context.Background(), c, a, body, "test-token")
|
||||
} else {
|
||||
r, err = s.buildUpstreamRequest(context.Background(), c, a, body, "test-token", true, "", true)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, r.Header.Get(responsesLiteHeader))
|
||||
require.Equal(t, "true", c.GetHeader(responsesLiteHeader))
|
||||
a.ID = 14
|
||||
body = []byte(`{"model":"gpt-6-astra","stream":true,"input":[]}`)
|
||||
if passthrough {
|
||||
r, err = s.buildUpstreamRequestOpenAIPassthrough(context.Background(), c, a, body, "test-token")
|
||||
} else {
|
||||
r, err = s.buildUpstreamRequest(context.Background(), c, a, body, "test-token", true, "", true)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "true", r.Header.Get(responsesLiteHeader))
|
||||
}
|
||||
}
|
||||
@@ -505,6 +505,9 @@ func (s *OpenAIGatewayService) proxyOpenAIWSHTTPBridgeTurn(
|
||||
if account.Platform != PlatformGrok && isOpenAIResponsesLiteWebSocketPayload(payload) {
|
||||
upstreamReq.Header.Set(responsesLiteHeader, "true")
|
||||
}
|
||||
if err := applyMappedGPT55LiteCompatibility(upstreamReq, account, requestBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return upstreamReq, nil
|
||||
}
|
||||
if account.Platform == PlatformGrok {
|
||||
|
||||
Reference in New Issue
Block a user