| package translatorcliproxy |
|
|
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "strings" |
|
|
| sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" |
| _ "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator/builtin" |
| ) |
|
|
| func ToOpenAI(from sdktranslator.Format, model string, raw []byte, stream bool) []byte { |
| return sdktranslator.TranslateRequest(from, sdktranslator.FormatOpenAI, model, raw, stream) |
| } |
|
|
| func FromOpenAINonStream(to sdktranslator.Format, model string, originalReq, translatedReq, raw []byte) []byte { |
| var param any |
| converted := sdktranslator.TranslateNonStream(context.Background(), sdktranslator.FormatOpenAI, to, model, originalReq, translatedReq, raw, ¶m) |
| usage, ok := extractOpenAIUsageFromJSON(raw) |
| if !ok { |
| return converted |
| } |
| return injectNonStreamUsageMetadata(converted, to, usage) |
| } |
|
|
| func FromOpenAIStream(to sdktranslator.Format, model string, originalReq, translatedReq, streamBody []byte) []byte { |
| var out bytes.Buffer |
| var param any |
| for _, line := range bytes.Split(streamBody, []byte("\n")) { |
| trimmed := strings.TrimSpace(string(line)) |
| if trimmed == "" { |
| continue |
| } |
| payload := append([]byte(nil), line...) |
| if !bytes.HasPrefix(payload, []byte("data:")) { |
| continue |
| } |
| chunks := sdktranslator.TranslateStream(context.Background(), sdktranslator.FormatOpenAI, to, model, originalReq, translatedReq, payload, ¶m) |
| for i := range chunks { |
| out.Write(chunks[i]) |
| if !bytes.HasSuffix(chunks[i], []byte("\n")) { |
| out.WriteByte('\n') |
| } |
| } |
| } |
| return out.Bytes() |
| } |
|
|
| func ParseFormat(name string) sdktranslator.Format { |
| switch strings.ToLower(strings.TrimSpace(name)) { |
| case "openai", "openai-chat", "chat", "chat-completions": |
| return sdktranslator.FormatOpenAI |
| case "openai-response", "responses", "openai-responses": |
| return sdktranslator.FormatOpenAIResponse |
| case "claude", "anthropic": |
| return sdktranslator.FormatClaude |
| case "gemini", "google": |
| return sdktranslator.FormatGemini |
| case "gemini-cli", "geminicli": |
| return sdktranslator.FormatGeminiCLI |
| case "codex", "openai-codex": |
| return sdktranslator.FormatCodex |
| case "antigravity": |
| return sdktranslator.FormatAntigravity |
| default: |
| return sdktranslator.FromString(name) |
| } |
| } |
|
|
| func ToOpenAIByName(formatName, model string, raw []byte, stream bool) []byte { |
| return ToOpenAI(ParseFormat(formatName), model, raw, stream) |
| } |
|
|
| func extractOpenAIUsageFromJSON(raw []byte) (openAIUsage, bool) { |
| payload := map[string]any{} |
| if err := json.Unmarshal(raw, &payload); err != nil { |
| return openAIUsage{}, false |
| } |
| usageObj, _ := payload["usage"].(map[string]any) |
| if usageObj == nil { |
| return openAIUsage{}, false |
| } |
| p := toInt(usageObj["prompt_tokens"]) |
| c := toInt(usageObj["completion_tokens"]) |
| t := toInt(usageObj["total_tokens"]) |
| if p <= 0 { |
| p = toInt(usageObj["input_tokens"]) |
| } |
| if c <= 0 { |
| c = toInt(usageObj["output_tokens"]) |
| } |
| if t <= 0 { |
| t = p + c |
| } |
| if p <= 0 && c <= 0 && t <= 0 { |
| return openAIUsage{}, false |
| } |
| return openAIUsage{PromptTokens: p, CompletionTokens: c, TotalTokens: t}, true |
| } |
|
|
| func injectNonStreamUsageMetadata(converted []byte, target sdktranslator.Format, usage openAIUsage) []byte { |
| obj := map[string]any{} |
| if err := json.Unmarshal(converted, &obj); err != nil { |
| return converted |
| } |
| switch target { |
| case sdktranslator.FormatClaude: |
| obj["usage"] = map[string]any{ |
| "input_tokens": usage.PromptTokens, |
| "output_tokens": usage.CompletionTokens, |
| } |
| case sdktranslator.FormatGemini: |
| obj["usageMetadata"] = map[string]any{ |
| "promptTokenCount": usage.PromptTokens, |
| "candidatesTokenCount": usage.CompletionTokens, |
| "totalTokenCount": usage.TotalTokens, |
| } |
| default: |
| return converted |
| } |
| out, err := json.Marshal(obj) |
| if err != nil { |
| return converted |
| } |
| return out |
| } |
|
|