@@ -208,6 +208,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
208208 }
209209
210210 case pubsub.Event[message.Message]:
211+ // TODO: handle nested messages for agentic tools
211212 if m.session == nil || msg.Payload.SessionID != m.session.ID {
212213 break
213214 }
@@ -217,8 +218,6 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
217218 case pubsub.UpdatedEvent:
218219 cmds = append(cmds, m.updateSessionMessage(msg.Payload))
219220 }
220− // TODO: Finish implementing me
221− // cmds = append(cmds, m.setMessageEvents(msg.Payload))
222221 case pubsub.Event[history.File]:
223222 cmds = append(cmds, m.handleFileEvent(msg.Payload))
224223 case pubsub.Event[app.LSPEvent]:
@@ -403,35 +402,81 @@ func (m *UI) setSessionMessages(msgs []message.Message) tea.Cmd {
403402}
404403
405404// appendSessionMessage appends a new message to the current session in the chat
405+// if the message is a tool result it will update the corresponding tool call message
406406func (m *UI) appendSessionMessage(msg message.Message) tea.Cmd {
407− items := chat.GetMessageItems(m.com.Styles, &msg, nil)
408407 var cmds []tea.Cmd
409− for _, item := range items {
410− if animatable, ok := item.(chat.Animatable); ok {
411− if cmd := animatable.StartAnimation(); cmd != nil {
412− cmds = append(cmds, cmd)
408+ switch msg.Role {
409+ case message.User, message.Assistant:
410+ items := chat.GetMessageItems(m.com.Styles, &msg, nil)
411+ for _, item := range items {
412+ if animatable, ok := item.(chat.Animatable); ok {
413+ if cmd := animatable.StartAnimation(); cmd != nil {
414+ cmds = append(cmds, cmd)
415+ }
416+ }
417+ }
418+ m.chat.AppendMessages(items...)
419+ if cmd := m.chat.ScrollToBottom(); cmd != nil {
420+ cmds = append(cmds, cmd)
421+ }
422+ case message.Tool:
423+ for _, tr := range msg.ToolResults() {
424+ toolItem := m.chat.GetMessageItem(tr.ToolCallID)
425+ if toolItem == nil {
426+ // we should have an item!
427+ continue
428+ }
429+ if toolMsgItem, ok := toolItem.(*chat.ToolMessageItem); ok {
430+ toolMsgItem.SetResult(&tr)
413431 }
414432 }
415− }
416− m.chat.AppendMessages(items...)
417− if cmd := m.chat.ScrollToBottom(); cmd != nil {
418− cmds = append(cmds, cmd)
419433 }
420434 return tea.Batch(cmds...)
421435}
422436
423437// updateSessionMessage updates an existing message in the current session in the chat
424−// INFO: currently only updates the assistant when I add tools this will get a bit more complex
438+// when an assistant message is updated it may include updated tool calls as well
439+// that is why we need to handle creating/updating each tool call message too
425440func (m *UI) updateSessionMessage(msg message.Message) tea.Cmd {
441+ var cmds []tea.Cmd
426442 existingItem := m.chat.GetMessageItem(msg.ID)
427− switch msg.Role {
428− case message.Assistant:
429− if assistantItem, ok := existingItem.(*chat.AssistantMessageItem); ok {
430− assistantItem.SetMessage(&msg)
443+ if existingItem == nil || msg.Role != message.Assistant {
444+ return nil
445+ }
446+
447+ if assistantItem, ok := existingItem.(*chat.AssistantMessageItem); ok {
448+ assistantItem.SetMessage(&msg)
449+ }
450+
451+ var items []chat.MessageItem
452+ for _, tc := range msg.ToolCalls() {
453+ existingToolItem := m.chat.GetMessageItem(tc.ID)
454+ if toolItem, ok := existingToolItem.(*chat.ToolMessageItem); ok {
455+ existingToolCall := toolItem.ToolCall()
456+ // only update if finished state changed or input changed
457+ // to avoid clearing the cache
458+ if (tc.Finished && !existingToolCall.Finished) || tc.Input != existingToolCall.Input {
459+ toolItem.SetToolCall(tc)
460+ }
461+ }
462+ if existingToolItem == nil {
463+ items = append(items, chat.GetToolMessageItem(m.com.Styles, tc, nil, false))
431464 }
432465 }
433466
434− return nil
467+ for _, item := range items {
468+ if animatable, ok := item.(chat.Animatable); ok {
469+ if cmd := animatable.StartAnimation(); cmd != nil {
470+ cmds = append(cmds, cmd)
471+ }
472+ }
473+ }
474+ m.chat.AppendMessages(items...)
475+ if cmd := m.chat.ScrollToBottom(); cmd != nil {
476+ cmds = append(cmds, cmd)
477+ }
478+
479+ return tea.Batch(cmds...)
435480}
436481
437482func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
@@ -498,6 +543,13 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
498543 case dialog.SwitchSessionsMsg:
499544 cmds = append(cmds, m.listSessions)
500545 m.dialog.CloseDialog(dialog.CommandsID)
546+ case dialog.NewSessionsMsg:
547+ if m.com.App.AgentCoordinator != nil && m.com.App.AgentCoordinator.IsBusy() {
548+ cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before starting a new session..."))
549+ break
550+ }
551+ m.newSession()
552+ m.dialog.CloseDialog(dialog.CommandsID)
501553 case dialog.CompactMsg:
502554 err := m.com.App.AgentCoordinator.Summarize(context.Background(), msg.SessionID)
503555 if err != nil {
@@ -548,6 +600,15 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
548600 m.randomizePlaceholders()
549601
550602 return m.sendMessage(value, attachments)
603+ case key.Matches(msg, m.keyMap.Chat.NewSession):
604+ if m.session == nil || m.session.ID == "" {
605+ break
606+ }
607+ if m.com.App.AgentCoordinator != nil && m.com.App.AgentCoordinator.IsBusy() {
608+ cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before starting a new session..."))
609+ break
610+ }
611+ m.newSession()
551612 case key.Matches(msg, m.keyMap.Tab):
552613 m.focus = uiFocusMain
553614 m.textarea.Blur()
@@ -1362,6 +1423,22 @@ func (m *UI) listSessions() tea.Msg {
13621423 return listSessionsMsg{sessions: allSessions}
13631424}
13641425
1426+// newSession clears the current session state and prepares for a new session.
1427+// The actual session creation happens when the user sends their first message.
1428+func (m *UI) newSession() {
1429+ if m.session == nil || m.session.ID == "" {
1430+ return
1431+ }
1432+
1433+ m.session = nil
1434+ m.sessionFiles = nil
1435+ m.state = uiLanding
1436+ m.focus = uiFocusEditor
1437+ m.textarea.Focus()
1438+ m.chat.Blur()
1439+ m.chat.ClearMessages()
1440+}
1441+
13651442// handlePasteMsg handles a paste message.
13661443func (m *UI) handlePasteMsg(msg tea.PasteMsg) tea.Cmd {
13671444 if m.focus != uiFocusEditor {