Pass file input from /chat/completions and /responses to codex and claude
Browse files
internal/translator/claude/openai/chat-completions/claude_openai_request.go
CHANGED
|
@@ -199,6 +199,21 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream
|
|
| 199 |
msg, _ = sjson.SetRaw(msg, "content.-1", imagePart)
|
| 200 |
}
|
| 201 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
}
|
| 203 |
return true
|
| 204 |
})
|
|
|
|
| 199 |
msg, _ = sjson.SetRaw(msg, "content.-1", imagePart)
|
| 200 |
}
|
| 201 |
}
|
| 202 |
+
|
| 203 |
+
case "file":
|
| 204 |
+
fileData := part.Get("file.file_data").String()
|
| 205 |
+
if strings.HasPrefix(fileData, "data:") {
|
| 206 |
+
semicolonIdx := strings.Index(fileData, ";")
|
| 207 |
+
commaIdx := strings.Index(fileData, ",")
|
| 208 |
+
if semicolonIdx != -1 && commaIdx != -1 && commaIdx > semicolonIdx {
|
| 209 |
+
mediaType := strings.TrimPrefix(fileData[:semicolonIdx], "data:")
|
| 210 |
+
data := fileData[commaIdx+1:]
|
| 211 |
+
docPart := `{"type":"document","source":{"type":"base64","media_type":"","data":""}}`
|
| 212 |
+
docPart, _ = sjson.Set(docPart, "source.media_type", mediaType)
|
| 213 |
+
docPart, _ = sjson.Set(docPart, "source.data", data)
|
| 214 |
+
msg, _ = sjson.SetRaw(msg, "content.-1", docPart)
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
}
|
| 218 |
return true
|
| 219 |
})
|
internal/translator/claude/openai/responses/claude_openai-responses_request.go
CHANGED
|
@@ -155,6 +155,7 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
|
|
| 155 |
var textAggregate strings.Builder
|
| 156 |
var partsJSON []string
|
| 157 |
hasImage := false
|
|
|
|
| 158 |
if parts := item.Get("content"); parts.Exists() && parts.IsArray() {
|
| 159 |
parts.ForEach(func(_, part gjson.Result) bool {
|
| 160 |
ptype := part.Get("type").String()
|
|
@@ -207,6 +208,30 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
|
|
| 207 |
hasImage = true
|
| 208 |
}
|
| 209 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
}
|
| 211 |
return true
|
| 212 |
})
|
|
@@ -228,7 +253,7 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
|
|
| 228 |
if len(partsJSON) > 0 {
|
| 229 |
msg := `{"role":"","content":[]}`
|
| 230 |
msg, _ = sjson.Set(msg, "role", role)
|
| 231 |
-
if len(partsJSON) == 1 && !hasImage {
|
| 232 |
// Preserve legacy behavior for single text content
|
| 233 |
msg, _ = sjson.Delete(msg, "content")
|
| 234 |
textPart := gjson.Parse(partsJSON[0])
|
|
|
|
| 155 |
var textAggregate strings.Builder
|
| 156 |
var partsJSON []string
|
| 157 |
hasImage := false
|
| 158 |
+
hasFile := false
|
| 159 |
if parts := item.Get("content"); parts.Exists() && parts.IsArray() {
|
| 160 |
parts.ForEach(func(_, part gjson.Result) bool {
|
| 161 |
ptype := part.Get("type").String()
|
|
|
|
| 208 |
hasImage = true
|
| 209 |
}
|
| 210 |
}
|
| 211 |
+
case "input_file":
|
| 212 |
+
fileData := part.Get("file_data").String()
|
| 213 |
+
if fileData != "" {
|
| 214 |
+
mediaType := "application/octet-stream"
|
| 215 |
+
data := fileData
|
| 216 |
+
if strings.HasPrefix(fileData, "data:") {
|
| 217 |
+
trimmed := strings.TrimPrefix(fileData, "data:")
|
| 218 |
+
mediaAndData := strings.SplitN(trimmed, ";base64,", 2)
|
| 219 |
+
if len(mediaAndData) == 2 {
|
| 220 |
+
if mediaAndData[0] != "" {
|
| 221 |
+
mediaType = mediaAndData[0]
|
| 222 |
+
}
|
| 223 |
+
data = mediaAndData[1]
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
contentPart := `{"type":"document","source":{"type":"base64","media_type":"","data":""}}`
|
| 227 |
+
contentPart, _ = sjson.Set(contentPart, "source.media_type", mediaType)
|
| 228 |
+
contentPart, _ = sjson.Set(contentPart, "source.data", data)
|
| 229 |
+
partsJSON = append(partsJSON, contentPart)
|
| 230 |
+
if role == "" {
|
| 231 |
+
role = "user"
|
| 232 |
+
}
|
| 233 |
+
hasFile = true
|
| 234 |
+
}
|
| 235 |
}
|
| 236 |
return true
|
| 237 |
})
|
|
|
|
| 253 |
if len(partsJSON) > 0 {
|
| 254 |
msg := `{"role":"","content":[]}`
|
| 255 |
msg, _ = sjson.Set(msg, "role", role)
|
| 256 |
+
if len(partsJSON) == 1 && !hasImage && !hasFile {
|
| 257 |
// Preserve legacy behavior for single text content
|
| 258 |
msg, _ = sjson.Delete(msg, "content")
|
| 259 |
textPart := gjson.Parse(partsJSON[0])
|
internal/translator/codex/openai/chat-completions/codex_openai_request.go
CHANGED
|
@@ -180,7 +180,19 @@ func ConvertOpenAIRequestToCodex(modelName string, inputRawJSON []byte, stream b
|
|
| 180 |
msg, _ = sjson.SetRaw(msg, "content.-1", part)
|
| 181 |
}
|
| 182 |
case "file":
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
}
|
| 185 |
}
|
| 186 |
}
|
|
|
|
| 180 |
msg, _ = sjson.SetRaw(msg, "content.-1", part)
|
| 181 |
}
|
| 182 |
case "file":
|
| 183 |
+
if role == "user" {
|
| 184 |
+
fileData := it.Get("file.file_data").String()
|
| 185 |
+
filename := it.Get("file.filename").String()
|
| 186 |
+
if fileData != "" {
|
| 187 |
+
part := `{}`
|
| 188 |
+
part, _ = sjson.Set(part, "type", "input_file")
|
| 189 |
+
part, _ = sjson.Set(part, "file_data", fileData)
|
| 190 |
+
if filename != "" {
|
| 191 |
+
part, _ = sjson.Set(part, "filename", filename)
|
| 192 |
+
}
|
| 193 |
+
msg, _ = sjson.SetRaw(msg, "content.-1", part)
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
}
|
| 197 |
}
|
| 198 |
}
|