on main help sign in
Merge pull request #505 from charmbracelet/fix-openai-provider
989e56eKujtim Hoxha
go.mod+2−2
@@ -14,7 +14,7 @@ require (
14 github.com/charlievieth/fastwalk v1.0.11
14 github.com/charlievieth/fastwalk v1.0.11
15 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5
15 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5
16 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d
16 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d
17− github.com/charmbracelet/catwalk v0.3.5
17+ github.com/charmbracelet/catwalk v0.4.5
18 github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674
18 github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674
19 github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
19 github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
20 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0
20 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0
@@ -56,7 +56,7 @@ require (
56 github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
56 github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
57 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
57 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
58 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
58 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
59− golang.org/x/oauth2 v0.25.0 // indirect
59+ golang.org/x/oauth2 v0.30.0 // indirect
60 golang.org/x/time v0.8.0 // indirect
60 golang.org/x/time v0.8.0 // indirect
61 google.golang.org/api v0.211.0 // indirect
61 google.golang.org/api v0.211.0 // indirect
62)
62)
go.sum+4−4
@@ -78,8 +78,8 @@ github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5
78github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
78github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
79github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d h1:YMXLZHSo8DjytVY/b5dK8LDuyQsVUmBK3ydQMpu2Ui4=
79github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d h1:YMXLZHSo8DjytVY/b5dK8LDuyQsVUmBK3ydQMpu2Ui4=
80github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc=
80github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc=
81−github.com/charmbracelet/catwalk v0.3.5 h1:ChMvA5ooTNZhDKFagmGNQgIZvZp8XjpdaJ+cDmhgCgA=
81+github.com/charmbracelet/catwalk v0.4.5 h1:Kv3PadDe8IF8gpcYTfAJdCee5Bv4HufvtNT61FXtq5g=
82−github.com/charmbracelet/catwalk v0.3.5/go.mod h1:gUUCqqZ8bk4D7ZzGTu3I77k7cC2x4exRuJBN1H2u2pc=
82+github.com/charmbracelet/catwalk v0.4.5/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc=
83github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
83github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
84github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
84github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
85github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0=
85github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0=
@@ -332,8 +332,8 @@ golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
332golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
332golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
333golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
333golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
334golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
334golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
335−golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
335+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
336−golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
336+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
337golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
337golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
338golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
338golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
339golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
339golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
internal/llm/provider/openai.go+71−67
@@ -2,10 +2,12 @@ package provider
2
2
3import (
3import (
4 "context"
4 "context"
5+ "encoding/json"
5 "errors"
6 "errors"
6 "fmt"
7 "fmt"
7 "io"
8 "io"
8 "log/slog"
9 "log/slog"
10+ "slices"
9 "strings"
11 "strings"
10 "time"
12 "time"
11
13
@@ -14,6 +16,7 @@ import (
14 "github.com/charmbracelet/crush/internal/llm/tools"
16 "github.com/charmbracelet/crush/internal/llm/tools"
15 "github.com/charmbracelet/crush/internal/log"
17 "github.com/charmbracelet/crush/internal/log"
16 "github.com/charmbracelet/crush/internal/message"
18 "github.com/charmbracelet/crush/internal/message"
19+ "github.com/google/uuid"
17 "github.com/openai/openai-go"
20 "github.com/openai/openai-go"
18 "github.com/openai/openai-go/option"
21 "github.com/openai/openai-go/option"
19 "github.com/openai/openai-go/packages/param"
22 "github.com/openai/openai-go/packages/param"
@@ -70,8 +73,9 @@ func (o *openaiClient) convertMessages(messages []message.Message) (openaiMessag
70 systemMessage = o.providerOptions.systemPromptPrefix + "\n" + systemMessage
73 systemMessage = o.providerOptions.systemPromptPrefix + "\n" + systemMessage
71 }
74 }
72
75
73− systemTextBlock := openai.ChatCompletionContentPartTextParam{Text: systemMessage}
76+ system := openai.SystemMessage(systemMessage)
74 if isAnthropicModel && !o.providerOptions.disableCache {
77 if isAnthropicModel && !o.providerOptions.disableCache {
78+ systemTextBlock := openai.ChatCompletionContentPartTextParam{Text: systemMessage}
75 systemTextBlock.SetExtraFields(
79 systemTextBlock.SetExtraFields(
76 map[string]any{
80 map[string]any{
77 "cache_control": map[string]string{
81 "cache_control": map[string]string{
@@ -79,10 +83,10 @@ func (o *openaiClient) convertMessages(messages []message.Message) (openaiMessag
79 },
83 },
80 },
84 },
81 )
85 )
86+ var content []openai.ChatCompletionContentPartTextParam
87+ content = append(content, systemTextBlock)
88+ system = openai.SystemMessage(content)
82 }
89 }
83− var content []openai.ChatCompletionContentPartTextParam
84− content = append(content, systemTextBlock)
85− system := openai.SystemMessage(content)
86 openaiMessages = append(openaiMessages, system)
90 openaiMessages = append(openaiMessages, system)
87
91
88 for i, msg := range messages {
92 for i, msg := range messages {
@@ -93,9 +97,12 @@ func (o *openaiClient) convertMessages(messages []message.Message) (openaiMessag
93 switch msg.Role {
97 switch msg.Role {
94 case message.User:
98 case message.User:
95 var content []openai.ChatCompletionContentPartUnionParam
99 var content []openai.ChatCompletionContentPartUnionParam
100+
96 textBlock := openai.ChatCompletionContentPartTextParam{Text: msg.Content().String()}
101 textBlock := openai.ChatCompletionContentPartTextParam{Text: msg.Content().String()}
97 content = append(content, openai.ChatCompletionContentPartUnionParam{OfText: &textBlock})
102 content = append(content, openai.ChatCompletionContentPartUnionParam{OfText: &textBlock})
103+ hasBinaryContent := false
98 for _, binaryContent := range msg.BinaryContent() {
104 for _, binaryContent := range msg.BinaryContent() {
105+ hasBinaryContent = true
99 imageURL := openai.ChatCompletionContentPartImageImageURLParam{URL: binaryContent.String(catwalk.InferenceProviderOpenAI)}
106 imageURL := openai.ChatCompletionContentPartImageImageURLParam{URL: binaryContent.String(catwalk.InferenceProviderOpenAI)}
100 imageBlock := openai.ChatCompletionContentPartImageParam{ImageURL: imageURL}
107 imageBlock := openai.ChatCompletionContentPartImageParam{ImageURL: imageURL}
101
108
@@ -108,8 +115,11 @@ func (o *openaiClient) convertMessages(messages []message.Message) (openaiMessag
108 },
115 },
109 })
116 })
110 }
117 }
111−
118+ if hasBinaryContent || (isAnthropicModel && !o.providerOptions.disableCache) {
112− openaiMessages = append(openaiMessages, openai.UserMessage(content))
119+ openaiMessages = append(openaiMessages, openai.UserMessage(content))
120+ } else {
121+ openaiMessages = append(openaiMessages, openai.UserMessage(msg.Content().String()))
122+ }
113
123
114 case message.Assistant:
124 case message.Assistant:
115 assistantMsg := openai.ChatCompletionAssistantMessageParam{
125 assistantMsg := openai.ChatCompletionAssistantMessageParam{
@@ -134,13 +144,15 @@ func (o *openaiClient) convertMessages(messages []message.Message) (openaiMessag
134 },
144 },
135 },
145 },
136 }
146 }
147+ if !isAnthropicModel {
148+ assistantMsg.Content = openai.ChatCompletionAssistantMessageParamContentUnion{
149+ OfString: param.NewOpt(msg.Content().String()),
150+ }
151+ }
137 }
152 }
138
153
139 if len(msg.ToolCalls()) > 0 {
154 if len(msg.ToolCalls()) > 0 {
140 hasContent = true
155 hasContent = true
141− assistantMsg.Content = openai.ChatCompletionAssistantMessageParamContentUnion{
142− OfString: param.NewOpt(msg.Content().String()),
143− }
144 assistantMsg.ToolCalls = make([]openai.ChatCompletionMessageToolCallParam, len(msg.ToolCalls()))
156 assistantMsg.ToolCalls = make([]openai.ChatCompletionMessageToolCallParam, len(msg.ToolCalls()))
145 for i, call := range msg.ToolCalls() {
157 for i, call := range msg.ToolCalls() {
146 assistantMsg.ToolCalls[i] = openai.ChatCompletionMessageToolCallParam{
158 assistantMsg.ToolCalls[i] = openai.ChatCompletionMessageToolCallParam{
@@ -329,21 +341,26 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
329 acc := openai.ChatCompletionAccumulator{}
341 acc := openai.ChatCompletionAccumulator{}
330 currentContent := ""
342 currentContent := ""
331 toolCalls := make([]message.ToolCall, 0)
343 toolCalls := make([]message.ToolCall, 0)
332−
333− var currentToolCallID string
334− var currentToolCall openai.ChatCompletionMessageToolCall
335 var msgToolCalls []openai.ChatCompletionMessageToolCall
344 var msgToolCalls []openai.ChatCompletionMessageToolCall
336− currentToolIndex := 0
337 for openaiStream.Next() {
345 for openaiStream.Next() {
338 chunk := openaiStream.Current()
346 chunk := openaiStream.Current()
339 // Kujtim: this is an issue with openrouter qwen, its sending -1 for the tool index
347 // Kujtim: this is an issue with openrouter qwen, its sending -1 for the tool index
340 if len(chunk.Choices) > 0 && len(chunk.Choices[0].Delta.ToolCalls) > 0 && chunk.Choices[0].Delta.ToolCalls[0].Index == -1 {
348 if len(chunk.Choices) > 0 && len(chunk.Choices[0].Delta.ToolCalls) > 0 && chunk.Choices[0].Delta.ToolCalls[0].Index == -1 {
341− chunk.Choices[0].Delta.ToolCalls[0].Index = int64(currentToolIndex)
349+ chunk.Choices[0].Delta.ToolCalls[0].Index = 0
342− currentToolIndex++
343 }
350 }
344 acc.AddChunk(chunk)
351 acc.AddChunk(chunk)
345− // This fixes multiple tool calls for some providers
346− for _, choice := range chunk.Choices {
352+ for i, choice := range chunk.Choices {
353+ reasoning, ok := choice.Delta.JSON.ExtraFields["reasoning"]
354+ if ok && reasoning.Raw() != "" {
355+ reasoningStr := ""
356+ json.Unmarshal([]byte(reasoning.Raw()), &reasoningStr)
357+ if reasoningStr != "" {
358+ eventChan <- ProviderEvent{
359+ Type: EventThinkingDelta,
360+ Thinking: reasoningStr,
361+ }
362+ }
363+ }
347 if choice.Delta.Content != "" {
364 if choice.Delta.Content != "" {
348 eventChan <- ProviderEvent{
365 eventChan <- ProviderEvent{
349 Type: EventContentDelta,
366 Type: EventContentDelta,
@@ -352,63 +369,50 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
352 currentContent += choice.Delta.Content
369 currentContent += choice.Delta.Content
353 } else if len(choice.Delta.ToolCalls) > 0 {
370 } else if len(choice.Delta.ToolCalls) > 0 {
354 toolCall := choice.Delta.ToolCalls[0]
371 toolCall := choice.Delta.ToolCalls[0]
355− // Detect tool use start
372+ newToolCall := false
356− if currentToolCallID == "" {
373+ if len(msgToolCalls)-1 >= int(toolCall.Index) { // tool call exists
374+ existingToolCall := msgToolCalls[toolCall.Index]
375+ if toolCall.ID != "" && toolCall.ID != existingToolCall.ID {
376+ found := false
377+ // try to find the tool based on the ID
378+ for i, tool := range msgToolCalls {
357− if toolCall.ID != "" {
379+ if tool.ID == toolCall.ID {
358− currentToolCallID = toolCall.ID
380+ msgToolCalls[i].Function.Arguments += toolCall.Function.Arguments
359− eventChan <- ProviderEvent{
381+ found = true
360− Type: EventToolUseStart,
361− ToolCall: &message.ToolCall{
362− ID: toolCall.ID,
363− Name: toolCall.Function.Name,
364− Finished: false,
365− },
382+ }
366 }
383 }
367− currentToolCall = openai.ChatCompletionMessageToolCall{
384+ if !found {
368− ID: toolCall.ID,
385+ newToolCall = true
369− Type: "function",
370− Function: openai.ChatCompletionMessageToolCallFunction{
371− Name: toolCall.Function.Name,
372− Arguments: toolCall.Function.Arguments,
373− },
374 }
386 }
375− }
376− } else {
377− // Delta tool use
378− if toolCall.ID == "" || toolCall.ID == currentToolCallID {
379− currentToolCall.Function.Arguments += toolCall.Function.Arguments
380 } else {
387 } else {
381− // Detect new tool use
388+ msgToolCalls[toolCall.Index].Function.Arguments += toolCall.Function.Arguments
382− if toolCall.ID != currentToolCallID {
383− msgToolCalls = append(msgToolCalls, currentToolCall)
384− currentToolCallID = toolCall.ID
385− eventChan <- ProviderEvent{
386− Type: EventToolUseStart,
387− ToolCall: &message.ToolCall{
388− ID: toolCall.ID,
389− Name: toolCall.Function.Name,
390− Finished: false,
391− },
392− }
393− currentToolCall = openai.ChatCompletionMessageToolCall{
394− ID: toolCall.ID,
395− Type: "function",
396− Function: openai.ChatCompletionMessageToolCallFunction{
397− Name: toolCall.Function.Name,
398− Arguments: toolCall.Function.Arguments,
399− },
400− }
401− }
402 }
389 }
390+ } else {
391+ newToolCall = true
403 }
392 }
404− }
393+ if newToolCall { // new tool call
405− // Kujtim: some models send finish stop even for tool calls
394+ if toolCall.ID == "" {
406− if choice.FinishReason == "tool_calls" || (choice.FinishReason == "stop" && currentToolCallID != "") {
395+ toolCall.ID = uuid.NewString()
396+ }
397+ eventChan <- ProviderEvent{
398+ Type: EventToolUseStart,
399+ ToolCall: &message.ToolCall{
400+ ID: toolCall.ID,
401+ Name: toolCall.Function.Name,
402+ Finished: false,
403+ },
404+ }
407− msgToolCalls = append(msgToolCalls, currentToolCall)
405+ msgToolCalls = append(msgToolCalls, openai.ChatCompletionMessageToolCall{
408− if len(acc.Choices) > 0 {
406+ ID: toolCall.ID,
409− acc.Choices[0].Message.ToolCalls = msgToolCalls
407+ Type: "function",
408+ Function: openai.ChatCompletionMessageToolCallFunction{
409+ Name: toolCall.Function.Name,
410+ Arguments: toolCall.Function.Arguments,
411+ },
412+ })
410 }
413 }
411 }
414 }
415+ acc.Choices[i].Message.ToolCalls = slices.Clone(msgToolCalls)
412 }
416 }
413 }
417 }
414
418
internal/tui/components/chat/messages/messages.go+3
@@ -274,6 +274,9 @@ func (m *messageCmp) renderThinkingContent() string {
274 if reasoningContent.StartedAt > 0 {
274 if reasoningContent.StartedAt > 0 {
275 duration := m.message.ThinkingDuration()
275 duration := m.message.ThinkingDuration()
276 if reasoningContent.FinishedAt > 0 {
276 if reasoningContent.FinishedAt > 0 {
277+ if duration.String() == "0s" {
278+ return ""
279+ }
277 m.anim.SetLabel("")
280 m.anim.SetLabel("")
278 opts := core.StatusOpts{
281 opts := core.StatusOpts{
279 Title: "Thought for",
282 Title: "Thought for",
internal/tui/components/chat/splash/splash.go−22
@@ -3,7 +3,6 @@ package splash
3import (
3import (
4 "fmt"
4 "fmt"
5 "os"
5 "os"
6− "slices"
7 "strings"
6 "strings"
8 "time"
7 "time"
9
8
@@ -103,27 +102,6 @@ func New() Splash {
103
102
104func (s *splashCmp) SetOnboarding(onboarding bool) {
103func (s *splashCmp) SetOnboarding(onboarding bool) {
105 s.isOnboarding = onboarding
104 s.isOnboarding = onboarding
106− if onboarding {
107− providers, err := config.Providers()
108− if err != nil {
109− return
110− }
111− filteredProviders := []catwalk.Provider{}
112− simpleProviders := []string{
113− "anthropic",
114− "openai",
115− "gemini",
116− "xai",
117− "groq",
118− "openrouter",
119− }
120− for _, p := range providers {
121− if slices.Contains(simpleProviders, string(p.ID)) {
122− filteredProviders = append(filteredProviders, p)
123− }
124− }
125− s.modelList.SetProviders(filteredProviders)
126− }
127}
105}
128
106
129func (s *splashCmp) SetProjectInit(needsInit bool) {
107func (s *splashCmp) SetProjectInit(needsInit bool) {
internal/tui/components/dialogs/models/list.go+10−5
@@ -3,6 +3,7 @@ package models
3import (
3import (
4 "fmt"
4 "fmt"
5 "slices"
5 "slices"
6+ "strings"
6
7
7 tea "github.com/charmbracelet/bubbletea/v2"
8 tea "github.com/charmbracelet/bubbletea/v2"
8 "github.com/charmbracelet/catwalk/pkg/catwalk"
9 "github.com/charmbracelet/catwalk/pkg/catwalk"
@@ -49,7 +50,15 @@ func (m *ModelListComponent) Init() tea.Cmd {
49 var cmds []tea.Cmd
50 var cmds []tea.Cmd
50 if len(m.providers) == 0 {
51 if len(m.providers) == 0 {
51 providers, err := config.Providers()
52 providers, err := config.Providers()
53+ filteredProviders := []catwalk.Provider{}
54+ for _, p := range providers {
55+ hasAPIKeyEnv := strings.HasPrefix(p.APIKey, "$")
56+ if hasAPIKeyEnv && p.ID != catwalk.InferenceProviderAzure {
57+ filteredProviders = append(filteredProviders, p)
58+ }
59+ }
60+
52− m.providers = providers
61+ m.providers = filteredProviders
53 if err != nil {
62 if err != nil {
54 cmds = append(cmds, util.ReportError(err))
63 cmds = append(cmds, util.ReportError(err))
55 }
64 }
@@ -242,7 +251,3 @@ func (m *ModelListComponent) GetModelType() int {
242func (m *ModelListComponent) SetInputPlaceholder(placeholder string) {
251func (m *ModelListComponent) SetInputPlaceholder(placeholder string) {
243 m.list.SetInputPlaceholder(placeholder)
252 m.list.SetInputPlaceholder(placeholder)
244}
253}
245−
246−func (m *ModelListComponent) SetProviders(providers []catwalk.Provider) {
247− m.providers = providers
248−}
internal/tui/components/dialogs/models/models.go−19
@@ -2,7 +2,6 @@ package models
2
2
3import (
3import (
4 "fmt"
4 "fmt"
5− "slices"
6 "time"
5 "time"
7
6
8 "github.com/charmbracelet/bubbles/v2/help"
7 "github.com/charmbracelet/bubbles/v2/help"
@@ -96,24 +95,6 @@ func NewModelDialogCmp() ModelDialog {
96}
95}
97
96
98func (m *modelDialogCmp) Init() tea.Cmd {
97func (m *modelDialogCmp) Init() tea.Cmd {
99− providers, err := config.Providers()
100− if err == nil {
101− filteredProviders := []catwalk.Provider{}
102− simpleProviders := []string{
103− "anthropic",
104− "openai",
105− "gemini",
106− "xai",
107− "groq",
108− "openrouter",
109− }
110− for _, p := range providers {
111− if slices.Contains(simpleProviders, string(p.ID)) {
112− filteredProviders = append(filteredProviders, p)
113− }
114− }
115− m.modelList.SetProviders(filteredProviders)
116− }
117 return tea.Batch(m.modelList.Init(), m.apiKeyInput.Init())
98 return tea.Batch(m.modelList.Init(), m.apiKeyInput.Init())
118}
99}
119
100