mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
fix(codex): convert system role to developer for codex input
This commit is contained in:
@@ -2,7 +2,9 @@ package responses
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,5 +22,31 @@ func ConvertOpenAIResponsesRequestToCodex(modelName string, inputRawJSON []byte,
|
|||||||
rawJSON, _ = sjson.DeleteBytes(rawJSON, "top_p")
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "top_p")
|
||||||
rawJSON, _ = sjson.DeleteBytes(rawJSON, "service_tier")
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "service_tier")
|
||||||
|
|
||||||
|
// Convert role "system" to "developer" in input array to comply with Codex API requirements.
|
||||||
|
rawJSON = convertSystemRoleToDeveloper(rawJSON)
|
||||||
|
|
||||||
return rawJSON
|
return rawJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertSystemRoleToDeveloper traverses the input array and converts any message items
|
||||||
|
// with role "system" to role "developer". This is necessary because Codex API does not
|
||||||
|
// accept "system" role in the input array.
|
||||||
|
func convertSystemRoleToDeveloper(rawJSON []byte) []byte {
|
||||||
|
inputResult := gjson.GetBytes(rawJSON, "input")
|
||||||
|
if !inputResult.IsArray() {
|
||||||
|
return rawJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
inputArray := inputResult.Array()
|
||||||
|
result := rawJSON
|
||||||
|
|
||||||
|
// Directly modify role values for items with "system" role
|
||||||
|
for i := 0; i < len(inputArray); i++ {
|
||||||
|
rolePath := fmt.Sprintf("input.%d.role", i)
|
||||||
|
if gjson.GetBytes(result, rolePath).String() == "system" {
|
||||||
|
result, _ = sjson.SetBytes(result, rolePath, "developer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConvertSystemRoleToDeveloper_BasicConversion tests the basic system -> developer role conversion
|
||||||
|
func TestConvertSystemRoleToDeveloper_BasicConversion(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "system",
|
||||||
|
"content": [{"type": "input_text", "text": "You are a pirate."}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "input_text", "text": "Say hello."}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Check that system role was converted to developer
|
||||||
|
firstItemRole := gjson.Get(outputStr, "input.0.role")
|
||||||
|
if firstItemRole.String() != "developer" {
|
||||||
|
t.Errorf("Expected role 'developer', got '%s'", firstItemRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that user role remains unchanged
|
||||||
|
secondItemRole := gjson.Get(outputStr, "input.1.role")
|
||||||
|
if secondItemRole.String() != "user" {
|
||||||
|
t.Errorf("Expected role 'user', got '%s'", secondItemRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check content is preserved
|
||||||
|
firstItemContent := gjson.Get(outputStr, "input.0.content.0.text")
|
||||||
|
if firstItemContent.String() != "You are a pirate." {
|
||||||
|
t.Errorf("Expected content 'You are a pirate.', got '%s'", firstItemContent.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertSystemRoleToDeveloper_MultipleSystemMessages tests conversion with multiple system messages
|
||||||
|
func TestConvertSystemRoleToDeveloper_MultipleSystemMessages(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "system",
|
||||||
|
"content": [{"type": "input_text", "text": "You are helpful."}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "system",
|
||||||
|
"content": [{"type": "input_text", "text": "Be concise."}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "input_text", "text": "Hello"}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Check that both system roles were converted
|
||||||
|
firstRole := gjson.Get(outputStr, "input.0.role")
|
||||||
|
if firstRole.String() != "developer" {
|
||||||
|
t.Errorf("Expected first role 'developer', got '%s'", firstRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
secondRole := gjson.Get(outputStr, "input.1.role")
|
||||||
|
if secondRole.String() != "developer" {
|
||||||
|
t.Errorf("Expected second role 'developer', got '%s'", secondRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that user role is unchanged
|
||||||
|
thirdRole := gjson.Get(outputStr, "input.2.role")
|
||||||
|
if thirdRole.String() != "user" {
|
||||||
|
t.Errorf("Expected third role 'user', got '%s'", thirdRole.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertSystemRoleToDeveloper_NoSystemMessages tests that requests without system messages are unchanged
|
||||||
|
func TestConvertSystemRoleToDeveloper_NoSystemMessages(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "input_text", "text": "Hello"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "output_text", "text": "Hi there!"}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Check that user and assistant roles are unchanged
|
||||||
|
firstRole := gjson.Get(outputStr, "input.0.role")
|
||||||
|
if firstRole.String() != "user" {
|
||||||
|
t.Errorf("Expected role 'user', got '%s'", firstRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
secondRole := gjson.Get(outputStr, "input.1.role")
|
||||||
|
if secondRole.String() != "assistant" {
|
||||||
|
t.Errorf("Expected role 'assistant', got '%s'", secondRole.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertSystemRoleToDeveloper_EmptyInput tests that empty input arrays are handled correctly
|
||||||
|
func TestConvertSystemRoleToDeveloper_EmptyInput(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"input": []
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Check that input is still an empty array
|
||||||
|
inputArray := gjson.Get(outputStr, "input")
|
||||||
|
if !inputArray.IsArray() {
|
||||||
|
t.Error("Input should still be an array")
|
||||||
|
}
|
||||||
|
if len(inputArray.Array()) != 0 {
|
||||||
|
t.Errorf("Expected empty array, got %d items", len(inputArray.Array()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertSystemRoleToDeveloper_NoInputField tests that requests without input field are unchanged
|
||||||
|
func TestConvertSystemRoleToDeveloper_NoInputField(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"stream": false
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Check that other fields are still set correctly
|
||||||
|
stream := gjson.Get(outputStr, "stream")
|
||||||
|
if !stream.Bool() {
|
||||||
|
t.Error("Stream should be set to true by conversion")
|
||||||
|
}
|
||||||
|
|
||||||
|
store := gjson.Get(outputStr, "store")
|
||||||
|
if store.Bool() {
|
||||||
|
t.Error("Store should be set to false by conversion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertOpenAIResponsesRequestToCodex_OriginalIssue tests the exact issue reported by the user
|
||||||
|
func TestConvertOpenAIResponsesRequestToCodex_OriginalIssue(t *testing.T) {
|
||||||
|
// This is the exact input that was failing with "System messages are not allowed"
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a pirate. Always respond in pirate speak."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "user",
|
||||||
|
"content": "Say hello."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stream": false
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Verify system role was converted to developer
|
||||||
|
firstRole := gjson.Get(outputStr, "input.0.role")
|
||||||
|
if firstRole.String() != "developer" {
|
||||||
|
t.Errorf("Expected role 'developer', got '%s'", firstRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify stream was set to true (as required by Codex)
|
||||||
|
stream := gjson.Get(outputStr, "stream")
|
||||||
|
if !stream.Bool() {
|
||||||
|
t.Error("Stream should be set to true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify other required fields for Codex
|
||||||
|
store := gjson.Get(outputStr, "store")
|
||||||
|
if store.Bool() {
|
||||||
|
t.Error("Store should be false")
|
||||||
|
}
|
||||||
|
|
||||||
|
parallelCalls := gjson.Get(outputStr, "parallel_tool_calls")
|
||||||
|
if !parallelCalls.Bool() {
|
||||||
|
t.Error("parallel_tool_calls should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
include := gjson.Get(outputStr, "include")
|
||||||
|
if !include.IsArray() || len(include.Array()) != 1 {
|
||||||
|
t.Error("include should be an array with one element")
|
||||||
|
} else if include.Array()[0].String() != "reasoning.encrypted_content" {
|
||||||
|
t.Errorf("Expected include[0] to be 'reasoning.encrypted_content', got '%s'", include.Array()[0].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertSystemRoleToDeveloper_AssistantRole tests that assistant role is preserved
|
||||||
|
func TestConvertSystemRoleToDeveloper_AssistantRole(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gpt-5.2",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "system",
|
||||||
|
"content": [{"type": "input_text", "text": "You are helpful."}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "input_text", "text": "Hello"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "output_text", "text": "Hi!"}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
// Check system -> developer
|
||||||
|
firstRole := gjson.Get(outputStr, "input.0.role")
|
||||||
|
if firstRole.String() != "developer" {
|
||||||
|
t.Errorf("Expected first role 'developer', got '%s'", firstRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user unchanged
|
||||||
|
secondRole := gjson.Get(outputStr, "input.1.role")
|
||||||
|
if secondRole.String() != "user" {
|
||||||
|
t.Errorf("Expected second role 'user', got '%s'", secondRole.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check assistant unchanged
|
||||||
|
thirdRole := gjson.Get(outputStr, "input.2.role")
|
||||||
|
if thirdRole.String() != "assistant" {
|
||||||
|
t.Errorf("Expected third role 'assistant', got '%s'", thirdRole.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user