// Package models builds model catalogs for official Codex clients.package models import ( "encoding/json" "sort" "strings" "sync" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry") type codexClientModelsPayload struct { Models []map[string]any `json:"models"`} // ProvidersForModelFunc returns the providers registered for a model.type ProvidersForModelFunc func(string) []string var ( codexClientModelTemplatesMu sync.Mutex codexClientModelTemplatesLoaded bool codexClientModelTemplatesRevision uint64 codexClientModelTemplates map[string]map[string]any codexClientDefaultTemplate map[string]any codexClientModelTemplatesErr error) var codexClientAllowedReasoningLevels = map[string]struct{}{ "none": {}, "minimal": {}, "low": {}, "medium": {}, "high": {}, "xhigh": {}, "max": {}, "ultra": {},} // BuildResponse builds a Codex client model response from available models.func BuildResponse(availableModels []map[string]any, providersForModel ProvidersForModelFunc, optimizeMultiAgentV2 bool) map[string]any { return map[string]any{ "models": buildCodexClientModels(availableModels, providersForModel, optimizeMultiAgentV2), }} func buildCodexClientModels(models []map[string]any, providersForModel ProvidersForModelFunc, optimizeMultiAgentV2 bool) []map[string]any { templates, defaultTemplate, err := loadCodexClientModelTemplates() if err != nil || defaultTemplate == nil { return nil } result := make([]map[string]any, 0, len(models)) for _, model := range models { id := strings.TrimSpace(stringModelValue(model, "id")) if id == "" { continue } if template, ok := templates[id]; ok { entry := cloneCodexClientModelMap(template) applyCodexClientDisplayName(entry, model) applyCodexClientSearchToolSupport(entry, id, true, providersForModel) sanitizeCodexClientReasoningMetadata(entry) applyCodexClientVisibilityOverride(entry, id) result = append(result, entry) continue } entry := cloneCodexClientModelMap(defaultTemplate) applyCodexClientModelMetadata(entry, id, model, optimizeMultiAgentV2) applyCodexClientSearchToolSupport(entry, id, false, providersForModel) sanitizeCodexClientReasoningMetadata(entry) applyCodexClientVisibilityOverride(entry, id) result = append(result, entry) } applyCodexClientNonTemplatePriorities(result, templates) sort.SliceStable(result, func(i, j int) bool { return codexClientModelPriority(result[i]) < codexClientModelPriority(result[j]) }) return result} func maxCodexClientTemplatePriority(templates map[string]map[string]any) int { maxPriority := 0 for _, template := range templates { priority := codexClientModelPriority(template) if priority > maxPriority { maxPriority = priority } } return maxPriority} func applyCodexClientNonTemplatePriorities(result []map[string]any, templates map[string]map[string]any) { if len(result) == 0 { return } basePriority := maxCodexClientTemplatePriority(templates) type nonTemplateEntry struct { index int displayName string slug string } pending := make([]nonTemplateEntry, 0) for index, entry := range result { slug := stringModelValue(entry, "slug") if _, ok := templates[slug]; ok { continue } displayName := stringModelValue(entry, "display_name") if displayName == "" { displayName = slug } pending = append(pending, nonTemplateEntry{ index: index, displayName: displayName, slug: slug, }) } sort.SliceStable(pending, func(i, j int) bool { left := strings.ToLower(pending[i].displayName) right := strings.ToLower(pending[j].displayName) if left == right { return pending[i].slug < pending[j].slug } return left < right }) for rank, entry := range pending { result[entry.index]["priority"] = basePriority + 100*(rank+1) }} func loadCodexClientModelTemplates() (map[string]map[string]any, map[string]any, error) { raw, revision := registry.GetCodexClientModelsSnapshot() return loadCodexClientModelTemplatesSnapshot(raw, revision)} func loadCodexClientModelTemplatesSnapshot(raw []byte, revision uint64) (map[string]map[string]any, map[string]any, error) { codexClientModelTemplatesMu.Lock() defer codexClientModelTemplatesMu.Unlock() if codexClientModelTemplatesLoaded && codexClientModelTemplatesRevision == revision { return codexClientModelTemplates, codexClientDefaultTemplate, codexClientModelTemplatesErr } var payload codexClientModelsPayload err := json.Unmarshal(raw, &payload) var templates map[string]map[string]any var defaultTemplate map[string]any if err == nil { templates = make(map[string]map[string]any, len(payload.Models)) for _, model := range payload.Models { slug := strings.TrimSpace(stringModelValue(model, "slug")) if slug == "" { continue } templates[slug] = cloneCodexClientModelMap(model) if slug == "gpt-5.5" { defaultTemplate = cloneCodexClientModelMap(model) } } } codexClientModelTemplatesLoaded = true codexClientModelTemplatesRevision = revision codexClientModelTemplates = templates codexClientDefaultTemplate = defaultTemplate codexClientModelTemplatesErr = err return codexClientModelTemplates, codexClientDefaultTemplate, codexClientModelTemplatesErr} func applyCodexClientDisplayName(entry map[string]any, model map[string]any) { if displayName := stringModelValue(model, "display_name"); displayName != "" { entry["display_name"] = displayName }} func applyCodexClientSearchToolSupport(entry map[string]any, id string, templateModel bool, providersForModel ProvidersForModelFunc) { supportsSearch, _ := entry["supports_search_tool"].(bool) if !supportsSearch { return } if !templateModel { entry["supports_search_tool"] = false return } if providersForModel == nil { return } providers := providersForModel(id) if len(providers) == 0 { entry["supports_search_tool"] = false return } for _, provider := range providers { if !strings.EqualFold(strings.TrimSpace(provider), "codex") { entry["supports_search_tool"] = false return } }} func applyCodexClientModelMetadata(entry map[string]any, id string, model map[string]any, optimizeMultiAgentV2 bool) { info := registry.LookupModelInfo(id) displayName := stringModelValue(model, "display_name") description := stringModelValue(model, "description") contextWindow := intModelValue(model, "context_length") if info != nil { if info.DisplayName != "" { displayName = info.DisplayName } if info.Description != "" { description = info.Description } if info.ContextLength > 0 { contextWindow = info.ContextLength } if info.Type == registry.OpenAIImageModelType { entry["visibility"] = "hide" delete(entry, "input_modalities") delete(entry, "supports_image_detail_original") } else { applyCodexClientInputModalitiesMetadata(entry, info.SupportedInputModalities) } applyCodexClientThinkingMetadata(entry, info.Thinking) } if displayName == "" { displayName = id } if description == "" { description = id } entry["slug"] = id entry["display_name"] = displayName entry["description"] = description entry["prefer_websockets"] = false if optimizeMultiAgentV2 { entry["multi_agent_version"] = "v2" } entry["service_tiers"] = []any{} delete(entry, "apply_patch_tool_type") delete(entry, "upgrade") delete(entry, "availability_nux") if contextWindow > 0 { entry["context_window"] = contextWindow entry["max_context_window"] = contextWindow } if baseInstructions := stringModelValue(model, "base_instructions"); baseInstructions != "" { entry["base_instructions"] = baseInstructions } if plans, ok := model["available_in_plans"]; ok { entry["available_in_plans"] = cloneCodexClientModelValue(plans) }} func applyCodexClientVisibilityOverride(entry map[string]any, id string) { switch strings.TrimSpace(id) { case "grok-imagine-image-quality", "gpt-image-1.5", "gpt-image-2", "grok-imagine-image", "grok-imagine-video", "grok-imagine-video-1.5-preview": entry["visibility"] = "hide" }} func applyCodexClientInputModalitiesMetadata(entry map[string]any, modalities []string) { if len(modalities) == 0 { return } // Codex client only accepts text/image input modalities. codexModalities := make([]any, 0, 2) seen := make(map[string]struct{}, 2) supportsImage := false for _, raw := range modalities { switch modality := strings.ToLower(strings.TrimSpace(raw)); modality { case "text", "image": if _, ok := seen[modality]; ok { continue } seen[modality] = struct{}{} codexModalities = append(codexModalities, modality) if modality == "image" { supportsImage = true } } } if len(codexModalities) == 0 { return } entry["input_modalities"] = codexModalities if supportsImage { entry["supports_image_detail_original"] = true } else { delete(entry, "supports_image_detail_original") }} func applyCodexClientThinkingMetadata(entry map[string]any, thinking *registry.ThinkingSupport) { if thinking == nil || len(thinking.Levels) == 0 { return } levels := make([]any, 0, len(thinking.Levels)) defaultLevel := "" firstLevel := "" for _, rawLevel := range thinking.Levels { level := normalizeCodexClientReasoningLevel(rawLevel) if level == "" { continue } if firstLevel == "" { firstLevel = level } if (defaultLevel == "" && level != "none") || level == "medium" { defaultLevel = level } levels = append(levels, map[string]any{ "effort": level, "description": codexClientReasoningDescription(level), }) } if len(levels) == 0 { return } if defaultLevel == "" { defaultLevel = firstLevel } entry["supported_reasoning_levels"] = levels entry["default_reasoning_level"] = defaultLevel} func sanitizeCodexClientReasoningMetadata(entry map[string]any) { rawLevels, ok := entry["supported_reasoning_levels"].([]any) if !ok { return } levels := make([]any, 0, len(rawLevels)) allowedDefaults := make(map[string]struct{}, len(rawLevels)) for _, rawLevelEntry := range rawLevels { levelEntry, ok := rawLevelEntry.(map[string]any) if !ok { continue } level := normalizeCodexClientReasoningLevel(stringModelValue(levelEntry, "effort")) if level == "" { continue } clonedEntry := cloneCodexClientModelMap(levelEntry) clonedEntry["effort"] = level levels = append(levels, clonedEntry) allowedDefaults[level] = struct{}{} } if len(levels) == 0 { delete(entry, "supported_reasoning_levels") delete(entry, "default_reasoning_level") return } defaultLevel := normalizeCodexClientReasoningLevel(stringModelValue(entry, "default_reasoning_level")) if _, ok := allowedDefaults[defaultLevel]; !ok { defaultLevel = stringModelValue(levels[0].(map[string]any), "effort") } entry["supported_reasoning_levels"] = levels entry["default_reasoning_level"] = defaultLevel} func normalizeCodexClientReasoningLevel(rawLevel string) string { level := strings.ToLower(strings.TrimSpace(rawLevel)) if _, ok := codexClientAllowedReasoningLevels[level]; !ok { return "" } return level} func codexClientReasoningDescription(level string) string { switch level { case "none": return "No reasoning" case "minimal": return "Fastest responses with minimal reasoning" case "low": return "Fast responses with lighter reasoning" case "medium": return "Balances speed and reasoning depth for everyday tasks" case "high": return "Greater reasoning depth for complex problems" case "xhigh": return "Extra high reasoning depth for complex problems" case "max": return "Maximum available reasoning depth for complex problems" default: return level }} func codexClientModelPriority(model map[string]any) int { if priority, ok := model["priority"].(int); ok { return priority } if priority, ok := model["priority"].(float64); ok { return int(priority) } return 100} func stringModelValue(model map[string]any, key string) string { if model == nil { return "" } value, ok := model[key] if !ok { return "" } if s, ok := value.(string); ok { return strings.TrimSpace(s) } return ""} func intModelValue(model map[string]any, key string) int { if model == nil { return 0 } switch value := model[key].(type) { case int: return value case int64: return int(value) case float64: return int(value) default: return 0 }} func cloneCodexClientModelMap(model map[string]any) map[string]any { if model == nil { return nil } cloned := make(map[string]any, len(model)) for key, value := range model { cloned[key] = cloneCodexClientModelValue(value) } return cloned} func cloneCodexClientModelValue(value any) any { switch typed := value.(type) { case map[string]any: return cloneCodexClientModelMap(typed) case []any: cloned := make([]any, len(typed)) for i, entry := range typed { cloned[i] = cloneCodexClientModelValue(entry) } return cloned case []string: return append([]string(nil), typed...) default: return value }}
// Package models builds model catalogs for official Codex clients.
package models
import (
"encoding/json"
"sort"
"strings"
"sync"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
type codexClientModelsPayload struct {
Models []map[string]any `json:"models"`
}
// ProvidersForModelFunc returns the providers registered for a model.
type ProvidersForModelFunc func(string) []string
var (
codexClientModelTemplatesMu sync.Mutex
codexClientModelTemplatesLoaded bool
codexClientModelTemplatesRevision uint64
codexClientModelTemplates map[string]map[string]any
codexClientDefaultTemplate map[string]any
codexClientModelTemplatesErr error
)
var codexClientAllowedReasoningLevels = map[string]struct{}{
"none": {},
"minimal": {},
"low": {},
"medium": {},
"high": {},
"xhigh": {},
"max": {},
"ultra": {},
}
// BuildResponse builds a Codex client model response from available models.
func BuildResponse(availableModels []map[string]any, providersForModel ProvidersForModelFunc, optimizeMultiAgentV2 bool) map[string]any {
return map[string]any{
"models": buildCodexClientModels(availableModels, providersForModel, optimizeMultiAgentV2),
}
}
func buildCodexClientModels(models []map[string]any, providersForModel ProvidersForModelFunc, optimizeMultiAgentV2 bool) []map[string]any {
templates, defaultTemplate, err := loadCodexClientModelTemplates()
if err != nil || defaultTemplate == nil {
return nil
}
result := make([]map[string]any, 0, len(models))
for _, model := range models {
id := strings.TrimSpace(stringModelValue(model, "id"))
if id == "" {
continue
}
if template, ok := templates[id]; ok {
entry := cloneCodexClientModelMap(template)
applyCodexClientDisplayName(entry, model)
applyCodexClientSearchToolSupport(entry, id, true, providersForModel)
sanitizeCodexClientReasoningMetadata(entry)
applyCodexClientVisibilityOverride(entry, id)
result = append(result, entry)
continue
}
entry := cloneCodexClientModelMap(defaultTemplate)
applyCodexClientModelMetadata(entry, id, model, optimizeMultiAgentV2)
applyCodexClientSearchToolSupport(entry, id, false, providersForModel)
sanitizeCodexClientReasoningMetadata(entry)
applyCodexClientVisibilityOverride(entry, id)
result = append(result, entry)
}
applyCodexClientNonTemplatePriorities(result, templates)
sort.SliceStable(result, func(i, j int) bool {
return codexClientModelPriority(result[i]) < codexClientModelPriority(result[j])
})
return result
}
func maxCodexClientTemplatePriority(templates map[string]map[string]any) int {
maxPriority := 0
for _, template := range templates {
priority := codexClientModelPriority(template)
if priority > maxPriority {
maxPriority = priority
}
}
return maxPriority
}
func applyCodexClientNonTemplatePriorities(result []map[string]any, templates map[string]map[string]any) {
if len(result) == 0 {
return
}
basePriority := maxCodexClientTemplatePriority(templates)
type nonTemplateEntry struct {
index int
displayName string
slug string
}
pending := make([]nonTemplateEntry, 0)
for index, entry := range result {
slug := stringModelValue(entry, "slug")
if _, ok := templates[slug]; ok {
continue
}
displayName := stringModelValue(entry, "display_name")
if displayName == "" {
displayName = slug
}
pending = append(pending, nonTemplateEntry{
index: index,
displayName: displayName,
slug: slug,
})
}
sort.SliceStable(pending, func(i, j int) bool {
left := strings.ToLower(pending[i].displayName)
right := strings.ToLower(pending[j].displayName)
if left == right {
return pending[i].slug < pending[j].slug
}
return left < right
})
for rank, entry := range pending {
result[entry.index]["priority"] = basePriority + 100*(rank+1)
}
}
func loadCodexClientModelTemplates() (map[string]map[string]any, map[string]any, error) {
raw, revision := registry.GetCodexClientModelsSnapshot()
return loadCodexClientModelTemplatesSnapshot(raw, revision)
}
func loadCodexClientModelTemplatesSnapshot(raw []byte, revision uint64) (map[string]map[string]any, map[string]any, error) {
codexClientModelTemplatesMu.Lock()
defer codexClientModelTemplatesMu.Unlock()
if codexClientModelTemplatesLoaded && codexClientModelTemplatesRevision == revision {
return codexClientModelTemplates, codexClientDefaultTemplate, codexClientModelTemplatesErr
}
var payload codexClientModelsPayload
err := json.Unmarshal(raw, &payload)
var templates map[string]map[string]any
var defaultTemplate map[string]any
if err == nil {
templates = make(map[string]map[string]any, len(payload.Models))
for _, model := range payload.Models {
slug := strings.TrimSpace(stringModelValue(model, "slug"))
if slug == "" {
continue
}
templates[slug] = cloneCodexClientModelMap(model)
if slug == "gpt-5.5" {
defaultTemplate = cloneCodexClientModelMap(model)
}
}
}
codexClientModelTemplatesLoaded = true
codexClientModelTemplatesRevision = revision
codexClientModelTemplates = templates
codexClientDefaultTemplate = defaultTemplate
codexClientModelTemplatesErr = err
return codexClientModelTemplates, codexClientDefaultTemplate, codexClientModelTemplatesErr
}
func applyCodexClientDisplayName(entry map[string]any, model map[string]any) {
if displayName := stringModelValue(model, "display_name"); displayName != "" {
entry["display_name"] = displayName
}
}
func applyCodexClientSearchToolSupport(entry map[string]any, id string, templateModel bool, providersForModel ProvidersForModelFunc) {
supportsSearch, _ := entry["supports_search_tool"].(bool)
if !supportsSearch {
return
}
if !templateModel {
entry["supports_search_tool"] = false
return
}
if providersForModel == nil {
return
}
providers := providersForModel(id)
if len(providers) == 0 {
entry["supports_search_tool"] = false
return
}
for _, provider := range providers {
if !strings.EqualFold(strings.TrimSpace(provider), "codex") {
entry["supports_search_tool"] = false
return
}
}
}
func applyCodexClientModelMetadata(entry map[string]any, id string, model map[string]any, optimizeMultiAgentV2 bool) {
info := registry.LookupModelInfo(id)
displayName := stringModelValue(model, "display_name")
description := stringModelValue(model, "description")
contextWindow := intModelValue(model, "context_length")
if info != nil {
if info.DisplayName != "" {
displayName = info.DisplayName
}
if info.Description != "" {
description = info.Description
}
if info.ContextLength > 0 {
contextWindow = info.ContextLength
}
if info.Type == registry.OpenAIImageModelType {
entry["visibility"] = "hide"
delete(entry, "input_modalities")
delete(entry, "supports_image_detail_original")
} else {
applyCodexClientInputModalitiesMetadata(entry, info.SupportedInputModalities)
}
applyCodexClientThinkingMetadata(entry, info.Thinking)
}
if displayName == "" {
displayName = id
}
if description == "" {
description = id
}
entry["slug"] = id
entry["display_name"] = displayName
entry["description"] = description
entry["prefer_websockets"] = false
if optimizeMultiAgentV2 {
entry["multi_agent_version"] = "v2"
}
entry["service_tiers"] = []any{}
delete(entry, "apply_patch_tool_type")
delete(entry, "upgrade")
delete(entry, "availability_nux")
if contextWindow > 0 {
entry["context_window"] = contextWindow
entry["max_context_window"] = contextWindow
}
if baseInstructions := stringModelValue(model, "base_instructions"); baseInstructions != "" {
entry["base_instructions"] = baseInstructions
}
if plans, ok := model["available_in_plans"]; ok {
entry["available_in_plans"] = cloneCodexClientModelValue(plans)
}
}
func applyCodexClientVisibilityOverride(entry map[string]any, id string) {
switch strings.TrimSpace(id) {
case "grok-imagine-image-quality", "gpt-image-1.5", "gpt-image-2", "grok-imagine-image", "grok-imagine-video", "grok-imagine-video-1.5-preview":
entry["visibility"] = "hide"
}
}
func applyCodexClientInputModalitiesMetadata(entry map[string]any, modalities []string) {
if len(modalities) == 0 {
return
}
// Codex client only accepts text/image input modalities.
codexModalities := make([]any, 0, 2)
seen := make(map[string]struct{}, 2)
supportsImage := false
for _, raw := range modalities {
switch modality := strings.ToLower(strings.TrimSpace(raw)); modality {
case "text", "image":
if _, ok := seen[modality]; ok {
continue
}
seen[modality] = struct{}{}
codexModalities = append(codexModalities, modality)
if modality == "image" {
supportsImage = true
}
}
}
if len(codexModalities) == 0 {
return
}
entry["input_modalities"] = codexModalities
if supportsImage {
entry["supports_image_detail_original"] = true
} else {
delete(entry, "supports_image_detail_original")
}
}
func applyCodexClientThinkingMetadata(entry map[string]any, thinking *registry.ThinkingSupport) {
if thinking == nil || len(thinking.Levels) == 0 {
return
}
levels := make([]any, 0, len(thinking.Levels))
defaultLevel := ""
firstLevel := ""
for _, rawLevel := range thinking.Levels {
level := normalizeCodexClientReasoningLevel(rawLevel)
if level == "" {
continue
}
if firstLevel == "" {
firstLevel = level
}
if (defaultLevel == "" && level != "none") || level == "medium" {
defaultLevel = level
}
levels = append(levels, map[string]any{
"effort": level,
"description": codexClientReasoningDescription(level),
})
}
if len(levels) == 0 {
return
}
if defaultLevel == "" {
defaultLevel = firstLevel
}
entry["supported_reasoning_levels"] = levels
entry["default_reasoning_level"] = defaultLevel
}
func sanitizeCodexClientReasoningMetadata(entry map[string]any) {
rawLevels, ok := entry["supported_reasoning_levels"].([]any)
if !ok {
return
}
levels := make([]any, 0, len(rawLevels))
allowedDefaults := make(map[string]struct{}, len(rawLevels))
for _, rawLevelEntry := range rawLevels {
levelEntry, ok := rawLevelEntry.(map[string]any)
if !ok {
continue
}
level := normalizeCodexClientReasoningLevel(stringModelValue(levelEntry, "effort"))
if level == "" {
continue
}
clonedEntry := cloneCodexClientModelMap(levelEntry)
clonedEntry["effort"] = level
levels = append(levels, clonedEntry)
allowedDefaults[level] = struct{}{}
}
if len(levels) == 0 {
delete(entry, "supported_reasoning_levels")
delete(entry, "default_reasoning_level")
return
}
defaultLevel := normalizeCodexClientReasoningLevel(stringModelValue(entry, "default_reasoning_level"))
if _, ok := allowedDefaults[defaultLevel]; !ok {
defaultLevel = stringModelValue(levels[0].(map[string]any), "effort")
}
entry["supported_reasoning_levels"] = levels
entry["default_reasoning_level"] = defaultLevel
}
func normalizeCodexClientReasoningLevel(rawLevel string) string {
level := strings.ToLower(strings.TrimSpace(rawLevel))
if _, ok := codexClientAllowedReasoningLevels[level]; !ok {
return ""
}
return level
}
func codexClientReasoningDescription(level string) string {
switch level {
case "none":
return "No reasoning"
case "minimal":
return "Fastest responses with minimal reasoning"
case "low":
return "Fast responses with lighter reasoning"
case "medium":
return "Balances speed and reasoning depth for everyday tasks"
case "high":
return "Greater reasoning depth for complex problems"
case "xhigh":
return "Extra high reasoning depth for complex problems"
case "max":
return "Maximum available reasoning depth for complex problems"
default:
return level
}
}
func codexClientModelPriority(model map[string]any) int {
if priority, ok := model["priority"].(int); ok {
return priority
}
if priority, ok := model["priority"].(float64); ok {
return int(priority)
}
return 100
}
func stringModelValue(model map[string]any, key string) string {
if model == nil {
return ""
}
value, ok := model[key]
if !ok {
return ""
}
if s, ok := value.(string); ok {
return strings.TrimSpace(s)
}
return ""
}
func intModelValue(model map[string]any, key string) int {
if model == nil {
return 0
}
switch value := model[key].(type) {
case int:
return value
case int64:
return int(value)
case float64:
return int(value)
default:
return 0
}
}
func cloneCodexClientModelMap(model map[string]any) map[string]any {
if model == nil {
return nil
}
cloned := make(map[string]any, len(model))
for key, value := range model {
cloned[key] = cloneCodexClientModelValue(value)
}
return cloned
}
func cloneCodexClientModelValue(value any) any {
switch typed := value.(type) {
case map[string]any:
return cloneCodexClientModelMap(typed)
case []any:
cloned := make([]any, len(typed))
for i, entry := range typed {
cloned[i] = cloneCodexClientModelValue(entry)
}
return cloned
case []string:
return append([]string(nil), typed...)
default:
return value
}
}