mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-24 03:28:23 +08:00
- Added `applyCodexClientNonTemplatePriorities` to assign higher priorities to non-template Codex client models dynamically. - Implemented `maxCodexClientTemplatePriority` to set base priority for non-template models relative to template models. - Updated unit tests to validate priority calculation for custom models.
383 lines
9.5 KiB
Go
383 lines
9.5 KiB
Go
package openai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
|
)
|
|
|
|
type codexClientModelsPayload struct {
|
|
Models []map[string]any `json:"models"`
|
|
}
|
|
|
|
var (
|
|
codexClientModelTemplatesOnce sync.Once
|
|
codexClientModelTemplates map[string]map[string]any
|
|
codexClientDefaultTemplate map[string]any
|
|
codexClientModelTemplatesErr error
|
|
)
|
|
|
|
var codexClientAllowedReasoningLevels = map[string]struct{}{
|
|
"none": {},
|
|
"low": {},
|
|
"medium": {},
|
|
"high": {},
|
|
"xhigh": {},
|
|
"max": {},
|
|
}
|
|
|
|
func (h *OpenAIAPIHandler) codexClientModelsResponse() map[string]any {
|
|
return CodexClientModelsResponse(h.Models())
|
|
}
|
|
|
|
func CodexClientModelsResponse(models []map[string]any) map[string]any {
|
|
return map[string]any{
|
|
"models": buildCodexClientModels(models),
|
|
}
|
|
}
|
|
|
|
func buildCodexClientModels(models []map[string]any) []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)
|
|
sanitizeCodexClientReasoningMetadata(entry)
|
|
applyCodexClientVisibilityOverride(entry, id)
|
|
result = append(result, entry)
|
|
continue
|
|
}
|
|
|
|
entry := cloneCodexClientModelMap(defaultTemplate)
|
|
applyCodexClientModelMetadata(entry, id, model)
|
|
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) {
|
|
codexClientModelTemplatesOnce.Do(func() {
|
|
var payload codexClientModelsPayload
|
|
codexClientModelTemplatesErr = json.Unmarshal(registry.GetCodexClientModelsJSON(), &payload)
|
|
if codexClientModelTemplatesErr != nil {
|
|
return
|
|
}
|
|
|
|
codexClientModelTemplates = make(map[string]map[string]any, len(payload.Models))
|
|
for _, model := range payload.Models {
|
|
slug := strings.TrimSpace(stringModelValue(model, "slug"))
|
|
if slug == "" {
|
|
continue
|
|
}
|
|
codexClientModelTemplates[slug] = cloneCodexClientModelMap(model)
|
|
if slug == "gpt-5.5" {
|
|
codexClientDefaultTemplate = cloneCodexClientModelMap(model)
|
|
}
|
|
}
|
|
})
|
|
|
|
return codexClientModelTemplates, codexClientDefaultTemplate, codexClientModelTemplatesErr
|
|
}
|
|
|
|
func applyCodexClientModelMetadata(entry map[string]any, id string, model map[string]any) {
|
|
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"
|
|
}
|
|
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
|
|
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 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 "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
|
|
}
|
|
}
|