1package agent
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "strings"
8 "sync"
9 "time"
10
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/llm/models"
13 "github.com/charmbracelet/crush/internal/llm/prompt"
14 "github.com/charmbracelet/crush/internal/llm/provider"
15 "github.com/charmbracelet/crush/internal/llm/tools"
16 "github.com/charmbracelet/crush/internal/logging"
17 "github.com/charmbracelet/crush/internal/message"
18 "github.com/charmbracelet/crush/internal/permission"
19 "github.com/charmbracelet/crush/internal/pubsub"
20 "github.com/charmbracelet/crush/internal/session"
21)
22
23// Common errors
24var (
25 ErrRequestCancelled = errors.New("request cancelled by user")
26 ErrSessionBusy = errors.New("session is currently processing another request")
27)
28
29type AgentEventType string
30
31const (
32 AgentEventTypeError AgentEventType = "error"
33 AgentEventTypeResponse AgentEventType = "response"
34 AgentEventTypeSummarize AgentEventType = "summarize"
35)
36
37type AgentEvent struct {
38 Type AgentEventType
39 Message message.Message
40 Error error
41
42 // When summarizing
43 SessionID string
44 Progress string
45 Done bool
46}
47
48type Service interface {
49 pubsub.Suscriber[AgentEvent]
50 Model() models.Model
51 Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error)
52 Cancel(sessionID string)
53 IsSessionBusy(sessionID string) bool
54 IsBusy() bool
55 Update(agentName config.AgentName, modelID models.ModelID) (models.Model, error)
56 Summarize(ctx context.Context, sessionID string) error
57}
58
59type agent struct {
60 *pubsub.Broker[AgentEvent]
61 sessions session.Service
62 messages message.Service
63
64 tools []tools.BaseTool
65 provider provider.Provider
66
67 titleProvider provider.Provider
68 summarizeProvider provider.Provider
69
70 activeRequests sync.Map
71}
72
73func NewAgent(
74 agentName config.AgentName,
75 sessions session.Service,
76 messages message.Service,
77 agentTools []tools.BaseTool,
78) (Service, error) {
79 agentProvider, err := createAgentProvider(agentName)
80 if err != nil {
81 return nil, err
82 }
83 var titleProvider provider.Provider
84 // Only generate titles for the coder agent
85 if agentName == config.AgentCoder {
86 titleProvider, err = createAgentProvider(config.AgentTitle)
87 if err != nil {
88 return nil, err
89 }
90 }
91 var summarizeProvider provider.Provider
92 if agentName == config.AgentCoder {
93 summarizeProvider, err = createAgentProvider(config.AgentSummarizer)
94 if err != nil {
95 return nil, err
96 }
97 }
98
99 agent := &agent{
100 Broker: pubsub.NewBroker[AgentEvent](),
101 provider: agentProvider,
102 messages: messages,
103 sessions: sessions,
104 tools: agentTools,
105 titleProvider: titleProvider,
106 summarizeProvider: summarizeProvider,
107 activeRequests: sync.Map{},
108 }
109
110 return agent, nil
111}
112
113func (a *agent) Model() models.Model {
114 return a.provider.Model()
115}
116
117func (a *agent) Cancel(sessionID string) {
118 // Cancel regular requests
119 if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID); exists {
120 if cancel, ok := cancelFunc.(context.CancelFunc); ok {
121 logging.InfoPersist(fmt.Sprintf("Request cancellation initiated for session: %s", sessionID))
122 cancel()
123 }
124 }
125
126 // Also check for summarize requests
127 if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID + "-summarize"); exists {
128 if cancel, ok := cancelFunc.(context.CancelFunc); ok {
129 logging.InfoPersist(fmt.Sprintf("Summarize cancellation initiated for session: %s", sessionID))
130 cancel()
131 }
132 }
133}
134
135func (a *agent) IsBusy() bool {
136 busy := false
137 a.activeRequests.Range(func(key, value interface{}) bool {
138 if cancelFunc, ok := value.(context.CancelFunc); ok {
139 if cancelFunc != nil {
140 busy = true
141 return false // Stop iterating
142 }
143 }
144 return true // Continue iterating
145 })
146 return busy
147}
148
149func (a *agent) IsSessionBusy(sessionID string) bool {
150 _, busy := a.activeRequests.Load(sessionID)
151 return busy
152}
153
154func (a *agent) generateTitle(ctx context.Context, sessionID string, content string) error {
155 if content == "" {
156 return nil
157 }
158 if a.titleProvider == nil {
159 return nil
160 }
161 session, err := a.sessions.Get(ctx, sessionID)
162 if err != nil {
163 return err
164 }
165 parts := []message.ContentPart{message.TextContent{Text: content}}
166 response, err := a.titleProvider.SendMessages(
167 ctx,
168 []message.Message{
169 {
170 Role: message.User,
171 Parts: parts,
172 },
173 },
174 make([]tools.BaseTool, 0),
175 )
176 if err != nil {
177 return err
178 }
179
180 title := strings.TrimSpace(strings.ReplaceAll(response.Content, "\n", " "))
181 if title == "" {
182 return nil
183 }
184
185 session.Title = title
186 _, err = a.sessions.Save(ctx, session)
187 return err
188}
189
190func (a *agent) err(err error) AgentEvent {
191 return AgentEvent{
192 Type: AgentEventTypeError,
193 Error: err,
194 }
195}
196
197func (a *agent) Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error) {
198 if !a.provider.Model().SupportsAttachments && attachments != nil {
199 attachments = nil
200 }
201 events := make(chan AgentEvent)
202 if a.IsSessionBusy(sessionID) {
203 return nil, ErrSessionBusy
204 }
205
206 genCtx, cancel := context.WithCancel(ctx)
207
208 a.activeRequests.Store(sessionID, cancel)
209 go func() {
210 logging.Debug("Request started", "sessionID", sessionID)
211 defer logging.RecoverPanic("agent.Run", func() {
212 events <- a.err(fmt.Errorf("panic while running the agent"))
213 })
214 var attachmentParts []message.ContentPart
215 for _, attachment := range attachments {
216 attachmentParts = append(attachmentParts, message.BinaryContent{Path: attachment.FilePath, MIMEType: attachment.MimeType, Data: attachment.Content})
217 }
218 result := a.processGeneration(genCtx, sessionID, content, attachmentParts)
219 if result.Error != nil && !errors.Is(result.Error, ErrRequestCancelled) && !errors.Is(result.Error, context.Canceled) {
220 logging.ErrorPersist(result.Error.Error())
221 }
222 logging.Debug("Request completed", "sessionID", sessionID)
223 a.activeRequests.Delete(sessionID)
224 cancel()
225 a.Publish(pubsub.CreatedEvent, result)
226 events <- result
227 close(events)
228 }()
229 return events, nil
230}
231
232func (a *agent) processGeneration(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) AgentEvent {
233 // List existing messages; if none, start title generation asynchronously.
234 msgs, err := a.messages.List(ctx, sessionID)
235 if err != nil {
236 return a.err(fmt.Errorf("failed to list messages: %w", err))
237 }
238 if len(msgs) == 0 {
239 go func() {
240 defer logging.RecoverPanic("agent.Run", func() {
241 logging.ErrorPersist("panic while generating title")
242 })
243 titleErr := a.generateTitle(context.Background(), sessionID, content)
244 if titleErr != nil {
245 logging.ErrorPersist(fmt.Sprintf("failed to generate title: %v", titleErr))
246 }
247 }()
248 }
249 session, err := a.sessions.Get(ctx, sessionID)
250 if err != nil {
251 return a.err(fmt.Errorf("failed to get session: %w", err))
252 }
253 if session.SummaryMessageID != "" {
254 summaryMsgInex := -1
255 for i, msg := range msgs {
256 if msg.ID == session.SummaryMessageID {
257 summaryMsgInex = i
258 break
259 }
260 }
261 if summaryMsgInex != -1 {
262 msgs = msgs[summaryMsgInex:]
263 msgs[0].Role = message.User
264 }
265 }
266
267 userMsg, err := a.createUserMessage(ctx, sessionID, content, attachmentParts)
268 if err != nil {
269 return a.err(fmt.Errorf("failed to create user message: %w", err))
270 }
271 // Append the new user message to the conversation history.
272 msgHistory := append(msgs, userMsg)
273
274 for {
275 // Check for cancellation before each iteration
276 select {
277 case <-ctx.Done():
278 return a.err(ctx.Err())
279 default:
280 // Continue processing
281 }
282 agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, msgHistory)
283 if err != nil {
284 if errors.Is(err, context.Canceled) {
285 agentMessage.AddFinish(message.FinishReasonCanceled)
286 a.messages.Update(context.Background(), agentMessage)
287 return a.err(ErrRequestCancelled)
288 }
289 return a.err(fmt.Errorf("failed to process events: %w", err))
290 }
291 logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
292 if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
293 // We are not done, we need to respond with the tool response
294 msgHistory = append(msgHistory, agentMessage, *toolResults)
295 continue
296 }
297 return AgentEvent{
298 Type: AgentEventTypeResponse,
299 Message: agentMessage,
300 Done: true,
301 }
302 }
303}
304
305func (a *agent) createUserMessage(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) (message.Message, error) {
306 parts := []message.ContentPart{message.TextContent{Text: content}}
307 parts = append(parts, attachmentParts...)
308 return a.messages.Create(ctx, sessionID, message.CreateMessageParams{
309 Role: message.User,
310 Parts: parts,
311 })
312}
313
314func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msgHistory []message.Message) (message.Message, *message.Message, error) {
315 eventChan := a.provider.StreamResponse(ctx, msgHistory, a.tools)
316
317 assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
318 Role: message.Assistant,
319 Parts: []message.ContentPart{},
320 Model: a.provider.Model().ID,
321 })
322 if err != nil {
323 return assistantMsg, nil, fmt.Errorf("failed to create assistant message: %w", err)
324 }
325
326 // Add the session and message ID into the context if needed by tools.
327 ctx = context.WithValue(ctx, tools.MessageIDContextKey, assistantMsg.ID)
328 ctx = context.WithValue(ctx, tools.SessionIDContextKey, sessionID)
329
330 // Process each event in the stream.
331 for event := range eventChan {
332 if processErr := a.processEvent(ctx, sessionID, &assistantMsg, event); processErr != nil {
333 a.finishMessage(ctx, &assistantMsg, message.FinishReasonCanceled)
334 return assistantMsg, nil, processErr
335 }
336 if ctx.Err() != nil {
337 a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
338 return assistantMsg, nil, ctx.Err()
339 }
340 }
341
342 toolResults := make([]message.ToolResult, len(assistantMsg.ToolCalls()))
343 toolCalls := assistantMsg.ToolCalls()
344 for i, toolCall := range toolCalls {
345 select {
346 case <-ctx.Done():
347 a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
348 // Make all future tool calls cancelled
349 for j := i; j < len(toolCalls); j++ {
350 toolResults[j] = message.ToolResult{
351 ToolCallID: toolCalls[j].ID,
352 Content: "Tool execution canceled by user",
353 IsError: true,
354 }
355 }
356 goto out
357 default:
358 // Continue processing
359 var tool tools.BaseTool
360 for _, availableTools := range a.tools {
361 if availableTools.Info().Name == toolCall.Name {
362 tool = availableTools
363 }
364 }
365
366 // Tool not found
367 if tool == nil {
368 toolResults[i] = message.ToolResult{
369 ToolCallID: toolCall.ID,
370 Content: fmt.Sprintf("Tool not found: %s", toolCall.Name),
371 IsError: true,
372 }
373 continue
374 }
375 toolResult, toolErr := tool.Run(ctx, tools.ToolCall{
376 ID: toolCall.ID,
377 Name: toolCall.Name,
378 Input: toolCall.Input,
379 })
380 if toolErr != nil {
381 if errors.Is(toolErr, permission.ErrorPermissionDenied) {
382 toolResults[i] = message.ToolResult{
383 ToolCallID: toolCall.ID,
384 Content: "Permission denied",
385 IsError: true,
386 }
387 for j := i + 1; j < len(toolCalls); j++ {
388 toolResults[j] = message.ToolResult{
389 ToolCallID: toolCalls[j].ID,
390 Content: "Tool execution canceled by user",
391 IsError: true,
392 }
393 }
394 a.finishMessage(ctx, &assistantMsg, message.FinishReasonPermissionDenied)
395 break
396 }
397 }
398 toolResults[i] = message.ToolResult{
399 ToolCallID: toolCall.ID,
400 Content: toolResult.Content,
401 Metadata: toolResult.Metadata,
402 IsError: toolResult.IsError,
403 }
404 }
405 }
406out:
407 if len(toolResults) == 0 {
408 return assistantMsg, nil, nil
409 }
410 parts := make([]message.ContentPart, 0)
411 for _, tr := range toolResults {
412 parts = append(parts, tr)
413 }
414 msg, err := a.messages.Create(context.Background(), assistantMsg.SessionID, message.CreateMessageParams{
415 Role: message.Tool,
416 Parts: parts,
417 })
418 if err != nil {
419 return assistantMsg, nil, fmt.Errorf("failed to create cancelled tool message: %w", err)
420 }
421
422 return assistantMsg, &msg, err
423}
424
425func (a *agent) finishMessage(ctx context.Context, msg *message.Message, finishReson message.FinishReason) {
426 msg.AddFinish(finishReson)
427 _ = a.messages.Update(ctx, *msg)
428}
429
430func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg *message.Message, event provider.ProviderEvent) error {
431 select {
432 case <-ctx.Done():
433 return ctx.Err()
434 default:
435 // Continue processing.
436 }
437
438 switch event.Type {
439 case provider.EventThinkingDelta:
440 assistantMsg.AppendReasoningContent(event.Content)
441 return a.messages.Update(ctx, *assistantMsg)
442 case provider.EventContentDelta:
443 assistantMsg.AppendContent(event.Content)
444 return a.messages.Update(ctx, *assistantMsg)
445 case provider.EventToolUseStart:
446 logging.Info("Tool call started", "toolCall", event.ToolCall)
447 assistantMsg.AddToolCall(*event.ToolCall)
448 return a.messages.Update(ctx, *assistantMsg)
449 case provider.EventToolUseDelta:
450 assistantMsg.AppendToolCallInput(event.ToolCall.ID, event.ToolCall.Input)
451 return a.messages.Update(ctx, *assistantMsg)
452 case provider.EventToolUseStop:
453 logging.Info("Finished tool call", "toolCall", event.ToolCall)
454 assistantMsg.FinishToolCall(event.ToolCall.ID)
455 return a.messages.Update(ctx, *assistantMsg)
456 case provider.EventError:
457 if errors.Is(event.Error, context.Canceled) {
458 logging.InfoPersist(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
459 return context.Canceled
460 }
461 logging.ErrorPersist(event.Error.Error())
462 return event.Error
463 case provider.EventComplete:
464 assistantMsg.SetToolCalls(event.Response.ToolCalls)
465 assistantMsg.AddFinish(event.Response.FinishReason)
466 if err := a.messages.Update(ctx, *assistantMsg); err != nil {
467 return fmt.Errorf("failed to update message: %w", err)
468 }
469 return a.TrackUsage(ctx, sessionID, a.provider.Model(), event.Response.Usage)
470 }
471
472 return nil
473}
474
475func (a *agent) TrackUsage(ctx context.Context, sessionID string, model models.Model, usage provider.TokenUsage) error {
476 sess, err := a.sessions.Get(ctx, sessionID)
477 if err != nil {
478 return fmt.Errorf("failed to get session: %w", err)
479 }
480
481 cost := model.CostPer1MInCached/1e6*float64(usage.CacheCreationTokens) +
482 model.CostPer1MOutCached/1e6*float64(usage.CacheReadTokens) +
483 model.CostPer1MIn/1e6*float64(usage.InputTokens) +
484 model.CostPer1MOut/1e6*float64(usage.OutputTokens)
485
486 sess.Cost += cost
487 sess.CompletionTokens = usage.OutputTokens + usage.CacheReadTokens
488 sess.PromptTokens = usage.InputTokens + usage.CacheCreationTokens
489
490 _, err = a.sessions.Save(ctx, sess)
491 if err != nil {
492 return fmt.Errorf("failed to save session: %w", err)
493 }
494 return nil
495}
496
497func (a *agent) Update(agentName config.AgentName, modelID models.ModelID) (models.Model, error) {
498 if a.IsBusy() {
499 return models.Model{}, fmt.Errorf("cannot change model while processing requests")
500 }
501
502 if err := config.UpdateAgentModel(agentName, modelID); err != nil {
503 return models.Model{}, fmt.Errorf("failed to update config: %w", err)
504 }
505
506 provider, err := createAgentProvider(agentName)
507 if err != nil {
508 return models.Model{}, fmt.Errorf("failed to create provider for model %s: %w", modelID, err)
509 }
510
511 a.provider = provider
512
513 return a.provider.Model(), nil
514}
515
516func (a *agent) Summarize(ctx context.Context, sessionID string) error {
517 if a.summarizeProvider == nil {
518 return fmt.Errorf("summarize provider not available")
519 }
520
521 // Check if session is busy
522 if a.IsSessionBusy(sessionID) {
523 return ErrSessionBusy
524 }
525
526 // Create a new context with cancellation
527 summarizeCtx, cancel := context.WithCancel(ctx)
528
529 // Store the cancel function in activeRequests to allow cancellation
530 a.activeRequests.Store(sessionID+"-summarize", cancel)
531
532 go func() {
533 defer a.activeRequests.Delete(sessionID + "-summarize")
534 defer cancel()
535 event := AgentEvent{
536 Type: AgentEventTypeSummarize,
537 Progress: "Starting summarization...",
538 }
539
540 a.Publish(pubsub.CreatedEvent, event)
541 // Get all messages from the session
542 msgs, err := a.messages.List(summarizeCtx, sessionID)
543 if err != nil {
544 event = AgentEvent{
545 Type: AgentEventTypeError,
546 Error: fmt.Errorf("failed to list messages: %w", err),
547 Done: true,
548 }
549 a.Publish(pubsub.CreatedEvent, event)
550 return
551 }
552
553 if len(msgs) == 0 {
554 event = AgentEvent{
555 Type: AgentEventTypeError,
556 Error: fmt.Errorf("no messages to summarize"),
557 Done: true,
558 }
559 a.Publish(pubsub.CreatedEvent, event)
560 return
561 }
562
563 event = AgentEvent{
564 Type: AgentEventTypeSummarize,
565 Progress: "Analyzing conversation...",
566 }
567 a.Publish(pubsub.CreatedEvent, event)
568
569 // Add a system message to guide the summarization
570 summarizePrompt := "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next."
571
572 // Create a new message with the summarize prompt
573 promptMsg := message.Message{
574 Role: message.User,
575 Parts: []message.ContentPart{message.TextContent{Text: summarizePrompt}},
576 }
577
578 // Append the prompt to the messages
579 msgsWithPrompt := append(msgs, promptMsg)
580
581 event = AgentEvent{
582 Type: AgentEventTypeSummarize,
583 Progress: "Generating summary...",
584 }
585
586 a.Publish(pubsub.CreatedEvent, event)
587
588 // Send the messages to the summarize provider
589 response, err := a.summarizeProvider.SendMessages(
590 summarizeCtx,
591 msgsWithPrompt,
592 make([]tools.BaseTool, 0),
593 )
594 if err != nil {
595 event = AgentEvent{
596 Type: AgentEventTypeError,
597 Error: fmt.Errorf("failed to summarize: %w", err),
598 Done: true,
599 }
600 a.Publish(pubsub.CreatedEvent, event)
601 return
602 }
603
604 summary := strings.TrimSpace(response.Content)
605 if summary == "" {
606 event = AgentEvent{
607 Type: AgentEventTypeError,
608 Error: fmt.Errorf("empty summary returned"),
609 Done: true,
610 }
611 a.Publish(pubsub.CreatedEvent, event)
612 return
613 }
614 event = AgentEvent{
615 Type: AgentEventTypeSummarize,
616 Progress: "Creating new session...",
617 }
618
619 a.Publish(pubsub.CreatedEvent, event)
620 oldSession, err := a.sessions.Get(summarizeCtx, sessionID)
621 if err != nil {
622 event = AgentEvent{
623 Type: AgentEventTypeError,
624 Error: fmt.Errorf("failed to get session: %w", err),
625 Done: true,
626 }
627
628 a.Publish(pubsub.CreatedEvent, event)
629 return
630 }
631 // Create a message in the new session with the summary
632 msg, err := a.messages.Create(summarizeCtx, oldSession.ID, message.CreateMessageParams{
633 Role: message.Assistant,
634 Parts: []message.ContentPart{
635 message.TextContent{Text: summary},
636 message.Finish{
637 Reason: message.FinishReasonEndTurn,
638 Time: time.Now().Unix(),
639 },
640 },
641 Model: a.summarizeProvider.Model().ID,
642 })
643 if err != nil {
644 event = AgentEvent{
645 Type: AgentEventTypeError,
646 Error: fmt.Errorf("failed to create summary message: %w", err),
647 Done: true,
648 }
649
650 a.Publish(pubsub.CreatedEvent, event)
651 return
652 }
653 oldSession.SummaryMessageID = msg.ID
654 oldSession.CompletionTokens = response.Usage.OutputTokens
655 oldSession.PromptTokens = 0
656 model := a.summarizeProvider.Model()
657 usage := response.Usage
658 cost := model.CostPer1MInCached/1e6*float64(usage.CacheCreationTokens) +
659 model.CostPer1MOutCached/1e6*float64(usage.CacheReadTokens) +
660 model.CostPer1MIn/1e6*float64(usage.InputTokens) +
661 model.CostPer1MOut/1e6*float64(usage.OutputTokens)
662 oldSession.Cost += cost
663 _, err = a.sessions.Save(summarizeCtx, oldSession)
664 if err != nil {
665 event = AgentEvent{
666 Type: AgentEventTypeError,
667 Error: fmt.Errorf("failed to save session: %w", err),
668 Done: true,
669 }
670 a.Publish(pubsub.CreatedEvent, event)
671 }
672
673 event = AgentEvent{
674 Type: AgentEventTypeSummarize,
675 SessionID: oldSession.ID,
676 Progress: "Summary complete",
677 Done: true,
678 }
679 a.Publish(pubsub.CreatedEvent, event)
680 // Send final success event with the new session ID
681 }()
682
683 return nil
684}
685
686func createAgentProvider(agentName config.AgentName) (provider.Provider, error) {
687 cfg := config.Get()
688 agentConfig, ok := cfg.Agents[agentName]
689 if !ok {
690 return nil, fmt.Errorf("agent %s not found", agentName)
691 }
692 model, ok := models.SupportedModels[agentConfig.Model]
693 if !ok {
694 return nil, fmt.Errorf("model %s not supported", agentConfig.Model)
695 }
696
697 providerCfg, ok := cfg.Providers[model.Provider]
698 if !ok {
699 return nil, fmt.Errorf("provider %s not supported", model.Provider)
700 }
701 if providerCfg.Disabled {
702 return nil, fmt.Errorf("provider %s is not enabled", model.Provider)
703 }
704 maxTokens := model.DefaultMaxTokens
705 if agentConfig.MaxTokens > 0 {
706 maxTokens = agentConfig.MaxTokens
707 }
708 opts := []provider.ProviderClientOption{
709 provider.WithAPIKey(providerCfg.APIKey),
710 provider.WithModel(model),
711 provider.WithSystemMessage(prompt.GetAgentPrompt(agentName, model.Provider)),
712 provider.WithMaxTokens(maxTokens),
713 }
714 if model.Provider == models.ProviderOpenAI || model.Provider == models.ProviderLocal && model.CanReason {
715 opts = append(
716 opts,
717 provider.WithOpenAIOptions(
718 provider.WithReasoningEffort(agentConfig.ReasoningEffort),
719 ),
720 )
721 } else if model.Provider == models.ProviderAnthropic && model.CanReason && agentName == config.AgentCoder {
722 opts = append(
723 opts,
724 provider.WithAnthropicOptions(
725 provider.WithAnthropicShouldThinkFn(provider.DefaultShouldThinkFn),
726 ),
727 )
728 }
729 agentProvider, err := provider.NewProvider(
730 model.Provider,
731 opts...,
732 )
733 if err != nil {
734 return nil, fmt.Errorf("could not create provider: %v", err)
735 }
736
737 return agentProvider, nil
738}