package clients import ( "bufio" "encoding/json" "fmt" "io" "net/http" "sort" "strings" "time" ) const ( ProtocolOpenAIChatCompletions = "openai_chat_completions" ProtocolOpenAIResponses = "openai_responses" ProtocolAnthropicMessages = "anthropic_messages" ) 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": {}, "store": {}, "previous_response_id": {}, "metadata": {}, "user": {}, } func ResponsesRequestToChat(body map[string]any, history []ResponseTurn) (map[string]any, error) { for key := range body { if _, ok := supportedResponseFallbackParameters[key]; !ok { return nil, unsupportedResponseParameter(key) } } 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", "user"} { if value, ok := body[key]; ok { out[key] = value } } if value, ok := body["max_output_tokens"]; ok { out["max_tokens"] = value } if rawReasoning, ok := body["reasoning"]; ok { reasoning, ok := rawReasoning.(map[string]any) if !ok { return nil, unsupportedResponseParameter("reasoning") } for key := range reasoning { if key != "effort" { return nil, unsupportedResponseParameter("reasoning." + key) } } if effort, ok := reasoning["effort"]; ok { out["reasoning_effort"] = effort } } if rawText, ok := body["text"]; ok { responseFormat, err := responseTextFormat(rawText) if err != nil { return nil, err } if responseFormat != nil { out["response_format"] = responseFormat } } 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) { 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 _, raw := range typed { 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": callID := firstNonEmptyString(item["call_id"], item["id"]) if callID == "" { return nil, &ClientError{Code: "invalid_parameter", Message: "function_call_output.call_id is required", StatusCode: http.StatusBadRequest} } out = append(out, map[string]any{"role": "tool", "tool_call_id": callID, "content": toolResultContent(item["output"])}) case "message", "": role := firstNonEmptyString(item["role"], "user") out = append(out, map[string]any{"role": role, "content": responseContentToChat(item["content"])}) default: return nil, unsupportedResponseParameter("input.type=" + stringFromAny(item["type"])) } default: return nil, unsupportedResponseParameter("input") } } return out, nil case map[string]any: return responseInputMessages([]any{typed}) default: return nil, unsupportedResponseParameter("input") } } func responseContentToChat(value any) any { items, ok := value.([]any) if !ok { return value } out := make([]any, 0, len(items)) for _, raw := range items { item, ok := raw.(map[string]any) if !ok { out = append(out, raw) continue } switch stringFromAny(item["type"]) { case "input_text", "output_text", "text": out = append(out, map[string]any{"type": "text", "text": stringFromAny(item["text"])}) case "input_image": url := firstNonEmptyString(item["image_url"], item["url"]) out = append(out, map[string]any{"type": "image_url", "image_url": map[string]any{"url": url}}) default: out = append(out, item) } } return out } func responseToolsToChat(value any) ([]any, error) { items, ok := value.([]any) if !ok { return nil, unsupportedResponseParameter("tools") } out := make([]any, 0, len(items)) for _, raw := range items { tool, ok := raw.(map[string]any) if !ok || stringFromAny(tool["type"]) != "function" { return nil, &ClientError{Code: "unsupported_response_tool", Message: "Chat fallback only supports custom function tools", StatusCode: http.StatusBadRequest} } function := map[string]any{ "name": tool["name"], "description": tool["description"], "parameters": tool["parameters"], } if strict, ok := tool["strict"]; ok { function["strict"] = strict } out = append(out, map[string]any{"type": "function", "function": function}) } return out, 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 || stringFromAny(choice["type"]) != "function" || stringFromAny(choice["name"]) == "" { return nil, unsupportedResponseParameter("tool_choice") } return map[string]any{"type": "function", "function": map[string]any{"name": choice["name"]}}, nil } func responseTextFormat(value any) (map[string]any, error) { text, ok := value.(map[string]any) if !ok { return nil, unsupportedResponseParameter("text") } for key := range text { if key != "format" { return nil, unsupportedResponseParameter("text." + key) } } format, ok := text["format"].(map[string]any) if !ok || len(format) == 0 { return nil, nil } switch stringFromAny(format["type"]) { case "text": return map[string]any{"type": "text"}, nil case "json_object": return map[string]any{"type": "json_object"}, nil case "json_schema": return map[string]any{"type": "json_schema", "json_schema": map[string]any{ "name": format["name"], "schema": format["schema"], "strict": format["strict"], }}, nil default: return 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) 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"])) } } 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"]}, }) } } message["content"] = strings.Join(textParts, "") if len(toolCalls) > 0 { message["tool_calls"] = toolCalls } if len(textParts) == 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) if content := visibleChatContent(message["content"]); content != "" { outputText = content output = append(output, map[string]any{ "id": "msg_" + responseIDSuffix(publicID), "type": "message", "status": "completed", "role": "assistant", "content": []any{map[string]any{"type": "output_text", "text": content, "annotations": []any{}, "logprobs": []any{}}}, }) } toolCalls, _ := message["tool_calls"].([]any) for index, rawToolCall := range toolCalls { toolCall, _ := rawToolCall.(map[string]any) function, _ := toolCall["function"].(map[string]any) callID := firstNonEmptyString(toolCall["id"], fmt.Sprintf("call_%d", index)) 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", "model": model, "output": output, "output_text": outputText, "error": nil, "incomplete_details": nil, "usage": usage, } 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{"instructions", "parallel_tool_calls", "temperature", "top_p", "tools", "tool_choice", "metadata"} { if value, ok := requestBody[key]; ok { out[key] = value } } return out } 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"} } } 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"]) } if stringFromAny(event["type"]) == "response.completed" { completed, _ = event["response"].(map[string]any) if onDelta != nil { return onDelta(StreamDeltaEvent{Event: event}) } return nil } if stringFromAny(event["type"]) == "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 response.completed", 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 started bool sequence int nextOutput int textOutput int textAdded bool text strings.Builder tools map[int]*chatResponseStreamTool } type chatResponseStreamTool struct { ItemID string CallID string Name string Arguments strings.Builder Added bool OutputIndex int } func newChatResponsesStreamAdapter(publicID string, model string) *chatResponsesStreamAdapter { return &chatResponsesStreamAdapter{publicID: publicID, model: model, textOutput: -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 := map[string]any{"id": a.publicID, "object": "response", "created_at": time.Now().Unix(), "status": "in_progress", "model": a.model, "output": []any{}} 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 !a.textAdded { a.textAdded = true a.textOutput = a.nextOutput a.nextOutput++ itemID := "msg_" + responseIDSuffix(a.publicID) if err := a.emit(onDelta, map[string]any{ "type": "response.output_item.added", "response_id": a.publicID, "output_index": a.textOutput, "item": map[string]any{"id": itemID, "type": "message", "status": "in_progress", "role": "assistant", "content": []any{}}, }); err != nil { return err } if err := a.emit(onDelta, map[string]any{ "type": "response.content_part.added", "response_id": a.publicID, "item_id": itemID, "output_index": a.textOutput, "content_index": 0, "part": map[string]any{"type": "output_text", "text": "", "annotations": []any{}, "logprobs": []any{}}, }); err != nil { return err } } a.text.WriteString(content) if err := a.emit(onDelta, map[string]any{ "type": "response.output_text.delta", "response_id": a.publicID, "item_id": "msg_" + responseIDSuffix(a.publicID), "output_index": a.textOutput, "content_index": 0, "delta": content, }); err != nil { return err } } toolCalls, _ := delta["tool_calls"].([]any) for _, rawToolCall := range toolCalls { toolCall, _ := rawToolCall.(map[string]any) index := intFromAny(toolCall["index"]) tool := a.tools[index] if tool == nil { tool = &chatResponseStreamTool{ItemID: fmt.Sprintf("fc_%s_%d", responseIDSuffix(a.publicID), index), OutputIndex: a.nextOutput} a.nextOutput++ a.tools[index] = tool } if callID := stringFromAny(toolCall["id"]); callID != "" { tool.CallID = callID } function, _ := toolCall["function"].(map[string]any) if name := stringFromAny(function["name"]); name != "" { tool.Name += name } arguments := stringFromAny(function["arguments"]) if !tool.Added && (tool.CallID != "" || tool.Name != "" || arguments != "") { tool.Added = true if err := a.emit(onDelta, map[string]any{ "type": "response.output_item.added", "response_id": a.publicID, "output_index": tool.OutputIndex, "item": map[string]any{"id": tool.ItemID, "type": "function_call", "status": "in_progress", "call_id": tool.CallID, "name": tool.Name, "arguments": ""}, }); err != nil { return err } } if arguments != "" { tool.Arguments.WriteString(arguments) if err := a.emit(onDelta, map[string]any{ "type": "response.function_call_arguments.delta", "response_id": a.publicID, "item_id": tool.ItemID, "output_index": tool.OutputIndex, "delta": arguments, }); err != nil { return err } } } } return nil } func (a *chatResponsesStreamAdapter) done(result map[string]any, onDelta StreamDelta) error { if onDelta == nil { return nil } a.alignOutput(result) if a.text.Len() > 0 { 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.textOutput, "content_index": 0, "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.textOutput, "content_index": 0, "part": map[string]any{"type": "output_text", "text": text, "annotations": []any{}, "logprobs": []any{}}, }); err != nil { return err } if err := a.emit(onDelta, map[string]any{"type": "response.output_item.done", "response_id": a.publicID, "output_index": a.textOutput, "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 { arguments := tool.Arguments.String() if err := a.emit(onDelta, map[string]any{"type": "response.function_call_arguments.done", "response_id": a.publicID, "item_id": tool.ItemID, "output_index": tool.OutputIndex, "arguments": arguments}); 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 } } 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.textOutput >= 0 { ordered[a.textOutput] = 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, StatusCode: http.StatusBadRequest} }