diff --git a/chat/chat.go b/chat/chat.go index be7daf1..f051604 100644 --- a/chat/chat.go +++ b/chat/chat.go @@ -3,6 +3,8 @@ package chat import ( "context" "strings" + + "github.com/x2d7/interlude/chat/tools" ) func (c *Chat) Complete(ctx context.Context, client Client) <-chan StreamEvent { @@ -69,7 +71,20 @@ func (s *sessionState) flushLastToolCall() bool { return ok } +func (c *Chat) ensureDefaults() { + if c.Messages == nil { + c.Messages = NewMessages() + } + if c.Tools == nil { + t := tools.NewTools() + c.Tools = &t + } +} + func (c *Chat) Session(ctx context.Context, client Client) <-chan StreamEvent { + // ensuring default values + c.ensureDefaults() + // insert chat context into client input configuration client = client.SyncInput(c) diff --git a/chat/chat_test.go b/chat/chat_test.go index f569de8..4e3a04d 100644 --- a/chat/chat_test.go +++ b/chat/chat_test.go @@ -1416,3 +1416,112 @@ func TestSession_MixedTokensAndToolCalls(t *testing.T) { t.Errorf("Expected 2 tool calls in history, got %d", toolCallsInHistory) } } + +// ==================== ensureDefaults Tests ==================== + +// TestEnsureDefaults_MessagesNil tests that Messages is initialized when nil +func TestEnsureDefaults_MessagesNil(t *testing.T) { + tools := tools.NewTools() + chat := &Chat{ + Messages: nil, + Tools: &tools, + } + + chat.ensureDefaults() + + if chat.Messages == nil { + t.Error("Messages should not be nil after ensureDefaults") + } + + // Verify Messages is usable + messages := chat.Messages.Snapshot() + if len(messages) != 0 { + t.Errorf("Expected empty messages, got %d", len(messages)) + } +} + +// TestEnsureDefaults_ToolsNil tests that Tools is initialized when nil +func TestEnsureDefaults_ToolsNil(t *testing.T) { + chat := &Chat{ + Messages: NewMessages(), + Tools: nil, + } + + chat.ensureDefaults() + + if chat.Tools == nil { + t.Error("Tools should not be nil after ensureDefaults") + } + + // Verify Tools is usable (empty slice, not nil) + toolList := chat.Tools.Snapshot() + if len(toolList) != 0 { + t.Errorf("Expected empty tools, got %d", len(toolList)) + } +} + +// TestEnsureDefaults_BothNil tests that both Messages and Tools are initialized when both are nil +func TestEnsureDefaults_BothNil(t *testing.T) { + chat := &Chat{ + Messages: nil, + Tools: nil, + } + + chat.ensureDefaults() + + if chat.Messages == nil { + t.Error("Messages should not be nil after ensureDefaults") + } + + if chat.Tools == nil { + t.Error("Tools should not be nil after ensureDefaults") + } +} + +// TestEnsureDefaults_AlreadySet tests that existing values are not overwritten +func TestEnsureDefaults_AlreadySet(t *testing.T) { + // Setup chat with existing messages and tools + ts := tools.NewTools() + chat := &Chat{ + Messages: NewMessages(), + Tools: &ts, // Use NewTools() to properly initialize + } + + // Add some data + chat.AddMessage(SenderUser{}, "test message") + tool, err := tools.NewTool("test-tool", "Test tool", + func(input map[string]string) (string, error) { + return "result", nil + }) + if err != nil { + t.Fatalf("Failed to create tool: %v", err) + } + chat.Tools.Add(tool) + + // Get pointers to verify they're not replaced + messagesBefore := chat.Messages + toolsBefore := chat.Tools + + // Call ensureDefaults + chat.ensureDefaults() + + // Verify same instances are preserved + if chat.Messages != messagesBefore { + t.Error("Messages should not be replaced when already set") + } + + if chat.Tools != toolsBefore { + t.Error("Tools should not be replaced when already set") + } + + // Verify data is still intact + messages := chat.Messages.Snapshot() + if len(messages) != 1 { + t.Errorf("Expected 1 message, got %d", len(messages)) + } + + toolList := chat.Tools.Snapshot() + if len(toolList) != 1 { + t.Errorf("Expected 1 tool, got %d", len(toolList)) + } +}