fix: add panic recovery in processMessage, use typed role constants
This commit is contained in:
parent
00c17ba00c
commit
23edcaa213
2 changed files with 33 additions and 17 deletions
|
|
@ -35,9 +35,19 @@ var thinkingFrames = [thinkingFrameCount]string{"⠋", "⠙", "⠹", "⠸"}
|
||||||
// tickMsg drives the thinking animation and event polling
|
// tickMsg drives the thinking animation and event polling
|
||||||
type tickMsg time.Time
|
type tickMsg time.Time
|
||||||
|
|
||||||
|
// chatRole is a typed constant for message roles
|
||||||
|
type chatRole string
|
||||||
|
|
||||||
|
const (
|
||||||
|
roleUser chatRole = "user"
|
||||||
|
roleAssistant chatRole = "assistant"
|
||||||
|
roleTool chatRole = "tool"
|
||||||
|
roleSystem chatRole = "system"
|
||||||
|
)
|
||||||
|
|
||||||
// chatMessage represents a single message in the chat history
|
// chatMessage represents a single message in the chat history
|
||||||
type chatMessage struct {
|
type chatMessage struct {
|
||||||
role string // "user", "assistant", "tool", "system"
|
role chatRole
|
||||||
content string
|
content string
|
||||||
toolName string
|
toolName string
|
||||||
toolID string
|
toolID string
|
||||||
|
|
@ -121,7 +131,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
case ToolCallStartedMsg:
|
case ToolCallStartedMsg:
|
||||||
m.messages = append(m.messages, chatMessage{
|
m.messages = append(m.messages, chatMessage{
|
||||||
role: "tool",
|
role: roleTool,
|
||||||
content: fmt.Sprintf("Running %s...", msg.Name),
|
content: fmt.Sprintf("Running %s...", msg.Name),
|
||||||
toolName: msg.Name,
|
toolName: msg.Name,
|
||||||
toolID: msg.ID,
|
toolID: msg.ID,
|
||||||
|
|
@ -131,7 +141,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
case ToolCallCompletedMsg:
|
case ToolCallCompletedMsg:
|
||||||
for i := len(m.messages) - 1; i >= 0; i-- {
|
for i := len(m.messages) - 1; i >= 0; i-- {
|
||||||
if m.messages[i].role == "tool" && m.messages[i].toolID == msg.ID {
|
if m.messages[i].role == roleTool && m.messages[i].toolID == msg.ID {
|
||||||
m.messages[i].toolDone = true
|
m.messages[i].toolDone = true
|
||||||
m.messages[i].toolErr = msg.IsError
|
m.messages[i].toolErr = msg.IsError
|
||||||
if msg.IsError {
|
if msg.IsError {
|
||||||
|
|
@ -147,7 +157,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
case ResponseMsg:
|
case ResponseMsg:
|
||||||
m.messages = append(m.messages, chatMessage{
|
m.messages = append(m.messages, chatMessage{
|
||||||
role: "assistant",
|
role: roleAssistant,
|
||||||
content: msg.Content,
|
content: msg.Content,
|
||||||
})
|
})
|
||||||
m.thinking = false
|
m.thinking = false
|
||||||
|
|
@ -156,7 +166,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
case ErrorMsg:
|
case ErrorMsg:
|
||||||
m.messages = append(m.messages, chatMessage{
|
m.messages = append(m.messages, chatMessage{
|
||||||
role: "system",
|
role: roleSystem,
|
||||||
content: fmt.Sprintf("Error: %v", msg.Err),
|
content: fmt.Sprintf("Error: %v", msg.Err),
|
||||||
})
|
})
|
||||||
m.thinking = false
|
m.thinking = false
|
||||||
|
|
@ -165,7 +175,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
case SlashCommandResultMsg:
|
case SlashCommandResultMsg:
|
||||||
m.messages = append(m.messages, chatMessage{
|
m.messages = append(m.messages, chatMessage{
|
||||||
role: "system",
|
role: roleSystem,
|
||||||
content: msg.Result,
|
content: msg.Result,
|
||||||
})
|
})
|
||||||
m.thinking = false
|
m.thinking = false
|
||||||
|
|
@ -229,7 +239,7 @@ func (m Model) handleKeyMsg(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
// Add user message to chat
|
// Add user message to chat
|
||||||
m.messages = append(m.messages, chatMessage{
|
m.messages = append(m.messages, chatMessage{
|
||||||
role: "user",
|
role: roleUser,
|
||||||
content: input,
|
content: input,
|
||||||
})
|
})
|
||||||
m.thinking = true
|
m.thinking = true
|
||||||
|
|
@ -298,7 +308,13 @@ func (m Model) processMessage(input string) tea.Cmd {
|
||||||
agentLoop := m.agentLoop
|
agentLoop := m.agentLoop
|
||||||
sessionKey := m.sessionKey
|
sessionKey := m.sessionKey
|
||||||
|
|
||||||
return func() tea.Msg {
|
return func() (result tea.Msg) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
result = ErrorMsg{Err: fmt.Errorf("agent panic: %v", r)}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -377,11 +393,11 @@ func (m *Model) updateViewport() {
|
||||||
|
|
||||||
for _, msg := range m.messages {
|
for _, msg := range m.messages {
|
||||||
switch msg.role {
|
switch msg.role {
|
||||||
case "user":
|
case roleUser:
|
||||||
label := userLabelStyle.Render("You:")
|
label := userLabelStyle.Render("You:")
|
||||||
sb.WriteString(label + " " + msg.content + "\n\n")
|
sb.WriteString(label + " " + msg.content + "\n\n")
|
||||||
|
|
||||||
case "assistant":
|
case roleAssistant:
|
||||||
label := assistantLabelStyle.Render("Assistant:")
|
label := assistantLabelStyle.Render("Assistant:")
|
||||||
rendered := msg.content
|
rendered := msg.content
|
||||||
if m.renderer != nil {
|
if m.renderer != nil {
|
||||||
|
|
@ -391,7 +407,7 @@ func (m *Model) updateViewport() {
|
||||||
}
|
}
|
||||||
sb.WriteString(label + "\n" + rendered + "\n\n")
|
sb.WriteString(label + "\n" + rendered + "\n\n")
|
||||||
|
|
||||||
case "tool":
|
case roleTool:
|
||||||
var styled string
|
var styled string
|
||||||
switch {
|
switch {
|
||||||
case msg.toolErr:
|
case msg.toolErr:
|
||||||
|
|
@ -403,7 +419,7 @@ func (m *Model) updateViewport() {
|
||||||
}
|
}
|
||||||
sb.WriteString(" " + styled + "\n")
|
sb.WriteString(" " + styled + "\n")
|
||||||
|
|
||||||
case "system":
|
case roleSystem:
|
||||||
sb.WriteString(msg.content + "\n\n")
|
sb.WriteString(msg.content + "\n\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -446,7 +462,7 @@ func (m Model) renderStatusBar() string {
|
||||||
// Count user and assistant messages
|
// Count user and assistant messages
|
||||||
msgCount := 0
|
msgCount := 0
|
||||||
for _, msg := range m.messages {
|
for _, msg := range m.messages {
|
||||||
if msg.role == "user" || msg.role == "assistant" {
|
if msg.role == roleUser || msg.role == roleAssistant {
|
||||||
msgCount++
|
msgCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ func TestModel_Update_ResponseMsg_AddsAssistantMessage(t *testing.T) {
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, msg := range model.messages {
|
for _, msg := range model.messages {
|
||||||
if msg.role == "assistant" && msg.content == "Hello, world!" {
|
if msg.role == roleAssistant && msg.content == "Hello, world!" {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ func TestModel_Update_ToolCallStarted_AddsToolMessage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := model.messages[0]
|
msg := model.messages[0]
|
||||||
if msg.role != "tool" {
|
if msg.role != roleTool {
|
||||||
t.Errorf("expected role 'tool', got %q", msg.role)
|
t.Errorf("expected role 'tool', got %q", msg.role)
|
||||||
}
|
}
|
||||||
if msg.toolName != "web_search" {
|
if msg.toolName != "web_search" {
|
||||||
|
|
@ -244,7 +244,7 @@ func TestModel_Update_ErrorMsg_AddsSystemMessage(t *testing.T) {
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, msg := range model.messages {
|
for _, msg := range model.messages {
|
||||||
if msg.role == "system" && strings.Contains(msg.content, "connection refused") {
|
if msg.role == roleSystem && strings.Contains(msg.content, "connection refused") {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +262,7 @@ func TestModel_Update_SlashCommandResult(t *testing.T) {
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, msg := range model.messages {
|
for _, msg := range model.messages {
|
||||||
if msg.role == "system" && msg.content == "Session: default" {
|
if msg.role == roleSystem && msg.content == "Session: default" {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue