Compare commits

...

2 Commits

Author SHA1 Message Date
Luis Pater
735b21394c Fixed: #137
refactor: simplify ConvertCodexResponseToClaudeNonStream by removing bufio.Scanner usage and restructuring response parsing logic
2025-10-18 06:22:42 +08:00
Luis Pater
9cdef937af fix: initialize contentBlocks with an empty slice and improve content handling in ConvertOpenAIResponseToClaudeNonStream 2025-10-17 08:47:09 +08:00
2 changed files with 199 additions and 206 deletions

View File

@@ -7,7 +7,6 @@
package claude package claude
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
@@ -180,56 +179,58 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa
// Returns: // Returns:
// - string: A Claude Code-compatible JSON response containing all message content and metadata // - string: A Claude Code-compatible JSON response containing all message content and metadata
func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, _ []byte, rawJSON []byte, _ *any) string { func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, _ []byte, rawJSON []byte, _ *any) string {
scanner := bufio.NewScanner(bytes.NewReader(rawJSON))
buffer := make([]byte, 20_971_520)
scanner.Buffer(buffer, 20_971_520)
revNames := buildReverseMapFromClaudeOriginalShortToOriginal(originalRequestRawJSON) revNames := buildReverseMapFromClaudeOriginalShortToOriginal(originalRequestRawJSON)
for scanner.Scan() { rootResult := gjson.ParseBytes(rawJSON)
line := scanner.Bytes() if rootResult.Get("type").String() != "response.completed" {
if !bytes.HasPrefix(line, dataTag) { return ""
continue }
}
payload := bytes.TrimSpace(line[len(dataTag):])
if len(payload) == 0 {
continue
}
rootResult := gjson.ParseBytes(payload) responseData := rootResult.Get("response")
if rootResult.Get("type").String() != "response.completed" { if !responseData.Exists() {
continue return ""
} }
responseData := rootResult.Get("response") response := map[string]interface{}{
if !responseData.Exists() { "id": responseData.Get("id").String(),
continue "type": "message",
} "role": "assistant",
"model": responseData.Get("model").String(),
"content": []interface{}{},
"stop_reason": nil,
"stop_sequence": nil,
"usage": map[string]interface{}{
"input_tokens": responseData.Get("usage.input_tokens").Int(),
"output_tokens": responseData.Get("usage.output_tokens").Int(),
},
}
response := map[string]interface{}{ var contentBlocks []interface{}
"id": responseData.Get("id").String(), hasToolCall := false
"type": "message",
"role": "assistant",
"model": responseData.Get("model").String(),
"content": []interface{}{},
"stop_reason": nil,
"stop_sequence": nil,
"usage": map[string]interface{}{
"input_tokens": responseData.Get("usage.input_tokens").Int(),
"output_tokens": responseData.Get("usage.output_tokens").Int(),
},
}
var contentBlocks []interface{} if output := responseData.Get("output"); output.Exists() && output.IsArray() {
hasToolCall := false output.ForEach(func(_, item gjson.Result) bool {
switch item.Get("type").String() {
if output := responseData.Get("output"); output.Exists() && output.IsArray() { case "reasoning":
output.ForEach(func(_, item gjson.Result) bool { thinkingBuilder := strings.Builder{}
switch item.Get("type").String() { if summary := item.Get("summary"); summary.Exists() {
case "reasoning": if summary.IsArray() {
thinkingBuilder := strings.Builder{} summary.ForEach(func(_, part gjson.Result) bool {
if summary := item.Get("summary"); summary.Exists() { if txt := part.Get("text"); txt.Exists() {
if summary.IsArray() { thinkingBuilder.WriteString(txt.String())
summary.ForEach(func(_, part gjson.Result) bool { } else {
thinkingBuilder.WriteString(part.String())
}
return true
})
} else {
thinkingBuilder.WriteString(summary.String())
}
}
if thinkingBuilder.Len() == 0 {
if content := item.Get("content"); content.Exists() {
if content.IsArray() {
content.ForEach(func(_, part gjson.Result) bool {
if txt := part.Get("text"); txt.Exists() { if txt := part.Get("text"); txt.Exists() {
thinkingBuilder.WriteString(txt.String()) thinkingBuilder.WriteString(txt.String())
} else { } else {
@@ -238,114 +239,96 @@ func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, original
return true return true
}) })
} else { } else {
thinkingBuilder.WriteString(summary.String()) thinkingBuilder.WriteString(content.String())
} }
} }
if thinkingBuilder.Len() == 0 {
if content := item.Get("content"); content.Exists() {
if content.IsArray() {
content.ForEach(func(_, part gjson.Result) bool {
if txt := part.Get("text"); txt.Exists() {
thinkingBuilder.WriteString(txt.String())
} else {
thinkingBuilder.WriteString(part.String())
}
return true
})
} else {
thinkingBuilder.WriteString(content.String())
}
}
}
if thinkingBuilder.Len() > 0 {
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "thinking",
"thinking": thinkingBuilder.String(),
})
}
case "message":
if content := item.Get("content"); content.Exists() {
if content.IsArray() {
content.ForEach(func(_, part gjson.Result) bool {
if part.Get("type").String() == "output_text" {
text := part.Get("text").String()
if text != "" {
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": text,
})
}
}
return true
})
} else {
text := content.String()
if text != "" {
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": text,
})
}
}
}
case "function_call":
hasToolCall = true
name := item.Get("name").String()
if original, ok := revNames[name]; ok {
name = original
}
toolBlock := map[string]interface{}{
"type": "tool_use",
"id": item.Get("call_id").String(),
"name": name,
"input": map[string]interface{}{},
}
if argsStr := item.Get("arguments").String(); argsStr != "" {
var args interface{}
if err := json.Unmarshal([]byte(argsStr), &args); err == nil {
toolBlock["input"] = args
}
}
contentBlocks = append(contentBlocks, toolBlock)
} }
return true if thinkingBuilder.Len() > 0 {
}) contentBlocks = append(contentBlocks, map[string]interface{}{
} "type": "thinking",
"thinking": thinkingBuilder.String(),
})
}
case "message":
if content := item.Get("content"); content.Exists() {
if content.IsArray() {
content.ForEach(func(_, part gjson.Result) bool {
if part.Get("type").String() == "output_text" {
text := part.Get("text").String()
if text != "" {
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": text,
})
}
}
return true
})
} else {
text := content.String()
if text != "" {
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": text,
})
}
}
}
case "function_call":
hasToolCall = true
name := item.Get("name").String()
if original, ok := revNames[name]; ok {
name = original
}
if len(contentBlocks) > 0 { toolBlock := map[string]interface{}{
response["content"] = contentBlocks "type": "tool_use",
} "id": item.Get("call_id").String(),
"name": name,
"input": map[string]interface{}{},
}
if stopReason := responseData.Get("stop_reason"); stopReason.Exists() && stopReason.String() != "" { if argsStr := item.Get("arguments").String(); argsStr != "" {
response["stop_reason"] = stopReason.String() var args interface{}
} else if hasToolCall { if err := json.Unmarshal([]byte(argsStr), &args); err == nil {
response["stop_reason"] = "tool_use" toolBlock["input"] = args
} else { }
response["stop_reason"] = "end_turn" }
}
if stopSequence := responseData.Get("stop_sequence"); stopSequence.Exists() && stopSequence.String() != "" { contentBlocks = append(contentBlocks, toolBlock)
response["stop_sequence"] = stopSequence.Value()
}
if responseData.Get("usage.input_tokens").Exists() || responseData.Get("usage.output_tokens").Exists() {
response["usage"] = map[string]interface{}{
"input_tokens": responseData.Get("usage.input_tokens").Int(),
"output_tokens": responseData.Get("usage.output_tokens").Int(),
} }
} return true
})
responseJSON, err := json.Marshal(response)
if err != nil {
return ""
}
return string(responseJSON)
} }
return "" if len(contentBlocks) > 0 {
response["content"] = contentBlocks
}
if stopReason := responseData.Get("stop_reason"); stopReason.Exists() && stopReason.String() != "" {
response["stop_reason"] = stopReason.String()
} else if hasToolCall {
response["stop_reason"] = "tool_use"
} else {
response["stop_reason"] = "end_turn"
}
if stopSequence := responseData.Get("stop_sequence"); stopSequence.Exists() && stopSequence.String() != "" {
response["stop_sequence"] = stopSequence.Value()
}
if responseData.Get("usage.input_tokens").Exists() || responseData.Get("usage.output_tokens").Exists() {
response["usage"] = map[string]interface{}{
"input_tokens": responseData.Get("usage.input_tokens").Int(),
"output_tokens": responseData.Get("usage.output_tokens").Int(),
}
}
responseJSON, err := json.Marshal(response)
if err != nil {
return ""
}
return string(responseJSON)
} }
// buildReverseMapFromClaudeOriginalShortToOriginal builds a map[short]original from original Claude request tools. // buildReverseMapFromClaudeOriginalShortToOriginal builds a map[short]original from original Claude request tools.

