on main help sign in
chore: show API errors
df84bdfKujtim Hoxha
internal/llm/agent/agent.go+12−13
@@ -403,7 +403,7 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
403 agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, msgHistory)
403 agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, msgHistory)
404 if err != nil {
404 if err != nil {
405 if errors.Is(err, context.Canceled) {
405 if errors.Is(err, context.Canceled) {
406− agentMessage.AddFinish(message.FinishReasonCanceled)
406+ agentMessage.AddFinish(message.FinishReasonCanceled, "Request cancelled", "")
407 a.messages.Update(context.Background(), agentMessage)
407 a.messages.Update(context.Background(), agentMessage)
408 return a.err(ErrRequestCancelled)
408 return a.err(ErrRequestCancelled)
409 }
409 }
@@ -454,11 +454,15 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
454 // Process each event in the stream.
454 // Process each event in the stream.
455 for event := range eventChan {
455 for event := range eventChan {
456 if processErr := a.processEvent(ctx, sessionID, &assistantMsg, event); processErr != nil {
456 if processErr := a.processEvent(ctx, sessionID, &assistantMsg, event); processErr != nil {
457+ if errors.Is(processErr, context.Canceled) {
458+ a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled, "Request cancelled", "")
459+ } else {
457− a.finishMessage(ctx, &assistantMsg, message.FinishReasonCanceled)
460+ a.finishMessage(ctx, &assistantMsg, message.FinishReasonError, "API Error", processErr.Error())
461+ }
458 return assistantMsg, nil, processErr
462 return assistantMsg, nil, processErr
459 }
463 }
460 if ctx.Err() != nil {
464 if ctx.Err() != nil {
461− a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
465+ a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled, "Request cancelled", "")
462 return assistantMsg, nil, ctx.Err()
466 return assistantMsg, nil, ctx.Err()
463 }
467 }
464 }
468 }
@@ -468,7 +472,7 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
468 for i, toolCall := range toolCalls {
472 for i, toolCall := range toolCalls {
469 select {
473 select {
470 case <-ctx.Done():
474 case <-ctx.Done():
471− a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
475+ a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled, "Request cancelled", "")
472 // Make all future tool calls cancelled
476 // Make all future tool calls cancelled
473 for j := i; j < len(toolCalls); j++ {
477 for j := i; j < len(toolCalls); j++ {
474 toolResults[j] = message.ToolResult{
478 toolResults[j] = message.ToolResult{
@@ -516,7 +520,7 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
516 IsError: true,
520 IsError: true,
517 }
521 }
518 }
522 }
519− a.finishMessage(ctx, &assistantMsg, message.FinishReasonPermissionDenied)
523+ a.finishMessage(ctx, &assistantMsg, message.FinishReasonPermissionDenied, "Permission denied", "")
520 break
524 break
521 }
525 }
522 }
526 }
@@ -548,8 +552,8 @@ out:
548 return assistantMsg, &msg, err
552 return assistantMsg, &msg, err
549}
553}
550
554
551−func (a *agent) finishMessage(ctx context.Context, msg *message.Message, finishReson message.FinishReason) {
555+func (a *agent) finishMessage(ctx context.Context, msg *message.Message, finishReson message.FinishReason, message, details string) {
552− msg.AddFinish(finishReson)
556+ msg.AddFinish(finishReson, message, details)
553 _ = a.messages.Update(ctx, *msg)
557 _ = a.messages.Update(ctx, *msg)
554}
558}
555
559
@@ -580,15 +584,10 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
580 assistantMsg.FinishToolCall(event.ToolCall.ID)
584 assistantMsg.FinishToolCall(event.ToolCall.ID)
581 return a.messages.Update(ctx, *assistantMsg)
585 return a.messages.Update(ctx, *assistantMsg)
582 case provider.EventError:
586 case provider.EventError:
583− if errors.Is(event.Error, context.Canceled) {
584− slog.Info(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
585− return context.Canceled
586− }
587− slog.Error(event.Error.Error())
588 return event.Error
587 return event.Error
589 case provider.EventComplete:
588 case provider.EventComplete:
590 assistantMsg.SetToolCalls(event.Response.ToolCalls)
589 assistantMsg.SetToolCalls(event.Response.ToolCalls)
591− assistantMsg.AddFinish(event.Response.FinishReason)
590+ assistantMsg.AddFinish(event.Response.FinishReason, "", "")
592 if err := a.messages.Update(ctx, *assistantMsg); err != nil {
591 if err := a.messages.Update(ctx, *assistantMsg); err != nil {
593 return fmt.Errorf("failed to update message: %w", err)
592 return fmt.Errorf("failed to update message: %w", err)
594 }
593 }
internal/message/content.go+6−4
@@ -102,8 +102,10 @@ type ToolResult struct {
102func (ToolResult) isPart() {}
102func (ToolResult) isPart() {}
103
103
104type Finish struct {
104type Finish struct {
105− Reason FinishReason `json:"reason"`
105+ Reason FinishReason `json:"reason"`
106− Time int64 `json:"time"`
106+ Time int64 `json:"time"`
107+ Message string `json:"message,omitempty"`
108+ Details string `json:"details,omitempty"`
107}
109}
108
110
109func (Finish) isPart() {}
111func (Finish) isPart() {}
@@ -308,7 +310,7 @@ func (m *Message) SetToolResults(tr []ToolResult) {
308 }
310 }
309}
311}
310
312
311−func (m *Message) AddFinish(reason FinishReason) {
313+func (m *Message) AddFinish(reason FinishReason, message, details string) {
312 // remove any existing finish part
314 // remove any existing finish part
313 for i, part := range m.Parts {
315 for i, part := range m.Parts {
314 if _, ok := part.(Finish); ok {
316 if _, ok := part.(Finish); ok {
@@ -316,7 +318,7 @@ func (m *Message) AddFinish(reason FinishReason) {
316 break
318 break
317 }
319 }
318 }
320 }
319− m.Parts = append(m.Parts, Finish{Reason: reason, Time: time.Now().Unix()})
321+ m.Parts = append(m.Parts, Finish{Reason: reason, Time: time.Now().Unix(), Message: message, Details: details})
320}
322}
321
323
322func (m *Message) AddImageURL(url, detail string) {
324func (m *Message) AddImageURL(url, detail string) {
internal/tui/components/chat/messages/messages.go+9
@@ -8,6 +8,7 @@ import (
8
8
9 tea "github.com/charmbracelet/bubbletea/v2"
9 tea "github.com/charmbracelet/bubbletea/v2"
10 "github.com/charmbracelet/lipgloss/v2"
10 "github.com/charmbracelet/lipgloss/v2"
11+ "github.com/charmbracelet/x/ansi"
11
12
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/fur/provider"
14 "github.com/charmbracelet/crush/internal/fur/provider"
@@ -184,6 +185,7 @@ func (m *messageCmp) toMarkdown(content string) string {
184// markdownContent processes the message content and handles special states.
185// markdownContent processes the message content and handles special states.
185// Returns appropriate content for thinking, finished, and error states.
186// Returns appropriate content for thinking, finished, and error states.
186func (m *messageCmp) markdownContent() string {
187func (m *messageCmp) markdownContent() string {
188+ t := styles.CurrentTheme()
187 content := m.message.Content().String()
189 content := m.message.Content().String()
188 if m.message.Role == message.Assistant {
190 if m.message.Role == message.Assistant {
189 thinking := m.message.IsThinking()
191 thinking := m.message.IsThinking()
@@ -199,6 +201,13 @@ func (m *messageCmp) markdownContent() string {
199 content = ""
201 content = ""
200 } else if finished && content == "" && finishedData.Reason == message.FinishReasonCanceled {
202 } else if finished && content == "" && finishedData.Reason == message.FinishReasonCanceled {
201 content = "*Canceled*"
203 content = "*Canceled*"
204+ } else if finished && content == "" && finishedData.Reason == message.FinishReasonError {
205+ errTag := t.S().Base.Padding(0, 1).Background(t.Red).Foreground(t.White).Render("ERROR")
206+ truncated := ansi.Truncate(finishedData.Message, m.textWidth()-2-lipgloss.Width(errTag), "...")
207+ title := fmt.Sprintf("%s %s", errTag, t.S().Base.Foreground(t.FgHalfMuted).Render(truncated))
208+ details := t.S().Base.Foreground(t.FgSubtle).Width(m.textWidth() - 2).Render(finishedData.Details)
209+ return fmt.Sprintf("%s\n\n%s", title, details)
210+
202 }
211 }
203 }
212 }
204 return m.toMarkdown(content)
213 return m.toMarkdown(content)