v0.0.62 1dafef8

extproto.go RawHistoryFolder

1// Package extproto defines the JSON-over-stdin/stdout wire format
2// spoken between zot and its extension subprocesses. Both the host
3// (internal/agent/extensions) and the SDK (pkg/zotext) 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
22// ProtocolVersion is the major version of this wire format. Bumped
23// for breaking changes; minor additions don't bump.
24const ProtocolVersion = 1
25
26// Frame is the lowest-common-denominator parse target so a reader can
27// peek at the type before unmarshalling the full payload.
28type Frame struct {
29	Type string `json:"type"`
30	ID   string `json:"id,omitempty"`
31}
32
33// ---- extension -> host ----
34
35// HelloFromExt is the first frame the extension sends after start.
36// Zot replies with HelloAckFromHost, then registration frames
37// (RegisterCommandFromExt, etc.) flow.
38type HelloFromExt struct {
39	Type         string   `json:"type"` // "hello"
40	Name         string   `json:"name"`
41	Version      string   `json:"version"`
42	Capabilities []string `json:"capabilities,omitempty"`
43}
44
45// RegisterCommandFromExt asks zot to bind /name to this extension.
46// Description appears in the slash autocomplete + /help.
47type RegisterCommandFromExt struct {
48	Type        string `json:"type"` // "register_command"
49	Name        string `json:"name"`
50	Description string `json:"description,omitempty"`
51}
52
53// RegisterToolFromExt asks zot to expose a tool to the LLM. The
54// schema is a JSON Schema object describing Args; zot doesn't validate
55// the model's arguments against it (the model providers do that), but
56// it must parse as valid JSON or registration is rejected.
57//
58// Tool names live in the same namespace as built-in tools (read,
59// write, edit, bash, skill). Conflicts are silently shadowed by the
60// built-in; check the extension's log for a warning.
61type RegisterToolFromExt struct {
62	Type        string          `json:"type"` // "register_tool"
63	Name        string          `json:"name"`
64	Description string          `json:"description,omitempty"`
65	Schema      json.RawMessage `json:"schema"`
66}
67
68// ReadyFromExt signals "all initial registrations sent". The host
69// waits for this (with a short timeout) before building the agent's
70// tool registry, so model calls don't race extension tool
71// registration.
72type ReadyFromExt struct {
73	Type string `json:"type"` // "ready"
74}
75
76// SubscribeFromExt declares which lifecycle events the extension
77// wants to observe (one-way `event` frames) and which it wants to
78// intercept (round-trip `event_intercept` frames). Send once after
79// hello, before ready.
80//
81// Recognised event names: "session_start", "turn_start",
82// "turn_end", "tool_call", "assistant_message".
83//
84// Only "tool_call" supports interception in this version; values
85// listed in Intercept that aren't "tool_call" are ignored.
86type SubscribeFromExt struct {
87	Type      string   `json:"type"` // "subscribe"
88	Events    []string `json:"events,omitempty"`
89	Intercept []string `json:"intercept,omitempty"`
90}
91
92// EventInterceptResponseFromExt is the extension's reply to an
93// EventInterceptFromHost. block=true refuses the underlying action;
94// reason is shown to the model (or the user) as the refusal text.
95// All fields default to "allow, pass through unmodified".
96//
97// Optional rewrite fields, their meaning depends on the event:
98//
99//   - ModifiedArgs: for event="tool_call", replaces the args the
100//     tool will see. Must be a JSON object literal or the rewrite is
101//     dropped and a warning logged.
102//   - ReplaceText: for event="assistant_message", replaces the user-
103//     visible text. The model's original text stays in the transcript
104//     (so the model can reference what it "said"); only the rendered
105//     output to the user is swapped.
106//
107// When block=true, rewrite fields are ignored.
108type EventInterceptResponseFromExt struct {
109	Type         string          `json:"type"` // "event_intercept_response"
110	ID           string          `json:"id"`
111	Block        bool            `json:"block,omitempty"`
112	Reason       string          `json:"reason,omitempty"`
113	ModifiedArgs json.RawMessage `json:"modified_args,omitempty"`
114	ReplaceText  string          `json:"replace_text,omitempty"`
115}
116
117// ToolResultFromExt is the extension's reply to a ToolCallFromHost.
118// Content[] follows the same shape as elsewhere in zot:
119//
120//	{"type":"text", "text":"..."}
121//	{"type":"image", "mime_type":"image/png", "data":"<base64>"}
122//
123// Set IsError true to mark the tool call as failed; the model sees
124// the content as the error explanation.
125type ToolResultFromExt struct {
126	Type    string         `json:"type"` // "tool_result"
127	ID      string         `json:"id"`
128	Content []ContentBlock `json:"content"`
129	IsError bool           `json:"is_error,omitempty"`
130}
131
132// ContentBlock is one entry in a tool result's content array.
133type ContentBlock struct {
134	Type     string `json:"type"` // "text" | "image"
135	Text     string `json:"text,omitempty"`
136	MimeType string `json:"mime_type,omitempty"`
137	Data     string `json:"data,omitempty"` // base64
138}
139
140// CommandResponseFromExt is the extension's answer to a
141// CommandInvokedFromHost. Action drives what zot does next:
142//
143//   - "prompt"  → submit Prompt as a fresh user message to the agent
144//   - "insert"  → insert Insert into the editor buffer at the cursor
145//   - "display" → append Display to the chat as a one-shot note
146//     (no model call, no transcript entry)
147//   - "noop"    → command handled internally, no UI change
148type CommandResponseFromExt struct {
149	Type    string `json:"type"` // "command_response"
150	ID      string `json:"id"`
151	Action  string `json:"action"`            // see above
152	Prompt  string `json:"prompt,omitempty"`  // for action=prompt
153	Insert  string `json:"insert,omitempty"`  // for action=insert
154	Display string `json:"display,omitempty"` // for action=display
155	Error   string `json:"error,omitempty"`   // command failed; render to user
156}
157
158// NotifyFromExt is a one-way status message the extension can push at
159// any time. Zot renders it in the chat as a styled note.
160type NotifyFromExt struct {
161	Type    string `json:"type"`  // "notify"
162	Level   string `json:"level"` // "info" | "warn" | "error" | "success"
163	Message string `json:"message"`
164}
165
166// ShutdownAckFromExt acknowledges the host's shutdown request. The
167// extension should exit shortly after sending this; zot waits a few
168// seconds before SIGTERM.
169type ShutdownAckFromExt struct {
170	Type string `json:"type"` // "shutdown_ack"
171}
172
173// ---- host -> extension ----
174
175// HelloAckFromHost is zot's reply to HelloFromExt. The extension may
176// inspect the host version + currently-active provider/model to decide
177// whether to register particular commands.
178type HelloAckFromHost struct {
179	Type            string `json:"type"` // "hello_ack"
180	ProtocolVersion int    `json:"protocol_version"`
181	ZotVersion      string `json:"zot_version"`
182	Provider        string `json:"provider"`
183	Model           string `json:"model"`
184	CWD             string `json:"cwd"`
185}
186
187// CommandInvokedFromHost is sent when the user runs a slash command
188// the extension previously registered. Args contains everything after
189// the command name (already trimmed).
190type CommandInvokedFromHost struct {
191	Type string `json:"type"` // "command_invoked"
192	ID   string `json:"id"`
193	Name string `json:"name"`
194	Args string `json:"args,omitempty"`
195}
196
197// ToolCallFromHost is sent when the LLM invokes a tool the extension
198// registered. Args is the raw JSON object the model produced; the
199// extension is responsible for validating/coercing it. Reply with
200// ToolResultFromExt within the host's tool timeout (default 60s).
201type ToolCallFromHost struct {
202	Type string          `json:"type"` // "tool_call"
203	ID   string          `json:"id"`
204	Name string          `json:"name"`
205	Args json.RawMessage `json:"args"`
206}
207
208// EventFromHost is a one-way lifecycle notification. The payload
209// fields populated depend on Event:
210//
211//	session_start    : (no extra fields)
212//	turn_start       : Step
213//	turn_end         : Stop, optional Error
214//	tool_call        : ToolID, ToolName, ToolArgs
215//	assistant_message: Text
216type EventFromHost struct {
217	Type  string `json:"type"` // "event"
218	Event string `json:"event"`
219
220	Step  int    `json:"step,omitempty"`
221	Stop  string `json:"stop,omitempty"`
222	Error string `json:"error,omitempty"`
223
224	ToolID   string          `json:"tool_id,omitempty"`
225	ToolName string          `json:"tool_name,omitempty"`
226	ToolArgs json.RawMessage `json:"tool_args,omitempty"`
227
228	Text string `json:"text,omitempty"`
229}
230
231// EventInterceptFromHost is sent when zot wants to give the
232// extension a chance to block, modify, or annotate a lifecycle
233// event before it happens. Reply with EventInterceptResponseFromExt
234// within the host's intercept timeout (default 5s); missing the
235// deadline is treated as "allow".
236//
237// Supported events and their effect on block=true:
238//
239//   - tool_call:         cancel the tool; model sees reason as error.
240//     Can also modify args via ModifiedArgs.
241//   - turn_start:        cancel the turn before the model call.
242//     Reason is shown as a chat status line.
243//   - assistant_message: suppress the message. Can also rewrite
244//     the user-visible text via ReplaceText.
245type EventInterceptFromHost struct {
246	Type  string `json:"type"` // "event_intercept"
247	ID    string `json:"id"`
248	Event string `json:"event"`
249
250	// tool_call payload
251	ToolID   string          `json:"tool_id,omitempty"`
252	ToolName string          `json:"tool_name,omitempty"`
253	ToolArgs json.RawMessage `json:"tool_args,omitempty"`
254
255	// turn_start payload
256	Step int `json:"step,omitempty"`
257
258	// assistant_message payload
259	Text string `json:"text,omitempty"`
260}
261
262// ShutdownFromHost asks the extension to clean up and exit. Zot
263// sends this when the user runs /reload-ext or zot itself is exiting
264// gracefully. Extensions that don't reply within a few seconds get
265// SIGTERM; SIGKILL after a few more.
266type ShutdownFromHost struct {
267	Type string `json:"type"` // "shutdown"
268}
269
270// ---- error frame (either direction) ----
271
272// Error is a generic failure response. Used by either side when a
273// frame can't be processed (malformed JSON, unknown type, etc.).
274type Error struct {
275	Type    string `json:"type"` // "error"
276	ID      string `json:"id,omitempty"`
277	Message string `json:"message"`
278}
279
280// ---- helpers ----
281
282// Encode marshals v and appends a trailing LF, ready to write to the
283// peer's pipe. Returns the marshalling error, if any.
284func Encode(v any) ([]byte, error) {
285	b, err := json.Marshal(v)
286	if err != nil {
287		return nil, err
288	}
289	return append(b, '\n'), nil
290}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close