@@ -183,6 +183,11 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
183183 body = normalizeCodexInstructions (body )
184184 body = ensureImageGenerationTool (body , baseModel , auth )
185185
186+ // 防御性去重:翻译链中可能因多 Key / 多层处理导致 input 数组里
187+ // 同一个 call_id 的 function_call_output 或 tool_search_output 被重复写入。
188+ // 在所有请求体变换完成后做一次最终去重,避免模型收到重复工具结果。
189+ body = dedupeToolOutputs (body )
190+
186191 url := strings .TrimSuffix (baseURL , "/" ) + "/responses"
187192 httpReq , err := e .cacheHelper (ctx , from , url , req , body )
188193 if err != nil {
@@ -426,6 +431,9 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
426431 body = normalizeCodexInstructions (body )
427432 body = ensureImageGenerationTool (body , baseModel , auth )
428433
434+ // 防御性去重:同 Execute 方法,防止 input 中工具输出重复。
435+ body = dedupeToolOutputs (body )
436+
429437 url := strings .TrimSuffix (baseURL , "/" ) + "/responses"
430438 httpReq , err := e .cacheHelper (ctx , from , url , req , body )
431439 if err != nil {
@@ -1040,3 +1048,59 @@ func codexConfigLookupAttrs(auth *cliproxyauth.Auth) (apiKey, baseURL string) {
10401048 }
10411049 return strings .TrimSpace (auth .Attributes ["api_key" ]), strings .TrimSpace (auth .Attributes ["base_url" ])
10421050}
1051+
1052+ // dedupeToolOutputs 移除 input 数组中 call_id 重复的 function_call_output
1053+ // 和 tool_search_output 项(保留首次出现),防止上游翻译链或重试逻辑
1054+ // 导致模型收到重复工具结果。
1055+ func dedupeToolOutputs (body []byte ) []byte {
1056+ inputItems := gjson .GetBytes (body , "input" )
1057+ if ! inputItems .IsArray () {
1058+ return body
1059+ }
1060+
1061+ arr := inputItems .Array ()
1062+ seenCallIDs := make (map [string ]struct {}, len (arr ))
1063+ dupes := make (map [int ]bool )
1064+
1065+ for i , item := range arr {
1066+ typ := item .Get ("type" ).String ()
1067+ if typ != "function_call_output" && typ != "tool_search_output" {
1068+ continue
1069+ }
1070+ callID := strings .TrimSpace (item .Get ("call_id" ).String ())
1071+ if callID == "" {
1072+ continue
1073+ }
1074+ if _ , exists := seenCallIDs [callID ]; exists {
1075+ dupes [i ] = true
1076+ continue
1077+ }
1078+ seenCallIDs [callID ] = struct {}{}
1079+ }
1080+
1081+ if len (dupes ) == 0 {
1082+ return body
1083+ }
1084+
1085+ // 重建 input 数组,跳过标记为重复的索引
1086+ filtered := make ([]byte , 0 , len (inputItems .Raw ))
1087+ filtered = append (filtered , '[' )
1088+ first := true
1089+ for i , item := range arr {
1090+ if dupes [i ] {
1091+ continue
1092+ }
1093+ if ! first {
1094+ filtered = append (filtered , ',' )
1095+ }
1096+ filtered = append (filtered , []byte (item .Raw )... )
1097+ first = false
1098+ }
1099+ filtered = append (filtered , ']' )
1100+
1101+ out , err := sjson .SetRawBytes (body , "input" , filtered )
1102+ if err != nil {
1103+ return body
1104+ }
1105+ return out
1106+ }
0 commit comments