Files
openteam/server/internal/proxy/convert/convert.go
T
SakurasanandClaude ec4de8d913 M0-M4: 推倒重来基线(基建+用户/密钥/核心代理+前端+管理后台+三协议互转)
- 后端 Go+Gin+GORM: 配置(OT_ env)/SQLite/Postgres 双驱动、用户体系(argon2id+JWT access/refresh)、
  API Key(sk- 48位, 仅存 SHA-256 哈希)
- 代理网关: /v1/chat/completions、/v1/responses、/v1/messages、/v1/models;错误按客户端协议返回
- 三协议互转(convert 包): Chat↔Messages↔Responses 请求/响应 + 流式 SSE 逐事件转换(直通优先)
- 用量计费: 异步批量记账、余额扣减、balance_logs、usage_daily 日聚合
- 管理 API: 用户/渠道 CRUD+测试+模型导入/模型定价+绑定/统计/系统配置
- 前端 Vue3+TS+Tailwind(taste-skill 设计 tokens): Landing/登录注册/控制台/管理后台,
  自建组件+Phosphor 图标+自建 SVG 趋势图, 已过 web-design-guidelines 复查
- mock 上游: OpenAI+Anthropic 双协议模拟(含流式)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-15 15:34:06 +08:00

158 lines
4.3 KiB
Go

// 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
}