@holokai/neuron-sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +246 -14
- package/dist/index.d.mts +54 -668
- package/dist/index.d.ts +54 -668
- package/dist/index.js +227 -209
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +227 -209
- package/dist/index.mjs.map +1 -1
- package/dist/ipc.d.mts +88 -0
- package/dist/ipc.d.ts +88 -0
- package/dist/ipc.js +86 -0
- package/dist/ipc.js.map +1 -0
- package/dist/ipc.mjs +84 -0
- package/dist/ipc.mjs.map +1 -0
- package/dist/transport-QqkooS7K.d.mts +742 -0
- package/dist/transport-QqkooS7K.d.ts +742 -0
- package/package.json +15 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Holokai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
SDK for building neurons — processes that connect to the BigBrain gateway and execute tasks on behalf of workflows.
|
|
4
4
|
|
|
5
|
-
A neuron advertises one or more **capabilities** (typed task handlers), opens a persistent connection to the gateway, and receives work via
|
|
5
|
+
A neuron advertises one or more **capabilities** (typed task handlers), opens a persistent connection to the gateway, and receives work via a pluggable `Transport`. Two transports ship in the SDK:
|
|
6
|
+
|
|
7
|
+
- **`HttpTransport`** — SSE for inbound, HTTPS POST for outbound. The default for any neuron talking to a remote BigBrain gateway. Re-exported from the package root.
|
|
8
|
+
- **`IpcTransport`** — Electron renderer ↔ main IPC. For neurons running inside an app that embeds BigBrain in main; no network hop, no JWT in the renderer. Imported from the `/ipc` subpath.
|
|
9
|
+
|
|
10
|
+
The gateway routes tasks to the right neuron based on capability type and scope. The built-in `apps/worker` uses this SDK over HTTP; the in-process Holokai Desktop renderer uses it over IPC; both share the same `Neuron` class and handler API.
|
|
6
11
|
|
|
7
12
|
---
|
|
8
13
|
|
|
@@ -115,19 +120,35 @@ neuronId: await readOrCreateStoredId('/var/lib/myapp/neuron-id')
|
|
|
115
120
|
|
|
116
121
|
### `new Neuron(opts: NeuronOptions)`
|
|
117
122
|
|
|
123
|
+
`NeuronOptions` is a discriminated union — pick the convenience shape (HTTP) or pass a transport explicitly.
|
|
124
|
+
|
|
118
125
|
```ts
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
type NeuronOptions = NeuronOptionsWithHttp | NeuronOptionsWithTransport;
|
|
127
|
+
|
|
128
|
+
interface NeuronCommonOptions {
|
|
121
129
|
neuronId: string; // Stable per process/device
|
|
122
|
-
auth: AuthCallback; // Token provider (see below)
|
|
123
130
|
logger?: NeuronLogger; // Plug in pino, console, etc. Default: silent
|
|
124
131
|
heartbeatIntervalMs?: number; // Default 10_000 (10s)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Convenience: SDK builds an HttpTransport for you. Most common path.
|
|
135
|
+
interface NeuronOptionsWithHttp extends NeuronCommonOptions {
|
|
136
|
+
gatewayUrl: string; // Base URL, e.g. 'https://api.example.com'
|
|
137
|
+
auth: AuthCallback; // Token provider (see below)
|
|
125
138
|
initialReconnectDelayMs?: number; // Default 500ms
|
|
126
139
|
maxReconnectDelayMs?: number; // Default 30_000 (30s)
|
|
127
140
|
fetch?: typeof fetch; // Override for tests
|
|
128
141
|
}
|
|
142
|
+
|
|
143
|
+
// Power-user: bring your own Transport. Used for IpcTransport, custom
|
|
144
|
+
// transports, or HttpTransport instances built explicitly.
|
|
145
|
+
interface NeuronOptionsWithTransport extends NeuronCommonOptions {
|
|
146
|
+
transport: Transport;
|
|
147
|
+
}
|
|
129
148
|
```
|
|
130
149
|
|
|
150
|
+
See the [Transports](#transports) section for `HttpTransport`, `IpcTransport`, and the `Transport` interface.
|
|
151
|
+
|
|
131
152
|
---
|
|
132
153
|
|
|
133
154
|
### `neuron.handle(reg, handler)`
|
|
@@ -358,6 +379,128 @@ The event surface is designed to grow — future entries (e.g. `'workflow:task-c
|
|
|
358
379
|
|
|
359
380
|
---
|
|
360
381
|
|
|
382
|
+
## Transports
|
|
383
|
+
|
|
384
|
+
A `Transport` is the wire-level abstraction the rest of the SDK builds on. It carries typed frames in both directions: inbound `ServerToNeuronFrame`s via callbacks, outbound `ClientFrame`s via `post(...)`. The `Neuron` class only ever talks to a `Transport` — adding a new transport (e.g. WebSocket, native messaging) means implementing one interface.
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
interface Transport {
|
|
388
|
+
subscribe(callbacks: TransportCallbacks): void;
|
|
389
|
+
start(): Promise<void>;
|
|
390
|
+
stop(): Promise<void>;
|
|
391
|
+
post<T extends ClientFrame>(path: PostPath, frame: T): Promise<unknown>;
|
|
392
|
+
isConnected(): boolean;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
interface TransportCallbacks {
|
|
396
|
+
onOpen?: () => void;
|
|
397
|
+
onClose?: (reason: string) => void;
|
|
398
|
+
onFrame?: (frame: ServerToNeuronFrame) => void;
|
|
399
|
+
onError?: (err: Error) => void;
|
|
400
|
+
onReconnecting?: (info: { attempt: number; delayMs: number; reason: string }) => void;
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Both `Transport` and `TransportCallbacks` are exported from the package root.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
### `HttpTransport`
|
|
409
|
+
|
|
410
|
+
Default transport. SSE for inbound, HTTPS POST for outbound. Reconnects with exponential backoff (default: 500ms → 30s ceiling). Refreshes the auth token via your `AuthCallback` and retries once on 401.
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
import { Neuron, HttpTransport } from '@holokai/neuron-sdk';
|
|
414
|
+
|
|
415
|
+
const transport = new HttpTransport({
|
|
416
|
+
gatewayUrl: 'https://api.example.com',
|
|
417
|
+
neuronId: 'my-worker-001',
|
|
418
|
+
auth: () => process.env.GATEWAY_TOKEN!,
|
|
419
|
+
// optional:
|
|
420
|
+
initialReconnectDelayMs: 500,
|
|
421
|
+
maxReconnectDelayMs: 30_000,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const neuron = new Neuron({ neuronId: 'my-worker-001', transport });
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
In practice you rarely construct `HttpTransport` directly — passing `gatewayUrl` + `auth` to `Neuron` builds it for you. Construct explicitly when you want to share a fetch implementation, swap the auth strategy, or instrument the transport in tests.
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
interface HttpTransportOptions {
|
|
431
|
+
gatewayUrl: string;
|
|
432
|
+
neuronId: string;
|
|
433
|
+
auth: AuthCallback;
|
|
434
|
+
logger?: NeuronLogger;
|
|
435
|
+
initialReconnectDelayMs?: number; // default 500ms
|
|
436
|
+
maxReconnectDelayMs?: number; // default 30_000ms
|
|
437
|
+
fetch?: typeof fetch;
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
### `IpcTransport`
|
|
444
|
+
|
|
445
|
+
For renderer-process neurons running inside an Electron app that hosts BigBrain in the main process. Leases arrive over Electron IPC, not over the network. Imported from the `/ipc` subpath so non-Electron consumers don't pay for it:
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
import { Neuron } from '@holokai/neuron-sdk';
|
|
449
|
+
import { IpcTransport } from '@holokai/neuron-sdk/ipc';
|
|
450
|
+
|
|
451
|
+
// `bridge` comes from the renderer's preload — see below.
|
|
452
|
+
const transport = new IpcTransport({ bridge: window.bigbrainNeuron, neuronId });
|
|
453
|
+
const neuron = new Neuron({ neuronId, transport });
|
|
454
|
+
|
|
455
|
+
neuron.handle({ type: 'desktop/local-tool', scope: { onBehalfOf: userId }, concurrency: 1 }, handler);
|
|
456
|
+
await neuron.start();
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Differences from `HttpTransport`:
|
|
460
|
+
|
|
461
|
+
- **No reconnect logic** — IPC has no transient socket. If the bridge throws, the failure surfaces via `onError` and the consumer decides what to do.
|
|
462
|
+
- **No auth callback** — the renderer never sees the JWT. The Electron main process attaches `Authorization` headers when it forwards POSTs to the embedded Fastify gateway.
|
|
463
|
+
- **No keep-alive** — IPC channels are process-local; nothing to time out.
|
|
464
|
+
|
|
465
|
+
The SDK does **not** depend on Electron. The renderer's preload script is responsible for constructing a concrete bridge that satisfies the `NeuronIpcBridge` shape and exposing it via `contextBridge`:
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
interface NeuronIpcBridge {
|
|
469
|
+
attach(neuronId: string): Promise<{ channel: string }>;
|
|
470
|
+
detach(neuronId: string): Promise<void>;
|
|
471
|
+
subscribe(channel: string, cb: (frame: ServerToNeuronFrame) => void): () => void;
|
|
472
|
+
invoke<T extends ClientFrame>(path: PostPath, frame: T): Promise<unknown>;
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Typical preload wiring (Electron-side, app code — not part of the SDK):
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
// preload.ts
|
|
480
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
481
|
+
|
|
482
|
+
contextBridge.exposeInMainWorld('bigbrainNeuron', {
|
|
483
|
+
attach: (neuronId) => ipcRenderer.invoke('bigbrain:attach', neuronId),
|
|
484
|
+
detach: (neuronId) => ipcRenderer.invoke('bigbrain:detach', neuronId),
|
|
485
|
+
subscribe: (channel, cb) => {
|
|
486
|
+
const listener = (_e, frame) => cb(frame);
|
|
487
|
+
ipcRenderer.on(channel, listener);
|
|
488
|
+
return () => ipcRenderer.off(channel, listener);
|
|
489
|
+
},
|
|
490
|
+
invoke: (path, frame) => ipcRenderer.invoke('bigbrain:post', path, frame),
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
interface IpcTransportOptions {
|
|
496
|
+
bridge: NeuronIpcBridge;
|
|
497
|
+
neuronId: string;
|
|
498
|
+
logger?: NeuronLogger;
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
361
504
|
## Examples
|
|
362
505
|
|
|
363
506
|
### Simple any-scoped capability
|
|
@@ -493,15 +636,17 @@ powerMonitor.on('on-battery', async () => {
|
|
|
493
636
|
|
|
494
637
|
## Configuration reference
|
|
495
638
|
|
|
496
|
-
| Option | Default | Notes |
|
|
497
|
-
|
|
498
|
-
| `
|
|
499
|
-
| `
|
|
500
|
-
| `
|
|
501
|
-
| `
|
|
502
|
-
| `
|
|
503
|
-
| `
|
|
504
|
-
| `
|
|
639
|
+
| Option | Default | Applies to | Notes |
|
|
640
|
+
|---|---|---|---|
|
|
641
|
+
| `neuronId` | — | both | Stable per device/process; reused on reconnect |
|
|
642
|
+
| `logger` | silent no-op | both | Plug in pino or console |
|
|
643
|
+
| `heartbeatIntervalMs` | `10_000` | both | Sent every 10s; renews lease expiries |
|
|
644
|
+
| `transport` | — | with-transport shape | Pre-built `Transport` (e.g. `IpcTransport` or a custom `HttpTransport`) |
|
|
645
|
+
| `gatewayUrl` | — | with-http shape | Base URL, no trailing slash |
|
|
646
|
+
| `auth` | — | with-http shape | Called on every POST + SSE open |
|
|
647
|
+
| `initialReconnectDelayMs` | `500` | with-http shape | First reconnect delay (HTTP only — IPC doesn't reconnect) |
|
|
648
|
+
| `maxReconnectDelayMs` | `30_000` | with-http shape | Reconnect delay ceiling (exponential backoff) |
|
|
649
|
+
| `fetch` | global `fetch` | with-http shape | Override for tests |
|
|
505
650
|
|
|
506
651
|
---
|
|
507
652
|
|
|
@@ -511,10 +656,17 @@ This section covers enough to debug; the wire-level frame schemas are bundled in
|
|
|
511
656
|
|
|
512
657
|
### Transport
|
|
513
658
|
|
|
659
|
+
The SDK abstracts the wire as a `Transport` (see [Transports](#transports) above). The frame types below are the same regardless of transport — only the carrier differs.
|
|
660
|
+
|
|
661
|
+
**`HttpTransport` (default):**
|
|
514
662
|
- **Server → Neuron**: SSE stream at `GET /neuron/events?neuronId={id}`
|
|
515
663
|
- **Neuron → Server**: HTTPS POST endpoints (one per frame type)
|
|
664
|
+
- No WebSockets — SSE + HTTPS POST traverses enterprise proxies cleanly.
|
|
516
665
|
|
|
517
|
-
|
|
666
|
+
**`IpcTransport` (Electron renderer):**
|
|
667
|
+
- **Server → Neuron**: per-neuron Electron IPC channel obtained from `bridge.attach()`.
|
|
668
|
+
- **Neuron → Server**: `bridge.invoke(path, frame)`, which the main process forwards to the embedded Fastify gateway via `app.inject(...)` — same auth and validation chain as HTTP.
|
|
669
|
+
- No reconnect, no auth callback, no keep-alive.
|
|
518
670
|
|
|
519
671
|
### Frame types
|
|
520
672
|
|
|
@@ -626,3 +778,83 @@ Heartbeats aren't reaching the gateway, so the reaper hasn't reclaimed them. Che
|
|
|
626
778
|
### Auth 401 on every request
|
|
627
779
|
|
|
628
780
|
The `auth` callback isn't returning a valid token. If using a refresh flow, ensure the callback fetches a fresh token on each call (the SDK caches nothing — it's your callback's job).
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
## Releasing (maintainers)
|
|
785
|
+
|
|
786
|
+
`@holokai/neuron-sdk` is published to public npm. Releases are cut from a
|
|
787
|
+
monorepo-scoped git tag, and CI does the publish — there is no npm token on
|
|
788
|
+
anyone's laptop and none stored in the repo.
|
|
789
|
+
|
|
790
|
+
### Cut a release
|
|
791
|
+
|
|
792
|
+
1. **Bump the version** in `packages/neuron-sdk/package.json`
|
|
793
|
+
(`version`). Follow semver:
|
|
794
|
+
- Pilot phase, we are in **`0.x`**. Because the protocol is still absorbing
|
|
795
|
+
pilot-developer feedback, a **breaking** change bumps the **minor**
|
|
796
|
+
(`0.2.0` → `0.3.0`); a backward-compatible change or fix bumps the
|
|
797
|
+
**patch** (`0.2.0` → `0.2.1`). This is the standard `0.x` convention and
|
|
798
|
+
is what buys us room to break things on feedback without a major bump.
|
|
799
|
+
- Hold **`1.0.0`** until the public API has survived pilot feedback and we're
|
|
800
|
+
ready to commit to "breaking changes bump the major." Jumping to `1.0.0`
|
|
801
|
+
is a stability promise, not a milestone — don't spend it early.
|
|
802
|
+
2. **Commit** the bump (reference the Linear ticket) and merge to `main`.
|
|
803
|
+
3. **Tag and push** from `main`:
|
|
804
|
+
```sh
|
|
805
|
+
git tag neuron-sdk-v0.2.0 # must equal the package.json version
|
|
806
|
+
git push origin neuron-sdk-v0.2.0
|
|
807
|
+
```
|
|
808
|
+
The tag is prefixed `neuron-sdk-v` (the Python SDK uses
|
|
809
|
+
`neuron-sdk-python-v`) so the two never collide.
|
|
810
|
+
4. **`publish-neuron-sdk.yml`** fires on the tag: it asserts the tag version
|
|
811
|
+
matches `package.json`, runs the `prepublish` gate (`build` + `test`), and
|
|
812
|
+
publishes to npm. The `npm` GitHub Environment gates the publish behind a
|
|
813
|
+
**required-reviewer approval** — approve the run under Actions, and on success
|
|
814
|
+
the new version is live at
|
|
815
|
+
[`@holokai/neuron-sdk`](https://www.npmjs.com/package/@holokai/neuron-sdk).
|
|
816
|
+
|
|
817
|
+
> No `--provenance`: bigbrain is a private repo, so npm's provenance
|
|
818
|
+
> attestation (a public transparency-log link back to the source) adds little
|
|
819
|
+
> and has historically errored from private repos. Trusted-publishing auth is
|
|
820
|
+
> unaffected. Add the flag back if bigbrain ever goes public.
|
|
821
|
+
|
|
822
|
+
The tag→`package.json` assertion means a mismatched tag fails **before** the
|
|
823
|
+
registry is touched — npm refuses to re-upload an existing version, so there's
|
|
824
|
+
no half-published state to clean up.
|
|
825
|
+
|
|
826
|
+
### Publish auth: npm Trusted Publishing (one-time setup)
|
|
827
|
+
|
|
828
|
+
CI authenticates to npm via **OIDC trusted publishing** — GitHub Actions
|
|
829
|
+
exchanges a short-lived OIDC token for publish rights, so no long-lived
|
|
830
|
+
`NPM_TOKEN` exists to leak. This mirrors the Python SDK's PyPI trusted
|
|
831
|
+
publishing. It requires two one-time setups:
|
|
832
|
+
|
|
833
|
+
1. **On npmjs.com** (a package admin, once): open the
|
|
834
|
+
[`@holokai/neuron-sdk`](https://www.npmjs.com/package/@holokai/neuron-sdk)
|
|
835
|
+
package → **Settings → Trusted Publisher → GitHub Actions**, and register:
|
|
836
|
+
- Organization / repository: `holok-ai/bigbrain`
|
|
837
|
+
- Workflow filename: `publish-neuron-sdk.yml`
|
|
838
|
+
- Environment: `npm`
|
|
839
|
+
2. **In this repo** (once): create the **`npm`** GitHub Environment
|
|
840
|
+
(Settings → Environments). Add reviewers/branch protection here if you want a
|
|
841
|
+
manual approval gate before each publish.
|
|
842
|
+
|
|
843
|
+
Until the trusted publisher is registered, the `npm publish` step fails with an
|
|
844
|
+
auth error — that's the expected "not set up yet" signal, not a code bug.
|
|
845
|
+
|
|
846
|
+
### Manual publish (fallback / first bootstrap)
|
|
847
|
+
|
|
848
|
+
The tag flow is the norm. If you ever need to publish by hand (e.g. to bootstrap
|
|
849
|
+
the very first version before trusted publishing is configured):
|
|
850
|
+
|
|
851
|
+
```sh
|
|
852
|
+
cd packages/neuron-sdk
|
|
853
|
+
pnpm run publish:npm:dry # inspect the tarball + files list
|
|
854
|
+
pnpm run publish:npm # runs the prepublishOnly build+test gate, then publishes
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
This uses your own `npm login` credentials and skips the CI approval gate —
|
|
858
|
+
prefer the tag flow for anything a developer will consume.
|
|
859
|
+
|
|
860
|
+
<!-- ci: force full build on the self-hosted runner (HOL-2028 verify); safe to remove -->
|