@@ -0,0 +1,171 @@
9+ "charm.land/catwalk/pkg/catwalk"
10+ "github.com/stretchr/testify/require"
13+func TestLitellmEnricher(t *testing.T) {
16+ t.Run("populates metadata from /model/info", func(t *testing.T) {
18+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19+ require.Equal(t, "/model/info", r.URL.Path)
20+ w.Header().Set("Content-Type", "application/json")
21+ _, _ = w.Write([]byte(`{
24+ "model_name": "gpt-4o",
26+ "max_input_tokens": 128000,
27+ "max_output_tokens": 16384,
28+ "input_cost_per_token": 2.5e-06,
29+ "output_cost_per_token": 1e-05
33+ "model_name": "claude-3-opus",
35+ "max_input_tokens": 200000,
36+ "max_output_tokens": 4096
49+ models := []catwalk.Model{
50+ {ID: "gpt-4o", Name: "gpt-4o"},
51+ {ID: "claude-3-opus", Name: "claude-3-opus"},
52+ {ID: "unknown-model", Name: "unknown-model"},
55+ e := &litellmEnricher{}
56+ result, err := e.EnrichModels(context.Background(), cfg, &mockResolver{}, models)
57+ require.NoError(t, err)
58+ require.Len(t, result, 3)
60+ // gpt-4o should have all fields populated.
61+ require.Equal(t, int64(128000), result[0].ContextWindow)
62+ require.Equal(t, int64(16384), result[0].DefaultMaxTokens)
63+ require.InDelta(t, 2.5, result[0].CostPer1MIn, 0.01)
64+ require.InDelta(t, 10.0, result[0].CostPer1MOut, 0.01)
66+ // claude-3-opus should have context window and max tokens.
67+ require.Equal(t, int64(200000), result[1].ContextWindow)
68+ require.Equal(t, int64(4096), result[1].DefaultMaxTokens)
70+ // unknown-model should be unchanged.
71+ require.Equal(t, int64(0), result[2].ContextWindow)
74+ t.Run("preserves existing non-zero values", func(t *testing.T) {
76+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
77+ w.Header().Set("Content-Type", "application/json")
78+ _, _ = w.Write([]byte(`{
80+ "model_name": "gpt-4o",
82+ "max_input_tokens": 128000,
83+ "max_output_tokens": 16384
94+ models := []catwalk.Model{
95+ {ID: "gpt-4o", Name: "GPT-4o Custom", ContextWindow: 200000, DefaultMaxTokens: 32768},
98+ e := &litellmEnricher{}
99+ result, err := e.EnrichModels(context.Background(), cfg, &mockResolver{}, models)
100+ require.NoError(t, err)
102+ // User overrides should be preserved.
103+ require.Equal(t, int64(200000), result[0].ContextWindow)
104+ require.Equal(t, int64(32768), result[0].DefaultMaxTokens)
105+ require.Equal(t, "GPT-4o Custom", result[0].Name)
108+ t.Run("returns models unchanged on HTTP error", func(t *testing.T) {
110+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
111+ w.WriteHeader(http.StatusInternalServerError)
119+ models := []catwalk.Model{{ID: "m1"}}
121+ e := &litellmEnricher{}
122+ result, err := e.EnrichModels(context.Background(), cfg, &mockResolver{}, models)
123+ require.NoError(t, err)
124+ require.Len(t, result, 1)
125+ require.Equal(t, "m1", result[0].ID)
128+ t.Run("sends auth and extra headers", func(t *testing.T) {
130+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131+ require.Equal(t, "Bearer my-key", r.Header.Get("Authorization"))
132+ require.Equal(t, "custom-val", r.Header.Get("X-Custom"))
133+ w.Header().Set("Content-Type", "application/json")
134+ _, _ = w.Write([]byte(`{"data": []}`))
142+ ExtraHeaders: map[string]string{
143+ "X-Custom": "custom-val",
147+ e := &litellmEnricher{}
148+ _, err := e.EnrichModels(context.Background(), cfg, &mockResolver{}, nil)
149+ require.NoError(t, err)
153+func TestGetEnricher(t *testing.T) {
156+ require.NotNil(t, GetEnricher("litellm"))
157+ require.Nil(t, GetEnricher("openai-compat"))
158+ require.Nil(t, GetEnricher(""))
161+func TestIsKnownCustomProvider(t *testing.T) {
164+ require.True(t, IsKnownCustomProvider("litellm"))
165+ require.True(t, IsKnownCustomProvider("ollama"))
166+ require.True(t, IsKnownCustomProvider("omlx"))
167+ require.True(t, IsKnownCustomProvider("lmstudio"))
168+ require.False(t, IsKnownCustomProvider("openai-compat"))
169+ require.False(t, IsKnownCustomProvider("anthropic"))
170+ require.False(t, IsKnownCustomProvider(""))