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 ZotVersion string `json:"zot_version"`
166 Provider string `json:"provider"`
167 Model string `json:"model"`
168 CWD string `json:"cwd"`
169 ExtensionDir string `json:"extension_dir,omitempty"`
170 DataDir string `json:"data_dir,omitempty"`
171}
172
173type CommandInvokedFromHost struct {
174 Type string `json:"type"`
175 ID string `json:"id"`
176 Name string `json:"name"`
177 Args string `json:"args,omitempty"`
178}
179
180// ToolCancelFromHost asks the extension to stop an abandoned invocation.
181type ToolCancelFromHost struct {
182 Type string `json:"type"`
183 ID string `json:"id"`
184}
185
186type ToolCallFromHost struct {
187 Type string `json:"type"`
188 ID string `json:"id"`
189 Name string `json:"name"`
190 Args json.RawMessage `json:"args"`
191}
192
193// EventResult is a tool outcome without extension-private Details or activation data.
194type EventResult struct {
195 Content []ContentBlock `json:"content"`
196 IsError bool `json:"is_error"`
197 Truncated bool `json:"truncated,omitempty"`
198}
199
200type EventFromHost struct {
201 SessionID string `json:"session_id,omitempty"`
202 AgentRunID string `json:"agent_run_id,omitempty"`
203 Queued bool `json:"queued,omitempty"`
204 ImageCount int `json:"image_count,omitempty"`
205 CWD string `json:"cwd,omitempty"`
206 Sequence uint64 `json:"sequence,omitempty"`
207 Status string `json:"status,omitempty"`
208 Reason string `json:"reason,omitempty"`
209 Source string `json:"source,omitempty"`
210 Decision string `json:"decision,omitempty"`
211 Stage string `json:"stage,omitempty"`
212 Result *EventResult `json:"result,omitempty"`
213 Executed *bool `json:"executed,omitempty"`
214 CompactionID string `json:"compaction_id,omitempty"`
215 MessageCount *int `json:"message_count,omitempty"`
216 TokenEstimate *int `json:"token_estimate,omitempty"`
217 AgentID string `json:"agent_id,omitempty"`
218 Name string `json:"name,omitempty"`
219 Type string `json:"type"`
220 Event string `json:"event"`
221 Step int `json:"step,omitempty"`
222 Stop string `json:"stop,omitempty"`
223 Error string `json:"error,omitempty"`
224 ToolID string `json:"tool_id,omitempty"`
225 ToolName string `json:"tool_name,omitempty"`
226 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
227 ToolPreview string `json:"tool_preview,omitempty"`
228 ToolArgsRaw string `json:"tool_args_raw,omitempty"`
229 Text string `json:"text,omitempty"`
230}
231
232type EventInterceptFromHost struct {
233 SystemPrompt *string `json:"system_prompt,omitempty"`
234 SessionID string `json:"session_id,omitempty"`
235 AgentRunID string `json:"agent_run_id,omitempty"`
236 CWD string `json:"cwd,omitempty"`
237 Provider string `json:"provider,omitempty"`
238 Model string `json:"model,omitempty"`
239 Type string `json:"type"`
240 ID string `json:"id"`
241 Event string `json:"event"`
242 ToolID string `json:"tool_id,omitempty"`
243 ToolName string `json:"tool_name,omitempty"`
244 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
245 Step int `json:"step,omitempty"`
246 Text string `json:"text,omitempty"`
247}
248
249type PanelKeyFromHost struct {
250 Type string `json:"type"`
251 PanelID string `json:"panel_id"`
252 Key string `json:"key"`
253 Text string `json:"text,omitempty"`
254}
255
256type PanelResizeFromHost struct {
257 Type string `json:"type"`
258 PanelID string `json:"panel_id"`
259 Width int `json:"width"`
260 Height int `json:"height"`
261}
262
263type PanelCloseFromHost struct {
264 Type string `json:"type"`
265 PanelID string `json:"panel_id"`
266}
267
268type ShutdownFromHost struct {
269 Type string `json:"type"`
270}
271
272func Encode(v any) ([]byte, error) {
273 b, err := json.Marshal(v)
274 if err != nil {
275 return nil, err
276 }
277 return append(b, '\n'), nil
278}