1package sdk
2
3import (
4 "encoding/json"
5
6 "github.com/patriceckhart/zot/packages/core"
7 "github.com/patriceckhart/zot/packages/provider"
8)
9
10// Event is the unit emitted by Runtime.Prompt's channel. Type
11// identifies the event kind; remaining fields are populated based on
12// kind. The wire schema is the same as the JSON-RPC stream notifications
13// emitted by `zot rpc`, so consumers can share parsing code.
14type Event struct {
15 Type string `json:"type"`
16
17 // Common fields.
18 Step int `json:"step,omitempty"`
19 Time string `json:"time,omitempty"`
20
21 // text_delta
22 Delta string `json:"delta,omitempty"`
23
24 // tool_call
25 ID string `json:"id,omitempty"`
26 Name string `json:"name,omitempty"`
27 Args json.RawMessage `json:"args,omitempty"`
28
29 // tool_progress
30 Text string `json:"text,omitempty"`
31
32 // tool_result
33 IsError bool `json:"is_error,omitempty"`
34 Result []ContentBlock `json:"content,omitempty"`
35
36 // usage
37 Usage *Usage `json:"usage,omitempty"`
38 Cumulative *Usage `json:"cumulative,omitempty"`
39
40 // user_message / assistant_message
41 Message *Message `json:"message,omitempty"`
42
43 // turn_end
44 Stop string `json:"stop,omitempty"`
45
46 // error
47 Error string `json:"error,omitempty"`
48}
49
50// Message is one transcript entry — a user prompt, assistant reply,
51// or tool result.
52type Message struct {
53 Role string `json:"role"`
54 Content []ContentBlock `json:"content"`
55 Time string `json:"time,omitempty"`
56}
57
58// ContentBlock is one piece of message content. Discriminate on Type:
59// - "text" → Text
60// - "image" → MimeType + Data (or Bytes for size-only)
61// - "tool_call" → ID + Name + Args
62// - "tool_result" → CallID + IsError + Content (recursive)
63type ContentBlock struct {
64 Type string `json:"type"`
65
66 Text string `json:"text,omitempty"`
67 MimeType string `json:"mime_type,omitempty"`
68 Data []byte `json:"data,omitempty"`
69 Bytes int `json:"bytes,omitempty"`
70 ID string `json:"id,omitempty"`
71 Name string `json:"name,omitempty"`
72 Args json.RawMessage `json:"args,omitempty"`
73 CallID string `json:"call_id,omitempty"`
74 IsError bool `json:"is_error,omitempty"`
75 Content []ContentBlock `json:"content,omitempty"`
76}
77
78// Image is a single user-attached image for Prompt.
79type Image struct {
80 MimeType string
81 Data []byte
82}
83
84// Usage is per-turn or cumulative token / cost counts.
85type Usage struct {
86 Input int `json:"input"`
87 Output int `json:"output"`
88 CacheRead int `json:"cache_read"`
89 CacheWrite int `json:"cache_write"`
90 CostUSD float64 `json:"cost_usd"`
91}
92
93// State is a snapshot of the runtime's current state.
94type State struct {
95 Provider string `json:"provider"`
96 Model string `json:"model"`
97 CWD string `json:"cwd"`
98 Busy bool `json:"busy"`
99 MessageCount int `json:"message_count"`
100}
101
102// CompactResult describes the outcome of Compact.
103type CompactResult struct {
104 Summary string `json:"summary"`
105 Messages []Message `json:"messages"`
106}
107
108// ModelInfo describes one model known to the runtime.
109type ModelInfo struct {
110 ID string `json:"id"`
111 Provider string `json:"provider"`
112 ContextWindow int `json:"context_window"`
113 MaxOutput int `json:"max_output"`
114 Reasoning bool `json:"reasoning"`
115}
116
117// ---- internal converters ----
118
119func toEvent(ev core.AgentEvent) Event {
120 out := Event{Type: ev.Type()}
121 switch e := ev.(type) {
122 case core.EvTurnStart:
123 out.Step = e.Step
124 case core.EvUserMessage:
125 out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
126 case core.EvAssistantMessage:
127 out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
128 case core.EvTextDelta:
129 out.Delta = e.Delta
130 case core.EvToolCall:
131 out.ID = e.ID
132 out.Name = e.Name
133 out.Args = e.Args
134 case core.EvToolProgress:
135 out.ID = e.ID
136 out.Text = e.Text
137 case core.EvToolResult:
138 out.ID = e.ID
139 out.IsError = e.Result.IsError
140 out.Result = convertContent(e.Result.Content)
141 case core.EvUsage:
142 out.Usage = &Usage{
143 Input: e.Usage.InputTokens,
144 Output: e.Usage.OutputTokens,
145 CacheRead: e.Usage.CacheReadTokens,
146 CacheWrite: e.Usage.CacheWriteTokens,
147 CostUSD: e.Usage.CostUSD,
148 }
149 out.Cumulative = &Usage{
150 Input: e.Cumulative.InputTokens,
151 Output: e.Cumulative.OutputTokens,
152 CacheRead: e.Cumulative.CacheReadTokens,
153 CacheWrite: e.Cumulative.CacheWriteTokens,
154 CostUSD: e.Cumulative.CostUSD,
155 }
156 case core.EvTurnEnd:
157 out.Stop = string(e.Stop)
158 if e.Err != nil {
159 out.Error = e.Err.Error()
160 }
161 case core.EvError:
162 if e.Err != nil {
163 out.Error = e.Err.Error()
164 }
165 }
166 return out
167}
168
169func convertContent(blocks []provider.Content) []ContentBlock {
170 out := make([]ContentBlock, 0, len(blocks))
171 for _, b := range blocks {
172 switch v := b.(type) {
173 case provider.TextBlock:
174 out = append(out, ContentBlock{Type: "text", Text: v.Text})
175 case provider.ImageBlock:
176 out = append(out, ContentBlock{Type: "image", MimeType: v.MimeType, Bytes: len(v.Data)})
177 case provider.ToolCallBlock:
178 out = append(out, ContentBlock{Type: "tool_call", ID: v.ID, Name: v.Name, Args: v.Arguments})
179 case provider.ToolResultBlock:
180 out = append(out, ContentBlock{
181 Type: "tool_result",
182 CallID: v.CallID,
183 IsError: v.IsError,
184 Content: convertContent(v.Content),
185 })
186 }
187 }
188 return out
189}
190
191func rebuildContent(blocks []ContentBlock) []provider.Content {
192 out := make([]provider.Content, 0, len(blocks))
193 for _, b := range blocks {
194 switch b.Type {
195 case "text":
196 out = append(out, provider.TextBlock{Text: b.Text})
197 case "image":
198 out = append(out, provider.ImageBlock{MimeType: b.MimeType, Data: b.Data})
199 case "tool_call":
200 args := b.Args
201 if len(args) == 0 {
202 args = json.RawMessage("{}")
203 }
204 out = append(out, provider.ToolCallBlock{ID: b.ID, Name: b.Name, Arguments: args})
205 case "tool_result":
206 out = append(out, provider.ToolResultBlock{
207 CallID: b.CallID,
208 IsError: b.IsError,
209 Content: rebuildContent(b.Content),
210 })
211 }
212 }
213 return out
214}