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 Reasoning string `json:"reasoning"`
98 CWD string `json:"cwd"`
99 Busy bool `json:"busy"`
100 MessageCount int `json:"message_count"`
101}
102
103// CompactResult describes the outcome of Compact.
104type CompactResult struct {
105 Summary string `json:"summary"`
106 Messages []Message `json:"messages"`
107}
108
109// ModelInfo describes one model known to the runtime.
110type ModelInfo struct {
111 ID string `json:"id"`
112 Provider string `json:"provider"`
113 ContextWindow int `json:"context_window"`
114 MaxOutput int `json:"max_output"`
115 Reasoning bool `json:"reasoning"`
116}
117
118// ---- internal converters ----
119
120func toEvent(ev core.AgentEvent) Event {
121 out := Event{Type: ev.Type()}
122 switch e := ev.(type) {
123 case core.EvTurnStart:
124 out.Step = e.Step
125 case core.EvUserMessage:
126 out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
127 case core.EvAssistantMessage:
128 out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
129 case core.EvTextDelta:
130 out.Delta = e.Delta
131 case core.EvToolCall:
132 out.ID = e.ID
133 out.Name = e.Name
134 out.Args = e.Args
135 case core.EvToolProgress:
136 out.ID = e.ID
137 out.Text = e.Text
138 case core.EvToolResult:
139 out.ID = e.ID
140 out.IsError = e.Result.IsError
141 out.Result = convertContent(e.Result.Content)
142 case core.EvUsage:
143 out.Usage = &Usage{
144 Input: e.Usage.InputTokens,
145 Output: e.Usage.OutputTokens,
146 CacheRead: e.Usage.CacheReadTokens,
147 CacheWrite: e.Usage.CacheWriteTokens,
148 CostUSD: e.Usage.CostUSD,
149 }
150 out.Cumulative = &Usage{
151 Input: e.Cumulative.InputTokens,
152 Output: e.Cumulative.OutputTokens,
153 CacheRead: e.Cumulative.CacheReadTokens,
154 CacheWrite: e.Cumulative.CacheWriteTokens,
155 CostUSD: e.Cumulative.CostUSD,
156 }
157 case core.EvTurnEnd:
158 out.Stop = string(e.Stop)
159 if e.Err != nil {
160 out.Error = e.Err.Error()
161 }
162 case core.EvError:
163 if e.Err != nil {
164 out.Error = e.Err.Error()
165 }
166 }
167 return out
168}
169
170func convertContent(blocks []provider.Content) []ContentBlock {
171 out := make([]ContentBlock, 0, len(blocks))
172 for _, b := range blocks {
173 switch v := b.(type) {
174 case provider.TextBlock:
175 out = append(out, ContentBlock{Type: "text", Text: v.Text})
176 case provider.ImageBlock:
177 out = append(out, ContentBlock{Type: "image", MimeType: v.MimeType, Bytes: len(v.Data)})
178 case provider.ToolCallBlock:
179 out = append(out, ContentBlock{Type: "tool_call", ID: v.ID, Name: v.Name, Args: v.Arguments})
180 case provider.ToolResultBlock:
181 out = append(out, ContentBlock{
182 Type: "tool_result",
183 CallID: v.CallID,
184 IsError: v.IsError,
185 Content: convertContent(v.Content),
186 })
187 }
188 }
189 return out
190}
191
192func rebuildContent(blocks []ContentBlock) []provider.Content {
193 out := make([]provider.Content, 0, len(blocks))
194 for _, b := range blocks {
195 switch b.Type {
196 case "text":
197 out = append(out, provider.TextBlock{Text: b.Text})
198 case "image":
199 out = append(out, provider.ImageBlock{MimeType: b.MimeType, Data: b.Data})
200 case "tool_call":
201 args := b.Args
202 if len(args) == 0 {
203 args = json.RawMessage("{}")
204 }
205 out = append(out, provider.ToolCallBlock{ID: b.ID, Name: b.Name, Arguments: args})
206 case "tool_result":
207 out = append(out, provider.ToolResultBlock{
208 CallID: b.CallID,
209 IsError: b.IsError,
210 Content: rebuildContent(b.Content),
211 })
212 }
213 }
214 return out
215}