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