@multi-agent-protocol/sdk 0.0.4 → 0.0.6
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 +164 -5
- package/dist/{index-Z76qC_Us.d.cts → index-BQXp4_rd.d.cts} +1898 -142
- package/dist/{index-Z76qC_Us.d.ts → index-BQXp4_rd.d.ts} +1898 -142
- package/dist/index.cjs +2906 -1104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +641 -24
- package/dist/index.d.ts +641 -24
- package/dist/index.js +2889 -1102
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +1092 -8
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -1
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +1092 -8
- package/dist/testing.js.map +1 -1
- package/package.json +8 -1
package/dist/testing.cjs
CHANGED
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var agenticMesh = require('agentic-mesh');
|
|
3
4
|
var ulid = require('ulid');
|
|
5
|
+
var events = require('events');
|
|
6
|
+
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/stream/agentic-mesh.ts
|
|
18
|
+
var agentic_mesh_exports = {};
|
|
19
|
+
__export(agentic_mesh_exports, {
|
|
20
|
+
agenticMeshStream: () => agenticMeshStream
|
|
21
|
+
});
|
|
22
|
+
async function agenticMeshStream(config) {
|
|
23
|
+
if (!config.transport.active) {
|
|
24
|
+
await config.transport.start();
|
|
25
|
+
}
|
|
26
|
+
const connected = await config.transport.connect(config.peer);
|
|
27
|
+
if (!connected) {
|
|
28
|
+
throw new Error(`Failed to connect to peer: ${config.peer.peerId}`);
|
|
29
|
+
}
|
|
30
|
+
const streamId = `map-${config.localPeerId}-${Date.now()}`;
|
|
31
|
+
const tunnelStream = new agenticMesh.TunnelStream({
|
|
32
|
+
transport: config.transport,
|
|
33
|
+
peerId: config.peer.peerId,
|
|
34
|
+
streamId
|
|
35
|
+
});
|
|
36
|
+
await tunnelStream.open();
|
|
37
|
+
return tunnelStreamToMapStream(tunnelStream);
|
|
38
|
+
}
|
|
39
|
+
function tunnelStreamToMapStream(tunnel) {
|
|
40
|
+
let readingAborted = false;
|
|
41
|
+
const readable = new ReadableStream({
|
|
42
|
+
async start(controller) {
|
|
43
|
+
try {
|
|
44
|
+
for await (const frame of tunnel) {
|
|
45
|
+
if (readingAborted) break;
|
|
46
|
+
controller.enqueue(frame);
|
|
47
|
+
}
|
|
48
|
+
if (!readingAborted) {
|
|
49
|
+
controller.close();
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (!readingAborted) {
|
|
53
|
+
controller.error(error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
cancel() {
|
|
58
|
+
readingAborted = true;
|
|
59
|
+
tunnel.close().catch(() => {
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const writable = new WritableStream({
|
|
64
|
+
async write(message) {
|
|
65
|
+
if (!tunnel.isOpen) {
|
|
66
|
+
throw new Error("Stream is not open");
|
|
67
|
+
}
|
|
68
|
+
await tunnel.write(message);
|
|
69
|
+
},
|
|
70
|
+
async close() {
|
|
71
|
+
await tunnel.close();
|
|
72
|
+
},
|
|
73
|
+
abort() {
|
|
74
|
+
readingAborted = true;
|
|
75
|
+
tunnel.close().catch(() => {
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return { readable, writable };
|
|
80
|
+
}
|
|
81
|
+
var init_agentic_mesh = __esm({
|
|
82
|
+
"src/stream/agentic-mesh.ts"() {
|
|
83
|
+
}
|
|
84
|
+
});
|
|
4
85
|
|
|
5
86
|
// src/jsonrpc/index.ts
|
|
6
87
|
function isRequest(message) {
|
|
@@ -63,6 +144,7 @@ var EVENT_TYPES = {
|
|
|
63
144
|
PARTICIPANT_DISCONNECTED: "participant_disconnected",
|
|
64
145
|
// Message events
|
|
65
146
|
MESSAGE_SENT: "message_sent",
|
|
147
|
+
MESSAGE_DELIVERED: "message_delivered",
|
|
66
148
|
// Scope events
|
|
67
149
|
SCOPE_CREATED: "scope_created",
|
|
68
150
|
SCOPE_MEMBER_JOINED: "scope_member_joined",
|
|
@@ -110,6 +192,10 @@ var SESSION_METHODS = {
|
|
|
110
192
|
SESSION_LOAD: "map/session/load",
|
|
111
193
|
SESSION_CLOSE: "map/session/close"
|
|
112
194
|
};
|
|
195
|
+
var AUTH_METHODS = {
|
|
196
|
+
AUTHENTICATE: "map/authenticate",
|
|
197
|
+
AUTH_REFRESH: "map/auth/refresh"
|
|
198
|
+
};
|
|
113
199
|
var PERMISSION_METHODS = {
|
|
114
200
|
PERMISSIONS_UPDATE: "map/permissions/update"
|
|
115
201
|
};
|
|
@@ -120,8 +206,7 @@ var NOTIFICATION_METHODS = {
|
|
|
120
206
|
EVENT: "map/event",
|
|
121
207
|
MESSAGE: "map/message",
|
|
122
208
|
/** Client acknowledges received events (for backpressure) */
|
|
123
|
-
SUBSCRIBE_ACK: "map/subscribe.ack"
|
|
124
|
-
};
|
|
209
|
+
SUBSCRIBE_ACK: "map/subscribe.ack"};
|
|
125
210
|
var PROTOCOL_ERROR_CODES = {
|
|
126
211
|
PARSE_ERROR: -32700,
|
|
127
212
|
INVALID_REQUEST: -32600,
|
|
@@ -133,7 +218,10 @@ var AUTH_ERROR_CODES = {
|
|
|
133
218
|
AUTH_REQUIRED: 1e3,
|
|
134
219
|
AUTH_FAILED: 1001,
|
|
135
220
|
TOKEN_EXPIRED: 1002,
|
|
136
|
-
PERMISSION_DENIED: 1003
|
|
221
|
+
PERMISSION_DENIED: 1003,
|
|
222
|
+
INSUFFICIENT_SCOPE: 1004,
|
|
223
|
+
METHOD_NOT_SUPPORTED: 1005,
|
|
224
|
+
INVALID_CREDENTIALS: 1006
|
|
137
225
|
};
|
|
138
226
|
var ROUTING_ERROR_CODES = {
|
|
139
227
|
ADDRESS_NOT_FOUND: 2e3,
|
|
@@ -272,6 +360,27 @@ var MAPRequestError = class _MAPRequestError extends Error {
|
|
|
272
360
|
{ category: "auth" }
|
|
273
361
|
);
|
|
274
362
|
}
|
|
363
|
+
static insufficientScope(required) {
|
|
364
|
+
return new _MAPRequestError(
|
|
365
|
+
AUTH_ERROR_CODES.INSUFFICIENT_SCOPE,
|
|
366
|
+
required ? `Insufficient scope: ${required}` : "Insufficient scope",
|
|
367
|
+
{ category: "auth" }
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
static methodNotSupported(method) {
|
|
371
|
+
return new _MAPRequestError(
|
|
372
|
+
AUTH_ERROR_CODES.METHOD_NOT_SUPPORTED,
|
|
373
|
+
`Authentication method not supported: ${method}`,
|
|
374
|
+
{ category: "auth" }
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
static invalidCredentials(details) {
|
|
378
|
+
return new _MAPRequestError(
|
|
379
|
+
AUTH_ERROR_CODES.INVALID_CREDENTIALS,
|
|
380
|
+
details ?? "Invalid credentials",
|
|
381
|
+
{ category: "auth" }
|
|
382
|
+
);
|
|
383
|
+
}
|
|
275
384
|
// ==========================================================================
|
|
276
385
|
// Routing Errors (2xxx)
|
|
277
386
|
// ==========================================================================
|
|
@@ -1398,6 +1507,11 @@ var TestServer = class {
|
|
|
1398
1507
|
if (participant) {
|
|
1399
1508
|
this.deliverMessage(recipientId, message);
|
|
1400
1509
|
delivered.push(recipientId);
|
|
1510
|
+
this.emitEvent({
|
|
1511
|
+
type: EVENT_TYPES.MESSAGE_DELIVERED,
|
|
1512
|
+
source: senderAgentId ?? senderId,
|
|
1513
|
+
data: { messageId, message, correlationId: params.meta?.correlationId }
|
|
1514
|
+
});
|
|
1401
1515
|
}
|
|
1402
1516
|
}
|
|
1403
1517
|
this.emitEvent({
|
|
@@ -1758,6 +1872,9 @@ var TestServer = class {
|
|
|
1758
1872
|
if (typeof address === "string") {
|
|
1759
1873
|
return [this.#findParticipantForAgent(address) ?? address];
|
|
1760
1874
|
}
|
|
1875
|
+
if ("participant" in address && address.participant) {
|
|
1876
|
+
return this.#participants.has(address.participant) ? [address.participant] : [];
|
|
1877
|
+
}
|
|
1761
1878
|
if ("agent" in address && !("system" in address)) {
|
|
1762
1879
|
const participantId = this.#findParticipantForAgent(address.agent);
|
|
1763
1880
|
return participantId ? [participantId] : [];
|
|
@@ -1843,11 +1960,6 @@ var TestServer = class {
|
|
|
1843
1960
|
return false;
|
|
1844
1961
|
}
|
|
1845
1962
|
}
|
|
1846
|
-
if (filter.agents?.length && event.source) {
|
|
1847
|
-
if (!filter.agents.includes(event.source)) {
|
|
1848
|
-
return false;
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
1963
|
if (filter.fromAgents?.length && event.source) {
|
|
1852
1964
|
if (!filter.fromAgents.includes(event.source)) {
|
|
1853
1965
|
return false;
|
|
@@ -2035,6 +2147,7 @@ var TestServer = class {
|
|
|
2035
2147
|
};
|
|
2036
2148
|
|
|
2037
2149
|
// src/stream/index.ts
|
|
2150
|
+
init_agentic_mesh();
|
|
2038
2151
|
function websocketStream(ws) {
|
|
2039
2152
|
const messageQueue = [];
|
|
2040
2153
|
let messageResolver = null;
|
|
@@ -2554,18 +2667,624 @@ function createSubscription(id, unsubscribe, options, sendAck) {
|
|
|
2554
2667
|
return new Subscription(id, unsubscribe, options, sendAck);
|
|
2555
2668
|
}
|
|
2556
2669
|
|
|
2670
|
+
// src/acp/types.ts
|
|
2671
|
+
var ACPError = class _ACPError extends Error {
|
|
2672
|
+
code;
|
|
2673
|
+
data;
|
|
2674
|
+
constructor(code, message, data) {
|
|
2675
|
+
super(message);
|
|
2676
|
+
this.name = "ACPError";
|
|
2677
|
+
this.code = code;
|
|
2678
|
+
this.data = data;
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Create an ACPError from an error response.
|
|
2682
|
+
*/
|
|
2683
|
+
static fromResponse(error) {
|
|
2684
|
+
return new _ACPError(error.code, error.message, error.data);
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Convert to JSON-RPC error object.
|
|
2688
|
+
*/
|
|
2689
|
+
toErrorObject() {
|
|
2690
|
+
return {
|
|
2691
|
+
code: this.code,
|
|
2692
|
+
message: this.message,
|
|
2693
|
+
...this.data !== void 0 && { data: this.data }
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
};
|
|
2697
|
+
var ACP_METHODS = {
|
|
2698
|
+
// Lifecycle
|
|
2699
|
+
INITIALIZE: "initialize",
|
|
2700
|
+
AUTHENTICATE: "authenticate",
|
|
2701
|
+
// Session management
|
|
2702
|
+
SESSION_NEW: "session/new",
|
|
2703
|
+
SESSION_LOAD: "session/load",
|
|
2704
|
+
SESSION_SET_MODE: "session/set_mode",
|
|
2705
|
+
// Prompt
|
|
2706
|
+
SESSION_PROMPT: "session/prompt",
|
|
2707
|
+
SESSION_CANCEL: "session/cancel",
|
|
2708
|
+
// Notifications
|
|
2709
|
+
SESSION_UPDATE: "session/update",
|
|
2710
|
+
// Agent→Client requests
|
|
2711
|
+
REQUEST_PERMISSION: "request_permission",
|
|
2712
|
+
FS_READ_TEXT_FILE: "fs/read_text_file",
|
|
2713
|
+
FS_WRITE_TEXT_FILE: "fs/write_text_file",
|
|
2714
|
+
TERMINAL_CREATE: "terminal/create",
|
|
2715
|
+
TERMINAL_OUTPUT: "terminal/output",
|
|
2716
|
+
TERMINAL_RELEASE: "terminal/release",
|
|
2717
|
+
TERMINAL_WAIT_FOR_EXIT: "terminal/wait_for_exit",
|
|
2718
|
+
TERMINAL_KILL: "terminal/kill"
|
|
2719
|
+
};
|
|
2720
|
+
function isACPEnvelope(payload) {
|
|
2721
|
+
if (typeof payload !== "object" || payload === null) {
|
|
2722
|
+
return false;
|
|
2723
|
+
}
|
|
2724
|
+
const envelope = payload;
|
|
2725
|
+
if (typeof envelope.acp !== "object" || envelope.acp === null || typeof envelope.acpContext !== "object" || envelope.acpContext === null) {
|
|
2726
|
+
return false;
|
|
2727
|
+
}
|
|
2728
|
+
const acpContext = envelope.acpContext;
|
|
2729
|
+
return typeof acpContext.streamId === "string";
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
// src/acp/stream.ts
|
|
2733
|
+
var ACPStreamConnection = class extends events.EventEmitter {
|
|
2734
|
+
#mapClient;
|
|
2735
|
+
#options;
|
|
2736
|
+
#streamId;
|
|
2737
|
+
#pendingRequests = /* @__PURE__ */ new Map();
|
|
2738
|
+
#subscription = null;
|
|
2739
|
+
#sessionId = null;
|
|
2740
|
+
#initialized = false;
|
|
2741
|
+
#capabilities = null;
|
|
2742
|
+
#closed = false;
|
|
2743
|
+
#lastEventId = null;
|
|
2744
|
+
#isReconnecting = false;
|
|
2745
|
+
#unsubscribeReconnection = null;
|
|
2746
|
+
/**
|
|
2747
|
+
* Create a new ACP stream connection.
|
|
2748
|
+
*
|
|
2749
|
+
* @param mapClient - The underlying MAP client connection
|
|
2750
|
+
* @param options - Stream configuration options
|
|
2751
|
+
*/
|
|
2752
|
+
constructor(mapClient, options) {
|
|
2753
|
+
super();
|
|
2754
|
+
this.#mapClient = mapClient;
|
|
2755
|
+
this.#options = options;
|
|
2756
|
+
this.#streamId = `acp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2757
|
+
this.#unsubscribeReconnection = mapClient.onReconnection((event) => {
|
|
2758
|
+
void this.#handleReconnectionEvent(event);
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
// ===========================================================================
|
|
2762
|
+
// Public Properties
|
|
2763
|
+
// ===========================================================================
|
|
2764
|
+
/** Unique identifier for this ACP stream */
|
|
2765
|
+
get streamId() {
|
|
2766
|
+
return this.#streamId;
|
|
2767
|
+
}
|
|
2768
|
+
/** Target agent this stream connects to */
|
|
2769
|
+
get targetAgent() {
|
|
2770
|
+
return this.#options.targetAgent;
|
|
2771
|
+
}
|
|
2772
|
+
/** Current ACP session ID (null until newSession called) */
|
|
2773
|
+
get sessionId() {
|
|
2774
|
+
return this.#sessionId;
|
|
2775
|
+
}
|
|
2776
|
+
/** Whether initialize() has been called */
|
|
2777
|
+
get initialized() {
|
|
2778
|
+
return this.#initialized;
|
|
2779
|
+
}
|
|
2780
|
+
/** Agent capabilities from initialize response */
|
|
2781
|
+
get capabilities() {
|
|
2782
|
+
return this.#capabilities;
|
|
2783
|
+
}
|
|
2784
|
+
/** Whether the stream is closed */
|
|
2785
|
+
get isClosed() {
|
|
2786
|
+
return this.#closed;
|
|
2787
|
+
}
|
|
2788
|
+
/** Last processed event ID (for reconnection support) */
|
|
2789
|
+
get lastEventId() {
|
|
2790
|
+
return this.#lastEventId;
|
|
2791
|
+
}
|
|
2792
|
+
/** Whether the stream is currently reconnecting */
|
|
2793
|
+
get isReconnecting() {
|
|
2794
|
+
return this.#isReconnecting;
|
|
2795
|
+
}
|
|
2796
|
+
// ===========================================================================
|
|
2797
|
+
// Reconnection Handling
|
|
2798
|
+
// ===========================================================================
|
|
2799
|
+
/**
|
|
2800
|
+
* Handle MAP reconnection events.
|
|
2801
|
+
*/
|
|
2802
|
+
async #handleReconnectionEvent(event) {
|
|
2803
|
+
if (this.#closed) return;
|
|
2804
|
+
switch (event.type) {
|
|
2805
|
+
case "disconnected":
|
|
2806
|
+
this.#isReconnecting = true;
|
|
2807
|
+
this.emit("reconnecting");
|
|
2808
|
+
for (const [id, pending] of this.#pendingRequests) {
|
|
2809
|
+
clearTimeout(pending.timeout);
|
|
2810
|
+
pending.reject(new Error("Connection lost during ACP request"));
|
|
2811
|
+
this.#pendingRequests.delete(id);
|
|
2812
|
+
}
|
|
2813
|
+
break;
|
|
2814
|
+
case "reconnected":
|
|
2815
|
+
await this.#handleReconnected();
|
|
2816
|
+
break;
|
|
2817
|
+
case "reconnectFailed":
|
|
2818
|
+
this.#isReconnecting = false;
|
|
2819
|
+
this.emit("error", event.error ?? new Error("MAP reconnection failed"));
|
|
2820
|
+
break;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* Handle successful MAP reconnection.
|
|
2825
|
+
*/
|
|
2826
|
+
async #handleReconnected() {
|
|
2827
|
+
this.#subscription = null;
|
|
2828
|
+
try {
|
|
2829
|
+
await this.#setupSubscription();
|
|
2830
|
+
if (this.#sessionId) {
|
|
2831
|
+
const sessionValid = await this.#verifySessionValid();
|
|
2832
|
+
if (!sessionValid) {
|
|
2833
|
+
const lostSessionId = this.#sessionId;
|
|
2834
|
+
this.#sessionId = null;
|
|
2835
|
+
this.emit("sessionLost", {
|
|
2836
|
+
sessionId: lostSessionId,
|
|
2837
|
+
reason: "Session no longer valid after reconnection"
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
this.#isReconnecting = false;
|
|
2842
|
+
this.emit("reconnected");
|
|
2843
|
+
} catch (error) {
|
|
2844
|
+
this.#isReconnecting = false;
|
|
2845
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Verify that the current ACP session is still valid.
|
|
2850
|
+
*
|
|
2851
|
+
* Since ACP doesn't have a dedicated status check method, we attempt
|
|
2852
|
+
* a lightweight operation (cancel with no effect) to verify the session.
|
|
2853
|
+
*/
|
|
2854
|
+
async #verifySessionValid() {
|
|
2855
|
+
if (!this.#sessionId) return false;
|
|
2856
|
+
try {
|
|
2857
|
+
await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
|
|
2858
|
+
sessionId: this.#sessionId,
|
|
2859
|
+
reason: "session_verification"
|
|
2860
|
+
});
|
|
2861
|
+
return true;
|
|
2862
|
+
} catch {
|
|
2863
|
+
return false;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
// ===========================================================================
|
|
2867
|
+
// Internal Methods
|
|
2868
|
+
// ===========================================================================
|
|
2869
|
+
/**
|
|
2870
|
+
* Set up the subscription for receiving messages from the target agent.
|
|
2871
|
+
*/
|
|
2872
|
+
async #setupSubscription() {
|
|
2873
|
+
if (this.#subscription) return;
|
|
2874
|
+
this.#subscription = await this.#mapClient.subscribe({
|
|
2875
|
+
fromAgents: [this.#options.targetAgent]
|
|
2876
|
+
});
|
|
2877
|
+
void this.#processEvents();
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Process incoming events from the subscription.
|
|
2881
|
+
*/
|
|
2882
|
+
async #processEvents() {
|
|
2883
|
+
if (!this.#subscription) return;
|
|
2884
|
+
try {
|
|
2885
|
+
for await (const event of this.#subscription) {
|
|
2886
|
+
if (this.#closed) break;
|
|
2887
|
+
if (event.id) {
|
|
2888
|
+
this.#lastEventId = event.id;
|
|
2889
|
+
}
|
|
2890
|
+
if (event.type === "message_delivered" && event.data) {
|
|
2891
|
+
const message = event.data.message;
|
|
2892
|
+
if (message?.payload) {
|
|
2893
|
+
await this.#handleIncomingMessage(message);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
} catch (error) {
|
|
2898
|
+
if (!this.#closed) {
|
|
2899
|
+
this.emit("error", error);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
/**
|
|
2904
|
+
* Handle an incoming message from the target agent.
|
|
2905
|
+
*/
|
|
2906
|
+
async #handleIncomingMessage(message) {
|
|
2907
|
+
const payload = message.payload;
|
|
2908
|
+
if (!isACPEnvelope(payload)) return;
|
|
2909
|
+
const envelope = payload;
|
|
2910
|
+
const { acp, acpContext } = envelope;
|
|
2911
|
+
if (acpContext.streamId !== this.#streamId) return;
|
|
2912
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
2913
|
+
const requestId = String(acp.id);
|
|
2914
|
+
const pending = this.#pendingRequests.get(requestId);
|
|
2915
|
+
if (pending) {
|
|
2916
|
+
clearTimeout(pending.timeout);
|
|
2917
|
+
this.#pendingRequests.delete(requestId);
|
|
2918
|
+
if (acp.error) {
|
|
2919
|
+
pending.reject(ACPError.fromResponse(acp.error));
|
|
2920
|
+
} else {
|
|
2921
|
+
pending.resolve(acp.result);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return;
|
|
2925
|
+
}
|
|
2926
|
+
if (acp.method && acp.id === void 0) {
|
|
2927
|
+
await this.#handleNotification(acp.method, acp.params, acpContext);
|
|
2928
|
+
return;
|
|
2929
|
+
}
|
|
2930
|
+
if (acp.method && acp.id !== void 0) {
|
|
2931
|
+
await this.#handleAgentRequest(acp.id, acp.method, acp.params, acpContext, message);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Handle an ACP notification from the agent.
|
|
2936
|
+
*/
|
|
2937
|
+
async #handleNotification(method, params, _acpContext) {
|
|
2938
|
+
if (method === ACP_METHODS.SESSION_UPDATE) {
|
|
2939
|
+
await this.#options.client.sessionUpdate(params);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Handle an agent→client request.
|
|
2944
|
+
*/
|
|
2945
|
+
async #handleAgentRequest(requestId, method, params, _ctx, originalMessage) {
|
|
2946
|
+
let result;
|
|
2947
|
+
let error;
|
|
2948
|
+
try {
|
|
2949
|
+
switch (method) {
|
|
2950
|
+
case ACP_METHODS.REQUEST_PERMISSION:
|
|
2951
|
+
result = await this.#options.client.requestPermission(
|
|
2952
|
+
params
|
|
2953
|
+
);
|
|
2954
|
+
break;
|
|
2955
|
+
case ACP_METHODS.FS_READ_TEXT_FILE:
|
|
2956
|
+
if (!this.#options.client.readTextFile) {
|
|
2957
|
+
throw new ACPError(-32601, "Method not supported: fs/read_text_file");
|
|
2958
|
+
}
|
|
2959
|
+
result = await this.#options.client.readTextFile(
|
|
2960
|
+
params
|
|
2961
|
+
);
|
|
2962
|
+
break;
|
|
2963
|
+
case ACP_METHODS.FS_WRITE_TEXT_FILE:
|
|
2964
|
+
if (!this.#options.client.writeTextFile) {
|
|
2965
|
+
throw new ACPError(-32601, "Method not supported: fs/write_text_file");
|
|
2966
|
+
}
|
|
2967
|
+
result = await this.#options.client.writeTextFile(
|
|
2968
|
+
params
|
|
2969
|
+
);
|
|
2970
|
+
break;
|
|
2971
|
+
case ACP_METHODS.TERMINAL_CREATE:
|
|
2972
|
+
if (!this.#options.client.createTerminal) {
|
|
2973
|
+
throw new ACPError(-32601, "Method not supported: terminal/create");
|
|
2974
|
+
}
|
|
2975
|
+
result = await this.#options.client.createTerminal(
|
|
2976
|
+
params
|
|
2977
|
+
);
|
|
2978
|
+
break;
|
|
2979
|
+
case ACP_METHODS.TERMINAL_OUTPUT:
|
|
2980
|
+
if (!this.#options.client.terminalOutput) {
|
|
2981
|
+
throw new ACPError(-32601, "Method not supported: terminal/output");
|
|
2982
|
+
}
|
|
2983
|
+
result = await this.#options.client.terminalOutput(
|
|
2984
|
+
params
|
|
2985
|
+
);
|
|
2986
|
+
break;
|
|
2987
|
+
case ACP_METHODS.TERMINAL_RELEASE:
|
|
2988
|
+
if (!this.#options.client.releaseTerminal) {
|
|
2989
|
+
throw new ACPError(-32601, "Method not supported: terminal/release");
|
|
2990
|
+
}
|
|
2991
|
+
result = await this.#options.client.releaseTerminal(
|
|
2992
|
+
params
|
|
2993
|
+
);
|
|
2994
|
+
break;
|
|
2995
|
+
case ACP_METHODS.TERMINAL_WAIT_FOR_EXIT:
|
|
2996
|
+
if (!this.#options.client.waitForTerminalExit) {
|
|
2997
|
+
throw new ACPError(-32601, "Method not supported: terminal/wait_for_exit");
|
|
2998
|
+
}
|
|
2999
|
+
result = await this.#options.client.waitForTerminalExit(
|
|
3000
|
+
params
|
|
3001
|
+
);
|
|
3002
|
+
break;
|
|
3003
|
+
case ACP_METHODS.TERMINAL_KILL:
|
|
3004
|
+
if (!this.#options.client.killTerminal) {
|
|
3005
|
+
throw new ACPError(-32601, "Method not supported: terminal/kill");
|
|
3006
|
+
}
|
|
3007
|
+
result = await this.#options.client.killTerminal(
|
|
3008
|
+
params
|
|
3009
|
+
);
|
|
3010
|
+
break;
|
|
3011
|
+
default:
|
|
3012
|
+
throw new ACPError(-32601, `Unknown method: ${method}`);
|
|
3013
|
+
}
|
|
3014
|
+
} catch (e) {
|
|
3015
|
+
if (e instanceof ACPError) {
|
|
3016
|
+
error = e;
|
|
3017
|
+
} else {
|
|
3018
|
+
error = new ACPError(-32603, e.message);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
const responseEnvelope = {
|
|
3022
|
+
acp: {
|
|
3023
|
+
jsonrpc: "2.0",
|
|
3024
|
+
id: requestId,
|
|
3025
|
+
...error ? { error: error.toErrorObject() } : { result }
|
|
3026
|
+
},
|
|
3027
|
+
acpContext: {
|
|
3028
|
+
streamId: this.#streamId,
|
|
3029
|
+
sessionId: this.#sessionId,
|
|
3030
|
+
direction: "client-to-agent"
|
|
3031
|
+
}
|
|
3032
|
+
};
|
|
3033
|
+
await this.#mapClient.send(
|
|
3034
|
+
{ agent: this.#options.targetAgent },
|
|
3035
|
+
responseEnvelope,
|
|
3036
|
+
{
|
|
3037
|
+
protocol: "acp",
|
|
3038
|
+
correlationId: originalMessage.id
|
|
3039
|
+
}
|
|
3040
|
+
);
|
|
3041
|
+
}
|
|
3042
|
+
/**
|
|
3043
|
+
* Send an ACP request and wait for response.
|
|
3044
|
+
*/
|
|
3045
|
+
async #sendRequest(method, params) {
|
|
3046
|
+
if (this.#closed) {
|
|
3047
|
+
throw new Error("ACP stream is closed");
|
|
3048
|
+
}
|
|
3049
|
+
await this.#setupSubscription();
|
|
3050
|
+
if (this.#closed) {
|
|
3051
|
+
throw new Error("ACP stream closed");
|
|
3052
|
+
}
|
|
3053
|
+
const correlationId = `${this.#streamId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3054
|
+
const timeout = this.#options.timeout ?? 3e4;
|
|
3055
|
+
const envelope = {
|
|
3056
|
+
acp: {
|
|
3057
|
+
jsonrpc: "2.0",
|
|
3058
|
+
id: correlationId,
|
|
3059
|
+
method,
|
|
3060
|
+
...params !== void 0 && { params }
|
|
3061
|
+
},
|
|
3062
|
+
acpContext: {
|
|
3063
|
+
streamId: this.#streamId,
|
|
3064
|
+
sessionId: this.#sessionId,
|
|
3065
|
+
direction: "client-to-agent"
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
3069
|
+
const timeoutHandle = setTimeout(() => {
|
|
3070
|
+
this.#pendingRequests.delete(correlationId);
|
|
3071
|
+
reject(new Error(`ACP request timed out after ${timeout}ms: ${method}`));
|
|
3072
|
+
}, timeout);
|
|
3073
|
+
this.#pendingRequests.set(correlationId, {
|
|
3074
|
+
resolve,
|
|
3075
|
+
reject,
|
|
3076
|
+
timeout: timeoutHandle,
|
|
3077
|
+
method
|
|
3078
|
+
});
|
|
3079
|
+
});
|
|
3080
|
+
try {
|
|
3081
|
+
await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
|
|
3082
|
+
protocol: "acp",
|
|
3083
|
+
correlationId
|
|
3084
|
+
});
|
|
3085
|
+
} catch (err) {
|
|
3086
|
+
const pending = this.#pendingRequests.get(correlationId);
|
|
3087
|
+
if (pending) {
|
|
3088
|
+
clearTimeout(pending.timeout);
|
|
3089
|
+
this.#pendingRequests.delete(correlationId);
|
|
3090
|
+
}
|
|
3091
|
+
throw err;
|
|
3092
|
+
}
|
|
3093
|
+
if (this.#closed && !this.#pendingRequests.has(correlationId)) {
|
|
3094
|
+
throw new Error("ACP stream closed");
|
|
3095
|
+
}
|
|
3096
|
+
return resultPromise;
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Send an ACP notification (no response expected).
|
|
3100
|
+
*/
|
|
3101
|
+
async #sendNotification(method, params) {
|
|
3102
|
+
if (this.#closed) {
|
|
3103
|
+
throw new Error("ACP stream is closed");
|
|
3104
|
+
}
|
|
3105
|
+
await this.#setupSubscription();
|
|
3106
|
+
const envelope = {
|
|
3107
|
+
acp: {
|
|
3108
|
+
jsonrpc: "2.0",
|
|
3109
|
+
method,
|
|
3110
|
+
...params !== void 0 && { params }
|
|
3111
|
+
},
|
|
3112
|
+
acpContext: {
|
|
3113
|
+
streamId: this.#streamId,
|
|
3114
|
+
sessionId: this.#sessionId,
|
|
3115
|
+
direction: "client-to-agent"
|
|
3116
|
+
}
|
|
3117
|
+
};
|
|
3118
|
+
await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
|
|
3119
|
+
protocol: "acp"
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
// ===========================================================================
|
|
3123
|
+
// ACP Lifecycle Methods
|
|
3124
|
+
// ===========================================================================
|
|
3125
|
+
/**
|
|
3126
|
+
* Initialize the ACP connection with the target agent.
|
|
3127
|
+
*/
|
|
3128
|
+
async initialize(params) {
|
|
3129
|
+
if (this.#initialized) {
|
|
3130
|
+
throw new Error("ACP stream already initialized");
|
|
3131
|
+
}
|
|
3132
|
+
const result = await this.#sendRequest(
|
|
3133
|
+
ACP_METHODS.INITIALIZE,
|
|
3134
|
+
params
|
|
3135
|
+
);
|
|
3136
|
+
this.#initialized = true;
|
|
3137
|
+
this.#capabilities = result.agentCapabilities ?? null;
|
|
3138
|
+
return result;
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* Authenticate with the agent.
|
|
3142
|
+
*/
|
|
3143
|
+
async authenticate(params) {
|
|
3144
|
+
if (!this.#initialized) {
|
|
3145
|
+
throw new Error("Must call initialize() before authenticate()");
|
|
3146
|
+
}
|
|
3147
|
+
return this.#sendRequest(
|
|
3148
|
+
ACP_METHODS.AUTHENTICATE,
|
|
3149
|
+
params
|
|
3150
|
+
);
|
|
3151
|
+
}
|
|
3152
|
+
// ===========================================================================
|
|
3153
|
+
// ACP Session Methods
|
|
3154
|
+
// ===========================================================================
|
|
3155
|
+
/**
|
|
3156
|
+
* Create a new ACP session.
|
|
3157
|
+
*/
|
|
3158
|
+
async newSession(params) {
|
|
3159
|
+
if (!this.#initialized) {
|
|
3160
|
+
throw new Error("Must call initialize() before newSession()");
|
|
3161
|
+
}
|
|
3162
|
+
const result = await this.#sendRequest(
|
|
3163
|
+
ACP_METHODS.SESSION_NEW,
|
|
3164
|
+
params
|
|
3165
|
+
);
|
|
3166
|
+
this.#sessionId = result.sessionId;
|
|
3167
|
+
return result;
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* Load an existing ACP session.
|
|
3171
|
+
*/
|
|
3172
|
+
async loadSession(params) {
|
|
3173
|
+
if (!this.#initialized) {
|
|
3174
|
+
throw new Error("Must call initialize() before loadSession()");
|
|
3175
|
+
}
|
|
3176
|
+
const result = await this.#sendRequest(
|
|
3177
|
+
ACP_METHODS.SESSION_LOAD,
|
|
3178
|
+
params
|
|
3179
|
+
);
|
|
3180
|
+
this.#sessionId = params.sessionId;
|
|
3181
|
+
return result;
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Set the session mode.
|
|
3185
|
+
*/
|
|
3186
|
+
async setSessionMode(params) {
|
|
3187
|
+
return this.#sendRequest(
|
|
3188
|
+
ACP_METHODS.SESSION_SET_MODE,
|
|
3189
|
+
params
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
// ===========================================================================
|
|
3193
|
+
// ACP Prompt Methods
|
|
3194
|
+
// ===========================================================================
|
|
3195
|
+
/**
|
|
3196
|
+
* Send a prompt to the agent.
|
|
3197
|
+
* Updates are received via the sessionUpdate handler.
|
|
3198
|
+
*/
|
|
3199
|
+
async prompt(params) {
|
|
3200
|
+
if (!this.#sessionId) {
|
|
3201
|
+
throw new Error("Must call newSession() or loadSession() before prompt()");
|
|
3202
|
+
}
|
|
3203
|
+
return this.#sendRequest(
|
|
3204
|
+
ACP_METHODS.SESSION_PROMPT,
|
|
3205
|
+
params
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
/**
|
|
3209
|
+
* Cancel ongoing operations for the current session.
|
|
3210
|
+
*/
|
|
3211
|
+
async cancel(params) {
|
|
3212
|
+
if (!this.#sessionId) {
|
|
3213
|
+
throw new Error("No active session to cancel");
|
|
3214
|
+
}
|
|
3215
|
+
await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
|
|
3216
|
+
sessionId: this.#sessionId,
|
|
3217
|
+
...params
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
// ===========================================================================
|
|
3221
|
+
// Extension Methods
|
|
3222
|
+
// ===========================================================================
|
|
3223
|
+
/**
|
|
3224
|
+
* Call an ACP extension method on the target agent.
|
|
3225
|
+
*
|
|
3226
|
+
* Extension methods are prefixed with "_" (e.g., "_macro/spawnAgent").
|
|
3227
|
+
* The agent must support the extension for this to succeed.
|
|
3228
|
+
*
|
|
3229
|
+
* @param method - The extension method name (e.g., "_macro/spawnAgent")
|
|
3230
|
+
* @param params - Parameters to pass to the extension method
|
|
3231
|
+
* @returns The result from the extension method
|
|
3232
|
+
*
|
|
3233
|
+
* @example
|
|
3234
|
+
* ```typescript
|
|
3235
|
+
* const result = await acp.callExtension("_macro/spawnAgent", {
|
|
3236
|
+
* task: "Implement feature X",
|
|
3237
|
+
* cwd: "/project"
|
|
3238
|
+
* });
|
|
3239
|
+
* ```
|
|
3240
|
+
*/
|
|
3241
|
+
async callExtension(method, params) {
|
|
3242
|
+
if (!this.#initialized) {
|
|
3243
|
+
throw new Error("Must call initialize() before callExtension()");
|
|
3244
|
+
}
|
|
3245
|
+
return this.#sendRequest(method, params);
|
|
3246
|
+
}
|
|
3247
|
+
// ===========================================================================
|
|
3248
|
+
// Lifecycle
|
|
3249
|
+
// ===========================================================================
|
|
3250
|
+
/**
|
|
3251
|
+
* Close this ACP stream and clean up resources.
|
|
3252
|
+
*/
|
|
3253
|
+
async close() {
|
|
3254
|
+
if (this.#closed) return;
|
|
3255
|
+
this.#closed = true;
|
|
3256
|
+
if (this.#unsubscribeReconnection) {
|
|
3257
|
+
this.#unsubscribeReconnection();
|
|
3258
|
+
this.#unsubscribeReconnection = null;
|
|
3259
|
+
}
|
|
3260
|
+
for (const [id, pending] of this.#pendingRequests) {
|
|
3261
|
+
clearTimeout(pending.timeout);
|
|
3262
|
+
pending.reject(new Error("ACP stream closed"));
|
|
3263
|
+
this.#pendingRequests.delete(id);
|
|
3264
|
+
}
|
|
3265
|
+
if (this.#subscription) {
|
|
3266
|
+
await this.#subscription.unsubscribe();
|
|
3267
|
+
this.#subscription = null;
|
|
3268
|
+
}
|
|
3269
|
+
this.emit("close");
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
|
|
2557
3273
|
// src/connection/client.ts
|
|
2558
3274
|
var ClientConnection = class _ClientConnection {
|
|
2559
3275
|
#connection;
|
|
2560
3276
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
2561
3277
|
#subscriptionStates = /* @__PURE__ */ new Map();
|
|
2562
3278
|
#reconnectionHandlers = /* @__PURE__ */ new Set();
|
|
3279
|
+
#acpStreams = /* @__PURE__ */ new Map();
|
|
2563
3280
|
#options;
|
|
2564
3281
|
#sessionId = null;
|
|
2565
3282
|
#serverCapabilities = null;
|
|
2566
3283
|
#connected = false;
|
|
2567
3284
|
#lastConnectOptions;
|
|
2568
3285
|
#isReconnecting = false;
|
|
3286
|
+
#lastResumeToken;
|
|
3287
|
+
#onTokenExpiring;
|
|
2569
3288
|
constructor(stream, options = {}) {
|
|
2570
3289
|
this.#connection = new BaseConnection(stream, options);
|
|
2571
3290
|
this.#options = options;
|
|
@@ -2631,11 +3350,70 @@ var ClientConnection = class _ClientConnection {
|
|
|
2631
3350
|
await client.connect({ auth: options?.auth });
|
|
2632
3351
|
return client;
|
|
2633
3352
|
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Connect to a MAP server via agentic-mesh transport.
|
|
3355
|
+
*
|
|
3356
|
+
* Handles:
|
|
3357
|
+
* - Dynamic import of agentic-mesh (optional peer dependency)
|
|
3358
|
+
* - Stream creation over encrypted mesh tunnel
|
|
3359
|
+
* - Auto-configuration of createStream for reconnection
|
|
3360
|
+
* - Initial MAP protocol connect handshake
|
|
3361
|
+
*
|
|
3362
|
+
* Requires `agentic-mesh` to be installed as a peer dependency.
|
|
3363
|
+
*
|
|
3364
|
+
* @param options - Mesh connection options
|
|
3365
|
+
* @returns Connected ClientConnection instance
|
|
3366
|
+
*
|
|
3367
|
+
* @example
|
|
3368
|
+
* ```typescript
|
|
3369
|
+
* import { createNebulaTransport } from 'agentic-mesh';
|
|
3370
|
+
*
|
|
3371
|
+
* const transport = createNebulaTransport({
|
|
3372
|
+
* configPath: '/etc/nebula/config.yml',
|
|
3373
|
+
* });
|
|
3374
|
+
*
|
|
3375
|
+
* const client = await ClientConnection.connectMesh({
|
|
3376
|
+
* transport,
|
|
3377
|
+
* peer: { peerId: 'server', address: '10.0.0.1', port: 4242 },
|
|
3378
|
+
* localPeerId: 'my-client',
|
|
3379
|
+
* name: 'MeshClient',
|
|
3380
|
+
* reconnection: true
|
|
3381
|
+
* });
|
|
3382
|
+
*
|
|
3383
|
+
* const agents = await client.listAgents();
|
|
3384
|
+
* ```
|
|
3385
|
+
*/
|
|
3386
|
+
static async connectMesh(options) {
|
|
3387
|
+
const { agenticMeshStream: agenticMeshStream2 } = await Promise.resolve().then(() => (init_agentic_mesh(), agentic_mesh_exports));
|
|
3388
|
+
const streamConfig = {
|
|
3389
|
+
transport: options.transport,
|
|
3390
|
+
peer: options.peer,
|
|
3391
|
+
localPeerId: options.localPeerId,
|
|
3392
|
+
timeout: options.timeout
|
|
3393
|
+
};
|
|
3394
|
+
const stream = await agenticMeshStream2(streamConfig);
|
|
3395
|
+
const createStream = async () => agenticMeshStream2(streamConfig);
|
|
3396
|
+
const reconnection = options.reconnection === true ? { enabled: true } : typeof options.reconnection === "object" ? options.reconnection : void 0;
|
|
3397
|
+
const client = new _ClientConnection(stream, {
|
|
3398
|
+
name: options.name,
|
|
3399
|
+
capabilities: options.capabilities,
|
|
3400
|
+
createStream,
|
|
3401
|
+
reconnection
|
|
3402
|
+
});
|
|
3403
|
+
await client.connect({ auth: options.auth });
|
|
3404
|
+
return client;
|
|
3405
|
+
}
|
|
2634
3406
|
// ===========================================================================
|
|
2635
3407
|
// Connection Lifecycle
|
|
2636
3408
|
// ===========================================================================
|
|
2637
3409
|
/**
|
|
2638
3410
|
* Connect to the MAP system
|
|
3411
|
+
*
|
|
3412
|
+
* @param options - Connection options
|
|
3413
|
+
* @param options.sessionId - Specific session ID to use
|
|
3414
|
+
* @param options.resumeToken - Token to resume a previously disconnected session
|
|
3415
|
+
* @param options.auth - Authentication credentials
|
|
3416
|
+
* @param options.onTokenExpiring - Callback invoked before token expires for proactive refresh
|
|
2639
3417
|
*/
|
|
2640
3418
|
async connect(options) {
|
|
2641
3419
|
const params = {
|
|
@@ -2651,10 +3429,134 @@ var ClientConnection = class _ClientConnection {
|
|
|
2651
3429
|
this.#sessionId = result.sessionId;
|
|
2652
3430
|
this.#serverCapabilities = result.capabilities;
|
|
2653
3431
|
this.#connected = true;
|
|
3432
|
+
if (result.resumeToken) {
|
|
3433
|
+
this.#lastResumeToken = result.resumeToken;
|
|
3434
|
+
}
|
|
3435
|
+
if (options?.onTokenExpiring) {
|
|
3436
|
+
this.#onTokenExpiring = options.onTokenExpiring;
|
|
3437
|
+
this.#setupTokenExpiryMonitoring(result);
|
|
3438
|
+
}
|
|
2654
3439
|
this.#connection._transitionTo("connected");
|
|
2655
3440
|
this.#lastConnectOptions = options;
|
|
2656
3441
|
return result;
|
|
2657
3442
|
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Get the resume token for this session.
|
|
3445
|
+
* Can be used to reconnect and restore session state after disconnection.
|
|
3446
|
+
*
|
|
3447
|
+
* @returns The resume token, or undefined if not available
|
|
3448
|
+
*/
|
|
3449
|
+
getResumeToken() {
|
|
3450
|
+
return this.#lastResumeToken;
|
|
3451
|
+
}
|
|
3452
|
+
/**
|
|
3453
|
+
* Reconnect to the server, optionally using a resume token to restore session.
|
|
3454
|
+
*
|
|
3455
|
+
* @param resumeToken - Token to resume previous session. If not provided, uses the last known token.
|
|
3456
|
+
* @returns Connect response result
|
|
3457
|
+
*
|
|
3458
|
+
* @example
|
|
3459
|
+
* ```typescript
|
|
3460
|
+
* // Save token before disconnect
|
|
3461
|
+
* const token = await client.disconnect();
|
|
3462
|
+
*
|
|
3463
|
+
* // Later, reconnect with the token
|
|
3464
|
+
* const result = await client.reconnect(token);
|
|
3465
|
+
* console.log('Reconnected:', result.reconnected);
|
|
3466
|
+
* ```
|
|
3467
|
+
*/
|
|
3468
|
+
async reconnect(resumeToken) {
|
|
3469
|
+
const tokenToUse = resumeToken ?? this.#lastResumeToken;
|
|
3470
|
+
return this.connect({
|
|
3471
|
+
...this.#lastConnectOptions,
|
|
3472
|
+
resumeToken: tokenToUse
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
/**
|
|
3476
|
+
* Set up monitoring for token expiration
|
|
3477
|
+
*/
|
|
3478
|
+
#setupTokenExpiryMonitoring(connectResult) {
|
|
3479
|
+
const principal = connectResult.principal;
|
|
3480
|
+
if (!principal?.expiresAt || !this.#onTokenExpiring) {
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
const expiresAt = principal.expiresAt;
|
|
3484
|
+
const now = Date.now();
|
|
3485
|
+
const warningTime = expiresAt - 6e4;
|
|
3486
|
+
const delay = warningTime - now;
|
|
3487
|
+
if (delay > 0) {
|
|
3488
|
+
setTimeout(async () => {
|
|
3489
|
+
if (!this.#connected || !this.#onTokenExpiring) return;
|
|
3490
|
+
try {
|
|
3491
|
+
const newCredentials = await this.#onTokenExpiring(expiresAt);
|
|
3492
|
+
if (newCredentials) {
|
|
3493
|
+
const refreshResult = await this.refreshAuth({
|
|
3494
|
+
method: newCredentials.method,
|
|
3495
|
+
credential: newCredentials.credential
|
|
3496
|
+
});
|
|
3497
|
+
if (refreshResult.success && refreshResult.principal?.expiresAt) {
|
|
3498
|
+
this.#setupTokenExpiryMonitoring({
|
|
3499
|
+
...connectResult,
|
|
3500
|
+
principal: refreshResult.principal
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
} catch {
|
|
3505
|
+
}
|
|
3506
|
+
}, delay);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
/**
|
|
3510
|
+
* Authenticate with the server after connection.
|
|
3511
|
+
*
|
|
3512
|
+
* Use this when the server returns `authRequired` in the connect response,
|
|
3513
|
+
* indicating that authentication is needed before accessing protected resources.
|
|
3514
|
+
*
|
|
3515
|
+
* @param auth - Authentication credentials
|
|
3516
|
+
* @returns Authentication result with principal if successful
|
|
3517
|
+
*
|
|
3518
|
+
* @example
|
|
3519
|
+
* ```typescript
|
|
3520
|
+
* const connectResult = await client.connect();
|
|
3521
|
+
*
|
|
3522
|
+
* if (connectResult.authRequired) {
|
|
3523
|
+
* const authResult = await client.authenticate({
|
|
3524
|
+
* method: 'api-key',
|
|
3525
|
+
* credential: process.env.API_KEY,
|
|
3526
|
+
* });
|
|
3527
|
+
*
|
|
3528
|
+
* if (authResult.success) {
|
|
3529
|
+
* console.log('Authenticated as:', authResult.principal?.id);
|
|
3530
|
+
* }
|
|
3531
|
+
* }
|
|
3532
|
+
* ```
|
|
3533
|
+
*/
|
|
3534
|
+
async authenticate(auth) {
|
|
3535
|
+
const params = {
|
|
3536
|
+
method: auth.method,
|
|
3537
|
+
credential: auth.credential
|
|
3538
|
+
};
|
|
3539
|
+
const result = await this.#connection.sendRequest(AUTH_METHODS.AUTHENTICATE, params);
|
|
3540
|
+
if (result.success && result.sessionId) {
|
|
3541
|
+
this.#sessionId = result.sessionId;
|
|
3542
|
+
}
|
|
3543
|
+
return result;
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Refresh authentication credentials.
|
|
3547
|
+
*
|
|
3548
|
+
* Use this to update credentials before they expire for long-lived connections.
|
|
3549
|
+
*
|
|
3550
|
+
* @param auth - New authentication credentials
|
|
3551
|
+
* @returns Updated principal information
|
|
3552
|
+
*/
|
|
3553
|
+
async refreshAuth(auth) {
|
|
3554
|
+
const params = {
|
|
3555
|
+
method: auth.method,
|
|
3556
|
+
credential: auth.credential
|
|
3557
|
+
};
|
|
3558
|
+
return this.#connection.sendRequest(AUTH_METHODS.AUTH_REFRESH, params);
|
|
3559
|
+
}
|
|
2658
3560
|
/**
|
|
2659
3561
|
* Disconnect from the MAP system
|
|
2660
3562
|
* @param reason - Optional reason for disconnecting
|
|
@@ -2669,7 +3571,14 @@ var ClientConnection = class _ClientConnection {
|
|
|
2669
3571
|
reason ? { reason } : void 0
|
|
2670
3572
|
);
|
|
2671
3573
|
resumeToken = result.resumeToken;
|
|
3574
|
+
if (resumeToken) {
|
|
3575
|
+
this.#lastResumeToken = resumeToken;
|
|
3576
|
+
}
|
|
2672
3577
|
} finally {
|
|
3578
|
+
for (const stream of this.#acpStreams.values()) {
|
|
3579
|
+
await stream.close();
|
|
3580
|
+
}
|
|
3581
|
+
this.#acpStreams.clear();
|
|
2673
3582
|
for (const subscription of this.#subscriptions.values()) {
|
|
2674
3583
|
subscription._close();
|
|
2675
3584
|
}
|
|
@@ -2851,6 +3760,65 @@ var ClientConnection = class _ClientConnection {
|
|
|
2851
3760
|
}
|
|
2852
3761
|
}
|
|
2853
3762
|
// ===========================================================================
|
|
3763
|
+
// ACP Streams
|
|
3764
|
+
// ===========================================================================
|
|
3765
|
+
/**
|
|
3766
|
+
* Create a virtual ACP stream connection to an agent.
|
|
3767
|
+
*
|
|
3768
|
+
* This allows clients to interact with ACP-compatible agents using the
|
|
3769
|
+
* familiar ACP interface while routing all messages through MAP.
|
|
3770
|
+
*
|
|
3771
|
+
* @param options - Stream configuration options
|
|
3772
|
+
* @returns ACPStreamConnection instance ready for initialize()
|
|
3773
|
+
*
|
|
3774
|
+
* @example
|
|
3775
|
+
* ```typescript
|
|
3776
|
+
* const acp = client.createACPStream({
|
|
3777
|
+
* targetAgent: 'coding-agent-1',
|
|
3778
|
+
* client: {
|
|
3779
|
+
* requestPermission: async (req) => ({
|
|
3780
|
+
* outcome: { outcome: 'selected', optionId: 'allow' }
|
|
3781
|
+
* }),
|
|
3782
|
+
* sessionUpdate: async (update) => {
|
|
3783
|
+
* console.log('Agent update:', update);
|
|
3784
|
+
* }
|
|
3785
|
+
* }
|
|
3786
|
+
* });
|
|
3787
|
+
*
|
|
3788
|
+
* await acp.initialize({
|
|
3789
|
+
* protocolVersion: 20241007,
|
|
3790
|
+
* clientInfo: { name: 'IDE', version: '1.0' }
|
|
3791
|
+
* });
|
|
3792
|
+
* const { sessionId } = await acp.newSession({ cwd: '/project', mcpServers: [] });
|
|
3793
|
+
* const result = await acp.prompt({
|
|
3794
|
+
* sessionId,
|
|
3795
|
+
* prompt: [{ type: 'text', text: 'Hello' }]
|
|
3796
|
+
* });
|
|
3797
|
+
*
|
|
3798
|
+
* await acp.close();
|
|
3799
|
+
* ```
|
|
3800
|
+
*/
|
|
3801
|
+
createACPStream(options) {
|
|
3802
|
+
const stream = new ACPStreamConnection(this, options);
|
|
3803
|
+
this.#acpStreams.set(stream.streamId, stream);
|
|
3804
|
+
stream.on("close", () => {
|
|
3805
|
+
this.#acpStreams.delete(stream.streamId);
|
|
3806
|
+
});
|
|
3807
|
+
return stream;
|
|
3808
|
+
}
|
|
3809
|
+
/**
|
|
3810
|
+
* Get an active ACP stream by ID.
|
|
3811
|
+
*/
|
|
3812
|
+
getACPStream(streamId) {
|
|
3813
|
+
return this.#acpStreams.get(streamId);
|
|
3814
|
+
}
|
|
3815
|
+
/**
|
|
3816
|
+
* Get all active ACP streams.
|
|
3817
|
+
*/
|
|
3818
|
+
get acpStreams() {
|
|
3819
|
+
return this.#acpStreams;
|
|
3820
|
+
}
|
|
3821
|
+
// ===========================================================================
|
|
2854
3822
|
// Subscriptions
|
|
2855
3823
|
// ===========================================================================
|
|
2856
3824
|
/**
|
|
@@ -3490,6 +4458,66 @@ var AgentConnection = class _AgentConnection {
|
|
|
3490
4458
|
await agent.connect({ auth: options?.auth });
|
|
3491
4459
|
return agent;
|
|
3492
4460
|
}
|
|
4461
|
+
/**
|
|
4462
|
+
* Connect and register an agent via agentic-mesh transport.
|
|
4463
|
+
*
|
|
4464
|
+
* Handles:
|
|
4465
|
+
* - Dynamic import of agentic-mesh (optional peer dependency)
|
|
4466
|
+
* - Stream creation over encrypted mesh tunnel
|
|
4467
|
+
* - Auto-configuration of createStream for reconnection
|
|
4468
|
+
* - Initial MAP protocol connect handshake
|
|
4469
|
+
* - Agent registration
|
|
4470
|
+
*
|
|
4471
|
+
* Requires `agentic-mesh` to be installed as a peer dependency.
|
|
4472
|
+
*
|
|
4473
|
+
* @param options - Mesh connection and agent options
|
|
4474
|
+
* @returns Connected and registered AgentConnection instance
|
|
4475
|
+
*
|
|
4476
|
+
* @example
|
|
4477
|
+
* ```typescript
|
|
4478
|
+
* import { createNebulaTransport } from 'agentic-mesh';
|
|
4479
|
+
*
|
|
4480
|
+
* const transport = createNebulaTransport({
|
|
4481
|
+
* configPath: '/etc/nebula/config.yml',
|
|
4482
|
+
* });
|
|
4483
|
+
*
|
|
4484
|
+
* const agent = await AgentConnection.connectMesh({
|
|
4485
|
+
* transport,
|
|
4486
|
+
* peer: { peerId: 'server', address: '10.0.0.1', port: 4242 },
|
|
4487
|
+
* localPeerId: 'my-agent',
|
|
4488
|
+
* name: 'MeshWorker',
|
|
4489
|
+
* role: 'processor',
|
|
4490
|
+
* reconnection: true
|
|
4491
|
+
* });
|
|
4492
|
+
*
|
|
4493
|
+
* agent.onMessage(handleMessage);
|
|
4494
|
+
* await agent.busy();
|
|
4495
|
+
* ```
|
|
4496
|
+
*/
|
|
4497
|
+
static async connectMesh(options) {
|
|
4498
|
+
const { agenticMeshStream: agenticMeshStream2 } = await Promise.resolve().then(() => (init_agentic_mesh(), agentic_mesh_exports));
|
|
4499
|
+
const streamConfig = {
|
|
4500
|
+
transport: options.transport,
|
|
4501
|
+
peer: options.peer,
|
|
4502
|
+
localPeerId: options.localPeerId,
|
|
4503
|
+
timeout: options.timeout
|
|
4504
|
+
};
|
|
4505
|
+
const stream = await agenticMeshStream2(streamConfig);
|
|
4506
|
+
const createStream = async () => agenticMeshStream2(streamConfig);
|
|
4507
|
+
const reconnection = options.reconnection === true ? { enabled: true } : typeof options.reconnection === "object" ? options.reconnection : void 0;
|
|
4508
|
+
const agent = new _AgentConnection(stream, {
|
|
4509
|
+
name: options.name,
|
|
4510
|
+
role: options.role,
|
|
4511
|
+
capabilities: options.capabilities,
|
|
4512
|
+
visibility: options.visibility,
|
|
4513
|
+
parent: options.parent,
|
|
4514
|
+
scopes: options.scopes,
|
|
4515
|
+
createStream,
|
|
4516
|
+
reconnection
|
|
4517
|
+
});
|
|
4518
|
+
await agent.connect({ auth: options.auth });
|
|
4519
|
+
return agent;
|
|
4520
|
+
}
|
|
3493
4521
|
// ===========================================================================
|
|
3494
4522
|
// Connection Lifecycle
|
|
3495
4523
|
// ===========================================================================
|
|
@@ -3526,6 +4554,62 @@ var AgentConnection = class _AgentConnection {
|
|
|
3526
4554
|
this.#connection._transitionTo("connected");
|
|
3527
4555
|
return { connection: connectResult, agent: registerResult.agent };
|
|
3528
4556
|
}
|
|
4557
|
+
/**
|
|
4558
|
+
* Authenticate with the server after connection.
|
|
4559
|
+
*
|
|
4560
|
+
* Use this when the server returns `authRequired` in the connect response,
|
|
4561
|
+
* indicating that authentication is needed before registering or accessing
|
|
4562
|
+
* protected resources.
|
|
4563
|
+
*
|
|
4564
|
+
* @param auth - Authentication credentials
|
|
4565
|
+
* @returns Authentication result with principal if successful
|
|
4566
|
+
*
|
|
4567
|
+
* @example
|
|
4568
|
+
* ```typescript
|
|
4569
|
+
* const agent = new AgentConnection(stream, { name: 'MyAgent' });
|
|
4570
|
+
*
|
|
4571
|
+
* // First connect to get auth requirements
|
|
4572
|
+
* const connectResult = await agent.connectOnly();
|
|
4573
|
+
*
|
|
4574
|
+
* if (connectResult.authRequired) {
|
|
4575
|
+
* const authResult = await agent.authenticate({
|
|
4576
|
+
* method: 'api-key',
|
|
4577
|
+
* token: process.env.AGENT_API_KEY,
|
|
4578
|
+
* });
|
|
4579
|
+
*
|
|
4580
|
+
* if (authResult.success) {
|
|
4581
|
+
* // Now register the agent
|
|
4582
|
+
* await agent.register({ name: 'MyAgent', role: 'worker' });
|
|
4583
|
+
* }
|
|
4584
|
+
* }
|
|
4585
|
+
* ```
|
|
4586
|
+
*/
|
|
4587
|
+
async authenticate(auth) {
|
|
4588
|
+
const params = {
|
|
4589
|
+
method: auth.method,
|
|
4590
|
+
credential: auth.token
|
|
4591
|
+
};
|
|
4592
|
+
const result = await this.#connection.sendRequest(AUTH_METHODS.AUTHENTICATE, params);
|
|
4593
|
+
if (result.success && result.sessionId) {
|
|
4594
|
+
this.#sessionId = result.sessionId;
|
|
4595
|
+
}
|
|
4596
|
+
return result;
|
|
4597
|
+
}
|
|
4598
|
+
/**
|
|
4599
|
+
* Refresh authentication credentials.
|
|
4600
|
+
*
|
|
4601
|
+
* Use this to update credentials before they expire for long-lived connections.
|
|
4602
|
+
*
|
|
4603
|
+
* @param auth - New authentication credentials
|
|
4604
|
+
* @returns Updated principal information
|
|
4605
|
+
*/
|
|
4606
|
+
async refreshAuth(auth) {
|
|
4607
|
+
const params = {
|
|
4608
|
+
method: auth.method,
|
|
4609
|
+
credential: auth.token
|
|
4610
|
+
};
|
|
4611
|
+
return this.#connection.sendRequest(AUTH_METHODS.AUTH_REFRESH, params);
|
|
4612
|
+
}
|
|
3529
4613
|
/**
|
|
3530
4614
|
* Disconnect from the MAP system
|
|
3531
4615
|
* @param reason - Optional reason for disconnecting
|