package openai import ( "encoding/json" "strings" "time" "github.com/google/uuid" "ds2api/internal/util" ) func BuildResponseObject(responseID, model, finalPrompt, finalThinking, finalText string, toolNames []string) map[string]any { // Align responses tool-call semantics with chat/completions: // mixed prose + tool_call payloads should still be interpreted as tool calls. detected := util.ParseToolCalls(finalText, toolNames) if len(detected) == 0 && strings.TrimSpace(finalThinking) != "" { detected = util.ParseToolCalls(finalThinking, toolNames) } exposedOutputText := finalText output := make([]any, 0, 2) if len(detected) > 0 { exposedOutputText = "" if strings.TrimSpace(finalThinking) != "" { output = append(output, map[string]any{ "type": "reasoning", "text": finalThinking, }) } formatted := util.FormatOpenAIToolCalls(detected) output = append(output, toResponsesFunctionCallItems(formatted)...) output = append(output, map[string]any{ "type": "tool_calls", "tool_calls": formatted, }) } else { content := make([]any, 0, 2) if finalThinking != "" { content = append([]any{map[string]any{ "type": "reasoning", "text": finalThinking, }}, content...) } if strings.TrimSpace(finalText) != "" { content = append(content, map[string]any{ "type": "output_text", "text": finalText, }) } if strings.TrimSpace(finalText) == "" && strings.TrimSpace(finalThinking) != "" { exposedOutputText = finalThinking } output = append(output, map[string]any{ "type": "message", "id": "msg_" + strings.ReplaceAll(uuid.NewString(), "-", ""), "role": "assistant", "content": content, }) } return map[string]any{ "id": responseID, "type": "response", "object": "response", "created_at": time.Now().Unix(), "status": "completed", "model": model, "output": output, "output_text": exposedOutputText, "usage": BuildResponsesUsage(finalPrompt, finalThinking, finalText), } } func toResponsesFunctionCallItems(toolCalls []map[string]any) []any { if len(toolCalls) == 0 { return nil } out := make([]any, 0, len(toolCalls)) for _, tc := range toolCalls { callID, _ := tc["id"].(string) if strings.TrimSpace(callID) == "" { callID = "call_" + strings.ReplaceAll(uuid.NewString(), "-", "") } name := "" args := "{}" if fn, ok := tc["function"].(map[string]any); ok { if n, _ := fn["name"].(string); strings.TrimSpace(n) != "" { name = n } if a, _ := fn["arguments"].(string); strings.TrimSpace(a) != "" { args = a } } out = append(out, map[string]any{ "id": "fc_" + strings.ReplaceAll(uuid.NewString(), "-", ""), "type": "function_call", "call_id": callID, "name": name, "arguments": normalizeJSONString(args), "status": "completed", }) } return out } func normalizeJSONString(raw string) string { s := strings.TrimSpace(raw) if s == "" { return "{}" } var v any if err := json.Unmarshal([]byte(s), &v); err != nil { return raw } b, err := json.Marshal(v) if err != nil { return raw } return string(b) }