Merge pull request #4612 from catoncat/fix/codex-call-id-length

fix(openai): 限制 Codex call_id 归一化后的长度
This commit is contained in:
Wesley Liddick
2026-07-20 11:05:18 +08:00
committed by GitHub
2 changed files with 95 additions and 7 deletions
@@ -1,6 +1,8 @@
package service
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
@@ -84,6 +86,34 @@ type codexOAuthTransformOptions struct {
OmitPromotedSystemMessagesFromInput bool
}
const (
codexCallIDMaxLength = 64
codexCallIDPrefix = "fc_"
)
func normalizeCodexCallID(id string) string {
candidate := id
switch {
case id == "":
return ""
case strings.HasPrefix(id, "fc"):
case strings.HasPrefix(id, "call_"):
candidate = codexCallIDPrefix + strings.TrimPrefix(id, "call_")
default:
candidate = codexCallIDPrefix + id
}
if len(candidate) <= codexCallIDMaxLength {
return candidate
}
return compactCodexCallID(candidate)
}
func compactCodexCallID(id string) string {
digest := sha256.Sum256([]byte("sub2api:codex-call-id:v1:" + id))
encoded := hex.EncodeToString(digest[:])
return codexCallIDPrefix + encoded[:codexCallIDMaxLength-len(codexCallIDPrefix)]
}
const codexImageGenerationFunctionToolName = "image_gen.imagegen"
const (
@@ -1381,13 +1411,7 @@ func filterCodexInputWithOptions(input []any, opts codexInputFilterOptions) []an
if opts.PreserveCallIDs {
return id
}
if id == "" || strings.HasPrefix(id, "fc") {
return id
}
if strings.HasPrefix(id, "call_") {
return "fc_" + strings.TrimPrefix(id, "call_")
}
return "fc_" + id
return normalizeCodexCallID(id)
}
if typ == "item_reference" {
@@ -127,6 +127,70 @@ func TestApplyCodexOAuthTransform_ToolContinuationNormalizesToolReferenceIDsOnly
require.Equal(t, "fc_1", second["call_id"])
}
func TestApplyCodexOAuthTransform_BoundsLongCallIDsAndPreservesPairing(t *testing.T) {
suffix := strings.Repeat("z", 62)
for _, tc := range []struct {
name string
callID string
outputCallID string
}{
{name: "non-native boundary id", callID: "call-" + strings.Repeat("x", 59), outputCallID: "call-" + strings.Repeat("x", 59)},
{name: "overlong fc id", callID: "fc_" + strings.Repeat("y", 62), outputCallID: "fc_" + strings.Repeat("y", 62)},
{name: "equivalent prefixes", callID: "call_" + suffix, outputCallID: "fc_" + suffix},
} {
t.Run(tc.name, func(t *testing.T) {
reqBody := map[string]any{
"model": "gpt-5.2",
"input": []any{
map[string]any{"type": "function_call", "call_id": tc.callID, "name": "shell"},
map[string]any{"type": "function_call_output", "call_id": tc.outputCallID, "output": "done"},
},
}
applyCodexOAuthTransform(reqBody, false, false)
input, ok := reqBody["input"].([]any)
require.True(t, ok)
call, ok := input[0].(map[string]any)
require.True(t, ok)
output, ok := input[1].(map[string]any)
require.True(t, ok)
fixedCallID, ok := call["call_id"].(string)
require.True(t, ok)
require.LessOrEqual(t, len(fixedCallID), codexCallIDMaxLength)
require.True(t, strings.HasPrefix(fixedCallID, codexCallIDPrefix))
require.Equal(t, fixedCallID, output["call_id"])
require.Equal(t, fixedCallID, normalizeCodexCallID(tc.callID))
require.Equal(t, fixedCallID, normalizeCodexCallID(tc.outputCallID))
})
}
}
func TestApplyCodexOAuthTransform_PreservesLongCallIDsWhenRequested(t *testing.T) {
callID := "call-" + strings.Repeat("x", 70)
reqBody := map[string]any{
"model": "gpt-5.2",
"input": []any{
map[string]any{"type": "function_call", "call_id": callID, "name": "shell"},
map[string]any{"type": "function_call_output", "call_id": callID, "output": "done"},
},
}
applyCodexOAuthTransformWithOptions(reqBody, codexOAuthTransformOptions{
PreserveToolCallIDs: true,
})
input, ok := reqBody["input"].([]any)
require.True(t, ok)
call, ok := input[0].(map[string]any)
require.True(t, ok)
output, ok := input[1].(map[string]any)
require.True(t, ok)
require.Equal(t, callID, call["call_id"])
require.Equal(t, callID, output["call_id"])
}
func TestApplyCodexOAuthTransform_ToolSearchOutputPreservesCallID(t *testing.T) {
reqBody := map[string]any{
"model": "gpt-5.2",