Skip to content

Commit 0a6dbb4

Browse files
authored
Merge pull request #1398 from krissetto/load-reasoning-from-session
Restore reasoning content in TUI when loading a session
2 parents 162cf2c + 2421d67 commit 0a6dbb4

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

pkg/tui/components/messages/messages.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,15 @@ func (m *model) LoadFromSession(sess *session.Session) tea.Cmd {
831831
msg := types.User(smsg.Message.Content)
832832
m.messages = append(m.messages, msg)
833833
m.views = append(m.views, m.createMessageView(msg))
834+
m.sessionState.PreviousMessage = msg
834835
case chat.MessageRoleAssistant:
836+
// Reasoning content comes first (matches live stream order)
837+
if smsg.Message.ReasoningContent != "" {
838+
msg := types.Agent(types.MessageTypeAssistantReasoning, smsg.AgentName, smsg.Message.ReasoningContent)
839+
m.messages = append(m.messages, msg)
840+
m.views = append(m.views, m.createMessageView(msg))
841+
m.sessionState.PreviousMessage = msg
842+
}
835843
for i, tc := range smsg.Message.ToolCalls {
836844
var toolDef tools.Tool
837845
if i < len(smsg.Message.ToolDefinitions) {
@@ -840,11 +848,13 @@ func (m *model) LoadFromSession(sess *session.Session) tea.Cmd {
840848
msg := types.ToolCallMessage(smsg.AgentName, tc, toolDef, types.ToolStatusCompleted)
841849
m.messages = append(m.messages, msg)
842850
m.views = append(m.views, m.createToolCallView(msg))
851+
m.sessionState.PreviousMessage = msg
843852
}
844853
if smsg.Message.Content != "" {
845854
msg := types.Agent(types.MessageTypeAssistant, smsg.AgentName, smsg.Message.Content)
846855
m.messages = append(m.messages, msg)
847856
m.views = append(m.views, m.createMessageView(msg))
857+
m.sessionState.PreviousMessage = msg
848858
}
849859
case chat.MessageRoleTool:
850860
continue

pkg/tui/components/messages/messages_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import (
66

77
"github.com/charmbracelet/x/ansi"
88
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
910

11+
"github.com/docker/cagent/pkg/chat"
12+
"github.com/docker/cagent/pkg/session"
13+
"github.com/docker/cagent/pkg/tools"
1014
"github.com/docker/cagent/pkg/tui/service"
1115
"github.com/docker/cagent/pkg/tui/types"
1216
)
@@ -27,3 +31,115 @@ func TestViewDoesNotWrapWideLines(t *testing.T) {
2731
assert.LessOrEqual(t, ansi.StringWidth(line), 20)
2832
}
2933
}
34+
35+
func TestLoadFromSessionIncludesReasoningContent(t *testing.T) {
36+
t.Parallel()
37+
38+
sessionState := &service.SessionState{}
39+
m := NewScrollableView(80, 24, sessionState).(*model)
40+
m.SetSize(80, 24)
41+
42+
sess := &session.Session{
43+
ID: "test-session",
44+
Messages: []session.Item{
45+
session.NewMessageItem(&session.Message{
46+
Message: chat.Message{
47+
Role: chat.MessageRoleUser,
48+
Content: "Hello",
49+
},
50+
}),
51+
session.NewMessageItem(&session.Message{
52+
AgentName: "root",
53+
Message: chat.Message{
54+
Role: chat.MessageRoleAssistant,
55+
ReasoningContent: "Let me think about this...",
56+
Content: "Hello back!",
57+
},
58+
}),
59+
},
60+
}
61+
62+
m.LoadFromSession(sess)
63+
64+
require.Len(t, m.messages, 3)
65+
// User message first
66+
assert.Equal(t, types.MessageTypeUser, m.messages[0].Type)
67+
assert.Equal(t, "Hello", m.messages[0].Content)
68+
// Reasoning content second
69+
assert.Equal(t, types.MessageTypeAssistantReasoning, m.messages[1].Type)
70+
assert.Equal(t, "Let me think about this...", m.messages[1].Content)
71+
assert.Equal(t, "root", m.messages[1].Sender)
72+
// Assistant content third
73+
assert.Equal(t, types.MessageTypeAssistant, m.messages[2].Type)
74+
assert.Equal(t, "Hello back!", m.messages[2].Content)
75+
assert.Equal(t, "root", m.messages[2].Sender)
76+
}
77+
78+
func TestLoadFromSessionReasoningOrderWithToolCalls(t *testing.T) {
79+
t.Parallel()
80+
81+
sessionState := &service.SessionState{}
82+
m := NewScrollableView(80, 24, sessionState).(*model)
83+
m.SetSize(80, 24)
84+
85+
sess := &session.Session{
86+
ID: "test-session",
87+
Messages: []session.Item{
88+
session.NewMessageItem(&session.Message{
89+
AgentName: "root",
90+
Message: chat.Message{
91+
Role: chat.MessageRoleAssistant,
92+
ReasoningContent: "I should call a tool...",
93+
ToolCalls: []tools.ToolCall{
94+
{ID: "call-1", Function: tools.FunctionCall{Name: "test_tool", Arguments: "{}"}},
95+
},
96+
ToolDefinitions: []tools.Tool{
97+
{Name: "test_tool", Description: "A test tool"},
98+
},
99+
Content: "Tool result processed.",
100+
},
101+
}),
102+
},
103+
}
104+
105+
m.LoadFromSession(sess)
106+
107+
require.Len(t, m.messages, 3)
108+
// Reasoning comes first
109+
assert.Equal(t, types.MessageTypeAssistantReasoning, m.messages[0].Type)
110+
assert.Equal(t, "I should call a tool...", m.messages[0].Content)
111+
// Tool call comes second
112+
assert.Equal(t, types.MessageTypeToolCall, m.messages[1].Type)
113+
assert.Equal(t, "test_tool", m.messages[1].ToolCall.Function.Name)
114+
// Assistant content comes last
115+
assert.Equal(t, types.MessageTypeAssistant, m.messages[2].Type)
116+
assert.Equal(t, "Tool result processed.", m.messages[2].Content)
117+
}
118+
119+
func TestLoadFromSessionReasoningOnlyNoContent(t *testing.T) {
120+
t.Parallel()
121+
122+
sessionState := &service.SessionState{}
123+
m := NewScrollableView(80, 24, sessionState).(*model)
124+
m.SetSize(80, 24)
125+
126+
sess := &session.Session{
127+
ID: "test-session",
128+
Messages: []session.Item{
129+
session.NewMessageItem(&session.Message{
130+
AgentName: "root",
131+
Message: chat.Message{
132+
Role: chat.MessageRoleAssistant,
133+
ReasoningContent: "Thinking deeply...",
134+
Content: "", // No visible content, only reasoning
135+
},
136+
}),
137+
},
138+
}
139+
140+
m.LoadFromSession(sess)
141+
142+
require.Len(t, m.messages, 1)
143+
assert.Equal(t, types.MessageTypeAssistantReasoning, m.messages[0].Type)
144+
assert.Equal(t, "Thinking deeply...", m.messages[0].Content)
145+
}

0 commit comments

Comments
 (0)