feat(translator): improve Claude request handling with enhanced content processing

- Introduced helper functions (`appendTextContent`, `appendImageContent`, etc.) for structured content construction.
- Refactored message generation logic for better clarity, supporting mixed content scenarios (text, images, and function calls).
- Added `flushMessage` to ensure proper grouping of message contents.
This commit is contained in:
Luis Pater
2025-10-14 23:58:37 +08:00
parent 1cd275f4c1
commit c945e35983
2 changed files with 130 additions and 45 deletions

View File

@@ -68,36 +68,79 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
for i := 0; i < len(messageResults); i++ {
messageResult := messageResults[i]
messageRole := messageResult.Get("role").String()
newMessage := func() string {
msg := `{"type": "message","role":"","content":[]}`
msg, _ = sjson.Set(msg, "role", messageRole)
return msg
}
message := newMessage()
contentIndex := 0
hasContent := false
flushMessage := func() {
if hasContent {
template, _ = sjson.SetRaw(template, "input.-1", message)
message = newMessage()
contentIndex = 0
hasContent = false
}
}
appendTextContent := func(text string) {
partType := "input_text"
if messageRole == "assistant" {
partType = "output_text"
}
message, _ = sjson.Set(message, fmt.Sprintf("content.%d.type", contentIndex), partType)
message, _ = sjson.Set(message, fmt.Sprintf("content.%d.text", contentIndex), text)
contentIndex++
hasContent = true
}
appendImageContent := func(dataURL string) {
message, _ = sjson.Set(message, fmt.Sprintf("content.%d.type", contentIndex), "input_image")
message, _ = sjson.Set(message, fmt.Sprintf("content.%d.image_url", contentIndex), dataURL)
contentIndex++
hasContent = true
}
messageContentsResult := messageResult.Get("content")
if messageContentsResult.IsArray() {
messageContentResults := messageContentsResult.Array()
for j := 0; j < len(messageContentResults); j++ {
messageContentResult := messageContentResults[j]
messageContentTypeResult := messageContentResult.Get("type")
contentType := messageContentTypeResult.String()
contentType := messageContentResult.Get("type").String()
if contentType == "text" {
// Handle text content by creating appropriate message structure.
message := `{"type": "message","role":"","content":[]}`
messageRole := messageResult.Get("role").String()
message, _ = sjson.Set(message, "role", messageRole)
partType := "input_text"
if messageRole == "assistant" {
partType = "output_text"
switch contentType {
case "text":
appendTextContent(messageContentResult.Get("text").String())
case "image":
sourceResult := messageContentResult.Get("source")
if sourceResult.Exists() {
data := sourceResult.Get("data").String()
if data == "" {
data = sourceResult.Get("base64").String()
}
if data != "" {
mediaType := sourceResult.Get("media_type").String()
if mediaType == "" {
mediaType = sourceResult.Get("mime_type").String()
}
if mediaType == "" {
mediaType = "application/octet-stream"
}
dataURL := fmt.Sprintf("data:%s;base64,%s", mediaType, data)
appendImageContent(dataURL)
}
}
currentIndex := len(gjson.Get(message, "content").Array())
message, _ = sjson.Set(message, fmt.Sprintf("content.%d.type", currentIndex), partType)
message, _ = sjson.Set(message, fmt.Sprintf("content.%d.text", currentIndex), messageContentResult.Get("text").String())
template, _ = sjson.SetRaw(template, "input.-1", message)
} else if contentType == "tool_use" {
// Handle tool use content by creating function call message.
case "tool_use":
flushMessage()
functionCallMessage := `{"type":"function_call"}`
functionCallMessage, _ = sjson.Set(functionCallMessage, "call_id", messageContentResult.Get("id").String())
{
// Shorten tool name if needed based on declared tools
name := messageContentResult.Get("name").String()
toolMap := buildReverseMapFromClaudeOriginalToShort(rawJSON)
if short, ok := toolMap[name]; ok {
@@ -109,28 +152,18 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
}
functionCallMessage, _ = sjson.Set(functionCallMessage, "arguments", messageContentResult.Get("input").Raw)
template, _ = sjson.SetRaw(template, "input.-1", functionCallMessage)
} else if contentType == "tool_result" {
// Handle tool result content by creating function call output message.
case "tool_result":
flushMessage()
functionCallOutputMessage := `{"type":"function_call_output"}`
functionCallOutputMessage, _ = sjson.Set(functionCallOutputMessage, "call_id", messageContentResult.Get("tool_use_id").String())
functionCallOutputMessage, _ = sjson.Set(functionCallOutputMessage, "output", messageContentResult.Get("content").String())
template, _ = sjson.SetRaw(template, "input.-1", functionCallOutputMessage)
}
}
flushMessage()
} else if messageContentsResult.Type == gjson.String {
// Handle string content by creating appropriate message structure.
message := `{"type": "message","role":"","content":[]}`
messageRole := messageResult.Get("role").String()
message, _ = sjson.Set(message, "role", messageRole)
partType := "input_text"
if messageRole == "assistant" {
partType = "output_text"
}
message, _ = sjson.Set(message, "content.0.type", partType)
message, _ = sjson.Set(message, "content.0.text", messageContentsResult.String())
template, _ = sjson.SetRaw(template, "input.-1", message)
appendTextContent(messageContentsResult.String())
flushMessage()
}
}