ccpoad / internal /protocol /types.go
anyalerob's picture
Upload folder using huggingface_hub
2986042 verified
Raw
History Blame Contribute Delete
7.46 kB
package protocol
import (
"context"
"fmt"
"slices"
"strings"
)
// Protocol identifies a client-facing or upstream request/response protocol.
type Protocol string
const (
// Anthropic is the Anthropic Messages protocol surface.
Anthropic Protocol = "anthropic"
// Codex is the Codex Responses protocol surface.
Codex Protocol = "codex"
// OpenAI is the OpenAI-compatible protocol surface.
OpenAI Protocol = "openai"
// Gemini is the Gemini generateContent protocol surface.
Gemini Protocol = "gemini"
)
// RequestFamily identifies the client request surface that is being transformed.
type RequestFamily string
// RequestFamily values enumerate the supported client request surfaces.
const (
RequestFamilyUnknown RequestFamily = ""
RequestFamilyChatCompletions RequestFamily = "chat_completions"
RequestFamilyResponses RequestFamily = "responses"
RequestFamilyMessages RequestFamily = "messages"
RequestFamilyGenerateContent RequestFamily = "generate_content"
RequestFamilyCompletions RequestFamily = "completions"
RequestFamilyEmbeddings RequestFamily = "embeddings"
RequestFamilyImages RequestFamily = "images"
)
// TransformPlan captures the chosen transform metadata for one proxy attempt.
//
// TODO(perf): OriginalBody 与 TranslatedBody 同时持有完整请求体,长流式请求或大上下文场景下
// 会让 plan 的内存峰值翻倍。后续可以分阶段释放:请求阶段结束后清空 OriginalBody,仅保留
// TranslatedBody 给响应阶段使用,或反之。当前调用链跨多个 goroutine(forward / writer / debug 捕获)
// 共享 plan 指针,简单清空可能引入悬挂引用,需先收敛所有读点再做改造。
type TransformPlan struct {
ClientProtocol Protocol
UpstreamProtocol Protocol
RequestFamily RequestFamily
OriginalPath string
UpstreamPath string
OriginalBody []byte
TranslatedBody []byte
OriginalModel string
ActualModel string
Streaming bool
NeedsTransform bool
}
var supportedTransformFamiliesByClientAndUpstream = map[Protocol]map[Protocol][]RequestFamily{
OpenAI: {
Gemini: {RequestFamilyChatCompletions},
Anthropic: {RequestFamilyChatCompletions},
Codex: {RequestFamilyChatCompletions},
},
Anthropic: {
OpenAI: {RequestFamilyMessages},
Gemini: {RequestFamilyMessages},
Codex: {RequestFamilyMessages},
},
Codex: {
OpenAI: {RequestFamilyResponses},
Gemini: {RequestFamilyResponses},
Anthropic: {RequestFamilyResponses},
},
Gemini: {
OpenAI: {RequestFamilyGenerateContent},
Anthropic: {RequestFamilyGenerateContent},
Codex: {RequestFamilyGenerateContent},
},
}
// SupportedClientProtocolsForUpstream returns the documented client-facing protocols
// that can be translated into the given upstream protocol.
func SupportedClientProtocolsForUpstream(upstream Protocol) []Protocol {
supported := make([]Protocol, 0, len(supportedTransformFamiliesByClientAndUpstream))
for client, upstreams := range supportedTransformFamiliesByClientAndUpstream {
if len(upstreams[upstream]) == 0 {
continue
}
supported = append(supported, client)
}
if len(supported) == 0 {
return nil
}
slices.Sort(supported)
return supported
}
// SupportsTransform reports whether the runtime has a documented transform path for
// the given client/upstream protocol pair.
func SupportsTransform(client, upstream Protocol) bool {
return len(supportedTransformFamiliesByClientAndUpstream[client][upstream]) > 0
}
// SupportsTransformFamily reports whether the runtime has a documented transform path for
// the given client/upstream protocol pair on the current request family.
func SupportsTransformFamily(client, upstream Protocol, family RequestFamily) bool {
for _, supportedFamily := range supportedTransformFamiliesByClientAndUpstream[client][upstream] {
if supportedFamily == family {
return true
}
}
return false
}
func matchesCanonicalEndpoint(path, endpoint string) bool {
idx := strings.Index(path, endpoint)
if idx < 0 {
return false
}
after := idx + len(endpoint)
return after == len(path) || path[after] == '?' || path[after] == '/'
}
// DetectRequestFamily infers the client request surface from the request path.
func DetectRequestFamily(path string) RequestFamily {
path = strings.TrimSpace(path)
switch {
case matchesCanonicalEndpoint(path, "/v1/chat/completions"):
return RequestFamilyChatCompletions
case matchesCanonicalEndpoint(path, "/v1/responses"):
return RequestFamilyResponses
case matchesCanonicalEndpoint(path, "/v1/messages"):
return RequestFamilyMessages
case strings.Contains(path, ":generateContent"), strings.Contains(path, ":streamGenerateContent"):
return RequestFamilyGenerateContent
case matchesCanonicalEndpoint(path, "/v1/completions"):
return RequestFamilyCompletions
case matchesCanonicalEndpoint(path, "/v1/embeddings"):
return RequestFamilyEmbeddings
case strings.Contains(path, "/v1/images/"):
return RequestFamilyImages
default:
return RequestFamilyUnknown
}
}
// BuildTransformPlan turns request metadata into a concrete runtime plan that can
// travel through request preparation, forwarding, and response translation.
func BuildTransformPlan(client, upstream Protocol, originalPath, upstreamPath string, originalBody, preparedBody []byte, originalModel, actualModel string, streaming bool) (TransformPlan, error) {
plan := TransformPlan{
ClientProtocol: client,
UpstreamProtocol: upstream,
RequestFamily: DetectRequestFamily(originalPath),
OriginalPath: originalPath,
UpstreamPath: upstreamPath,
OriginalBody: originalBody,
TranslatedBody: preparedBody,
OriginalModel: originalModel,
ActualModel: actualModel,
Streaming: streaming,
}
if plan.UpstreamPath == "" {
plan.UpstreamPath = plan.OriginalPath
}
if plan.TranslatedBody == nil {
plan.TranslatedBody = plan.OriginalBody
}
if plan.ActualModel == "" {
plan.ActualModel = plan.OriginalModel
}
if client == "" || upstream == "" || client == upstream {
return plan, nil
}
if !SupportsTransformFamily(client, upstream, plan.RequestFamily) {
return TransformPlan{}, fmt.Errorf("unsupported protocol transform: %s -> %s", client, upstream)
}
plan.NeedsTransform = true
return plan, nil
}
// RequestModel returns the model name that should be sent upstream.
func (p TransformPlan) RequestModel() string {
if p.ActualModel != "" {
return p.ActualModel
}
return p.OriginalModel
}
// ResponseModel returns the client-visible model name to use in translated
// responses so redirects remain transparent to callers.
func (p TransformPlan) ResponseModel() string {
if p.OriginalModel != "" {
return p.OriginalModel
}
return p.ActualModel
}
// RequestTransform rewrites one client request body into the upstream protocol shape.
type RequestTransform func(model string, rawJSON []byte, stream bool) ([]byte, error)
// ResponseStreamTransform rewrites one upstream streaming event into client-facing chunks.
type ResponseStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) ([][]byte, error)
// ResponseNonStreamTransform rewrites one upstream non-stream response into the client-facing shape.
type ResponseNonStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte) ([]byte, error)