mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-08 01:16:47 +08:00
feat(models): add Claude client model catalog and response builder
- Introduced a new `models` package for organizing Claude client model templates and building responses. - Migrated Claude response handling to `claudemodels.BuildResponse`. - Added comprehensive tests for model ID transformation, sorting, and metadata validation. - Removed redundant utility functions and simplified integration with the API server.
This commit is contained in:
+3
-28
@@ -28,6 +28,7 @@ import (
|
||||
managementHandlers "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/api/middleware"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/cache"
|
||||
claudemodels "github.com/router-for-me/CLIProxyAPI/v7/internal/client/claude/models"
|
||||
codexmodels "github.com/router-for-me/CLIProxyAPI/v7/internal/client/codex/models"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
||||
@@ -1411,23 +1412,7 @@ func (s *Server) handleHomeModels(c *gin.Context) {
|
||||
isClaude := isAnthropicModelsRequest(c)
|
||||
|
||||
if isClaude {
|
||||
out := formatHomeClaudeModels(entries)
|
||||
firstID := ""
|
||||
lastID := ""
|
||||
if len(out) > 0 {
|
||||
if id, okID := out[0]["id"].(string); okID {
|
||||
firstID = id
|
||||
}
|
||||
if id, okID := out[len(out)-1]["id"].(string); okID {
|
||||
lastID = id
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": out,
|
||||
"has_more": false,
|
||||
"first_id": firstID,
|
||||
"last_id": lastID,
|
||||
})
|
||||
c.JSON(http.StatusOK, claudemodels.BuildResponse(formatHomeClaudeModels(entries)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1456,16 +1441,6 @@ func formatHomeClaudeModels(entries []homeModelEntry) []map[string]any {
|
||||
for _, entry := range entries {
|
||||
out = append(out, formatHomeClaudeModel(entry))
|
||||
}
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
di, _ := out[i]["display_name"].(string)
|
||||
dj, _ := out[j]["display_name"].(string)
|
||||
if di != dj {
|
||||
return di < dj
|
||||
}
|
||||
idi, _ := out[i]["id"].(string)
|
||||
idj, _ := out[j]["id"].(string)
|
||||
return idi < idj
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1483,7 +1458,7 @@ func formatHomeClaudeModel(entry homeModelEntry) map[string]any {
|
||||
maxOutput = registry.DefaultClaudeMaxOutputTokens
|
||||
}
|
||||
model := map[string]any{
|
||||
"id": util.EnsureClaudeModelIDPrefix(entry.id),
|
||||
"id": entry.id,
|
||||
"object": "model",
|
||||
"owned_by": entry.ownedBy,
|
||||
"type": "model",
|
||||
|
||||
@@ -1719,11 +1719,11 @@ func TestFormatHomeClaudeModelIncludesAnthropicSchemaFields(t *testing.T) {
|
||||
t.Fatalf("display_name fallback = %v, want claude-no-limits", got)
|
||||
}
|
||||
|
||||
prefixed := formatHomeClaudeModel(homeModelEntry{id: "gpt-4o", displayName: "GPT-4o"})
|
||||
if got := prefixed["id"]; got != "claude-fable-5-dd-o4-tpg" {
|
||||
t.Fatalf("id = %v, want claude-fable-5-dd-o4-tpg", got)
|
||||
customModel := formatHomeClaudeModel(homeModelEntry{id: "gpt-4o", displayName: "GPT-4o"})
|
||||
if got := customModel["id"]; got != "gpt-4o" {
|
||||
t.Fatalf("id = %v, want gpt-4o", got)
|
||||
}
|
||||
if got := prefixed["display_name"]; got != "GPT-4o" {
|
||||
if got := customModel["display_name"]; got != "GPT-4o" {
|
||||
t.Fatalf("display_name = %v, want GPT-4o", got)
|
||||
}
|
||||
if got := withDefaults["max_input_tokens"]; got != registry.DefaultClaudeMaxInputTokens {
|
||||
@@ -1737,24 +1737,6 @@ func TestFormatHomeClaudeModelIncludesAnthropicSchemaFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatHomeClaudeModelsSortsByDisplayName(t *testing.T) {
|
||||
out := formatHomeClaudeModels([]homeModelEntry{
|
||||
{id: "claude-z", displayName: "Zebra"},
|
||||
{id: "gpt-4o", displayName: "Alpha"},
|
||||
{id: "claude-b", displayName: "Beta"},
|
||||
})
|
||||
if len(out) != 3 {
|
||||
t.Fatalf("len(out) = %d, want 3", len(out))
|
||||
}
|
||||
wantNames := []string{"Alpha", "Beta", "Zebra"}
|
||||
for i, want := range wantNames {
|
||||
got, _ := out[i]["display_name"].(string)
|
||||
if got != want {
|
||||
t.Fatalf("out[%d].display_name = %q, want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeHomeModelsKeepsTokenMetadata(t *testing.T) {
|
||||
entries, errDecode := decodeHomeModels([]byte(`{
|
||||
"claude": [
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Package models builds model catalogs for Anthropic clients.
|
||||
package models
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const claudeDDModelPrefix = "claude-fable-5-dd-"
|
||||
|
||||
// BuildResponse builds an Anthropic model response from available models.
|
||||
func BuildResponse(availableModels []map[string]any) map[string]any {
|
||||
models := make([]map[string]any, len(availableModels))
|
||||
for i, model := range availableModels {
|
||||
models[i] = cloneModel(model)
|
||||
if id, ok := models[i]["id"].(string); ok {
|
||||
models[i]["id"] = EnsureClaudeModelIDPrefix(id)
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(models, func(i, j int) bool {
|
||||
displayNameI, _ := models[i]["display_name"].(string)
|
||||
displayNameJ, _ := models[j]["display_name"].(string)
|
||||
if displayNameI != displayNameJ {
|
||||
return displayNameI < displayNameJ
|
||||
}
|
||||
idI, _ := models[i]["id"].(string)
|
||||
idJ, _ := models[j]["id"].(string)
|
||||
return idI < idJ
|
||||
})
|
||||
|
||||
firstID := ""
|
||||
lastID := ""
|
||||
if len(models) > 0 {
|
||||
firstID, _ = models[0]["id"].(string)
|
||||
lastID, _ = models[len(models)-1]["id"].(string)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"data": models,
|
||||
"has_more": false,
|
||||
"first_id": firstID,
|
||||
"last_id": lastID,
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureClaudeModelIDPrefix rewrites model IDs for Anthropic model listings.
|
||||
// IDs that already start with "claude-" are returned unchanged; all other IDs
|
||||
// become "claude-fable-5-dd-" plus the original ID with its characters reversed.
|
||||
func EnsureClaudeModelIDPrefix(id string) string {
|
||||
if id == "" || strings.HasPrefix(id, "claude-") {
|
||||
return id
|
||||
}
|
||||
return claudeDDModelPrefix + reverseModelID(id)
|
||||
}
|
||||
|
||||
// ResolveClaudeModelIDPrefix reverses EnsureClaudeModelIDPrefix for request routing.
|
||||
// Optional thinking suffixes in model(value) form are preserved.
|
||||
func ResolveClaudeModelIDPrefix(id string) string {
|
||||
if id == "" {
|
||||
return id
|
||||
}
|
||||
base, suffix, hasSuffix := splitModelThinkingSuffix(id)
|
||||
if !strings.HasPrefix(base, claudeDDModelPrefix) {
|
||||
return id
|
||||
}
|
||||
encoded := base[len(claudeDDModelPrefix):]
|
||||
if encoded == "" {
|
||||
return id
|
||||
}
|
||||
resolved := reverseModelID(encoded)
|
||||
if hasSuffix {
|
||||
return resolved + "(" + suffix + ")"
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
func cloneModel(model map[string]any) map[string]any {
|
||||
cloned := make(map[string]any, len(model))
|
||||
for key, value := range model {
|
||||
cloned[key] = value
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func splitModelThinkingSuffix(model string) (base, suffix string, hasSuffix bool) {
|
||||
lastOpen := strings.LastIndex(model, "(")
|
||||
if lastOpen == -1 || !strings.HasSuffix(model, ")") {
|
||||
return model, "", false
|
||||
}
|
||||
return model[:lastOpen], model[lastOpen+1 : len(model)-1], true
|
||||
}
|
||||
|
||||
func reverseModelID(id string) string {
|
||||
runes := []rune(id)
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package models
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBuildResponse(t *testing.T) {
|
||||
availableModels := []map[string]any{
|
||||
{"id": "claude-z", "display_name": "Zebra", "max_tokens": 64000},
|
||||
{"id": "gpt-4o", "display_name": "Alpha"},
|
||||
{"id": "claude-c", "display_name": "Alpha"},
|
||||
{"id": "claude-b", "display_name": "Beta"},
|
||||
}
|
||||
|
||||
response := BuildResponse(availableModels)
|
||||
models, ok := response["data"].([]map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("data type = %T, want []map[string]any", response["data"])
|
||||
}
|
||||
|
||||
wantIDs := []string{
|
||||
"claude-c",
|
||||
"claude-fable-5-dd-o4-tpg",
|
||||
"claude-b",
|
||||
"claude-z",
|
||||
}
|
||||
if len(models) != len(wantIDs) {
|
||||
t.Fatalf("len(data) = %d, want %d", len(models), len(wantIDs))
|
||||
}
|
||||
for i, want := range wantIDs {
|
||||
if got, _ := models[i]["id"].(string); got != want {
|
||||
t.Fatalf("data[%d].id = %q, want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
if got := models[3]["max_tokens"]; got != 64000 {
|
||||
t.Fatalf("max_tokens = %v, want 64000", got)
|
||||
}
|
||||
if got := response["has_more"]; got != false {
|
||||
t.Fatalf("has_more = %v, want false", got)
|
||||
}
|
||||
if got := response["first_id"]; got != wantIDs[0] {
|
||||
t.Fatalf("first_id = %v, want %q", got, wantIDs[0])
|
||||
}
|
||||
if got := response["last_id"]; got != wantIDs[len(wantIDs)-1] {
|
||||
t.Fatalf("last_id = %v, want %q", got, wantIDs[len(wantIDs)-1])
|
||||
}
|
||||
|
||||
if got := availableModels[1]["id"]; got != "gpt-4o" {
|
||||
t.Fatalf("BuildResponse mutated input id to %v", got)
|
||||
}
|
||||
if got := availableModels[0]["id"]; got != "claude-z" {
|
||||
t.Fatalf("BuildResponse reordered input: first id = %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildResponseEmpty(t *testing.T) {
|
||||
response := BuildResponse(nil)
|
||||
models, ok := response["data"].([]map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("data type = %T, want []map[string]any", response["data"])
|
||||
}
|
||||
if len(models) != 0 {
|
||||
t.Fatalf("len(data) = %d, want 0", len(models))
|
||||
}
|
||||
if response["first_id"] != "" || response["last_id"] != "" {
|
||||
t.Fatalf("empty response IDs = (%v, %v), want empty", response["first_id"], response["last_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureClaudeModelIDPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"already has claude prefix", "claude-sonnet-4-6", "claude-sonnet-4-6"},
|
||||
{"contains claude mid-string is reversed", "my-claude-custom", "claude-fable-5-dd-motsuc-edualc-ym"},
|
||||
{"uppercase Claude prefix is reversed", "Claude-Opus-4", "claude-fable-5-dd-4-supO-edualC"},
|
||||
{"gpt model is reversed", "gpt-4o", "claude-fable-5-dd-o4-tpg"},
|
||||
{"gemini model is reversed", "gemini-2.5-pro", "claude-fable-5-dd-orp-5.2-inimeg"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := EnsureClaudeModelIDPrefix(tt.id); got != tt.want {
|
||||
t.Fatalf("EnsureClaudeModelIDPrefix(%q) = %q, want %q", tt.id, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveClaudeModelIDPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"plain claude id unchanged", "claude-sonnet-4-6", "claude-sonnet-4-6"},
|
||||
{"non encoded id unchanged", "gpt-4o", "gpt-4o"},
|
||||
{"encoded gpt model", "claude-fable-5-dd-o4-tpg", "gpt-4o"},
|
||||
{"encoded gemini model", "claude-fable-5-dd-orp-5.2-inimeg", "gemini-2.5-pro"},
|
||||
{"empty encoded body unchanged", "claude-fable-5-dd-", "claude-fable-5-dd-"},
|
||||
{"preserves thinking suffix", "claude-fable-5-dd-o4-tpg(high)", "gpt-4o(high)"},
|
||||
{"round trip", EnsureClaudeModelIDPrefix("custom-model-x"), "custom-model-x"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ResolveClaudeModelIDPrefix(tt.id); got != tt.want {
|
||||
t.Fatalf("ResolveClaudeModelIDPrefix(%q) = %q, want %q", tt.id, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,56 +8,3 @@ func IsClaudeThinkingModel(model string) bool {
|
||||
lower := strings.ToLower(model)
|
||||
return strings.Contains(lower, "claude") && strings.Contains(lower, "thinking")
|
||||
}
|
||||
|
||||
const claudeDDModelPrefix = "claude-fable-5-dd-"
|
||||
|
||||
// EnsureClaudeModelIDPrefix rewrites model IDs for Anthropic /models listings.
|
||||
// IDs that already start with "claude-" are returned unchanged; all other IDs
|
||||
// become "claude-fable-5-dd-" plus the original ID with its characters reversed.
|
||||
func EnsureClaudeModelIDPrefix(id string) string {
|
||||
if id == "" {
|
||||
return id
|
||||
}
|
||||
if strings.HasPrefix(id, "claude-") {
|
||||
return id
|
||||
}
|
||||
return claudeDDModelPrefix + reverseModelID(id)
|
||||
}
|
||||
|
||||
// ResolveClaudeModelIDPrefix reverses EnsureClaudeModelIDPrefix for request routing.
|
||||
// IDs that start with "claude-fable-5-dd-" are decoded by stripping the prefix and reversing
|
||||
// the remainder. Optional thinking suffixes in model(value) form are preserved.
|
||||
func ResolveClaudeModelIDPrefix(id string) string {
|
||||
if id == "" {
|
||||
return id
|
||||
}
|
||||
base, suffix, hasSuffix := splitModelThinkingSuffix(id)
|
||||
if !strings.HasPrefix(base, claudeDDModelPrefix) {
|
||||
return id
|
||||
}
|
||||
encoded := base[len(claudeDDModelPrefix):]
|
||||
if encoded == "" {
|
||||
return id
|
||||
}
|
||||
resolved := reverseModelID(encoded)
|
||||
if hasSuffix {
|
||||
return resolved + "(" + suffix + ")"
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
func splitModelThinkingSuffix(model string) (base, suffix string, hasSuffix bool) {
|
||||
lastOpen := strings.LastIndex(model, "(")
|
||||
if lastOpen == -1 || !strings.HasSuffix(model, ")") {
|
||||
return model, "", false
|
||||
}
|
||||
return model[:lastOpen], model[lastOpen+1 : len(model)-1], true
|
||||
}
|
||||
|
||||
func reverseModelID(id string) string {
|
||||
runes := []rune(id)
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
@@ -40,51 +40,3 @@ func TestIsClaudeThinkingModel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureClaudeModelIDPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"already has claude prefix", "claude-sonnet-4-6", "claude-sonnet-4-6"},
|
||||
{"contains claude mid-string is reversed", "my-claude-custom", "claude-fable-5-dd-motsuc-edualc-ym"},
|
||||
{"uppercase Claude prefix is reversed", "Claude-Opus-4", "claude-fable-5-dd-4-supO-edualC"},
|
||||
{"gpt model is reversed", "gpt-4o", "claude-fable-5-dd-o4-tpg"},
|
||||
{"gemini model is reversed", "gemini-2.5-pro", "claude-fable-5-dd-orp-5.2-inimeg"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := EnsureClaudeModelIDPrefix(tt.id); got != tt.want {
|
||||
t.Fatalf("EnsureClaudeModelIDPrefix(%q) = %q, want %q", tt.id, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveClaudeModelIDPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"plain claude id unchanged", "claude-sonnet-4-6", "claude-sonnet-4-6"},
|
||||
{"non encoded id unchanged", "gpt-4o", "gpt-4o"},
|
||||
{"encoded gpt model", "claude-fable-5-dd-o4-tpg", "gpt-4o"},
|
||||
{"encoded gemini model", "claude-fable-5-dd-orp-5.2-inimeg", "gemini-2.5-pro"},
|
||||
{"empty encoded body unchanged", "claude-fable-5-dd-", "claude-fable-5-dd-"},
|
||||
{"preserves thinking suffix", "claude-fable-5-dd-o4-tpg(high)", "gpt-4o(high)"},
|
||||
{"round trip", EnsureClaudeModelIDPrefix("custom-model-x"), "custom-model-x"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ResolveClaudeModelIDPrefix(tt.id); got != tt.want {
|
||||
t.Fatalf("ResolveClaudeModelIDPrefix(%q) = %q, want %q", tt.id, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
claudemodels "github.com/router-for-me/CLIProxyAPI/v7/internal/client/claude/models"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v7/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
@@ -138,7 +137,7 @@ func (h *ClaudeCodeAPIHandler) ClaudeCountTokens(c *gin.Context) {
|
||||
// back into the original model name used for routing and upstream requests.
|
||||
func rewriteClaudeDDModelInBody(rawJSON []byte) []byte {
|
||||
modelName := gjson.GetBytes(rawJSON, "model").String()
|
||||
resolved := util.ResolveClaudeModelIDPrefix(modelName)
|
||||
resolved := claudemodels.ResolveClaudeModelIDPrefix(modelName)
|
||||
if resolved == modelName {
|
||||
return rawJSON
|
||||
}
|
||||
@@ -155,45 +154,7 @@ func rewriteClaudeDDModelInBody(rawJSON []byte) []byte {
|
||||
// Parameters:
|
||||
// - c: The Gin context for the request.
|
||||
func (h *ClaudeCodeAPIHandler) ClaudeModels(c *gin.Context) {
|
||||
models := h.Models()
|
||||
for i := range models {
|
||||
if id, ok := models[i]["id"].(string); ok {
|
||||
models[i]["id"] = util.EnsureClaudeModelIDPrefix(id)
|
||||
}
|
||||
}
|
||||
sortClaudeModelsByDisplayName(models)
|
||||
firstID := ""
|
||||
lastID := ""
|
||||
if len(models) > 0 {
|
||||
if id, ok := models[0]["id"].(string); ok {
|
||||
firstID = id
|
||||
}
|
||||
if id, ok := models[len(models)-1]["id"].(string); ok {
|
||||
lastID = id
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": models,
|
||||
"has_more": false,
|
||||
"first_id": firstID,
|
||||
"last_id": lastID,
|
||||
})
|
||||
}
|
||||
|
||||
// sortClaudeModelsByDisplayName sorts models by display_name ascending.
|
||||
// When display_name is equal or missing, id is used as a stable tie-breaker.
|
||||
func sortClaudeModelsByDisplayName(models []map[string]any) {
|
||||
sort.SliceStable(models, func(i, j int) bool {
|
||||
di, _ := models[i]["display_name"].(string)
|
||||
dj, _ := models[j]["display_name"].(string)
|
||||
if di != dj {
|
||||
return di < dj
|
||||
}
|
||||
idi, _ := models[i]["id"].(string)
|
||||
idj, _ := models[j]["id"].(string)
|
||||
return idi < idj
|
||||
})
|
||||
c.JSON(http.StatusOK, claudemodels.BuildResponse(h.Models()))
|
||||
}
|
||||
|
||||
// handleNonStreamingResponse handles non-streaming content generation requests for Claude models.
|
||||
|
||||
@@ -11,24 +11,6 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestSortClaudeModelsByDisplayName(t *testing.T) {
|
||||
models := []map[string]any{
|
||||
{"id": "claude-fable-5-dd-b", "display_name": "Zebra"},
|
||||
{"id": "claude-a", "display_name": "Alpha"},
|
||||
{"id": "claude-c", "display_name": "Alpha"},
|
||||
{"id": "claude-fable-5-dd-d", "display_name": "Beta"},
|
||||
}
|
||||
sortClaudeModelsByDisplayName(models)
|
||||
|
||||
wantIDs := []string{"claude-a", "claude-c", "claude-fable-5-dd-d", "claude-fable-5-dd-b"}
|
||||
for i, want := range wantIDs {
|
||||
got, _ := models[i]["id"].(string)
|
||||
if got != want {
|
||||
t.Fatalf("models[%d].id = %q, want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeModelsResponseUsesConfiguredDisplayName(t *testing.T) {
|
||||
const clientID = "claude-display-name-catalog-test"
|
||||
const modelID = "claude-display-name-catalog-test"
|
||||
|
||||
Reference in New Issue
Block a user