chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,717 @@
|
||||
package interactions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
func ConvertInteractionsRequestToCodex(modelName string, inputRawJSON []byte, stream bool) []byte {
|
||||
root := gjson.ParseBytes(inputRawJSON)
|
||||
out := []byte(`{"model":"","instructions":"","input":[]}`)
|
||||
out, _ = sjson.SetBytes(out, "model", modelName)
|
||||
if stream || root.Get("stream").Bool() {
|
||||
out, _ = sjson.SetBytes(out, "stream", true)
|
||||
}
|
||||
out = copyInteractionsSystemToCodex(out, root)
|
||||
out = copyInteractionsGenerationConfigToCodex(out, root)
|
||||
out = appendInteractionsInputToCodex(out, root.Get("input"))
|
||||
out = copyInteractionsToolsToCodex(out, root)
|
||||
out = copyInteractionsCodexTopLevel(out, root)
|
||||
return out
|
||||
}
|
||||
|
||||
func copyInteractionsSystemToCodex(out []byte, root gjson.Result) []byte {
|
||||
systemInstruction := root.Get("system_instruction")
|
||||
if !systemInstruction.Exists() {
|
||||
systemInstruction = root.Get("systemInstruction")
|
||||
}
|
||||
if !systemInstruction.Exists() {
|
||||
return out
|
||||
}
|
||||
if systemInstruction.Type == gjson.String {
|
||||
out, _ = sjson.SetBytes(out, "instructions", systemInstruction.String())
|
||||
return out
|
||||
}
|
||||
if text := systemInstruction.Get("text"); text.Exists() && text.Type == gjson.String {
|
||||
out, _ = sjson.SetBytes(out, "instructions", text.String())
|
||||
return out
|
||||
}
|
||||
if parts := systemInstruction.Get("parts"); parts.Exists() && parts.IsArray() {
|
||||
var builder strings.Builder
|
||||
parts.ForEach(func(_, part gjson.Result) bool {
|
||||
text := part.Get("text").String()
|
||||
if text == "" {
|
||||
return true
|
||||
}
|
||||
if builder.Len() > 0 {
|
||||
builder.WriteByte('\n')
|
||||
}
|
||||
builder.WriteString(text)
|
||||
return true
|
||||
})
|
||||
if builder.Len() > 0 {
|
||||
out, _ = sjson.SetBytes(out, "instructions", builder.String())
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyInteractionsGenerationConfigToCodex(out []byte, root gjson.Result) []byte {
|
||||
cfg := root.Get("generation_config")
|
||||
if !cfg.Exists() {
|
||||
cfg = root.Get("generationConfig")
|
||||
}
|
||||
if !cfg.Exists() {
|
||||
if reasoning := root.Get("reasoning"); reasoning.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "reasoning", []byte(reasoning.Raw))
|
||||
}
|
||||
return out
|
||||
}
|
||||
if reasoning := cfg.Get("reasoning"); reasoning.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "reasoning", []byte(reasoning.Raw))
|
||||
}
|
||||
if effort := interactionsCodexReasoningEffort(cfg); effort != "" {
|
||||
out, _ = sjson.SetBytes(out, "reasoning.effort", effort)
|
||||
}
|
||||
if summary := interactionsCodexReasoningSummary(cfg); summary != "" {
|
||||
out, _ = sjson.SetBytes(out, "reasoning.summary", summary)
|
||||
}
|
||||
copyRawPaths := map[string]string{
|
||||
"max_output_tokens": "max_output_tokens",
|
||||
"maxOutputTokens": "max_output_tokens",
|
||||
"max_tokens": "max_output_tokens",
|
||||
"temperature": "temperature",
|
||||
"top_p": "top_p",
|
||||
"topP": "top_p",
|
||||
"presence_penalty": "presence_penalty",
|
||||
"presencePenalty": "presence_penalty",
|
||||
"frequency_penalty": "frequency_penalty",
|
||||
"frequencyPenalty": "frequency_penalty",
|
||||
"parallel_tool_calls": "parallel_tool_calls",
|
||||
"parallelToolCalls": "parallel_tool_calls",
|
||||
"response_format": "response_format",
|
||||
"responseFormat": "response_format",
|
||||
"text": "text",
|
||||
"verbosity": "text.verbosity",
|
||||
"truncation": "truncation",
|
||||
"tool_choice": "tool_choice",
|
||||
"toolChoice": "tool_choice",
|
||||
"service_tier": "service_tier",
|
||||
"serviceTier": "service_tier",
|
||||
}
|
||||
for sourcePath, targetPath := range copyRawPaths {
|
||||
if value := cfg.Get(sourcePath); value.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, targetPath, []byte(value.Raw))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func interactionsCodexReasoningEffort(cfg gjson.Result) string {
|
||||
for _, path := range []string{
|
||||
"thinking_level",
|
||||
"thinkingLevel",
|
||||
"thinking_config.thinking_level",
|
||||
"thinking_config.thinkingLevel",
|
||||
"thinkingConfig.thinking_level",
|
||||
"thinkingConfig.thinkingLevel",
|
||||
"reasoning.effort",
|
||||
} {
|
||||
if value := cfg.Get(path); value.Exists() {
|
||||
effort := strings.ToLower(strings.TrimSpace(value.String()))
|
||||
if effort != "" {
|
||||
return effort
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, path := range []string{
|
||||
"thinking_budget",
|
||||
"thinkingBudget",
|
||||
"thinking_config.thinking_budget",
|
||||
"thinking_config.thinkingBudget",
|
||||
"thinkingConfig.thinking_budget",
|
||||
"thinkingConfig.thinkingBudget",
|
||||
} {
|
||||
if value := cfg.Get(path); value.Exists() {
|
||||
if effort, ok := thinking.ConvertBudgetToLevel(int(value.Int())); ok {
|
||||
return effort
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func interactionsCodexReasoningSummary(cfg gjson.Result) string {
|
||||
for _, path := range []string{
|
||||
"thinking_summaries",
|
||||
"thinkingSummaries",
|
||||
"reasoning.summary",
|
||||
} {
|
||||
if value := cfg.Get(path); value.Exists() {
|
||||
switch value.Type {
|
||||
case gjson.True:
|
||||
return "auto"
|
||||
case gjson.False:
|
||||
return "none"
|
||||
case gjson.String:
|
||||
summary := strings.ToLower(strings.TrimSpace(value.String()))
|
||||
if summary != "" {
|
||||
return summary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, path := range []string{
|
||||
"include_thoughts",
|
||||
"includeThoughts",
|
||||
"thinking_config.include_thoughts",
|
||||
"thinking_config.includeThoughts",
|
||||
"thinkingConfig.include_thoughts",
|
||||
"thinkingConfig.includeThoughts",
|
||||
} {
|
||||
if value := cfg.Get(path); value.Exists() {
|
||||
if value.Bool() {
|
||||
return "auto"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func appendInteractionsInputToCodex(out []byte, input gjson.Result) []byte {
|
||||
if !input.Exists() {
|
||||
return out
|
||||
}
|
||||
if input.Type == gjson.String {
|
||||
return appendInteractionsTextToCodex(out, "user", input.String())
|
||||
}
|
||||
if input.IsArray() {
|
||||
input.ForEach(func(_, step gjson.Result) bool {
|
||||
out = appendInteractionsStepToCodex(out, step, "user")
|
||||
return true
|
||||
})
|
||||
return out
|
||||
}
|
||||
if steps := input.Get("steps"); steps.Exists() && steps.IsArray() {
|
||||
defaultRole := interactionsCodexDefaultRole(input.Get("role").String(), "user")
|
||||
steps.ForEach(func(_, step gjson.Result) bool {
|
||||
out = appendInteractionsStepToCodex(out, step, defaultRole)
|
||||
return true
|
||||
})
|
||||
return out
|
||||
}
|
||||
return appendInteractionsStepToCodex(out, input, "user")
|
||||
}
|
||||
|
||||
func appendInteractionsStepToCodex(out []byte, step gjson.Result, defaultRole string) []byte {
|
||||
if step.Type == gjson.String {
|
||||
return appendInteractionsTextToCodex(out, defaultRole, step.String())
|
||||
}
|
||||
if steps := step.Get("steps"); steps.Exists() && steps.IsArray() {
|
||||
role := interactionsCodexDefaultRole(step.Get("role").String(), defaultRole)
|
||||
steps.ForEach(func(_, nested gjson.Result) bool {
|
||||
out = appendInteractionsStepToCodex(out, nested, role)
|
||||
return true
|
||||
})
|
||||
return out
|
||||
}
|
||||
stepType := strings.ToLower(strings.TrimSpace(step.Get("type").String()))
|
||||
switch stepType {
|
||||
case "function_call":
|
||||
return appendInteractionsFunctionCallToCodex(out, step)
|
||||
case "function_result", "function_call_output":
|
||||
return appendInteractionsFunctionResultToCodex(out, step)
|
||||
case "model_output", "assistant":
|
||||
return appendInteractionsContentToCodexItem(out, step.Get("content"), "assistant")
|
||||
case "thought", "reasoning":
|
||||
return appendInteractionsThoughtToCodex(out, step)
|
||||
case "user_input", "message", "":
|
||||
role := interactionsCodexDefaultRole(step.Get("role").String(), defaultRole)
|
||||
if content := step.Get("content"); content.Exists() {
|
||||
return appendInteractionsContentToCodexItem(out, content, role)
|
||||
}
|
||||
if text := step.Get("text"); text.Exists() {
|
||||
return appendInteractionsTextToCodex(out, role, text.String())
|
||||
}
|
||||
default:
|
||||
role := interactionsCodexDefaultRole(step.Get("role").String(), defaultRole)
|
||||
if content := step.Get("content"); content.Exists() {
|
||||
return appendInteractionsContentToCodexItem(out, content, role)
|
||||
}
|
||||
if text := step.Get("text"); text.Exists() {
|
||||
return appendInteractionsTextToCodex(out, role, text.String())
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func appendInteractionsContentToCodexItem(out []byte, content gjson.Result, role string) []byte {
|
||||
if !content.Exists() {
|
||||
return out
|
||||
}
|
||||
if content.Type == gjson.String {
|
||||
return appendInteractionsTextToCodex(out, role, content.String())
|
||||
}
|
||||
if content.IsArray() {
|
||||
content.ForEach(func(_, part gjson.Result) bool {
|
||||
item := interactionsCodexMessagePart(part, role)
|
||||
if len(item) > 0 {
|
||||
out = appendInteractionsMessagePartToCodex(out, role, item)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return out
|
||||
}
|
||||
if content.IsObject() {
|
||||
if item := interactionsCodexMessagePart(content, role); len(item) > 0 {
|
||||
return appendInteractionsMessagePartToCodex(out, role, item)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func appendInteractionsFunctionCallToCodex(out []byte, step gjson.Result) []byte {
|
||||
item := []byte(`{"type":"function_call"}`)
|
||||
if name := step.Get("name"); name.Exists() {
|
||||
item, _ = sjson.SetBytes(item, "name", shortenCodexToolNameIfNeeded(name.String()))
|
||||
}
|
||||
if callID := interactionsCodexCallID(step); callID != "" {
|
||||
item, _ = sjson.SetBytes(item, "call_id", callID)
|
||||
}
|
||||
if args := step.Get("arguments"); args.Exists() {
|
||||
item, _ = sjson.SetBytes(item, "arguments", interactionsCodexJSONString(args))
|
||||
} else if args := step.Get("args"); args.Exists() {
|
||||
item, _ = sjson.SetBytes(item, "arguments", interactionsCodexJSONString(args))
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", item)
|
||||
return out
|
||||
}
|
||||
|
||||
func appendInteractionsFunctionResultToCodex(out []byte, step gjson.Result) []byte {
|
||||
item := []byte(`{"type":"function_call_output"}`)
|
||||
if callID := interactionsCodexCallID(step); callID != "" {
|
||||
item, _ = sjson.SetBytes(item, "call_id", callID)
|
||||
}
|
||||
if result := step.Get("result"); result.Exists() {
|
||||
item, _ = sjson.SetBytes(item, "output", interactionsCodexOutputString(result))
|
||||
} else if output := step.Get("output"); output.Exists() {
|
||||
item, _ = sjson.SetBytes(item, "output", interactionsCodexOutputString(output))
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", item)
|
||||
return out
|
||||
}
|
||||
|
||||
func copyInteractionsToolsToCodex(out []byte, root gjson.Result) []byte {
|
||||
tools := root.Get("tools")
|
||||
if !tools.Exists() {
|
||||
return out
|
||||
}
|
||||
if !tools.IsArray() {
|
||||
out, _ = sjson.SetRawBytes(out, "tools", []byte(tools.Raw))
|
||||
return out
|
||||
}
|
||||
normalized := make([]map[string]any, 0)
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if decls := tool.Get("function_declarations"); decls.Exists() {
|
||||
appendCodexToolDeclarations(&normalized, decls)
|
||||
return true
|
||||
}
|
||||
if decls := tool.Get("functionDeclarations"); decls.Exists() {
|
||||
appendCodexToolDeclarations(&normalized, decls)
|
||||
return true
|
||||
}
|
||||
if name := tool.Get("name"); name.Exists() {
|
||||
normalized = append(normalized, codexToolFromDeclaration(tool))
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(normalized) == 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "tools", []byte(tools.Raw))
|
||||
return out
|
||||
}
|
||||
raw, errMarshal := json.Marshal(normalized)
|
||||
if errMarshal != nil {
|
||||
out, _ = sjson.SetRawBytes(out, "tools", []byte(tools.Raw))
|
||||
return out
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "tools", raw)
|
||||
if !gjson.GetBytes(out, "tool_choice").Exists() {
|
||||
out, _ = sjson.SetBytes(out, "tool_choice", "auto")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyInteractionsCodexTopLevel(out []byte, root gjson.Result) []byte {
|
||||
if serviceTier := normalizeInteractionsCodexServiceTier(root.Get("service_tier")); serviceTier != "" {
|
||||
out, _ = sjson.SetBytes(out, "service_tier", serviceTier)
|
||||
}
|
||||
if toolChoice := root.Get("tool_choice"); toolChoice.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "tool_choice", []byte(toolChoice.Raw))
|
||||
}
|
||||
for _, path := range []string{"parallel_tool_calls", "store", "metadata", "include", "truncation"} {
|
||||
if value := root.Get(path); value.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, path, []byte(value.Raw))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func appendInteractionsThoughtToCodex(out []byte, step gjson.Result) []byte {
|
||||
text := interactionsCodexContentText(step.Get("content"))
|
||||
if text == "" {
|
||||
text = step.Get("text").String()
|
||||
}
|
||||
item := []byte(`{"type":"reasoning"}`)
|
||||
if text != "" {
|
||||
item, _ = sjson.SetBytes(item, "content", text)
|
||||
}
|
||||
if id := step.Get("id"); id.Exists() {
|
||||
item, _ = sjson.SetBytes(item, "id", id.String())
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", item)
|
||||
return out
|
||||
}
|
||||
|
||||
func appendInteractionsTextToCodex(out []byte, role, text string) []byte {
|
||||
part := []byte(`{"type":"","text":""}`)
|
||||
if role == "assistant" {
|
||||
part, _ = sjson.SetBytes(part, "type", "output_text")
|
||||
} else {
|
||||
part, _ = sjson.SetBytes(part, "type", "input_text")
|
||||
}
|
||||
part, _ = sjson.SetBytes(part, "text", text)
|
||||
return appendInteractionsMessagePartToCodex(out, role, part)
|
||||
}
|
||||
|
||||
func appendInteractionsMessagePartToCodex(out []byte, role string, part []byte) []byte {
|
||||
message := []byte(`{"type":"message","role":"","content":[]}`)
|
||||
message, _ = sjson.SetBytes(message, "role", role)
|
||||
message, _ = sjson.SetRawBytes(message, "content.-1", part)
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", message)
|
||||
return out
|
||||
}
|
||||
|
||||
func interactionsCodexMessagePart(part gjson.Result, role string) []byte {
|
||||
if text := part.Get("text"); text.Exists() {
|
||||
item := []byte(`{"type":"","text":""}`)
|
||||
if role == "assistant" {
|
||||
item, _ = sjson.SetBytes(item, "type", "output_text")
|
||||
} else {
|
||||
item, _ = sjson.SetBytes(item, "type", "input_text")
|
||||
}
|
||||
item, _ = sjson.SetBytes(item, "text", text.String())
|
||||
return item
|
||||
}
|
||||
partType := strings.ToLower(strings.TrimSpace(part.Get("type").String()))
|
||||
switch partType {
|
||||
case "text", "":
|
||||
return nil
|
||||
case "image":
|
||||
return interactionsCodexImagePart(part)
|
||||
case "image_url":
|
||||
item := []byte(`{"type":"input_image","image_url":""}`)
|
||||
item, _ = sjson.SetBytes(item, "image_url", part.Get("image_url.url").String())
|
||||
return item
|
||||
case "audio":
|
||||
return interactionsCodexAudioPart(part)
|
||||
case "input_audio":
|
||||
item := []byte(`{"type":"input_audio","input_audio":{}}`)
|
||||
if audio := part.Get("input_audio"); audio.Exists() {
|
||||
item, _ = sjson.SetRawBytes(item, "input_audio", []byte(audio.Raw))
|
||||
}
|
||||
return item
|
||||
case "video", "document", "file":
|
||||
return interactionsCodexFilePart(part)
|
||||
default:
|
||||
if inline := part.Get("inline_data"); inline.Exists() {
|
||||
return interactionsCodexInlinePart(inline)
|
||||
}
|
||||
if inline := part.Get("inlineData"); inline.Exists() {
|
||||
return interactionsCodexInlinePart(inline)
|
||||
}
|
||||
if file := part.Get("file_data"); file.Exists() {
|
||||
return interactionsCodexFileDataPart(file)
|
||||
}
|
||||
if file := part.Get("fileData"); file.Exists() {
|
||||
return interactionsCodexFileDataPart(file)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func interactionsCodexImagePart(part gjson.Result) []byte {
|
||||
if url := part.Get("url"); url.Exists() {
|
||||
item := []byte(`{"type":"input_image","image_url":""}`)
|
||||
item, _ = sjson.SetBytes(item, "image_url", url.String())
|
||||
return item
|
||||
}
|
||||
if fileURI := firstString(part, "file_uri", "fileUri"); fileURI != "" {
|
||||
item := []byte(`{"type":"input_image","image_url":""}`)
|
||||
item, _ = sjson.SetBytes(item, "image_url", fileURI)
|
||||
return item
|
||||
}
|
||||
mimeType := firstString(part, "mime_type", "mimeType")
|
||||
data := part.Get("data").String()
|
||||
if mimeType == "" || data == "" {
|
||||
return nil
|
||||
}
|
||||
item := []byte(`{"type":"input_image","image_url":""}`)
|
||||
item, _ = sjson.SetBytes(item, "image_url", fmt.Sprintf("data:%s;base64,%s", mimeType, data))
|
||||
return item
|
||||
}
|
||||
|
||||
func interactionsCodexAudioPart(part gjson.Result) []byte {
|
||||
mimeType := firstString(part, "mime_type", "mimeType")
|
||||
data := part.Get("data").String()
|
||||
if mimeType == "" || data == "" {
|
||||
return nil
|
||||
}
|
||||
item := []byte(`{"type":"input_audio","input_audio":{"data":"","format":""}}`)
|
||||
item, _ = sjson.SetBytes(item, "input_audio.data", data)
|
||||
item, _ = sjson.SetBytes(item, "input_audio.format", codexInputAudioFormatFromMIME(mimeType))
|
||||
return item
|
||||
}
|
||||
|
||||
func interactionsCodexFilePart(part gjson.Result) []byte {
|
||||
if fileData := part.Get("file.file_data").String(); fileData != "" {
|
||||
item := []byte(`{"type":"input_file","file_data":"","filename":""}`)
|
||||
item, _ = sjson.SetBytes(item, "file_data", fileData)
|
||||
item, _ = sjson.SetBytes(item, "filename", part.Get("file.filename").String())
|
||||
return item
|
||||
}
|
||||
mimeType := firstString(part, "mime_type", "mimeType")
|
||||
if fileURI := firstString(part, "file_uri", "fileUri", "url"); fileURI != "" {
|
||||
item := []byte(`{"type":"input_file","file_url":"","filename":""}`)
|
||||
item, _ = sjson.SetBytes(item, "file_url", fileURI)
|
||||
item, _ = sjson.SetBytes(item, "filename", codexFileNameFromMIME(mimeType))
|
||||
return item
|
||||
}
|
||||
data := part.Get("data").String()
|
||||
if mimeType == "" || data == "" {
|
||||
return nil
|
||||
}
|
||||
item := []byte(`{"type":"input_file","file_data":"","filename":""}`)
|
||||
item, _ = sjson.SetBytes(item, "file_data", data)
|
||||
item, _ = sjson.SetBytes(item, "filename", codexFileNameFromMIME(mimeType))
|
||||
return item
|
||||
}
|
||||
|
||||
func interactionsCodexInlinePart(inline gjson.Result) []byte {
|
||||
mimeType := firstString(inline, "mime_type", "mimeType")
|
||||
data := inline.Get("data").String()
|
||||
if mimeType == "" || data == "" {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(strings.ToLower(mimeType), "image/"):
|
||||
return interactionsCodexImagePart(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, mimeType, data)))
|
||||
case strings.HasPrefix(strings.ToLower(mimeType), "audio/"):
|
||||
return interactionsCodexAudioPart(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, mimeType, data)))
|
||||
default:
|
||||
return interactionsCodexFilePart(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, mimeType, data)))
|
||||
}
|
||||
}
|
||||
|
||||
func interactionsCodexFileDataPart(fileData gjson.Result) []byte {
|
||||
mimeType := firstString(fileData, "mime_type", "mimeType")
|
||||
fileURI := firstString(fileData, "file_uri", "fileUri")
|
||||
if fileURI == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(mimeType), "image/") {
|
||||
item := []byte(`{"type":"input_image","image_url":""}`)
|
||||
item, _ = sjson.SetBytes(item, "image_url", fileURI)
|
||||
return item
|
||||
}
|
||||
item := []byte(`{"type":"input_file","file_url":"","filename":""}`)
|
||||
item, _ = sjson.SetBytes(item, "file_url", fileURI)
|
||||
item, _ = sjson.SetBytes(item, "filename", codexFileNameFromMIME(mimeType))
|
||||
return item
|
||||
}
|
||||
|
||||
func appendCodexToolDeclarations(normalized *[]map[string]any, declarations gjson.Result) {
|
||||
if !declarations.IsArray() {
|
||||
return
|
||||
}
|
||||
declarations.ForEach(func(_, declaration gjson.Result) bool {
|
||||
if declaration.Get("name").Exists() {
|
||||
*normalized = append(*normalized, codexToolFromDeclaration(declaration))
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func codexToolFromDeclaration(declaration gjson.Result) map[string]any {
|
||||
tool := map[string]any{
|
||||
"type": "function",
|
||||
"name": shortenCodexToolNameIfNeeded(declaration.Get("name").String()),
|
||||
"strict": false,
|
||||
}
|
||||
if desc := declaration.Get("description"); desc.Exists() {
|
||||
tool["description"] = desc.String()
|
||||
}
|
||||
if params := declaration.Get("parameters"); params.Exists() {
|
||||
tool["parameters"] = cleanedCodexToolParameters(params)
|
||||
} else if params := declaration.Get("parametersJsonSchema"); params.Exists() {
|
||||
tool["parameters"] = cleanedCodexToolParameters(params)
|
||||
} else if params := declaration.Get("parameters_json_schema"); params.Exists() {
|
||||
tool["parameters"] = cleanedCodexToolParameters(params)
|
||||
}
|
||||
return tool
|
||||
}
|
||||
|
||||
func cleanedCodexToolParameters(params gjson.Result) json.RawMessage {
|
||||
cleaned := []byte(params.Raw)
|
||||
cleaned, _ = sjson.DeleteBytes(cleaned, "$schema")
|
||||
cleaned, _ = sjson.SetBytes(cleaned, "additionalProperties", false)
|
||||
return json.RawMessage(cleaned)
|
||||
}
|
||||
|
||||
func interactionsCodexContentText(content gjson.Result) string {
|
||||
if !content.Exists() {
|
||||
return ""
|
||||
}
|
||||
if content.Type == gjson.String {
|
||||
return content.String()
|
||||
}
|
||||
if content.IsObject() {
|
||||
return content.Get("text").String()
|
||||
}
|
||||
if content.IsArray() {
|
||||
var builder strings.Builder
|
||||
content.ForEach(func(_, part gjson.Result) bool {
|
||||
text := part.Get("text").String()
|
||||
if text == "" {
|
||||
return true
|
||||
}
|
||||
if builder.Len() > 0 {
|
||||
builder.WriteByte('\n')
|
||||
}
|
||||
builder.WriteString(text)
|
||||
return true
|
||||
})
|
||||
return builder.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func interactionsCodexCallID(step gjson.Result) string {
|
||||
if callID := strings.TrimSpace(step.Get("call_id").String()); callID != "" {
|
||||
return callID
|
||||
}
|
||||
return strings.TrimSpace(step.Get("id").String())
|
||||
}
|
||||
|
||||
func interactionsCodexJSONString(value gjson.Result) string {
|
||||
if value.Type == gjson.String {
|
||||
return value.String()
|
||||
}
|
||||
if value.Exists() {
|
||||
return value.Raw
|
||||
}
|
||||
return "{}"
|
||||
}
|
||||
|
||||
func interactionsCodexOutputString(value gjson.Result) string {
|
||||
if value.Type == gjson.String {
|
||||
return value.String()
|
||||
}
|
||||
if value.Exists() {
|
||||
return value.Raw
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func interactionsCodexDefaultRole(role, fallback string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(role)) {
|
||||
case "model", "assistant":
|
||||
return "assistant"
|
||||
case "developer", "system":
|
||||
return "developer"
|
||||
case "user":
|
||||
return "user"
|
||||
}
|
||||
if fallback == "assistant" || fallback == "developer" {
|
||||
return fallback
|
||||
}
|
||||
return "user"
|
||||
}
|
||||
|
||||
func normalizeInteractionsCodexServiceTier(serviceTier gjson.Result) string {
|
||||
if !serviceTier.Exists() || serviceTier.Type != gjson.String {
|
||||
return ""
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(serviceTier.String())) {
|
||||
case "priority", "fast":
|
||||
return "priority"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func codexInputAudioFormatFromMIME(mimeType string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mimeType)) {
|
||||
case "audio/wav", "audio/wave", "audio/x-wav":
|
||||
return "wav"
|
||||
case "audio/flac":
|
||||
return "flac"
|
||||
case "audio/opus", "audio/ogg":
|
||||
return "opus"
|
||||
case "audio/pcm", "audio/l16":
|
||||
return "pcm16"
|
||||
default:
|
||||
return "mp3"
|
||||
}
|
||||
}
|
||||
|
||||
func codexFileNameFromMIME(mimeType string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mimeType)) {
|
||||
case "application/pdf":
|
||||
return "document.pdf"
|
||||
case "text/plain":
|
||||
return "document.txt"
|
||||
case "text/csv":
|
||||
return "document.csv"
|
||||
case "application/json":
|
||||
return "document.json"
|
||||
case "application/xml", "text/xml":
|
||||
return "document.xml"
|
||||
default:
|
||||
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(mimeType)), "video/") {
|
||||
return "video"
|
||||
}
|
||||
return "document"
|
||||
}
|
||||
}
|
||||
|
||||
func shortenCodexToolNameIfNeeded(name string) string {
|
||||
const limit = 64
|
||||
if len(name) <= limit {
|
||||
return name
|
||||
}
|
||||
if strings.HasPrefix(name, "mcp__") {
|
||||
idx := strings.LastIndex(name, "__")
|
||||
if idx > 0 {
|
||||
candidate := "mcp__" + name[idx+2:]
|
||||
if len(candidate) > limit {
|
||||
return candidate[:limit]
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return name[:limit]
|
||||
}
|
||||
|
||||
func firstString(root gjson.Result, paths ...string) string {
|
||||
for _, path := range paths {
|
||||
if value := root.Get(path); value.Exists() {
|
||||
return value.String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user