@holokai/neuron-sdk 0.1.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.
@@ -0,0 +1,1022 @@
1
+ import { z } from 'zod';
2
+ import { EventEmitter } from 'eventemitter3';
3
+ import { ErrorObject } from 'ajv';
4
+
5
+ /** Opaque JSON Schema; runtime structure handled by ajv at the gateway. */
6
+ type JsonSchema = unknown;
7
+
8
+ declare const CapabilitySchema: z.ZodObject<{
9
+ type: z.ZodString;
10
+ scope: z.ZodUnion<[z.ZodLiteral<"any">, z.ZodObject<{
11
+ onBehalfOf: z.ZodString;
12
+ }, "strict", z.ZodTypeAny, {
13
+ onBehalfOf: string;
14
+ }, {
15
+ onBehalfOf: string;
16
+ }>]>;
17
+ concurrency: z.ZodNumber;
18
+ }, "strict", z.ZodTypeAny, {
19
+ type: string;
20
+ scope: "any" | {
21
+ onBehalfOf: string;
22
+ };
23
+ concurrency: number;
24
+ }, {
25
+ type: string;
26
+ scope: "any" | {
27
+ onBehalfOf: string;
28
+ };
29
+ concurrency: number;
30
+ }>;
31
+ type Capability = z.infer<typeof CapabilitySchema>;
32
+
33
+ declare const TaskPayloadSchema: z.ZodObject<{
34
+ taskId: z.ZodString;
35
+ workflowId: z.ZodString;
36
+ capability: z.ZodString;
37
+ onBehalfOf: z.ZodOptional<z.ZodString>;
38
+ input: z.ZodUnknown;
39
+ attempt: z.ZodNumber;
40
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
41
+ }, "strict", z.ZodTypeAny, {
42
+ taskId: string;
43
+ workflowId: string;
44
+ capability: string;
45
+ attempt: number;
46
+ onBehalfOf?: string | undefined;
47
+ input?: unknown;
48
+ metadata?: Record<string, unknown> | undefined;
49
+ }, {
50
+ taskId: string;
51
+ workflowId: string;
52
+ capability: string;
53
+ attempt: number;
54
+ onBehalfOf?: string | undefined;
55
+ input?: unknown;
56
+ metadata?: Record<string, unknown> | undefined;
57
+ }>;
58
+ type TaskPayload = z.infer<typeof TaskPayloadSchema>;
59
+ declare const LeasePayloadSchema: z.ZodObject<{
60
+ leaseId: z.ZodString;
61
+ expiresAt: z.ZodString;
62
+ task: z.ZodObject<{
63
+ taskId: z.ZodString;
64
+ workflowId: z.ZodString;
65
+ capability: z.ZodString;
66
+ onBehalfOf: z.ZodOptional<z.ZodString>;
67
+ input: z.ZodUnknown;
68
+ attempt: z.ZodNumber;
69
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
70
+ }, "strict", z.ZodTypeAny, {
71
+ taskId: string;
72
+ workflowId: string;
73
+ capability: string;
74
+ attempt: number;
75
+ onBehalfOf?: string | undefined;
76
+ input?: unknown;
77
+ metadata?: Record<string, unknown> | undefined;
78
+ }, {
79
+ taskId: string;
80
+ workflowId: string;
81
+ capability: string;
82
+ attempt: number;
83
+ onBehalfOf?: string | undefined;
84
+ input?: unknown;
85
+ metadata?: Record<string, unknown> | undefined;
86
+ }>;
87
+ inputSchema: z.ZodUnknown;
88
+ outputSchema: z.ZodUnknown;
89
+ }, "strict", z.ZodTypeAny, {
90
+ leaseId: string;
91
+ expiresAt: string;
92
+ task: {
93
+ taskId: string;
94
+ workflowId: string;
95
+ capability: string;
96
+ attempt: number;
97
+ onBehalfOf?: string | undefined;
98
+ input?: unknown;
99
+ metadata?: Record<string, unknown> | undefined;
100
+ };
101
+ inputSchema?: unknown;
102
+ outputSchema?: unknown;
103
+ }, {
104
+ leaseId: string;
105
+ expiresAt: string;
106
+ task: {
107
+ taskId: string;
108
+ workflowId: string;
109
+ capability: string;
110
+ attempt: number;
111
+ onBehalfOf?: string | undefined;
112
+ input?: unknown;
113
+ metadata?: Record<string, unknown> | undefined;
114
+ };
115
+ inputSchema?: unknown;
116
+ outputSchema?: unknown;
117
+ }>;
118
+ type LeasePayload = z.infer<typeof LeasePayloadSchema>;
119
+
120
+ declare const RegisterFrameSchema: z.ZodObject<{
121
+ type: z.ZodLiteral<"register">;
122
+ neuronId: z.ZodString;
123
+ protocolVersion: z.ZodString;
124
+ capabilities: z.ZodEffects<z.ZodArray<z.ZodObject<{
125
+ type: z.ZodString;
126
+ scope: z.ZodUnion<[z.ZodLiteral<"any">, z.ZodObject<{
127
+ onBehalfOf: z.ZodString;
128
+ }, "strict", z.ZodTypeAny, {
129
+ onBehalfOf: string;
130
+ }, {
131
+ onBehalfOf: string;
132
+ }>]>;
133
+ concurrency: z.ZodNumber;
134
+ }, "strict", z.ZodTypeAny, {
135
+ type: string;
136
+ scope: "any" | {
137
+ onBehalfOf: string;
138
+ };
139
+ concurrency: number;
140
+ }, {
141
+ type: string;
142
+ scope: "any" | {
143
+ onBehalfOf: string;
144
+ };
145
+ concurrency: number;
146
+ }>, "many">, {
147
+ type: string;
148
+ scope: "any" | {
149
+ onBehalfOf: string;
150
+ };
151
+ concurrency: number;
152
+ }[], {
153
+ type: string;
154
+ scope: "any" | {
155
+ onBehalfOf: string;
156
+ };
157
+ concurrency: number;
158
+ }[]>;
159
+ }, "strict", z.ZodTypeAny, {
160
+ type: "register";
161
+ neuronId: string;
162
+ protocolVersion: string;
163
+ capabilities: {
164
+ type: string;
165
+ scope: "any" | {
166
+ onBehalfOf: string;
167
+ };
168
+ concurrency: number;
169
+ }[];
170
+ }, {
171
+ type: "register";
172
+ neuronId: string;
173
+ protocolVersion: string;
174
+ capabilities: {
175
+ type: string;
176
+ scope: "any" | {
177
+ onBehalfOf: string;
178
+ };
179
+ concurrency: number;
180
+ }[];
181
+ }>;
182
+ type RegisterFrame = z.infer<typeof RegisterFrameSchema>;
183
+ declare const UpdateCapabilityFrameSchema: z.ZodObject<{
184
+ type: z.ZodLiteral<"update-capability">;
185
+ neuronId: z.ZodString;
186
+ capability: z.ZodObject<{
187
+ type: z.ZodString;
188
+ scope: z.ZodUnion<[z.ZodLiteral<"any">, z.ZodObject<{
189
+ onBehalfOf: z.ZodString;
190
+ }, "strict", z.ZodTypeAny, {
191
+ onBehalfOf: string;
192
+ }, {
193
+ onBehalfOf: string;
194
+ }>]>;
195
+ concurrency: z.ZodNumber;
196
+ }, "strict", z.ZodTypeAny, {
197
+ type: string;
198
+ scope: "any" | {
199
+ onBehalfOf: string;
200
+ };
201
+ concurrency: number;
202
+ }, {
203
+ type: string;
204
+ scope: "any" | {
205
+ onBehalfOf: string;
206
+ };
207
+ concurrency: number;
208
+ }>;
209
+ }, "strict", z.ZodTypeAny, {
210
+ type: "update-capability";
211
+ capability: {
212
+ type: string;
213
+ scope: "any" | {
214
+ onBehalfOf: string;
215
+ };
216
+ concurrency: number;
217
+ };
218
+ neuronId: string;
219
+ }, {
220
+ type: "update-capability";
221
+ capability: {
222
+ type: string;
223
+ scope: "any" | {
224
+ onBehalfOf: string;
225
+ };
226
+ concurrency: number;
227
+ };
228
+ neuronId: string;
229
+ }>;
230
+ type UpdateCapabilityFrame = z.infer<typeof UpdateCapabilityFrameSchema>;
231
+ declare const HeartbeatInFlightEntrySchema: z.ZodObject<{
232
+ leaseId: z.ZodString;
233
+ taskId: z.ZodString;
234
+ startedAt: z.ZodString;
235
+ }, "strict", z.ZodTypeAny, {
236
+ taskId: string;
237
+ leaseId: string;
238
+ startedAt: string;
239
+ }, {
240
+ taskId: string;
241
+ leaseId: string;
242
+ startedAt: string;
243
+ }>;
244
+ type HeartbeatInFlightEntry = z.infer<typeof HeartbeatInFlightEntrySchema>;
245
+ declare const HeartbeatFrameSchema: z.ZodObject<{
246
+ type: z.ZodLiteral<"heartbeat">;
247
+ neuronId: z.ZodString;
248
+ inFlight: z.ZodArray<z.ZodObject<{
249
+ leaseId: z.ZodString;
250
+ taskId: z.ZodString;
251
+ startedAt: z.ZodString;
252
+ }, "strict", z.ZodTypeAny, {
253
+ taskId: string;
254
+ leaseId: string;
255
+ startedAt: string;
256
+ }, {
257
+ taskId: string;
258
+ leaseId: string;
259
+ startedAt: string;
260
+ }>, "many">;
261
+ sentAt: z.ZodString;
262
+ }, "strict", z.ZodTypeAny, {
263
+ type: "heartbeat";
264
+ neuronId: string;
265
+ inFlight: {
266
+ taskId: string;
267
+ leaseId: string;
268
+ startedAt: string;
269
+ }[];
270
+ sentAt: string;
271
+ }, {
272
+ type: "heartbeat";
273
+ neuronId: string;
274
+ inFlight: {
275
+ taskId: string;
276
+ leaseId: string;
277
+ startedAt: string;
278
+ }[];
279
+ sentAt: string;
280
+ }>;
281
+ type HeartbeatFrame = z.infer<typeof HeartbeatFrameSchema>;
282
+ declare const AckFrameSchema: z.ZodObject<{
283
+ type: z.ZodLiteral<"ack">;
284
+ leaseId: z.ZodString;
285
+ output: z.ZodUnknown;
286
+ completedAt: z.ZodString;
287
+ }, "strict", z.ZodTypeAny, {
288
+ type: "ack";
289
+ leaseId: string;
290
+ completedAt: string;
291
+ output?: unknown;
292
+ }, {
293
+ type: "ack";
294
+ leaseId: string;
295
+ completedAt: string;
296
+ output?: unknown;
297
+ }>;
298
+ type AckFrame = z.infer<typeof AckFrameSchema>;
299
+ declare const NackFrameSchema: z.ZodObject<{
300
+ type: z.ZodLiteral<"nack">;
301
+ leaseId: z.ZodString;
302
+ kind: z.ZodEnum<["retryable", "terminal", "schema-mismatch", "cancelled"]>;
303
+ reason: z.ZodString;
304
+ code: z.ZodOptional<z.ZodString>;
305
+ details: z.ZodOptional<z.ZodUnknown>;
306
+ failedAt: z.ZodString;
307
+ }, "strict", z.ZodTypeAny, {
308
+ type: "nack";
309
+ leaseId: string;
310
+ kind: "retryable" | "terminal" | "schema-mismatch" | "cancelled";
311
+ reason: string;
312
+ failedAt: string;
313
+ code?: string | undefined;
314
+ details?: unknown;
315
+ }, {
316
+ type: "nack";
317
+ leaseId: string;
318
+ kind: "retryable" | "terminal" | "schema-mismatch" | "cancelled";
319
+ reason: string;
320
+ failedAt: string;
321
+ code?: string | undefined;
322
+ details?: unknown;
323
+ }>;
324
+ type NackFrame = z.infer<typeof NackFrameSchema>;
325
+ declare const ProgressFrameSchema: z.ZodObject<{
326
+ type: z.ZodLiteral<"progress">;
327
+ leaseId: z.ZodString;
328
+ percent: z.ZodOptional<z.ZodNumber>;
329
+ message: z.ZodOptional<z.ZodString>;
330
+ data: z.ZodOptional<z.ZodUnknown>;
331
+ sentAt: z.ZodString;
332
+ }, "strict", z.ZodTypeAny, {
333
+ type: "progress";
334
+ leaseId: string;
335
+ sentAt: string;
336
+ message?: string | undefined;
337
+ percent?: number | undefined;
338
+ data?: unknown;
339
+ }, {
340
+ type: "progress";
341
+ leaseId: string;
342
+ sentAt: string;
343
+ message?: string | undefined;
344
+ percent?: number | undefined;
345
+ data?: unknown;
346
+ }>;
347
+ type ProgressFrame = z.infer<typeof ProgressFrameSchema>;
348
+ declare const ServerToNeuronFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
349
+ type: z.ZodLiteral<"registered">;
350
+ sessionId: z.ZodString;
351
+ capabilities: z.ZodArray<z.ZodObject<{
352
+ type: z.ZodString;
353
+ scope: z.ZodUnion<[z.ZodLiteral<"any">, z.ZodObject<{
354
+ onBehalfOf: z.ZodString;
355
+ }, "strict", z.ZodTypeAny, {
356
+ onBehalfOf: string;
357
+ }, {
358
+ onBehalfOf: string;
359
+ }>]>;
360
+ concurrency: z.ZodNumber;
361
+ }, "strict", z.ZodTypeAny, {
362
+ type: string;
363
+ scope: "any" | {
364
+ onBehalfOf: string;
365
+ };
366
+ concurrency: number;
367
+ }, {
368
+ type: string;
369
+ scope: "any" | {
370
+ onBehalfOf: string;
371
+ };
372
+ concurrency: number;
373
+ }>, "many">;
374
+ acceptedAt: z.ZodString;
375
+ }, "strict", z.ZodTypeAny, {
376
+ type: "registered";
377
+ capabilities: {
378
+ type: string;
379
+ scope: "any" | {
380
+ onBehalfOf: string;
381
+ };
382
+ concurrency: number;
383
+ }[];
384
+ sessionId: string;
385
+ acceptedAt: string;
386
+ }, {
387
+ type: "registered";
388
+ capabilities: {
389
+ type: string;
390
+ scope: "any" | {
391
+ onBehalfOf: string;
392
+ };
393
+ concurrency: number;
394
+ }[];
395
+ sessionId: string;
396
+ acceptedAt: string;
397
+ }>, z.ZodObject<{
398
+ type: z.ZodLiteral<"lease">;
399
+ lease: z.ZodObject<{
400
+ leaseId: z.ZodString;
401
+ expiresAt: z.ZodString;
402
+ task: z.ZodObject<{
403
+ taskId: z.ZodString;
404
+ workflowId: z.ZodString;
405
+ capability: z.ZodString;
406
+ onBehalfOf: z.ZodOptional<z.ZodString>;
407
+ input: z.ZodUnknown;
408
+ attempt: z.ZodNumber;
409
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
410
+ }, "strict", z.ZodTypeAny, {
411
+ taskId: string;
412
+ workflowId: string;
413
+ capability: string;
414
+ attempt: number;
415
+ onBehalfOf?: string | undefined;
416
+ input?: unknown;
417
+ metadata?: Record<string, unknown> | undefined;
418
+ }, {
419
+ taskId: string;
420
+ workflowId: string;
421
+ capability: string;
422
+ attempt: number;
423
+ onBehalfOf?: string | undefined;
424
+ input?: unknown;
425
+ metadata?: Record<string, unknown> | undefined;
426
+ }>;
427
+ inputSchema: z.ZodUnknown;
428
+ outputSchema: z.ZodUnknown;
429
+ }, "strict", z.ZodTypeAny, {
430
+ leaseId: string;
431
+ expiresAt: string;
432
+ task: {
433
+ taskId: string;
434
+ workflowId: string;
435
+ capability: string;
436
+ attempt: number;
437
+ onBehalfOf?: string | undefined;
438
+ input?: unknown;
439
+ metadata?: Record<string, unknown> | undefined;
440
+ };
441
+ inputSchema?: unknown;
442
+ outputSchema?: unknown;
443
+ }, {
444
+ leaseId: string;
445
+ expiresAt: string;
446
+ task: {
447
+ taskId: string;
448
+ workflowId: string;
449
+ capability: string;
450
+ attempt: number;
451
+ onBehalfOf?: string | undefined;
452
+ input?: unknown;
453
+ metadata?: Record<string, unknown> | undefined;
454
+ };
455
+ inputSchema?: unknown;
456
+ outputSchema?: unknown;
457
+ }>;
458
+ sentAt: z.ZodString;
459
+ }, "strict", z.ZodTypeAny, {
460
+ type: "lease";
461
+ sentAt: string;
462
+ lease: {
463
+ leaseId: string;
464
+ expiresAt: string;
465
+ task: {
466
+ taskId: string;
467
+ workflowId: string;
468
+ capability: string;
469
+ attempt: number;
470
+ onBehalfOf?: string | undefined;
471
+ input?: unknown;
472
+ metadata?: Record<string, unknown> | undefined;
473
+ };
474
+ inputSchema?: unknown;
475
+ outputSchema?: unknown;
476
+ };
477
+ }, {
478
+ type: "lease";
479
+ sentAt: string;
480
+ lease: {
481
+ leaseId: string;
482
+ expiresAt: string;
483
+ task: {
484
+ taskId: string;
485
+ workflowId: string;
486
+ capability: string;
487
+ attempt: number;
488
+ onBehalfOf?: string | undefined;
489
+ input?: unknown;
490
+ metadata?: Record<string, unknown> | undefined;
491
+ };
492
+ inputSchema?: unknown;
493
+ outputSchema?: unknown;
494
+ };
495
+ }>, z.ZodObject<{
496
+ type: z.ZodLiteral<"cancel">;
497
+ leaseId: z.ZodString;
498
+ reason: z.ZodString;
499
+ cancelledAt: z.ZodString;
500
+ }, "strict", z.ZodTypeAny, {
501
+ type: "cancel";
502
+ leaseId: string;
503
+ reason: string;
504
+ cancelledAt: string;
505
+ }, {
506
+ type: "cancel";
507
+ leaseId: string;
508
+ reason: string;
509
+ cancelledAt: string;
510
+ }>, z.ZodObject<{
511
+ type: z.ZodLiteral<"capability-revoked">;
512
+ capability: z.ZodString;
513
+ reason: z.ZodString;
514
+ revokedAt: z.ZodString;
515
+ }, "strict", z.ZodTypeAny, {
516
+ type: "capability-revoked";
517
+ capability: string;
518
+ reason: string;
519
+ revokedAt: string;
520
+ }, {
521
+ type: "capability-revoked";
522
+ capability: string;
523
+ reason: string;
524
+ revokedAt: string;
525
+ }>, z.ZodObject<{
526
+ type: z.ZodLiteral<"disconnect">;
527
+ reason: z.ZodString;
528
+ code: z.ZodOptional<z.ZodString>;
529
+ reconnectable: z.ZodBoolean;
530
+ sentAt: z.ZodString;
531
+ }, "strict", z.ZodTypeAny, {
532
+ type: "disconnect";
533
+ sentAt: string;
534
+ reason: string;
535
+ reconnectable: boolean;
536
+ code?: string | undefined;
537
+ }, {
538
+ type: "disconnect";
539
+ sentAt: string;
540
+ reason: string;
541
+ reconnectable: boolean;
542
+ code?: string | undefined;
543
+ }>]>;
544
+ type ServerToNeuronFrame = z.infer<typeof ServerToNeuronFrameSchema>;
545
+
546
+ declare const POST_PATH: {
547
+ readonly register: "/neuron/register";
548
+ readonly updateCapability: "/neuron/update-capability";
549
+ readonly heartbeat: "/neuron/heartbeat";
550
+ readonly ack: "/neuron/ack";
551
+ readonly nack: "/neuron/nack";
552
+ readonly progress: "/neuron/progress";
553
+ };
554
+ type PostPath = (typeof POST_PATH)[keyof typeof POST_PATH];
555
+
556
+ /** Function returning the current bearer JWT. Called on each POST and SSE
557
+ * open; can refresh tokens internally. The SDK retries once on 401, so
558
+ * refreshing on 401 inside the callback is the right place to do it. */
559
+ type AuthCallback = () => Promise<string> | string;
560
+ /** Logger surface — minimal shape so consumers can plug in pino, console,
561
+ * etc. without an interface mismatch. */
562
+ interface NeuronLogger {
563
+ debug(meta: Record<string, unknown>, msg?: string): void;
564
+ info(meta: Record<string, unknown>, msg?: string): void;
565
+ warn(meta: Record<string, unknown>, msg?: string): void;
566
+ error(meta: Record<string, unknown>, msg?: string): void;
567
+ }
568
+ /** Per-handler context passed to `neuron.handle(...)` callbacks. */
569
+ interface HandlerContext {
570
+ /** Lease metadata for this delivery. */
571
+ task: TaskPayload;
572
+ /** UUID identifying the lease the gateway holds for this task. */
573
+ leaseId: string;
574
+ /**
575
+ * AbortSignal that fires when the gateway sends a `cancel` frame for
576
+ * this lease (workflow cancellation) or the SDK is shutting down.
577
+ * Handlers that wrap fetch/HTTP calls should pass this signal through.
578
+ */
579
+ signal: AbortSignal;
580
+ /** Logger scoped to this lease. */
581
+ log: NeuronLogger;
582
+ /**
583
+ * Send a `progress` frame to the gateway. Non-blocking; safe to call
584
+ * many times during a long-running handler.
585
+ */
586
+ progress(update: {
587
+ percent?: number;
588
+ message?: string;
589
+ data?: unknown;
590
+ }): void;
591
+ /**
592
+ * Terminal failure — short-circuits the handler with a non-retryable
593
+ * nack containing the given reason. Throws an internal error to bail
594
+ * out of the handler; do not catch it.
595
+ */
596
+ fail(reason: string, opts?: {
597
+ code?: string;
598
+ }): never;
599
+ }
600
+ /**
601
+ * Low-level capability registration: type + scope + concurrency + optional
602
+ * schemas. Most users go through `defineCapability` + `neuron.handle(def,
603
+ * fn)` instead, but this shape is still accepted by `neuron.handle` for
604
+ * cases where a zod schema isn't on hand (or for the legacy worker path).
605
+ */
606
+ interface CapabilityRegistration extends Capability {
607
+ /** JSON Schema validated against the lease input before invoking the
608
+ * handler. Mismatch produces a `schema-mismatch` nack and the handler
609
+ * is never called. */
610
+ inputSchema?: JsonSchema;
611
+ /** JSON Schema validated against the handler's return value. Mismatch
612
+ * produces a `schema-mismatch` nack and the gateway publishes a failed
613
+ * TaskResult. */
614
+ outputSchema?: JsonSchema;
615
+ }
616
+ /**
617
+ * Per-deployment options when registering a `CapabilityDef` with a neuron.
618
+ * Both fields override the corresponding defaults on the def. If neither
619
+ * the def nor these options provide `scope`/`concurrency`, registration
620
+ * throws at start time (intentional fail-fast).
621
+ */
622
+ interface CapabilityHandleOptions {
623
+ scope?: Capability['scope'];
624
+ concurrency?: number;
625
+ }
626
+ /** Handler signature: receives the resolved input and a context, returns
627
+ * the output. Throwing nacks the lease (retryable by default; ctx.fail
628
+ * for terminal). */
629
+ type Handler<TInput = unknown, TOutput = unknown> = (input: TInput, ctx: HandlerContext) => Promise<TOutput> | TOutput;
630
+ /** Internal: the wire frames the SDK emits. */
631
+ type ClientFrame = RegisterFrame | UpdateCapabilityFrame | HeartbeatFrame | AckFrame | NackFrame | ProgressFrame;
632
+
633
+ /**
634
+ * `defineCapability` — single source of truth for a capability's contract.
635
+ *
636
+ * One object carries everything a neuron and a planner need to know about a
637
+ * capability: namespaced type, human-readable description, typed input and
638
+ * output schemas, and (optionally) deployment defaults like scope and
639
+ * concurrency. The SDK serializes only the fields each endpoint cares
640
+ * about — plan-time gets the contract, registration gets the deployment.
641
+ *
642
+ * Why one object: users don't have to juggle a "plan-time def" vs a
643
+ * "registration-time def". Defaults set on the def can still be overridden
644
+ * at `neuron.handle(...)` time per deployment (e.g., desktop vs server).
645
+ */
646
+
647
+ interface CapabilityDefInit<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny> {
648
+ /** Namespaced capability type, e.g. `core/script.python`. */
649
+ type: string;
650
+ /**
651
+ * Plain-language description shown to the planner LLM. Required at
652
+ * plan-time so the model can choose this capability appropriately.
653
+ */
654
+ description: string;
655
+ /** Zod schema for the capability's input payload. */
656
+ input: TIn;
657
+ /** Zod schema for the capability's output payload. */
658
+ output: TOut;
659
+ /**
660
+ * Optional deployment defaults. Both can be overridden at
661
+ * `neuron.handle(def, opts, fn)` time.
662
+ */
663
+ scope?: Capability['scope'];
664
+ concurrency?: number;
665
+ }
666
+ /**
667
+ * A typed capability definition. The phantom `_input` / `_output` fields
668
+ * carry the inferred TS types so `neuron.handle(def, fn)` can constrain
669
+ * `fn`'s argument and return type.
670
+ *
671
+ * Internal serializers (`toPlanShape`, `toRegistrationCapability`) keep the
672
+ * "send only what the endpoint needs" rule in one place — endpoints get
673
+ * the right subset and never accidentally see the wrong fields.
674
+ */
675
+ interface CapabilityDef<TIn = unknown, TOut = unknown> {
676
+ readonly type: string;
677
+ readonly description: string;
678
+ /** Compiled JSON Schema — derived once at definition time. */
679
+ readonly inputSchema: JsonSchema;
680
+ readonly outputSchema: JsonSchema;
681
+ readonly scope?: Capability['scope'];
682
+ readonly concurrency?: number;
683
+ /** Original zod schemas — kept for runtime validation in handlers if needed. */
684
+ readonly _zod: {
685
+ input: z.ZodType<TIn>;
686
+ output: z.ZodType<TOut>;
687
+ };
688
+ /** Phantom type carriers — never read at runtime. */
689
+ readonly _input?: TIn;
690
+ readonly _output?: TOut;
691
+ }
692
+ /**
693
+ * Plan-request shape: `{ type, description, input, output }`. Scope and
694
+ * concurrency are intentionally dropped — they're per-deployment and
695
+ * don't belong in the planner's catalog.
696
+ */
697
+ interface CapabilityPlanShape {
698
+ type: string;
699
+ description: string;
700
+ input: JsonSchema;
701
+ output: JsonSchema;
702
+ }
703
+ declare function defineCapability<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>(init: CapabilityDefInit<TIn, TOut>): CapabilityDef<z.infer<TIn>, z.infer<TOut>>;
704
+ /** Render the def for inclusion in a `POST /plan` catalog. */
705
+ declare function toPlanShape(def: CapabilityDef): CapabilityPlanShape;
706
+ /**
707
+ * Render the registration `Capability` frame for the gateway. `opts`
708
+ * overrides any defaults set on the def. `scope` and `concurrency` are
709
+ * required at registration time — if neither the def nor `opts` provides
710
+ * them, this throws so the misconfiguration surfaces at start time
711
+ * rather than as a confusing wire-validation error.
712
+ */
713
+ declare function toRegistrationCapability(def: CapabilityDef, opts?: CapabilityHandleOptions): Capability;
714
+
715
+ /**
716
+ * Public event surface. The `Neuron` class extends `EventEmitter<NeuronEvents>`
717
+ * from `eventemitter3` so consumers can subscribe with the familiar
718
+ * `.on() / .once() / .off()` API:
719
+ *
720
+ * neuron.on('frame', (frame) => log.append(frame));
721
+ * neuron.once('connection:registered', ({ sessionId }) => mountUI(sessionId));
722
+ * const off = neuron.on('error', captureError);
723
+ * off();
724
+ *
725
+ * Two flavors:
726
+ *
727
+ * - `frame` — a pass-through of the gateway's `/neuron/events` SSE stream.
728
+ * Fires for every parsed `ServerToNeuronFrame`, including ones for
729
+ * which no handler is registered. Useful for live event logs,
730
+ * observability, and the neuron-tester debug UI.
731
+ *
732
+ * - `connection:*` and `error` — synthetic, SDK-generated. Mirror the
733
+ * transport's lifecycle (SSE handshake, register acceptance, close,
734
+ * reconnect scheduling).
735
+ *
736
+ * Adding new events is intentionally cheap — extend `NeuronEvents` and emit
737
+ * from inside the class. Future entries (e.g. `'workflow:task-completed'`
738
+ * for sibling-neuron notifications) slot in without breaking subscribers.
739
+ */
740
+ interface NeuronEvents {
741
+ /** Fired after the SSE handshake succeeds, before register is posted. */
742
+ 'connection:open': () => void;
743
+ /** Fired after the gateway accepts the register frame. */
744
+ 'connection:registered': (info: {
745
+ sessionId: string;
746
+ capabilities: number;
747
+ }) => void;
748
+ /** Fired when the SSE stream closes — clean shutdown or transient error. */
749
+ 'connection:close': (reason: string) => void;
750
+ /** Fired when a reconnect is scheduled, before the backoff delay elapses. */
751
+ 'connection:reconnecting': (info: {
752
+ attempt: number;
753
+ delayMs: number;
754
+ reason: string;
755
+ }) => void;
756
+ /** Fired on transport / parse errors. Reconnect handling is automatic. */
757
+ error: (err: Error) => void;
758
+ /** Pass-through of every parsed inbound SSE frame. */
759
+ frame: (frame: ServerToNeuronFrame) => void;
760
+ }
761
+ interface NeuronOptions {
762
+ /** Base URL of the gateway, e.g. `https://api.holokai.dev`. */
763
+ gatewayUrl: string;
764
+ /**
765
+ * Stable per-process identifier. Reused across reconnects so the
766
+ * gateway can correlate the new session with the same neuron. SDK does
767
+ * NOT generate one for you — pick something stable per device/process
768
+ * (UUID stored on disk for desktop, hostname+pid for server).
769
+ */
770
+ neuronId: string;
771
+ /** Token provider — see `AuthCallback` for refresh semantics. */
772
+ auth: AuthCallback;
773
+ /** Logger; defaults to a silent no-op. */
774
+ logger?: NeuronLogger;
775
+ /** Heartbeat interval (ms). Default 10s. */
776
+ heartbeatIntervalMs?: number;
777
+ /** Initial reconnect delay (ms). Default 500ms. */
778
+ initialReconnectDelayMs?: number;
779
+ /** Max reconnect delay (ms). Default 30s. */
780
+ maxReconnectDelayMs?: number;
781
+ /** Override `fetch`; mainly for tests. */
782
+ fetch?: typeof fetch;
783
+ }
784
+ /**
785
+ * Public SDK class. Compose:
786
+ *
787
+ * const neuron = new Neuron({ gatewayUrl, neuronId, auth: () => token });
788
+ * neuron.handle({ type: 'core/script.python', scope: 'any', concurrency: 4 }, handler);
789
+ * neuron.on('connection:registered', ({ sessionId }) => log(sessionId));
790
+ * await neuron.start();
791
+ * ...
792
+ * await neuron.stop({ drain: true, timeoutMs: 30_000 });
793
+ *
794
+ * See `NeuronEvents` for the full set of subscribable events.
795
+ */
796
+ declare class Neuron extends EventEmitter<NeuronEvents> {
797
+ private readonly transport;
798
+ private readonly handlers;
799
+ private readonly inFlightLeases;
800
+ /**
801
+ * Leases admitted by `dispatchLease` but still waiting for a worker-pool
802
+ * slot. Tracked separately from `inFlightLeases` so the heartbeat snapshot
803
+ * doesn't lie — only running leases extend the gateway's lease TTL.
804
+ */
805
+ private readonly queuedLeases;
806
+ private readonly heartbeat;
807
+ private readonly validator;
808
+ private readonly logger;
809
+ private readonly opts;
810
+ private started;
811
+ private stopping;
812
+ private hasOpenedOnce;
813
+ constructor(opts: NeuronOptions);
814
+ /**
815
+ * Register a handler for a capability. Must be called before `start()`;
816
+ * the gateway expects the full capability list in the register frame.
817
+ *
818
+ * Three call shapes:
819
+ *
820
+ * neuron.handle(capDef, fn) // typed via defineCapability defaults
821
+ * neuron.handle(capDef, opts, fn) // typed; override scope/concurrency
822
+ * neuron.handle(capabilityRegistration, fn) // legacy/low-level: explicit JSON Schemas
823
+ *
824
+ * If called twice for the same capability before start, the second call
825
+ * replaces the first. After start, use `updateCapability` to adjust
826
+ * concurrency at runtime.
827
+ */
828
+ handle<TIn, TOut>(def: CapabilityDef<TIn, TOut>, handler: Handler<TIn, TOut>): void;
829
+ handle<TIn, TOut>(def: CapabilityDef<TIn, TOut>, opts: CapabilityHandleOptions, handler: Handler<TIn, TOut>): void;
830
+ handle(reg: CapabilityRegistration, handler: Handler): void;
831
+ /**
832
+ * Adjust an already-registered capability's concurrency at runtime and
833
+ * notify the gateway via an `update-capability` frame so prefetch can be
834
+ * tuned. Schema and handler are not replaced — re-register before start
835
+ * if those need to change.
836
+ */
837
+ updateCapability(reg: Pick<CapabilityRegistration, 'type' | 'concurrency'>): Promise<void>;
838
+ /**
839
+ * Open the SSE stream, post register with all advertised capabilities,
840
+ * and start the heartbeat loop. Resolves once the gateway accepts the
841
+ * register frame.
842
+ */
843
+ start(): Promise<void>;
844
+ /**
845
+ * Refuse new leases, drain in-flight handlers up to `timeoutMs`, then
846
+ * tear down the connection. Pending leases (queued but not started)
847
+ * are abandoned — the gateway's lease TTL will reclaim them.
848
+ */
849
+ stop(opts?: {
850
+ drain?: boolean;
851
+ timeoutMs?: number;
852
+ }): Promise<void>;
853
+ private snapshotInFlight;
854
+ private handleConnectionOpen;
855
+ private handleConnectionClose;
856
+ private postRegister;
857
+ private handleFrame;
858
+ private dispatchLease;
859
+ private handleCancel;
860
+ }
861
+ /** SSE event names re-exported for tests / consumers that want to match
862
+ * by name without importing the protocol package. */
863
+ declare const SSE_EVENT_NAMES: {
864
+ readonly registered: "registered";
865
+ readonly lease: "lease";
866
+ readonly cancel: "cancel";
867
+ readonly capabilityRevoked: "capability-revoked";
868
+ readonly disconnect: "disconnect";
869
+ };
870
+
871
+ /**
872
+ * Internal callback surface — the `Neuron` that owns a `Transport` is the
873
+ * single subscriber. There's no broadcast or late-subscription use case
874
+ * here, so a callbacks-on-options shape is simpler than an emitter and
875
+ * keeps the transport free of any event-library dependency.
876
+ */
877
+ interface TransportCallbacks {
878
+ /** Fired after the SSE handshake succeeds. */
879
+ onOpen?: () => void;
880
+ /** Fired when the SSE stream ends — clean close or error. */
881
+ onClose?: (reason: string) => void;
882
+ /** Fired on every parsed server frame. */
883
+ onFrame?: (frame: ServerToNeuronFrame) => void;
884
+ /** Fired on connection or parse error; transport will reconnect unless stopped. */
885
+ onError?: (err: Error) => void;
886
+ /** Fired when a reconnect is scheduled, before the backoff delay elapses. */
887
+ onReconnecting?: (info: {
888
+ attempt: number;
889
+ delayMs: number;
890
+ reason: string;
891
+ }) => void;
892
+ }
893
+ interface TransportOptions extends TransportCallbacks {
894
+ /** Base URL of the gateway (e.g. `https://api.holokai.dev`). */
895
+ gatewayUrl: string;
896
+ /** Stable per-process identifier sent as `?neuronId=` on the SSE GET. */
897
+ neuronId: string;
898
+ /** Token provider — called on every POST and SSE open; called again on 401 retry. */
899
+ auth: AuthCallback;
900
+ /** Logger; defaults to a silent no-op. */
901
+ logger?: NeuronLogger;
902
+ /**
903
+ * Initial reconnect delay in ms. Doubles on each consecutive failure
904
+ * up to `maxReconnectDelayMs`. Defaults: 500ms initial, 30s ceiling.
905
+ */
906
+ initialReconnectDelayMs?: number;
907
+ maxReconnectDelayMs?: number;
908
+ /** Override `fetch`. Defaults to global fetch (Node 20+ undici). */
909
+ fetch?: typeof fetch;
910
+ }
911
+ /**
912
+ * Wire-level transport. Holds a single SSE stream open against the
913
+ * gateway, parses inbound frames, exposes a typed `post(...)` for
914
+ * outbound frames, and reconnects on disconnect with exponential backoff.
915
+ *
916
+ * Lifecycle:
917
+ * - `start()` opens the SSE stream and resolves once the connection is
918
+ * established. Reconnect logic kicks in if the stream drops.
919
+ * - `post(path, frame)` sends an outbound frame; refreshes the token via
920
+ * the auth callback on 401 and retries once.
921
+ * - `stop()` aborts the SSE stream and prevents further reconnects.
922
+ *
923
+ * Note: `Transport` does NOT call `register` itself — that's the Neuron
924
+ * class's job, since register requires the SSE stream to already exist
925
+ * (the gateway matches identity+neuronId between SSE pending stream and
926
+ * register POST).
927
+ */
928
+ declare class Transport {
929
+ private readonly opts;
930
+ private readonly callbacks;
931
+ private abortController;
932
+ private stopped;
933
+ private connected;
934
+ private reconnectAttempts;
935
+ private reconnectTimer;
936
+ constructor(opts: TransportOptions);
937
+ isConnected(): boolean;
938
+ /** Open the SSE stream. Resolves after the connection is established. */
939
+ start(): Promise<void>;
940
+ /** Tear down the SSE stream and prevent further reconnect attempts. */
941
+ stop(): Promise<void>;
942
+ /**
943
+ * POST a client frame to the gateway. Refreshes the token via the auth
944
+ * callback and retries once on 401 — that's the contract: callers
945
+ * implement refresh inside their callback.
946
+ */
947
+ post<T extends ClientFrame>(path: PostPath, frame: T): Promise<unknown>;
948
+ private postRaw;
949
+ private openStream;
950
+ private consumeStream;
951
+ private handleStreamEnd;
952
+ private scheduleReconnect;
953
+ }
954
+ declare class TransportError extends Error {
955
+ readonly statusCode: number;
956
+ constructor(message: string, statusCode: number);
957
+ }
958
+
959
+ /**
960
+ * Per-capability concurrency limiter. Bounds the number of in-flight
961
+ * handler invocations locally even if the gateway overshoots prefetch — a
962
+ * misbehaving gateway should never push past the contract.
963
+ *
964
+ * Pools are created per (neuron, capability) and live for the duration of
965
+ * the SDK's session. Drain support (used by `neuron.stop({ drain: true })`)
966
+ * resolves once all in-flight tasks have completed; new submissions during
967
+ * drain are rejected with `ShutdownError`.
968
+ */
969
+ declare class WorkerPool {
970
+ private maxConcurrency;
971
+ private inFlight;
972
+ private queue;
973
+ private draining;
974
+ private drained;
975
+ constructor(maxConcurrency: number);
976
+ /** Currently-running task count. */
977
+ size(): number;
978
+ /** Tasks waiting for a slot. */
979
+ pending(): number;
980
+ /**
981
+ * Run `fn` once a concurrency slot is available. Resolves with the
982
+ * function's return value (or rejects with its error). Throws
983
+ * ShutdownError if the pool is already draining.
984
+ */
985
+ run<T>(fn: () => Promise<T>): Promise<T>;
986
+ /**
987
+ * Update the max concurrency. Lowering doesn't kill in-flight tasks —
988
+ * they finish naturally — but it does prevent new ones from starting
989
+ * until the in-flight count drops below the new limit.
990
+ */
991
+ setConcurrency(newMax: number): void;
992
+ /**
993
+ * Stop accepting new submissions. Returns a promise that resolves once
994
+ * every in-flight AND queued task has finished — queued waiters from
995
+ * before drain are released as in-flight slots free, run normally, and
996
+ * are awaited as part of drain. Only fresh `run()` calls received after
997
+ * `drain()` reject with ShutdownError. Callers that need a hard stop
998
+ * should race this promise against a timeout and abort handlers.
999
+ */
1000
+ drain(): Promise<void>;
1001
+ }
1002
+ declare class ShutdownError extends Error {
1003
+ constructor(message: string);
1004
+ }
1005
+
1006
+ /**
1007
+ * Compiles JSON Schemas once and caches by reference. Mirror of the
1008
+ * gateway-side OutputValidator — both ends of the wire validate against
1009
+ * the same schemas so a misbehaving party can't sneak by either side.
1010
+ */
1011
+ declare class SchemaValidator {
1012
+ private readonly ajv;
1013
+ private readonly cache;
1014
+ constructor();
1015
+ /**
1016
+ * Returns null if the value matches the schema (or no schema given);
1017
+ * otherwise an array of structured errors.
1018
+ */
1019
+ validate(schema: JsonSchema | undefined, data: unknown): ErrorObject[] | null;
1020
+ }
1021
+
1022
+ export { type AuthCallback, type Capability, type CapabilityDef, type CapabilityDefInit, type CapabilityHandleOptions, type CapabilityPlanShape, type CapabilityRegistration, type Handler, type HandlerContext, type HeartbeatInFlightEntry, type LeasePayload, Neuron, type NeuronEvents, type NeuronLogger, type NeuronOptions, SSE_EVENT_NAMES, SchemaValidator, type ServerToNeuronFrame, ShutdownError, type TaskPayload, Transport, type TransportCallbacks, TransportError, type TransportOptions, WorkerPool, defineCapability, toPlanShape, toRegistrationCapability };