Skip to content

Commit 109cbc4

Browse files
test(agent): pin a drained read-ahead sibling's image through the guard halt
The halt drains read-ahead siblings that already ran and records them as completed, images included. Images now reach the transcript through the turn's deferred builder, and nothing checked that a drained sibling's image still went through it: dropping it left every test green. The probe tool returns an image from the sibling and the run must carry exactly one image message.
1 parent 2b10462 commit 109cbc4

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

‎internal/agent/parallel_readahead_halt_test.go‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,61 @@ func TestParallelSiblingsAreFinalizedWhenTheGuardHalts(t *testing.T) {
136136
}
137137
}
138138

139+
// imageReadTool is readAheadTool with an image on one named call, so a drained
140+
// sibling's image can be followed from execution to the transcript.
141+
type imageReadTool struct {
142+
readAheadTool
143+
imageFor string
144+
}
145+
146+
func (t *imageReadTool) Run(ctx context.Context, args map[string]any) tools.Result {
147+
result := t.readAheadTool.Run(ctx, args)
148+
if id, _ := args["id"].(string); id == t.imageFor {
149+
result.Images = []zeroruntime.ImageBlock{{MediaType: "image/png", Data: []byte("\x89PNG")}}
150+
}
151+
return result
152+
}
153+
154+
// The drained sibling's image is part of what ran, so it follows the tool
155+
// results like any other call's image. Images reach the transcript through the
156+
// turn's deferred builder, which the halt path calls after closing out the
157+
// siblings; a drain that skipped the sibling's image would lose it silently.
158+
func TestParallelSiblingImageSurvivesTheGuardHalt(t *testing.T) {
159+
tool := &imageReadTool{imageFor: "pB"}
160+
registry := tools.NewRegistry()
161+
registry.Register(tool)
162+
163+
var turns [][]zeroruntime.StreamEvent
164+
for i := 0; i < toolFailureAnyErrorStopAt-1; i++ {
165+
id := "s" + strconv.Itoa(i)
166+
turns = append(turns, append(readCall(id), zeroruntime.StreamEvent{Type: zeroruntime.StreamEventDone}))
167+
}
168+
pair := append(readCall("pA"), readCall("pB")...)
169+
turns = append(turns, append(pair, zeroruntime.StreamEvent{Type: zeroruntime.StreamEventDone}))
170+
171+
result, err := Run(context.Background(), "read things", &mockProvider{turns: turns}, Options{
172+
Registry: registry,
173+
PermissionMode: PermissionModeAuto,
174+
MaxTurns: len(turns) + 5,
175+
SupportsVision: func(string) bool { return true },
176+
})
177+
if err != nil {
178+
t.Fatal(err)
179+
}
180+
if !strings.HasPrefix(result.FinalAnswer, "Agent stopped:") {
181+
t.Fatalf("the guard did not halt this run: %q", result.FinalAnswer)
182+
}
183+
images := 0
184+
for _, message := range result.Messages {
185+
if message.Role == zeroruntime.MessageRoleUser {
186+
images += len(message.Images)
187+
}
188+
}
189+
if images != 1 {
190+
t.Errorf("the drained sibling's image reached the transcript %d times, want once", images)
191+
}
192+
}
193+
139194
// A call that genuinely never ran still gets an aborted placeholder, so the fix
140195
// did not simply stop aborting anything.
141196
func TestUnstartedCallsStillGetAbortedPlaceholders(t *testing.T) {

0 commit comments

Comments
 (0)