135 lines
4.5 KiB
Go
135 lines
4.5 KiB
Go
package responses
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
func ConvertOpenAIResponsesRequestToCodex(modelName string, inputRawJSON []byte, _ bool) []byte {
|
|
rawJSON := inputRawJSON
|
|
|
|
inputResult := gjson.GetBytes(rawJSON, "input")
|
|
if inputResult.Type == gjson.String {
|
|
input, _ := sjson.Set(`[{"type":"message","role":"user","content":[{"type":"input_text","text":""}]}]`, "0.content.0.text", inputResult.String())
|
|
rawJSON, _ = sjson.SetRawBytes(rawJSON, "input", []byte(input))
|
|
}
|
|
|
|
rawJSON, _ = sjson.SetBytes(rawJSON, "stream", true)
|
|
rawJSON, _ = sjson.SetBytes(rawJSON, "store", false)
|
|
rawJSON, _ = sjson.SetBytes(rawJSON, "parallel_tool_calls", true)
|
|
rawJSON, _ = sjson.SetBytes(rawJSON, "include", []string{"reasoning.encrypted_content"})
|
|
// Codex Responses rejects token limit fields, so strip them out before forwarding.
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "max_output_tokens")
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "max_completion_tokens")
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "temperature")
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "top_p")
|
|
if v := gjson.GetBytes(rawJSON, "service_tier"); v.Exists() {
|
|
if v.String() != "priority" {
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "service_tier")
|
|
}
|
|
}
|
|
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "truncation")
|
|
rawJSON = applyResponsesCompactionCompatibility(rawJSON)
|
|
|
|
// Delete the user field as it is not supported by the Codex upstream.
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "user")
|
|
|
|
// Convert role "system" to "developer" in input array to comply with Codex API requirements.
|
|
rawJSON = convertSystemRoleToDeveloper(rawJSON)
|
|
rawJSON = normalizeCodexBuiltinTools(rawJSON)
|
|
|
|
return rawJSON
|
|
}
|
|
|
|
// applyResponsesCompactionCompatibility handles OpenAI Responses context_management.compaction
|
|
// for Codex upstream compatibility.
|
|
//
|
|
// Codex /responses currently rejects context_management with:
|
|
// {"detail":"Unsupported parameter: context_management"}.
|
|
//
|
|
// Compatibility strategy:
|
|
// 1) Remove context_management before forwarding to Codex upstream.
|
|
func applyResponsesCompactionCompatibility(rawJSON []byte) []byte {
|
|
if !gjson.GetBytes(rawJSON, "context_management").Exists() {
|
|
return rawJSON
|
|
}
|
|
|
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "context_management")
|
|
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
|
|
}
|
|
|
|
// normalizeCodexBuiltinTools rewrites legacy/preview built-in tool variants to the
|
|
// stable names expected by the current Codex upstream.
|
|
func normalizeCodexBuiltinTools(rawJSON []byte) []byte {
|
|
result := rawJSON
|
|
|
|
tools := gjson.GetBytes(result, "tools")
|
|
if tools.IsArray() {
|
|
toolArray := tools.Array()
|
|
for i := 0; i < len(toolArray); i++ {
|
|
typePath := fmt.Sprintf("tools.%d.type", i)
|
|
if normalized := normalizeCodexBuiltinToolType(gjson.GetBytes(result, typePath).String()); normalized != "" {
|
|
if updated, err := sjson.SetBytes(result, typePath, normalized); err == nil {
|
|
result = updated
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if normalized := normalizeCodexBuiltinToolType(gjson.GetBytes(result, "tool_choice.type").String()); normalized != "" {
|
|
if updated, err := sjson.SetBytes(result, "tool_choice.type", normalized); err == nil {
|
|
result = updated
|
|
}
|
|
}
|
|
|
|
toolChoiceTools := gjson.GetBytes(result, "tool_choice.tools")
|
|
if toolChoiceTools.IsArray() {
|
|
toolArray := toolChoiceTools.Array()
|
|
for i := 0; i < len(toolArray); i++ {
|
|
typePath := fmt.Sprintf("tool_choice.tools.%d.type", i)
|
|
if normalized := normalizeCodexBuiltinToolType(gjson.GetBytes(result, typePath).String()); normalized != "" {
|
|
if updated, err := sjson.SetBytes(result, typePath, normalized); err == nil {
|
|
result = updated
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func normalizeCodexBuiltinToolType(toolType string) string {
|
|
switch toolType {
|
|
case "web_search_preview", "web_search_preview_2025_03_11":
|
|
return "web_search"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|