| package protocol |
|
|
| import ( |
| "context" |
| "fmt" |
| "slices" |
| "strings" |
| ) |
|
|
| |
| type Protocol string |
|
|
| const ( |
| |
| Anthropic Protocol = "anthropic" |
| |
| Codex Protocol = "codex" |
| |
| OpenAI Protocol = "openai" |
| |
| Gemini Protocol = "gemini" |
| ) |
|
|
| |
| type RequestFamily string |
|
|
| |
| 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" |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| 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}, |
| }, |
| } |
|
|
| |
| |
| 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 |
| } |
|
|
| |
| |
| func SupportsTransform(client, upstream Protocol) bool { |
| return len(supportedTransformFamiliesByClientAndUpstream[client][upstream]) > 0 |
| } |
|
|
| |
| |
| 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] == '/' |
| } |
|
|
| |
| 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 |
| } |
| } |
|
|
| |
| |
| 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 |
| } |
|
|
| |
| func (p TransformPlan) RequestModel() string { |
| if p.ActualModel != "" { |
| return p.ActualModel |
| } |
| return p.OriginalModel |
| } |
|
|
| |
| |
| func (p TransformPlan) ResponseModel() string { |
| if p.OriginalModel != "" { |
| return p.OriginalModel |
| } |
| return p.ActualModel |
| } |
|
|
| |
| type RequestTransform func(model string, rawJSON []byte, stream bool) ([]byte, error) |
|
|
| |
| type ResponseStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) ([][]byte, error) |
|
|
| |
| type ResponseNonStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte) ([]byte, error) |
|
|