@@ -1,6 +1,7 @@
11package config
22
33import (
4+ "encoding/json"
45 "testing"
56
67 "github.com/charmbracelet/crush/internal/fur/provider"
@@ -103,3 +104,177 @@ func TestResetProviders(t *testing.T) {
103104 // Should get the same mock data
104105 assert.Equal(t, len(providers1), len(providers2))
105106}
107+
108+func TestReasoningEffortSupport(t *testing.T) {
109+ originalUseMock := UseMockProviders
110+ UseMockProviders = true
111+ defer func() {
112+ UseMockProviders = originalUseMock
113+ ResetProviders()
114+ }()
115+
116+ ResetProviders()
117+ providers := Providers()
118+
119+ var openaiProvider provider.Provider
120+ for _, p := range providers {
121+ if p.ID == provider.InferenceProviderOpenAI {
122+ openaiProvider = p
123+ break
124+ }
125+ }
126+ require.NotEmpty(t, openaiProvider.ID)
127+
128+ var reasoningModel, nonReasoningModel provider.Model
129+ for _, model := range openaiProvider.Models {
130+ if model.CanReason && model.HasReasoningEffort {
131+ reasoningModel = model
132+ } else if !model.CanReason {
133+ nonReasoningModel = model
134+ }
135+ }
136+
137+ require.NotEmpty(t, reasoningModel.ID)
138+ assert.Equal(t, "medium", reasoningModel.DefaultReasoningEffort)
139+ assert.True(t, reasoningModel.HasReasoningEffort)
140+
141+ require.NotEmpty(t, nonReasoningModel.ID)
142+ assert.False(t, nonReasoningModel.HasReasoningEffort)
143+ assert.Empty(t, nonReasoningModel.DefaultReasoningEffort)
144+}
145+
146+func TestReasoningEffortConfigTransfer(t *testing.T) {
147+ originalUseMock := UseMockProviders
148+ UseMockProviders = true
149+ defer func() {
150+ UseMockProviders = originalUseMock
151+ ResetProviders()
152+ }()
153+
154+ ResetProviders()
155+ t.Setenv("OPENAI_API_KEY", "test-openai-key")
156+
157+ cfg, err := Init(t.TempDir(), false)
158+ require.NoError(t, err)
159+
160+ openaiProviderConfig, exists := cfg.Providers[provider.InferenceProviderOpenAI]
161+ require.True(t, exists)
162+
163+ var foundReasoning, foundNonReasoning bool
164+ for _, model := range openaiProviderConfig.Models {
165+ if model.CanReason && model.HasReasoningEffort && model.ReasoningEffort != "" {
166+ assert.Equal(t, "medium", model.ReasoningEffort)
167+ assert.True(t, model.HasReasoningEffort)
168+ foundReasoning = true
169+ } else if !model.CanReason {
170+ assert.Empty(t, model.ReasoningEffort)
171+ assert.False(t, model.HasReasoningEffort)
172+ foundNonReasoning = true
173+ }
174+ }
175+
176+ assert.True(t, foundReasoning, "Should find at least one reasoning model")
177+ assert.True(t, foundNonReasoning, "Should find at least one non-reasoning model")
178+}
179+
180+func TestNewProviders(t *testing.T) {
181+ originalUseMock := UseMockProviders
182+ UseMockProviders = true
183+ defer func() {
184+ UseMockProviders = originalUseMock
185+ ResetProviders()
186+ }()
187+
188+ ResetProviders()
189+ providers := Providers()
190+ require.NotEmpty(t, providers)
191+
192+ var xaiProvider, openRouterProvider provider.Provider
193+ for _, p := range providers {
194+ switch p.ID {
195+ case provider.InferenceProviderXAI:
196+ xaiProvider = p
197+ case provider.InferenceProviderOpenRouter:
198+ openRouterProvider = p
199+ }
200+ }
201+
202+ require.NotEmpty(t, xaiProvider.ID)
203+ assert.Equal(t, "xAI", xaiProvider.Name)
204+ assert.Equal(t, "grok-beta", xaiProvider.DefaultLargeModelID)
205+
206+ require.NotEmpty(t, openRouterProvider.ID)
207+ assert.Equal(t, "OpenRouter", openRouterProvider.Name)
208+ assert.Equal(t, "anthropic/claude-3.5-sonnet", openRouterProvider.DefaultLargeModelID)
209+}
210+
211+func TestO1ModelsInMockProvider(t *testing.T) {
212+ originalUseMock := UseMockProviders
213+ UseMockProviders = true
214+ defer func() {
215+ UseMockProviders = originalUseMock
216+ ResetProviders()
217+ }()
218+
219+ ResetProviders()
220+ providers := Providers()
221+
222+ var openaiProvider provider.Provider
223+ for _, p := range providers {
224+ if p.ID == provider.InferenceProviderOpenAI {
225+ openaiProvider = p
226+ break
227+ }
228+ }
229+ require.NotEmpty(t, openaiProvider.ID)
230+
231+ modelTests := []struct {
232+ id string
233+ name string
234+ }{
235+ {"o1-preview", "o1-preview"},
236+ {"o1-mini", "o1-mini"},
237+ }
238+
239+ for _, test := range modelTests {
240+ var model provider.Model
241+ var found bool
242+ for _, m := range openaiProvider.Models {
243+ if m.ID == test.id {
244+ model = m
245+ found = true
246+ break
247+ }
248+ }
249+ require.True(t, found, "Should find %s model", test.id)
250+ assert.Equal(t, test.name, model.Name)
251+ assert.True(t, model.CanReason)
252+ assert.True(t, model.HasReasoningEffort)
253+ assert.Equal(t, "medium", model.DefaultReasoningEffort)
254+ }
255+}
256+
257+func TestPreferredModelReasoningEffort(t *testing.T) {
258+ // Test that PreferredModel struct can hold reasoning effort
259+ preferredModel := PreferredModel{
260+ ModelID: "o1-preview",
261+ Provider: provider.InferenceProviderOpenAI,
262+ ReasoningEffort: "high",
263+ }
264+
265+ assert.Equal(t, "o1-preview", preferredModel.ModelID)
266+ assert.Equal(t, provider.InferenceProviderOpenAI, preferredModel.Provider)
267+ assert.Equal(t, "high", preferredModel.ReasoningEffort)
268+
269+ // Test JSON marshaling/unmarshaling
270+ jsonData, err := json.Marshal(preferredModel)
271+ require.NoError(t, err)
272+
273+ var unmarshaled PreferredModel
274+ err = json.Unmarshal(jsonData, &unmarshaled)
275+ require.NoError(t, err)
276+
277+ assert.Equal(t, preferredModel.ModelID, unmarshaled.ModelID)
278+ assert.Equal(t, preferredModel.Provider, unmarshaled.Provider)
279+ assert.Equal(t, preferredModel.ReasoningEffort, unmarshaled.ReasoningEffort)
280+}