-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathagent_spawn_callback_test.go
More file actions
121 lines (111 loc) · 3.55 KB
/
Copy pathagent_spawn_callback_test.go
File metadata and controls
121 lines (111 loc) · 3.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package cogito
import (
"context"
"sync"
"testing"
)
// TestWithAgentSpawnCallbackStores asserts the option stores the fn on Options.
func TestWithAgentSpawnCallbackStores(t *testing.T) {
o := defaultOptions()
o.Apply(WithAgentSpawnCallback(func(*AgentState) {}))
if o.agentSpawnCallback == nil {
t.Fatal("spawn callback not stored")
}
}
// TestSpawnCallbackFiresForeground asserts a foreground spawn fires the spawn
// callback with a running AgentState whose Type matches the requested type.
func TestSpawnCallbackFiresForeground(t *testing.T) {
var mu sync.Mutex
var fired bool
var gotStatus AgentStatusType
var gotType string
var nonNil bool
defs := []AgentDefinition{{Name: "explore", SystemPrompt: "You are EXPLORE."}}
runner := &spawnAgentRunner{
llm: newReplyLLM("foreground done"),
manager: NewAgentManager(),
ctx: context.Background(),
agentDefinitions: defs,
// Snapshot the AgentState fields at callback time. The foreground agent
// runs in a goroutine and may mutate Status to "completed" by the time
// Run returns, so we must capture the values inside the callback (while
// Status is still running) rather than reading the live pointer after.
agentSpawnCallback: func(a *AgentState) {
mu.Lock()
fired = true
nonNil = a != nil
if a != nil {
gotStatus = a.Status
gotType = a.Type
}
mu.Unlock()
},
}
_, _, err := runner.Run(SpawnAgentArgs{AgentType: "explore", Task: "look around", Background: false})
if err != nil {
t.Fatalf("foreground spawn errored: %v", err)
}
mu.Lock()
defer mu.Unlock()
if !fired {
t.Fatal("spawn callback did not fire for foreground spawn")
}
if !nonNil {
t.Fatal("spawn callback received a nil AgentState")
}
if gotStatus != AgentStatusRunning {
t.Fatalf("spawn callback AgentState status = %q, want %q", gotStatus, AgentStatusRunning)
}
if gotType != "explore" {
t.Fatalf("spawn callback AgentState type = %q, want %q", gotType, "explore")
}
}
// TestSpawnCallbackFiresBackground asserts a background spawn fires the spawn
// callback synchronously (before Run returns the ID) with a running AgentState
// whose Type matches the requested type.
func TestSpawnCallbackFiresBackground(t *testing.T) {
var mu sync.Mutex
var fired bool
var gotStatus AgentStatusType
var gotType string
var nonNil bool
defs := []AgentDefinition{{Name: "plan", SystemPrompt: "You are PLAN."}}
runner := &spawnAgentRunner{
llm: newReplyLLM("background done"),
manager: NewAgentManager(),
ctx: context.Background(),
agentDefinitions: defs,
// The background spawn fires the callback synchronously (before Run
// returns the ID) while Status is still running, but the agent's
// goroutine may mutate Status afterward, so snapshot inside the callback.
agentSpawnCallback: func(a *AgentState) {
mu.Lock()
fired = true
nonNil = a != nil
if a != nil {
gotStatus = a.Status
gotType = a.Type
}
mu.Unlock()
},
}
out, _, err := runner.Run(SpawnAgentArgs{AgentType: "plan", Task: "make a plan", Background: true})
if err != nil {
t.Fatalf("background spawn errored: %v", err)
}
_ = out
mu.Lock()
defer mu.Unlock()
if !fired {
t.Fatal("spawn callback did not fire for background spawn")
}
if !nonNil {
t.Fatal("spawn callback received a nil AgentState")
}
if gotStatus != AgentStatusRunning {
t.Fatalf("spawn callback AgentState status = %q, want %q", gotStatus, AgentStatusRunning)
}
if gotType != "plan" {
t.Fatalf("spawn callback AgentState type = %q, want %q", gotType, "plan")
}
}