Files
ds2api/internal/adapter/openai/responses_stream_runtime_toolcalls_finalize.go

157 lines
3.7 KiB
Go

package openai
import (
"encoding/json"
"sort"
"strings"
openaifmt "ds2api/internal/format/openai"
"ds2api/internal/util"
)
func (s *responsesStreamRuntime) closeIncompleteFunctionItems() {
if len(s.functionAdded) == 0 {
return
}
indices := make([]int, 0, len(s.functionAdded))
for idx, added := range s.functionAdded {
if !added || s.functionDone[idx] {
continue
}
indices = append(indices, idx)
}
if len(indices) == 0 {
return
}
sort.Ints(indices)
for _, idx := range indices {
name := strings.TrimSpace(s.functionNames[idx])
if name == "" {
continue
}
args := strings.TrimSpace(s.functionArgs[idx])
if args == "" {
args = "{}"
}
outputIndex := s.ensureFunctionOutputIndex(idx)
itemID := s.ensureFunctionItemID(idx)
callID := s.ensureToolCallID(idx)
s.sendEvent(
"response.function_call_arguments.done",
openaifmt.BuildResponsesFunctionCallArgumentsDonePayload(s.responseID, itemID, outputIndex, callID, name, args),
)
item := map[string]any{
"id": itemID,
"type": "function_call",
"call_id": callID,
"name": name,
"arguments": args,
"status": "completed",
}
s.sendEvent(
"response.output_item.done",
openaifmt.BuildResponsesOutputItemDonePayload(s.responseID, itemID, outputIndex, item),
)
s.functionDone[idx] = true
s.toolCallsDoneEmitted = true
}
}
func (s *responsesStreamRuntime) buildCompletedResponseObject(finalThinking, finalText string, calls []util.ParsedToolCall) map[string]any {
type indexedItem struct {
index int
item map[string]any
}
indexed := make([]indexedItem, 0, len(calls)+1)
if s.messageAdded {
text := s.visibleText.String()
indexed = append(indexed, indexedItem{
index: s.ensureMessageOutputIndex(),
item: map[string]any{
"id": s.ensureMessageItemID(),
"type": "message",
"role": "assistant",
"status": "completed",
"content": []map[string]any{
{
"type": "output_text",
"text": text,
},
},
},
})
} else if len(calls) == 0 {
content := make([]map[string]any, 0, 2)
if strings.TrimSpace(finalThinking) != "" {
content = append(content, map[string]any{
"type": "reasoning",
"text": finalThinking,
})
}
if strings.TrimSpace(finalText) != "" {
content = append(content, map[string]any{
"type": "output_text",
"text": finalText,
})
}
if len(content) > 0 {
indexed = append(indexed, indexedItem{
index: s.ensureMessageOutputIndex(),
item: map[string]any{
"id": s.ensureMessageItemID(),
"type": "message",
"role": "assistant",
"status": "completed",
"content": content,
},
})
}
}
for idx, tc := range calls {
if strings.TrimSpace(tc.Name) == "" {
continue
}
argsBytes, _ := json.Marshal(tc.Input)
indexed = append(indexed, indexedItem{
index: s.ensureFunctionOutputIndex(idx),
item: map[string]any{
"id": s.ensureFunctionItemID(idx),
"type": "function_call",
"call_id": s.ensureToolCallID(idx),
"name": tc.Name,
"arguments": string(argsBytes),
"status": "completed",
},
})
}
sort.SliceStable(indexed, func(i, j int) bool {
return indexed[i].index < indexed[j].index
})
output := make([]any, 0, len(indexed))
for _, it := range indexed {
output = append(output, it.item)
}
outputText := s.visibleText.String()
if strings.TrimSpace(outputText) == "" && len(calls) == 0 {
if strings.TrimSpace(finalText) != "" {
outputText = finalText
} else if strings.TrimSpace(finalThinking) != "" {
outputText = finalThinking
}
}
return openaifmt.BuildResponseObjectFromItems(
s.responseID,
s.model,
s.finalPrompt,
finalThinking,
finalText,
output,
outputText,
)
}