@easynet-run/node 0.27.14 → 0.39.29

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.
Files changed (38) hide show
  1. package/README.md +39 -1
  2. package/native/dendrite-bridge-manifest.json +5 -4
  3. package/native/dendrite-bridge.json +1 -1
  4. package/native/include/axon_dendrite_bridge.h +18 -4
  5. package/native/libaxon_dendrite_bridge.so +0 -0
  6. package/package.json +9 -5
  7. package/runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz +0 -0
  8. package/runtime/runtime-bridge-manifest.json +4 -4
  9. package/runtime/runtime-bridge.json +3 -3
  10. package/src/ability_lifecycle.d.ts +12 -1
  11. package/src/ability_lifecycle.js +117 -31
  12. package/src/capability_request.js +3 -1
  13. package/src/dendrite_bridge/bridge.d.ts +10 -2
  14. package/src/dendrite_bridge/bridge.js +75 -14
  15. package/src/dendrite_bridge/ffi.d.ts +4 -0
  16. package/src/dendrite_bridge/ffi.js +194 -18
  17. package/src/dendrite_bridge/types.d.ts +4 -0
  18. package/src/errors.js +9 -3
  19. package/src/index.d.ts +3 -3
  20. package/src/index.js +9 -10
  21. package/src/mcp/server.d.ts +24 -2
  22. package/src/mcp/server.js +218 -18
  23. package/src/mcp/server.test.js +100 -0
  24. package/src/presets/ability_dispatch/workflow.js +8 -30
  25. package/src/presets/remote_control/config.d.ts +3 -0
  26. package/src/presets/remote_control/config.js +22 -24
  27. package/src/presets/remote_control/descriptor.d.ts +36 -0
  28. package/src/presets/remote_control/descriptor.js +267 -11
  29. package/src/presets/remote_control/handlers.d.ts +8 -0
  30. package/src/presets/remote_control/handlers.js +230 -26
  31. package/src/presets/remote_control/kit.d.ts +4 -2
  32. package/src/presets/remote_control/kit.js +106 -1
  33. package/src/presets/remote_control/kit.test.js +994 -0
  34. package/src/presets/remote_control/orchestrator.d.ts +6 -0
  35. package/src/presets/remote_control/orchestrator.js +36 -1
  36. package/src/presets/remote_control/specs.js +217 -61
  37. package/src/receipt.js +6 -3
  38. package/runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz +0 -0
@@ -2,28 +2,27 @@
2
2
  // =========================
3
3
  //
4
4
  // File: sdk/node/src/dendrite_bridge/bridge.ts
5
- // Description: DendriteBridge class implementation; provides the high-level API surface for interacting with the native dendrite bridge over FFI/JSON.
5
+ // Description: Node DendriteBridge implementation that exposes typed protocol methods over the native Axon bridge.
6
6
  //
7
7
  // Protocol Responsibility:
8
- // - Implements the DendriteBridge class with all RPC, capability, voice, and transport methods.
9
- // - Preserves stable request/response semantics and error mapping.
8
+ // - Implements the high-level DendriteBridge API for Node callers across RPC, capability, interop, and voice surfaces.
9
+ // - Preserves JSON request and error semantics expected by the Node SDK public API.
10
10
  //
11
11
  // Implementation Approach:
12
- // - Delegates FFI calls to the ffi submodule.
13
- // - Delegates validation/payload building to capability_request helpers.
12
+ // - Delegates native symbol loading to the FFI layer while keeping method-level payload shaping near the class boundary.
13
+ // - Uses typed options and explicit method families so transport intent remains readable at call sites.
14
14
  //
15
15
  // Usage Contract:
16
- // - Callers construct a DendriteBridge with endpoint options and invoke methods for each protocol surface.
17
- // - Errors are surfaced as DendriteError instances.
16
+ // - Construct bridge instances with valid endpoint or library configuration before invoking protocol methods.
17
+ // - Callers should prefer this layer over direct FFI usage unless they are extending native bindings.
18
18
  //
19
19
  // Architectural Position:
20
- // - Part of the Node SDK dendrite_bridge submodule layer.
21
- // - Should not embed FFI loading or type declarations outside this class's responsibility.
20
+ // - Node SDK bridge facade above FFI bindings and below application or preset code.
22
21
  //
23
22
  // Author: Silan.Hu
24
23
  // Email: silan.hu@u.nus.edu
25
24
  // Copyright (c) 2026-2027 easynet. All rights reserved.
26
- import { callJson, loadDendriteLib, resolveLibraryPath, requireBridgeFn, DendriteError, DEFAULT_TIMEOUT_MS, DEFAULT_CONNECT_TIMEOUT_MS, } from "./ffi.js";
25
+ import { callJson, loadDendriteLib, resolveLibraryPath, requireBridgeFn, DendriteError, DEFAULT_TIMEOUT_MS, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS, } from "./ffi.js";
27
26
  const STREAMING_UNSUPPORTED = "incremental streaming not supported by loaded native library";
28
27
  import { validatePublishCapabilityRequest, buildPublishCapabilityPayload, validateInstallCapabilityRequest, buildInstallCapabilityPayload, validateDeployMcpListDirRequest, buildDeployMcpListDirPayload, validateUpdateMcpListDirRequest, buildUpdateMcpListDirPayload, buildUninstallCapabilityPayload, } from "../capability_request.js";
