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}
49
50type ReadyFromExt struct {
51 Type string `json:"type"`
52}
53
54type SubscribeFromExt struct {
55 Type string `json:"type"`
56 Events []string `json:"events,omitempty"`
57 Intercept []string `json:"intercept,omitempty"`
58}
59
60type EventInterceptResponseFromExt struct {
61 SystemPrompt json.RawMessage `json:"system_prompt,omitempty"`
62 Type string `json:"type"`
63 ID string `json:"id"`
64 Block bool `json:"block,omitempty"`
65 Reason string `json:"reason,omitempty"`
66 ModifiedArgs json.RawMessage `json:"modified_args,omitempty"`
67 ReplaceText string `json:"replace_text,omitempty"`
68}
69
70type ToolResultFromExt struct {
71 Type string `json:"type"`
72 ID string `json:"id"`
73 Content []ContentBlock `json:"content"`
74 IsError bool `json:"is_error,omitempty"`
75 ActivateTools []string `json:"activate_tools,omitempty"`
76}
77
78type ContentBlock struct {
79 Type string `json:"type"`
80 Text string `json:"text,omitempty"`
81 MimeType string `json:"mime_type,omitempty"`
82 Data string `json:"data,omitempty"`
83}
84
85type CommandResponseFromExt struct {
86 Type string `json:"type"`
87 ID string `json:"id"`
88 Action string `json:"action"`
89 Prompt string `json:"prompt,omitempty"`
90 Insert string `json:"insert,omitempty"`
91 Display string `json:"display,omitempty"`
92 OpenPanel *PanelSpec `json:"open_panel,omitempty"`
93 Error string `json:"error,omitempty"`
94}
95
96type PanelSpec struct {
97 ID string `json:"id"`
98 Title string `json:"title,omitempty"`
99 Lines []string `json:"lines,omitempty"`
100 Footer string `json:"footer,omitempty"`
101}
102
103// OpenPanelFromExt is a spontaneous one-way frame an extension can send at
104// any time to open an interactive panel. Unlike the open_panel action inside
105// CommandResponseFromExt, this form is uncoupled from any command invocation
106// and may be sent from a tool handler goroutine or any background context.
107type OpenPanelFromExt struct {
108 Type string `json:"type"` // "open_panel"
109 Panel PanelSpec `json:"panel"`
110}
111
112type PanelRenderFromExt struct {
113 Type string `json:"type"`
114 PanelID string `json:"panel_id"`
115 Title string `json:"title,omitempty"`
116 Lines []string `json:"lines,omitempty"`
117 Footer string `json:"footer,omitempty"`
118}
119
120type PanelCloseFromExt struct {
121 Type string `json:"type"`
122 PanelID string `json:"panel_id"`
123}
124
125type NotifyFromExt struct {
126 Type string `json:"type"`
127 Level string `json:"level"`
128 Message string `json:"message"`
129}
130
131// ClearNotesFromExt is a spontaneous frame an extension can send to
132// retract every note it previously pushed via notify/display, so
133// transient status lines (e.g. an approval prompt) do not stack up
134// forever in the bottom-sticky notes block.
135type ClearNotesFromExt struct {
136 Type string `json:"type"`
137}
138
139// SubmitSlashFromExt is a spontaneous frame an extension can send at
140// any time (typically from a panel_key handler) to invoke a slash
141// command in the host's TUI as if the user had typed it. Text must
142// start with '/'. Reserved for internal / opt-in extensions today;
143// the wire format is stable but not yet exposed in the public docs.
144type SubmitSlashFromExt struct {
145 Type string `json:"type"`
146 Text string `json:"text"`
147}
148
149// SubmitFromExt is a spontaneous frame an extension can send to queue
150// a model prompt in the interactive host. The manager routes it through
151// HostHooks.Submit, so the TUI handles busy state and turn startup.
152type SubmitFromExt struct {
153 Type string `json:"type"`
154 Text string `json:"text"`
155}
156
157type ShutdownAckFromExt struct {
158 Type string `json:"type"`
159}
160
161type HelloAckFromHost struct {
162 Type string `json:"type"`
163 ProtocolVersion int `json:"protocol_version"`
164 ZotVersion string `json:"zot_version"`
165 Provider string `json:"provider"`
166 Model string `json:"model"`
167 CWD string `json:"cwd"`
168 ExtensionDir string `json:"extension_dir,omitempty"`
169 DataDir string `json:"data_dir,omitempty"`
170}
171
172type CommandInvokedFromHost struct {
173 Type string `json:"type"`
174 ID string `json:"id"`
175 Name string `json:"name"`
176 Args string `json:"args,omitempty"`
177}
178
179type ToolCallFromHost struct {
180 Type string `json:"type"`
181 ID string `json:"id"`
182 Name string `json:"name"`
183 Args json.RawMessage `json:"args"`
184}
185
186type EventFromHost struct {
187 Type string `json:"type"`
188 Event string `json:"event"`
189 Step int `json:"step,omitempty"`
190 Stop string `json:"stop,omitempty"`
191 Error string `json:"error,omitempty"`
192 ToolID string `json:"tool_id,omitempty"`
193 ToolName string `json:"tool_name,omitempty"`
194 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
195 ToolPreview string `json:"tool_preview,omitempty"`
196 Text string `json:"text,omitempty"`
197}
198
199type EventInterceptFromHost struct {
200 SystemPrompt *string `json:"system_prompt,omitempty"`
201 SessionID string `json:"session_id,omitempty"`
202 AgentRunID string `json:"agent_run_id,omitempty"`
203 CWD string `json:"cwd,omitempty"`
204 Provider string `json:"provider,omitempty"`
205 Model string `json:"model,omitempty"`
206 Type string `json:"type"`
207 ID string `json:"id"`
208 Event string `json:"event"`
209 ToolID string `json:"tool_id,omitempty"`
210 ToolName string `json:"tool_name,omitempty"`
211 ToolArgs json.RawMessage `json:"tool_args,omitempty"`
212 Step int `json:"step,omitempty"`
213 Text string `json:"text,omitempty"`
214}
215
216type PanelKeyFromHost struct {
217 Type string `json:"type"`
218 PanelID string `json:"panel_id"`
219 Key string `json:"key"`
220 Text string `json:"text,omitempty"`
221}
222
223type PanelResizeFromHost struct {
224 Type string `json:"type"`
225 PanelID string `json:"panel_id"`
226 Width int `json:"width"`
227 Height int `json:"height"`
228}
229
230type PanelCloseFromHost struct {
231 Type string `json:"type"`
232 PanelID string `json:"panel_id"`
233}
234
235type ShutdownFromHost struct {
236 Type string `json:"type"`
237}
238
239func Encode(v any) ([]byte, error) {
240 b, err := json.Marshal(v)
241 if err != nil {
242 return nil, err
243 }
244 return append(b, '\n'), nil
245}