Extension Protocol RPC Wire Format and Bootstrapping in DeepSeek-Reasonix

The Extension Protocol is a strict JSON-RPC 2.0 dialect using NDJSON framing that enables reliable communication between the DeepSeek-Reasonix desktop host and extension processes, initialized through a mandatory bootstrap handshake that exchanges protocol versions and UI capabilities.

The DeepSeek-Reasonix project implements a robust extension system defined by a precise Extension Protocol RPC wire format that governs all cross-process communication. This protocol, implemented primarily in sdk/go/wire.go, utilizes newline-delimited JSON (NDJSON) for message framing and enforces strict validation rules to ensure type safety and protocol compliance. Before any extension-specific operations occur, the system executes a structured bootstrapping process that establishes the communication channel and synchronizes initial state between the host and the extension process.

Extension Protocol RPC Wire Format Specification

The wire format revolves around strict JSON-RPC 2.0 semantics with additional constraints to prevent protocol drift and ensure predictable behavior across language boundaries.

NDJSON Framing and Message Structure

All messages transit as NDJSON frames—individual JSON objects terminated by newline characters. The reader implementation in sdk/go/wire.go uses readLine to extract frames, enforcing a maximum size limit defined by FrameBytes. If a frame exceeds this budget, the connection terminates immediately with a FrameTooLargeError.

Each message conforms to the outbound or inbound struct definitions containing standard JSON-RPC fields:

  • jsonrpc: Must always be the string "2.0" (validated by validateStrictFrame)
  • id: Restricted to integers only or null; the validRPCID helper rejects string IDs or other types
  • method: String identifier for the RPC endpoint
  • params: Must be a JSON object; empty objects are permitted but bare primitives cause validation failures
  • result and error: Populated in responses according to JSON-RPC 2.0 specification

Request-Response and Notification Flows

The protocol distinguishes between synchronous requests and fire-and-forget notifications, handling each with distinct concurrency models.

Request-Response Flow: The call method marshals requests into NDJSON frames and registers a response channel in the pending map using the integer ID as the key. The caller blocks until resolve matches an inbound response to the pending call, or until context cancellation or connection closure occurs. This design ensures strict request-response correlation without ambiguity.

Notification Flow: For one-way communication, the notify method marshals notifications and enqueues them on the notifyQueue channel. A dedicated writer goroutine, serveOutboundNotifications, serializes these messages to guarantee transmission order. The queue enforces a maximum depth of maxQueuedNotifications (256 messages) to prevent memory exhaustion under backpressure.

Concurrency Guards and Transport Limits

The implementation maintains thread safety through mutex-protected mutable state. Key limits include:

  • Maximum concurrent handlers: 32 (maxConcurrentHandlers)
  • Maximum queued outbound notifications: 256 (maxQueuedNotifications)
  • Frame size limit: Enforced by FrameBytes via readLine

Panic-safe wrappers surround all handler invocations, translating panics into logged diagnostics without crashing the critical read loop.

Error Handling Protocol

Errors follow standard JSON-RPC 2.0 codes ranging from -32700 (Parse error) to -32603 (Internal error). The protocol additionally defines a custom transport-local code -32099 (CodeServerBusy) for overload scenarios. All errors wrap the rpcErrorObject struct and propagate through respondError, ensuring consistent error serialization across the wire.

DeepSeek-Reasonix Bootstrapping Process

Before extension-specific APIs become available, the host and extension perform a capability handshake that establishes the runtime contract.

Connection Establishment

The host initializes communication by invoking newConn to create a bidirectional pipe wrapping the child process's stdin and stdout streams. This connection object integrates with the host's logging system and immediately launches background goroutines via Serve to manage the read loop and notification writer.

The Bootstrap Handshake

Immediately after connection creation, the host issues a JSON-RPC request with the method "bootstrap" (defined in the TypeScript bridge at desktop/frontend/src/lib/bridge.ts). The request parameters include the host's protocol version:

type BootstrapParams struct {
    ProtocolVersion string `json:"protocolVersion"`
}

The extension must respond with a BootstrapResult containing:

  • Protocol version: Must match the host's declared version (currently "2.0")
  • Actions: A slice of ExtensionActionView objects describing UI palette entries
  • Generations: A map of initial generation numbers (extensionGenerations) for each UI surface
type BootstrapResult struct {
    ProtocolVersion string               `json:"protocolVersion"`
    Actions         []ExtensionActionView `json:"actions"`
    Generations     map[string]int64      `json:"generations"`
}

State Initialization and Runtime Epoch Fencing

