From 851436c552bdc003d4a1b1affa13b19855494b8d Mon Sep 17 00:00:00 2001 From: li Date: Wed, 22 Jul 2026 16:46:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(openai):=20OAuth=20=E9=80=8F=E4=BC=A0?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E8=A1=A5=E9=BD=90=20input=20=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E5=8C=96=EF=BC=8C=E4=BF=AE=E5=A4=8D=20Input=20must=20?= =?UTF-8?q?be=20a=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openai_passthrough=true 的 OAuth 账号透传路径 (normalizeOpenAIPassthroughOAuthBody) 未对 input 字段做 string→array 转换,导致上游拒绝 "Input must be a list"。 正常 OAuth 路径 (openai_codex_transform.go:290) 已有此逻辑, 透传路径遗漏。补齐三种非数组 input 的规范化: - string → [{type:"message",role:"user",content:}] - 空白 string → [] - 单 object → [] --- .../service/openai_gateway_request_body.go | 28 ++++++++++ .../openai_passthrough_normalization_test.go | 54 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/backend/internal/service/openai_gateway_request_body.go b/backend/internal/service/openai_gateway_request_body.go index b47c2c8638..d2846c59a0 100644 --- a/backend/internal/service/openai_gateway_request_body.go +++ b/backend/internal/service/openai_gateway_request_body.go @@ -575,6 +575,34 @@ func normalizeOpenAIPassthroughOAuthBody(body []byte, compact bool) ([]byte, boo changed = true } + if inputResult := gjson.GetBytes(normalized, "input"); inputResult.Exists() { + switch { + case inputResult.Type == gjson.String: + text := inputResult.String() + var inputValue any + if strings.TrimSpace(text) != "" { + inputValue = []map[string]any{{ + "type": "message", "role": "user", "content": text, + }} + } else { + inputValue = []any{} + } + next, err := sjson.SetBytes(normalized, "input", inputValue) + if err != nil { + return body, false, fmt.Errorf("normalize passthrough body input string: %w", err) + } + normalized = next + changed = true + case inputResult.Type == gjson.JSON && !inputResult.IsArray(): + next, err := sjson.SetRawBytes(normalized, "input", []byte("["+inputResult.Raw+"]")) + if err != nil { + return body, false, fmt.Errorf("normalize passthrough body input object: %w", err) + } + normalized = next + changed = true + } + } + if compact { if store := gjson.GetBytes(normalized, "store"); store.Exists() { next, err := sjson.DeleteBytes(normalized, "store") diff --git a/backend/internal/service/openai_passthrough_normalization_test.go b/backend/internal/service/openai_passthrough_normalization_test.go index 492ff61057..0212c3e1f8 100644 --- a/backend/internal/service/openai_passthrough_normalization_test.go +++ b/backend/internal/service/openai_passthrough_normalization_test.go @@ -31,3 +31,57 @@ func TestNormalizeOpenAIPassthroughOAuthBody_CompactRemovesUnsupportedUser(t *te require.False(t, gjson.GetBytes(normalized, "stream").Exists()) require.False(t, gjson.GetBytes(normalized, "store").Exists()) } + +func TestNormalizeOpenAIPassthroughOAuthBody_StringInputWrappedAsArray(t *testing.T) { + body := []byte(`{"model":"gpt-5.4","input":"hello world"}`) + + normalized, changed, err := normalizeOpenAIPassthroughOAuthBody(body, false) + require.NoError(t, err) + require.True(t, changed) + + input := gjson.GetBytes(normalized, "input") + require.True(t, input.IsArray(), "string input should be converted to array") + items := input.Array() + require.Len(t, items, 1) + require.Equal(t, "message", items[0].Get("type").String()) + require.Equal(t, "user", items[0].Get("role").String()) + require.Equal(t, "hello world", items[0].Get("content").String()) +} + +func TestNormalizeOpenAIPassthroughOAuthBody_EmptyStringInputWrappedAsEmptyArray(t *testing.T) { + body := []byte(`{"model":"gpt-5.4","input":" "}`) + + normalized, changed, err := normalizeOpenAIPassthroughOAuthBody(body, false) + require.NoError(t, err) + require.True(t, changed) + + input := gjson.GetBytes(normalized, "input") + require.True(t, input.IsArray()) + require.Len(t, input.Array(), 0, "whitespace-only input should become empty array") +} + +func TestNormalizeOpenAIPassthroughOAuthBody_ObjectInputWrappedAsArray(t *testing.T) { + body := []byte(`{"model":"gpt-5.4","input":{"type":"message","role":"user","content":"hi"}}`) + + normalized, changed, err := normalizeOpenAIPassthroughOAuthBody(body, false) + require.NoError(t, err) + require.True(t, changed) + + input := gjson.GetBytes(normalized, "input") + require.True(t, input.IsArray(), "object input should be wrapped in array") + items := input.Array() + require.Len(t, items, 1) + require.Equal(t, "message", items[0].Get("type").String()) +} + +func TestNormalizeOpenAIPassthroughOAuthBody_ArrayInputUnchanged(t *testing.T) { + body := []byte(`{"model":"gpt-5.4","input":[{"type":"message","role":"user","content":"hi"}]}`) + + normalized, _, err := normalizeOpenAIPassthroughOAuthBody(body, false) + require.NoError(t, err) + + input := gjson.GetBytes(normalized, "input") + require.True(t, input.IsArray()) + require.Len(t, input.Array(), 1) + require.Equal(t, "message", input.Array()[0].Get("type").String()) +}