Merge pull request #4649 from superman2003/fix/preserve-call-id-length-20260720

fix(openai): preserve 路径 call_id 超长时压缩至 64 字符上限
This commit is contained in:
Wesley Liddick
2026-07-21 10:40:37 +08:00
committed by GitHub
2 changed files with 58 additions and 5 deletions
@@ -1409,7 +1409,15 @@ func filterCodexInputWithOptions(input []any, opts codexInputFilterOptions) []an
// 若 item_reference 指向 legacy call_* 标识,则仅修正该引用本身。
fixCallIDPrefix := func(id string) string {
if opts.PreserveCallIDs {
return id
// preserve 模式尽量原样透传客户端 id 以维持 tool_use/tool_result
// 配对,但上游对 call_id 有 64 字符硬上限,超长原样透传必然被
// 400 拒绝("Invalid 'input[N].call_id': string too long")。
// 超长时退回确定性压缩:同一逻辑 id 在 function_call 与
// function_call_output 两侧结果一致,配对不受影响。
if len(id) <= codexCallIDMaxLength {
return id
}
return compactCodexCallID(id)
}
return normalizeCodexCallID(id)
}
@@ -167,8 +167,48 @@ func TestApplyCodexOAuthTransform_BoundsLongCallIDsAndPreservesPairing(t *testin
}
}
func TestApplyCodexOAuthTransform_PreservesLongCallIDsWhenRequested(t *testing.T) {
callID := "call-" + strings.Repeat("x", 70)
func TestApplyCodexOAuthTransform_PreservesCallIDsWithinLimitWhenRequested(t *testing.T) {
// preserve 模式下 ≤64 字符的 id 必须原样透传(含 64 字符等长边界),
// 不做任何前缀改写或压缩。
for _, tc := range []struct {
name string
callID string
}{
{name: "anthropic toolu id", callID: "toolu_01ABCdefGHIjklMNOpqrsTUV"},
{name: "boundary 64 chars", callID: "toolu_" + strings.Repeat("x", codexCallIDMaxLength-len("toolu_"))},
} {
t.Run(tc.name, func(t *testing.T) {
require.LessOrEqual(t, len(tc.callID), codexCallIDMaxLength)
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.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, tc.callID, call["call_id"])
require.Equal(t, tc.callID, output["call_id"])
})
}
}
func TestApplyCodexOAuthTransform_CompactsOverlongCallIDsWhenPreserveRequested(t *testing.T) {
// preserve 模式下超过 64 字符的 id 若原样透传,上游必然 400
// ("Invalid 'input[N].call_id': string too long"),需退回确定性压缩;
// function_call 与 function_call_output 两侧压缩结果一致,配对保持。
callID := "srvtoolu_" + strings.Repeat("x", 69) // 78 字符,对应生产环境真实报错长度
require.Len(t, callID, 78)
reqBody := map[string]any{
"model": "gpt-5.2",
"input": []any{
@@ -187,8 +227,13 @@ func TestApplyCodexOAuthTransform_PreservesLongCallIDsWhenRequested(t *testing.T
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"])
compacted, ok := call["call_id"].(string)
require.True(t, ok)
require.Len(t, compacted, codexCallIDMaxLength)
require.True(t, strings.HasPrefix(compacted, codexCallIDPrefix))
require.Equal(t, compacted, output["call_id"], "两侧压缩结果必须一致以保持配对")
require.Equal(t, compactCodexCallID(callID), compacted, "压缩必须是确定性的")
}
func TestApplyCodexOAuthTransform_ToolSearchOutputPreservesCallID(t *testing.T) {