Upon receiving the bootstrap response, the host stores the declared actions in paletteExtensionActions and records the generation numbers. These generation numbers establish a per-tab runtime epoch fence utilized by applyExtensionSurfaceEvent and withAcceptedExtensionGeneration to discard out-of-order UI updates. This mechanism ensures that asynchronous extension UI events apply deterministically regardless of network latency or goroutine scheduling.

Post-Bootstrap API Surface

After successful handshake completion, the host exposes three public extension APIs through the bridge:

  • ExtensionActions(tabID): Enumerates the actions declared during bootstrap
  • InvokeExtensionAction(tabID, name, args): Triggers a specific extension action with provided arguments
  • SubmitExtensionForm(tabID, pluginID, surfaceID, values): Submits form data to a specific UI surface

Implementation Examples

Establishing the low-level connection and executing the bootstrap sequence:

// Create connection wrapping child process I/O
conn := extension.NewConn(childStdout, childStdin, logger)
go conn.Serve(context.Background()) // Launches read loop and notification writer

// Execute bootstrap handshake
var result BootstrapResult
raw, err := conn.Call(ctx, "bootstrap", BootstrapParams{ProtocolVersion: "2.0"})
if err != nil {
    log.Fatalf("bootstrap failed: %v", err)
}
if err := json.Unmarshal(raw, &result); err != nil {
    log.Fatalf("invalid bootstrap response: %v", err)
}

// Initialize host state with extension capabilities
paletteExtensionActions = result.Actions
extensionGenerations = result.Generations

Invoking an extension action after successful bootstrapping:

type InvokeParams struct {
    Name string            `json:"name"`
    Args map[string]string `json:"args"`
}

raw, err := conn.Call(ctx, "invokeExtensionAction", InvokeParams{
    Name: "generateDocumentation",
    Args: map[string]string{"language": "go", "style": "godoc"},
})
if err != nil {
    // Handle JSON-RPC error (possibly CodeServerBusy if overloaded)
    return err
}
// Process result returned by extension

Submitting form data to an extension surface:

type FormParams struct {
    PluginID  string                 `json:"pluginId"`
    SurfaceID string                 `json:"surfaceId"`
    Values    map[string]interface{} `json:"values"`
}

_, err := conn.Call(ctx, "submitExtensionForm", FormParams{
    PluginID:  "reasonix-helper",
    SurfaceID: "config-panel",
    Values:    map[string]interface{}{"theme": "dark", "autoSave": true},
})
// Errors return via JSON-RPC error objects; success returns empty result

Summary

  • The Extension Protocol RPC wire format uses strict NDJSON framing with validated JSON-RPC 2.0 envelopes, enforcing integer-only IDs and object-typed parameters.
  • Transport limits include 32 concurrent handlers and 256 queued notifications, with FrameBytes limiting individual message sizes.
  • The bootstrap handshake exchanges protocol versions, UI actions, and generation numbers before normal RPC operations commence.
  • Generation numbers establish epoch fencing that prevents out-of-order UI updates across asynchronous extension events.
  • Key implementation files include sdk/go/wire.go for the core protocol and desktop/frontend/src/lib/bridge.ts for the host-side TypeScript integration.

Frequently Asked Questions

What is the Extension Protocol RPC wire format in DeepSeek-Reasonix?

The Extension Protocol RPC wire format is a strict JSON-RPC 2.0 dialect transmitted over NDJSON (newline-delimited JSON) streams. It enforces specific constraints including integer-only request IDs, object-typed parameters, and a maximum frame size (FrameBytes) to ensure reliable communication between the desktop host and extension processes.

How does the bootstrapping process establish communication between host and extension?

The bootstrapping process begins with the host creating a bidirectional pipe via newConn, then immediately sending a "bootstrap" method request containing the host's protocol version. The extension responds with its version, a list of ExtensionActionView objects, and initial generation numbers, which the host stores to establish the runtime state before allowing calls to ExtensionActions or InvokeExtensionAction.

What are the transport limits and concurrency constraints in the Extension Protocol?

The protocol enforces a maximum of 32 concurrent handlers (maxConcurrentHandlers) for processing requests and 256 queued outbound notifications (maxQueuedNotifications). Individual frames are limited by FrameBytes, and connections terminate with FrameTooLargeError if messages exceed this threshold.

How does the protocol handle errors and panic recovery?

Errors use standard JSON-RPC 2.0 codes (-32700 to -32603) plus the custom -32099 (CodeServerBusy) for overload conditions, wrapped in rpcErrorObject structures. The read loop includes panic-safe wrappers that convert panics into logged diagnostics without terminating the connection, ensuring robust error isolation between extensions and the host.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →