Go SDK Architecture for Developing Extensions in Reasonix: A Technical Deep Dive

The Reasonix Go SDK implements the Extension Protocol v1 as a sidecar that communicates via JSON-RPC 2.0 over NDJSON, using a state-machine-driven server that manages the lifecycle through strict handshake barriers, concurrent callback handling, and modular interfaces for interceptors, providers, and UI handlers.

The sdk/go package in the esengine/DeepSeek-Reasonix repository provides a robust framework for building sidecar extensions that extend Reasonix's core functionality. This Go SDK architecture for developing extensions in Reasonix centers on a JSON-RPC transport layer, a rigorous state machine for protocol compliance, and carefully isolated concurrency boundaries to ensure reliable host communication.

Core Architectural Components

Server and Transport Layer

The server struct in sdk/go/sdk.go orchestrates the entire extension lifecycle, managing the transport layer and routing incoming requests. It wraps a conn object—created by newConn—that handles low-level NDJSON framing for JSON-RPC 2.0 messages. This connection layer serializes messages, assigns request IDs, and performs raw call and notify operations that underpin all host communication.

Handler Interface and Initialization

Every extension must implement the Handler interface defined at lines 43-49 of sdk.go. The Initialize method serves as the mandatory entry point where extensions announce their name, version, and capabilities through InitializeResult. This handshake occurs while the internal state machine is in stateHandshake, ensuring the host receives the extension manifest before any operational methods are invoked.

Configuration and Concurrency Safety

The Options struct (lines 28-58) configures the sidecar's behavior, including stdin/stdout redirection, interceptors, provider implementations, and UI callbacks. Critically, the SDK may invoke up to 32 callbacks concurrently, requiring extension developers to protect shared state with synchronization primitives like sync.Mutex. The Shutdown callback, also configured via Options, enables graceful termination when the host requests shutdown via handleShutdown (approximately lines 44-84).

Event Interception System

Interceptors allow extensions to inspect and modify events through InterceptorFunc types defined at lines 51-57. The handleIntercept function (approximately lines 88-125) processes these rules, enabling extensions to Continue, Block, Replace, Allow, or Deny intercepted events based on custom logic.

Provider Streaming Architecture

The Provider interface (lines 58-73) enables extensions to supply model catalogs and streaming inference results. Implementations must provide Catalog for model discovery and Stream for opening token streams. The SDK manages these through handleStreamOpen and pumpStream, ensuring chunks are delivered to the host while preserving ordering and handling cancellation signals properly.

UI Integration and Host Communication

Extensions implement UIHandler (lines 17-26) to respond to host-side UI actions like Action and Submit events. Conversely, the HostUI client (starting at line 46) allows extensions to initiate host-side interactions, presenting status cards, notifications, and user prompts through methods like RequestConfirm and PublishStatus.

Protocol State Machine

The SDK enforces strict protocol compliance through a state machine with four phases: stateNew, stateHandshake, stateReady, and stateShutdown. The gateRequest and gateNotification functions (approximately lines 25-70) reject all calls until the host completes the handshake sequence: first sending extension/initialize (triggering Handler.Initialize), then extension/initialized (transitioning to stateReady).

Content References and Error Handling

For large payloads, the SDK externalizes data via content references. The ReadContentRef (approximately lines 998-1055) and ResolveExternalized (approximately lines 1068-1085) helpers paginate, verify SHA-256 checksums, and safely re-assemble data. Host-side JSON-RPC errors are mapped to typed ProtocolError structures via mapCallError (lines 127-137), providing extensions with specific failure reasons like ErrProtocolError and ErrProviderFailed.

Implementation Examples

Basic Extension Setup

The following example demonstrates the minimal structure required to implement the Handler interface and start the server:

package main

import (
	"context"
	"log"
	"os"

	ext "github.com/esengine/DeepSeek-Reasonix/sdk/go"
)

type MyHandler struct{}

func (h *MyHandler) Initialize(ctx context.Context, p ext.InitializeParams) (*ext.InitializeResult, error) {
	return &ext.InitializeResult{
		Name:    "demo-extension",
		Version: "0.1.0",
	}, nil
}

func main() {
	h := &MyHandler{}
	opts := ext.Options{
		Name:    "demo-extension",
		Version: "0.1.0",
		Logger:  log.New(os.Stderr, "demo: ", log.LstdFlags),
	}
	if err := ext.Serve(context.Background(), h, opts); err != nil {
		log.Fatalf("extension terminated: %v", err)
	}
}

Implementing Interceptors

Interceptors examine events and return control directives to the SDK:

func blockSensitive(ctx context.Context, event string, payload json.RawMessage) (*ext.InterceptResult, error) {
	if event == "session.start" {
		return ext.Block("session starts are not allowed in demo mode"), nil
	}
	return ext.Continue(), nil
}