29
28
  export class DendriteBridge {
@@ -202,6 +201,19 @@ export class DendriteBridge {
202
201
  });
203
202
  return callJson(this.lib.axon_dendrite_deregister_node_json, this.handle, req);
204
203
  }
204
+ drainNode(tenantId, nodeId, reason = "") {
205
+ if (this.handle <= 0n)
206
+ throw new DendriteError("dendrite bridge already closed");
207
+ if (!this.lib.axon_dendrite_drain_node_json) {
208
+ throw new DendriteError("dendrite bridge symbol not available: axon_dendrite_drain_node_json");
209
+ }
210
+ const req = JSON.stringify({
211
+ tenant_id: tenantId,
212
+ node_id: nodeId,
213
+ reason,
214
+ });
215
+ return callJson(this.lib.axon_dendrite_drain_node_json, this.handle, req);
216
+ }
205
217
  heartbeat(tenantId, nodeId) {
206
218
  if (this.handle <= 0n)
207
219
  throw new DendriteError("dendrite bridge already closed");
@@ -304,6 +316,37 @@ export class DendriteBridge {
304
316
  });
305
317
  return callJson(this.lib.axon_dendrite_call_mcp_tool_json, this.handle, req);
306
318
  }
319
+ /**
320
+ * Open a streaming MCP tool call that returns incremental response chunks.
321
+ * Requires native library support for streaming symbols.
322
+ * Uses DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS (60s) per chunk if no timeout specified.
323
+ */
324
+ callMcpToolStream(tenantId, toolName, options = {}) {
325
+ if (this.handle <= 0n)
326
+ throw new DendriteError("dendrite bridge already closed");
327
+ const streamOpen = requireBridgeFn(this.lib.axon_dendrite_call_mcp_tool_stream_open_json, STREAMING_UNSUPPORTED);
328
+ requireBridgeFn(this.lib.axon_dendrite_stream_next_json, STREAMING_UNSUPPORTED);
329
+ const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0
330
+ ? options.timeoutMs
331
+ : DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS;
332
+ const req = JSON.stringify({
333
+ tenant_id: tenantId,
334
+ tool_name: toolName,
335
+ target_node_id: options.targetNodeId ?? "",
336
+ arguments_json: options.argumentsJson ?? {},
337
+ arguments_base64: options.argumentsBase64,
338
+ timeout_ms: timeoutMs,
339
+ });
340
+ const resp = callJson(streamOpen, this.handle, req);
341
+ if (resp.stream_handle == null || resp.stream_handle === "") {
342
+ throw new DendriteError(`failed to open mcp tool stream: missing stream_handle in ${JSON.stringify(resp)}`);
343
+ }
344
+ const streamHandle = BigInt(resp.stream_handle);
345
+ if (streamHandle <= 0n) {
346
+ throw new DendriteError(`failed to open mcp tool stream: ${JSON.stringify(resp)}`);
347
+ }
348
+ return new DendriteServerStream(this.lib, streamHandle, timeoutMs);
349
+ }
307
350
  uninstallCapability(tenantId, nodeId, installId, options = {}) {
308
351
  if (this.handle <= 0n)
309
352
  throw new DendriteError("dendrite bridge already closed");
@@ -596,16 +639,20 @@ export class DendriteBridge {
596
639
  return new DendriteBidiStream(this.lib, streamHandle);
597
640
  }
598
641
  }
