fix: align claude codex translation

This commit is contained in:
sususu98
2026-04-29 18:47:03 +08:00
parent 2ea8f77efb
commit 1c0c426b85
4 changed files with 454 additions and 36 deletions
@@ -40,6 +40,7 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
template := []byte(`{"model":"","instructions":"","input":[]}`)
rootResult := gjson.ParseBytes(rawJSON)
toolNameMap := buildReverseMapFromClaudeOriginalToShort(rawJSON)
template, _ = sjson.SetBytes(template, "model", modelName)
// Process system messages and convert them to input content format.
@@ -174,8 +175,7 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
functionCallMessage, _ = sjson.SetBytes(functionCallMessage, "call_id", messageContentResult.Get("id").String())
{
name := messageContentResult.Get("name").String()
toolMap := buildReverseMapFromClaudeOriginalToShort(rawJSON)
if short, ok := toolMap[name]; ok {
if short, ok := toolNameMap[name]; ok {
name = short
} else {
name = shortenNameIfNeeded(name)
@@ -249,23 +249,14 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
toolsResult := rootResult.Get("tools")
if toolsResult.IsArray() {
template, _ = sjson.SetRawBytes(template, "tools", []byte(`[]`))
template, _ = sjson.SetBytes(template, "tool_choice", `auto`)
webSearchToolNames := buildClaudeWebSearchToolNameSet(toolsResult)
template, _ = sjson.SetRawBytes(template, "tool_choice", convertClaudeToolChoiceToCodex(rootResult.Get("tool_choice"), toolNameMap, webSearchToolNames))
toolResults := toolsResult.Array()
// Build short name map from declared tools
var names []string
for i := 0; i < len(toolResults); i++ {
n := toolResults[i].Get("name").String()
if n != "" {
names = append(names, n)
}
}
shortMap := buildShortNameMap(names)
for i := 0; i < len(toolResults); i++ {
toolResult := toolResults[i]
// Special handling: map Claude web search tool to Codex web_search
if toolResult.Get("type").String() == "web_search_20250305" {
// Replace the tool content entirely with {"type":"web_search"}
template, _ = sjson.SetRawBytes(template, "tools.-1", []byte(`{"type":"web_search"}`))
if isClaudeWebSearchToolType(toolResult.Get("type").String()) {
template, _ = sjson.SetRawBytes(template, "tools.-1", convertClaudeWebSearchToolToCodex(toolResult))
continue
}
tool := []byte(toolResult.Raw)
@@ -273,7 +264,7 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
// Apply shortened name if needed
if v := toolResult.Get("name"); v.Exists() {
name := v.String()
if short, ok := shortMap[name]; ok {
if short, ok := toolNameMap[name]; ok {
name = short
} else {
name = shortenNameIfNeeded(name)
@@ -370,6 +361,83 @@ func isFernetLikeReasoningSignature(signature string) bool {
return ciphertextLen > 0 && ciphertextLen%aesBlockSize == 0
}
func isClaudeWebSearchToolType(toolType string) bool {
return toolType == "web_search_20250305" || toolType == "web_search_20260209"
}
func buildClaudeWebSearchToolNameSet(tools gjson.Result) map[string]struct{} {
names := map[string]struct{}{}
if !tools.IsArray() {
return names
}
tools.ForEach(func(_, tool gjson.Result) bool {
toolType := tool.Get("type").String()
if !isClaudeWebSearchToolType(toolType) {
return true
}
names["web_search"] = struct{}{}
names[toolType] = struct{}{}
if name := tool.Get("name").String(); name != "" {
names[name] = struct{}{}
}
return true
})
return names
}
func convertClaudeToolChoiceToCodex(toolChoice gjson.Result, toolNameMap map[string]string, webSearchToolNames map[string]struct{}) []byte {
if !toolChoice.Exists() || toolChoice.Type == gjson.Null {
return []byte(`"auto"`)
}
choiceType := toolChoice.Get("type").String()
if choiceType == "" && toolChoice.Type == gjson.String {
choiceType = toolChoice.String()
}
switch choiceType {
case "auto", "":
return []byte(`"auto"`)
case "any":
return []byte(`"required"`)
case "none":
return []byte(`"none"`)
case "tool":
name := toolChoice.Get("name").String()
if _, ok := webSearchToolNames[name]; ok {
return []byte(`{"type":"web_search"}`)
}
if short, ok := toolNameMap[name]; ok {
name = short
} else {
name = shortenNameIfNeeded(name)
}
if name == "" {
return []byte(`"auto"`)
}
choice := []byte(`{"type":"function","name":""}`)
choice, _ = sjson.SetBytes(choice, "name", name)
return choice
default:
return []byte(`"auto"`)
}
}
func convertClaudeWebSearchToolToCodex(tool gjson.Result) []byte {
out := []byte(`{"type":"web_search"}`)
if allowedDomains := tool.Get("allowed_domains"); allowedDomains.Exists() && allowedDomains.IsArray() {
out, _ = sjson.SetRawBytes(out, "filters.allowed_domains", []byte(allowedDomains.Raw))
}
if userLocation := tool.Get("user_location"); userLocation.Exists() && userLocation.IsObject() {
out, _ = sjson.SetRawBytes(out, "user_location", []byte(userLocation.Raw))
}
return out
}
// shortenNameIfNeeded applies a simple shortening rule for a single name.
func shortenNameIfNeeded(name string) string {
const limit = 64