@holokai/neuron-sdk 0.3.0 → 0.4.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/README.md +28 -5
- package/dist/index.d.mts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +100 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +100 -29
- package/dist/index.mjs.map +1 -1
- package/dist/ipc.d.mts +1 -1
- package/dist/ipc.d.ts +1 -1
- package/dist/{transport-QqkooS7K.d.mts → transport-Cs5tY2IO.d.mts} +91 -2
- package/dist/{transport-QqkooS7K.d.ts → transport-Cs5tY2IO.d.ts} +91 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -21,6 +21,16 @@ pnpm add @holokai/neuron-sdk
|
|
|
21
21
|
|
|
22
22
|
Runs in **Node 20+** (uses the global `fetch` and `ReadableStream`) and **modern browsers** (Chromium, Firefox, Safari latest). The package has no `node:*` imports — Holokai Desktop / Electron and the in-browser neuron-tester both consume the same SDK build.
|
|
23
23
|
|
|
24
|
+
> **Gateway compatibility (protocol 1.1.0, HOL-2926).** Since protocol
|
|
25
|
+
> 1.1.0 the SDK advertises each capability's contract (`description` +
|
|
26
|
+
> input/output JSON Schemas) on the register frame. The gateway validates
|
|
27
|
+
> register frames **strictly**, so a gateway older than HOL-2926 rejects
|
|
28
|
+
> them with `INVALID_FRAME` — **deploy the gateway before publishing or
|
|
29
|
+
> rolling out any 1.1.0-protocol SDK (including neuron-tester OTA)**. A
|
|
30
|
+
> rejected registration leaves the SSE stream open but the neuron
|
|
31
|
+
> lease-less; the SDK surfaces it via the `error` event
|
|
32
|
+
> (`registration rejected by gateway: ...`) so apps can react.
|
|
33
|
+
|
|
24
34
|
---
|
|
25
35
|
|
|
26
36
|
## Quick start
|
|
@@ -29,7 +39,7 @@ Runs in **Node 20+** (uses the global `fetch` and `ReadableStream`) and **modern
|
|
|
29
39
|
import { Neuron } from '@holokai/neuron-sdk';
|
|
30
40
|
|
|
31
41
|
const neuron = new Neuron({
|
|
32
|
-
gatewayUrl: 'https://
|
|
42
|
+
gatewayUrl: 'https://bigbrain.holokai.dev',
|
|
33
43
|
neuronId: 'my-worker-001', // stable per process/device
|
|
34
44
|
auth: () => process.env.GATEWAY_TOKEN!, // or an async refresh function
|
|
35
45
|
});
|
|
@@ -75,7 +85,7 @@ import { Neuron } from '@holokai/neuron-sdk';
|
|
|
75
85
|
import { createClientCredentialsAuth, defaultCredentialPath } from '@holokai/neuron-sdk/auth';
|
|
76
86
|
|
|
77
87
|
const neuron = new Neuron({
|
|
78
|
-
gatewayUrl: 'https://
|
|
88
|
+
gatewayUrl: 'https://bigbrain.holokai.dev',
|
|
79
89
|
neuronId: 'warehouse-connector-001',
|
|
80
90
|
auth: createClientCredentialsAuth(defaultCredentialPath()),
|
|
81
91
|
});
|
|
@@ -83,7 +93,7 @@ const neuron = new Neuron({
|
|
|
83
93
|
|
|
84
94
|
`createClientCredentialsAuth` mints `aud=bigbrain` tokens via the OAuth2 `client_credentials` grant, caches them against the response's `expires_in` (proactive refresh, single-flight), and re-reads the credential file on `invalid_client` so an operator-rotated secret is picked up without a restart. If the credential was revoked in Moku it throws `CredentialRevokedError` — re-enroll to recover.
|
|
85
95
|
|
|
86
|
-
> `@holokai/neuron-sdk/auth` is **Node-only** (it reads the credential file); the root and `./ipc` entries remain browser-clean. Python SDK parity
|
|
96
|
+
> `@holokai/neuron-sdk/auth` is **Node-only** (it reads the credential file); the root and `./ipc` entries remain browser-clean. Python SDK parity ships in `holokai-neuron-sdk` ≥ 0.2.0 (`holokai_neuron_sdk.auth`: `holokai-neuron-sdk enroll` + `create_client_credentials_auth`) — same credential file, interchangeable across both SDKs.
|
|
87
97
|
|
|
88
98
|
---
|
|
89
99
|
|
|
@@ -170,7 +180,7 @@ interface NeuronCommonOptions {
|
|
|
170
180
|
|
|
171
181
|
// Convenience: SDK builds an HttpTransport for you. Most common path.
|
|
172
182
|
interface NeuronOptionsWithHttp extends NeuronCommonOptions {
|
|
173
|
-
gatewayUrl: string; // Base URL, e.g. 'https://
|
|
183
|
+
gatewayUrl: string; // Base URL, e.g. 'https://bigbrain.holokai.dev'
|
|
174
184
|
auth: AuthCallback; // Token provider (see below)
|
|
175
185
|
initialReconnectDelayMs?: number; // Default 500ms
|
|
176
186
|
maxReconnectDelayMs?: number; // Default 30_000 (30s)
|
|
@@ -213,6 +223,19 @@ type Handler<TInput = unknown, TOutput = unknown> = (
|
|
|
213
223
|
|
|
214
224
|
If `inputSchema` is provided and the lease input fails validation, the handler is never called — the SDK sends a `schema-mismatch` nack automatically. Same for `outputSchema` on the return value.
|
|
215
225
|
|
|
226
|
+
**Schema precedence.** For each of input and output the SDK picks exactly one validator:
|
|
227
|
+
|
|
228
|
+
1. The zod schema from `defineCapability`, when the capability was registered that way. JSON Schema can't express `.refine()`, `.transform()`, or `.preprocess()`, so the zod contract stays authoritative and its transforms are what the handler sees and what goes on the wire.
|
|
229
|
+
2. Otherwise the JSON Schema passed here on `CapabilityRegistration`.
|
|
230
|
+
|
|
231
|
+
A capability registered with neither is not validated client-side — its handler runs on whatever arrives.
|
|
232
|
+
|
|
233
|
+
The `inputSchema` / `outputSchema` that ride on the lease frame are **not** consulted. They can only ever be your own schemas handed back to you — the register frame carries no schemas, so the server's only source is the `POST /plan` handler catalog the client supplies. And the planner doesn't persist those onto task rows, so on real workflow tasks the lease frame's schemas are absent entirely; the only case where they arrive populated is the dev-only `/__dev__/inject-task` route used by neuron-tester, which echoes back what the capability registered.
|
|
234
|
+
|
|
235
|
+
> **This is the only validation that runs.** The gateway's ack-time check validates against that same (empty) `lease.outputSchema`, so it does not currently catch anything. If a capability declares no schema here, its input and output are unchecked end to end.
|
|
236
|
+
|
|
237
|
+
> **Note:** the Go and Python SDKs currently prefer the lease frame's schema and fall back to the registered one. TypeScript deliberately diverges here; the other two are expected to follow.
|
|
238
|
+
|
|
216
239
|
---
|
|
217
240
|
|
|
218
241
|
### `neuron.start()`
|
|
@@ -450,7 +473,7 @@ Default transport. SSE for inbound, HTTPS POST for outbound. Reconnects with exp
|
|
|
450
473
|
import { Neuron, HttpTransport } from '@holokai/neuron-sdk';
|
|
451
474
|
|
|
452
475
|
const transport = new HttpTransport({
|
|
453
|
-
gatewayUrl: 'https://
|
|
476
|
+
gatewayUrl: 'https://bigbrain.holokai.dev',
|
|
454
477
|
neuronId: 'my-worker-001',
|
|
455
478
|
auth: () => process.env.GATEWAY_TOKEN!,
|
|
456
479
|
// optional:
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-
|
|
2
|
-
export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-
|
|
1
|
+
import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Cs5tY2IO.mjs';
|
|
2
|
+
export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Cs5tY2IO.mjs';
|
|
3
3
|
import { EventEmitter } from 'eventemitter3';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { ErrorObject } from 'ajv';
|
|
@@ -97,6 +97,11 @@ declare function toPlanShape(def: CapabilityDef): CapabilityPlanShape;
|
|
|
97
97
|
* required at registration time — if neither the def nor `opts` provides
|
|
98
98
|
* them, this throws so the misconfiguration surfaces at start time
|
|
99
99
|
* rather than as a confusing wire-validation error.
|
|
100
|
+
*
|
|
101
|
+
* Since HOL-2926 the frame also carries the contract (`description` +
|
|
102
|
+
* compiled JSON Schemas): the gateway forwards it to Moku's capability
|
|
103
|
+
* catalog so registry-sourced capabilities render with a real contract
|
|
104
|
+
* in the supervisor catalog instead of type-only stubs.
|
|
100
105
|
*/
|
|
101
106
|
declare function toRegistrationCapability(def: CapabilityDef, opts?: CapabilityHandleOptions): Capability;
|
|
102
107
|
|
|
@@ -141,7 +146,12 @@ interface NeuronEvents {
|
|
|
141
146
|
delayMs: number;
|
|
142
147
|
reason: string;
|
|
143
148
|
}) => void;
|
|
144
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Fired on transport / parse errors (reconnect handling is automatic)
|
|
151
|
+
* and on register rejection (HOL-2926 — NOT automatically recovered:
|
|
152
|
+
* the SSE stream stays open, so the neuron sits connected but
|
|
153
|
+
* lease-less until the app reacts, e.g. by calling `stop()`).
|
|
154
|
+
*/
|
|
145
155
|
error: (err: Error) => void;
|
|
146
156
|
/** Pass-through of every parsed inbound SSE frame. */
|
|
147
157
|
frame: (frame: ServerToNeuronFrame) => void;
|
|
@@ -176,7 +186,7 @@ interface NeuronOptionsWithTransport extends NeuronCommonOptions {
|
|
|
176
186
|
* surface so existing call sites keep compiling unchanged.
|
|
177
187
|
*/
|
|
178
188
|
interface NeuronOptionsWithHttp extends NeuronCommonOptions {
|
|
179
|
-
/** Base URL of the gateway, e.g. `https://
|
|
189
|
+
/** Base URL of the gateway, e.g. `https://bigbrain.holokai.dev`. */
|
|
180
190
|
gatewayUrl: string;
|
|
181
191
|
/** Token provider — see `AuthCallback` for refresh semantics. */
|
|
182
192
|
auth: AuthCallback;
|
|
@@ -276,7 +286,7 @@ declare const SSE_EVENT_NAMES: {
|
|
|
276
286
|
};
|
|
277
287
|
|
|
278
288
|
interface HttpTransportOptions {
|
|
279
|
-
/** Base URL of the gateway (e.g. `https://
|
|
289
|
+
/** Base URL of the gateway (e.g. `https://bigbrain.holokai.dev`). */
|
|
280
290
|
gatewayUrl: string;
|
|
281
291
|
/** Stable per-process identifier sent as `?neuronId=` on the SSE GET. */
|
|
282
292
|
neuronId: string;
|
|
@@ -395,12 +405,15 @@ declare class ShutdownError extends Error {
|
|
|
395
405
|
* the same schemas so a misbehaving party can't sneak by either side.
|
|
396
406
|
*/
|
|
397
407
|
declare class SchemaValidator {
|
|
408
|
+
private readonly onCompileError?;
|
|
398
409
|
private readonly ajv;
|
|
399
410
|
private readonly cache;
|
|
400
|
-
|
|
411
|
+
private readonly uncompilable;
|
|
412
|
+
constructor(onCompileError?: ((message: string) => void) | undefined);
|
|
401
413
|
/**
|
|
402
|
-
* Returns null if the value matches the schema
|
|
403
|
-
*
|
|
414
|
+
* Returns null if the value matches the schema, no schema was given, or
|
|
415
|
+
* the schema cannot be compiled in this runtime (fail-open — the gateway
|
|
416
|
+
* remains the enforcement point); otherwise an array of structured errors.
|
|
404
417
|
*/
|
|
405
418
|
validate(schema: JsonSchema | undefined, data: unknown): ErrorObject[] | null;
|
|
406
419
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-
|
|
2
|
-
export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-
|
|
1
|
+
import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Cs5tY2IO.js';
|
|
2
|
+
export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Cs5tY2IO.js';
|
|
3
3
|
import { EventEmitter } from 'eventemitter3';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { ErrorObject } from 'ajv';
|
|
@@ -97,6 +97,11 @@ declare function toPlanShape(def: CapabilityDef): CapabilityPlanShape;
|
|
|
97
97
|
* required at registration time — if neither the def nor `opts` provides
|
|
98
98
|
* them, this throws so the misconfiguration surfaces at start time
|
|
99
99
|
* rather than as a confusing wire-validation error.
|
|
100
|
+
*
|
|
101
|
+
* Since HOL-2926 the frame also carries the contract (`description` +
|
|
102
|
+
* compiled JSON Schemas): the gateway forwards it to Moku's capability
|
|
103
|
+
* catalog so registry-sourced capabilities render with a real contract
|
|
104
|
+
* in the supervisor catalog instead of type-only stubs.
|
|
100
105
|
*/
|
|
101
106
|
declare function toRegistrationCapability(def: CapabilityDef, opts?: CapabilityHandleOptions): Capability;
|
|
102
107
|
|
|
@@ -141,7 +146,12 @@ interface NeuronEvents {
|
|
|
141
146
|
delayMs: number;
|
|
142
147
|
reason: string;
|
|
143
148
|
}) => void;
|
|
144
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Fired on transport / parse errors (reconnect handling is automatic)
|
|
151
|
+
* and on register rejection (HOL-2926 — NOT automatically recovered:
|
|
152
|
+
* the SSE stream stays open, so the neuron sits connected but
|
|
153
|
+
* lease-less until the app reacts, e.g. by calling `stop()`).
|
|
154
|
+
*/
|
|
145
155
|
error: (err: Error) => void;
|
|
146
156
|
/** Pass-through of every parsed inbound SSE frame. */
|
|
147
157
|
frame: (frame: ServerToNeuronFrame) => void;
|
|
@@ -176,7 +186,7 @@ interface NeuronOptionsWithTransport extends NeuronCommonOptions {
|
|
|
176
186
|
* surface so existing call sites keep compiling unchanged.
|
|
177
187
|
*/
|
|
178
188
|
interface NeuronOptionsWithHttp extends NeuronCommonOptions {
|
|
179
|
-
/** Base URL of the gateway, e.g. `https://
|
|
189
|
+
/** Base URL of the gateway, e.g. `https://bigbrain.holokai.dev`. */
|
|
180
190
|
gatewayUrl: string;
|
|
181
191
|
/** Token provider — see `AuthCallback` for refresh semantics. */
|
|
182
192
|
auth: AuthCallback;
|
|
@@ -276,7 +286,7 @@ declare const SSE_EVENT_NAMES: {
|
|
|
276
286
|
};
|
|
277
287
|
|
|
278
288
|
interface HttpTransportOptions {
|
|
279
|
-
/** Base URL of the gateway (e.g. `https://
|
|
289
|
+
/** Base URL of the gateway (e.g. `https://bigbrain.holokai.dev`). */
|
|
280
290
|
gatewayUrl: string;
|
|
281
291
|
/** Stable per-process identifier sent as `?neuronId=` on the SSE GET. */
|
|
282
292
|
neuronId: string;
|
|
@@ -395,12 +405,15 @@ declare class ShutdownError extends Error {
|
|
|
395
405
|
* the same schemas so a misbehaving party can't sneak by either side.
|
|
396
406
|
*/
|
|
397
407
|
declare class SchemaValidator {
|
|
408
|
+
private readonly onCompileError?;
|
|
398
409
|
private readonly ajv;
|
|
399
410
|
private readonly cache;
|
|
400
|
-
|
|
411
|
+
private readonly uncompilable;
|
|
412
|
+
constructor(onCompileError?: ((message: string) => void) | undefined);
|
|
401
413
|
/**
|
|
402
|
-
* Returns null if the value matches the schema
|
|
403
|
-
*
|
|
414
|
+
* Returns null if the value matches the schema, no schema was given, or
|
|
415
|
+
* the schema cannot be compiled in this runtime (fail-open — the gateway
|
|
416
|
+
* remains the enforcement point); otherwise an array of structured errors.
|
|
404
417
|
*/
|
|
405
418
|
validate(schema: JsonSchema | undefined, data: unknown): ErrorObject[] | null;
|
|
406
419
|
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ var CapabilityTypeSchema = zod.z.string().min(1).regex(
|
|
|
17
17
|
"capability type must be namespaced: {owner}/{package}[/{name}][.{variant}]"
|
|
18
18
|
);
|
|
19
19
|
var UserIdSchema = zod.z.string().min(1);
|
|
20
|
+
var JsonSchemaSchema = zod.z.unknown();
|
|
20
21
|
var CapabilityScopeSchema = zod.z.union([
|
|
21
22
|
zod.z.literal("any"),
|
|
22
23
|
zod.z.object({ onBehalfOf: UserIdSchema }).strict()
|
|
@@ -25,6 +26,36 @@ var CapabilitySchema = zod.z.object({
|
|
|
25
26
|
type: CapabilityTypeSchema,
|
|
26
27
|
scope: CapabilityScopeSchema,
|
|
27
28
|
concurrency: zod.z.number().int().positive(),
|
|
29
|
+
/**
|
|
30
|
+
* Plain-language contract advertisement (HOL-2926). All three fields
|
|
31
|
+
* are optional and additive: older neurons keep sending
|
|
32
|
+
* `{ type, scope, concurrency }` and the gateway accepts both shapes.
|
|
33
|
+
* When present they flow (fire-and-forget) to Moku's
|
|
34
|
+
* `capability_catalog` so registry-sourced capabilities render with a
|
|
35
|
+
* real contract in the supervisor catalog instead of type-only stubs.
|
|
36
|
+
*
|
|
37
|
+
* Omit rather than null — the fields are `.optional()`, not
|
|
38
|
+
* `.nullable()`, and a `null` description fails validation.
|
|
39
|
+
*/
|
|
40
|
+
description: zod.z.string().optional(),
|
|
41
|
+
/**
|
|
42
|
+
* JSON Schema for the capability's input payload — opaque on the wire,
|
|
43
|
+
* but `null` is rejected: the reconcile push to Moku is last-writer-wins
|
|
44
|
+
* per present key, so a null here could wipe a stored schema.
|
|
45
|
+
* (`description` already rejects null via `z.string()`.)
|
|
46
|
+
*/
|
|
47
|
+
// The `: boolean` return annotations stop TS (5.5+) from inferring the
|
|
48
|
+
// lambdas as type predicates, which would narrow the field from
|
|
49
|
+
// `unknown` to `{}` and ripple into every consumer.
|
|
50
|
+
inputSchema: JsonSchemaSchema.refine(
|
|
51
|
+
(v) => v !== null,
|
|
52
|
+
"omit inputSchema instead of sending null"
|
|
53
|
+
).optional(),
|
|
54
|
+
/** JSON Schema for the capability's output payload — same rules as inputSchema. */
|
|
55
|
+
outputSchema: JsonSchemaSchema.refine(
|
|
56
|
+
(v) => v !== null,
|
|
57
|
+
"omit outputSchema instead of sending null"
|
|
58
|
+
).optional(),
|
|
28
59
|
/**
|
|
29
60
|
* Per-capability lease TTL override in milliseconds (HOL-356).
|
|
30
61
|
* The gateway uses this when minting a lease's `expiresAt`; absent
|
|
@@ -40,7 +71,6 @@ var CapabilitySchema = zod.z.object({
|
|
|
40
71
|
*/
|
|
41
72
|
leaseTtlMs: zod.z.number().int().min(1e3).max(60 * 60 * 1e3).optional()
|
|
42
73
|
}).strict();
|
|
43
|
-
var JsonSchemaSchema = zod.z.unknown();
|
|
44
74
|
var LeaseIdSchema = zod.z.string().min(1);
|
|
45
75
|
var TaskPayloadSchema = zod.z.object({
|
|
46
76
|
taskId: zod.z.string().min(1),
|
|
@@ -62,9 +92,10 @@ var NeuronIdSchema = zod.z.string().min(1);
|
|
|
62
92
|
var RegisterFrameSchema = zod.z.object({
|
|
63
93
|
type: zod.z.literal("register"),
|
|
64
94
|
neuronId: NeuronIdSchema,
|
|
65
|
-
// Free-form by design — the gateway
|
|
66
|
-
//
|
|
67
|
-
//
|
|
95
|
+
// Free-form by design — the gateway records this verbatim and performs
|
|
96
|
+
// no version negotiation; compatibility comes from additive-only frame
|
|
97
|
+
// evolution (see PROTOCOL_VERSION in transport.ts). Do not pin this to
|
|
98
|
+
// the exported constant.
|
|
68
99
|
protocolVersion: zod.z.string().min(1),
|
|
69
100
|
capabilities: zod.z.array(CapabilitySchema).min(1).refine(
|
|
70
101
|
(caps) => new Set(caps.map((c) => c.type)).size === caps.length,
|
|
@@ -178,7 +209,7 @@ zod.z.discriminatedUnion("type", [
|
|
|
178
209
|
]);
|
|
179
210
|
|
|
180
211
|
// ../neuron-protocol/src/transport.ts
|
|
181
|
-
var PROTOCOL_VERSION = "1.
|
|
212
|
+
var PROTOCOL_VERSION = "1.1.0";
|
|
182
213
|
var SSE_PATH = "/neuron/events";
|
|
183
214
|
var SSE_EVENT = {
|
|
184
215
|
registered: "registered",
|
|
@@ -233,6 +264,12 @@ function toRegistrationCapability(def, opts) {
|
|
|
233
264
|
type: def.type,
|
|
234
265
|
scope,
|
|
235
266
|
concurrency,
|
|
267
|
+
// Contract advertisement (HOL-2926). `description` is required on the
|
|
268
|
+
// def, so it is always present here; `opts.description` lets a
|
|
269
|
+
// deployment reword it without redefining the capability.
|
|
270
|
+
description: opts?.description ?? def.description,
|
|
271
|
+
inputSchema: def.inputSchema,
|
|
272
|
+
outputSchema: def.outputSchema,
|
|
236
273
|
...typeof leaseTtlMs === "number" ? { leaseTtlMs } : {}
|
|
237
274
|
};
|
|
238
275
|
}
|
|
@@ -252,7 +289,6 @@ var HeartbeatLoop = class {
|
|
|
252
289
|
this.timer = setInterval(() => {
|
|
253
290
|
void this.tick();
|
|
254
291
|
}, this.intervalMs);
|
|
255
|
-
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
256
292
|
}
|
|
257
293
|
stop() {
|
|
258
294
|
if (this.timer) {
|
|
@@ -503,9 +539,6 @@ var HttpTransport = class {
|
|
|
503
539
|
this.reconnectTimer = null;
|
|
504
540
|
void this.openStream();
|
|
505
541
|
}, delay);
|
|
506
|
-
if (typeof this.reconnectTimer === "object" && this.reconnectTimer !== null && "unref" in this.reconnectTimer && typeof this.reconnectTimer.unref === "function") {
|
|
507
|
-
this.reconnectTimer.unref();
|
|
508
|
-
}
|
|
509
542
|
}
|
|
510
543
|
};
|
|
511
544
|
var TransportError = class extends Error {
|
|
@@ -537,8 +570,19 @@ function validateInput(zod, validator, jsonSchema, data) {
|
|
|
537
570
|
return errors === null ? { ok: true, value: data } : { ok: false, errors };
|
|
538
571
|
}
|
|
539
572
|
async function runLease(opts) {
|
|
540
|
-
const {
|
|
541
|
-
|
|
573
|
+
const {
|
|
574
|
+
lease,
|
|
575
|
+
handler,
|
|
576
|
+
zodInput,
|
|
577
|
+
zodOutput,
|
|
578
|
+
inputSchema,
|
|
579
|
+
outputSchema,
|
|
580
|
+
transport,
|
|
581
|
+
validator,
|
|
582
|
+
logger,
|
|
583
|
+
signal
|
|
584
|
+
} = opts;
|
|
585
|
+
const inputCheck = validateInput(zodInput, validator, inputSchema, lease.task.input);
|
|
542
586
|
if (!inputCheck.ok) {
|
|
543
587
|
await sendNack(
|
|
544
588
|
transport,
|
|
@@ -600,7 +644,7 @@ async function runLease(opts) {
|
|
|
600
644
|
);
|
|
601
645
|
return;
|
|
602
646
|
}
|
|
603
|
-
const outputCheck = validateInput(zodOutput, validator,
|
|
647
|
+
const outputCheck = validateInput(zodOutput, validator, outputSchema, output);
|
|
604
648
|
if (!outputCheck.ok) {
|
|
605
649
|
await sendNack(
|
|
606
650
|
transport,
|
|
@@ -667,32 +711,39 @@ function scopedLogger(logger, lease) {
|
|
|
667
711
|
var Ajv = AjvDefault__default.default.default ?? AjvDefault__default.default;
|
|
668
712
|
var addFormats = addFormatsDefault__default.default.default ?? addFormatsDefault__default.default;
|
|
669
713
|
var SchemaValidator = class {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
constructor() {
|
|
714
|
+
constructor(onCompileError) {
|
|
715
|
+
this.onCompileError = onCompileError;
|
|
673
716
|
this.ajv = new Ajv({ allErrors: true, strict: false });
|
|
674
717
|
addFormats(this.ajv);
|
|
675
718
|
}
|
|
719
|
+
ajv;
|
|
720
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
721
|
+
// Schemas whose compilation threw. Cannot-compile is an environment or
|
|
722
|
+
// schema-authoring problem, not evidence about the data — so validate()
|
|
723
|
+
// FAILS OPEN for these (returns null) instead of reporting a mismatch.
|
|
724
|
+
// Nacking on compile failure turned every lease into an
|
|
725
|
+
// INPUT_SCHEMA_MISMATCH retry loop on runtimes where ajv's codegen is
|
|
726
|
+
// unavailable (Electron renderers with CSP `script-src 'self'` block
|
|
727
|
+
// `new Function`) — HOL-2937.
|
|
728
|
+
uncompilable = /* @__PURE__ */ new WeakSet();
|
|
676
729
|
/**
|
|
677
|
-
* Returns null if the value matches the schema
|
|
678
|
-
*
|
|
730
|
+
* Returns null if the value matches the schema, no schema was given, or
|
|
731
|
+
* the schema cannot be compiled in this runtime (fail-open — the gateway
|
|
732
|
+
* remains the enforcement point); otherwise an array of structured errors.
|
|
679
733
|
*/
|
|
680
734
|
validate(schema, data) {
|
|
681
735
|
if (!schema || typeof schema !== "object") return null;
|
|
736
|
+
if (this.uncompilable.has(schema)) return null;
|
|
682
737
|
let validator = this.cache.get(schema);
|
|
683
738
|
if (!validator) {
|
|
684
739
|
try {
|
|
685
740
|
validator = this.ajv.compile(schema);
|
|
686
741
|
} catch (err) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
params: {},
|
|
693
|
-
message: `schema failed to compile: ${err instanceof Error ? err.message : String(err)}`
|
|
694
|
-
}
|
|
695
|
-
];
|
|
742
|
+
this.uncompilable.add(schema);
|
|
743
|
+
this.onCompileError?.(
|
|
744
|
+
`schema failed to compile \u2014 skipping SDK-side validation for this schema: ${err instanceof Error ? err.message : String(err)}`
|
|
745
|
+
);
|
|
746
|
+
return null;
|
|
696
747
|
}
|
|
697
748
|
this.cache.set(schema, validator);
|
|
698
749
|
}
|
|
@@ -825,7 +876,9 @@ var Neuron = class extends eventemitter3.EventEmitter {
|
|
|
825
876
|
super();
|
|
826
877
|
this.logger = opts.logger ?? noopLogger;
|
|
827
878
|
this.neuronId = opts.neuronId;
|
|
828
|
-
this.validator = new SchemaValidator(
|
|
879
|
+
this.validator = new SchemaValidator(
|
|
880
|
+
(message) => this.logger.warn({ neuronId: this.neuronId }, message)
|
|
881
|
+
);
|
|
829
882
|
this.transport = isWithTransport(opts) ? opts.transport : new HttpTransport({
|
|
830
883
|
gatewayUrl: opts.gatewayUrl,
|
|
831
884
|
neuronId: opts.neuronId,
|
|
@@ -897,7 +950,15 @@ var Neuron = class extends eventemitter3.EventEmitter {
|
|
|
897
950
|
);
|
|
898
951
|
}
|
|
899
952
|
handler = optsOrHandler;
|
|
900
|
-
capability = {
|
|
953
|
+
capability = {
|
|
954
|
+
type: reg.type,
|
|
955
|
+
scope: reg.scope,
|
|
956
|
+
concurrency: reg.concurrency,
|
|
957
|
+
...reg.description !== void 0 ? { description: reg.description } : {},
|
|
958
|
+
...reg.inputSchema !== void 0 ? { inputSchema: reg.inputSchema } : {},
|
|
959
|
+
...reg.outputSchema !== void 0 ? { outputSchema: reg.outputSchema } : {},
|
|
960
|
+
...typeof reg.leaseTtlMs === "number" ? { leaseTtlMs: reg.leaseTtlMs } : {}
|
|
961
|
+
};
|
|
901
962
|
inputSchema = reg.inputSchema;
|
|
902
963
|
outputSchema = reg.outputSchema;
|
|
903
964
|
}
|
|
@@ -1008,7 +1069,15 @@ var Neuron = class extends eventemitter3.EventEmitter {
|
|
|
1008
1069
|
this.hasOpenedOnce = true;
|
|
1009
1070
|
this.heartbeat.start();
|
|
1010
1071
|
} catch (err) {
|
|
1011
|
-
|
|
1072
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
1073
|
+
this.logger.error(
|
|
1074
|
+
{ err: cause },
|
|
1075
|
+
"register POST rejected; SSE is open but the neuron is NOT registered and will receive no leases"
|
|
1076
|
+
);
|
|
1077
|
+
this.emit(
|
|
1078
|
+
"error",
|
|
1079
|
+
new Error(`registration rejected by gateway: ${cause.message}`, { cause })
|
|
1080
|
+
);
|
|
1012
1081
|
}
|
|
1013
1082
|
}
|
|
1014
1083
|
handleConnectionClose(reason) {
|
|
@@ -1085,6 +1154,8 @@ var Neuron = class extends eventemitter3.EventEmitter {
|
|
|
1085
1154
|
handler: handler.handler,
|
|
1086
1155
|
zodInput: handler.zodInput,
|
|
1087
1156
|
zodOutput: handler.zodOutput,
|
|
1157
|
+
inputSchema: handler.inputSchema,
|
|
1158
|
+
outputSchema: handler.outputSchema,
|
|
1088
1159
|
transport: this.transport,
|
|
1089
1160
|
validator: this.validator,
|
|
1090
1161
|
logger: this.logger,
|