mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix(openai): strip internal metadata from OAuth input items
This commit is contained in:
@@ -1062,6 +1062,19 @@ func normalizeOpenAIOAuthResponsesCompatibilityFields(reqBody map[string]any) bo
|
||||
delete(reqBody, "commands")
|
||||
changed = true
|
||||
}
|
||||
// Codex can attach internal message metadata when a custom provider is
|
||||
// named OpenAI. ChatGPT rejects this field on input items (#7066).
|
||||
input, _ := reqBody["input"].([]any)
|
||||
for _, value := range input {
|
||||
item, ok := value.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, exists := item["internal_chat_message_metadata_passthrough"]; exists {
|
||||
delete(item, "internal_chat_message_metadata_passthrough")
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
@@ -1096,6 +1109,22 @@ func normalizeOpenAIOAuthResponsesCompatibilityBody(body []byte) ([]byte, bool,
|
||||
normalized = next
|
||||
changed = true
|
||||
}
|
||||
// Only remove the input-item field, never same-named user content.
|
||||
input := gjson.GetBytes(normalized, "input")
|
||||
if !input.IsArray() {
|
||||
return normalized, changed, nil
|
||||
}
|
||||
for i, item := range input.Array() {
|
||||
if !item.IsObject() || !item.Get("internal_chat_message_metadata_passthrough").Exists() {
|
||||
continue
|
||||
}
|
||||
next, err := sjson.DeleteBytes(normalized, fmt.Sprintf("input.%d.internal_chat_message_metadata_passthrough", i))
|
||||
if err != nil {
|
||||
return body, false, fmt.Errorf("normalize oauth input metadata: %w", err)
|
||||
}
|
||||
normalized = next
|
||||
changed = true
|
||||
}
|
||||
return normalized, changed, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestOAuthInputInternalMetadata(t *testing.T) {
|
||||
const field = "internal_chat_message_metadata_passthrough"
|
||||
// The identically named field in user content must not be recursively stripped.
|
||||
const body = `{"model":"gpt-5.5","input":[{"role":"user","content":[{"type":"input_text","text":"hello","internal_chat_message_metadata_passthrough":{"keep":true}}],"internal_chat_message_metadata_passthrough":{"content_item_kinds":["text"]}},{"type":"function_call","call_id":"fc_123","name":"echo","arguments":"{\"internal_chat_message_metadata_passthrough\":true}","internal_chat_message_metadata_passthrough":null},{"type":"function_call_output","call_id":"fc_123","output":"result"}],"internal_chat_message_metadata_passthrough":{"keep":true}}`
|
||||
tests := []struct {
|
||||
name string
|
||||
normalize func([]byte) ([]byte, bool, error)
|
||||
strip bool
|
||||
}{
|
||||
{"transformed OAuth", func(b []byte) ([]byte, bool, error) {
|
||||
var req map[string]any
|
||||
if err := json.Unmarshal(b, &req); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
result := applyCodexOAuthTransform(req, false, false)
|
||||
out, err := json.Marshal(req)
|
||||
return out, result.Modified, err
|
||||
}, true},
|
||||
{"OAuth passthrough", func(b []byte) ([]byte, bool, error) { return normalizeOpenAIPassthroughOAuthBody(b, false) }, true},
|
||||
{"OAuth compact", func(b []byte) ([]byte, bool, error) { return normalizeOpenAIPassthroughOAuthBody(b, true) }, true},
|
||||
{"OAuth websocket", func(b []byte) ([]byte, bool, error) {
|
||||
return normalizeOpenAIResponsesWebSocketCompatibilityBody(b, &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth}, false)
|
||||
}, true},
|
||||
{"API key websocket", func(b []byte) ([]byte, bool, error) {
|
||||
return normalizeOpenAIResponsesWebSocketCompatibilityBody(b, &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}, false)
|
||||
}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
out, _, err := tt.normalize([]byte(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, !tt.strip, gjson.GetBytes(out, "input.0."+field).Exists())
|
||||
require.Equal(t, !tt.strip, gjson.GetBytes(out, "input.1."+field).Exists())
|
||||
require.Equal(t, "hello", gjson.GetBytes(out, "input.0.content.0.text").String())
|
||||
require.True(t, gjson.GetBytes(out, "input.0.content.0."+field+".keep").Bool())
|
||||
require.True(t, gjson.GetBytes(out, field+".keep").Bool())
|
||||
require.Equal(t, `{"internal_chat_message_metadata_passthrough":true}`, gjson.GetBytes(out, "input.1.arguments").String())
|
||||
require.Equal(t, "fc_123", gjson.GetBytes(out, "input.1.call_id").String())
|
||||
require.Equal(t, "result", gjson.GetBytes(out, "input.2.output").String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthInputInternalMetadataCompatibility(t *testing.T) {
|
||||
for _, body := range []string{
|
||||
`{"input":"hello"}`,
|
||||
`{"input":{"internal_chat_message_metadata_passthrough":true}}`,
|
||||
`{"input":[null,"hello",{"role":"user","content":"hello"}]}`,
|
||||
`{"input":[{"content":{"internal_chat_message_metadata_passthrough":true}}]}`,
|
||||
} {
|
||||
t.Run(body, func(t *testing.T) {
|
||||
out, changed, err := normalizeOpenAIOAuthResponsesCompatibilityBody([]byte(body))
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
require.Equal(t, body, string(out))
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(body), &req))
|
||||
require.False(t, normalizeOpenAIOAuthResponsesCompatibilityFields(req))
|
||||
})
|
||||
}
|
||||
// Legacy prompt aliases and later-turn payloads use the same boundary.
|
||||
body := []byte(`{"prompt":[{"role":"user","content":"hello","internal_chat_message_metadata_passthrough":{}}],"previous_response_id":"resp_123"}`)
|
||||
out, changed, err := normalizeOpenAIOAuthResponsesCompatibilityBody(body)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
require.False(t, gjson.GetBytes(out, "input.0.internal_chat_message_metadata_passthrough").Exists())
|
||||
require.Equal(t, "resp_123", gjson.GetBytes(out, "previous_response_id").String())
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal(body, &req))
|
||||
require.True(t, normalizeOpenAIOAuthResponsesCompatibilityFields(req))
|
||||
require.NotContains(t, req["input"].([]any)[0], "internal_chat_message_metadata_passthrough")
|
||||
}
|
||||
Reference in New Issue
Block a user