on main help sign in
chore: show API errors
commitdf84bdf59cebddb4f1659b7cabc93eec860a4fdc authorKujtim Hoxha <kujtimii.h@gmail.com> dateMon 14 Jul 2025, 14:00 (1 year ago) parentfebf526
3 files changed+27−17
internal/llm/agent/agent.go+12−13 internal/message/content.go+6−4 internal/tui/components/chat/messages/messages.go+9
internal/llm/agent/agent.go+12−13
@@ -403,7 +403,7 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
403403 agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, msgHistory)
404404 if err != nil {
405405 if errors.Is(err, context.Canceled) {
406− agentMessage.AddFinish(message.FinishReasonCanceled)
406+ agentMessage.AddFinish(message.FinishReasonCanceled, "Request cancelled", "")
407407 a.messages.Update(context.Background(), agentMessage)
408408 return a.err(ErrRequestCancelled)
409409 }
@@ -454,11 +454,15 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
454454 // Process each event in the stream.
455455 for event := range eventChan {
456456 if processErr := a.processEvent(ctx, sessionID, &assistantMsg, event); processErr != nil {
457− a.finishMessage(ctx, &assistantMsg, message.FinishReasonCanceled)
457+ if errors.Is(processErr, context.Canceled) {
458+ a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled, "Request cancelled", "")
459+ } else {
460+ a.finishMessage(ctx, &assistantMsg, message.FinishReasonError, "API Error", processErr.Error())
461+ }
458462 return assistantMsg, nil, processErr
459463 }
460464 if ctx.Err() != nil {
461− a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
465+ a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled, "Request cancelled", "")
462466 return assistantMsg, nil, ctx.Err()
463467 }
464468 }
@@ -468,7 +472,7 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
468472 for i, toolCall := range toolCalls {
469473 select {
470474 case <-ctx.Done():
471− a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
475+ a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled, "Request cancelled", "")
472476 // Make all future tool calls cancelled
473477 for j := i; j < len(toolCalls); j++ {
474478 toolResults[j] = message.ToolResult{
@@ -516,7 +520,7 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
516520 IsError: true,
517521 }
518522 }
519− a.finishMessage(ctx, &assistantMsg, message.FinishReasonPermissionDenied)
523+ a.finishMessage(ctx, &assistantMsg, message.FinishReasonPermissionDenied, "Permission denied", "")
520524 break
521525 }
522526 }
@@ -548,8 +552,8 @@ out:
548552 return assistantMsg, &msg, err
549553}
550554
551−func (a *agent) finishMessage(ctx context.Context, msg *message.Message, finishReson message.FinishReason) {
552− msg.AddFinish(finishReson)
555+func (a *agent) finishMessage(ctx context.Context, msg *message.Message, finishReson message.FinishReason, message, details string) {
556+ msg.AddFinish(finishReson, message, details)
553557 _ = a.messages.Update(ctx, *msg)
554558}
555559
@@ -580,15 +584,10 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
580584 assistantMsg.FinishToolCall(event.ToolCall.ID)
581585 return a.messages.Update(ctx, *assistantMsg)
582586 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())
588587 return event.Error
589588 case provider.EventComplete:
590589 assistantMsg.SetToolCalls(event.Response.ToolCalls)
591− assistantMsg.AddFinish(event.Response.FinishReason)
590+ assistantMsg.AddFinish(event.Response.FinishReason, "", "")
592591 if err := a.messages.Update(ctx, *assistantMsg); err != nil {
593592 return fmt.Errorf("failed to update message: %w", err)
594593 }
internal/message/content.go+6−4
@@ -102,8 +102,10 @@ type ToolResult struct {
102102func (ToolResult) isPart() {}
103103
104104type Finish struct {
105− Reason FinishReason `json:"reason"`
106− Time int64 `json:"time"`
105+ Reason FinishReason `json:"reason"`
106+ Time int64 `json:"time"`
107+ Message string `json:"message,omitempty"`
108+ Details string `json:"details,omitempty"`
107109}
108110
109111func (Finish) isPart() {}
@@ -308,7 +310,7 @@ func (m *Message) SetToolResults(tr []ToolResult) {
308310 }
309311}
310312
311−func (m *Message) AddFinish(reason FinishReason) {
313+func (m *Message) AddFinish(reason FinishReason, message, details string) {
312314 // remove any existing finish part
313315 for i, part := range m.Parts {
314316 if _, ok := part.(Finish); ok {
@@ -316,7 +318,7 @@ func (m *Message) AddFinish(reason FinishReason) {
316318 break
317319 }
318320 }
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})
320322}
321323
322324func (m *Message) AddImageURL(url, detail string) {
internal/tui/components/chat/messages/messages.go+9
@@ -8,6 +8,7 @@ import (
88
99 tea "github.com/charmbracelet/bubbletea/v2"
1010 "github.com/charmbracelet/lipgloss/v2"
11+ "github.com/charmbracelet/x/ansi"
1112
1213 "github.com/charmbracelet/crush/internal/config"
1314 "github.com/charmbracelet/crush/internal/fur/provider"
@@ -184,6 +185,7 @@ func (m *messageCmp) toMarkdown(content string) string {
184185// markdownContent processes the message content and handles special states.
185186// Returns appropriate content for thinking, finished, and error states.
186187func (m *messageCmp) markdownContent() string {
188+ t := styles.CurrentTheme()
187189 content := m.message.Content().String()
188190 if m.message.Role == message.Assistant {
189191 thinking := m.message.IsThinking()
@@ -199,6 +201,13 @@ func (m *messageCmp) markdownContent() string {
199201 content = ""
200202 } else if finished && content == "" && finishedData.Reason == message.FinishReasonCanceled {
201203 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+
202211 }
203212 }
204213 return m.toMarkdown(content)