// 三协议互转注册表:OpenAI Chat / OpenAI Responses / Anthropic Messages。 // 网关以 Chat 形状作为标准中间模型:非跨 chat 的转换经 chat 中转。 // 请求/响应(非流式)走 JSON 转换;流式走逐行 SSE 转换(stream_transform.go)。 package convert import ( "bytes" "encoding/json" "fmt" ) // 协议标识。 const ( ProtoChat = "chat" ProtoMessages = "messages" ProtoResponses = "responses" ) // trimBody 去掉首尾空白。部分上游(如 OpenRouter)会在 JSON 前输出空白或 // SSE 注释行再跟正文,直接 Unmarshal 会失败。 func trimBody(body []byte) []byte { return bytes.TrimSpace(body) } // CleanJSON 剥离非 JSON 前缀(空白、SSE 注释、`data:` 行)并压缩为标准 JSON。 // 部分上游(如 OpenRouter)的 non-stream 响应在 JSON 前夹带空白/注释; // 原样透传会让客户端解析失败。找不到 JSON 对象时原样返回。 func CleanJSON(body []byte) []byte { i := bytes.IndexByte(body, '{') if i < 0 { return body } var v any if err := json.Unmarshal(bytes.TrimSpace(body[i:]), &v); err != nil { return body } out, err := json.Marshal(v) if err != nil { return body } return out } // ConvertRequest 转换请求体。from==to 时原样返回。 func ConvertRequest(body []byte, from, to string) ([]byte, error) { if from == to { return body, nil } body = trimBody(body) 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 } body = trimBody(body) 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 _, p := range parts { if p == "" { continue } if out != "" { out += sep } out += p } 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 } // rawOrObject 把 RawMessage 解为 map;非对象返回空对象。 func rawOrObject(raw json.RawMessage) any { if len(raw) == 0 || string(raw) == "null" { return map[string]any{} } var m map[string]any if json.Unmarshal(raw, &m) == nil { return m } return map[string]any{} } // intOrNil 取指针值,nil 时返回默认值。 func intOrNil(p *int, def int) any { if p == nil { return def } return *p } // strField 取 any 中的字符串字段。 func strField(v any) string { if s, ok := v.(string); ok { return s } return "" }