642
+ /** Maximum consecutive timeout results before aborting iteration (matches Python/Go/Java/Swift). */
643
+ const MAX_CONSECUTIVE_TIMEOUT_RETRIES = 3;
599
644
  export class DendriteServerStream {
600
645
  lib;
601
646
  streamHandle;
647
+ defaultChunkTimeoutMs;
602
648
  done = false;
603
649
  nativeClosed = false;
604
- constructor(lib, streamHandle) {
650
+ constructor(lib, streamHandle, defaultChunkTimeoutMs = DEFAULT_TIMEOUT_MS) {
605
651
  this.lib = lib;
606
652
  this.streamHandle = streamHandle;
653
+ this.defaultChunkTimeoutMs = defaultChunkTimeoutMs > 0 ? defaultChunkTimeoutMs : DEFAULT_TIMEOUT_MS;
607
654
  }
608
- next(timeoutMs = DEFAULT_TIMEOUT_MS) {
655
+ next(timeoutMs = this.defaultChunkTimeoutMs) {
609
656
  if (this.done)
610
657
  return { chunk: null, done: true };
611
658
  const streamNext = requireBridgeFn(this.lib.axon_dendrite_stream_next_json, STREAMING_UNSUPPORTED);
@@ -630,13 +677,20 @@ export class DendriteServerStream {
630
677
  }
631
678
  }
632
679
  async *[Symbol.asyncIterator]() {
680
+ let consecutiveTimeouts = 0;
633
681
  try {
634
682
  while (true) {
635
683
  const { chunk, done, timeout } = this.next();
636
684
  if (done)
637
685
  break;
638
- if (timeout)
686
+ if (timeout) {
687
+ consecutiveTimeouts++;
688
+ if (consecutiveTimeouts >= MAX_CONSECUTIVE_TIMEOUT_RETRIES) {
689
+ throw new DendriteError(`stream timed out after ${consecutiveTimeouts} consecutive retries`);
690
+ }
639
691
  continue;
692
+ }
693
+ consecutiveTimeouts = 0;
640
694
  if (chunk)
641
695
  yield chunk;
642
696
  }
@@ -694,13 +748,20 @@ export class DendriteBidiStream {
694
748
  }
695
749
  }
696
750
  async *[Symbol.asyncIterator]() {
751
+ let consecutiveTimeouts = 0;
697
752
  try {
698
753
  while (true) {
699
754
  const { chunk, done, timeout } = this.recv();
700
755
  if (done)
701
756
  break;
702
- if (timeout)
757
+ if (timeout) {
758
+ consecutiveTimeouts++;
759
+ if (consecutiveTimeouts >= MAX_CONSECUTIVE_TIMEOUT_RETRIES) {
760
+ throw new DendriteError(`stream timed out after ${consecutiveTimeouts} consecutive retries`);
761
+ }
703
762
  continue;
763
+ }
764
+ consecutiveTimeouts = 0;
704
765
  if (chunk)
705
766
  yield chunk;
706
767
  }
@@ -1,6 +1,7 @@
1
1
  import { DendriteError } from "../capability_request.js";
2
2
  export declare const DEFAULT_TIMEOUT_MS = 30000;
3
3
  export declare const DEFAULT_CONNECT_TIMEOUT_MS = 5000;
4
+ export declare const DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS = 60000;
4
5
  interface DendriteBridgeJson {
5
6
  ok: boolean;
6
7
  [key: string]: unknown;
@@ -20,6 +21,7 @@ export type DendriteBridgeLib = {
20
21
  axon_dendrite_list_nodes_json: DendriteJsonFn<[bigint, string]>;
21
22
  axon_dendrite_register_node_json: DendriteJsonFn<[bigint, string]>;
22
23
  axon_dendrite_deregister_node_json?: DendriteJsonFn<[bigint, string]>;
24
+ axon_dendrite_drain_node_json?: DendriteJsonFn<[bigint, string]>;
23
25
  axon_dendrite_heartbeat_json: DendriteJsonFn<[bigint, string]>;
24
26
  axon_dendrite_publish_capability_json: DendriteJsonFn<[bigint, string]>;
25
27
  axon_dendrite_install_capability_json: DendriteJsonFn<[bigint, string]>;
@@ -30,6 +32,8 @@ export type DendriteBridgeLib = {
30
32
  axon_dendrite_deploy_mcp_list_dir_json: DendriteJsonFn<[bigint, string]>;
31
33
  axon_dendrite_list_mcp_tools_json: DendriteJsonFn<[bigint, string]>;
32
34
  axon_dendrite_call_mcp_tool_json: DendriteJsonFn<[bigint, string]>;
35
+ /** FFI binding to open a streaming MCP tool call. Returns a stream_handle. Optional — unavailable in older native libraries. */
36
+ axon_dendrite_call_mcp_tool_stream_open_json?: DendriteJsonFn<[bigint, string]>;
33
37
  axon_dendrite_uninstall_capability_json: DendriteJsonFn<[bigint, string]>;
34
38
  axon_dendrite_update_mcp_list_dir_json: DendriteJsonFn<[bigint, string]>;
35
39
  axon_dendrite_voice_create_call_json: DendriteJsonFn<[bigint, string]>;
@@ -2,29 +2,28 @@
2
2
  // =========================
3
3
  //
4
4
  // File: sdk/node/src/dendrite_bridge/ffi.ts
5
- // Description: FFI loading and JSON calling layer for the Dendrite native bridge; resolves the shared library, binds koffi function pointers, and provides the callJson helper.
5
+ // Description: Node FFI loader and raw JSON-call bindings for the native Axon dendrite bridge.
6
6
  //
7
7
  // Protocol Responsibility:
8
- // - Loads the native dendrite bridge shared library and exposes typed function bindings.
9
- // - Provides the low-level callJson helper that handles JSON serialization/error mapping.
8
+ // - Loads the native dendrite shared library and exposes the raw JSON-call symbol set to Node code.
9
+ // - Normalizes low-level FFI failures into stable Node-side error contracts.
10
10
  //
11
11
  // Implementation Approach:
12
- // - Uses koffi for native FFI binding.
13
- // - Keeps library resolution and error handling explicit.
12
+ // - Keeps library path resolution, symbol binding, and JSON marshalling isolated from higher-level SDK classes.
13
+ // - Defines timeout and transport constants close to the native boundary for consistent reuse.
14
14
  //
15
15
  // Usage Contract:
16
- // - Callers should use loadDendriteLib to obtain function bindings, then callJson to invoke them.
17
- // - Errors are surfaced as DendriteError instances.
16
+ // - Treat this module as the lowest-level Node native binding layer and avoid embedding application logic here.
17
+ // - Regenerate or update bindings here before adding new native bridge capabilities to higher layers.
18
18
  //
19
19
  // Architectural Position:
20
- // - Part of the Node SDK dendrite_bridge submodule layer.
21
- // - Should not embed class or interface logic outside FFI concerns.
20
+ // - Native binding boundary underneath the Node SDK bridge facade.
22
21
  //
23
22
  // Author: Silan.Hu
24
23
  // Email: silan.hu@u.nus.edu
25
24
  // Copyright (c) 2026-2027 easynet. All rights reserved.
26
25
  import { createRequire } from "node:module";
27
- import { existsSync } from "node:fs";
26
+ import { existsSync, readFileSync } from "node:fs";
28
27
  import { dirname, resolve } from "node:path";
29
28
  import { fileURLToPath } from "node:url";
30
29
  import { DendriteError } from "../capability_request.js";
@@ -32,23 +31,198 @@ const require = createRequire(import.meta.url);
32
31
  const koffi = require("koffi");
33
32
  export const DEFAULT_TIMEOUT_MS = 30000;
34
33
  export const DEFAULT_CONNECT_TIMEOUT_MS = 5000;
34
+ export const DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS = 60_000;
35
35
  export { DendriteError };
36
+ function bridgeDebugLog(tried) {
37
+ if ((process.env.EASYNET_DENDRITE_BRIDGE_DEBUG || "").trim() === "1") {
38
+ process.stderr.write(`[dendrite-bridge] tried ${tried.length} candidate(s):\n`);
39
+ for (const p of tried) {
40
+ process.stderr.write(` - ${p}\n`);
41
+ }
42
+ }
43
+ }
36
44
  export function resolveLibraryPath(explicitPath) {
37
45
  if (explicitPath)
38
46
  return explicitPath;
39
47
  if (process.env.EASYNET_DENDRITE_BRIDGE_LIB)
40
48
  return process.env.EASYNET_DENDRITE_BRIDGE_LIB;
41
- const here = dirname(fileURLToPath(import.meta.url));
42
- const candidates = [
43
- resolve(here, "../../native/libaxon_dendrite_bridge.dylib"),
44
- resolve(here, "../../native/libaxon_dendrite_bridge.so"),
45
- resolve(here, "../../native/axon_dendrite_bridge.dll"),
46
- ];
47
- for (const c of candidates) {
49
+ const names = libraryFileNames();
50
+ const versions = sdkVersionCandidates();
51
+ const tried = [];
52
+ for (const c of homeLibraryCandidates(names, versions)) {
53
+ tried.push(c);
54
+ if (existsSync(c))
55
+ return c;
56
+ }
57
+ if (useLocalSource()) {
58
+ for (const c of sourceLibraryCandidates(names, versions)) {
59
+ tried.push(c);
60
+ if (existsSync(c))
61
+ return c;
62
+ }
63
+ }
64
+ for (const c of packageLibraryCandidates(names, versions)) {
65
+ tried.push(c);
48
66
  if (existsSync(c))
49
67
  return c;
50
68
  }
51
- throw new DendriteError("dendrite bridge library not found; set EASYNET_DENDRITE_BRIDGE_LIB or ensure package contains native/libaxon_dendrite_bridge.*");
69
+ bridgeDebugLog(tried);
70
+ throw new DendriteError("dendrite bridge library not found; set EASYNET_DENDRITE_BRIDGE_LIB or EASYNET_DENDRITE_BRIDGE_HOME — run with EASYNET_DENDRITE_BRIDGE_DEBUG=1 for candidate details");
71
+ }
72
+ function dedupeCandidates(values) {
73
+ const seen = new Set();
74
+ const out = [];
75
+ for (const value of values) {
76
+ if (seen.has(value)) {
77
+ continue;
78
+ }
79
+ seen.add(value);
80
+ out.push(value);
81
+ }
82
+ return out;
83
+ }
84
+ function libraryFileNames() {
85
+ const rawHint = (process.env.EASYNET_DENDRITE_BRIDGE_PLATFORM || "").trim().toLowerCase();
86
+ if (rawHint === "ios") {
87
+ return [
88
+ "libaxon_dendrite_bridge.dylib",
89
+ "libaxon_dendrite_bridge.so",
90
+ "axon_dendrite_bridge.dll",
91
+ ];
92
+ }
93
+ if (["android", "linux", "linux-gnu"].includes(rawHint)) {
94
+ return [
95
+ "libaxon_dendrite_bridge.so",
96
+ "libaxon_dendrite_bridge.dylib",
97
+ "axon_dendrite_bridge.dll",
98
+ ];
99
+ }
100
+ if (["macos", "darwin", "mac"].includes(rawHint)) {
101
+ return [
102
+ "libaxon_dendrite_bridge.dylib",
103
+ "libaxon_dendrite_bridge.so",
104
+ "axon_dendrite_bridge.dll",
105
+ ];
106
+ }
107
+ if (["windows", "win", "win32", "win64"].includes(rawHint)) {
108
+ return [
109
+ "axon_dendrite_bridge.dll",
110
+ "libaxon_dendrite_bridge.dylib",
111
+ "libaxon_dendrite_bridge.so",
112
+ ];
113
+ }
114
+ const primary = process.platform === "win32"
115
+ ? "axon_dendrite_bridge.dll"
116
+ : process.platform === "darwin"
117
+ ? "libaxon_dendrite_bridge.dylib"
118
+ : "libaxon_dendrite_bridge.so";
119
+ const all = [
120
+ "libaxon_dendrite_bridge.dylib",
121
+ "libaxon_dendrite_bridge.so",
122
+ "axon_dendrite_bridge.dll",
123
+ ];
124
+ return [primary, ...all.filter((n) => n !== primary)];
125
+ }
126
+ function projectRoot() {
127
+ let cursor = process.cwd();
128
+ for (let depth = 0; depth < 12; depth += 1) {
129
+ if (existsSync(resolve(cursor, "core/runtime-rs"))) {
130
+ return cursor;
131
+ }
132
+ const parent = dirname(cursor);
133
+ if (parent === cursor)
134
+ break;
135
+ cursor = parent;
136
+ }
137
+ return undefined;
138
+ }
139
+ function sdkVersionCandidates() {
140
+ const versions = [];
141
+ const envVersion = (process.env.SDK_VERSION || "").trim();
142
+ if (envVersion) {
143
+ versions.push(envVersion);
144
+ }
145
+ const root = projectRoot();
146
+ if (root) {
147
+ try {
148
+ const raw = readFileSync(resolve(root, "VERSION"), "utf8").trim();
149
+ if (raw) {
150
+ versions.push(raw.split("\n")[0].trim());
151
+ }
152
+ }
153
+ catch {
154
+ // no-op
155
+ }
156
+ }
157
+ if (versions.length === 0) {
158
+ if ((process.env.EASYNET_DENDRITE_BRIDGE_DEBUG || "").trim() === "1") {
159
+ console.error("warning: no SDK version detected, falling back to 0.1.0");
160
+ }
161
+ }
162
+ versions.push("0.1.0");
163
+ return dedupeCandidates(versions).filter(Boolean);
164
+ }
165
+ function addLibraryCandidates(targets, names, roots) {
166
+ for (const root of roots) {
167
+ for (const name of names) {
168
+ targets.push(resolve(root, name));
169
+ }
170
+ }
171
+ }
172
+ function homeLibraryCandidates(names, versions) {
173
+ const raw = (process.env.EASYNET_DENDRITE_BRIDGE_HOME || "").trim();
174
+ if (!raw)
175
+ return [];
176
+ const homeRoot = resolve(raw);
177
+ const candidateRoots = [
178
+ homeRoot,
179
+ resolve(homeRoot, "native"),
180
+ ...versions.map((version) => resolve(homeRoot, "dist", "sdk-packs", version, "native")),
181
+ ];
182
+ const candidates = [];
183
+ addLibraryCandidates(candidates, names, candidateRoots);
184
+ return dedupeCandidates(candidates);
185
+ }
186
+ function useLocalSource() {
187
+ const raw = (process.env.EASYNET_DENDRITE_BRIDGE_SOURCE || "").trim().toLowerCase();
188
+ return new Set(["1", "true", "yes", "on", "local", "source"]).has(raw);
189
+ }
190
+ function sourceLibraryCandidates(names, versions) {
191
+ const root = projectRoot();
192
+ if (!root)
193
+ return [];
194
+ const candidates = [];
195
+ // Versioned dist paths
196
+ for (const version of versions) {
197
+ addLibraryCandidates(candidates, names, [resolve(root, "dist", "sdk-packs", version, "native")]);
198
+ }
199
+ // Build artifacts
200
+ const bridgeTarget = resolve(root, "core/runtime-rs/dendrite-bridge/target");
201
+ const roots = [
202
+ resolve(bridgeTarget, "release"),
203
+ resolve(bridgeTarget, "debug"),
204
+ ];
205
+ const sdkTarget = (process.env.SDK_TARGET || "").trim();
206
+ if (sdkTarget) {
207
+ roots.push(resolve(bridgeTarget, sdkTarget, "release"));
208
+ roots.push(resolve(bridgeTarget, sdkTarget, "debug"));
209
+ }
210
+ addLibraryCandidates(candidates, names, roots);
211
+ return dedupeCandidates(candidates);
212
+ }
213
+ function packageLibraryCandidates(names, versions) {
214
+ const here = dirname(fileURLToPath(import.meta.url));
215
+ const pkgBase = resolve(here, "../..");
216
+ const roots = [
217
+ pkgBase,
218
+ resolve(pkgBase, "native"),
219
+ ];
220
+ for (const version of versions) {
221
+ roots.push(resolve(pkgBase, "dist", "sdk-packs", version, "native"));
222
+ }
223
+ const candidates = [];
224
+ addLibraryCandidates(candidates, names, roots);
225
+ return dedupeCandidates(candidates);
52
226
  }
53
227
  export function loadDendriteLib(libraryPath) {
54
228
  const lib = koffi.load(libraryPath);
@@ -78,6 +252,7 @@ export function loadDendriteLib(libraryPath) {
78
252
  axon_dendrite_list_nodes_json: lib.func("axon_dendrite_list_nodes_json", bridgeString, ["uint64_t", "str"]),
79
253
  axon_dendrite_register_node_json: lib.func("axon_dendrite_register_node_json", bridgeString, ["uint64_t", "str"]),
80
254
  axon_dendrite_deregister_node_json: optionalJsonFn("axon_dendrite_deregister_node_json", ["uint64_t", "str"]),
255
+ axon_dendrite_drain_node_json: optionalJsonFn("axon_dendrite_drain_node_json", ["uint64_t", "str"]),
81
256
  axon_dendrite_heartbeat_json: lib.func("axon_dendrite_heartbeat_json", bridgeString, ["uint64_t", "str"]),
82
257
  axon_dendrite_publish_capability_json: lib.func("axon_dendrite_publish_capability_json", bridgeString, ["uint64_t", "str"]),
83
258
  axon_dendrite_install_capability_json: lib.func("axon_dendrite_install_capability_json", bridgeString, ["uint64_t", "str"]),
@@ -88,6 +263,7 @@ export function loadDendriteLib(libraryPath) {
88
263
  axon_dendrite_deploy_mcp_list_dir_json: lib.func("axon_dendrite_deploy_mcp_list_dir_json", bridgeString, ["uint64_t", "str"]),
89
264
  axon_dendrite_list_mcp_tools_json: lib.func("axon_dendrite_list_mcp_tools_json", bridgeString, ["uint64_t", "str"]),
90
265
  axon_dendrite_call_mcp_tool_json: lib.func("axon_dendrite_call_mcp_tool_json", bridgeString, ["uint64_t", "str"]),
266
+ axon_dendrite_call_mcp_tool_stream_open_json: optionalJsonFn("axon_dendrite_call_mcp_tool_stream_open_json", ["uint64_t", "str"]),
91
267
  axon_dendrite_uninstall_capability_json: lib.func("axon_dendrite_uninstall_capability_json", bridgeString, ["uint64_t", "str"]),
92
268
  axon_dendrite_update_mcp_list_dir_json: lib.func("axon_dendrite_update_mcp_list_dir_json", bridgeString, ["uint64_t", "str"]),
93
269
  axon_dendrite_voice_create_call_json: lib.func("axon_dendrite_voice_create_call_json", bridgeString, ["uint64_t", "str"]),
@@ -84,6 +84,10 @@ export interface DendriteCallMcpToolOptions {
84
84
  argumentsJson?: unknown;
85
85
  argumentsBase64?: string;
86
86
  }
87
+ export interface DendriteCallMcpToolStreamOptions extends DendriteCallMcpToolOptions {
88
+ /** Per-chunk timeout in milliseconds for streaming reads. Default: 60,000 ms. */
89
+ timeoutMs?: number;
90
+ }
87
91
  export interface DendriteUninstallCapabilityOptions {
88
92
  deactivateFirst?: boolean;
89
93
  deactivateReason?: string;
package/src/errors.js CHANGED
@@ -1,4 +1,12 @@
1
- // sdk/node/src/errors.ts Canonical error taxonomy for the Axon SDK.
1
+ // EasyNet Axon for AgentNet
2
+ // =========================
3
+ //
4
+ // File: sdk/node/src/errors.ts
5
+ // Description: Canonical error taxonomy for the Axon SDK.
6
+ //
7
+ // Author: Silan.Hu
8
+ // Email: silan.hu@u.nus.edu
9
+ // Copyright (c) 2026-2027 easynet. All rights reserved.
2
10
  //
3
11
  // Terminology:
4
12
  // Ability — network-addressable governed execution contract
@@ -7,8 +15,6 @@
7
15
  //
8
16
  // Each error class exposes a `code` property for cross-SDK mapping.
9
17
  // See the Rust reference: sdk/rust/src/error.rs
10
- //
11
- // Copyright (c) 2026-2027 easynet. All rights reserved.
12
18
  /** Base error for all Axon SDK errors. */
13
19
  export class AxonError extends Error {
14
20
  /** Canonical error code for cross-SDK mapping. */
package/src/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { validateUninstallCapabilityRequest, buildUninstallCapabilityPayload, } from "./capability_request.js";
2
- export type { McpToolProvider, McpToolResult, } from "./mcp/server.js";
2
+ export type { McpToolProvider, McpToolResult, McpToolStreamHandle, } from "./mcp/server.js";
3
3
  export { StdioMcpServer } from "./mcp/server.js";
4
4
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
5
5
  [k: string]: JsonValue;
@@ -48,8 +48,8 @@ export declare class Client {
48
48
  callRaw(payload: JsonValue): Promise<Record<string, unknown>>;
49
49
  }
50
50
  export declare function client(transport?: Transport): Client;
51
- export { createAbility, exportAbility, deployToNode, listAbilities, invokeAbility, uninstallAbility, discoverNodes, executeCommand, forgetAll, disconnectDevice, drainDevice, listRemoteTools, buildDeployPackage, deployPackage, startServer, toToolSpec, type AbilityDescriptor, type AbilityTarget, type AbilityExportResult, type ServerHandle, type BridgeInterface, type ForgetAllResult, type ForgetAllFailure, type DeployResult, } from "./ability_lifecycle.js";
51
+ export { buildAbilityDescriptor, exportAbility, deployToNode, listAbilities, invokeAbility, uninstallAbility, discoverNodes, executeCommand, forgetAll, disconnectDevice, drainDevice, listRemoteTools, buildDeployPackage, deployPackage, startServer, toToolSpec, type AbilityDescriptor, type AbilityTarget, type AbilityExportResult, type ServerHandle, type BridgeInterface, type ForgetAllResult, type ForgetAllFailure, type DeployResult, } from "./ability_lifecycle.js";
52
52
  export { AxonError, AxonConfigError, AxonBridgeError, AxonNotInstalledError, AxonNotActivatedError, AxonInvocationError, AxonStreamError, AxonPolicyDeniedError, AxonMcpError, AxonPartialSuccessError, AxonJsonError, AxonIoError, AxonSymbolNotFoundError, } from "./errors.js";
53
53
  export { beginPhase, skippedReceipt, buildDeployTrace, phaseFromTrace, PhaseReceiptBuilder, type Phase, type PhaseStatus, type PhaseReceipt, type DeployTrace, } from "./receipt.js";
54
54
  export { AbilityToolAdapter, type ToolSpec as AdapterToolSpec, type OpenAITool, type OpenAIChatTool, type AnthropicTool, type ToolParameters, type LocalHandler, type RegisterOptions, type AbilityToolAdapterOptions, } from "./tool_adapter.js";
55
- export { DendriteBridge, DendriteServerStream, DendriteBidiStream, type DendriteBridgeOptions, DendriteError, type DendriteUnaryCallOptions, type DendriteAbilityCallOptions, type DendriteStreamCallOptions, type DendriteClientStreamCallOptions, type DendriteBidiStreamCallOptions, type DendriteListNodesOptions, type DendriteRegisterNodeOptions, type DendritePublishCapabilityOptions, type DendriteInstallCapabilityOptions, type DendriteListA2aAgentsOptions, type DendriteSendA2aTaskOptions, type DendriteDeployMcpListDirOptions, type DendriteListMcpToolsOptions, type DendriteCallMcpToolOptions, type DendriteUninstallCapabilityOptions, type DendriteUpdateMcpListDirOptions, type DendriteVoiceCodecProfileOptions, type DendriteVoiceMetricsOptions, type DendriteVoiceDescriptionOptions, type DendriteVoiceCandidateOptions, type DendriteVoiceCreateCallOptions, type DendriteVoiceJoinCallOptions, type DendriteVoiceUpdateMediaPathOptions, type DendriteVoiceEndCallOptions, type DendriteVoiceWatchOptions, type DendriteVoiceCreateTransportSessionOptions, type DendriteVoiceEndTransportSessionOptions, type DendriteProtocolInvokeOptions, } from "./dendrite_bridge.js";
55
+ export { DendriteBridge, DendriteServerStream, DendriteBidiStream, type DendriteBridgeOptions, DendriteError, type DendriteUnaryCallOptions, type DendriteAbilityCallOptions, type DendriteStreamCallOptions, type DendriteClientStreamCallOptions, type DendriteBidiStreamCallOptions, type DendriteListNodesOptions, type DendriteRegisterNodeOptions, type DendritePublishCapabilityOptions, type DendriteInstallCapabilityOptions, type DendriteListA2aAgentsOptions, type DendriteSendA2aTaskOptions, type DendriteDeployMcpListDirOptions, type DendriteListMcpToolsOptions, type DendriteCallMcpToolOptions, type DendriteCallMcpToolStreamOptions, type DendriteUninstallCapabilityOptions, type DendriteUpdateMcpListDirOptions, type DendriteVoiceCodecProfileOptions, type DendriteVoiceMetricsOptions, type DendriteVoiceDescriptionOptions, type DendriteVoiceCandidateOptions, type DendriteVoiceCreateCallOptions, type DendriteVoiceJoinCallOptions, type DendriteVoiceUpdateMediaPathOptions, type DendriteVoiceEndCallOptions, type DendriteVoiceWatchOptions, type DendriteVoiceCreateTransportSessionOptions, type DendriteVoiceEndTransportSessionOptions, type DendriteProtocolInvokeOptions, } from "./dendrite_bridge.js";
package/src/index.js CHANGED
@@ -2,23 +2,22 @@
2
2
  // =========================
3
3
  //
4
4
  // File: sdk/node/src/index.ts
5
- // Description: Source file for Node SDK facade and Dendrite integration; keeps behavior explicit and interoperable across language/runtime boundaries with tenant/principal fluent context.
5
+ // Description: Node SDK package-root facade for tenant-aware clients, sidecar transport, MCP helpers, and bridge exports.
6
6
  //
7
7
  // Protocol Responsibility:
8
- // - Implements Node SDK facade and Dendrite integration contracts required by current Axon service and SDK surfaces.
9
- // - Preserves stable request/response semantics and error mapping for index.ts call paths.
8
+ // - Defines the package-root SDK facade and re-export surface consumed by application code.
9
+ // - Keeps tenant-aware client, bridge, and tool APIs reachable through one stable import boundary.
10
10
  //
11
11
  // Implementation Approach:
12
- // - Uses small typed helpers and explicit control flow to avoid hidden side effects.
13
- // - Keeps protocol translation and transport details close to this module boundary.
12
+ // - Composes smaller modules rather than re-implementing transport or protocol logic at the package root.
13
+ // - Uses explicit exports so public surface changes remain auditable.
14
14
  //
15
15
  // Usage Contract:
16
- // - Callers should provide valid tenant/resource/runtime context before invoking exported APIs; principal context is optional and, when provided, is mapped to EasyNet subject context.
17
- // - Errors should be treated as typed protocol/runtime outcomes rather than silently ignored.
16
+ // - Prefer importing from this boundary when consuming the SDK from application code.
17
+ // - Public export changes here should be synchronized with language-specific packaging and documentation.
18
18
  //
19
19
  // Architectural Position:
20
- // - Part of the Node SDK facade and Dendrite integration layer.
21
- // - Should not embed unrelated orchestration logic outside this file's responsibility.
20
+ // - Top-level SDK package boundary for application-facing imports.
22
21
  //
23
22
  // Author: Silan.Hu
24
23
  // Email: silan.hu@u.nus.edu
@@ -157,7 +156,7 @@ export class Client {
157
156
  export function client(transport) {
158
157
  return new Client(transport);
159
158
  }
160
- export { createAbility, exportAbility, deployToNode, listAbilities, invokeAbility, uninstallAbility, discoverNodes, executeCommand, forgetAll, disconnectDevice, drainDevice, listRemoteTools, buildDeployPackage, deployPackage, startServer, toToolSpec, } from "./ability_lifecycle.js";
159
+ export { buildAbilityDescriptor, exportAbility, deployToNode, listAbilities, invokeAbility, uninstallAbility, discoverNodes, executeCommand, forgetAll, disconnectDevice, drainDevice, listRemoteTools, buildDeployPackage, deployPackage, startServer, toToolSpec, } from "./ability_lifecycle.js";
161
160
  export { AxonError, AxonConfigError, AxonBridgeError, AxonNotInstalledError, AxonNotActivatedError, AxonInvocationError, AxonStreamError, AxonPolicyDeniedError, AxonMcpError, AxonPartialSuccessError, AxonJsonError, AxonIoError, AxonSymbolNotFoundError, } from "./errors.js";
162
161
  export { beginPhase, skippedReceipt, buildDeployTrace, phaseFromTrace, PhaseReceiptBuilder, } from "./receipt.js";
163
162
  export { AbilityToolAdapter, } from "./tool_adapter.js";
@@ -2,9 +2,20 @@ export interface McpToolResult {
2
2
  payload: Record<string, unknown>;
3
3
  isError: boolean;
4
4
  }
5
+ /** Handle returned by streaming MCP tool providers. Consumers iterate `stream` and must call `close()` when done. */
6
+ export interface McpToolStreamHandle {
7
+ stream: AsyncIterable<Uint8Array>;
8
+ close(): void;
9
+ }
5
10
  export interface McpToolProvider {
6
11
  toolSpecs(): Array<Record<string, unknown>>;
7
12
  handleToolCall(name: string, args: Record<string, unknown>): McpToolResult | Promise<McpToolResult>;
13
+ /**
14
+ * Optional streaming handler. Return a `McpToolStreamHandle` to stream incremental chunks,
15
+ * or `null` to fall back to the unary `handleToolCall` path.
16
+ * When called via a non-streaming transport, the server buffers chunks into a single response.
17
+ */
18
+ handleToolCallStream?(name: string, args: Record<string, unknown>): McpToolStreamHandle | null;
8
19
  }
9
20
  interface McpTransportOptions {
10
21
  protocolVersion?: string;
@@ -14,16 +25,27 @@ interface McpTransportOptions {
14
25
  interface JsonMessage {
15
26
  [key: string]: unknown;
16
27
  }
28
+ /** Callback used by the MCP server to write JSON-RPC messages to the transport. */
29
+ export type McpWriteFn = (payload: JsonMessage) => Promise<void>;
17
30
  export declare class StdioMcpServer {
18
31
  private readonly provider;
19
32
  private readonly protocolVersion;
20
33
  private readonly serverName;
21
34
  private readonly serverVersion;
22
35
  private _closed;
36
+ private _pendingWrite;
23
37
  constructor(provider: McpToolProvider, options?: McpTransportOptions);
24
38
  close(): void;
25
39
  run(input: NodeJS.ReadableStream, output: NodeJS.WritableStream): void;
26
- handleRawLine(raw: string): JsonMessage | Promise<JsonMessage> | null;
27
- handleRequest(request: JsonMessage): JsonMessage | Promise<JsonMessage> | null;
40
+ private dispatchRequest;
41
+ private enqueueWrite;
42
+ handleRawLine(raw: string, writeFn?: McpWriteFn): JsonMessage | Promise<JsonMessage | null> | null;
43
+ handleRequest(request: JsonMessage, writeFn?: McpWriteFn): JsonMessage | Promise<JsonMessage | null> | null;
28
44
  }
45
+ /**
46
+ * Buffer all chunks from a streaming MCP tool handle into a single result.
47
+ * Stops accumulating after `maxBytes` (default 64 MiB) to prevent unbounded
48
+ * memory growth. Always closes the handle when done, even on error.
49
+ */
50
+ export declare function consumeStream(handle: McpToolStreamHandle, maxBytes?: number): Promise<McpToolResult>;
29
51
  export {};