1package agent
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "github.com/patriceckhart/zot/packages/agent/modes"
9 "github.com/patriceckhart/zot/packages/core"
10 "github.com/patriceckhart/zot/packages/provider"
11)
12
13func TestLiveInteractiveAgentUsesReplacementAgentForSessionResume(t *testing.T) {
14 startup := core.NewAgent(nil, "startup-model", "", nil)
15 startup.SetMessages([]provider.Message{{
16 Role: provider.RoleUser,
17 Content: []provider.Content{provider.TextBlock{Text: "startup transcript"}},
18 }})
19
20 replacement := core.NewAgent(nil, "replacement-model", "", nil)
21 iv := modes.NewInteractive(modes.InteractiveConfig{Agent: replacement})
22
23 resumed := []provider.Message{{
24 Role: provider.RoleUser,
25 Content: []provider.Content{provider.TextBlock{Text: "resumed transcript"}},
26 }}
27 liveInteractiveAgent(iv, startup).SetMessages(resumed)
28
29 if got := firstMessageText(replacement.Messages()); got != "resumed transcript" {
30 t.Fatalf("replacement agent transcript = %q, want resumed transcript", got)
31 }
32 if got := firstMessageText(startup.Messages()); got != "startup transcript" {
33 t.Fatalf("startup agent transcript changed to %q", got)
34 }
35}
36
37func TestBindAgentSession(t *testing.T) {
38 ag := core.NewAgent(nil, "@preset/flash", "", nil)
39 bindAgentSession(ag, &core.Session{ID: "sess-xyz"})
40 if ag.SessionID != "sess-xyz" {
41 t.Fatalf("SessionID = %q; want sess-xyz", ag.SessionID)
42 }
43 bindAgentSession(ag, &core.Session{ID: "other"})
44 if ag.SessionID != "other" {
45 t.Fatalf("SessionID = %q; want other after rebind", ag.SessionID)
46 }
47 bindAgentSession(ag, nil)
48 if ag.SessionID != "other" {
49 t.Fatalf("nil session mutated SessionID to %q", ag.SessionID)
50 }
51}
52
53func TestRotateInteractiveSessionPreservesOldTranscriptAndResetsAgent(t *testing.T) {
54 root := t.TempDir()
55 cwd := t.TempDir()
56 old, err := core.NewSession(root, cwd, "test-provider", "old-model", "test")
57 if err != nil {
58 t.Fatal(err)
59 }
60 ag := core.NewAgent(nil, "new-model", "", nil)
61 ag.SetMessages([]provider.Message{{
62 Role: provider.RoleUser,
63 Content: []provider.Content{provider.TextBlock{Text: "keep this conversation"}},
64 }})
65 ag.SeedCost(provider.Usage{InputTokens: 12, CostUSD: 0.25})
66 ag.SeedLastTurnUsage(provider.Usage{InputTokens: 12})
67 ag.QueueMessage("drop queued prompt")
68
69 fresh, err := rotateInteractiveSession(root, cwd, "test-provider", "new-model", "test", ag, old, 0)
70 if err != nil {
71 t.Fatal(err)
72 }
73 defer fresh.Close()
74 if fresh.ID == old.ID || fresh.Path == old.Path {
75 t.Fatalf("session was not rotated: old=%s new=%s", old.Path, fresh.Path)
76 }
77 if ag.SessionID != fresh.ID {
78 t.Fatalf("agent session ID = %q, want %q", ag.SessionID, fresh.ID)
79 }
80 if len(ag.Messages()) != 0 {
81 t.Fatalf("new transcript is not empty: %+v", ag.Messages())
82 }
83 if got := ag.Cost(); got != (provider.Usage{}) {
84 t.Fatalf("new session cost = %+v, want zero", got)
85 }
86 if got := ag.LastTurnUsage(); got != (provider.Usage{}) {
87 t.Fatalf("new session last usage = %+v, want zero", got)
88 }
89 if got := ag.QueuedMessageCount(); got != 0 {
90 t.Fatalf("queued messages = %d, want zero", got)
91 }
92
93 persisted, messages, err := core.OpenSession(old.Path)
94 if err != nil {
95 t.Fatalf("open previous session: %v", err)
96 }
97 defer persisted.Close()
98 if got := firstMessageText(messages); got != "keep this conversation" {
99 t.Fatalf("previous transcript = %q", got)
100 }
101}
102
103func TestRotateInteractiveSessionCreationFailureLeavesAgentUntouched(t *testing.T) {
104 ag := core.NewAgent(nil, "model", "", nil)
105 ag.SetMessages([]provider.Message{{Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "still active"}}}})
106 blockedRoot := filepath.Join(t.TempDir(), "file")
107 if err := os.WriteFile(blockedRoot, []byte("not a directory"), 0o600); err != nil {
108 t.Fatal(err)
109 }
110
111 if _, err := rotateInteractiveSession(blockedRoot, t.TempDir(), "test", "model", "test", ag, nil, 0); err == nil {
112 t.Fatal("rotateInteractiveSession returned nil error for invalid root")
113 }
114 if got := firstMessageText(ag.Messages()); got != "still active" {
115 t.Fatalf("agent transcript changed to %q after failed rotation", got)
116 }
117}
118
119func TestLiveInteractiveAgentFallsBackBeforeInteractiveConstruction(t *testing.T) {
120 startup := core.NewAgent(nil, "startup-model", "", nil)
121 if got := liveInteractiveAgent(nil, startup); got != startup {
122 t.Fatalf("fallback agent = %p, want %p", got, startup)
123 }
124}
125
126func firstMessageText(messages []provider.Message) string {
127 if len(messages) == 0 || len(messages[0].Content) == 0 {
128 return ""
129 }
130 text, _ := messages[0].Content[0].(provider.TextBlock)
131 return text.Text
132}