package responses import ( "ds2api/internal/toolcall" "encoding/json" "sort" "strings" openaifmt "ds2api/internal/format/openai" ) 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 []toolcall.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 finalThinking != "" { content = append(content, map[string]any{ "type": "reasoning", "text": finalThinking, }) } if 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, }, }) } } normalizedCalls := toolcall.NormalizeParsedToolCallsForSchemas(calls, s.toolsRaw) for idx, tc := range normalizedCalls { 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 outputText == "" && len(calls) == 0 { if finalText != "" { outputText = finalText } else if finalThinking != "" { outputText = finalThinking } } obj := openaifmt.BuildResponseObjectFromItems( s.responseID, s.model, s.finalPrompt, finalThinking, finalText, output, outputText, ) if s.refFileTokens > 0 { addRefFileTokensToUsage(obj, s.refFileTokens) } return obj }