feat: support responses compaction payload compatibility for codex translator

This commit is contained in:
ciberponk
2026-02-21 12:56:10 +08:00
parent 7b0eb41ebc
commit d693d7993b
2 changed files with 78 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ package responses
import (
"fmt"
"strings"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
@@ -26,6 +27,8 @@ func ConvertOpenAIResponsesRequestToCodex(modelName string, inputRawJSON []byte,
rawJSON, _ = sjson.DeleteBytes(rawJSON, "temperature")
rawJSON, _ = sjson.DeleteBytes(rawJSON, "top_p")
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")
@@ -36,6 +39,43 @@ func ConvertOpenAIResponsesRequestToCodex(modelName string, inputRawJSON []byte,
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.
// 2) Remove truncation as Codex upstream currently rejects it as unsupported.
func applyResponsesCompactionCompatibility(rawJSON []byte) []byte {
contextManagement := gjson.GetBytes(rawJSON, "context_management")
if !contextManagement.Exists() {
return rawJSON
}
hasCompactionRule := false
switch {
case contextManagement.IsArray():
for _, item := range contextManagement.Array() {
if strings.EqualFold(item.Get("type").String(), "compaction") {
hasCompactionRule = true
break
}
}
case contextManagement.IsObject():
hasCompactionRule = strings.EqualFold(contextManagement.Get("type").String(), "compaction")
}
if hasCompactionRule {
// no-op marker: compaction hint detected and consumed for compatibility.
}
rawJSON, _ = sjson.DeleteBytes(rawJSON, "context_management")
rawJSON, _ = sjson.DeleteBytes(rawJSON, "truncation")
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.

View File

@@ -280,3 +280,41 @@ func TestUserFieldDeletion(t *testing.T) {
t.Errorf("user field should be deleted, but it was found with value: %s", userField.Raw)
}
}
func TestContextManagementCompactionCompatibility(t *testing.T) {
inputJSON := []byte(`{
"model": "gpt-5.2",
"context_management": [
{
"type": "compaction",
"compact_threshold": 12000
}
],
"input": [{"role":"user","content":"hello"}]
}`)
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
outputStr := string(output)
if gjson.Get(outputStr, "context_management").Exists() {
t.Fatalf("context_management should be removed for Codex compatibility")
}
if gjson.Get(outputStr, "truncation").Exists() {
t.Fatalf("truncation should be removed for Codex compatibility")
}
}
func TestTruncationRemovedForCodexCompatibility(t *testing.T) {
inputJSON := []byte(`{
"model": "gpt-5.2",
"truncation": "disabled",
"input": [{"role":"user","content":"hello"}]
}`)
output := ConvertOpenAIResponsesRequestToCodex("gpt-5.2", inputJSON, false)
outputStr := string(output)
if gjson.Get(outputStr, "truncation").Exists() {
t.Fatalf("truncation should be removed for Codex compatibility")
}
}