View File

@@ -466,7 +466,7 @@ func ConvertOpenAIResponseToClaudeNonStream(_ context.Context, _ string, origina
}, },
} }
var contentBlocks []interface{} contentBlocks := make([]interface{}, 0)
hasToolCall := false hasToolCall := false
if choices := root.Get("choices"); choices.Exists() && choices.IsArray() && len(choices.Array()) > 0 { if choices := root.Get("choices"); choices.Exists() && choices.IsArray() && len(choices.Array()) > 0 {
@@ -477,80 +477,90 @@ func ConvertOpenAIResponseToClaudeNonStream(_ context.Context, _ string, origina
} }
if message := choice.Get("message"); message.Exists() { if message := choice.Get("message"); message.Exists() {
if contentArray := message.Get("content"); contentArray.Exists() && contentArray.IsArray() { if contentResult := message.Get("content"); contentResult.Exists() {
var textBuilder strings.Builder if contentResult.IsArray() {
var thinkingBuilder strings.Builder var textBuilder strings.Builder
var thinkingBuilder strings.Builder
flushText := func() { flushText := func() {
if textBuilder.Len() == 0 { if textBuilder.Len() == 0 {
return return
}
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": textBuilder.String(),
})
textBuilder.Reset()
} }
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": textBuilder.String(),
})
textBuilder.Reset()
}
flushThinking := func() { flushThinking := func() {
if thinkingBuilder.Len() == 0 { if thinkingBuilder.Len() == 0 {
return return
}
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "thinking",
"thinking": thinkingBuilder.String(),
})
thinkingBuilder.Reset()
} }
contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "thinking",
"thinking": thinkingBuilder.String(),
})
thinkingBuilder.Reset()
}
for _, item := range contentArray.Array() { for _, item := range contentResult.Array() {
typeStr := item.Get("type").String() typeStr := item.Get("type").String()
switch typeStr { switch typeStr {
case "text": case "text":
flushThinking() flushThinking()
textBuilder.WriteString(item.Get("text").String()) textBuilder.WriteString(item.Get("text").String())
case "tool_calls": case "tool_calls":
flushThinking() flushThinking()
flushText() flushText()
toolCalls := item.Get("tool_calls") toolCalls := item.Get("tool_calls")
if toolCalls.IsArray() { if toolCalls.IsArray() {
toolCalls.ForEach(func(_, tc gjson.Result) bool { toolCalls.ForEach(func(_, tc gjson.Result) bool {
hasToolCall = true hasToolCall = true
toolUse := map[string]interface{}{ toolUse := map[string]interface{}{
"type": "tool_use", "type": "tool_use",
"id": tc.Get("id").String(), "id": tc.Get("id").String(),
"name": tc.Get("function.name").String(), "name": tc.Get("function.name").String(),
} }
argsStr := util.FixJSON(tc.Get("function.arguments").String()) argsStr := util.FixJSON(tc.Get("function.arguments").String())
if argsStr != "" { if argsStr != "" {
var parsed interface{} var parsed interface{}
if err := json.Unmarshal([]byte(argsStr), &parsed); err == nil { if err := json.Unmarshal([]byte(argsStr), &parsed); err == nil {
toolUse["input"] = parsed toolUse["input"] = parsed
} else {
toolUse["input"] = map[string]interface{}{}
}
} else { } else {
toolUse["input"] = map[string]interface{}{} toolUse["input"] = map[string]interface{}{}
} }
} else {
toolUse["input"] = map[string]interface{}{}
}
contentBlocks = append(contentBlocks, toolUse) contentBlocks = append(contentBlocks, toolUse)
return true return true
}) })
}
case "reasoning":
flushText()
if thinking := item.Get("text"); thinking.Exists() {
thinkingBuilder.WriteString(thinking.String())
}
default:
flushThinking()
flushText()
} }
case "reasoning": }
flushText()
if thinking := item.Get("text"); thinking.Exists() { flushThinking()
thinkingBuilder.WriteString(thinking.String()) flushText()
} } else if contentResult.Type == gjson.String {
default: textContent := contentResult.String()
flushThinking() if textContent != "" {
flushText() contentBlocks = append(contentBlocks, map[string]interface{}{
"type": "text",
"text": textContent,
})
} }
} }
flushThinking()
flushText()
} }
if toolCalls := message.Get("tool_calls"); toolCalls.Exists() && toolCalls.IsArray() { if toolCalls := message.Get("tool_calls"); toolCalls.Exists() && toolCalls.IsArray() {