mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
Merge pull request #3337 from ddnio/codex/openai-json-mode-developer-input
fix(openai): preserve JSON instructions in Codex OAuth input
This commit is contained in:
@@ -212,7 +212,9 @@ func applyCodexOAuthTransformWithOptions(reqBody map[string]any, opts codexOAuth
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 input 中 role:"system" 消息至 instructions(OAuth 上游不支持 system role)。
|
||||
// ChatGPT internal Codex endpoint does not accept role:"system".
|
||||
// Keep the guidance in input as developer for Responses JSON mode, and
|
||||
// also mirror it into instructions because Codex OAuth requires it.
|
||||
if extractSystemMessagesFromInput(reqBody) {
|
||||
result.Modified = true
|
||||
}
|
||||
@@ -948,10 +950,10 @@ func extractTextFromContent(content any) string {
|
||||
}
|
||||
}
|
||||
|
||||
// extractSystemMessagesFromInput scans the input array for items with role=="system",
|
||||
// removes them, and merges their content into reqBody["instructions"].
|
||||
// If instructions is already non-empty, extracted content is prepended with "\n\n".
|
||||
// Returns true if any system messages were extracted.
|
||||
// extractSystemMessagesFromInput scans input for role=="system", maps those
|
||||
// items to developer, and mirrors their text into reqBody["instructions"].
|
||||
// It preserves the input items so Responses JSON mode can still see JSON
|
||||
// instructions in input messages.
|
||||
func extractSystemMessagesFromInput(reqBody map[string]any) bool {
|
||||
input, ok := reqBody["input"].([]any)
|
||||
if !ok || len(input) == 0 {
|
||||
@@ -959,25 +961,24 @@ func extractSystemMessagesFromInput(reqBody map[string]any) bool {
|
||||
}
|
||||
|
||||
var systemTexts []string
|
||||
remaining := make([]any, 0, len(input))
|
||||
|
||||
modified := false
|
||||
for _, item := range input {
|
||||
m, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
remaining = append(remaining, item)
|
||||
continue
|
||||
}
|
||||
if role, _ := m["role"].(string); role != "system" {
|
||||
remaining = append(remaining, item)
|
||||
continue
|
||||
}
|
||||
m["role"] = "developer"
|
||||
modified = true
|
||||
if text := extractTextFromContent(m["content"]); text != "" {
|
||||
systemTexts = append(systemTexts, text)
|
||||
}
|
||||
}
|
||||
|
||||
if len(systemTexts) == 0 {
|
||||
return false
|
||||
return modified
|
||||
}
|
||||
|
||||
extracted := strings.Join(systemTexts, "\n\n")
|
||||
@@ -986,7 +987,6 @@ func extractSystemMessagesFromInput(reqBody map[string]any) bool {
|
||||
} else {
|
||||
reqBody["instructions"] = extracted
|
||||
}
|
||||
reqBody["input"] = remaining
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1094,10 +1094,14 @@ func TestExtractSystemMessagesFromInput(t *testing.T) {
|
||||
require.True(t, result)
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 1)
|
||||
require.Len(t, input, 2)
|
||||
msg, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", msg["role"])
|
||||
require.Equal(t, "developer", msg["role"])
|
||||
require.Equal(t, "You are an assistant.", msg["content"])
|
||||
user, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", user["role"])
|
||||
require.Equal(t, "You are an assistant.", reqBody["instructions"])
|
||||
})
|
||||
|
||||
@@ -1117,7 +1121,14 @@ func TestExtractSystemMessagesFromInput(t *testing.T) {
|
||||
require.Equal(t, "Be helpful.", reqBody["instructions"])
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 0)
|
||||
require.Len(t, input, 1)
|
||||
msg, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "developer", msg["role"])
|
||||
require.Equal(t, []any{
|
||||
map[string]any{"type": "text", "text": "Be helpful."},
|
||||
}, msg["content"])
|
||||
require.Equal(t, "Be helpful.", reqBody["instructions"])
|
||||
})
|
||||
|
||||
t.Run("multiple system messages concatenated", func(t *testing.T) {
|
||||
@@ -1133,7 +1144,17 @@ func TestExtractSystemMessagesFromInput(t *testing.T) {
|
||||
require.Equal(t, "First.\n\nSecond.", reqBody["instructions"])
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 1)
|
||||
require.Len(t, input, 3)
|
||||
first, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "developer", first["role"])
|
||||
second, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "developer", second["role"])
|
||||
user, ok := input[2].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", user["role"])
|
||||
require.Equal(t, "First.\n\nSecond.", reqBody["instructions"])
|
||||
})
|
||||
|
||||
t.Run("mixed system and non-system preserves non-system", func(t *testing.T) {
|
||||
@@ -1148,13 +1169,17 @@ func TestExtractSystemMessagesFromInput(t *testing.T) {
|
||||
require.True(t, result)
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 2)
|
||||
require.Len(t, input, 3)
|
||||
first, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", first["role"])
|
||||
second, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "assistant", second["role"])
|
||||
require.Equal(t, "developer", second["role"])
|
||||
third, ok := input[2].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "assistant", third["role"])
|
||||
require.Equal(t, "Sys prompt.", reqBody["instructions"])
|
||||
})
|
||||
|
||||
t.Run("existing instructions prepended", func(t *testing.T) {
|
||||
@@ -1168,6 +1193,11 @@ func TestExtractSystemMessagesFromInput(t *testing.T) {
|
||||
result := extractSystemMessagesFromInput(reqBody)
|
||||
require.True(t, result)
|
||||
require.Equal(t, "Extracted.\n\nExisting instructions.", reqBody["instructions"])
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
msg, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "developer", msg["role"])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1227,14 +1257,53 @@ func TestApplyCodexOAuthTransform_ExtractsSystemMessages(t *testing.T) {
|
||||
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 1)
|
||||
msg, ok := input[0].(map[string]any)
|
||||
require.Len(t, input, 2)
|
||||
system, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", msg["role"])
|
||||
require.Equal(t, "developer", system["role"])
|
||||
require.Equal(t, "You are a coding assistant.", system["content"])
|
||||
user, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", user["role"])
|
||||
require.Equal(t, "You are a coding assistant.", reqBody["instructions"])
|
||||
}
|
||||
|
||||
func TestApplyCodexOAuthTransform_JsonObjectKeepsJsonInstructionInInput(t *testing.T) {
|
||||
reqBody := map[string]any{
|
||||
"model": "gpt-5.4",
|
||||
"input": []any{
|
||||
map[string]any{
|
||||
"role": "system",
|
||||
"content": "You are an assistant. Output JSON only.",
|
||||
},
|
||||
map[string]any{
|
||||
"role": "user",
|
||||
"content": "symbol data without the keyword",
|
||||
},
|
||||
},
|
||||
"text": map[string]any{
|
||||
"format": map[string]any{
|
||||
"type": "json_object",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := applyCodexOAuthTransform(reqBody, false, false)
|
||||
|
||||
require.True(t, result.Modified)
|
||||
instructions, ok := reqBody["instructions"].(string)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "You are a coding assistant.", instructions)
|
||||
require.Contains(t, instructions, "JSON")
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 2)
|
||||
developer, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "developer", developer["role"])
|
||||
require.Contains(t, developer["content"], "JSON")
|
||||
user, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "user", user["role"])
|
||||
}
|
||||
|
||||
func TestIsInstructionsEmpty(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user