| package main |
|
|
| import ( |
| "encoding/json" |
| "errors" |
| "strings" |
| ) |
|
|
| |
| |
| |
| |
| |
| func estimateTokens(text string) int { |
| if text == "" { |
| return 0 |
| } |
| |
| |
| estimated := (len(text) * 2) / 5 |
| if estimated == 0 && len(text) > 0 { |
| return 1 |
| } |
| return estimated |
| } |
|
|
| func extractTextFromClaudeContent(content json.RawMessage, role string) string { |
| var contentStr string |
| if err := json.Unmarshal(content, &contentStr); err == nil { |
| return contentStr |
| } |
| var contentBlocks []ClaudeContentBlock |
| if err := json.Unmarshal(content, &contentBlocks); err == nil { |
| var parts []string |
| for _, block := range contentBlocks { |
| if block.Type == "text" { |
| parts = append(parts, block.Text) |
| } |
| } |
| return strings.Join(parts, "\n") |
| } |
| return "" |
| } |
|
|
| |
| func estimateTokensFromClaudeRequest(claudeReq *ClaudeRequest) int { |
| var textBuilder strings.Builder |
| if len(claudeReq.System) > 0 { |
| textBuilder.WriteString(extractTextFromClaudeContent(claudeReq.System, "system")) |
| textBuilder.WriteString("\n") |
| } |
| for _, msg := range claudeReq.Messages { |
| textBuilder.WriteString(extractTextFromClaudeContent(msg.Content, msg.Role)) |
| textBuilder.WriteString("\n") |
| } |
| return estimateTokens(textBuilder.String()) |
| } |
|
|
| |
|
|
| func convertClaudeRequestToOpenAI(claudeReq *ClaudeRequest) (*OpenAIRequest, error) { |
| openAIMessages := []OpenAIMessage{} |
| if len(claudeReq.System) > 0 { |
| systemContent := extractTextFromClaudeContent(claudeReq.System, "system") |
| if systemContent != "" { |
| openAIMessages = append(openAIMessages, OpenAIMessage{Role: "system", Content: systemContent}) |
| } |
| } |
| for _, msg := range claudeReq.Messages { |
| if msg.Role != "user" && msg.Role != "assistant" { |
| continue |
| } |
| messageContent := extractTextFromClaudeContent(msg.Content, msg.Role) |
| if messageContent != "" || msg.Role == "assistant" { |
| openAIMessages = append(openAIMessages, OpenAIMessage{Role: msg.Role, Content: messageContent}) |
| } |
| } |
| if len(openAIMessages) == 0 { |
| return nil, errors.New("conversion resulted in no valid messages for OpenAI request") |
| } |
| openAIReq := &OpenAIRequest{Model: claudeReq.Model, Messages: openAIMessages, Stream: claudeReq.Stream, MaxTokens: claudeReq.MaxTokens, Temperature: claudeReq.Temperature, TopP: claudeReq.TopP, TopK: claudeReq.TopK, Stop: claudeReq.StopSequences} |
| if openAIReq.Model == "" { |
| openAIReq.Model = "gpt-3.5-turbo" |
| } |
| return openAIReq, nil |
| } |
|
|
|
|
| func mapOpenAIFinishReasonToClaude(openAIFinishReason *string) string { |
| if openAIFinishReason == nil { |
| return "end_turn" |
| } |
| reason := *openAIFinishReason |
| switch reason { |
| case "stop": |
| return "end_turn" |
| case "length": |
| return "max_tokens" |
| case "function_call", "tool_calls": |
| return "tool_use" |
| case "content_filter": |
| return "stop_sequence" |
| default: |
| return "end_turn" |
| } |
| } |
|
|
| func convertOpenAIResponseToClaude(openAIResp *OpenAIResponse, claudeRequestID string) (*ClaudeResponse, error) { |
| if len(openAIResp.Choices) == 0 { |
| return nil, errors.New("OpenAI response has no choices") |
| } |
| choice := openAIResp.Choices[0] |
| claudeStopReason := mapOpenAIFinishReasonToClaude(choice.FinishReason) |
| claudeUsage := ClaudeUsage{} |
| if openAIResp.Usage != nil { |
| claudeUsage.InputTokens = openAIResp.Usage.PromptTokens |
| claudeUsage.OutputTokens = openAIResp.Usage.CompletionTokens |
| } |
| content := strings.TrimSpace(choice.Message.Content) |
| claudeContent := []ClaudeContentBlock{{Type: "text", Text: content}} |
| claudeResp := &ClaudeResponse{ |
| ID: openAIResp.ID, |
| Type: "message", |
| Role: "assistant", |
| Content: claudeContent, |
| Model: openAIResp.Model, |
| StopReason: claudeStopReason, |
| StopSequence: nil, |
| Usage: claudeUsage, |
| } |
| if claudeResp.ID == "" { |
| claudeResp.ID = claudeRequestID |
| } |
| return claudeResp, nil |
| } |