1// Package modes implements zut's three run modes: print, json, interactive.
2package modes
3
4import (
5 "context"
6 "fmt"
7 "io"
8 "strings"
9
10 "github.com/bnema/zut/packages/core"
11 "github.com/bnema/zut/packages/provider"
12)
13
14// RunPrint runs the agent to completion and writes only the final
15// assistant text block to out. It returns usage for this invocation,
16// excluding any cumulative usage restored from a session.
17func RunPrint(ctx context.Context, ag *core.Agent, prompt string, images []provider.ImageBlock, out io.Writer) (provider.Usage, error) {
18 usage, _, err := RunPrintWithContextRecovery(ctx, ag, prompt, images, out, nil)
19 return usage, err
20}
21
22// RunPrintWithContextRecovery is RunPrint with an optional checkpoint writer
23// for a one-shot context-overflow compaction. OutputStart identifies the
24// transcript suffix that callers must persist after a successful checkpoint.
25func RunPrintWithContextRecovery(ctx context.Context, ag *core.Agent, prompt string, images []provider.ImageBlock, out io.Writer, persistCompaction func([]provider.Message) error) (provider.Usage, ContextRecoveryResult, error) {
26 var finalText strings.Builder
27 var lastAssistant string
28 var usage provider.Usage
29 var haveUsage bool
30 var runErr error
31
32 sink := func(ev core.AgentEvent) {
33 switch e := ev.(type) {
34 case core.EvAssistantMessage:
35 // Keep the most recent assistant text block; by the end it's the final answer.
36 var sb strings.Builder
37 for _, c := range e.Message.Content {
38 if tb, ok := c.(provider.TextBlock); ok {
39 if sb.Len() > 0 {
40 sb.WriteString("\n")
41 }
42 sb.WriteString(tb.Text)
43 }
44 }
45 if sb.Len() > 0 {
46 lastAssistant = sb.String()
47 }
48 case core.EvUsage:
49 if haveUsage {
50 usage = usage.Add(e.Usage)
51 } else {
52 usage = e.Usage
53 haveUsage = true
54 }
55 case core.EvTurnEnd:
56 if e.Err != nil {
57 runErr = e.Err
58 }
59 }
60 }
61
62 recovery, err := PromptWithContextRecovery(ctx, ag, prompt, images, sink, ContextRecoveryOptions{
63 PersistCompaction: persistCompaction,
64 SuppressInitialOverflowEvent: true,
65 })
66 if err != nil {
67 return usage, recovery, err
68 }
69 if runErr != nil {
70 return usage, recovery, runErr
71 }
72
73 finalText.WriteString(lastAssistant)
74 if finalText.Len() > 0 && !strings.HasSuffix(finalText.String(), "\n") {
75 finalText.WriteString("\n")
76 }
77 _, err = fmt.Fprint(out, finalText.String())
78 return usage, recovery, err
79}