package responses import ( "strings" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) // ConvertOpenAIResponsesRequestToOpenAIChatCompletions converts OpenAI responses format to OpenAI chat completions format. // It transforms the OpenAI responses API format (with instructions and input array) into the standard // OpenAI chat completions format (with messages array and system content). // // The conversion handles: // 1. Model name and streaming configuration // 2. Instructions to system message conversion // 3. Input array to messages array transformation // 4. Tool definitions and tool choice conversion // 5. Function calls and function results handling // 6. Generation parameters mapping (max_tokens, reasoning, etc.) // // Parameters: // - modelName: The name of the model to use for the request // - rawJSON: The raw JSON request data in OpenAI responses format // - stream: A boolean indicating if the request is for a streaming response // // Returns: // - []byte: The transformed request data in OpenAI chat completions format func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, inputRawJSON []byte, stream bool) []byte { rawJSON := inputRawJSON // Base OpenAI chat completions template with default values out := `{"model":"","messages":[],"stream":false}` root := gjson.ParseBytes(rawJSON) // Set model name out, _ = sjson.Set(out, "model", modelName) // Set stream configuration out, _ = sjson.Set(out, "stream", stream) // Map generation parameters from responses format to chat completions format if maxTokens := root.Get("max_output_tokens"); maxTokens.Exists() { out, _ = sjson.Set(out, "max_tokens", maxTokens.Int()) } if parallelToolCalls := root.Get("parallel_tool_calls"); parallelToolCalls.Exists() { out, _ = sjson.Set(out, "parallel_tool_calls", parallelToolCalls.Bool()) } // Convert instructions to system message if instructions := root.Get("instructions"); instructions.Exists() { systemMessage := `{"role":"system","content":""}` systemMessage, _ = sjson.Set(systemMessage, "content", instructions.String()) out, _ = sjson.SetRaw(out, "messages.-1", systemMessage) } // Convert input array to messages if input := root.Get("input"); input.Exists() && input.IsArray() { input.ForEach(func(_, item gjson.Result) bool { itemType := item.Get("type").String() if itemType == "" && item.Get("role").String() != "" { itemType = "message" } switch itemType { case "message", "": // Handle regular message conversion role := item.Get("role").String() if role == "developer" { role = "user" } message := `{"role":"","content":""}` message, _ = sjson.Set(message, "role", role) if content := item.Get("content"); content.Exists() && content.IsArray() { var messageContent string var toolCalls []interface{} content.ForEach(func(_, contentItem gjson.Result) bool { contentType := contentItem.Get("type").String() if contentType == "" { contentType = "input_text" } switch contentType { case "input_text": text := contentItem.Get("text").String() if messageContent != "" { messageContent += "\n" + text } else { messageContent = text } case "output_text": text := contentItem.Get("text").String() if messageContent != "" { messageContent += "\n" + text } else { messageContent = text } } return true }) if messageContent != "" { message, _ = sjson.Set(message, "content", messageContent) } if len(toolCalls) > 0 { message, _ = sjson.Set(message, "tool_calls", toolCalls) } } else if content.Type == gjson.String { message, _ = sjson.Set(message, "content", content.String()) } out, _ = sjson.SetRaw(out, "messages.-1", message) case "function_call": // Handle function call conversion to assistant message with tool_calls assistantMessage := `{"role":"assistant","tool_calls":[]}` toolCall := `{"id":"","type":"function","function":{"name":"","arguments":""}}` if callId := item.Get("call_id"); callId.Exists() { toolCall, _ = sjson.Set(toolCall, "id", callId.String()) } if name := item.Get("name"); name.Exists() { toolCall, _ = sjson.Set(toolCall, "function.name", name.String()) } if arguments := item.Get("arguments"); arguments.Exists() { toolCall, _ = sjson.Set(toolCall, "function.arguments", arguments.String()) } assistantMessage, _ = sjson.SetRaw(assistantMessage, "tool_calls.0", toolCall) out, _ = sjson.SetRaw(out, "messages.-1", assistantMessage) case "function_call_output": // Handle function call output conversion to tool message toolMessage := `{"role":"tool","tool_call_id":"","content":""}` if callId := item.Get("call_id"); callId.Exists() { toolMessage, _ = sjson.Set(toolMessage, "tool_call_id", callId.String()) } if output := item.Get("output"); output.Exists() { toolMessage, _ = sjson.Set(toolMessage, "content", output.String()) } out, _ = sjson.SetRaw(out, "messages.-1", toolMessage) } return true }) } else if input.Type == gjson.String { msg := "{}" msg, _ = sjson.Set(msg, "role", "user") msg, _ = sjson.Set(msg, "content", input.String()) out, _ = sjson.SetRaw(out, "messages.-1", msg) } // Convert tools from responses format to chat completions format if tools := root.Get("tools"); tools.Exists() && tools.IsArray() { var chatCompletionsTools []interface{} tools.ForEach(func(_, tool gjson.Result) bool { // Built-in tools (e.g. {"type":"web_search"}) are already compatible with the Chat Completions schema. // Only function tools need structural conversion because Chat Completions nests details under "function". toolType := tool.Get("type").String() if toolType != "" && toolType != "function" && tool.IsObject() { // Almost all providers lack built-in tools, so we just ignore them. // chatCompletionsTools = append(chatCompletionsTools, tool.Value()) return true } chatTool := `{"type":"function","function":{}}` // Convert tool structure from responses format to chat completions format function := `{"name":"","description":"","parameters":{}}` if name := tool.Get("name"); name.Exists() { function, _ = sjson.Set(function, "name", name.String()) } if description := tool.Get("description"); description.Exists() { function, _ = sjson.Set(function, "description", description.String()) } if parameters := tool.Get("parameters"); parameters.Exists() { function, _ = sjson.SetRaw(function, "parameters", parameters.Raw) } chatTool, _ = sjson.SetRaw(chatTool, "function", function) chatCompletionsTools = append(chatCompletionsTools, gjson.Parse(chatTool).Value()) return true }) if len(chatCompletionsTools) > 0 { out, _ = sjson.Set(out, "tools", chatCompletionsTools) } } if reasoningEffort := root.Get("reasoning.effort"); reasoningEffort.Exists() { effort := strings.ToLower(strings.TrimSpace(reasoningEffort.String())) if effort != "" { out, _ = sjson.Set(out, "reasoning_effort", effort) } } // Convert tool_choice if present if toolChoice := root.Get("tool_choice"); toolChoice.Exists() { out, _ = sjson.Set(out, "tool_choice", toolChoice.String()) } return []byte(out) }