// Package extproto defines the JSON-over-stdin/stdout wire format // spoken between zot and its extension subprocesses. Both the host // (packages/agent/extensions) and the SDK (packages/agent/ext) marshal/ // unmarshal the same types, so changes here ripple through both. // // All frames are one JSON object terminated by a single LF. Object // boundaries follow newline boundaries; no multi-line JSON. // // Direction conventions in this file: // - Type names ending in "FromExt" are sent by the extension to zot. // - Type names ending in "FromHost" are sent by zot to the extension. // - Names without a suffix are direction-neutral payloads or shared // value types. // // Every frame has a top-level Type discriminator. Optional ID is // present on commands and on responses to commands so the sender can // correlate; events and notifications never carry an ID. package extproto import "encoding/json" const ProtocolVersion = 1 type Frame struct { Type string `json:"type"` ID string `json:"id,omitempty"` } type HelloFromExt struct { Type string `json:"type"` Name string `json:"name"` Version string `json:"version"` Capabilities []string `json:"capabilities,omitempty"` } type RegisterCommandFromExt struct { Type string `json:"type"` Name string `json:"name"` Description string `json:"description,omitempty"` } type RegisterToolFromExt struct { Type string `json:"type"` Name string `json:"name"` Description string `json:"description,omitempty"` Schema json.RawMessage `json:"schema"` Deferred bool `json:"deferred,omitempty"` Interactive bool `json:"interactive,omitempty"` } type ReadyFromExt struct { Type string `json:"type"` } type SubscribeFromExt struct { Type string `json:"type"` Events []string `json:"events,omitempty"` Intercept []string `json:"intercept,omitempty"` } type EventInterceptResponseFromExt struct { SystemPrompt json.RawMessage `json:"system_prompt,omitempty"` Type string `json:"type"` ID string `json:"id"` Block bool `json:"block,omitempty"` Reason string `json:"reason,omitempty"` ModifiedArgs json.RawMessage `json:"modified_args,omitempty"` ReplaceText string `json:"replace_text,omitempty"` } type ToolResultFromExt struct { Type string `json:"type"` ID string `json:"id"` Content []ContentBlock `json:"content"` IsError bool `json:"is_error,omitempty"` ActivateTools []string `json:"activate_tools,omitempty"` } type ContentBlock struct { Type string `json:"type"` Text string `json:"text,omitempty"` MimeType string `json:"mime_type,omitempty"` Data string `json:"data,omitempty"` } type CommandResponseFromExt struct { Type string `json:"type"` ID string `json:"id"` Action string `json:"action"` Prompt string `json:"prompt,omitempty"` Insert string `json:"insert,omitempty"` Display string `json:"display,omitempty"` OpenPanel *PanelSpec `json:"open_panel,omitempty"` Error string `json:"error,omitempty"` } type PanelSpec struct { ID string `json:"id"` Title string `json:"title,omitempty"` Lines []string `json:"lines,omitempty"` Footer string `json:"footer,omitempty"` } // OpenPanelFromExt is a spontaneous one-way frame an extension can send at // any time to open an interactive panel. Unlike the open_panel action inside // CommandResponseFromExt, this form is uncoupled from any command invocation // and may be sent from a tool handler goroutine or any background context. type OpenPanelFromExt struct { Type string `json:"type"` // "open_panel" Panel PanelSpec `json:"panel"` } type PanelRenderFromExt struct { Type string `json:"type"` PanelID string `json:"panel_id"` Title string `json:"title,omitempty"` Lines []string `json:"lines,omitempty"` Footer string `json:"footer,omitempty"` } type PanelCloseFromExt struct { Type string `json:"type"` PanelID string `json:"panel_id"` } type NotifyFromExt struct { Type string `json:"type"` Level string `json:"level"` Message string `json:"message"` } // ClearNotesFromExt is a spontaneous frame an extension can send to // retract every note it previously pushed via notify/display, so // transient status lines (e.g. an approval prompt) do not stack up // forever in the bottom-sticky notes block. type ClearNotesFromExt struct { Type string `json:"type"` } // SubmitSlashFromExt is a spontaneous frame an extension can send at // any time (typically from a panel_key handler) to invoke a slash // command in the host's TUI as if the user had typed it. Text must // start with '/'. Reserved for internal / opt-in extensions today; // the wire format is stable but not yet exposed in the public docs. type SubmitSlashFromExt struct { Type string `json:"type"` Text string `json:"text"` } // SubmitFromExt is a spontaneous frame an extension can send to queue // a model prompt in the interactive host. The manager routes it through // HostHooks.Submit, so the TUI handles busy state and turn startup. type SubmitFromExt struct { Type string `json:"type"` Text string `json:"text"` } type ShutdownAckFromExt struct { Type string `json:"type"` } type HelloAckFromHost struct { Type string `json:"type"` ProtocolVersion int `json:"protocol_version"` Capabilities []string `json:"capabilities,omitempty"` ZotVersion string `json:"zot_version"` Provider string `json:"provider"` Model string `json:"model"` CWD string `json:"cwd"` ExtensionDir string `json:"extension_dir,omitempty"` DataDir string `json:"data_dir,omitempty"` } type CommandInvokedFromHost struct { Type string `json:"type"` ID string `json:"id"` Name string `json:"name"` Args string `json:"args,omitempty"` } // ToolCancelFromHost asks the extension to stop an abandoned invocation. type ToolCancelFromHost struct { Type string `json:"type"` ID string `json:"id"` } type ToolCallFromHost struct { Type string `json:"type"` ID string `json:"id"` Name string `json:"name"` Args json.RawMessage `json:"args"` } // CallToolFromExt asks the host to execute an active tool. ParentID is set // by the SDK when the request originates inside a host-initiated tool call. type CallToolFromExt struct { Type string `json:"type"` ID string `json:"id"` Name string `json:"name"` Args json.RawMessage `json:"args"` ParentID string `json:"parent_id,omitempty"` } // ToolResultFromHost is correlated to CallToolFromExt, not to a model call. type ToolResultFromHost struct { Type string `json:"type"` ID string `json:"id"` Name string `json:"name"` Content []ContentBlock `json:"content"` IsError bool `json:"is_error,omitempty"` Truncated bool `json:"truncated,omitempty"` } // EventResult is a tool outcome without extension-private Details or activation data. type EventResult struct { Content []ContentBlock `json:"content"` IsError bool `json:"is_error"` Truncated bool `json:"truncated,omitempty"` } type EventFromHost struct { SessionID string `json:"session_id,omitempty"` AgentRunID string `json:"agent_run_id,omitempty"` Queued bool `json:"queued,omitempty"` ImageCount int `json:"image_count,omitempty"` CWD string `json:"cwd,omitempty"` Sequence uint64 `json:"sequence,omitempty"` Status string `json:"status,omitempty"` Reason string `json:"reason,omitempty"` Source string `json:"source,omitempty"` Decision string `json:"decision,omitempty"` Stage string `json:"stage,omitempty"` Result *EventResult `json:"result,omitempty"` Executed *bool `json:"executed,omitempty"` CompactionID string `json:"compaction_id,omitempty"` MessageCount *int `json:"message_count,omitempty"` TokenEstimate *int `json:"token_estimate,omitempty"` AgentID string `json:"agent_id,omitempty"` OriginExtension string `json:"origin_extension,omitempty"` Name string `json:"name,omitempty"` Type string `json:"type"` Event string `json:"event"` Step int `json:"step,omitempty"` Stop string `json:"stop,omitempty"` Error string `json:"error,omitempty"` ToolID string `json:"tool_id,omitempty"` ToolName string `json:"tool_name,omitempty"` ToolArgs json.RawMessage `json:"tool_args,omitempty"` ToolPreview string `json:"tool_preview,omitempty"` ToolArgsRaw string `json:"tool_args_raw,omitempty"` Text string `json:"text,omitempty"` } type EventInterceptFromHost struct { SystemPrompt *string `json:"system_prompt,omitempty"` OriginExtension string `json:"origin_extension,omitempty"` SessionID string `json:"session_id,omitempty"` AgentRunID string `json:"agent_run_id,omitempty"` CWD string `json:"cwd,omitempty"` Provider string `json:"provider,omitempty"` Model string `json:"model,omitempty"` Type string `json:"type"` ID string `json:"id"` Event string `json:"event"` ToolID string `json:"tool_id,omitempty"` ToolName string `json:"tool_name,omitempty"` ToolArgs json.RawMessage `json:"tool_args,omitempty"` Step int `json:"step,omitempty"` Text string `json:"text,omitempty"` } type PanelKeyFromHost struct { Type string `json:"type"` PanelID string `json:"panel_id"` Key string `json:"key"` Text string `json:"text,omitempty"` } type PanelResizeFromHost struct { Type string `json:"type"` PanelID string `json:"panel_id"` Width int `json:"width"` Height int `json:"height"` } type PanelCloseFromHost struct { Type string `json:"type"` PanelID string `json:"panel_id"` } type ShutdownFromHost struct { Type string `json:"type"` } func Encode(v any) ([]byte, error) { b, err := json.Marshal(v) if err != nil { return nil, err } return append(b, '\n'), nil }