mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
feat(cliproxy): implement model aliasing and hashing for Codex configurations, enhance request routing logic, and normalize Codex model entries
This commit is contained in:
@@ -104,6 +104,9 @@ ws-auth: false
|
|||||||
# headers:
|
# headers:
|
||||||
# X-Custom-Header: "custom-value"
|
# X-Custom-Header: "custom-value"
|
||||||
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
# models:
|
||||||
|
# - name: "gpt-5-codex" # upstream model name
|
||||||
|
# alias: "codex-latest" # client alias mapped to the upstream model
|
||||||
# excluded-models:
|
# excluded-models:
|
||||||
# - "gpt-5.1" # exclude specific models (exact match)
|
# - "gpt-5.1" # exclude specific models (exact match)
|
||||||
# - "gpt-5-*" # wildcard matching prefix (e.g. gpt-5-medium, gpt-5-codex)
|
# - "gpt-5-*" # wildcard matching prefix (e.g. gpt-5-medium, gpt-5-codex)
|
||||||
|
|||||||
@@ -597,11 +597,7 @@ func (h *Handler) PutCodexKeys(c *gin.Context) {
|
|||||||
filtered := make([]config.CodexKey, 0, len(arr))
|
filtered := make([]config.CodexKey, 0, len(arr))
|
||||||
for i := range arr {
|
for i := range arr {
|
||||||
entry := arr[i]
|
entry := arr[i]
|
||||||
entry.APIKey = strings.TrimSpace(entry.APIKey)
|
normalizeCodexKey(&entry)
|
||||||
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
|
||||||
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
|
||||||
entry.Headers = config.NormalizeHeaders(entry.Headers)
|
|
||||||
entry.ExcludedModels = config.NormalizeExcludedModels(entry.ExcludedModels)
|
|
||||||
if entry.BaseURL == "" {
|
if entry.BaseURL == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -617,6 +613,7 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
|
|||||||
Prefix *string `json:"prefix"`
|
Prefix *string `json:"prefix"`
|
||||||
BaseURL *string `json:"base-url"`
|
BaseURL *string `json:"base-url"`
|
||||||
ProxyURL *string `json:"proxy-url"`
|
ProxyURL *string `json:"proxy-url"`
|
||||||
|
Models *[]config.CodexModel `json:"models"`
|
||||||
Headers *map[string]string `json:"headers"`
|
Headers *map[string]string `json:"headers"`
|
||||||
ExcludedModels *[]string `json:"excluded-models"`
|
ExcludedModels *[]string `json:"excluded-models"`
|
||||||
}
|
}
|
||||||
@@ -667,12 +664,16 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
|
|||||||
if body.Value.ProxyURL != nil {
|
if body.Value.ProxyURL != nil {
|
||||||
entry.ProxyURL = strings.TrimSpace(*body.Value.ProxyURL)
|
entry.ProxyURL = strings.TrimSpace(*body.Value.ProxyURL)
|
||||||
}
|
}
|
||||||
|
if body.Value.Models != nil {
|
||||||
|
entry.Models = append([]config.CodexModel(nil), (*body.Value.Models)...)
|
||||||
|
}
|
||||||
if body.Value.Headers != nil {
|
if body.Value.Headers != nil {
|
||||||
entry.Headers = config.NormalizeHeaders(*body.Value.Headers)
|
entry.Headers = config.NormalizeHeaders(*body.Value.Headers)
|
||||||
}
|
}
|
||||||
if body.Value.ExcludedModels != nil {
|
if body.Value.ExcludedModels != nil {
|
||||||
entry.ExcludedModels = config.NormalizeExcludedModels(*body.Value.ExcludedModels)
|
entry.ExcludedModels = config.NormalizeExcludedModels(*body.Value.ExcludedModels)
|
||||||
}
|
}
|
||||||
|
normalizeCodexKey(&entry)
|
||||||
h.cfg.CodexKey[targetIndex] = entry
|
h.cfg.CodexKey[targetIndex] = entry
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persist(c)
|
||||||
@@ -762,6 +763,32 @@ func normalizeClaudeKey(entry *config.ClaudeKey) {
|
|||||||
entry.Models = normalized
|
entry.Models = normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeCodexKey(entry *config.CodexKey) {
|
||||||
|
if entry == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entry.APIKey = strings.TrimSpace(entry.APIKey)
|
||||||
|
entry.Prefix = strings.TrimSpace(entry.Prefix)
|
||||||
|
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
||||||
|
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
||||||
|
entry.Headers = config.NormalizeHeaders(entry.Headers)
|
||||||
|
entry.ExcludedModels = config.NormalizeExcludedModels(entry.ExcludedModels)
|
||||||
|
if len(entry.Models) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
normalized := make([]config.CodexModel, 0, len(entry.Models))
|
||||||
|
for i := range entry.Models {
|
||||||
|
model := entry.Models[i]
|
||||||
|
model.Name = strings.TrimSpace(model.Name)
|
||||||
|
model.Alias = strings.TrimSpace(model.Alias)
|
||||||
|
if model.Name == "" && model.Alias == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
normalized = append(normalized, model)
|
||||||
|
}
|
||||||
|
entry.Models = normalized
|
||||||
|
}
|
||||||
|
|
||||||
// GetAmpCode returns the complete ampcode configuration.
|
// GetAmpCode returns the complete ampcode configuration.
|
||||||
func (h *Handler) GetAmpCode(c *gin.Context) {
|
func (h *Handler) GetAmpCode(c *gin.Context) {
|
||||||
if h == nil || h.cfg == nil {
|
if h == nil || h.cfg == nil {
|
||||||
|
|||||||
@@ -253,6 +253,9 @@ type CodexKey struct {
|
|||||||
// ProxyURL overrides the global proxy setting for this API key if provided.
|
// ProxyURL overrides the global proxy setting for this API key if provided.
|
||||||
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
||||||
|
|
||||||
|
// Models defines upstream model names and aliases for request routing.
|
||||||
|
Models []CodexModel `yaml:"models" json:"models"`
|
||||||
|
|
||||||
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
||||||
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
|
|
||||||
@@ -260,6 +263,15 @@ type CodexKey struct {
|
|||||||
ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"`
|
ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CodexModel describes a mapping between an alias and the actual upstream model name.
|
||||||
|
type CodexModel struct {
|
||||||
|
// Name is the upstream model identifier used when issuing requests.
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
|
||||||
|
// Alias is the client-facing model name that maps to Name.
|
||||||
|
Alias string `yaml:"alias" json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
// GeminiKey represents the configuration for a Gemini API key,
|
// GeminiKey represents the configuration for a Gemini API key,
|
||||||
// including optional overrides for upstream base URL, proxy routing, and headers.
|
// including optional overrides for upstream base URL, proxy routing, and headers.
|
||||||
type GeminiKey struct {
|
type GeminiKey struct {
|
||||||
|
|||||||
@@ -50,6 +50,16 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
|||||||
defer reporter.trackFailure(ctx, &err)
|
defer reporter.trackFailure(ctx, &err)
|
||||||
|
|
||||||
upstreamModel := util.ResolveOriginalModel(req.Model, req.Metadata)
|
upstreamModel := util.ResolveOriginalModel(req.Model, req.Metadata)
|
||||||
|
if upstreamModel == "" {
|
||||||
|
upstreamModel = req.Model
|
||||||
|
}
|
||||||
|
if modelOverride := e.resolveUpstreamModel(upstreamModel, auth); modelOverride != "" {
|
||||||
|
upstreamModel = modelOverride
|
||||||
|
} else if !strings.EqualFold(upstreamModel, req.Model) {
|
||||||
|
if modelOverride := e.resolveUpstreamModel(req.Model, auth); modelOverride != "" {
|
||||||
|
upstreamModel = modelOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
from := opts.SourceFormat
|
from := opts.SourceFormat
|
||||||
to := sdktranslator.FromString("codex")
|
to := sdktranslator.FromString("codex")
|
||||||
@@ -147,6 +157,16 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
defer reporter.trackFailure(ctx, &err)
|
defer reporter.trackFailure(ctx, &err)
|
||||||
|
|
||||||
upstreamModel := util.ResolveOriginalModel(req.Model, req.Metadata)
|
upstreamModel := util.ResolveOriginalModel(req.Model, req.Metadata)
|
||||||
|
if upstreamModel == "" {
|
||||||
|
upstreamModel = req.Model
|
||||||
|
}
|
||||||
|
if modelOverride := e.resolveUpstreamModel(upstreamModel, auth); modelOverride != "" {
|
||||||
|
upstreamModel = modelOverride
|
||||||
|
} else if !strings.EqualFold(upstreamModel, req.Model) {
|
||||||
|
if modelOverride := e.resolveUpstreamModel(req.Model, auth); modelOverride != "" {
|
||||||
|
upstreamModel = modelOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
from := opts.SourceFormat
|
from := opts.SourceFormat
|
||||||
to := sdktranslator.FromString("codex")
|
to := sdktranslator.FromString("codex")
|
||||||
@@ -247,12 +267,22 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
|
|
||||||
func (e *CodexExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
func (e *CodexExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||||
upstreamModel := util.ResolveOriginalModel(req.Model, req.Metadata)
|
upstreamModel := util.ResolveOriginalModel(req.Model, req.Metadata)
|
||||||
|
if upstreamModel == "" {
|
||||||
|
upstreamModel = req.Model
|
||||||
|
}
|
||||||
|
if modelOverride := e.resolveUpstreamModel(upstreamModel, auth); modelOverride != "" {
|
||||||
|
upstreamModel = modelOverride
|
||||||
|
} else if !strings.EqualFold(upstreamModel, req.Model) {
|
||||||
|
if modelOverride := e.resolveUpstreamModel(req.Model, auth); modelOverride != "" {
|
||||||
|
upstreamModel = modelOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
from := opts.SourceFormat
|
from := opts.SourceFormat
|
||||||
to := sdktranslator.FromString("codex")
|
to := sdktranslator.FromString("codex")
|
||||||
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||||
|
|
||||||
modelForCounting := req.Model
|
modelForCounting := upstreamModel
|
||||||
|
|
||||||
body = ApplyReasoningEffortMetadata(body, req.Metadata, req.Model, "reasoning.effort", false)
|
body = ApplyReasoningEffortMetadata(body, req.Metadata, req.Model, "reasoning.effort", false)
|
||||||
body, _ = sjson.SetBytes(body, "model", upstreamModel)
|
body, _ = sjson.SetBytes(body, "model", upstreamModel)
|
||||||
@@ -520,3 +550,87 @@ func codexCreds(a *cliproxyauth.Auth) (apiKey, baseURL string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *CodexExecutor) resolveUpstreamModel(alias string, auth *cliproxyauth.Auth) string {
|
||||||
|
trimmed := strings.TrimSpace(alias)
|
||||||
|
if trimmed == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := e.resolveCodexConfig(auth)
|
||||||
|
if entry == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedModel, metadata := util.NormalizeThinkingModel(trimmed)
|
||||||
|
|
||||||
|
// Candidate names to match against configured aliases/names.
|
||||||
|
candidates := []string{strings.TrimSpace(normalizedModel)}
|
||||||
|
if !strings.EqualFold(normalizedModel, trimmed) {
|
||||||
|
candidates = append(candidates, trimmed)
|
||||||
|
}
|
||||||
|
if original := util.ResolveOriginalModel(normalizedModel, metadata); original != "" && !strings.EqualFold(original, normalizedModel) {
|
||||||
|
candidates = append(candidates, original)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range entry.Models {
|
||||||
|
model := entry.Models[i]
|
||||||
|
name := strings.TrimSpace(model.Name)
|
||||||
|
modelAlias := strings.TrimSpace(model.Alias)
|
||||||
|
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
if candidate == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if modelAlias != "" && strings.EqualFold(modelAlias, candidate) {
|
||||||
|
if name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
if name != "" && strings.EqualFold(name, candidate) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CodexExecutor) resolveCodexConfig(auth *cliproxyauth.Auth) *config.CodexKey {
|
||||||
|
if auth == nil || e.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var attrKey, attrBase string
|
||||||
|
if auth.Attributes != nil {
|
||||||
|
attrKey = strings.TrimSpace(auth.Attributes["api_key"])
|
||||||
|
attrBase = strings.TrimSpace(auth.Attributes["base_url"])
|
||||||
|
}
|
||||||
|
for i := range e.cfg.CodexKey {
|
||||||
|
entry := &e.cfg.CodexKey[i]
|
||||||
|
cfgKey := strings.TrimSpace(entry.APIKey)
|
||||||
|
cfgBase := strings.TrimSpace(entry.BaseURL)
|
||||||
|
if attrKey != "" && attrBase != "" {
|
||||||
|
if strings.EqualFold(cfgKey, attrKey) && strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if attrKey != "" && strings.EqualFold(cfgKey, attrKey) {
|
||||||
|
if cfgBase == "" || strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if attrKey == "" && attrBase != "" && strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if attrKey != "" {
|
||||||
|
for i := range e.cfg.CodexKey {
|
||||||
|
entry := &e.cfg.CodexKey[i]
|
||||||
|
if strings.EqualFold(strings.TrimSpace(entry.APIKey), attrKey) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,21 @@ func ComputeClaudeModelsHash(models []config.ClaudeModel) string {
|
|||||||
return hashJoined(keys)
|
return hashJoined(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ComputeCodexModelsHash returns a stable hash for Codex model aliases.
|
||||||
|
func ComputeCodexModelsHash(models []config.CodexModel) string {
|
||||||
|
keys := normalizeModelPairs(func(out func(key string)) {
|
||||||
|
for _, model := range models {
|
||||||
|
name := strings.TrimSpace(model.Name)
|
||||||
|
alias := strings.TrimSpace(model.Alias)
|
||||||
|
if name == "" && alias == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out(strings.ToLower(name) + "|" + strings.ToLower(alias))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return hashJoined(keys)
|
||||||
|
}
|
||||||
|
|
||||||
// ComputeExcludedModelsHash returns a normalized hash for excluded model lists.
|
// ComputeExcludedModelsHash returns a normalized hash for excluded model lists.
|
||||||
func ComputeExcludedModelsHash(excluded []string) string {
|
func ComputeExcludedModelsHash(excluded []string) string {
|
||||||
if len(excluded) == 0 {
|
if len(excluded) == 0 {
|
||||||
|
|||||||
@@ -81,6 +81,15 @@ func TestComputeClaudeModelsHash_Empty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestComputeCodexModelsHash_Empty(t *testing.T) {
|
||||||
|
if got := ComputeCodexModelsHash(nil); got != "" {
|
||||||
|
t.Fatalf("expected empty hash for nil models, got %q", got)
|
||||||
|
}
|
||||||
|
if got := ComputeCodexModelsHash([]config.CodexModel{}); got != "" {
|
||||||
|
t.Fatalf("expected empty hash for empty slice, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestComputeClaudeModelsHash_IgnoresBlankAndDedup(t *testing.T) {
|
func TestComputeClaudeModelsHash_IgnoresBlankAndDedup(t *testing.T) {
|
||||||
a := []config.ClaudeModel{
|
a := []config.ClaudeModel{
|
||||||
{Name: "m1", Alias: "a1"},
|
{Name: "m1", Alias: "a1"},
|
||||||
@@ -95,6 +104,20 @@ func TestComputeClaudeModelsHash_IgnoresBlankAndDedup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestComputeCodexModelsHash_IgnoresBlankAndDedup(t *testing.T) {
|
||||||
|
a := []config.CodexModel{
|
||||||
|
{Name: "m1", Alias: "a1"},
|
||||||
|
{Name: " "},
|
||||||
|
{Name: "M1", Alias: "A1"},
|
||||||
|
}
|
||||||
|
b := []config.CodexModel{
|
||||||
|
{Name: "m1", Alias: "a1"},
|
||||||
|
}
|
||||||
|
if h1, h2 := ComputeCodexModelsHash(a), ComputeCodexModelsHash(b); h1 == "" || h1 != h2 {
|
||||||
|
t.Fatalf("expected same hash ignoring blanks/dupes, got %q / %q", h1, h2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestComputeExcludedModelsHash_Normalizes(t *testing.T) {
|
func TestComputeExcludedModelsHash_Normalizes(t *testing.T) {
|
||||||
hash1 := ComputeExcludedModelsHash([]string{" A ", "b", "a"})
|
hash1 := ComputeExcludedModelsHash([]string{" A ", "b", "a"})
|
||||||
hash2 := ComputeExcludedModelsHash([]string{"a", " b", "A"})
|
hash2 := ComputeExcludedModelsHash([]string{"a", " b", "A"})
|
||||||
@@ -157,3 +180,15 @@ func TestComputeClaudeModelsHash_Deterministic(t *testing.T) {
|
|||||||
t.Fatalf("expected different hash when models change, got %s", h3)
|
t.Fatalf("expected different hash when models change, got %s", h3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestComputeCodexModelsHash_Deterministic(t *testing.T) {
|
||||||
|
models := []config.CodexModel{{Name: "a", Alias: "A"}, {Name: "b"}}
|
||||||
|
h1 := ComputeCodexModelsHash(models)
|
||||||
|
h2 := ComputeCodexModelsHash(models)
|
||||||
|
if h1 == "" || h1 != h2 {
|
||||||
|
t.Fatalf("expected deterministic hash, got %s / %s", h1, h2)
|
||||||
|
}
|
||||||
|
if h3 := ComputeCodexModelsHash([]config.CodexModel{{Name: "a"}}); h3 == h1 {
|
||||||
|
t.Fatalf("expected different hash when models change, got %s", h3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ func (s *ConfigSynthesizer) synthesizeCodexKeys(ctx *SynthesisContext) []*coreau
|
|||||||
if ck.BaseURL != "" {
|
if ck.BaseURL != "" {
|
||||||
attrs["base_url"] = ck.BaseURL
|
attrs["base_url"] = ck.BaseURL
|
||||||
}
|
}
|
||||||
|
if hash := diff.ComputeCodexModelsHash(ck.Models); hash != "" {
|
||||||
|
attrs["models_hash"] = hash
|
||||||
|
}
|
||||||
addConfigHeadersToAttrs(ck.Headers, attrs)
|
addConfigHeadersToAttrs(ck.Headers, attrs)
|
||||||
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
|
|||||||
@@ -741,6 +741,9 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
case "codex":
|
case "codex":
|
||||||
models = registry.GetOpenAIModels()
|
models = registry.GetOpenAIModels()
|
||||||
if entry := s.resolveConfigCodexKey(a); entry != nil {
|
if entry := s.resolveConfigCodexKey(a); entry != nil {
|
||||||
|
if len(entry.Models) > 0 {
|
||||||
|
models = buildCodexConfigModels(entry)
|
||||||
|
}
|
||||||
if authKind == "apikey" {
|
if authKind == "apikey" {
|
||||||
excluded = entry.ExcludedModels
|
excluded = entry.ExcludedModels
|
||||||
}
|
}
|
||||||
@@ -1179,3 +1182,41 @@ func buildClaudeConfigModels(entry *config.ClaudeKey) []*ModelInfo {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildCodexConfigModels(entry *config.CodexKey) []*ModelInfo {
|
||||||
|
if entry == nil || len(entry.Models) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
out := make([]*ModelInfo, 0, len(entry.Models))
|
||||||
|
seen := make(map[string]struct{}, len(entry.Models))
|
||||||
|
for i := range entry.Models {
|
||||||
|
model := entry.Models[i]
|
||||||
|
name := strings.TrimSpace(model.Name)
|
||||||
|
alias := strings.TrimSpace(model.Alias)
|
||||||
|
if alias == "" {
|
||||||
|
alias = name
|
||||||
|
}
|
||||||
|
if alias == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.ToLower(alias)
|
||||||
|
if _, exists := seen[key]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
display := name
|
||||||
|
if display == "" {
|
||||||
|
display = alias
|
||||||
|
}
|
||||||
|
out = append(out, &ModelInfo{
|
||||||
|
ID: alias,
|
||||||
|
Object: "model",
|
||||||
|
Created: now,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Type: "openai",
|
||||||
|
DisplayName: display,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user