package clients import ( "bufio" "encoding/json" "fmt" "io" "net/http" "sort" "strings" "time" ) const ( ProtocolOpenAIChatCompletions = "openai_chat_completions" ProtocolOpenAIResponses = "openai_responses" ProtocolAnthropicMessages = "anthropic_messages" ProtocolOpenAIEmbeddings = "openai_embeddings" ProtocolOpenAIImages = "openai_images" ProtocolGeminiGenerateContent = "gemini_generate_content" ProtocolGeminiVeo = "gemini_veo_predict_long_running" ProtocolVolcesContents = "volces_contents_generations_v3" ProtocolKlingV1Omni = "kling_v1_omni_video" ProtocolKlingV2Omni = "kling_v2_omni_video" ) var supportedResponseFallbackParameters = map[string]struct{}{ "model": {}, "input": {}, "messages": {}, "instructions": {}, "tools": {}, "tool_choice": {}, "parallel_tool_calls": {}, "max_output_tokens": {}, "temperature": {}, "top_p": {}, "presence_penalty": {}, "frequency_penalty": {}, "reasoning": {}, "text": {}, "stream": {}, "stream_options": {}, "store": {}, "previous_response_id": {}, "metadata": {}, "user": {}, "moderation": {}, "prompt_cache_key": {}, "prompt_cache_options": {}, "prompt_cache_retention": {}, "safety_identifier": {}, "service_tier": {}, "top_logprobs": {}, "include": {}, "background": {}, "context_management": {}, "conversation": {}, "max_tool_calls": {}, "prompt": {}, "truncation": {}, } // ValidateResponsesChatFallback determines whether a Responses request has a // semantics-preserving Chat Completions representation. It is intentionally // reusable by routing so native-only requests never reach a Chat candidate. func ValidateResponsesChatFallback(body map[string]any) error { for key := range body { if _, internal := gatewayOpenAIRequestExtensions[key]; internal { continue } if _, ok := supportedResponseFallbackParameters[key]; !ok { return unsupportedResponseParameter(key) } } for key, value := range map[string]any{ "background": body["background"], "context_management": body["context_management"], "conversation": body["conversation"], "max_tool_calls": body["max_tool_calls"], "prompt": body["prompt"], } { if !responseFallbackNoop(value) { return unsupportedResponseParameter(key) } } if truncation := strings.TrimSpace(stringFromAny(body["truncation"])); truncation != "" && truncation != "disabled" { return unsupportedResponseParameter("truncation") } if background, ok := body["background"].(bool); ok && background { return unsupportedResponseParameter("background") } if include, ok := body["include"].([]any); ok { for index, raw := range include { if stringFromAny(raw) != "message.output_text.logprobs" { return unsupportedResponseParameter(fmt.Sprintf("include[%d]", index)) } } } else if body["include"] != nil { return unsupportedResponseParameter("include") } if reasoning, ok := body["reasoning"].(map[string]any); ok { for key, value := range reasoning { if key != "effort" && !responseFallbackNoop(value) { return unsupportedResponseParameter("reasoning." + key) } } } else if body["reasoning"] != nil { return unsupportedResponseParameter("reasoning") } if tools, ok := body["tools"]; ok { if _, err := responseToolsToChat(tools); err != nil { return err } } if choice, ok := body["tool_choice"]; ok { if _, err := responseToolChoiceToChat(choice); err != nil { return err } } if text, ok := body["text"]; ok { if _, _, err := responseTextParams(text); err != nil { return err } } if streamOptions, ok := body["stream_options"].(map[string]any); ok { for key, value := range streamOptions { if key == "include_usage" { continue } if !responseFallbackNoop(value) { return unsupportedResponseParameter("stream_options." + key) } } } else if body["stream_options"] != nil { return unsupportedResponseParameter("stream_options") } if _, hasChatMessages := body["messages"].([]any); !hasChatMessages { if _, err := responseInputMessages(body["input"]); err != nil { return err } } return nil } func responseFallbackNoop(value any) bool { switch typed := value.(type) { case nil: return true case bool: return !typed case string: return strings.TrimSpace(typed) == "" case map[string]any: return len(typed) == 0 case []any: return len(typed) == 0 default: return false } } func ResponsesRequestToChat(body map[string]any, history []ResponseTurn) (map[string]any, error) { if err := ValidateResponsesChatFallback(body); err != nil { return nil, err } messages := make([]any, 0) for _, turn := range history { priorInput := turn.Request["input"] if priorInput == nil { priorInput = turn.Request["messages"] } priorMessages, err := responseInputMessages(priorInput) if err != nil { return nil, err } messages = append(messages, priorMessages...) if assistant := chatAssistantMessage(turn.Internal, turn.Response); assistant != nil { messages = append(messages, assistant) } } if instructions := strings.TrimSpace(stringFromAny(body["instructions"])); instructions != "" { messages = append(messages, map[string]any{"role": "system", "content": instructions}) } if rawMessages, ok := body["messages"].([]any); ok { messages = append(messages, rawMessages...) } else { currentMessages, err := responseInputMessages(body["input"]) if err != nil { return nil, err } messages = append(messages, currentMessages...) } if len(messages) == 0 { return nil, &ClientError{Code: "invalid_parameter", Message: "input is required", StatusCode: http.StatusBadRequest} } out := map[string]any{"messages": messages} for _, key := range []string{ "temperature", "top_p", "presence_penalty", "frequency_penalty", "parallel_tool_calls", "stream", "store", "metadata", "user", "moderation", "prompt_cache_key", "prompt_cache_options", "prompt_cache_retention", "safety_identifier", "service_tier", } { if value, ok := body[key]; ok { out[key] = value } } if streamOptions, ok := body["stream_options"].(map[string]any); ok { chatOptions := map[string]any{} if includeUsage, explicit := streamOptions["include_usage"]; explicit { chatOptions["include_usage"] = includeUsage } if len(chatOptions) > 0 { out["stream_options"] = chatOptions } } if value, ok := body["top_logprobs"]; ok { out["top_logprobs"] = value out["logprobs"] = true } if include, _ := body["include"].([]any); len(include) > 0 { out["logprobs"] = true } if value, ok := body["max_output_tokens"]; ok { out["max_completion_tokens"] = value } if rawReasoning, ok := body["reasoning"]; ok { reasoning, ok := rawReasoning.(map[string]any) if !ok { return nil, unsupportedResponseParameter("reasoning") } if effort, ok := reasoning["effort"]; ok { out["reasoning_effort"] = effort } } if rawText, ok := body["text"]; ok { responseFormat, verbosity, err := responseTextParams(rawText) if err != nil { return nil, err } if responseFormat != nil { out["response_format"] = responseFormat } if verbosity != nil { out["verbosity"] = verbosity } } if rawTools, ok := body["tools"]; ok { tools, err := responseToolsToChat(rawTools) if err != nil { return nil, err } out["tools"] = tools } if rawChoice, ok := body["tool_choice"]; ok { choice, err := responseToolChoiceToChat(rawChoice) if err != nil { return nil, err } out["tool_choice"] = choice } return out, nil } func responseInputMessages(value any) ([]any, error) { return responseInputMessagesAt(value, "input") } func responseInputMessagesAt(value any, path string) ([]any, error) { switch typed := value.(type) { case nil: return nil, nil case string: return []any{map[string]any{"role": "user", "content": typed}}, nil case []any: out := make([]any, 0, len(typed)) for index, raw := range typed { itemPath := fmt.Sprintf("%s[%d]", path, index) switch item := raw.(type) { case string: out = append(out, map[string]any{"role": "user", "content": item}) case map[string]any: switch stringFromAny(item["type"]) { case "function_call_output", "custom_tool_call_output": if err := validateFallbackObjectKeys(item, itemPath, stringSet("type", "id", "call_id", "output", "status")); err != nil { return nil, err } if err := validateFallbackItemStatus(item, itemPath); err != nil { return nil, err } callID := firstNonEmptyString(item["call_id"], item["id"]) if callID == "" { return nil, &ClientError{Code: "invalid_parameter", Message: itemPath + ".call_id is required", Param: itemPath + ".call_id", StatusCode: http.StatusBadRequest} } output, err := responseToolOutputToChat(item["output"], itemPath+".output") if err != nil { return nil, err } out = append(out, map[string]any{"role": "tool", "tool_call_id": callID, "content": output}) case "function_call": if err := validateFallbackObjectKeys(item, itemPath, stringSet("type", "id", "call_id", "name", "arguments", "status")); err != nil { return nil, err } if err := validateFallbackItemStatus(item, itemPath); err != nil { return nil, err } if firstNonEmptyString(item["call_id"], item["id"]) == "" { return nil, &ClientError{Code: "invalid_parameter", Message: itemPath + ".call_id is required", Param: itemPath + ".call_id", StatusCode: http.StatusBadRequest} } out = append(out, responseCallMessage(item, "function")) case "custom_tool_call": if err := validateFallbackObjectKeys(item, itemPath, stringSet("type", "id", "call_id", "name", "input", "status")); err != nil { return nil, err } if err := validateFallbackItemStatus(item, itemPath); err != nil { return nil, err } if firstNonEmptyString(item["call_id"], item["id"]) == "" { return nil, &ClientError{Code: "invalid_parameter", Message: itemPath + ".call_id is required", Param: itemPath + ".call_id", StatusCode: http.StatusBadRequest} } out = append(out, responseCallMessage(item, "custom")) case "message", "": if err := validateFallbackObjectKeys(item, itemPath, stringSet("type", "id", "role", "content", "name", "audio", "status")); err != nil { return nil, err } if err := validateFallbackItemStatus(item, itemPath); err != nil { return nil, err } message, err := responseMessageToChat(item, itemPath) if err != nil { return nil, err } out = append(out, message) default: return nil, unsupportedResponseParameter(itemPath + ".type") } default: return nil, unsupportedResponseParameter(itemPath) } } return out, nil case map[string]any: return responseInputMessagesAt([]any{typed}, path) default: return nil, unsupportedResponseParameter("input") } } func validateFallbackItemStatus(item map[string]any, path string) error { status := strings.TrimSpace(stringFromAny(item["status"])) if status == "" || status == "completed" { return nil } return unsupportedResponseParameter(path + ".status") } func responseMessageToChat(item map[string]any, path string) (map[string]any, error) { role := firstNonEmptyString(item["role"], "user") content, refusal, err := responseContentToChat(item["content"], path+".content") if err != nil { return nil, err } message := map[string]any{"role": role, "content": content} if refusal != "" { message["refusal"] = refusal } for _, key := range []string{"name", "audio"} { if value, ok := item[key]; ok { message[key] = value } } return message, nil } func responseCallMessage(item map[string]any, kind string) map[string]any { callID := firstNonEmptyString(item["call_id"], item["id"]) toolCall := map[string]any{"id": callID, "type": kind} if kind == "custom" { toolCall["custom"] = map[string]any{"name": item["name"], "input": item["input"]} } else { toolCall["function"] = map[string]any{"name": item["name"], "arguments": item["arguments"]} } return map[string]any{"role": "assistant", "content": nil, "tool_calls": []any{toolCall}} } func responseToolOutputToChat(value any, path string) (any, error) { if value == nil { return nil, &ClientError{Code: "invalid_parameter", Message: path + " is required", Param: path, StatusCode: http.StatusBadRequest} } if text, ok := value.(string); ok { return text, nil } items, ok := value.([]any) if !ok { return toolResultContent(value), nil } parts := make([]any, 0, len(items)) for index, raw := range items { partPath := fmt.Sprintf("%s[%d]", path, index) item, ok := raw.(map[string]any) if !ok { return nil, unsupportedResponseParameter(partPath) } typeName := stringFromAny(item["type"]) if typeName != "input_text" && typeName != "text" { return nil, unsupportedResponseParameter(partPath + ".type") } if err := validateFallbackObjectKeys(item, partPath, stringSet("type", "text", "prompt_cache_breakpoint")); err != nil { return nil, err } part := map[string]any{"type": "text", "text": stringFromAny(item["text"])} if breakpoint, ok := item["prompt_cache_breakpoint"]; ok { part["prompt_cache_breakpoint"] = breakpoint } parts = append(parts, part) } return parts, nil } func responseContentToChat(value any, path string) (any, string, error) { items, ok := value.([]any) if !ok { return value, "", nil } out := make([]any, 0, len(items)) refusals := make([]string, 0) for index, raw := range items { partPath := fmt.Sprintf("%s[%d]", path, index) item, ok := raw.(map[string]any) if !ok { return nil, "", unsupportedResponseParameter(partPath) } cacheBreakpoint := item["prompt_cache_breakpoint"] part := map[string]any{} switch stringFromAny(item["type"]) { case "input_text", "output_text", "text": if err := validateFallbackObjectKeys(item, partPath, stringSet("type", "text", "annotations", "logprobs", "prompt_cache_breakpoint")); err != nil { return nil, "", err } part = map[string]any{"type": "text", "text": stringFromAny(item["text"])} case "input_image": if err := validateFallbackObjectKeys(item, partPath, stringSet("type", "image_url", "url", "file_id", "detail", "prompt_cache_breakpoint")); err != nil { return nil, "", err } if fileID := stringFromAny(item["file_id"]); fileID != "" { part = map[string]any{"type": "file", "file": map[string]any{"file_id": fileID}} if detail, ok := item["detail"]; ok { part["detail"] = detail } } else { image := map[string]any{"url": firstNonEmptyString(item["image_url"], item["url"])} if detail, ok := item["detail"]; ok { image["detail"] = detail } part = map[string]any{"type": "image_url", "image_url": image} } case "input_file", "file": if err := validateFallbackObjectKeys(item, partPath, stringSet("type", "file_id", "file_data", "filename", "prompt_cache_breakpoint")); err != nil { return nil, "", err } file := map[string]any{} for _, key := range []string{"file_id", "file_data", "filename"} { if value, ok := item[key]; ok { file[key] = value } } part = map[string]any{"type": "file", "file": file} case "input_audio": if err := validateFallbackObjectKeys(item, partPath, stringSet("type", "input_audio", "data", "format", "prompt_cache_breakpoint")); err != nil { return nil, "", err } audio := item["input_audio"] if audio == nil { audio = map[string]any{"data": item["data"], "format": item["format"]} } part = map[string]any{"type": "input_audio", "input_audio": audio} case "refusal": if err := validateFallbackObjectKeys(item, partPath, stringSet("type", "refusal")); err != nil { return nil, "", err } refusals = append(refusals, stringFromAny(item["refusal"])) continue default: return nil, "", unsupportedResponseParameter(partPath + ".type") } if cacheBreakpoint != nil { part["prompt_cache_breakpoint"] = cacheBreakpoint } out = append(out, part) } return out, strings.Join(refusals, ""), nil } func responseToolsToChat(value any) ([]any, error) { items, ok := value.([]any) if !ok { return nil, unsupportedResponseParameter("tools") } out := make([]any, 0, len(items)) for index, raw := range items { tool, ok := raw.(map[string]any) if !ok { return nil, unsupportedResponseParameter(fmt.Sprintf("tools[%d]", index)) } switch stringFromAny(tool["type"]) { case "function": if err := validateFallbackObjectKeys(tool, fmt.Sprintf("tools[%d]", index), stringSet("type", "name", "description", "parameters", "strict", "prompt_cache_breakpoint", "defer_loading")); err != nil { return nil, err } if value := tool["defer_loading"]; !responseFallbackNoop(value) { return nil, unsupportedResponseParameter(fmt.Sprintf("tools[%d].defer_loading", index)) } function := map[string]any{} for _, key := range []string{"name", "description", "parameters", "strict"} { if value, ok := tool[key]; ok { function[key] = value } } converted := map[string]any{"type": "function", "function": function} if breakpoint, ok := tool["prompt_cache_breakpoint"]; ok { converted["prompt_cache_breakpoint"] = breakpoint } out = append(out, converted) case "custom": if err := validateFallbackObjectKeys(tool, fmt.Sprintf("tools[%d]", index), stringSet("type", "name", "description", "format", "prompt_cache_breakpoint", "defer_loading")); err != nil { return nil, err } if value := tool["defer_loading"]; !responseFallbackNoop(value) { return nil, unsupportedResponseParameter(fmt.Sprintf("tools[%d].defer_loading", index)) } custom := map[string]any{} for _, key := range []string{"name", "description", "format"} { if value, ok := tool[key]; ok { custom[key] = value } } converted := map[string]any{"type": "custom", "custom": custom} if breakpoint, ok := tool["prompt_cache_breakpoint"]; ok { converted["prompt_cache_breakpoint"] = breakpoint } out = append(out, converted) default: return nil, unsupportedResponseParameter(fmt.Sprintf("tools[%d].type", index)) } } return out, nil } func validateFallbackObjectKeys(value map[string]any, path string, allowed map[string]struct{}) error { for key, field := range value { if _, ok := allowed[key]; ok { continue } if !responseFallbackNoop(field) { return unsupportedResponseParameter(path + "." + key) } } return nil } func responseToolChoiceToChat(value any) (any, error) { if text, ok := value.(string); ok { switch text { case "auto", "none", "required": return text, nil default: return nil, unsupportedResponseParameter("tool_choice") } } choice, ok := value.(map[string]any) if !ok { return nil, unsupportedResponseParameter("tool_choice") } switch stringFromAny(choice["type"]) { case "function": if err := validateFallbackObjectKeys(choice, "tool_choice", stringSet("type", "name")); err != nil { return nil, err } if stringFromAny(choice["name"]) == "" { return nil, unsupportedResponseParameter("tool_choice.name") } return map[string]any{"type": "function", "function": map[string]any{"name": choice["name"]}}, nil case "custom": if err := validateFallbackObjectKeys(choice, "tool_choice", stringSet("type", "name")); err != nil { return nil, err } if stringFromAny(choice["name"]) == "" { return nil, unsupportedResponseParameter("tool_choice.name") } return map[string]any{"type": "custom", "custom": map[string]any{"name": choice["name"]}}, nil case "allowed_tools": if err := validateFallbackObjectKeys(choice, "tool_choice", stringSet("type", "mode", "tools")); err != nil { return nil, err } allowed := map[string]any{"mode": choice["mode"]} tools, err := responseToolReferencesToChat(choice["tools"]) if err != nil { return nil, err } allowed["tools"] = tools return map[string]any{"type": "allowed_tools", "allowed_tools": allowed}, nil default: return nil, unsupportedResponseParameter("tool_choice.type") } } func responseToolReferencesToChat(value any) ([]any, error) { items, ok := value.([]any) if !ok { return nil, unsupportedResponseParameter("tool_choice.tools") } out := make([]any, 0, len(items)) for index, raw := range items { tool, ok := raw.(map[string]any) if !ok { return nil, unsupportedResponseParameter(fmt.Sprintf("tool_choice.tools[%d]", index)) } switch stringFromAny(tool["type"]) { case "function": out = append(out, map[string]any{"type": "function", "function": map[string]any{"name": tool["name"]}}) case "custom": out = append(out, map[string]any{"type": "custom", "custom": map[string]any{"name": tool["name"]}}) default: return nil, unsupportedResponseParameter(fmt.Sprintf("tool_choice.tools[%d].type", index)) } } return out, nil } func responseTextParams(value any) (map[string]any, any, error) { text, ok := value.(map[string]any) if !ok { return nil, nil, unsupportedResponseParameter("text") } for key, value := range text { if key != "format" && key != "verbosity" && !responseFallbackNoop(value) { return nil, nil, unsupportedResponseParameter("text." + key) } } verbosity := text["verbosity"] format, ok := text["format"].(map[string]any) if !ok || len(format) == 0 { return nil, verbosity, nil } switch stringFromAny(format["type"]) { case "text": if err := validateFallbackObjectKeys(format, "text.format", stringSet("type")); err != nil { return nil, nil, err } return map[string]any{"type": "text"}, verbosity, nil case "json_object": if err := validateFallbackObjectKeys(format, "text.format", stringSet("type")); err != nil { return nil, nil, err } return map[string]any{"type": "json_object"}, verbosity, nil case "json_schema": if err := validateFallbackObjectKeys(format, "text.format", stringSet("type", "name", "description", "schema", "strict")); err != nil { return nil, nil, err } jsonSchema := map[string]any{} for _, key := range []string{"name", "description", "schema", "strict"} { if value, ok := format[key]; ok { jsonSchema[key] = value } } return map[string]any{"type": "json_schema", "json_schema": jsonSchema}, verbosity, nil default: return nil, nil, unsupportedResponseParameter("text.format.type") } } func chatAssistantMessage(internal map[string]any, visible map[string]any) map[string]any { if choices, ok := internal["choices"].([]any); ok && len(choices) > 0 { choice, _ := choices[0].(map[string]any) if message, ok := choice["message"].(map[string]any); ok { return cloneMapAny(message) } } output, _ := visible["output"].([]any) message := map[string]any{"role": "assistant"} textParts := make([]string, 0) toolCalls := make([]any, 0) refusals := make([]string, 0) for _, raw := range output { item, _ := raw.(map[string]any) switch stringFromAny(item["type"]) { case "message": content, _ := item["content"].([]any) for _, rawContent := range content { part, _ := rawContent.(map[string]any) if stringFromAny(part["type"]) == "output_text" { textParts = append(textParts, stringFromAny(part["text"])) } else if stringFromAny(part["type"]) == "refusal" { refusals = append(refusals, stringFromAny(part["refusal"])) } } case "function_call": toolCalls = append(toolCalls, map[string]any{ "id": item["call_id"], "type": "function", "function": map[string]any{"name": item["name"], "arguments": item["arguments"]}, }) case "custom_tool_call": toolCalls = append(toolCalls, map[string]any{ "id": item["call_id"], "type": "custom", "custom": map[string]any{"name": item["name"], "input": item["input"]}, }) } } message["content"] = strings.Join(textParts, "") if len(refusals) > 0 { message["refusal"] = strings.Join(refusals, "") } if len(toolCalls) > 0 { message["tool_calls"] = toolCalls } if len(textParts) == 0 && len(refusals) == 0 && len(toolCalls) == 0 { return nil } return message } func ChatResultToResponse(chat map[string]any, publicID string, model string, requestBody map[string]any) map[string]any { createdAt := time.Now().Unix() if value := intFromAny(chat["created"]); value > 0 { createdAt = int64(value) } output := make([]any, 0) outputText := "" choices, _ := chat["choices"].([]any) if len(choices) > 0 { choice, _ := choices[0].(map[string]any) message, _ := choice["message"].(map[string]any) content := visibleChatContent(message["content"]) refusal := stringFromAny(message["refusal"]) if content != "" || refusal != "" { outputText = content parts := make([]any, 0, 2) if content != "" { annotations, _ := message["annotations"].([]any) if annotations == nil { annotations = []any{} } parts = append(parts, map[string]any{ "type": "output_text", "text": content, "annotations": annotations, "logprobs": responseLogprobsFromChoice(choice), }) } if refusal != "" { parts = append(parts, map[string]any{"type": "refusal", "refusal": refusal}) } output = append(output, map[string]any{ "id": "msg_" + responseIDSuffix(publicID), "type": "message", "status": "completed", "role": "assistant", "content": parts, }) } toolCalls, _ := message["tool_calls"].([]any) if functionCall, ok := message["function_call"].(map[string]any); ok { legacyCall := map[string]any{ "id": "call_legacy", "type": "function", "function": functionCall, } toolCalls = append([]any{legacyCall}, toolCalls...) } for index, rawToolCall := range toolCalls { toolCall, _ := rawToolCall.(map[string]any) callID := firstNonEmptyString(toolCall["id"], fmt.Sprintf("call_%d", index)) if stringFromAny(toolCall["type"]) == "custom" { custom, _ := toolCall["custom"].(map[string]any) output = append(output, map[string]any{ "id": "ctc_" + responseIDSuffix(publicID) + fmt.Sprintf("_%d", index), "type": "custom_tool_call", "status": "completed", "call_id": callID, "name": stringFromAny(custom["name"]), "input": stringFromAny(custom["input"]), }) continue } function, _ := toolCall["function"].(map[string]any) output = append(output, map[string]any{ "id": "fc_" + responseIDSuffix(publicID) + fmt.Sprintf("_%d", index), "type": "function_call", "status": "completed", "call_id": callID, "name": stringFromAny(function["name"]), "arguments": stringFromAny(function["arguments"]), }) } } usage := responseUsageFromChat(chat["usage"]) out := map[string]any{ "id": publicID, "object": "response", "created_at": createdAt, "status": "completed", "completed_at": createdAt, "model": model, "output": output, "output_text": outputText, "error": nil, "incomplete_details": nil, "usage": usage, "background": false, "conversation": nil, "max_output_tokens": nil, "max_tool_calls": nil, "moderation": nil, "prompt": nil, "prompt_cache_key": nil, "prompt_cache_options": nil, "prompt_cache_retention": nil, "reasoning": nil, "safety_identifier": nil, "service_tier": nil, "text": nil, "top_logprobs": nil, "truncation": "disabled", "user": nil, "instructions": nil, "metadata": map[string]any{}, "parallel_tool_calls": true, "temperature": nil, "tool_choice": "auto", "tools": []any{}, "top_p": nil, } if previousResponseID := strings.TrimSpace(stringFromAny(requestBody["previous_response_id"])); previousResponseID != "" { out["previous_response_id"] = previousResponseID } else { out["previous_response_id"] = nil } applyResponseFinishReason(out, choices) for _, key := range []string{ "background", "conversation", "instructions", "max_output_tokens", "max_tool_calls", "metadata", "moderation", "parallel_tool_calls", "prompt", "prompt_cache_key", "prompt_cache_options", "prompt_cache_retention", "reasoning", "safety_identifier", "service_tier", "temperature", "text", "tool_choice", "tools", "top_logprobs", "top_p", "truncation", "user", } { if value, ok := requestBody[key]; ok { out[key] = value } } for _, key := range []string{"service_tier", "moderation"} { if value, ok := chat[key]; ok { out[key] = value } } return out } func responseLogprobsFromChoice(choice map[string]any) []any { logprobs, _ := choice["logprobs"].(map[string]any) content, _ := logprobs["content"].([]any) if content == nil { return []any{} } return content } func applyResponseFinishReason(response map[string]any, choices []any) { if len(choices) == 0 { return } choice, _ := choices[0].(map[string]any) switch strings.TrimSpace(stringFromAny(choice["finish_reason"])) { case "length": response["status"] = "incomplete" response["incomplete_details"] = map[string]any{"reason": "max_output_tokens"} case "content_filter": response["status"] = "incomplete" response["incomplete_details"] = map[string]any{"reason": "content_filter"} } if response["status"] == "incomplete" { response["completed_at"] = nil } } func responseUsageFromChat(value any) map[string]any { usage, _ := value.(map[string]any) normalized := usageFromOpenAIUsage(usage) return map[string]any{ "input_tokens": normalized.InputTokens, "input_tokens_details": map[string]any{"cached_tokens": normalized.CachedInputTokens}, "output_tokens": normalized.OutputTokens, "output_tokens_details": map[string]any{"reasoning_tokens": reasoningTokensFromUsage(usage)}, "total_tokens": normalized.TotalTokens, } } func reasoningTokensFromUsage(usage map[string]any) int { for _, key := range []string{"completion_tokens_details", "output_tokens_details"} { if details, ok := usage[key].(map[string]any); ok { if value := intFromAny(details["reasoning_tokens"]); value > 0 { return value } } } return 0 } func visibleChatContent(value any) string { if text, ok := value.(string); ok { return text } items, _ := value.([]any) parts := make([]string, 0, len(items)) for _, raw := range items { item, _ := raw.(map[string]any) if text := stringFromAny(firstPresent(item["text"], item["content"])); text != "" && !isReasoningContentBlock(item) { parts = append(parts, text) } } return strings.Join(parts, "") } func decodeNativeResponsesStream(resp *http.Response, onDelta StreamDelta) (map[string]any, string, error) { defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { raw, _ := io.ReadAll(io.LimitReader(resp.Body, 16*1024*1024)) return nil, "", &ClientError{Code: statusCodeName(resp.StatusCode), Message: errorMessage(raw, resp.Status), StatusCode: resp.StatusCode, RequestID: requestIDFromHTTPResponse(resp), Retryable: HTTPRetryable(resp.StatusCode)} } scanner := bufio.NewScanner(resp.Body) scanner.Buffer(make([]byte, 0, 64*1024), 16*1024*1024) var completed map[string]any upstreamID := "" rawLines := make([]string, 0) eventName := "" dataLines := make([]string, 0) processFrame := func() error { payload := strings.TrimSpace(strings.Join(dataLines, "\n")) frameEventName := eventName eventName = "" dataLines = dataLines[:0] if payload == "" || payload == "[DONE]" { return nil } var event map[string]any if err := json.Unmarshal([]byte(payload), &event); err != nil { return nil } if stringFromAny(event["type"]) == "" && frameEventName != "" { event["type"] = frameEventName } if failure := responseStreamError(event); failure != nil { return failure } if response, ok := event["response"].(map[string]any); ok && upstreamID == "" { upstreamID = stringFromAny(response["id"]) } eventType := stringFromAny(event["type"]) if eventType == "response.completed" || eventType == "response.incomplete" || eventType == "response.cancelled" { completed, _ = event["response"].(map[string]any) if onDelta != nil { return onDelta(StreamDeltaEvent{Event: event}) } return nil } if eventType == "response.failed" { if onDelta != nil { if err := onDelta(StreamDeltaEvent{Event: event}); err != nil { return err } } failed, _ := event["response"].(map[string]any) failure, _ := failed["error"].(map[string]any) code := firstNonEmptyString(failure["code"], "response_failed") message := firstNonEmptyString(failure["message"], "upstream Responses request failed") return &ClientError{Code: code, Message: message, StatusCode: http.StatusBadRequest, Retryable: false} } if onDelta != nil { return onDelta(StreamDeltaEvent{Text: nativeResponseDeltaText(event), Event: event}) } return nil } for scanner.Scan() { rawLine := scanner.Text() rawLines = append(rawLines, rawLine) line := strings.TrimSpace(rawLine) if line == "" { if err := processFrame(); err != nil { return nil, upstreamID, err } continue } if strings.HasPrefix(line, "event:") { eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) continue } if strings.HasPrefix(line, "data:") { dataLines = append(dataLines, strings.TrimSpace(strings.TrimPrefix(line, "data:"))) } } if err := processFrame(); err != nil { return nil, upstreamID, err } if err := scanner.Err(); err != nil { return nil, upstreamID, &ClientError{Code: "stream_read_error", Message: err.Error(), Retryable: true} } if completed == nil { raw := strings.TrimSpace(strings.Join(rawLines, "\n")) var response map[string]any if raw != "" && json.Unmarshal([]byte(raw), &response) == nil { if failure := responseStreamError(response); failure != nil { return nil, upstreamID, failure } if stringFromAny(response["object"]) == "response" { upstreamID = stringFromAny(response["id"]) return response, upstreamID, nil } } return nil, upstreamID, &ClientError{Code: "invalid_response", Message: "Responses stream ended without a terminal response event", Retryable: false} } return completed, upstreamID, nil } func responseStreamError(event map[string]any) error { if stringFromAny(event["type"]) != "error" && event["error"] == nil { return nil } failure, _ := event["error"].(map[string]any) if failure == nil { failure = event } code := firstNonEmptyString(failure["code"], "response_failed") message := firstNonEmptyString(failure["message"], "upstream Responses stream failed") return &ClientError{Code: code, Message: message, StatusCode: http.StatusBadRequest, Retryable: false} } func nativeResponseDeltaText(event map[string]any) string { if stringFromAny(event["type"]) == "response.output_text.delta" { return stringFromAny(event["delta"]) } return "" } type chatResponsesStreamAdapter struct { publicID string model string requestBody map[string]any started bool sequence int nextOutput int messageOutput int nextContent int textContentIndex int refusalContentIndex int messageAdded bool textAdded bool refusalAdded bool text strings.Builder refusal strings.Builder tools map[int]*chatResponseStreamTool } type chatResponseStreamTool struct { ItemID string CallID string Kind string Name string Payload strings.Builder Added bool OutputIndex int } func newChatResponsesStreamAdapter(publicID string, model string, requestBody ...map[string]any) *chatResponsesStreamAdapter { var body map[string]any if len(requestBody) > 0 { body = requestBody[0] } return &chatResponsesStreamAdapter{ publicID: publicID, model: model, requestBody: body, messageOutput: -1, textContentIndex: -1, refusalContentIndex: -1, tools: map[int]*chatResponseStreamTool{}, } } func (a *chatResponsesStreamAdapter) emit(onDelta StreamDelta, event map[string]any) error { if onDelta == nil { return nil } event["sequence_number"] = a.sequence a.sequence++ return onDelta(StreamDeltaEvent{Text: nativeResponseDeltaText(event), Event: event}) } func (a *chatResponsesStreamAdapter) start(onDelta StreamDelta) error { if a.started || onDelta == nil { return nil } a.started = true response := ChatResultToResponse(map[string]any{}, a.publicID, a.model, a.requestBody) response["status"] = "in_progress" response["completed_at"] = nil response["output"] = []any{} response["output_text"] = "" response["usage"] = nil for _, eventType := range []string{"response.created", "response.in_progress"} { if err := a.emit(onDelta, map[string]any{"type": eventType, "response": response}); err != nil { return err } } return nil } func (a *chatResponsesStreamAdapter) delta(event StreamDeltaEvent, onDelta StreamDelta) error { if onDelta == nil || event.Event == nil { return nil } if err := a.start(onDelta); err != nil { return err } choices, _ := event.Event["choices"].([]any) for _, rawChoice := range choices { choice, _ := rawChoice.(map[string]any) delta, _ := choice["delta"].(map[string]any) if content := stringFromAny(delta["content"]); content != "" { if err := a.ensureMessage(onDelta); err != nil { return err } if !a.textAdded { a.textAdded = true a.textContentIndex = a.nextContent a.nextContent++ itemID := "msg_" + responseIDSuffix(a.publicID) if err := a.emit(onDelta, map[string]any{ "type": "response.content_part.added", "response_id": a.publicID, "item_id": itemID, "output_index": a.messageOutput, "content_index": a.textContentIndex, "part": map[string]any{"type": "output_text", "text": "", "annotations": []any{}, "logprobs": []any{}}, }); err != nil { return err } } a.text.WriteString(content) textEvent := map[string]any{ "type": "response.output_text.delta", "response_id": a.publicID, "item_id": "msg_" + responseIDSuffix(a.publicID), "output_index": a.messageOutput, "content_index": a.textContentIndex, "delta": content, } if logprobs := responseStreamLogprobs(choice); len(logprobs) > 0 { textEvent["logprobs"] = logprobs } if err := a.emit(onDelta, textEvent); err != nil { return err } } if refusal := stringFromAny(delta["refusal"]); refusal != "" { if err := a.ensureMessage(onDelta); err != nil { return err } if !a.refusalAdded { a.refusalAdded = true a.refusalContentIndex = a.nextContent a.nextContent++ if err := a.emit(onDelta, map[string]any{ "type": "response.content_part.added", "response_id": a.publicID, "item_id": "msg_" + responseIDSuffix(a.publicID), "output_index": a.messageOutput, "content_index": a.refusalContentIndex, "part": map[string]any{"type": "refusal", "refusal": ""}, }); err != nil { return err } } a.refusal.WriteString(refusal) if err := a.emit(onDelta, map[string]any{ "type": "response.refusal.delta", "response_id": a.publicID, "item_id": "msg_" + responseIDSuffix(a.publicID), "output_index": a.messageOutput, "content_index": a.refusalContentIndex, "delta": refusal, }); err != nil { return err } } toolCalls := streamToolCallsFromDelta(delta) for _, rawToolCall := range toolCalls { toolCall, _ := rawToolCall.(map[string]any) index := intFromAny(toolCall["index"]) tool := a.tools[index] if tool == nil { kind := "function" prefix := "fc" if stringFromAny(toolCall["type"]) == "custom" || toolCall["custom"] != nil { kind = "custom" prefix = "ctc" } tool = &chatResponseStreamTool{ItemID: fmt.Sprintf("%s_%s_%d", prefix, responseIDSuffix(a.publicID), index), Kind: kind, OutputIndex: a.nextOutput} a.nextOutput++ a.tools[index] = tool } if callID := stringFromAny(toolCall["id"]); callID != "" { tool.CallID = callID } payloadContainer, _ := toolCall[tool.Kind].(map[string]any) if name := stringFromAny(payloadContainer["name"]); name != "" { tool.Name += name } payloadKey := "arguments" if tool.Kind == "custom" { payloadKey = "input" } payload := stringFromAny(payloadContainer[payloadKey]) if !tool.Added && (tool.CallID != "" || tool.Name != "" || payload != "") { tool.Added = true item := map[string]any{"id": tool.ItemID, "status": "in_progress", "call_id": tool.CallID, "name": tool.Name} if tool.Kind == "custom" { item["type"] = "custom_tool_call" item["input"] = "" } else { item["type"] = "function_call" item["arguments"] = "" } if err := a.emit(onDelta, map[string]any{ "type": "response.output_item.added", "response_id": a.publicID, "output_index": tool.OutputIndex, "item": item, }); err != nil { return err } } if payload != "" { tool.Payload.WriteString(payload) eventType := "response.function_call_arguments.delta" if tool.Kind == "custom" { eventType = "response.custom_tool_call_input.delta" } if err := a.emit(onDelta, map[string]any{ "type": eventType, "response_id": a.publicID, "item_id": tool.ItemID, "output_index": tool.OutputIndex, "delta": payload, }); err != nil { return err } } } } return nil } func (a *chatResponsesStreamAdapter) ensureMessage(onDelta StreamDelta) error { if a.messageAdded { return nil } a.messageAdded = true a.messageOutput = a.nextOutput a.nextOutput++ return a.emit(onDelta, map[string]any{ "type": "response.output_item.added", "response_id": a.publicID, "output_index": a.messageOutput, "item": map[string]any{"id": "msg_" + responseIDSuffix(a.publicID), "type": "message", "status": "in_progress", "role": "assistant", "content": []any{}}, }) } func responseStreamLogprobs(choice map[string]any) []any { logprobs, _ := choice["logprobs"].(map[string]any) content, _ := logprobs["content"].([]any) return content } func (a *chatResponsesStreamAdapter) done(result map[string]any, onDelta StreamDelta) error { if onDelta == nil { return nil } if err := a.start(onDelta); err != nil { return err } a.alignOutput(result) if a.textAdded { text := a.text.String() itemID := "msg_" + responseIDSuffix(a.publicID) if err := a.emit(onDelta, map[string]any{"type": "response.output_text.done", "response_id": a.publicID, "item_id": itemID, "output_index": a.messageOutput, "content_index": a.textContentIndex, "text": text}); err != nil { return err } if err := a.emit(onDelta, map[string]any{ "type": "response.content_part.done", "response_id": a.publicID, "item_id": itemID, "output_index": a.messageOutput, "content_index": a.textContentIndex, "part": responseMessagePart(result, "output_text"), }); err != nil { return err } } if a.refusalAdded { refusal := a.refusal.String() itemID := "msg_" + responseIDSuffix(a.publicID) if err := a.emit(onDelta, map[string]any{"type": "response.refusal.done", "response_id": a.publicID, "item_id": itemID, "output_index": a.messageOutput, "content_index": a.refusalContentIndex, "refusal": refusal}); err != nil { return err } if err := a.emit(onDelta, map[string]any{ "type": "response.content_part.done", "response_id": a.publicID, "item_id": itemID, "output_index": a.messageOutput, "content_index": a.refusalContentIndex, "part": responseMessagePart(result, "refusal"), }); err != nil { return err } } if a.messageAdded { if err := a.emit(onDelta, map[string]any{"type": "response.output_item.done", "response_id": a.publicID, "output_index": a.messageOutput, "item": firstResponseOutputItem(result, "message")}); err != nil { return err } } tools := make([]*chatResponseStreamTool, 0, len(a.tools)) for _, tool := range a.tools { tools = append(tools, tool) } sort.Slice(tools, func(i, j int) bool { return tools[i].OutputIndex < tools[j].OutputIndex }) for _, tool := range tools { payload := tool.Payload.String() eventType := "response.function_call_arguments.done" payloadKey := "arguments" if tool.Kind == "custom" { eventType = "response.custom_tool_call_input.done" payloadKey = "input" } if err := a.emit(onDelta, map[string]any{"type": eventType, "response_id": a.publicID, "item_id": tool.ItemID, "output_index": tool.OutputIndex, payloadKey: payload}); err != nil { return err } if err := a.emit(onDelta, map[string]any{"type": "response.output_item.done", "response_id": a.publicID, "output_index": tool.OutputIndex, "item": responseOutputItemByID(result, tool.ItemID)}); err != nil { return err } } terminalType := "response.completed" if stringFromAny(result["status"]) == "incomplete" { terminalType = "response.incomplete" } return a.emit(onDelta, map[string]any{"type": terminalType, "response": result}) } func responseMessagePart(response map[string]any, partType string) any { message, _ := firstResponseOutputItem(response, "message").(map[string]any) parts, _ := message["content"].([]any) for _, raw := range parts { part, _ := raw.(map[string]any) if stringFromAny(part["type"]) == partType { return part } } return nil } func (a *chatResponsesStreamAdapter) alignOutput(response map[string]any) { items, _ := response["output"].([]any) if len(items) == 0 || a.nextOutput == 0 { return } ordered := make([]any, a.nextOutput) if a.messageOutput >= 0 { ordered[a.messageOutput] = firstResponseOutputItem(response, "message") } for _, tool := range a.tools { ordered[tool.OutputIndex] = responseOutputItemByID(response, tool.ItemID) } result := make([]any, 0, len(ordered)) for _, item := range ordered { if item != nil { result = append(result, item) } } response["output"] = result } func firstResponseOutputItem(response map[string]any, itemType string) any { items, _ := response["output"].([]any) for _, raw := range items { item, _ := raw.(map[string]any) if stringFromAny(item["type"]) == itemType { return item } } return nil } func responseOutputItemByID(response map[string]any, id string) any { items, _ := response["output"].([]any) for _, raw := range items { item, _ := raw.(map[string]any) if stringFromAny(item["id"]) == id { return item } } return nil } func responseIDSuffix(publicID string) string { return strings.TrimPrefix(publicID, "resp_") } func unsupportedResponseParameter(parameter string) error { return &ClientError{ Code: "unsupported_response_parameter", Message: "Chat fallback does not support Responses parameter: " + parameter, Param: parameter, StatusCode: http.StatusBadRequest, Retryable: false, } }