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 Insert string `json:"insert,omitempty"`
92 Display string `json:"display,omitempty"`
93 OpenPanel *PanelSpec `json:"open_panel,omitempty"`
94 Error string `json:"error,omitempty"`
95}
96
97type PanelSpec struct {
98 ID string `json:"id"`
99 Title string `json:"title,omitempty"`
100 Lines []string `json:"lines,omitempty"`
101 Footer string `json:"footer,omitempty"`
102}
103
104// OpenPanelFromExt is a spontaneous one-way frame an extension can send at
105// any time to open an interactive panel. Unlike the open_panel action inside
106// CommandResponseFromExt, this form is uncoupled from any command invocation
107// and may be sent from a tool handler goroutine or any background context.
108type OpenPanelFromExt struct {
109 Type string `json:"type"` // "open_panel"
110 Panel PanelSpec `json:"panel"`
111}
112
113type PanelRenderFromExt struct {
114 Type string `json:"type"`
115 PanelID string `json:"panel_id"`
116 Title string `json:"title,omitempty"`
117 Lines []string `json:"lines,omitempty"`
118 Footer string `json:"footer,omitempty"`
119}
120
121type PanelCloseFromExt struct {
122 Type string `json:"type"`
123 PanelID string `json:"panel_id"`
124}
125
126type NotifyFromExt struct {
127 Type string `json:"type"`
128 Level string `json:"level"`
129 Message string `json:"message"`
130}
131
132// ClearNotesFromExt is a spontaneous frame an extension can send to
133// retract every note it previously pushed via notify/display, so
134// transient status lines (e.g. an approval prompt) do not stack up
135// forever in the bottom-sticky notes block.
136type ClearNotesFromExt struct {
137 Type string `json:"type"`
138}
139
140// SubmitSlashFromExt is a spontaneous frame an extension can send at
141// any time (typically from a panel_key handler) to invoke a slash
142// command in the host's TUI as if the user had typed it. Text must
143// start with '/'. Reserved for internal / opt-in extensions today;
144// the wire format is stable but not yet exposed in the public docs.
145type SubmitSlashFromExt struct {
146 Type string `json:"type"`
147 Text string `json:"text"`
148}
149
150// SubmitFromExt is a spontaneous frame an extension can send to queue
151// a model prompt in the interactive host. The manager routes it through
152// HostHooks.Submit, so the TUI handles busy state and turn startup.
153type SubmitFromExt struct {
154 Type string `json:"type"`
155 Text string `json:"text"`
156}
157
158type ShutdownAckFromExt struct {
159 Type string `json:"type"`
160}
161
162type HelloAckFromHost struct {
163 Type string `json:"type"`
164 ProtocolVersion int `json:"protocol_version"`
165 Capabilities []string `json:"capabilities,omitempty"`
166 ZotVersion string `json:"zot_version"`
167 Provider string `json:"provider"`
168 Model string `json:"model"`
169 CWD string `json:"cwd"`
170 ExtensionDir string `json:"extension_dir,omitempty"`
171 DataDir string `json:"data_dir,omitempty"`
172}
173
174type CommandInvokedFromHost struct {
175 Type string `json:"type"`
176 ID string `json:"id"`
177 Name string `json:"name"`
178 Args string `json:"args,omitempty"`
179}
180
181// ToolCancelFromHost asks the extension to stop an abandoned invocation.
182type ToolCancelFromHost struct {
183 Type string `json:"type"`
184 ID string `json:"id"`
185}
186
187type ToolCallFromHost struct {
188 Type string `json:"type"`
189 ID string `json:"id"`
190 Name string `json:"name"`
191 Args json.RawMessage `json:"args"`
192}
193
194// CallToolFromExt asks the host to execute an active tool. ParentID is set
195// by the SDK when the request originates inside a host-initiated tool call.
196type CallToolFromExt struct {
197 Type string `json:"type"`
198 ID string `json:"id"`
199 Name string `json:"name"`
200 Args json.RawMessage `json:"args"`
201 ParentID string `json:"parent_id,omitempty"`
202}
203
204// ToolResultFromHost is correlated to CallToolFromExt, not to a model call.
205type ToolResultFromHost struct {
206 Type string `json:"type"`
207 ID string `json:"id"`
208 Name string `json:"name"`
209 Content []ContentBlock `json:"content"`
210 IsError bool `json:"is_error,omitempty"`
211 Truncated bool `json:"truncated,omitempty"`
212}
213
214// EventResult is a tool outcome without extension-private Details or activation data.
215type EventResult struct {
216 Content []ContentBlock `json:"content"`
217 IsError bool `json:"is_error"`
218 Truncated bool `json:"truncated,omitempty"`
219}
220
221type EventFromHost struct {
222 SessionID string `json:"session_id,omitempty"`
223 AgentRunID string `json:"agent_run_id,omitempty"`
224 Queued bool `json:"queued,omitempty"`
225 ImageCount int `json:"image_count,omitempty"`
226 CWD string `json:"cwd,omitempty"`
227 Sequence uint64 `json:"sequence,omitempty"`
228 Status string `json:"status,omitempty"`
229 Reason string `json:"reason,omitempty"`
230 Source string `json:"source,omitempty"`
231 Decision string `json:"decision,omitempty"`
232 Stage string `json:"stage,omitempty"`
233 Result *EventResult `json:"result,omitempty"`
234 Executed *bool `json:"executed,omitempty"`
235 CompactionID string `json:"compaction_id,omitempty"`
236 MessageCount *int `json:"message_count,omitempty"`
237 TokenEstimate *int `json:"token_estimate,omitempty"`
238 AgentID string `json:"agent_id,omitempty"`
239 OriginExtension string `json:"origin_extension,omitempty"`
240 Name string `json:"name,omitempty"`
241 Type string `json:"type"`
242 Event string `json:"event"`
243 Step int `json:"step,omitempty"`
244 Stop string `json:"stop,omitempty"`
245 Error string `json:"error,omitempty"`
246 ToolID string `json:"tool_id,omitempty"`
247 ToolName string `json:"tool_name,omitempty"`
248 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
249 ToolPreview string `json:"tool_preview,omitempty"`
250 ToolArgsRaw string `json:"tool_args_raw,omitempty"`
251 Text string `json:"text,omitempty"`
252}
253
254type EventInterceptFromHost struct {
255 SystemPrompt *string `json:"system_prompt,omitempty"`
256 OriginExtension string `json:"origin_extension,omitempty"`
257 SessionID string `json:"session_id,omitempty"`
258 AgentRunID string `json:"agent_run_id,omitempty"`
259 CWD string `json:"cwd,omitempty"`
260 Provider string `json:"provider,omitempty"`
261 Model string `json:"model,omitempty"`
262 Type string `json:"type"`
263 ID string `json:"id"`
264 Event string `json:"event"`
265 ToolID string `json:"tool_id,omitempty"`
266 ToolName string `json:"tool_name,omitempty"`
267 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
268 Step int `json:"step,omitempty"`
269 Text string `json:"text,omitempty"`
270}
271
272type PanelKeyFromHost struct {
273 Type string `json:"type"`
274 PanelID string `json:"panel_id"`
275 Key string `json:"key"`
276 Text string `json:"text,omitempty"`
277}
278
279type PanelResizeFromHost struct {
280 Type string `json:"type"`
281 PanelID string `json:"panel_id"`
282 Width int `json:"width"`
283 Height int `json:"height"`
284}
285
286type PanelCloseFromHost struct {
287 Type string `json:"type"`
288 PanelID string `json:"panel_id"`
289}
290
291type ShutdownFromHost struct {
292 Type string `json:"type"`
293}
294
295func Encode(v any) ([]byte, error) {
296 b, err := json.Marshal(v)
297 if err != nil {
298 return nil, err
299 }
300 return append(b, '\n'), nil
301}