opts := ext.Options{
	Interceptors: map[string]ext.InterceptorFunc{
		"*": blockSensitive,
	},
}

Building a Custom Provider

Providers stream model responses through Go channels:

type DemoProvider struct{}

func (p *DemoProvider) Catalog(ctx context.Context) ([]ext.ProviderDescriptor, error) {
	return []ext.ProviderDescriptor{
		{Ref: "demo-model", Name: "Demo Model", Owner: "demo-org"},
	}, nil
}

func (p *DemoProvider) Stream(ctx context.Context, req ext.StreamRequest) (<-chan ext.StreamChunk, error) {
	ch := make(chan ext.StreamChunk)
	go func() {
		defer close(ch)
		ch <- ext.TextChunk("Hello, ")
		time.Sleep(100 * time.Millisecond)
		ch <- ext.TextChunk("world!")
		ch <- ext.DoneChunk()
	}()
	return ch, nil
}

opts := ext.Options{Provider: &DemoProvider{}}

Host UI Interaction

Extensions can prompt users through the host interface during initialization or runtime:

func (h *MyHandler) Initialize(ctx context.Context, p ext.InitializeParams) (*ext.InitializeResult, error) {
	confirm, err := ext.HostUI{}.RequestConfirm(ctx, "sess-1", 0, "confirm-1", "Proceed?")
	if err != nil || !confirm {
		return nil, fmt.Errorf("user declined")
	}
	return &ext.InitializeResult{Name: "demo-extension", Version: "0.1.0"}, nil
}

Key Source Files

  • sdk/go/sdk.go – Core server implementation, lifecycle orchestration, transport management, and public API definitions.
  • sdk/go/wire.go – Low-level JSON-RPC wire protocol definitions, message structs, method constants, and error types.
  • sdk/go/types_generated.go – Auto-generated type definitions for the extension protocol, including catalog entries and stream chunks.
  • sdk/go/types_ext.go – Extensions to generated types, providing convenience helpers and custom error values.
  • sdk/go/sdk_test.go – Unit tests exercising the server state machine, interceptors, provider streams, and UI client helpers.
  • sdk/go/ui_test.go – Tests for the HostUI client covering prompts and surface publishing.
  • sdk/go/provider_test.go – Example provider implementations used in the test suite.

Summary

  • The SDK uses JSON-RPC 2.0 over NDJSON for host communication, implemented in the conn type within sdk.go.
  • A strict state machine (stateNew → stateHandshake → stateReady) gates all operations until the handshake completes.
  • Extensions must implement the Handler interface, providing initialization metadata and optional capabilities.
  • The Provider interface enables streaming model responses through managed channels with automatic pumping and cancellation.
  • Interceptors can modify, block, or allow events using the InterceptorFunc signature processed by handleIntercept.
  • Up to 32 concurrent callbacks may execute simultaneously, requiring thread-safe extension code.
  • Large payloads use content references with SHA-256 verification via ReadContentRef and ResolveExternalized.
  • UI integration works bidirectionally through UIHandler (host-to-extension) and HostUI (extension-to-host).

Frequently Asked Questions

How does the Reasonix Go SDK handle concurrent requests?

The SDK maintains an internal worker pool that can execute up to 32 callbacks concurrently according to the Options struct configuration. Because the host may invoke interceptors, providers, and UI handlers simultaneously, extension developers must protect shared state using synchronization primitives like sync.Mutex or atomic operations to prevent race conditions.

What is the extension handshake protocol in the Go SDK?

The protocol requires a two-step handshake: after startup (stateNew), the host sends extension/initialize which triggers the extension's Handler.Initialize method while in stateHandshake. The extension returns its manifest, then the host sends extension/initialized, transitioning the state machine to stateReady. The gateRequest and gateNotification functions reject all other calls until this barrier opens.

How do interceptors work in the Reasonix extension architecture?

Interceptors are functions matching the InterceptorFunc signature defined at lines 51-57 of sdk.go. Registered in the Options.Interceptors map, they receive event names and raw JSON payloads, then return InterceptResult values that instruct the SDK to Continue, Block, Replace, Allow, or Deny the event. The handleIntercept function (lines 88-125) evaluates these rules before passing events to downstream handlers.

What is the purpose of the Provider interface in the Go SDK?

The Provider interface (lines 58-73) abstracts model inference capabilities, requiring implementations to provide Catalog for model discovery and Stream for generating token responses. When the host requests inference, the SDK invokes handleStreamOpen to establish the stream, then uses pumpStream to deliver chunks to the host while managing backpressure and cancellation tokens.

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 →