1package agent
2
3import (
4 "context"
5 "encoding/base64"
6 "unicode/utf8"
7
8 "github.com/patriceckhart/zot/packages/agent/extensions"
9 "github.com/patriceckhart/zot/packages/agent/extproto"
10 "github.com/patriceckhart/zot/packages/agent/swarm"
11 "github.com/patriceckhart/zot/packages/core"
12 "github.com/patriceckhart/zot/packages/provider"
13)
14
15func startExtensionSession(mgr *extensions.Manager, ag *core.Agent, cwd, reason string) {
16 id := ""
17 if ag != nil {
18 id = ag.SessionID
19 }
20 mgr.StartSession(id, cwd, reason)
21}
22
23func wireLifecycleEvents(ag *core.Agent, mgr *extensions.Manager) {
24 ag.OnEvent = func(ev core.AgentEvent) {
25 _, cwd := mgr.SessionContext()
26 startExtensionSession(mgr, ag, cwd, "session_switch")
27 fanoutAgentEvent(mgr, ev)
28 }
29}
30
31// Bound observation payloads independently from tool output and transcripts.
32// The budget includes base64 bytes; images are omitted rather than corrupted.
33const eventResultBudget = 256 * 1024
34
35func extensionEventResult(result core.ToolResult) *extproto.EventResult {
36 out := &extproto.EventResult{IsError: result.IsError, Content: []extproto.ContentBlock{}}
37 remaining := eventResultBudget
38 for _, block := range result.Content {
39 if len(out.Content) >= 128 {
40 out.Truncated = true
41 break
42 }
43 switch b := block.(type) {
44 case provider.TextBlock:
45 text := b.Text
46 if len(text) > remaining {
47 end := remaining
48 for end > 0 && !utf8.RuneStart(text[end]) {
49 end--
50 }
51 text = text[:end]
52 out.Truncated = true
53 }
54 remaining -= len(text)
55 out.Content = append(out.Content, extproto.ContentBlock{Type: "text", Text: text})
56 case provider.ImageBlock:
57 size := base64.StdEncoding.EncodedLen(len(b.Data)) + len(b.MimeType)
58 if size > remaining {
59 out.Truncated = true
60 continue
61 }
62 remaining -= size
63 out.Content = append(out.Content, extproto.ContentBlock{Type: "image", MimeType: b.MimeType, Data: base64.StdEncoding.EncodeToString(b.Data)})
64 default:
65 out.Truncated = true
66 }
67 }
68 return out
69}
70
71func emitPermissionDecision(ctx context.Context, mgr *extensions.Manager, call provider.ToolCallBlock, d core.ConfirmDecision) {
72 decision := "denied"
73 if d.Allow {
74 decision = "approved"
75 }
76 if ctx.Err() != nil {
77 decision = "cancelled"
78 }
79 mgr.EmitEvent(extproto.EventFromHost{Event: "permission_decision", ToolID: call.ID, ToolName: call.Name, Decision: decision, Source: d.Source, Reason: d.Reason, Stage: "pre_execution"})
80}
81
82func emitSwarmLifecycle(mgr *extensions.Manager, e swarm.LifecycleEvent) {
83 mgr.EmitEvent(extproto.EventFromHost{Event: e.Event, SessionID: e.SessionID, AgentID: e.AgentID, AgentRunID: e.AgentRunID, Name: e.Name, CWD: e.CWD, Status: e.Status, Error: e.Error})
84}