| 1 | # Reasonix Extension SDK for Go |
| 2 | |
| 3 | Write [Reasonix](https://github.com/esengine/DeepSeek-Reasonix) extensions in |
| 4 | Go. An extension is a small sidecar process speaking **Extension Protocol |
| 5 | v1** (`reasonix.extension.v1`) over stdio: Reasonix launches it, hands it the |
| 6 | initialize handshake, and then drives intercepts, event observation, |
| 7 | extension-hosted provider streams, and structured UI surfaces. |
| 8 | |
| 9 | The module is **standard library only** — zero dependencies. |
| 10 | |
| 11 | ## Install |
| 12 | |
| 13 | ```sh |
| 14 | go get github.com/esengine/DeepSeek-Reasonix/sdk/go@v1.0.0 |
| 15 | ``` |
| 16 | |
| 17 | Requires Go 1.23+. SDK releases use immutable `sdk/go/vX.Y.Z` repository |
| 18 | tags; `sdk/go/v1.0.0` is published with the first product release containing |
| 19 | Extension Protocol v1. Before that tag exists, develop against a source |
| 20 | checkout instead of depending on an unversioned API. |
| 21 | |
| 22 | ## Minimal example |
| 23 | |
| 24 | ```go |
| 25 | package main |
| 26 | |
| 27 | import ( |
| 28 | "context" |
| 29 | "encoding/json" |
| 30 | "os" |
| 31 | |
| 32 | extension "github.com/esengine/DeepSeek-Reasonix/sdk/go" |
| 33 | ) |
| 34 | |
| 35 | type ext struct{} |
| 36 | |
| 37 | func (ext) Initialize(_ context.Context, p extension.InitializeParams) (*extension.InitializeResult, error) { |
| 38 | return &extension.InitializeResult{ |
| 39 | Name: "my-ext", |
| 40 | Version: "0.1.0", |
| 41 | Subscriptions: []string{"tool.before"}, |
| 42 | }, nil |
| 43 | } |
| 44 | |
| 45 | func main() { |
| 46 | err := extension.Serve(context.Background(), ext{}, extension.Options{ |
| 47 | Interceptors: map[string]extension.InterceptorFunc{ |
| 48 | "tool.before": func(_ context.Context, event string, payload json.RawMessage) (*extension.InterceptResult, error) { |
| 49 | return extension.Continue(), nil // or Block / Replace / Allow / Deny |
| 50 | }, |
| 51 | }, |
| 52 | }) |
| 53 | if err != nil { |
| 54 | os.Exit(1) |
| 55 | } |
| 56 | // Serve returned nil: the host asked for shutdown. Exit 0. |
| 57 | } |
| 58 | ``` |
| 59 | |
| 60 | Everything else is optional and declared through `Options`: an `Observer` |
| 61 | for fire-and-forget events, a `Provider` for extension-hosted model |
| 62 | providers, `UI` callbacks plus the `HostUI` client for structured surfaces |
| 63 | (status, cards, forms, notifications and blocking prompts — never HTML/JS), |
| 64 | `ReadContentRef`/`ResolveExternalized` for large externalized payloads, and a |
| 65 | `Shutdown` hook. |
| 66 | |
| 67 | ## Concurrency contract |
| 68 | |
| 69 | `Initialize` runs once and completes before any other callback. After that, |
| 70 | the SDK may run up to 32 inbound callbacks concurrently: interceptors, |
| 71 | observers, resource notifications, provider `Catalog`/`Stream`, and UI |
| 72 | callbacks can overlap, and multiple provider streams may be active at once. |
| 73 | Treat callback inputs as call-local and protect mutable state shared by |
| 74 | callbacks with a mutex, atomics, channels, or another explicit ownership |
| 75 | scheme. Cancellation and shutdown may overlap work already in flight, so |
| 76 | callbacks and stream producers must honor their contexts. |
| 77 | |
| 78 | The SDK serializes protocol writes itself; extensions must not write directly |
| 79 | to stdout. stderr remains available for diagnostics. |
| 80 | |
| 81 | ## Runnable example |
| 82 | |
| 83 | [`examples/starterextension`](examples/starterextension/README.md) is the |
| 84 | copyable first extension: it includes a Manifest v1 file, a minimal sidecar, |
| 85 | cross-platform build commands, linked installation, `/reload`, and a visible |
| 86 | input-rewrite check. |
| 87 | |
| 88 | [`examples/fullsidecar`](examples/fullsidecar/main.go) is the reference |
| 89 | extension: input rewriting (try the `/fs ` trigger), tool interception |
| 90 | (block + argument rewrite), system-prompt strategy replacement, a fake |
| 91 | streaming provider (text chunks, a tool call, usage), structured UI (status + |
| 92 | card on session start, a form prompt behind the `demo` action), and a clean |
| 93 | bounded shutdown — all in one small stdlib-only program. |
| 94 | |
| 95 | ```sh |
| 96 | go build -o /tmp/fullsidecar ./examples/fullsidecar |
| 97 | ``` |
| 98 | |
| 99 | The binary speaks the protocol on stdin/stdout, so install it as a plugin |
| 100 | package runtime (or point the host-side conformance suite at it) rather than |
| 101 | running it interactively. It is driven end-to-end against the real host by |
| 102 | `internal/extension/conformance` in the Reasonix repository. |
| 103 | |
| 104 | ## Generated wire types |
| 105 | |
| 106 | `types_generated.go` is produced from the host's frozen protocol registry by |
| 107 | `go run ./cmd/extension-protocol-gen -root .` (repository root). Edit nothing |
| 108 | in that file; the handwritten half of the type layer (validators, error |
| 109 | constructors, enum helpers) lives in `types_ext.go`. |
| 110 | |
| 111 | ## Protocol reference |
| 112 | |
| 113 | - Method/DTO contract: [`docs/EXTENSION_PROTOCOL.generated.md`](../../docs/EXTENSION_PROTOCOL.generated.md) |
| 114 | - Canonical JSON schema: [`internal/extension/protocol/schema.generated.json`](../../internal/extension/protocol/schema.generated.json) |
| 115 | - Transport: strict JSON-RPC 2.0 over NDJSON (one object per line), integer |
| 116 | request ids, object params, 8 MiB frames. |
| 117 | |
| 118 | ## Stability |
| 119 | |
| 120 | Extension Protocol v1's compatibility promise applies: within major version |
| 121 | 1, only optional fields, new enum values, and new methods are added; existing |
| 122 | required fields, method names, directions, limits, error reasons, and |
| 123 | semantics never change. This SDK tracks that contract — upgrading within v1 |
| 124 | never breaks a compiled extension. |
| 125 |