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 openai provides request translation functionality for OpenAI to Anthropic API.
// It handles parsing and transforming OpenAI Chat Completions API requests into Anthropic API format,
// Package openai provides request translation functionality for OpenAI to Claude Code API compatibility.
// It handles parsing and transforming OpenAI Chat Completions 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 OpenAI API format and Anthropic API's expected format.
// between OpenAI API format and Claude Code API's expected format.
package openai
import (
@@ -15,20 +15,35 @@ import (
"github.com/tidwall/sjson"
)
// ConvertOpenAIRequestToAnthropic parses and transforms an OpenAI Chat Completions API request into Anthropic API format.
// ConvertOpenAIRequestToClaude parses and transforms an OpenAI Chat Completions 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 ConvertOpenAIRequestToAnthropic(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 parameter extraction (max_tokens, temperature, top_p, etc.)
// 2. Message content conversion from OpenAI to Claude Code format
// 3. Tool call and tool result handling with proper ID mapping
// 4. Image data conversion from OpenAI data URLs to Claude Code base64 format
// 5. Stop sequence and streaming configuration handling
//
// Parameters:
// - modelName: The name of the model to use for the request
// - rawJSON: The raw JSON request data from the OpenAI 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 ConvertOpenAIRequestToClaude(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()])
@@ -36,28 +51,25 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
return "toolu_" + b.String()
}
// Model mapping
if model := root.Get("model"); model.Exists() {
modelStr := model.String()
out, _ = sjson.Set(out, "model", modelStr)
}
// Model mapping to specify which Claude Code model to use
out, _ = sjson.Set(out, "model", modelName)
// Max tokens
// Max tokens configuration with fallback to default value
if maxTokens := root.Get("max_tokens"); maxTokens.Exists() {
out, _ = sjson.Set(out, "max_tokens", maxTokens.Int())
}
// Temperature
// Temperature setting for controlling response randomness
if temp := root.Get("temperature"); temp.Exists() {
out, _ = sjson.Set(out, "temperature", temp.Float())
}
// Top P
// Top P setting for nucleus sampling
if topP := root.Get("top_p"); topP.Exists() {
out, _ = sjson.Set(out, "top_p", topP.Float())
}
// Stop sequences
// Stop sequences configuration for custom termination conditions
if stop := root.Get("stop"); stop.Exists() {
if stop.IsArray() {
var stopSequences []string
@@ -73,12 +85,10 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
}
}
// Stream
if stream := root.Get("stream"); stream.Exists() {
out, _ = sjson.Set(out, "stream", stream.Bool())
}
// Stream configuration to enable or disable streaming responses
out, _ = sjson.Set(out, "stream", stream)
// Process messages
// Process messages and transform them to Claude Code format
var anthropicMessages []interface{}
var toolCallIDs []string // Track tool call IDs for matching with tool results
@@ -89,7 +99,7 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
switch role {
case "system", "user", "assistant":
// Create Anthropic message
// Create Claude Code message with appropriate role mapping
if role == "system" {
role = "user"
}
@@ -99,9 +109,9 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
"content": []interface{}{},
}
// Handle content
// Handle content based on its type (string or array)
if contentResult.Exists() && contentResult.Type == gjson.String && contentResult.String() != "" {
// Simple text content
// Simple text content conversion
msg["content"] = []interface{}{
map[string]interface{}{
"type": "text",
@@ -109,23 +119,24 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
},
}
} else if contentResult.Exists() && contentResult.IsArray() {
// Array of content parts
// Array of content parts processing
var contentParts []interface{}
contentResult.ForEach(func(_, part gjson.Result) bool {
partType := part.Get("type").String()
switch partType {
case "text":
// Text part conversion
contentParts = append(contentParts, map[string]interface{}{
"type": "text",
"text": part.Get("text").String(),
})
case "image_url":
// Convert OpenAI image format to Anthropic format
// Convert OpenAI image format to Claude Code format
imageURL := part.Get("image_url.url").String()
if strings.HasPrefix(imageURL, "data:") {
// Extract base64 data and media type
// Extract base64 data and media type from data URL
parts := strings.Split(imageURL, ",")
if len(parts) == 2 {
mediaTypePart := strings.Split(parts[0], ";")[0]
@@ -177,7 +188,7 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
"name": function.Get("name").String(),
}
// Parse arguments
// Parse arguments for the tool call
if args := function.Get("arguments"); args.Exists() {
argsStr := args.String()
if argsStr != "" {
@@ -204,11 +215,11 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
anthropicMessages = append(anthropicMessages, msg)
case "tool":
// Handle tool result messages
// Handle tool result messages conversion
toolCallID := message.Get("tool_call_id").String()
content := message.Get("content").String()
// Create tool result message
// Create tool result message in Claude Code format
msg := map[string]interface{}{
"role": "user",
"content": []interface{}{
@@ -226,13 +237,13 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
})
}
// Set messages
// Set messages in the output template
if len(anthropicMessages) > 0 {
messagesJSON, _ := json.Marshal(anthropicMessages)
out, _ = sjson.SetRaw(out, "messages", string(messagesJSON))
}
// Tools mapping: OpenAI tools -> Anthropic tools
// Tools mapping: OpenAI tools -> Claude Code tools
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
var anthropicTools []interface{}
tools.ForEach(func(_, tool gjson.Result) bool {
@@ -243,9 +254,11 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
"description": function.Get("description").String(),
}
// Convert parameters schema
// Convert parameters schema for the tool
if parameters := function.Get("parameters"); parameters.Exists() {
anthropicTool["input_schema"] = parameters.Value()
} else if parameters = function.Get("parametersJsonSchema"); parameters.Exists() {
anthropicTool["input_schema"] = parameters.Value()
}
anthropicTools = append(anthropicTools, anthropicTool)
@@ -259,21 +272,21 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
}
}
// Tool choice mapping
// Tool choice mapping from OpenAI format to Claude Code format
if toolChoice := root.Get("tool_choice"); toolChoice.Exists() {
switch toolChoice.Type {
case gjson.String:
choice := toolChoice.String()
switch choice {
case "none":
// Don't set tool_choice, Anthropic will not use tools
// Don't set tool_choice, Claude Code will not use tools
case "auto":
out, _ = sjson.Set(out, "tool_choice", map[string]interface{}{"type": "auto"})
case "required":
out, _ = sjson.Set(out, "tool_choice", map[string]interface{}{"type": "any"})
}
case gjson.JSON:
// Specific tool choice
// Specific tool choice mapping
if toolChoice.Get("type").String() == "function" {
functionName := toolChoice.Get("function.name").String()
out, _ = sjson.Set(out, "tool_choice", map[string]interface{}{
@@ -285,5 +298,5 @@ func ConvertOpenAIRequestToAnthropic(rawJSON []byte) string {
}
}
return out
return []byte(out)
}