@easynet-run/node 0.27.14
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 +135 -0
- package/native/dendrite-bridge-manifest.json +38 -0
- package/native/dendrite-bridge.json +15 -0
- package/native/include/axon_dendrite_bridge.h +460 -0
- package/native/libaxon_dendrite_bridge.so +0 -0
- package/package.json +67 -0
- package/runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz +0 -0
- package/runtime/runtime-bridge-manifest.json +20 -0
- package/runtime/runtime-bridge.json +9 -0
- package/src/ability_lifecycle.d.ts +140 -0
- package/src/ability_lifecycle.js +525 -0
- package/src/capability_request.d.ts +14 -0
- package/src/capability_request.js +247 -0
- package/src/dendrite_bridge/bridge.d.ts +98 -0
- package/src/dendrite_bridge/bridge.js +712 -0
- package/src/dendrite_bridge/ffi.d.ts +60 -0
- package/src/dendrite_bridge/ffi.js +139 -0
- package/src/dendrite_bridge/index.d.ts +3 -0
- package/src/dendrite_bridge/index.js +25 -0
- package/src/dendrite_bridge/types.d.ts +179 -0
- package/src/dendrite_bridge/types.js +23 -0
- package/src/dendrite_bridge.d.ts +1 -0
- package/src/dendrite_bridge.js +27 -0
- package/src/errors.d.ts +83 -0
- package/src/errors.js +146 -0
- package/src/index.d.ts +55 -0
- package/src/index.js +164 -0
- package/src/koffi.d.ts +34 -0
- package/src/mcp/server.d.ts +29 -0
- package/src/mcp/server.js +190 -0
- package/src/presets/ability_dispatch/args.d.ts +5 -0
- package/src/presets/ability_dispatch/args.js +36 -0
- package/src/presets/ability_dispatch/bundle.d.ts +7 -0
- package/src/presets/ability_dispatch/bundle.js +102 -0
- package/src/presets/ability_dispatch/media.d.ts +6 -0
- package/src/presets/ability_dispatch/media.js +48 -0
- package/src/presets/ability_dispatch/orchestrator.d.ts +21 -0
- package/src/presets/ability_dispatch/orchestrator.js +117 -0
- package/src/presets/ability_dispatch/workflow.d.ts +50 -0
- package/src/presets/ability_dispatch/workflow.js +333 -0
- package/src/presets/ability_dispatch.d.ts +1 -0
- package/src/presets/ability_dispatch.js +2 -0
- package/src/presets/remote_control/config.d.ts +16 -0
- package/src/presets/remote_control/config.js +63 -0
- package/src/presets/remote_control/descriptor.d.ts +34 -0
- package/src/presets/remote_control/descriptor.js +183 -0
- package/src/presets/remote_control/handlers.d.ts +12 -0
- package/src/presets/remote_control/handlers.js +279 -0
- package/src/presets/remote_control/kit.d.ts +22 -0
- package/src/presets/remote_control/kit.js +72 -0
- package/src/presets/remote_control/kit.test.js +87 -0
- package/src/presets/remote_control/orchestrator.d.ts +28 -0
- package/src/presets/remote_control/orchestrator.js +118 -0
- package/src/presets/remote_control/specs.d.ts +2 -0
- package/src/presets/remote_control/specs.js +152 -0
- package/src/presets/remote_control_case.d.ts +7 -0
- package/src/presets/remote_control_case.js +3 -0
- package/src/receipt.d.ts +46 -0
- package/src/receipt.js +98 -0
- package/src/tool_adapter.d.ts +90 -0
- package/src/tool_adapter.js +169 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
// EasyNet Axon for AgentNet
|
|
2
|
+
// =========================
|
|
3
|
+
//
|
|
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.
|
|
6
|
+
//
|
|
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.
|
|
10
|
+
//
|
|
11
|
+
// Implementation Approach:
|
|
12
|
+
// - Delegates FFI calls to the ffi submodule.
|
|
13
|
+
// - Delegates validation/payload building to capability_request helpers.
|
|
14
|
+
//
|
|
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.
|
|
18
|
+
//
|
|
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.
|
|
22
|
+
//
|
|
23
|
+
// Author: Silan.Hu
|
|
24
|
+
// Email: silan.hu@u.nus.edu
|
|
25
|
+
// 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";
|
|
27
|
+
const STREAMING_UNSUPPORTED = "incremental streaming not supported by loaded native library";
|
|
28
|
+
import { validatePublishCapabilityRequest, buildPublishCapabilityPayload, validateInstallCapabilityRequest, buildInstallCapabilityPayload, validateDeployMcpListDirRequest, buildDeployMcpListDirPayload, validateUpdateMcpListDirRequest, buildUpdateMcpListDirPayload, buildUninstallCapabilityPayload, } from "../capability_request.js";
|
|
29
|
+
export class DendriteBridge {
|
|
30
|
+
lib;
|
|
31
|
+
handle;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
const libraryPath = resolveLibraryPath(options.libraryPath);
|
|
34
|
+
this.lib = loadDendriteLib(libraryPath);
|
|
35
|
+
const payload = JSON.stringify({
|
|
36
|
+
endpoint: options.endpoint,
|
|
37
|
+
connect_timeout_ms: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,
|
|
38
|
+
});
|
|
39
|
+
const resp = callJson(this.lib.axon_dendrite_client_open_json, payload);
|
|
40
|
+
const handleRaw = resp.handle;
|
|
41
|
+
if (typeof handleRaw !== "number" && typeof handleRaw !== "string") {
|
|
42
|
+
throw new DendriteError(`invalid dendrite handle payload: ${JSON.stringify(resp)}`);
|
|
43
|
+
}
|
|
44
|
+
this.handle = BigInt(handleRaw);
|
|
45
|
+
if (this.handle <= 0n) {
|
|
46
|
+
throw new DendriteError(`invalid dendrite handle: ${this.handle.toString()}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
close() {
|
|
50
|
+
if (this.handle <= 0n)
|
|
51
|
+
return;
|
|
52
|
+
callJson(this.lib.axon_dendrite_client_close_json, this.handle);
|
|
53
|
+
this.handle = 0n;
|
|
54
|
+
}
|
|
55
|
+
protocolCoverage() {
|
|
56
|
+
return callJson(this.lib.axon_dendrite_protocol_coverage_json);
|
|
57
|
+
}
|
|
58
|
+
protocolCatalog() {
|
|
59
|
+
return callJson(this.lib.axon_dendrite_protocol_catalog_json);
|
|
60
|
+
}
|
|
61
|
+
invokeProtocol(options = {}) {
|
|
62
|
+
if (this.handle <= 0n)
|
|
63
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
64
|
+
const payload = {
|
|
65
|
+
service: options.service,
|
|
66
|
+
rpc: options.rpc,
|
|
67
|
+
path: options.path,
|
|
68
|
+
metadata: options.metadata ?? {},
|
|
69
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
70
|
+
max_chunks: options.maxChunks ?? 4096,
|
|
71
|
+
max_request_chunks: options.maxRequestChunks ?? 4096,
|
|
72
|
+
max_response_chunks: options.maxResponseChunks ?? 4096,
|
|
73
|
+
};
|
|
74
|
+
if (options.requestChunks !== undefined) {
|
|
75
|
+
payload.request_chunks_base64 = options.requestChunks.map((v) => Buffer.from(v).toString("base64"));
|
|
76
|
+
}
|
|
77
|
+
else if (options.requestBytes !== undefined) {
|
|
78
|
+
payload.request_base64 = Buffer.from(options.requestBytes).toString("base64");
|
|
79
|
+
}
|
|
80
|
+
const req = JSON.stringify(payload);
|
|
81
|
+
return callJson(this.lib.axon_dendrite_invoke_protocol_json, this.handle, req);
|
|
82
|
+
}
|
|
83
|
+
unaryCall(path, requestBytes, options = {}) {
|
|
84
|
+
if (this.handle <= 0n)
|
|
85
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
86
|
+
const payload = JSON.stringify({
|
|
87
|
+
path,
|
|
88
|
+
request_base64: Buffer.from(requestBytes).toString("base64"),
|
|
89
|
+
metadata: options.metadata ?? {},
|
|
90
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
91
|
+
});
|
|
92
|
+
const resp = callJson(this.lib.axon_dendrite_unary_call_json, this.handle, payload);
|
|
93
|
+
const b64 = String(resp.response_base64 ?? "");
|
|
94
|
+
if (!b64) {
|
|
95
|
+
throw new DendriteError(`missing unary response payload: ${JSON.stringify(resp)}`);
|
|
96
|
+
}
|
|
97
|
+
return Buffer.from(b64, "base64");
|
|
98
|
+
}
|
|
99
|
+
serverStreamCall(path, requestBytes, options = {}) {
|
|
100
|
+
if (this.handle <= 0n)
|
|
101
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
102
|
+
const payload = JSON.stringify({
|
|
103
|
+
path,
|
|
104
|
+
request_base64: Buffer.from(requestBytes).toString("base64"),
|
|
105
|
+
metadata: options.metadata ?? {},
|
|
106
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
107
|
+
max_chunks: options.maxChunks ?? 4096,
|
|
108
|
+
});
|
|
109
|
+
const resp = callJson(this.lib.axon_dendrite_server_stream_call_json, this.handle, payload);
|
|
110
|
+
const chunksRaw = Array.isArray(resp.chunks_base64) ? resp.chunks_base64 : [];
|
|
111
|
+
const chunks = chunksRaw.map((v) => Buffer.from(String(v), "base64"));
|
|
112
|
+
const truncated = Boolean(resp.truncated ?? false);
|
|
113
|
+
return { chunks, truncated };
|
|
114
|
+
}
|
|
115
|
+
clientStreamCall(path, requestChunks, options = {}) {
|
|
116
|
+
if (this.handle <= 0n)
|
|
117
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
118
|
+
const payload = JSON.stringify({
|
|
119
|
+
path,
|
|
120
|
+
request_chunks_base64: requestChunks.map((v) => Buffer.from(v).toString("base64")),
|
|
121
|
+
metadata: options.metadata ?? {},
|
|
122
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
123
|
+
max_request_chunks: options.maxRequestChunks ?? 4096,
|
|
124
|
+
});
|
|
125
|
+
const resp = callJson(this.lib.axon_dendrite_client_stream_call_json, this.handle, payload);
|
|
126
|
+
const b64 = String(resp.response_base64 ?? "");
|
|
127
|
+
if (!b64) {
|
|
128
|
+
throw new DendriteError(`missing client-stream response payload: ${JSON.stringify(resp)}`);
|
|
129
|
+
}
|
|
130
|
+
return Buffer.from(b64, "base64");
|
|
131
|
+
}
|
|
132
|
+
bidiStreamCall(path, requestChunks, options = {}) {
|
|
133
|
+
if (this.handle <= 0n)
|
|
134
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
135
|
+
const payload = JSON.stringify({
|
|
136
|
+
path,
|
|
137
|
+
request_chunks_base64: requestChunks.map((v) => Buffer.from(v).toString("base64")),
|
|
138
|
+
metadata: options.metadata ?? {},
|
|
139
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
140
|
+
max_request_chunks: options.maxRequestChunks ?? 4096,
|
|
141
|
+
max_response_chunks: options.maxResponseChunks ?? 4096,
|
|
142
|
+
});
|
|
143
|
+
const resp = callJson(this.lib.axon_dendrite_bidi_stream_call_json, this.handle, payload);
|
|
144
|
+
const chunksRaw = Array.isArray(resp.chunks_base64) ? resp.chunks_base64 : [];
|
|
145
|
+
const chunks = chunksRaw.map((v) => Buffer.from(String(v), "base64"));
|
|
146
|
+
const truncated = Boolean(resp.truncated ?? false);
|
|
147
|
+
return { chunks, truncated };
|
|
148
|
+
}
|
|
149
|
+
abilityCall(tenantId, resourceUri, payload, options = {}) {
|
|
150
|
+
return this.abilityCallRaw(tenantId, resourceUri, payload, options);
|
|
151
|
+
}
|
|
152
|
+
abilityCallRaw(tenantId, resourceUri, payload, options = {}) {
|
|
153
|
+
if (this.handle <= 0n)
|
|
154
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
155
|
+
const req = JSON.stringify({
|
|
156
|
+
tenant_id: tenantId,
|
|
157
|
+
resource_uri: resourceUri,
|
|
158
|
+
payload_json: payload,
|
|
159
|
+
subject_id: options.subjectId,
|
|
160
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
161
|
+
metadata: options.metadata ?? {},
|
|
162
|
+
});
|
|
163
|
+
const resp = callJson(this.lib.axon_dendrite_invoke_ability_json, this.handle, req);
|
|
164
|
+
const out = {};
|
|
165
|
+
for (const [k, v] of Object.entries(resp)) {
|
|
166
|
+
if (k === "ok")
|
|
167
|
+
continue;
|
|
168
|
+
out[k] = v;
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
listNodes(tenantId, options = {}) {
|
|
173
|
+
if (this.handle <= 0n)
|
|
174
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
175
|
+
const req = JSON.stringify({
|
|
176
|
+
tenant_id: tenantId,
|
|
177
|
+
owner_id: options.ownerId ?? "",
|
|
178
|
+
});
|
|
179
|
+
const resp = callJson(this.lib.axon_dendrite_list_nodes_json, this.handle, req);
|
|
180
|
+
return Array.isArray(resp.nodes) ? resp.nodes : [];
|
|
181
|
+
}
|
|
182
|
+
registerNode(tenantId, nodeId, options = {}) {
|
|
183
|
+
if (this.handle <= 0n)
|
|
184
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
185
|
+
const req = JSON.stringify({
|
|
186
|
+
tenant_id: tenantId,
|
|
187
|
+
node_id: nodeId,
|
|
188
|
+
display_name: options.displayName,
|
|
189
|
+
});
|
|
190
|
+
return callJson(this.lib.axon_dendrite_register_node_json, this.handle, req);
|
|
191
|
+
}
|
|
192
|
+
deregisterNode(tenantId, nodeId, reason = "") {
|
|
193
|
+
if (this.handle <= 0n)
|
|
194
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
195
|
+
if (!this.lib.axon_dendrite_deregister_node_json) {
|
|
196
|
+
throw new DendriteError("dendrite bridge symbol not available: axon_dendrite_deregister_node_json");
|
|
197
|
+
}
|
|
198
|
+
const req = JSON.stringify({
|
|
199
|
+
tenant_id: tenantId,
|
|
200
|
+
node_id: nodeId,
|
|
201
|
+
reason,
|
|
202
|
+
});
|
|
203
|
+
return callJson(this.lib.axon_dendrite_deregister_node_json, this.handle, req);
|
|
204
|
+
}
|
|
205
|
+
heartbeat(tenantId, nodeId) {
|
|
206
|
+
if (this.handle <= 0n)
|
|
207
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
208
|
+
const req = JSON.stringify({
|
|
209
|
+
tenant_id: tenantId,
|
|
210
|
+
node_id: nodeId,
|
|
211
|
+
});
|
|
212
|
+
return callJson(this.lib.axon_dendrite_heartbeat_json, this.handle, req);
|
|
213
|
+
}
|
|
214
|
+
publishCapability(tenantId, packageId, capabilityName, options) {
|
|
215
|
+
if (this.handle <= 0n)
|
|
216
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
217
|
+
validatePublishCapabilityRequest(tenantId, packageId, capabilityName, options);
|
|
218
|
+
const payload = buildPublishCapabilityPayload(tenantId, packageId, capabilityName, options);
|
|
219
|
+
const req = JSON.stringify(payload);
|
|
220
|
+
return callJson(this.lib.axon_dendrite_publish_capability_json, this.handle, req);
|
|
221
|
+
}
|
|
222
|
+
installCapability(tenantId, nodeId, packageId, options) {
|
|
223
|
+
if (this.handle <= 0n)
|
|
224
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
225
|
+
validateInstallCapabilityRequest(tenantId, nodeId, packageId, options);
|
|
226
|
+
const payload = buildInstallCapabilityPayload(tenantId, nodeId, packageId, options);
|
|
227
|
+
const req = JSON.stringify(payload);
|
|
228
|
+
return callJson(this.lib.axon_dendrite_install_capability_json, this.handle, req);
|
|
229
|
+
}
|
|
230
|
+
activateCapability(tenantId, nodeId, installId) {
|
|
231
|
+
if (this.handle <= 0n)
|
|
232
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
233
|
+
const req = JSON.stringify({
|
|
234
|
+
tenant_id: tenantId,
|
|
235
|
+
node_id: nodeId,
|
|
236
|
+
install_id: installId,
|
|
237
|
+
});
|
|
238
|
+
return callJson(this.lib.axon_dendrite_activate_capability_json, this.handle, req);
|
|
239
|
+
}
|
|
240
|
+
listA2aAgents(tenantId, options = {}) {
|
|
241
|
+
if (this.handle <= 0n)
|
|
242
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
243
|
+
const req = JSON.stringify({
|
|
244
|
+
tenant_id: tenantId,
|
|
245
|
+
tags: options.tags ?? [],
|
|
246
|
+
owner_id: options.ownerId ?? "",
|
|
247
|
+
limit: options.limit ?? 100,
|
|
248
|
+
});
|
|
249
|
+
const resp = callJson(this.lib.axon_dendrite_list_a2a_agents_json, this.handle, req);
|
|
250
|
+
return Array.isArray(resp.agents) ? resp.agents : [];
|
|
251
|
+
}
|
|
252
|
+
getA2aAgentCard(tenantId, nodeId) {
|
|
253
|
+
if (this.handle <= 0n)
|
|
254
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
255
|
+
const req = JSON.stringify({
|
|
256
|
+
tenant_id: tenantId,
|
|
257
|
+
node_id: nodeId,
|
|
258
|
+
});
|
|
259
|
+
return callJson(this.lib.axon_dendrite_get_a2a_agent_card_json, this.handle, req);
|
|
260
|
+
}
|
|
261
|
+
sendA2aTask(tenantId, targetAgentId, skillId, options = {}) {
|
|
262
|
+
if (this.handle <= 0n)
|
|
263
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
264
|
+
const req = JSON.stringify({
|
|
265
|
+
tenant_id: tenantId,
|
|
266
|
+
target_agent_id: targetAgentId,
|
|
267
|
+
skill_id: skillId,
|
|
268
|
+
input_json: options.inputJson ?? {},
|
|
269
|
+
input_base64: options.inputBase64,
|
|
270
|
+
task_id: options.taskId,
|
|
271
|
+
idempotency_key: options.idempotencyKey,
|
|
272
|
+
});
|
|
273
|
+
return callJson(this.lib.axon_dendrite_send_a2a_task_json, this.handle, req);
|
|
274
|
+
}
|
|
275
|
+
deployMcpListDir(tenantId, nodeId, options = {}) {
|
|
276
|
+
if (this.handle <= 0n)
|
|
277
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
278
|
+
validateDeployMcpListDirRequest(tenantId, nodeId, options);
|
|
279
|
+
const payload = buildDeployMcpListDirPayload(tenantId, nodeId, options);
|
|
280
|
+
const req = JSON.stringify(payload);
|
|
281
|
+
return callJson(this.lib.axon_dendrite_deploy_mcp_list_dir_json, this.handle, req);
|
|
282
|
+
}
|
|
283
|
+
listMcpTools(tenantId, options = {}) {
|
|
284
|
+
if (this.handle <= 0n)
|
|
285
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
286
|
+
const req = JSON.stringify({
|
|
287
|
+
tenant_id: tenantId,
|
|
288
|
+
name_pattern: options.namePattern ?? "",
|
|
289
|
+
tags: options.tags ?? [],
|
|
290
|
+
node_id: options.nodeId ?? "",
|
|
291
|
+
});
|
|
292
|
+
const resp = callJson(this.lib.axon_dendrite_list_mcp_tools_json, this.handle, req);
|
|
293
|
+
return Array.isArray(resp.tools) ? resp.tools : [];
|
|
294
|
+
}
|
|
295
|
+
callMcpTool(tenantId, toolName, options = {}) {
|
|
296
|
+
if (this.handle <= 0n)
|
|
297
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
298
|
+
const req = JSON.stringify({
|
|
299
|
+
tenant_id: tenantId,
|
|
300
|
+
tool_name: toolName,
|
|
301
|
+
target_node_id: options.targetNodeId ?? "",
|
|
302
|
+
arguments_json: options.argumentsJson ?? {},
|
|
303
|
+
arguments_base64: options.argumentsBase64,
|
|
304
|
+
});
|
|
305
|
+
return callJson(this.lib.axon_dendrite_call_mcp_tool_json, this.handle, req);
|
|
306
|
+
}
|
|
307
|
+
uninstallCapability(tenantId, nodeId, installId, options = {}) {
|
|
308
|
+
if (this.handle <= 0n)
|
|
309
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
310
|
+
const payload = buildUninstallCapabilityPayload(tenantId, nodeId, installId, options);
|
|
311
|
+
const req = JSON.stringify(payload);
|
|
312
|
+
return callJson(this.lib.axon_dendrite_uninstall_capability_json, this.handle, req);
|
|
313
|
+
}
|
|
314
|
+
updateMcpListDir(tenantId, nodeId, options = {}) {
|
|
315
|
+
if (this.handle <= 0n)
|
|
316
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
317
|
+
validateUpdateMcpListDirRequest(tenantId, nodeId, options);
|
|
318
|
+
const payload = buildUpdateMcpListDirPayload(tenantId, nodeId, options);
|
|
319
|
+
const req = JSON.stringify(payload);
|
|
320
|
+
return callJson(this.lib.axon_dendrite_update_mcp_list_dir_json, this.handle, req);
|
|
321
|
+
}
|
|
322
|
+
createVoiceCall(tenantId, options = {}) {
|
|
323
|
+
if (this.handle <= 0n)
|
|
324
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
325
|
+
const req = JSON.stringify({
|
|
326
|
+
tenant_id: tenantId,
|
|
327
|
+
call_id: options.callId,
|
|
328
|
+
display_name: options.displayName,
|
|
329
|
+
participant_limit: options.participantLimit,
|
|
330
|
+
preferred_codec: options.preferredCodec
|
|
331
|
+
? {
|
|
332
|
+
codec: options.preferredCodec.codec,
|
|
333
|
+
sample_rate_hz: options.preferredCodec.sampleRateHz,
|
|
334
|
+
channels: options.preferredCodec.channels,
|
|
335
|
+
ptime_ms: options.preferredCodec.ptimeMs,
|
|
336
|
+
max_bitrate_kbps: options.preferredCodec.maxBitrateKbps,
|
|
337
|
+
fec_enabled: options.preferredCodec.fecEnabled,
|
|
338
|
+
dtx_enabled: options.preferredCodec.dtxEnabled,
|
|
339
|
+
}
|
|
340
|
+
: undefined,
|
|
341
|
+
metadata: options.metadata ?? {},
|
|
342
|
+
});
|
|
343
|
+
return callJson(this.lib.axon_dendrite_voice_create_call_json, this.handle, req);
|
|
344
|
+
}
|
|
345
|
+
getVoiceCall(tenantId, callId) {
|
|
346
|
+
if (this.handle <= 0n)
|
|
347
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
348
|
+
const req = JSON.stringify({
|
|
349
|
+
tenant_id: tenantId,
|
|
350
|
+
call_id: callId,
|
|
351
|
+
});
|
|
352
|
+
return callJson(this.lib.axon_dendrite_voice_get_call_json, this.handle, req);
|
|
353
|
+
}
|
|
354
|
+
joinVoiceCall(tenantId, callId, options = {}) {
|
|
355
|
+
if (this.handle <= 0n)
|
|
356
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
357
|
+
const req = JSON.stringify({
|
|
358
|
+
tenant_id: tenantId,
|
|
359
|
+
call_id: callId,
|
|
360
|
+
participant_id: options.participantId,
|
|
361
|
+
node_id: options.nodeId,
|
|
362
|
+
transport: options.transport,
|
|
363
|
+
stream_session_id: options.streamSessionId,
|
|
364
|
+
codec_profile: options.codecProfile
|
|
365
|
+
? {
|
|
366
|
+
codec: options.codecProfile.codec,
|
|
367
|
+
sample_rate_hz: options.codecProfile.sampleRateHz,
|
|
368
|
+
channels: options.codecProfile.channels,
|
|
369
|
+
ptime_ms: options.codecProfile.ptimeMs,
|
|
370
|
+
max_bitrate_kbps: options.codecProfile.maxBitrateKbps,
|
|
371
|
+
fec_enabled: options.codecProfile.fecEnabled,
|
|
372
|
+
dtx_enabled: options.codecProfile.dtxEnabled,
|
|
373
|
+
}
|
|
374
|
+
: undefined,
|
|
375
|
+
muted: options.muted ?? false,
|
|
376
|
+
});
|
|
377
|
+
return callJson(this.lib.axon_dendrite_voice_join_call_json, this.handle, req);
|
|
378
|
+
}
|
|
379
|
+
leaveVoiceCall(tenantId, callId, participantId, reason) {
|
|
380
|
+
if (this.handle <= 0n)
|
|
381
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
382
|
+
const req = JSON.stringify({
|
|
383
|
+
tenant_id: tenantId,
|
|
384
|
+
call_id: callId,
|
|
385
|
+
participant_id: participantId,
|
|
386
|
+
reason,
|
|
387
|
+
});
|
|
388
|
+
return callJson(this.lib.axon_dendrite_voice_leave_call_json, this.handle, req);
|
|
389
|
+
}
|
|
390
|
+
updateVoiceMediaPath(tenantId, callId, participantId, options = {}) {
|
|
391
|
+
if (this.handle <= 0n)
|
|
392
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
393
|
+
const req = JSON.stringify({
|
|
394
|
+
tenant_id: tenantId,
|
|
395
|
+
call_id: callId,
|
|
396
|
+
participant_id: participantId,
|
|
397
|
+
transport: options.transport,
|
|
398
|
+
stream_session_id: options.streamSessionId,
|
|
399
|
+
muted: options.muted ?? false,
|
|
400
|
+
});
|
|
401
|
+
return callJson(this.lib.axon_dendrite_voice_update_media_path_json, this.handle, req);
|
|
402
|
+
}
|
|
403
|
+
reportVoiceCallMetrics(tenantId, callId, participantId, metrics = {}) {
|
|
404
|
+
if (this.handle <= 0n)
|
|
405
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
406
|
+
const req = JSON.stringify({
|
|
407
|
+
tenant_id: tenantId,
|
|
408
|
+
call_id: callId,
|
|
409
|
+
participant_id: participantId,
|
|
410
|
+
metrics: {
|
|
411
|
+
rtt_ms: metrics.rttMs,
|
|
412
|
+
jitter_ms: metrics.jitterMs,
|
|
413
|
+
packet_loss_ratio: metrics.packetLossRatio,
|
|
414
|
+
concealed_samples: metrics.concealedSamples,
|
|
415
|
+
audio_level_dbov: metrics.audioLevelDbov,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
return callJson(this.lib.axon_dendrite_voice_report_call_metrics_json, this.handle, req);
|
|
419
|
+
}
|
|
420
|
+
endVoiceCall(tenantId, callId, options = {}) {
|
|
421
|
+
if (this.handle <= 0n)
|
|
422
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
423
|
+
const req = JSON.stringify({
|
|
424
|
+
tenant_id: tenantId,
|
|
425
|
+
call_id: callId,
|
|
426
|
+
end_reason: options.endReason,
|
|
427
|
+
detail: options.detail,
|
|
428
|
+
});
|
|
429
|
+
return callJson(this.lib.axon_dendrite_voice_end_call_json, this.handle, req);
|
|
430
|
+
}
|
|
431
|
+
watchVoiceCallEvents(tenantId, callId, options = {}) {
|
|
432
|
+
if (this.handle <= 0n)
|
|
433
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
434
|
+
const req = JSON.stringify({
|
|
435
|
+
tenant_id: tenantId,
|
|
436
|
+
call_id: callId,
|
|
437
|
+
from_sequence: options.fromSequence ?? 0,
|
|
438
|
+
max_events: options.maxEvents ?? 256,
|
|
439
|
+
timeout_ms: options.timeoutMs ?? 1500,
|
|
440
|
+
});
|
|
441
|
+
return callJson(this.lib.axon_dendrite_voice_watch_call_events_json, this.handle, req);
|
|
442
|
+
}
|
|
443
|
+
createVoiceTransportSession(tenantId, callId, participantId, options = {}) {
|
|
444
|
+
if (this.handle <= 0n)
|
|
445
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
446
|
+
const req = JSON.stringify({
|
|
447
|
+
tenant_id: tenantId,
|
|
448
|
+
call_id: callId,
|
|
449
|
+
participant_id: participantId,
|
|
450
|
+
transport_session_id: options.transportSessionId,
|
|
451
|
+
transport: options.transport,
|
|
452
|
+
local_description: options.localDescription
|
|
453
|
+
? {
|
|
454
|
+
sdp_type: options.localDescription.sdpType,
|
|
455
|
+
sdp: options.localDescription.sdp,
|
|
456
|
+
updated_at_unix_ms: options.localDescription.updatedAtUnixMs,
|
|
457
|
+
revision_id: options.localDescription.revisionId,
|
|
458
|
+
}
|
|
459
|
+
: undefined,
|
|
460
|
+
requested_ttl_seconds: options.requestedTtlSeconds,
|
|
461
|
+
metadata: options.metadata ?? {},
|
|
462
|
+
});
|
|
463
|
+
return callJson(this.lib.axon_dendrite_voice_create_transport_session_json, this.handle, req);
|
|
464
|
+
}
|
|
465
|
+
getVoiceTransportSession(tenantId, callId, transportSessionId) {
|
|
466
|
+
if (this.handle <= 0n)
|
|
467
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
468
|
+
const req = JSON.stringify({
|
|
469
|
+
tenant_id: tenantId,
|
|
470
|
+
call_id: callId,
|
|
471
|
+
transport_session_id: transportSessionId,
|
|
472
|
+
});
|
|
473
|
+
return callJson(this.lib.axon_dendrite_voice_get_transport_session_json, this.handle, req);
|
|
474
|
+
}
|
|
475
|
+
setVoiceTransportDescription(tenantId, callId, transportSessionId, side, description) {
|
|
476
|
+
if (this.handle <= 0n)
|
|
477
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
478
|
+
const req = JSON.stringify({
|
|
479
|
+
tenant_id: tenantId,
|
|
480
|
+
call_id: callId,
|
|
481
|
+
transport_session_id: transportSessionId,
|
|
482
|
+
side,
|
|
483
|
+
description: {
|
|
484
|
+
sdp_type: description.sdpType,
|
|
485
|
+
sdp: description.sdp,
|
|
486
|
+
updated_at_unix_ms: description.updatedAtUnixMs,
|
|
487
|
+
revision_id: description.revisionId,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
return callJson(this.lib.axon_dendrite_voice_set_transport_description_json, this.handle, req);
|
|
491
|
+
}
|
|
492
|
+
addVoiceTransportCandidate(tenantId, callId, transportSessionId, side, candidate) {
|
|
493
|
+
if (this.handle <= 0n)
|
|
494
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
495
|
+
const req = JSON.stringify({
|
|
496
|
+
tenant_id: tenantId,
|
|
497
|
+
call_id: callId,
|
|
498
|
+
transport_session_id: transportSessionId,
|
|
499
|
+
side,
|
|
500
|
+
candidate: {
|
|
501
|
+
candidate: candidate.candidate,
|
|
502
|
+
sdp_mid: candidate.sdpMid,
|
|
503
|
+
sdp_mline_index: candidate.sdpMlineIndex,
|
|
504
|
+
username_fragment: candidate.usernameFragment,
|
|
505
|
+
gathered_at_unix_ms: candidate.gatheredAtUnixMs,
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
return callJson(this.lib.axon_dendrite_voice_add_transport_candidate_json, this.handle, req);
|
|
509
|
+
}
|
|
510
|
+
refreshVoiceTransportLease(tenantId, callId, transportSessionId, requestedTtlSeconds = 0) {
|
|
511
|
+
if (this.handle <= 0n)
|
|
512
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
513
|
+
const req = JSON.stringify({
|
|
514
|
+
tenant_id: tenantId,
|
|
515
|
+
call_id: callId,
|
|
516
|
+
transport_session_id: transportSessionId,
|
|
517
|
+
requested_ttl_seconds: requestedTtlSeconds,
|
|
518
|
+
});
|
|
519
|
+
return callJson(this.lib.axon_dendrite_voice_refresh_transport_lease_json, this.handle, req);
|
|
520
|
+
}
|
|
521
|
+
endVoiceTransportSession(tenantId, callId, transportSessionId, options = {}) {
|
|
522
|
+
if (this.handle <= 0n)
|
|
523
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
524
|
+
const req = JSON.stringify({
|
|
525
|
+
tenant_id: tenantId,
|
|
526
|
+
call_id: callId,
|
|
527
|
+
transport_session_id: transportSessionId,
|
|
528
|
+
failed: options.failed ?? false,
|
|
529
|
+
reason: options.reason,
|
|
530
|
+
});
|
|
531
|
+
return callJson(this.lib.axon_dendrite_voice_end_transport_session_json, this.handle, req);
|
|
532
|
+
}
|
|
533
|
+
watchVoiceTransportEvents(tenantId, callId, transportSessionId, options = {}) {
|
|
534
|
+
if (this.handle <= 0n)
|
|
535
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
536
|
+
const req = JSON.stringify({
|
|
537
|
+
tenant_id: tenantId,
|
|
538
|
+
call_id: callId,
|
|
539
|
+
transport_session_id: transportSessionId,
|
|
540
|
+
from_sequence: options.fromSequence ?? 0,
|
|
541
|
+
max_events: options.maxEvents ?? 256,
|
|
542
|
+
timeout_ms: options.timeoutMs ?? 1500,
|
|
543
|
+
});
|
|
544
|
+
return callJson(this.lib.axon_dendrite_voice_watch_transport_events_json, this.handle, req);
|
|
545
|
+
}
|
|
546
|
+
// -- Incremental streaming (pull model) ---------------------------------
|
|
547
|
+
serverStreamOpen(path, requestBytes, options = {}) {
|
|
548
|
+
if (this.handle <= 0n)
|
|
549
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
550
|
+
const streamOpen = requireBridgeFn(this.lib.axon_dendrite_server_stream_open_json, STREAMING_UNSUPPORTED);
|
|
551
|
+
requireBridgeFn(this.lib.axon_dendrite_stream_next_json, STREAMING_UNSUPPORTED);
|
|
552
|
+
const payload = {
|
|
553
|
+
path,
|
|
554
|
+
request_base64: Buffer.from(requestBytes).toString("base64"),
|
|
555
|
+
metadata: options.metadata ?? {},
|
|
556
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
557
|
+
chunk_buffer_size: options.chunkBufferSize ?? 64,
|
|
558
|
+
};
|
|
559
|
+
if (options.chunkTimeoutMs !== undefined) {
|
|
560
|
+
payload.chunk_timeout_ms = options.chunkTimeoutMs;
|
|
561
|
+
}
|
|
562
|
+
const resp = callJson(streamOpen, this.handle, JSON.stringify(payload));
|
|
563
|
+
const streamHandle = BigInt(resp.stream_handle);
|
|
564
|
+
if (streamHandle <= 0n) {
|
|
565
|
+
throw new DendriteError(`failed to open server stream: ${JSON.stringify(resp)}`);
|
|
566
|
+
}
|
|
567
|
+
return new DendriteServerStream(this.lib, streamHandle);
|
|
568
|
+
}
|
|
569
|
+
bidiStreamOpen(path, options = {}) {
|
|
570
|
+
if (this.handle <= 0n)
|
|
571
|
+
throw new DendriteError("dendrite bridge already closed");
|
|
572
|
+
const streamOpen = requireBridgeFn(this.lib.axon_dendrite_bidi_stream_open_json, STREAMING_UNSUPPORTED);
|
|
573
|
+
requireBridgeFn(this.lib.axon_dendrite_stream_next_json, STREAMING_UNSUPPORTED);
|
|
574
|
+
requireBridgeFn(this.lib.axon_dendrite_bidi_stream_send_json, STREAMING_UNSUPPORTED);
|
|
575
|
+
const payload = {
|
|
576
|
+
path,
|
|
577
|
+
metadata: options.metadata ?? {},
|
|
578
|
+
timeout_ms: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
579
|
+
chunk_buffer_size: options.chunkBufferSize ?? 64,
|
|
580
|
+
request_buffer_size: options.requestBufferSize ?? 64,
|
|
581
|
+
};
|
|
582
|
+
if (options.chunkTimeoutMs !== undefined) {
|
|
583
|
+
payload.chunk_timeout_ms = options.chunkTimeoutMs;
|
|
584
|
+
}
|
|
585
|
+
if (options.requestBytes !== undefined) {
|
|
586
|
+
payload.request_base64 = Buffer.from(options.requestBytes).toString("base64");
|
|
587
|
+
}
|
|
588
|
+
if (options.requestChunks !== undefined) {
|
|
589
|
+
payload.request_chunks_base64 = options.requestChunks.map((v) => Buffer.from(v).toString("base64"));
|
|
590
|
+
}
|
|
591
|
+
const resp = callJson(streamOpen, this.handle, JSON.stringify(payload));
|
|
592
|
+
const streamHandle = BigInt(resp.stream_handle);
|
|
593
|
+
if (streamHandle <= 0n) {
|
|
594
|
+
throw new DendriteError(`failed to open bidi stream: ${JSON.stringify(resp)}`);
|
|
595
|
+
}
|
|
596
|
+
return new DendriteBidiStream(this.lib, streamHandle);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
export class DendriteServerStream {
|
|
600
|
+
lib;
|
|
601
|
+
streamHandle;
|
|
602
|
+
done = false;
|
|
603
|
+
nativeClosed = false;
|
|
604
|
+
constructor(lib, streamHandle) {
|
|
605
|
+
this.lib = lib;
|
|
606
|
+
this.streamHandle = streamHandle;
|
|
607
|
+
}
|
|
608
|
+
next(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
609
|
+
if (this.done)
|
|
610
|
+
return { chunk: null, done: true };
|
|
611
|
+
const streamNext = requireBridgeFn(this.lib.axon_dendrite_stream_next_json, STREAMING_UNSUPPORTED);
|
|
612
|
+
const resp = callJson(streamNext, this.streamHandle, JSON.stringify({ timeout_ms: timeoutMs }));
|
|
613
|
+
if (resp.done) {
|
|
614
|
+
this.done = true;
|
|
615
|
+
return { chunk: null, done: true };
|
|
616
|
+
}
|
|
617
|
+
if (resp.timeout) {
|
|
618
|
+
return { chunk: null, done: false, timeout: true };
|
|
619
|
+
}
|
|
620
|
+
const b64 = String(resp.chunk_base64 ?? "");
|
|
621
|
+
return { chunk: Buffer.from(b64, "base64"), done: false };
|
|
622
|
+
}
|
|
623
|
+
close() {
|
|
624
|
+
if (!this.nativeClosed) {
|
|
625
|
+
this.nativeClosed = true;
|
|
626
|
+
this.done = true;
|
|
627
|
+
if (this.lib.axon_dendrite_stream_close_json) {
|
|
628
|
+
callJson(this.lib.axon_dendrite_stream_close_json, this.streamHandle);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
async *[Symbol.asyncIterator]() {
|
|
633
|
+
try {
|
|
634
|
+
while (true) {
|
|
635
|
+
const { chunk, done, timeout } = this.next();
|
|
636
|
+
if (done)
|
|
637
|
+
break;
|
|
638
|
+
if (timeout)
|
|
639
|
+
continue;
|
|
640
|
+
if (chunk)
|
|
641
|
+
yield chunk;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
finally {
|
|
645
|
+
this.close();
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
export class DendriteBidiStream {
|
|
650
|
+
lib;
|
|
651
|
+
streamHandle;
|
|
652
|
+
done = false;
|
|
653
|
+
nativeClosed = false;
|
|
654
|
+
constructor(lib, streamHandle) {
|
|
655
|
+
this.lib = lib;
|
|
656
|
+
this.streamHandle = streamHandle;
|
|
657
|
+
}
|
|
658
|
+
send(chunk) {
|
|
659
|
+
if (this.done)
|
|
660
|
+
throw new DendriteError("bidi stream is closed");
|
|
661
|
+
const streamSend = requireBridgeFn(this.lib.axon_dendrite_bidi_stream_send_json, STREAMING_UNSUPPORTED);
|
|
662
|
+
const resp = callJson(streamSend, this.streamHandle, JSON.stringify({ chunk_base64: Buffer.from(chunk).toString("base64") }));
|
|
663
|
+
return Boolean(resp.sent);
|
|
664
|
+
}
|
|
665
|
+
finishSend() {
|
|
666
|
+
if (this.done)
|
|
667
|
+
throw new DendriteError("bidi stream is closed");
|
|
668
|
+
const streamSend = requireBridgeFn(this.lib.axon_dendrite_bidi_stream_send_json, STREAMING_UNSUPPORTED);
|
|
669
|
+
const resp = callJson(streamSend, this.streamHandle, JSON.stringify({ done: true }));
|
|
670
|
+
return Boolean(resp.request_stream_closed);
|
|
671
|
+
}
|
|
672
|
+
recv(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
673
|
+
if (this.done)
|
|
674
|
+
return { chunk: null, done: true };
|
|
675
|
+
const streamNext = requireBridgeFn(this.lib.axon_dendrite_stream_next_json, STREAMING_UNSUPPORTED);
|
|
676
|
+
const resp = callJson(streamNext, this.streamHandle, JSON.stringify({ timeout_ms: timeoutMs }));
|
|
677
|
+
if (resp.done) {
|
|
678
|
+
this.done = true;
|
|
679
|
+
return { chunk: null, done: true };
|
|
680
|
+
}
|
|
681
|
+
if (resp.timeout) {
|
|
682
|
+
return { chunk: null, done: false, timeout: true };
|
|
683
|
+
}
|
|
684
|
+
const b64 = String(resp.chunk_base64 ?? "");
|
|
685
|
+
return { chunk: Buffer.from(b64, "base64"), done: false };
|
|
686
|
+
}
|
|
687
|
+
close() {
|
|
688
|
+
if (!this.nativeClosed) {
|
|
689
|
+
this.nativeClosed = true;
|
|
690
|
+
this.done = true;
|
|
691
|
+
if (this.lib.axon_dendrite_stream_close_json) {
|
|
692
|
+
callJson(this.lib.axon_dendrite_stream_close_json, this.streamHandle);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async *[Symbol.asyncIterator]() {
|
|
697
|
+
try {
|
|
698
|
+
while (true) {
|
|
699
|
+
const { chunk, done, timeout } = this.recv();
|
|
700
|
+
if (done)
|
|
701
|
+
break;
|
|
702
|
+
if (timeout)
|
|
703
|
+
continue;
|
|
704
|
+
if (chunk)
|
|
705
|
+
yield chunk;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
finally {
|
|
709
|
+
this.close();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|