// Package convert 三协议互转:OpenAI Chat / OpenAI Responses / Anthropic Messages。 // 网关以 OpenAI Chat 形状作为标准中间模型(PLANNING §5.1.1)。 // 请求与响应(非流式)走 JSON 转换;流式走逐行 SSE 转换(见 stream.go)。 package convert import ( "encoding/json" "fmt" ) // 协议标识。 const ( ProtoChat = "chat" ProtoMessages = "messages" ProtoResponses = "responses" ) // ConvertRequest 转换请求体。from==to 时原样返回。 func ConvertRequest(body []byte, from, to string) ([]byte, error) { if from == to { return body, nil } switch { case from == ProtoMessages && to == ProtoChat: return messagesToChatReq(body) case from == ProtoChat && to == ProtoMessages: return chatToMessagesReq(body) case from == ProtoResponses && to == ProtoChat: return responsesToChatReq(body) case from == ProtoChat && to == ProtoResponses: return chatToResponsesReq(body) case from == ProtoResponses && to == ProtoMessages: mid, err := responsesToChatReq(body) if err != nil { return nil, err } return chatToMessagesReq(mid) case from == ProtoMessages && to == ProtoResponses: mid, err := messagesToChatReq(body) if err != nil { return nil, err } return chatToResponsesReq(mid) } return nil, fmt.Errorf("unsupported request conversion %s->%s", from, to) } // ConvertResponse 转换响应体(非流式)。from==to 时原样返回。 func ConvertResponse(body []byte, from, to string) ([]byte, error) { if from == to { return body, nil } switch { case from == ProtoMessages && to == ProtoChat: return messagesToChatResp(body) case from == ProtoChat && to == ProtoMessages: return chatToMessagesResp(body) case from == ProtoResponses && to == ProtoChat: return responsesToChatResp(body) case from == ProtoChat && to == ProtoResponses: return chatToResponsesResp(body) case from == ProtoResponses && to == ProtoMessages: mid, err := responsesToChatResp(body) if err != nil { return nil, err } return chatToMessagesResp(mid) case from == ProtoMessages && to == ProtoResponses: mid, err := messagesToChatResp(body) if err != nil { return nil, err } return chatToResponsesResp(mid) } return nil, fmt.Errorf("unsupported response conversion %s->%s", from, to) } // NewStreamTransformer 构造流式逐行转换器:输入上游 SSE 一行,返回客户端 SSE 行。 // 返回 nil 表示丢弃该行;(from==to 时无需转换)。 func NewStreamTransformer(from, to string) func([]byte) []byte { switch { case from == ProtoMessages && to == ProtoChat: return newMessagesToChat().line case from == ProtoChat && to == ProtoMessages: return newChatToMessages().line case from == ProtoResponses && to == ProtoChat: return newResponsesToChat().line case from == ProtoChat && to == ProtoResponses: return newChatToResponses().line case from == ProtoResponses && to == ProtoMessages: return newResponsesToMessages().line case from == ProtoMessages && to == ProtoResponses: return newMessagesToResponses().line } return nil } // --------------------------------------------------------------------------- // 工具函数 // str 返回字符串字段;json.RawMessage 为字符串字面量时去引号。 func str(raw json.RawMessage) string { if len(raw) == 0 || string(raw) == "null" { return "" } var s string if json.Unmarshal(raw, &s) == nil { return s } // 数组/对象:尝试取 type=text 的 text var arr []map[string]any if json.Unmarshal(raw, &arr) == nil { var parts []string for _, b := range arr { if t, _ := b["type"].(string); t == "text" || t == "input_text" || t == "output_text" { if txt, _ := b["text"].(string); txt != "" { parts = append(parts, txt) } } } return joinNonEmpty(parts, "\n") } return "" } func joinNonEmpty(parts []string, sep string) string { out := "" for i, p := range parts { if p == "" { continue } if out != "" { out += sep } out += p _ = i } return out } // rawJSON 安全取字段;不存在或 null 返回 nil。 func rawJSON(m map[string]json.RawMessage, key string) json.RawMessage { raw, ok := m[key] if !ok || string(raw) == "null" { return nil } return raw } // decode 把 RawMessage 解到 map。 func decode(raw json.RawMessage) (map[string]any, error) { var m map[string]any if err := json.Unmarshal(raw, &m); err != nil { return nil, err } return m, nil }