| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- package service
- import "strings"
- // This is intentionally a small equivalence map, not a second forwarding
- // pipeline. Unknown fields, item types, tools and content remain comparable.
- func mode1CanonicalizeCodexRequest(body map[string]any) {
- if model, ok := body["model"].(string); ok {
- body["model"] = strings.TrimSpace(model)
- }
- if reasoning, ok := body["reasoning"].(map[string]any); ok {
- if reasoning["effort"] == "minimal" {
- reasoning["effort"] = "none"
- }
- model, _ := body["model"].(string)
- mode, _ := reasoning["mode"].(string)
- effort, _ := reasoning["effort"].(string)
- if !isOpenAIGPT6AstraModel(model) && strings.EqualFold(strings.TrimSpace(mode), "pro") && strings.TrimSpace(effort) == "" {
- reasoning["effort"] = "max"
- delete(reasoning, "mode")
- }
- }
- if text, ok := body["input"].(string); ok {
- body["input"] = []any{}
- if strings.TrimSpace(text) != "" {
- body["input"] = []any{map[string]any{"type": "message", "role": "user", "content": text}}
- }
- }
- // The proven OAuth path promotes text-only system messages into
- // instructions. Retain them as developer for JSON object requests.
- omitSystem := true
- if text, ok := body["text"].(map[string]any); ok {
- if format, ok := text["format"].(map[string]any); ok && format["type"] == "json_object" {
- omitSystem = false
- }
- }
- extractSystemMessagesFromInput(body, omitSystem)
- if instructions, ok := body["instructions"].(string); body["instructions"] == nil || (ok && strings.TrimSpace(instructions) == "") {
- delete(body, "instructions") // an empty instructions field may receive defaults
- }
- _, hasTools := body["tools"]
- if functions, ok := body["functions"].([]any); ok && !hasTools {
- tools := make([]any, 0, len(functions))
- for _, function := range functions {
- tools = append(tools, map[string]any{"type": "function", "function": function})
- }
- body["tools"] = tools
- delete(body, "functions")
- }
- _, hasChoice := body["tool_choice"]
- if choice, exists := body["function_call"]; exists && !hasChoice {
- switch c := choice.(type) {
- case string:
- body["tool_choice"] = c
- delete(body, "function_call")
- case map[string]any:
- if name, ok := c["name"].(string); ok && name != "" {
- body["tool_choice"] = map[string]any{"type": "function", "name": name}
- delete(body, "function_call")
- }
- }
- }
- if tools, ok := body["tools"].([]any); ok {
- for _, raw := range tools {
- tool, ok := raw.(map[string]any)
- if !ok || tool["type"] != "function" {
- continue
- }
- if function, ok := tool["function"].(map[string]any); ok {
- for key, value := range function {
- if _, exists := tool[key]; !exists {
- tool[key] = value
- }
- }
- delete(tool, "function")
- }
- }
- }
- if choice, ok := body["tool_choice"].(map[string]any); ok && choice["type"] == "function" {
- if function, ok := choice["function"].(map[string]any); ok {
- if _, exists := choice["name"]; !exists {
- choice["name"] = function["name"]
- }
- delete(choice, "function")
- }
- }
- // Aliasing reserved tool names is bijective and applied to declarations,
- // choices and call items together. Errors leave invalid input comparable.
- _, _, _ = aliasOpenAIOAuthReservedToolNames(body)
- input, ok := body["input"].([]any)
- if !ok {
- return
- }
- referenceIDs := codexItemReferenceIDMappings(input, false)
- itemIDs := codexInputItemIDs(input)
- callIDs := codexInputCallIDs(input)
- for i, raw := range input {
- item, ok := raw.(map[string]any)
- if !ok {
- continue
- }
- if item["role"] == "tool" && strings.TrimSpace(firstNonEmptyString(item["call_id"], item["tool_call_id"], item["id"])) != "" {
- if _, lossless := extractLosslessTextFromContent(item["content"]); lossless {
- if result, changed := normalizeCodexToolRoleMessages([]any{item}); changed {
- item = result[0].(map[string]any)
- input[i] = item
- }
- }
- }
- typ, _ := item["type"].(string)
- if typ == "" && (item["role"] == "user" || item["role"] == "assistant" || item["role"] == "developer") {
- typ = "message"
- item["type"] = typ
- }
- switch typ {
- case "message":
- // Message IDs are store=false replay metadata. Content and role
- // stay protected, including multimodal payloads and their order.
- delete(item, "id")
- if text, ok := item["content"].(string); ok {
- item["content"] = []any{map[string]any{"type": "input_text", "text": text}}
- }
- if parts, ok := item["content"].([]any); ok {
- for _, rawPart := range parts {
- if part, ok := rawPart.(map[string]any); ok && (part["type"] == "text" || part["type"] == "output_text") {
- part["type"] = "input_text"
- }
- }
- }
- case "reasoning":
- delete(item, "id")
- delete(item, "call_id")
- if item["summary"] == nil {
- item["summary"] = []any{}
- }
- case "compaction_summary":
- delete(item, "id")
- case "item_reference":
- id, _ := item["id"].(string)
- id = strings.TrimSpace(id)
- if _, existing := itemIDs[id]; !existing && strings.HasPrefix(id, "call_") {
- if mapped, exists := referenceIDs[id]; exists {
- item["id"] = mapped
- } else if _, sameTurnCall := callIDs[id]; !sameTurnCall {
- item["id"] = normalizeCodexCallID(id)
- }
- }
- }
- if isCodexToolCallItemType(typ) {
- id := firstNonEmptyString(item["call_id"], item["id"])
- if id != "" {
- item["call_id"] = normalizeCodexCallIDForItemType(typ, id)
- }
- delete(item, "id")
- }
- }
- }
|