返回 DeepSeek-Reasonix
EXTENSION_PROTOCOL.md
根目录 / docs / EXTENSION_PROTOCOL.md
1 # Reasonix Extension Protocol v1
2
3 The Extension Protocol is the stable wire contract between Reasonix (the
4 **host**) and code extensions running as out-of-process **sidecars**. It is
5 how an installed plugin with a `runtime` block intercepts runtime events,
6 owns replacement strategies, contributes streaming model providers, and
7 publishes structured UI — without ever linking into the host binary.
8
9 - Protocol ID: `reasonix.extension.v1`
10 - Machine-readable schema: `internal/extension/protocol/schema.generated.json`
11 - Method/event/limit/error index: `docs/EXTENSION_PROTOCOL.generated.md`
12 (generated, drift-checked in CI)
13 - Go SDK (implements everything below): `sdk/go`
14
15 This document is the prose companion to the generated index. Where they
16 disagree, the generated schema wins.
17
18 ## Transport
19
20 - Strict JSON-RPC 2.0 over **NDJSON**: one complete JSON object per line on
21 stdin/stdout. stderr belongs to the extension for diagnostics; the host
22 captures a bounded, credential-redacted tail for errors.
23 - Frames are capped at **8 MiB** in both directions; oversized frames are a
24 connection-fatal `frame_too_large` error.
25 - Request IDs are integers. `params` must be an object. Unknown members are
26 tolerated at the frame level; DTO decoding is strict (unknown fields are
27 rejected) so typos surface immediately.
28
29 ## Lifecycle
30
31 1. The host spawns the sidecar (exec form, no shell) and sends
32 `extension/initialize` first. The params carry the manifest expectation:
33 the intercepts, replaces, providers, and UI actions the host will accept.
34 For one runtime generation, the host initializes at most four sidecars in
35 parallel under one shared 30-second startup budget.
36 2. The sidecar answers with its declaration. The host validates it: exact
37 protocol major version, and every subscription, replacement slot,
38 provider, and UI action must be a **subset of the plugin manifest**.
39 Anything beyond the manifest fails the handshake with
40 `capability_not_declared`.
41 3. The host sends `extension/initialized`. Any extension-to-host traffic
42 before this point poisons the connection.
43 4. Shutdown is bounded: `extension/shutdown` with a timeout, then stdin
44 closes, then the process tree is killed if the sidecar does not exit.
45 5. Crashes: a sidecar that dies cancels all of its pending RPCs. If it owned
46 the currently selected provider or a replacement slot, the current
47 operation fails explicitly — the host never silently falls back to another
48 model or strategy. A crashed sidecar is only restarted by an idle-time
49 runtime reload.
50
51 ## Content references
52
53 Payload fields marked externalizable that exceed **64 KiB** are offloaded
54 into the host content store: the frame carries an `ExternalizedField`
55 descriptor (JSON pointer, content ref, byte count, SHA-256) and a `null`
56 placeholder. The peer pages the bytes back with `host/content/read` in
57 **256 KiB** chunks, verifying byte count and hash. A single content object is
58 capped at **8 MiB**. Unknown or expired refs fail with `content_ref_expired`.
59
60 ## Interception
61
62 Seventeen frozen hook points (see the generated index). `extension/intercept`
63 is blocking; `extension/event` is fire-and-forget observation of the same
64 points. Event delivery uses a bounded non-blocking writer queue: saturation
65 drops the observation with a warning instead of stalling the Agent.
66
67 - Ordinary interceptors run **sequentially** in a deterministic order:
68 priority ascending (manifest `priority`, -1000..1000, default 0), then
69 plugin ID, then registration order.
70 - Decisions per call: `continue` (pass the payload along), `block` (abort
71 the operation with a user-visible reason), `replace` (substitute the
72 payload — the host re-validates it against the point's DTO and schema
73 before use), and `allow`/`deny` (only legal at `permission.decision`).
74 A full-trust `allow` overrides a host deny and is audited.
75 - Replacement **strategy slots** (`system_prompt`, `context`,
76 `provider_request`, `provider_response`, `compaction`, `session_policy`,
77 `permission`, `frontend_events`, `tool:<name>`, `provider:<ref>`) have
78 exactly one owner across all installed plugins. The chain runs first; the
79 slot owner gets the final say. A strategy owner's timeout or error always
80 fails the operation.
81 - Timeouts: input/tool/permission points default to 5s; the
82 session/context/compaction/system-prompt family to 30s; a manifest may tune
83 per-runtime up to a 60s ceiling. Optional observation-only extensions that
84 time out are warned about once and skipped; required extensions and slot
85 owners fail the operation.
86
87 ## Streaming providers
88
89 An extension with the `providers` capability answers
90 `extension/provider/catalog` with descriptors equivalent to host providers
91 (models, context windows, pricing, vision, reasoning, effort) — never
92 credentials. Models appear as `plugin/<plugin>/<provider>/<model>`.
93
94 Streams follow `extension/provider/stream/open` → `stream/chunk` →
95 `stream/end`:
96
97 - Chunks carry a 1-based contiguous sequence number; `stream/end.lastSeq`
98 freezes the terminal boundary. The host buffers out-of-order chunks,
99 drops duplicates, and fails the stream as interrupted naming the missing
100 sequence when a gap persists.
101 - Chunk types: `text`, `reasoning` (with `signature`), `tool_call_start`,
102 `tool_call_args_delta`, `tool_call`, `usage` (including cache tokens),
103 `done`, `error`. Provider errors must be redacted by the producer and are
104 defensively redacted again by the host.
105 - Cancelling the stream context sends `stream/cancel`; the sidecar must stop
106 producing chunks.
107 - The extension reads its own environment and credentials; the host never
108 sends another provider's API keys or headers. A crashed provider never
109 triggers fallback to a different model.
110
111 ## Structured UI
112
113 Extensions with the `ui` capability publish `status`, `card`, `form`, and
114 `notification` payloads (`host/ui/publish`) and ask questions
115 (`host/ui/request`: confirm, input, select, multiselect). Surfaces are
116 **structured only**: no HTML, CSS, JavaScript, remote scripts, arbitrary
117 frontend components, or uncontrolled URLs; Markdown renders through each
118 frontend's existing safe renderer. Every surface update carries the plugin
119 ID, surface ID, session ID, and runtime generation; stale-generation
120 updates are dropped so late results after a tab switch or reload can never
121 overwrite current state.
122
123 Actions declared at initialize are namespaced `/<plugin>:<action>` and are
124 invoked via `extension/ui/action`; form submissions arrive via
125 `extension/ui/submit`.
126
127 ## Errors
128
129 Domain errors travel as JSON-RPC error code `-32000` with structured data
130 (reason, retryable, action); `protocol_error`, `unknown_method`,
131 `invalid_params`, and `internal` use the standard JSON-RPC codes. The frozen
132 reason table lives in the generated index.
133
134 ## Stability contract
135
136 Within major version 1, the only permitted evolutions are: new optional
137 fields, new enum values, and new methods. Existing required fields,
138 directions, limits, error reasons, and semantics never change. The canonical
139 schema and its SHA-256 hash are produced by `cmd/extension-protocol-gen`;
140 CI's `go test ./...` enforces this via the deterministic-generation test
141 (`TestGeneratedArtifactsAreDeterministicAndCommitted`), so any drift —
142 including an accidental semantic change — fails the build.
143
144 ## Security model
145
146 A code extension is **full trust**: it runs outside the Reasonix sandbox
147 with the unfiltered inherited environment, can read the full session and
148 environment, can bypass permissions, and can operate the machine directly.
149 Installing, updating, replacing, or `--link`ing a plugin with a `runtime`
150 block is the authorization — there is no second confirmation. Only plugins
151 installed through the plugin flow (recorded in `plugin-packages.json`) can
152 start a sidecar; project configuration can never declare one. Before any
153 sidecar diagnostics, structured UI, interceptor reasons, or provider errors
154 reach the UI, logs, or error surfaces, the host runs its credential redaction
155 pass. Ordinary provider/model content is preserved as product data. The
156 install preview, plugin details, and capability diagnostics always display the
157 FULL TRUST block for runtime plugins.
158
158 lines MARKDOWN