mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-20 13:07:00 +08:00
fix: keep tool call replay when assistant text interleaves call and result
trimReplayDanglingAssistantToolCalls only collected tool results that immediately followed the assistant tool-call message. Models such as gpt-5.3-codex-spark may emit the function_call item before the explanation text within one response, so history replay order becomes assistant[tool_call] -> assistant[text] -> tool[result]. The call was misjudged as dangling and stripped while the tool result survived, producing a function_call_output without a matching function_call that the Responses API rejects with 400. - widen the response collection window to skip interleaved plain assistant text messages, and drop orphan tool results in the same pass - synthesize a placeholder function_call (or drop the output when the tool name is unknown) in normalizeOpenAIResponsesInput so conversations already persisted with corrupted history can resume
This commit is contained in:
@@ -1956,6 +1956,7 @@ func normalizeOpenAIResponsesInput(messages []Message) (string, []map[string]any
|
||||
instructionParts := make([]string, 0, 2)
|
||||
items := make([]map[string]any, 0, len(messages))
|
||||
responsesCallIDs := make(map[string]string)
|
||||
emittedCallIDs := make(map[string]struct{})
|
||||
activeAssistantReasoningKey := ""
|
||||
for _, message := range messages {
|
||||
role := strings.TrimSpace(message.Role)
|
||||
@@ -1968,6 +1969,26 @@ func normalizeOpenAIResponsesInput(messages []Message) (string, []map[string]any
|
||||
}
|
||||
if role == "tool" && strings.TrimSpace(message.ToolCallID) != "" {
|
||||
callID := openAIResponsesToolMessageCallID(message, responsesCallIDs)
|
||||
if strings.TrimSpace(callID) != "" {
|
||||
if _, ok := emittedCallIDs[callID]; !ok {
|
||||
// 历史损坏时可能出现没有配对 function_call 的工具结果
|
||||
// (例如旧版本回放逻辑剥离了 assistant 调用但保留了结果)。
|
||||
// Responses API 会直接拒绝这种 input,这里补一个占位 function_call
|
||||
// 让旧会话可以继续;无法补齐时丢弃该结果。
|
||||
if name := strings.TrimSpace(message.Name); name != "" {
|
||||
items = append(items, map[string]any{
|
||||
"type": "function_call",
|
||||
"call_id": callID,
|
||||
"name": name,
|
||||
"arguments": "{}",
|
||||
"status": "completed",
|
||||
})
|
||||
emittedCallIDs[callID] = struct{}{}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
var output any = openAIResponsesMessageText(message)
|
||||
if hasImageContentParts(message.ContentParts) {
|
||||
content, err := openAIResponsesMessageContent(message, false)
|
||||
@@ -2034,6 +2055,9 @@ func normalizeOpenAIResponsesInput(messages []Message) (string, []map[string]any
|
||||
toolItem["status"] = "completed"
|
||||
}
|
||||
items = append(items, toolItem)
|
||||
if strings.TrimSpace(callID) != "" {
|
||||
emittedCallIDs[strings.TrimSpace(callID)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user