Refactor codebase

This commit is contained in:
Luis Pater
2025-08-22 01:31:12 +08:00
parent 2b1762be16
commit 8c555c4e69
109 changed files with 7319 additions and 5735 deletions

View File

@@ -1,8 +1,8 @@
// Package gemini provides request translation functionality for Gemini to Anthropic API.
// It handles parsing and transforming Gemini API requests into Anthropic API format,
// Package gemini provides request translation functionality for Gemini to Claude Code API compatibility.
// It handles parsing and transforming Gemini API requests into Claude Code API format,
// extracting model information, system instructions, message contents, and tool declarations.
// The package performs JSON data transformation to ensure compatibility
// between Gemini API format and Anthropic API's expected format.
// between Gemini API format and Claude Code API's expected format.
package gemini
import (
@@ -16,20 +16,36 @@ import (
"github.com/tidwall/sjson"
)
// ConvertGeminiRequestToAnthropic parses and transforms a Gemini API request into Anthropic API format.
// ConvertGeminiRequestToClaude parses and transforms a Gemini API request into Claude Code API format.
// It extracts the model name, system instruction, message contents, and tool declarations
// from the raw JSON request and returns them in the format expected by the Anthropic API.
func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
// Base Anthropic API template
// from the raw JSON request and returns them in the format expected by the Claude Code API.
// The function performs comprehensive transformation including:
// 1. Model name mapping and generation configuration extraction
// 2. System instruction conversion to Claude Code format
// 3. Message content conversion with proper role mapping
// 4. Tool call and tool result handling with FIFO queue for ID matching
// 5. Image and file data conversion to Claude Code base64 format
// 6. Tool declaration and tool choice configuration mapping
//
// Parameters:
// - modelName: The name of the model to use for the request
// - rawJSON: The raw JSON request data from the Gemini API
// - stream: A boolean indicating if the request is for a streaming response
//
// Returns:
// - []byte: The transformed request data in Claude Code API format
func ConvertGeminiRequestToClaude(modelName string, rawJSON []byte, stream bool) []byte {
// Base Claude Code API template with default max_tokens value
out := `{"model":"","max_tokens":32000,"messages":[]}`
root := gjson.ParseBytes(rawJSON)
// Helper for generating tool call IDs in the form: toolu_<alphanum>
// This ensures unique identifiers for tool calls in the Claude Code format
genToolCallID := func() string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var b strings.Builder
// 24 chars random suffix
// 24 chars random suffix for uniqueness
for i := 0; i < 24; i++ {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
b.WriteByte(letters[n.Int64()])
@@ -43,23 +59,24 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
// consume them in order when functionResponses arrive.
var pendingToolIDs []string
// Model mapping
if v := root.Get("model"); v.Exists() {
modelName := v.String()
out, _ = sjson.Set(out, "model", modelName)
}
// Model mapping to specify which Claude Code model to use
out, _ = sjson.Set(out, "model", modelName)
// Generation config
// Generation config extraction from Gemini format
if genConfig := root.Get("generationConfig"); genConfig.Exists() {
// Max output tokens configuration
if maxTokens := genConfig.Get("maxOutputTokens"); maxTokens.Exists() {
out, _ = sjson.Set(out, "max_tokens", maxTokens.Int())
}
// Temperature setting for controlling response randomness
if temp := genConfig.Get("temperature"); temp.Exists() {
out, _ = sjson.Set(out, "temperature", temp.Float())
}
// Top P setting for nucleus sampling
if topP := genConfig.Get("topP"); topP.Exists() {
out, _ = sjson.Set(out, "top_p", topP.Float())
}
// Stop sequences configuration for custom termination conditions
if stopSeqs := genConfig.Get("stopSequences"); stopSeqs.Exists() && stopSeqs.IsArray() {
var stopSequences []string
stopSeqs.ForEach(func(_, value gjson.Result) bool {
@@ -72,7 +89,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
}
}
// System instruction -> system field
// System instruction conversion to Claude Code format
if sysInstr := root.Get("system_instruction"); sysInstr.Exists() {
if parts := sysInstr.Get("parts"); parts.Exists() && parts.IsArray() {
var systemText strings.Builder
@@ -86,6 +103,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
return true
})
if systemText.Len() > 0 {
// Create system message in Claude Code format
systemMessage := `{"role":"user","content":[{"type":"text","text":""}]}`
systemMessage, _ = sjson.Set(systemMessage, "content.0.text", systemText.String())
out, _ = sjson.SetRaw(out, "messages.-1", systemMessage)
@@ -93,10 +111,11 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
}
}
// Contents -> messages
// Contents conversion to messages with proper role mapping
if contents := root.Get("contents"); contents.Exists() && contents.IsArray() {
contents.ForEach(func(_, content gjson.Result) bool {
role := content.Get("role").String()
// Map Gemini roles to Claude Code roles
if role == "model" {
role = "assistant"
}
@@ -105,13 +124,17 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
role = "user"
}
// Create message
if role == "tool" {
role = "user"
}
// Create message structure in Claude Code format
msg := `{"role":"","content":[]}`
msg, _ = sjson.Set(msg, "role", role)
if parts := content.Get("parts"); parts.Exists() && parts.IsArray() {
parts.ForEach(func(_, part gjson.Result) bool {
// Text content
// Text content conversion
if text := part.Get("text"); text.Exists() {
textContent := `{"type":"text","text":""}`
textContent, _ = sjson.Set(textContent, "text", text.String())
@@ -119,7 +142,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
return true
}
// Function call (from model/assistant)
// Function call (from model/assistant) conversion to tool use
if fc := part.Get("functionCall"); fc.Exists() && role == "assistant" {
toolUse := `{"type":"tool_use","id":"","name":"","input":{}}`
@@ -139,7 +162,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
return true
}
// Function response (from user)
// Function response (from user) conversion to tool result
if fr := part.Get("functionResponse"); fr.Exists() {
toolResult := `{"type":"tool_result","tool_use_id":"","content":""}`
@@ -156,7 +179,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
}
toolResult, _ = sjson.Set(toolResult, "tool_use_id", toolID)
// Extract result content
// Extract result content from the function response
if result := fr.Get("response.result"); result.Exists() {
toolResult, _ = sjson.Set(toolResult, "content", result.String())
} else if response := fr.Get("response"); response.Exists() {
@@ -166,7 +189,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
return true
}
// Image content (inline_data)
// Image content (inline_data) conversion to Claude Code format
if inlineData := part.Get("inline_data"); inlineData.Exists() {
imageContent := `{"type":"image","source":{"type":"base64","media_type":"","data":""}}`
if mimeType := inlineData.Get("mime_type"); mimeType.Exists() {
@@ -179,7 +202,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
return true
}
// File data
// File data conversion to text content with file info
if fileData := part.Get("file_data"); fileData.Exists() {
// For file data, we'll convert to text content with file info
textContent := `{"type":"text","text":""}`
@@ -205,14 +228,14 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
})
}
// Tools mapping: Gemini functionDeclarations -> Anthropic tools
// Tools mapping: Gemini functionDeclarations -> Claude Code tools
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
var anthropicTools []interface{}
tools.ForEach(func(_, tool gjson.Result) bool {
if funcDecls := tool.Get("functionDeclarations"); funcDecls.Exists() && funcDecls.IsArray() {
funcDecls.ForEach(func(_, funcDecl gjson.Result) bool {
anthropicTool := `"name":"","description":"","input_schema":{}}`
anthropicTool := `{"name":"","description":"","input_schema":{}}`
if name := funcDecl.Get("name"); name.Exists() {
anthropicTool, _ = sjson.Set(anthropicTool, "name", name.String())
@@ -221,13 +244,13 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
anthropicTool, _ = sjson.Set(anthropicTool, "description", desc.String())
}
if params := funcDecl.Get("parameters"); params.Exists() {
// Clean up the parameters schema
// Clean up the parameters schema for Claude Code compatibility
cleaned := params.Raw
cleaned, _ = sjson.Set(cleaned, "additionalProperties", false)
cleaned, _ = sjson.Set(cleaned, "$schema", "http://json-schema.org/draft-07/schema#")
anthropicTool, _ = sjson.SetRaw(anthropicTool, "input_schema", cleaned)
} else if params = funcDecl.Get("parametersJsonSchema"); params.Exists() {
// Clean up the parameters schema
// Clean up the parameters schema for Claude Code compatibility
cleaned := params.Raw
cleaned, _ = sjson.Set(cleaned, "additionalProperties", false)
cleaned, _ = sjson.Set(cleaned, "$schema", "http://json-schema.org/draft-07/schema#")
@@ -246,7 +269,7 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
}
}
// Tool config
// Tool config mapping from Gemini format to Claude Code format
if toolConfig := root.Get("tool_config"); toolConfig.Exists() {
if funcCalling := toolConfig.Get("function_calling_config"); funcCalling.Exists() {
if mode := funcCalling.Get("mode"); mode.Exists() {
@@ -262,13 +285,10 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
}
}
// Stream setting
if stream := root.Get("stream"); stream.Exists() {
out, _ = sjson.Set(out, "stream", stream.Bool())
} else {
out, _ = sjson.Set(out, "stream", false)
}
// Stream setting configuration
out, _ = sjson.Set(out, "stream", stream)
// Convert tool parameter types to lowercase for Claude Code compatibility
var pathsToLower []string
toolsResult := gjson.Get(out, "tools")
util.Walk(toolsResult, "", "type", &pathsToLower)
@@ -277,5 +297,5 @@ func ConvertGeminiRequestToAnthropic(rawJSON []byte) string {
out, _ = sjson.Set(out, fullPath, strings.ToLower(gjson.Get(out, fullPath).String()))
}
return out
return []byte(out)
}