mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 00:15:28 +08:00
128 lines
3.7 KiB
Go
128 lines
3.7 KiB
Go
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
|
|
}
|