@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.
- package/README.md +39 -1
- package/native/dendrite-bridge-manifest.json +5 -4
- package/native/dendrite-bridge.json +1 -1
- package/native/include/axon_dendrite_bridge.h +18 -4
- package/native/libaxon_dendrite_bridge.so +0 -0
- package/package.json +9 -5
- package/runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz +0 -0
- package/runtime/runtime-bridge-manifest.json +4 -4
- package/runtime/runtime-bridge.json +3 -3
- package/src/ability_lifecycle.d.ts +12 -1
- package/src/ability_lifecycle.js +117 -31
- package/src/capability_request.js +3 -1
- package/src/dendrite_bridge/bridge.d.ts +10 -2
- package/src/dendrite_bridge/bridge.js +75 -14
- package/src/dendrite_bridge/ffi.d.ts +4 -0
- package/src/dendrite_bridge/ffi.js +194 -18
- package/src/dendrite_bridge/types.d.ts +4 -0
- package/src/errors.js +9 -3
- package/src/index.d.ts +3 -3
- package/src/index.js +9 -10
- package/src/mcp/server.d.ts +24 -2
- package/src/mcp/server.js +218 -18
- package/src/mcp/server.test.js +100 -0
- package/src/presets/ability_dispatch/workflow.js +8 -30
- package/src/presets/remote_control/config.d.ts +3 -0
- package/src/presets/remote_control/config.js +22 -24
- package/src/presets/remote_control/descriptor.d.ts +36 -0
- package/src/presets/remote_control/descriptor.js +267 -11
- package/src/presets/remote_control/handlers.d.ts +8 -0
- package/src/presets/remote_control/handlers.js +230 -26
- package/src/presets/remote_control/kit.d.ts +4 -2
- package/src/presets/remote_control/kit.js +106 -1
- package/src/presets/remote_control/kit.test.js +994 -0
- package/src/presets/remote_control/orchestrator.d.ts +6 -0
- package/src/presets/remote_control/orchestrator.js +36 -1
- package/src/presets/remote_control/specs.js +217 -61
- package/src/receipt.js +6 -3
- 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
|
|
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
|
|
9
|
-
// - Preserves
|
|
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
|
|
13
|
-
// -
|
|
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
|
-
// -
|
|
17
|
-
// -
|
|
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
|
-
// -
|
|
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 =
|
|
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
|
|
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
|
|
9
|
-
// -
|
|
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
|
-
// -
|
|
13
|
-
// -
|
|
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
|
-
// -
|
|
17
|
-
// -
|
|
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
|
-
// -
|
|
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
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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 {
|
|
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:
|
|
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
|
-
// -
|
|
9
|
-
// -
|
|
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
|
-
// -
|
|
13
|
-
// -
|
|
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
|
-
// -
|
|
17
|
-
// -
|
|
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
|
-
// -
|
|
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 {
|
|
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";
|
package/src/mcp/server.d.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
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 {};
|