1// Package extproto defines the JSON-over-stdin/stdout wire format
2// spoken between zot and its extension subprocesses. Both the host
3// (packages/agent/extensions) and the SDK (packages/agent/ext) marshal/
4// unmarshal the same types, so changes here ripple through both.
5//
6// All frames are one JSON object terminated by a single LF. Object
7// boundaries follow newline boundaries; no multi-line JSON.
8//
9// Direction conventions in this file:
10// - Type names ending in "FromExt" are sent by the extension to zot.
11// - Type names ending in "FromHost" are sent by zot to the extension.
12// - Names without a suffix are direction-neutral payloads or shared
13// value types.
14//
15// Every frame has a top-level Type discriminator. Optional ID is
16// present on commands and on responses to commands so the sender can
17// correlate; events and notifications never carry an ID.
18package extproto
19
20import "encoding/json"
21
22const ProtocolVersion = 1
23
24type Frame struct {
25 Type string `json:"type"`
26 ID string `json:"id,omitempty"`
27}
28
29type HelloFromExt struct {
30 Type string `json:"type"`
31 Name string `json:"name"`
32 Version string `json:"version"`
33 Capabilities []string `json:"capabilities,omitempty"`
34}
35
36type RegisterCommandFromExt struct {
37 Type string `json:"type"`
38 Name string `json:"name"`
39 Description string `json:"description,omitempty"`
40}
41
42type RegisterToolFromExt struct {
43 Type string `json:"type"`
44 Name string `json:"name"`
45 Description string `json:"description,omitempty"`
46 Schema json.RawMessage `json:"schema"`
47 Deferred bool `json:"deferred,omitempty"`
48 Interactive bool `json:"interactive,omitempty"`
49}
50
51type ReadyFromExt struct {
52 Type string `json:"type"`
53}
54
55type SubscribeFromExt struct {
56 Type string `json:"type"`
57 Events []string `json:"events,omitempty"`
58 Intercept []string `json:"intercept,omitempty"`
59}
60
61type EventInterceptResponseFromExt struct {
62 SystemPrompt json.RawMessage `json:"system_prompt,omitempty"`
63 Type string `json:"type"`
64 ID string `json:"id"`
65 Block bool `json:"block,omitempty"`
66 Reason string `json:"reason,omitempty"`
67 ModifiedArgs json.RawMessage `json:"modified_args,omitempty"`
68 ReplaceText string `json:"replace_text,omitempty"`
69}
70
71type ToolResultFromExt struct {
72 Type string `json:"type"`
73 ID string `json:"id"`
74 Content []ContentBlock `json:"content"`
75 IsError bool `json:"is_error,omitempty"`
76 ActivateTools []string `json:"activate_tools,omitempty"`
77}
78
79type ContentBlock struct {
80 Type string `json:"type"`
81 Text string `json:"text,omitempty"`
82 MimeType string `json:"mime_type,omitempty"`
83 Data string `json:"data,omitempty"`
84}
85
86type CommandResponseFromExt struct {
87 Type string `json:"type"`
88 ID string `json:"id"`
89 Action string `json:"action"`
90 Prompt string `json:"prompt,omitempty"`
91 ToolName string `json:"tool_name,omitempty"`
92 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
93 Insert string `json:"insert,omitempty"`
94 Display string `json:"display,omitempty"`
95 OpenPanel *PanelSpec `json:"open_panel,omitempty"`
96 Error string `json:"error,omitempty"`
97}
98
99type PanelSpec struct {
100 ID string `json:"id"`
101 Title string `json:"title,omitempty"`
102 Lines []string `json:"lines,omitempty"`
103 Footer string `json:"footer,omitempty"`
104}
105
106// OpenPanelFromExt is a spontaneous one-way frame an extension can send at
107// any time to open an interactive panel. Unlike the open_panel action inside
108// CommandResponseFromExt, this form is uncoupled from any command invocation
109// and may be sent from a tool handler goroutine or any background context.
110type OpenPanelFromExt struct {
111 Type string `json:"type"` // "open_panel"
112 Panel PanelSpec `json:"panel"`
113}
114
115type PanelRenderFromExt struct {
116 Type string `json:"type"`
117 PanelID string `json:"panel_id"`
118 Title string `json:"title,omitempty"`
119 Lines []string `json:"lines,omitempty"`
120 Footer string `json:"footer,omitempty"`
121}
122
123type PanelCloseFromExt struct {
124 Type string `json:"type"`
125 PanelID string `json:"panel_id"`
126}
127
128type NotifyFromExt struct {
129 Type string `json:"type"`
130 Level string `json:"level"`
131 Message string `json:"message"`
132}
133
134// ClearNotesFromExt is a spontaneous frame an extension can send to
135// retract every note it previously pushed via notify/display, so
136// transient status lines (e.g. an approval prompt) do not stack up
137// forever in the bottom-sticky notes block.
138type ClearNotesFromExt struct {
139 Type string `json:"type"`
140}
141
142// SubmitSlashFromExt is a spontaneous frame an extension can send at
143// any time (typically from a panel_key handler) to invoke a slash
144// command in the host's TUI as if the user had typed it. Text must
145// start with '/'. Reserved for internal / opt-in extensions today;
146// the wire format is stable but not yet exposed in the public docs.
147type SubmitSlashFromExt struct {
148 Type string `json:"type"`
149 Text string `json:"text"`
150}
151
152// SubmitFromExt is a spontaneous frame an extension can send to queue
153// a model prompt in the interactive host. The manager routes it through
154// HostHooks.Submit, so the TUI handles busy state and turn startup.
155type SubmitFromExt struct {
156 Type string `json:"type"`
157 Text string `json:"text"`
158}
159
160type ShutdownAckFromExt struct {
161 Type string `json:"type"`
162}
163
164type HelloAckFromHost struct {
165 Type string `json:"type"`
166 ProtocolVersion int `json:"protocol_version"`
167 Capabilities []string `json:"capabilities,omitempty"`
168 ZotVersion string `json:"zot_version"`
169 Provider string `json:"provider"`
170 Model string `json:"model"`
171 CWD string `json:"cwd"`
172 ExtensionDir string `json:"extension_dir,omitempty"`
173 DataDir string `json:"data_dir,omitempty"`
174}
175
176type CommandInvokedFromHost struct {
177 Type string `json:"type"`
178 ID string `json:"id"`
179 Name string `json:"name"`
180 Args string `json:"args,omitempty"`
181}
182
183// ToolCancelFromHost asks the extension to stop an abandoned invocation.
184type ToolCancelFromHost struct {
185 Type string `json:"type"`
186 ID string `json:"id"`
187}
188
189type ToolCallFromHost struct {
190 Type string `json:"type"`
191 ID string `json:"id"`
192 Name string `json:"name"`
193 Args json.RawMessage `json:"args"`
194}
195
196// CallToolFromExt asks the host to execute an active tool. ParentID is set
197// by the SDK when the request originates inside a host-initiated tool call.
198type CallToolFromExt struct {
199 Type string `json:"type"`
200 ID string `json:"id"`
201 Name string `json:"name"`
202 Args json.RawMessage `json:"args"`
203 ParentID string `json:"parent_id,omitempty"`
204}
205
206// ToolResultFromHost is correlated to CallToolFromExt, not to a model call.
207type ToolResultFromHost struct {
208 Type string `json:"type"`
209 ID string `json:"id"`
210 Name string `json:"name"`
211 Content []ContentBlock `json:"content"`
212 IsError bool `json:"is_error,omitempty"`
213 Truncated bool `json:"truncated,omitempty"`
214}
215
216// EventResult is a tool outcome without extension-private Details or activation data.
217type EventResult struct {
218 Content []ContentBlock `json:"content"`
219 IsError bool `json:"is_error"`
220 Truncated bool `json:"truncated,omitempty"`
221}
222
223type EventFromHost struct {
224 SessionID string `json:"session_id,omitempty"`
225 AgentRunID string `json:"agent_run_id,omitempty"`
226 Queued bool `json:"queued,omitempty"`
227 ImageCount int `json:"image_count,omitempty"`
228 CWD string `json:"cwd,omitempty"`
229 Sequence uint64 `json:"sequence,omitempty"`
230 Status string `json:"status,omitempty"`
231 Reason string `json:"reason,omitempty"`
232 Source string `json:"source,omitempty"`
233 Decision string `json:"decision,omitempty"`
234 Stage string `json:"stage,omitempty"`
235 Result *EventResult `json:"result,omitempty"`
236 Executed *bool `json:"executed,omitempty"`
237 CompactionID string `json:"compaction_id,omitempty"`
238 MessageCount *int `json:"message_count,omitempty"`
239 TokenEstimate *int `json:"token_estimate,omitempty"`
240 AgentID string `json:"agent_id,omitempty"`
241 OriginExtension string `json:"origin_extension,omitempty"`
242 Name string `json:"name,omitempty"`
243 Type string `json:"type"`
244 Event string `json:"event"`
245 Step int `json:"step,omitempty"`
246 Stop string `json:"stop,omitempty"`
247 Error string `json:"error,omitempty"`
248 ToolID string `json:"tool_id,omitempty"`
249 ToolName string `json:"tool_name,omitempty"`
250 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
251 ToolPreview string `json:"tool_preview,omitempty"`
252 ToolArgsRaw string `json:"tool_args_raw,omitempty"`
253 Text string `json:"text,omitempty"`
254}
255
256type EventInterceptFromHost struct {
257 SystemPrompt *string `json:"system_prompt,omitempty"`
258 OriginExtension string `json:"origin_extension,omitempty"`
259 SessionID string `json:"session_id,omitempty"`
260 AgentRunID string `json:"agent_run_id,omitempty"`
261 CWD string `json:"cwd,omitempty"`
262 Provider string `json:"provider,omitempty"`
263 Model string `json:"model,omitempty"`
264 Type string `json:"type"`
265 ID string `json:"id"`
266 Event string `json:"event"`
267 ToolID string `json:"tool_id,omitempty"`
268 ToolName string `json:"tool_name,omitempty"`
269 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
270 Step int `json:"step,omitempty"`
271 Text string `json:"text,omitempty"`
272}
273
274type PanelKeyFromHost struct {
275 Type string `json:"type"`
276 PanelID string `json:"panel_id"`
277 Key string `json:"key"`
278 Text string `json:"text,omitempty"`
279}
280
281type PanelResizeFromHost struct {
282 Type string `json:"type"`
283 PanelID string `json:"panel_id"`
284 Width int `json:"width"`
285 Height int `json:"height"`
286}
287
288type PanelCloseFromHost struct {
289 Type string `json:"type"`
290 PanelID string `json:"panel_id"`
291}
292
293type ShutdownFromHost struct {
294 Type string `json:"type"`
295}
296
297func Encode(v any) ([]byte, error) {
298 b, err := json.Marshal(v)
299 if err != nil {
300 return nil, err
301 }
302 return append(b, '\n'), nil
303}