@multi-agent-protocol/sdk 0.0.3 → 0.0.5
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/dist/{index-C7XPWnxS.d.cts → index-DMcRbXOS.d.cts} +1358 -5
- package/dist/{index-C7XPWnxS.d.ts → index-DMcRbXOS.d.ts} +1358 -5
- package/dist/index.cjs +1319 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +246 -15
- package/dist/index.d.ts +246 -15
- package/dist/index.js +1297 -15
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +1046 -159
- 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 +1046 -159
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var ulid = require('ulid');
|
|
4
|
+
var events = require('events');
|
|
4
5
|
var zod = require('zod');
|
|
5
6
|
|
|
6
7
|
// src/types/index.ts
|
|
@@ -227,9 +228,6 @@ function isDirectAddress(address) {
|
|
|
227
228
|
function isFederatedAddress(address) {
|
|
228
229
|
return typeof address === "object" && "system" in address && "agent" in address;
|
|
229
230
|
}
|
|
230
|
-
function isScopeAddress(address) {
|
|
231
|
-
return typeof address === "object" && "scope" in address;
|
|
232
|
-
}
|
|
233
231
|
function isBroadcastAddress(address) {
|
|
234
232
|
return typeof address === "object" && "broadcast" in address;
|
|
235
233
|
}
|
|
@@ -719,6 +717,30 @@ function websocketStream(ws) {
|
|
|
719
717
|
});
|
|
720
718
|
return { readable, writable };
|
|
721
719
|
}
|
|
720
|
+
function waitForOpen(ws, timeoutMs = 1e4) {
|
|
721
|
+
return new Promise((resolve, reject) => {
|
|
722
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
723
|
+
resolve();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
const timeout = setTimeout(() => {
|
|
727
|
+
ws.close();
|
|
728
|
+
reject(new Error(`WebSocket connection timeout after ${timeoutMs}ms`));
|
|
729
|
+
}, timeoutMs);
|
|
730
|
+
const onOpen = () => {
|
|
731
|
+
clearTimeout(timeout);
|
|
732
|
+
ws.removeEventListener("error", onError);
|
|
733
|
+
resolve();
|
|
734
|
+
};
|
|
735
|
+
const onError = () => {
|
|
736
|
+
clearTimeout(timeout);
|
|
737
|
+
ws.removeEventListener("open", onOpen);
|
|
738
|
+
reject(new Error("WebSocket connection failed"));
|
|
739
|
+
};
|
|
740
|
+
ws.addEventListener("open", onOpen, { once: true });
|
|
741
|
+
ws.addEventListener("error", onError, { once: true });
|
|
742
|
+
});
|
|
743
|
+
}
|
|
722
744
|
function createStreamPair() {
|
|
723
745
|
const clientToServer = [];
|
|
724
746
|
const serverToClient = [];
|
|
@@ -1525,9 +1547,16 @@ var CausalEventBuffer = class {
|
|
|
1525
1547
|
this.#options = {
|
|
1526
1548
|
maxWaitTime: options.maxWaitTime ?? 5e3,
|
|
1527
1549
|
maxBufferSize: options.maxBufferSize ?? 1e3,
|
|
1550
|
+
multiCauseMode: options.multiCauseMode ?? "all",
|
|
1528
1551
|
onForcedRelease: options.onForcedRelease
|
|
1529
1552
|
};
|
|
1530
1553
|
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Get the current multi-cause mode.
|
|
1556
|
+
*/
|
|
1557
|
+
get multiCauseMode() {
|
|
1558
|
+
return this.#options.multiCauseMode;
|
|
1559
|
+
}
|
|
1531
1560
|
/**
|
|
1532
1561
|
* Push an event into the buffer.
|
|
1533
1562
|
*
|
|
@@ -1543,13 +1572,14 @@ var CausalEventBuffer = class {
|
|
|
1543
1572
|
if (!event.receivedAt) {
|
|
1544
1573
|
event = { ...event, receivedAt: Date.now() };
|
|
1545
1574
|
}
|
|
1546
|
-
const
|
|
1547
|
-
if (
|
|
1575
|
+
const shouldRelease = this.#shouldReleaseEvent(event);
|
|
1576
|
+
if (shouldRelease) {
|
|
1548
1577
|
ready.push(event);
|
|
1549
1578
|
this.#releaseWaiting(event.eventId, ready);
|
|
1550
1579
|
} else {
|
|
1551
1580
|
this.#pending.set(event.eventId, event);
|
|
1552
|
-
|
|
1581
|
+
const predecessors = event.causedBy ?? [];
|
|
1582
|
+
for (const predecessorId of predecessors) {
|
|
1553
1583
|
if (!this.#waitingFor.has(predecessorId)) {
|
|
1554
1584
|
this.#waitingFor.set(predecessorId, /* @__PURE__ */ new Set());
|
|
1555
1585
|
}
|
|
@@ -1608,6 +1638,20 @@ var CausalEventBuffer = class {
|
|
|
1608
1638
|
this.#pending.clear();
|
|
1609
1639
|
this.#waitingFor.clear();
|
|
1610
1640
|
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Check if an event should be released based on its predecessors and the multi-cause mode.
|
|
1643
|
+
*/
|
|
1644
|
+
#shouldReleaseEvent(event) {
|
|
1645
|
+
if (!event.causedBy || event.causedBy.length === 0) {
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
const missingPredecessors = this.#getMissingPredecessors(event);
|
|
1649
|
+
if (this.#options.multiCauseMode === "any") {
|
|
1650
|
+
return missingPredecessors.length < event.causedBy.length;
|
|
1651
|
+
} else {
|
|
1652
|
+
return missingPredecessors.length === 0;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1611
1655
|
/**
|
|
1612
1656
|
* Get missing predecessors for an event.
|
|
1613
1657
|
* A predecessor is considered "missing" if it hasn't been released yet
|
|
@@ -1631,9 +1675,21 @@ var CausalEventBuffer = class {
|
|
|
1631
1675
|
for (const waitingEventId of waitingEventIds) {
|
|
1632
1676
|
const waitingEvent = this.#pending.get(waitingEventId);
|
|
1633
1677
|
if (!waitingEvent) continue;
|
|
1634
|
-
|
|
1635
|
-
if (stillMissing.length === 0) {
|
|
1678
|
+
if (this.#shouldReleaseEvent(waitingEvent)) {
|
|
1636
1679
|
this.#pending.delete(waitingEventId);
|
|
1680
|
+
if (waitingEvent.causedBy) {
|
|
1681
|
+
for (const otherPredId of waitingEvent.causedBy) {
|
|
1682
|
+
if (otherPredId !== predecessorId) {
|
|
1683
|
+
const waiting = this.#waitingFor.get(otherPredId);
|
|
1684
|
+
if (waiting) {
|
|
1685
|
+
waiting.delete(waitingEventId);
|
|
1686
|
+
if (waiting.size === 0) {
|
|
1687
|
+
this.#waitingFor.delete(otherPredId);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1637
1693
|
ready.push(waitingEvent);
|
|
1638
1694
|
this.#releaseWaiting(waitingEventId, ready);
|
|
1639
1695
|
}
|
|
@@ -1746,12 +1802,633 @@ function sortCausalOrder(events) {
|
|
|
1746
1802
|
return result;
|
|
1747
1803
|
}
|
|
1748
1804
|
|
|
1805
|
+
// src/acp/types.ts
|
|
1806
|
+
var ACP_ERROR_CODES = {
|
|
1807
|
+
PARSE_ERROR: -32700,
|
|
1808
|
+
INVALID_REQUEST: -32600,
|
|
1809
|
+
METHOD_NOT_FOUND: -32601,
|
|
1810
|
+
INVALID_PARAMS: -32602,
|
|
1811
|
+
INTERNAL_ERROR: -32603,
|
|
1812
|
+
REQUEST_CANCELLED: -32800,
|
|
1813
|
+
AUTH_REQUIRED: -32e3,
|
|
1814
|
+
SESSION_NOT_FOUND: -32002
|
|
1815
|
+
};
|
|
1816
|
+
var ACPError = class _ACPError extends Error {
|
|
1817
|
+
code;
|
|
1818
|
+
data;
|
|
1819
|
+
constructor(code, message, data) {
|
|
1820
|
+
super(message);
|
|
1821
|
+
this.name = "ACPError";
|
|
1822
|
+
this.code = code;
|
|
1823
|
+
this.data = data;
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Create an ACPError from an error response.
|
|
1827
|
+
*/
|
|
1828
|
+
static fromResponse(error) {
|
|
1829
|
+
return new _ACPError(error.code, error.message, error.data);
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Convert to JSON-RPC error object.
|
|
1833
|
+
*/
|
|
1834
|
+
toErrorObject() {
|
|
1835
|
+
return {
|
|
1836
|
+
code: this.code,
|
|
1837
|
+
message: this.message,
|
|
1838
|
+
...this.data !== void 0 && { data: this.data }
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
var ACP_PROTOCOL_VERSION = 20241007;
|
|
1843
|
+
var ACP_METHODS = {
|
|
1844
|
+
// Lifecycle
|
|
1845
|
+
INITIALIZE: "initialize",
|
|
1846
|
+
AUTHENTICATE: "authenticate",
|
|
1847
|
+
// Session management
|
|
1848
|
+
SESSION_NEW: "session/new",
|
|
1849
|
+
SESSION_LOAD: "session/load",
|
|
1850
|
+
SESSION_SET_MODE: "session/set_mode",
|
|
1851
|
+
// Prompt
|
|
1852
|
+
SESSION_PROMPT: "session/prompt",
|
|
1853
|
+
SESSION_CANCEL: "session/cancel",
|
|
1854
|
+
// Notifications
|
|
1855
|
+
SESSION_UPDATE: "session/update",
|
|
1856
|
+
// Agent→Client requests
|
|
1857
|
+
REQUEST_PERMISSION: "request_permission",
|
|
1858
|
+
FS_READ_TEXT_FILE: "fs/read_text_file",
|
|
1859
|
+
FS_WRITE_TEXT_FILE: "fs/write_text_file",
|
|
1860
|
+
TERMINAL_CREATE: "terminal/create",
|
|
1861
|
+
TERMINAL_OUTPUT: "terminal/output",
|
|
1862
|
+
TERMINAL_RELEASE: "terminal/release",
|
|
1863
|
+
TERMINAL_WAIT_FOR_EXIT: "terminal/wait_for_exit",
|
|
1864
|
+
TERMINAL_KILL: "terminal/kill"
|
|
1865
|
+
};
|
|
1866
|
+
function isACPRequest(msg) {
|
|
1867
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
return "method" in msg && "id" in msg && msg.id !== void 0;
|
|
1871
|
+
}
|
|
1872
|
+
function isACPNotification(msg) {
|
|
1873
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1874
|
+
return false;
|
|
1875
|
+
}
|
|
1876
|
+
return "method" in msg && !("id" in msg);
|
|
1877
|
+
}
|
|
1878
|
+
function isACPResponse(msg) {
|
|
1879
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1880
|
+
return false;
|
|
1881
|
+
}
|
|
1882
|
+
return "id" in msg && !("method" in msg);
|
|
1883
|
+
}
|
|
1884
|
+
function isACPErrorResponse(response) {
|
|
1885
|
+
if (typeof response !== "object" || response === null) {
|
|
1886
|
+
return false;
|
|
1887
|
+
}
|
|
1888
|
+
return "error" in response;
|
|
1889
|
+
}
|
|
1890
|
+
function isACPSuccessResponse(response) {
|
|
1891
|
+
if (typeof response !== "object" || response === null) {
|
|
1892
|
+
return false;
|
|
1893
|
+
}
|
|
1894
|
+
return "result" in response;
|
|
1895
|
+
}
|
|
1896
|
+
function isACPEnvelope(payload) {
|
|
1897
|
+
if (typeof payload !== "object" || payload === null) {
|
|
1898
|
+
return false;
|
|
1899
|
+
}
|
|
1900
|
+
const envelope = payload;
|
|
1901
|
+
if (typeof envelope.acp !== "object" || envelope.acp === null || typeof envelope.acpContext !== "object" || envelope.acpContext === null) {
|
|
1902
|
+
return false;
|
|
1903
|
+
}
|
|
1904
|
+
const acpContext = envelope.acpContext;
|
|
1905
|
+
return typeof acpContext.streamId === "string";
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/acp/stream.ts
|
|
1909
|
+
var ACPStreamConnection = class extends events.EventEmitter {
|
|
1910
|
+
#mapClient;
|
|
1911
|
+
#options;
|
|
1912
|
+
#streamId;
|
|
1913
|
+
#pendingRequests = /* @__PURE__ */ new Map();
|
|
1914
|
+
#subscription = null;
|
|
1915
|
+
#sessionId = null;
|
|
1916
|
+
#initialized = false;
|
|
1917
|
+
#capabilities = null;
|
|
1918
|
+
#closed = false;
|
|
1919
|
+
#lastEventId = null;
|
|
1920
|
+
#isReconnecting = false;
|
|
1921
|
+
#unsubscribeReconnection = null;
|
|
1922
|
+
/**
|
|
1923
|
+
* Create a new ACP stream connection.
|
|
1924
|
+
*
|
|
1925
|
+
* @param mapClient - The underlying MAP client connection
|
|
1926
|
+
* @param options - Stream configuration options
|
|
1927
|
+
*/
|
|
1928
|
+
constructor(mapClient, options) {
|
|
1929
|
+
super();
|
|
1930
|
+
this.#mapClient = mapClient;
|
|
1931
|
+
this.#options = options;
|
|
1932
|
+
this.#streamId = `acp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1933
|
+
this.#unsubscribeReconnection = mapClient.onReconnection((event) => {
|
|
1934
|
+
void this.#handleReconnectionEvent(event);
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
// ===========================================================================
|
|
1938
|
+
// Public Properties
|
|
1939
|
+
// ===========================================================================
|
|
1940
|
+
/** Unique identifier for this ACP stream */
|
|
1941
|
+
get streamId() {
|
|
1942
|
+
return this.#streamId;
|
|
1943
|
+
}
|
|
1944
|
+
/** Target agent this stream connects to */
|
|
1945
|
+
get targetAgent() {
|
|
1946
|
+
return this.#options.targetAgent;
|
|
1947
|
+
}
|
|
1948
|
+
/** Current ACP session ID (null until newSession called) */
|
|
1949
|
+
get sessionId() {
|
|
1950
|
+
return this.#sessionId;
|
|
1951
|
+
}
|
|
1952
|
+
/** Whether initialize() has been called */
|
|
1953
|
+
get initialized() {
|
|
1954
|
+
return this.#initialized;
|
|
1955
|
+
}
|
|
1956
|
+
/** Agent capabilities from initialize response */
|
|
1957
|
+
get capabilities() {
|
|
1958
|
+
return this.#capabilities;
|
|
1959
|
+
}
|
|
1960
|
+
/** Whether the stream is closed */
|
|
1961
|
+
get isClosed() {
|
|
1962
|
+
return this.#closed;
|
|
1963
|
+
}
|
|
1964
|
+
/** Last processed event ID (for reconnection support) */
|
|
1965
|
+
get lastEventId() {
|
|
1966
|
+
return this.#lastEventId;
|
|
1967
|
+
}
|
|
1968
|
+
/** Whether the stream is currently reconnecting */
|
|
1969
|
+
get isReconnecting() {
|
|
1970
|
+
return this.#isReconnecting;
|
|
1971
|
+
}
|
|
1972
|
+
// ===========================================================================
|
|
1973
|
+
// Reconnection Handling
|
|
1974
|
+
// ===========================================================================
|
|
1975
|
+
/**
|
|
1976
|
+
* Handle MAP reconnection events.
|
|
1977
|
+
*/
|
|
1978
|
+
async #handleReconnectionEvent(event) {
|
|
1979
|
+
if (this.#closed) return;
|
|
1980
|
+
switch (event.type) {
|
|
1981
|
+
case "disconnected":
|
|
1982
|
+
this.#isReconnecting = true;
|
|
1983
|
+
this.emit("reconnecting");
|
|
1984
|
+
for (const [id, pending] of this.#pendingRequests) {
|
|
1985
|
+
clearTimeout(pending.timeout);
|
|
1986
|
+
pending.reject(new Error("Connection lost during ACP request"));
|
|
1987
|
+
this.#pendingRequests.delete(id);
|
|
1988
|
+
}
|
|
1989
|
+
break;
|
|
1990
|
+
case "reconnected":
|
|
1991
|
+
await this.#handleReconnected();
|
|
1992
|
+
break;
|
|
1993
|
+
case "reconnectFailed":
|
|
1994
|
+
this.#isReconnecting = false;
|
|
1995
|
+
this.emit("error", event.error ?? new Error("MAP reconnection failed"));
|
|
1996
|
+
break;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Handle successful MAP reconnection.
|
|
2001
|
+
*/
|
|
2002
|
+
async #handleReconnected() {
|
|
2003
|
+
this.#subscription = null;
|
|
2004
|
+
try {
|
|
2005
|
+
await this.#setupSubscription();
|
|
2006
|
+
if (this.#sessionId) {
|
|
2007
|
+
const sessionValid = await this.#verifySessionValid();
|
|
2008
|
+
if (!sessionValid) {
|
|
2009
|
+
const lostSessionId = this.#sessionId;
|
|
2010
|
+
this.#sessionId = null;
|
|
2011
|
+
this.emit("sessionLost", {
|
|
2012
|
+
sessionId: lostSessionId,
|
|
2013
|
+
reason: "Session no longer valid after reconnection"
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
this.#isReconnecting = false;
|
|
2018
|
+
this.emit("reconnected");
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
this.#isReconnecting = false;
|
|
2021
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Verify that the current ACP session is still valid.
|
|
2026
|
+
*
|
|
2027
|
+
* Since ACP doesn't have a dedicated status check method, we attempt
|
|
2028
|
+
* a lightweight operation (cancel with no effect) to verify the session.
|
|
2029
|
+
*/
|
|
2030
|
+
async #verifySessionValid() {
|
|
2031
|
+
if (!this.#sessionId) return false;
|
|
2032
|
+
try {
|
|
2033
|
+
await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
|
|
2034
|
+
sessionId: this.#sessionId,
|
|
2035
|
+
reason: "session_verification"
|
|
2036
|
+
});
|
|
2037
|
+
return true;
|
|
2038
|
+
} catch {
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
// ===========================================================================
|
|
2043
|
+
// Internal Methods
|
|
2044
|
+
// ===========================================================================
|
|
2045
|
+
/**
|
|
2046
|
+
* Set up the subscription for receiving messages from the target agent.
|
|
2047
|
+
*/
|
|
2048
|
+
async #setupSubscription() {
|
|
2049
|
+
if (this.#subscription) return;
|
|
2050
|
+
this.#subscription = await this.#mapClient.subscribe({
|
|
2051
|
+
fromAgents: [this.#options.targetAgent]
|
|
2052
|
+
});
|
|
2053
|
+
void this.#processEvents();
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Process incoming events from the subscription.
|
|
2057
|
+
*/
|
|
2058
|
+
async #processEvents() {
|
|
2059
|
+
if (!this.#subscription) return;
|
|
2060
|
+
try {
|
|
2061
|
+
for await (const event of this.#subscription) {
|
|
2062
|
+
if (this.#closed) break;
|
|
2063
|
+
if (event.id) {
|
|
2064
|
+
this.#lastEventId = event.id;
|
|
2065
|
+
}
|
|
2066
|
+
if (event.type === "message_delivered" && event.data) {
|
|
2067
|
+
const message = event.data.message;
|
|
2068
|
+
if (message?.payload) {
|
|
2069
|
+
await this.#handleIncomingMessage(message);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
if (!this.#closed) {
|
|
2075
|
+
this.emit("error", error);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Handle an incoming message from the target agent.
|
|
2081
|
+
*/
|
|
2082
|
+
async #handleIncomingMessage(message) {
|
|
2083
|
+
const payload = message.payload;
|
|
2084
|
+
if (!isACPEnvelope(payload)) return;
|
|
2085
|
+
const envelope = payload;
|
|
2086
|
+
const { acp, acpContext } = envelope;
|
|
2087
|
+
if (acpContext.streamId !== this.#streamId) return;
|
|
2088
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
2089
|
+
const requestId = String(acp.id);
|
|
2090
|
+
const pending = this.#pendingRequests.get(requestId);
|
|
2091
|
+
if (pending) {
|
|
2092
|
+
clearTimeout(pending.timeout);
|
|
2093
|
+
this.#pendingRequests.delete(requestId);
|
|
2094
|
+
if (acp.error) {
|
|
2095
|
+
pending.reject(ACPError.fromResponse(acp.error));
|
|
2096
|
+
} else {
|
|
2097
|
+
pending.resolve(acp.result);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
if (acp.method && acp.id === void 0) {
|
|
2103
|
+
await this.#handleNotification(acp.method, acp.params, acpContext);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
if (acp.method && acp.id !== void 0) {
|
|
2107
|
+
await this.#handleAgentRequest(acp.id, acp.method, acp.params, acpContext, message);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Handle an ACP notification from the agent.
|
|
2112
|
+
*/
|
|
2113
|
+
async #handleNotification(method, params, _acpContext) {
|
|
2114
|
+
if (method === ACP_METHODS.SESSION_UPDATE) {
|
|
2115
|
+
await this.#options.client.sessionUpdate(params);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Handle an agent→client request.
|
|
2120
|
+
*/
|
|
2121
|
+
async #handleAgentRequest(requestId, method, params, _ctx, originalMessage) {
|
|
2122
|
+
let result;
|
|
2123
|
+
let error;
|
|
2124
|
+
try {
|
|
2125
|
+
switch (method) {
|
|
2126
|
+
case ACP_METHODS.REQUEST_PERMISSION:
|
|
2127
|
+
result = await this.#options.client.requestPermission(
|
|
2128
|
+
params
|
|
2129
|
+
);
|
|
2130
|
+
break;
|
|
2131
|
+
case ACP_METHODS.FS_READ_TEXT_FILE:
|
|
2132
|
+
if (!this.#options.client.readTextFile) {
|
|
2133
|
+
throw new ACPError(-32601, "Method not supported: fs/read_text_file");
|
|
2134
|
+
}
|
|
2135
|
+
result = await this.#options.client.readTextFile(
|
|
2136
|
+
params
|
|
2137
|
+
);
|
|
2138
|
+
break;
|
|
2139
|
+
case ACP_METHODS.FS_WRITE_TEXT_FILE:
|
|
2140
|
+
if (!this.#options.client.writeTextFile) {
|
|
2141
|
+
throw new ACPError(-32601, "Method not supported: fs/write_text_file");
|
|
2142
|
+
}
|
|
2143
|
+
result = await this.#options.client.writeTextFile(
|
|
2144
|
+
params
|
|
2145
|
+
);
|
|
2146
|
+
break;
|
|
2147
|
+
case ACP_METHODS.TERMINAL_CREATE:
|
|
2148
|
+
if (!this.#options.client.createTerminal) {
|
|
2149
|
+
throw new ACPError(-32601, "Method not supported: terminal/create");
|
|
2150
|
+
}
|
|
2151
|
+
result = await this.#options.client.createTerminal(
|
|
2152
|
+
params
|
|
2153
|
+
);
|
|
2154
|
+
break;
|
|
2155
|
+
case ACP_METHODS.TERMINAL_OUTPUT:
|
|
2156
|
+
if (!this.#options.client.terminalOutput) {
|
|
2157
|
+
throw new ACPError(-32601, "Method not supported: terminal/output");
|
|
2158
|
+
}
|
|
2159
|
+
result = await this.#options.client.terminalOutput(
|
|
2160
|
+
params
|
|
2161
|
+
);
|
|
2162
|
+
break;
|
|
2163
|
+
case ACP_METHODS.TERMINAL_RELEASE:
|
|
2164
|
+
if (!this.#options.client.releaseTerminal) {
|
|
2165
|
+
throw new ACPError(-32601, "Method not supported: terminal/release");
|
|
2166
|
+
}
|
|
2167
|
+
result = await this.#options.client.releaseTerminal(
|
|
2168
|
+
params
|
|
2169
|
+
);
|
|
2170
|
+
break;
|
|
2171
|
+
case ACP_METHODS.TERMINAL_WAIT_FOR_EXIT:
|
|
2172
|
+
if (!this.#options.client.waitForTerminalExit) {
|
|
2173
|
+
throw new ACPError(-32601, "Method not supported: terminal/wait_for_exit");
|
|
2174
|
+
}
|
|
2175
|
+
result = await this.#options.client.waitForTerminalExit(
|
|
2176
|
+
params
|
|
2177
|
+
);
|
|
2178
|
+
break;
|
|
2179
|
+
case ACP_METHODS.TERMINAL_KILL:
|
|
2180
|
+
if (!this.#options.client.killTerminal) {
|
|
2181
|
+
throw new ACPError(-32601, "Method not supported: terminal/kill");
|
|
2182
|
+
}
|
|
2183
|
+
result = await this.#options.client.killTerminal(
|
|
2184
|
+
params
|
|
2185
|
+
);
|
|
2186
|
+
break;
|
|
2187
|
+
default:
|
|
2188
|
+
throw new ACPError(-32601, `Unknown method: ${method}`);
|
|
2189
|
+
}
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
if (e instanceof ACPError) {
|
|
2192
|
+
error = e;
|
|
2193
|
+
} else {
|
|
2194
|
+
error = new ACPError(-32603, e.message);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
const responseEnvelope = {
|
|
2198
|
+
acp: {
|
|
2199
|
+
jsonrpc: "2.0",
|
|
2200
|
+
id: requestId,
|
|
2201
|
+
...error ? { error: error.toErrorObject() } : { result }
|
|
2202
|
+
},
|
|
2203
|
+
acpContext: {
|
|
2204
|
+
streamId: this.#streamId,
|
|
2205
|
+
sessionId: this.#sessionId,
|
|
2206
|
+
direction: "client-to-agent"
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
await this.#mapClient.send(
|
|
2210
|
+
{ agent: this.#options.targetAgent },
|
|
2211
|
+
responseEnvelope,
|
|
2212
|
+
{
|
|
2213
|
+
protocol: "acp",
|
|
2214
|
+
correlationId: originalMessage.id
|
|
2215
|
+
}
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Send an ACP request and wait for response.
|
|
2220
|
+
*/
|
|
2221
|
+
async #sendRequest(method, params) {
|
|
2222
|
+
if (this.#closed) {
|
|
2223
|
+
throw new Error("ACP stream is closed");
|
|
2224
|
+
}
|
|
2225
|
+
await this.#setupSubscription();
|
|
2226
|
+
if (this.#closed) {
|
|
2227
|
+
throw new Error("ACP stream closed");
|
|
2228
|
+
}
|
|
2229
|
+
const correlationId = `${this.#streamId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2230
|
+
const timeout = this.#options.timeout ?? 3e4;
|
|
2231
|
+
const envelope = {
|
|
2232
|
+
acp: {
|
|
2233
|
+
jsonrpc: "2.0",
|
|
2234
|
+
id: correlationId,
|
|
2235
|
+
method,
|
|
2236
|
+
...params !== void 0 && { params }
|
|
2237
|
+
},
|
|
2238
|
+
acpContext: {
|
|
2239
|
+
streamId: this.#streamId,
|
|
2240
|
+
sessionId: this.#sessionId,
|
|
2241
|
+
direction: "client-to-agent"
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
2245
|
+
const timeoutHandle = setTimeout(() => {
|
|
2246
|
+
this.#pendingRequests.delete(correlationId);
|
|
2247
|
+
reject(new Error(`ACP request timed out after ${timeout}ms: ${method}`));
|
|
2248
|
+
}, timeout);
|
|
2249
|
+
this.#pendingRequests.set(correlationId, {
|
|
2250
|
+
resolve,
|
|
2251
|
+
reject,
|
|
2252
|
+
timeout: timeoutHandle,
|
|
2253
|
+
method
|
|
2254
|
+
});
|
|
2255
|
+
});
|
|
2256
|
+
try {
|
|
2257
|
+
await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
|
|
2258
|
+
protocol: "acp",
|
|
2259
|
+
correlationId
|
|
2260
|
+
});
|
|
2261
|
+
} catch (err) {
|
|
2262
|
+
const pending = this.#pendingRequests.get(correlationId);
|
|
2263
|
+
if (pending) {
|
|
2264
|
+
clearTimeout(pending.timeout);
|
|
2265
|
+
this.#pendingRequests.delete(correlationId);
|
|
2266
|
+
}
|
|
2267
|
+
throw err;
|
|
2268
|
+
}
|
|
2269
|
+
if (this.#closed && !this.#pendingRequests.has(correlationId)) {
|
|
2270
|
+
throw new Error("ACP stream closed");
|
|
2271
|
+
}
|
|
2272
|
+
return resultPromise;
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Send an ACP notification (no response expected).
|
|
2276
|
+
*/
|
|
2277
|
+
async #sendNotification(method, params) {
|
|
2278
|
+
if (this.#closed) {
|
|
2279
|
+
throw new Error("ACP stream is closed");
|
|
2280
|
+
}
|
|
2281
|
+
await this.#setupSubscription();
|
|
2282
|
+
const envelope = {
|
|
2283
|
+
acp: {
|
|
2284
|
+
jsonrpc: "2.0",
|
|
2285
|
+
method,
|
|
2286
|
+
...params !== void 0 && { params }
|
|
2287
|
+
},
|
|
2288
|
+
acpContext: {
|
|
2289
|
+
streamId: this.#streamId,
|
|
2290
|
+
sessionId: this.#sessionId,
|
|
2291
|
+
direction: "client-to-agent"
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
|
|
2295
|
+
protocol: "acp"
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
// ===========================================================================
|
|
2299
|
+
// ACP Lifecycle Methods
|
|
2300
|
+
// ===========================================================================
|
|
2301
|
+
/**
|
|
2302
|
+
* Initialize the ACP connection with the target agent.
|
|
2303
|
+
*/
|
|
2304
|
+
async initialize(params) {
|
|
2305
|
+
if (this.#initialized) {
|
|
2306
|
+
throw new Error("ACP stream already initialized");
|
|
2307
|
+
}
|
|
2308
|
+
const result = await this.#sendRequest(
|
|
2309
|
+
ACP_METHODS.INITIALIZE,
|
|
2310
|
+
params
|
|
2311
|
+
);
|
|
2312
|
+
this.#initialized = true;
|
|
2313
|
+
this.#capabilities = result.agentCapabilities ?? null;
|
|
2314
|
+
return result;
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Authenticate with the agent.
|
|
2318
|
+
*/
|
|
2319
|
+
async authenticate(params) {
|
|
2320
|
+
if (!this.#initialized) {
|
|
2321
|
+
throw new Error("Must call initialize() before authenticate()");
|
|
2322
|
+
}
|
|
2323
|
+
return this.#sendRequest(
|
|
2324
|
+
ACP_METHODS.AUTHENTICATE,
|
|
2325
|
+
params
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
// ===========================================================================
|
|
2329
|
+
// ACP Session Methods
|
|
2330
|
+
// ===========================================================================
|
|
2331
|
+
/**
|
|
2332
|
+
* Create a new ACP session.
|
|
2333
|
+
*/
|
|
2334
|
+
async newSession(params) {
|
|
2335
|
+
if (!this.#initialized) {
|
|
2336
|
+
throw new Error("Must call initialize() before newSession()");
|
|
2337
|
+
}
|
|
2338
|
+
const result = await this.#sendRequest(
|
|
2339
|
+
ACP_METHODS.SESSION_NEW,
|
|
2340
|
+
params
|
|
2341
|
+
);
|
|
2342
|
+
this.#sessionId = result.sessionId;
|
|
2343
|
+
return result;
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Load an existing ACP session.
|
|
2347
|
+
*/
|
|
2348
|
+
async loadSession(params) {
|
|
2349
|
+
if (!this.#initialized) {
|
|
2350
|
+
throw new Error("Must call initialize() before loadSession()");
|
|
2351
|
+
}
|
|
2352
|
+
const result = await this.#sendRequest(
|
|
2353
|
+
ACP_METHODS.SESSION_LOAD,
|
|
2354
|
+
params
|
|
2355
|
+
);
|
|
2356
|
+
this.#sessionId = params.sessionId;
|
|
2357
|
+
return result;
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Set the session mode.
|
|
2361
|
+
*/
|
|
2362
|
+
async setSessionMode(params) {
|
|
2363
|
+
return this.#sendRequest(
|
|
2364
|
+
ACP_METHODS.SESSION_SET_MODE,
|
|
2365
|
+
params
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
// ===========================================================================
|
|
2369
|
+
// ACP Prompt Methods
|
|
2370
|
+
// ===========================================================================
|
|
2371
|
+
/**
|
|
2372
|
+
* Send a prompt to the agent.
|
|
2373
|
+
* Updates are received via the sessionUpdate handler.
|
|
2374
|
+
*/
|
|
2375
|
+
async prompt(params) {
|
|
2376
|
+
if (!this.#sessionId) {
|
|
2377
|
+
throw new Error("Must call newSession() or loadSession() before prompt()");
|
|
2378
|
+
}
|
|
2379
|
+
return this.#sendRequest(
|
|
2380
|
+
ACP_METHODS.SESSION_PROMPT,
|
|
2381
|
+
params
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Cancel ongoing operations for the current session.
|
|
2386
|
+
*/
|
|
2387
|
+
async cancel(params) {
|
|
2388
|
+
if (!this.#sessionId) {
|
|
2389
|
+
throw new Error("No active session to cancel");
|
|
2390
|
+
}
|
|
2391
|
+
await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
|
|
2392
|
+
sessionId: this.#sessionId,
|
|
2393
|
+
...params
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
// ===========================================================================
|
|
2397
|
+
// Lifecycle
|
|
2398
|
+
// ===========================================================================
|
|
2399
|
+
/**
|
|
2400
|
+
* Close this ACP stream and clean up resources.
|
|
2401
|
+
*/
|
|
2402
|
+
async close() {
|
|
2403
|
+
if (this.#closed) return;
|
|
2404
|
+
this.#closed = true;
|
|
2405
|
+
if (this.#unsubscribeReconnection) {
|
|
2406
|
+
this.#unsubscribeReconnection();
|
|
2407
|
+
this.#unsubscribeReconnection = null;
|
|
2408
|
+
}
|
|
2409
|
+
for (const [id, pending] of this.#pendingRequests) {
|
|
2410
|
+
clearTimeout(pending.timeout);
|
|
2411
|
+
pending.reject(new Error("ACP stream closed"));
|
|
2412
|
+
this.#pendingRequests.delete(id);
|
|
2413
|
+
}
|
|
2414
|
+
if (this.#subscription) {
|
|
2415
|
+
await this.#subscription.unsubscribe();
|
|
2416
|
+
this.#subscription = null;
|
|
2417
|
+
}
|
|
2418
|
+
this.emit("close");
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
function createACPStream(mapClient, options) {
|
|
2422
|
+
return new ACPStreamConnection(mapClient, options);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
1749
2425
|
// src/connection/client.ts
|
|
1750
|
-
var ClientConnection = class {
|
|
2426
|
+
var ClientConnection = class _ClientConnection {
|
|
1751
2427
|
#connection;
|
|
1752
2428
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
1753
2429
|
#subscriptionStates = /* @__PURE__ */ new Map();
|
|
1754
2430
|
#reconnectionHandlers = /* @__PURE__ */ new Set();
|
|
2431
|
+
#acpStreams = /* @__PURE__ */ new Map();
|
|
1755
2432
|
#options;
|
|
1756
2433
|
#sessionId = null;
|
|
1757
2434
|
#serverCapabilities = null;
|
|
@@ -1771,6 +2448,59 @@ var ClientConnection = class {
|
|
|
1771
2448
|
}
|
|
1772
2449
|
}
|
|
1773
2450
|
// ===========================================================================
|
|
2451
|
+
// Static Factory Methods
|
|
2452
|
+
// ===========================================================================
|
|
2453
|
+
/**
|
|
2454
|
+
* Connect to a MAP server via WebSocket URL.
|
|
2455
|
+
*
|
|
2456
|
+
* Handles:
|
|
2457
|
+
* - WebSocket creation and connection
|
|
2458
|
+
* - Stream wrapping
|
|
2459
|
+
* - Auto-configuration of createStream for reconnection
|
|
2460
|
+
* - Initial MAP protocol connect handshake
|
|
2461
|
+
*
|
|
2462
|
+
* @param url - WebSocket URL (ws:// or wss://)
|
|
2463
|
+
* @param options - Connection options
|
|
2464
|
+
* @returns Connected ClientConnection instance
|
|
2465
|
+
*
|
|
2466
|
+
* @example
|
|
2467
|
+
* ```typescript
|
|
2468
|
+
* const client = await ClientConnection.connect('ws://localhost:8080', {
|
|
2469
|
+
* name: 'MyClient',
|
|
2470
|
+
* reconnection: true
|
|
2471
|
+
* });
|
|
2472
|
+
*
|
|
2473
|
+
* // Already connected, ready to use
|
|
2474
|
+
* const agents = await client.listAgents();
|
|
2475
|
+
* ```
|
|
2476
|
+
*/
|
|
2477
|
+
static async connect(url, options) {
|
|
2478
|
+
const parsedUrl = new URL(url);
|
|
2479
|
+
if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
|
|
2480
|
+
throw new Error(
|
|
2481
|
+
`Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
const timeout = options?.connectTimeout ?? 1e4;
|
|
2485
|
+
const ws = new WebSocket(url);
|
|
2486
|
+
await waitForOpen(ws, timeout);
|
|
2487
|
+
const stream = websocketStream(ws);
|
|
2488
|
+
const createStream = async () => {
|
|
2489
|
+
const newWs = new WebSocket(url);
|
|
2490
|
+
await waitForOpen(newWs, timeout);
|
|
2491
|
+
return websocketStream(newWs);
|
|
2492
|
+
};
|
|
2493
|
+
const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
|
|
2494
|
+
const client = new _ClientConnection(stream, {
|
|
2495
|
+
name: options?.name,
|
|
2496
|
+
capabilities: options?.capabilities,
|
|
2497
|
+
createStream,
|
|
2498
|
+
reconnection
|
|
2499
|
+
});
|
|
2500
|
+
await client.connect({ auth: options?.auth });
|
|
2501
|
+
return client;
|
|
2502
|
+
}
|
|
2503
|
+
// ===========================================================================
|
|
1774
2504
|
// Connection Lifecycle
|
|
1775
2505
|
// ===========================================================================
|
|
1776
2506
|
/**
|
|
@@ -1783,6 +2513,7 @@ var ClientConnection = class {
|
|
|
1783
2513
|
name: this.#options.name,
|
|
1784
2514
|
capabilities: this.#options.capabilities,
|
|
1785
2515
|
sessionId: options?.sessionId,
|
|
2516
|
+
resumeToken: options?.resumeToken,
|
|
1786
2517
|
auth: options?.auth
|
|
1787
2518
|
};
|
|
1788
2519
|
const result = await this.#connection.sendRequest(CORE_METHODS.CONNECT, params);
|
|
@@ -1795,15 +2526,23 @@ var ClientConnection = class {
|
|
|
1795
2526
|
}
|
|
1796
2527
|
/**
|
|
1797
2528
|
* Disconnect from the MAP system
|
|
2529
|
+
* @param reason - Optional reason for disconnecting
|
|
2530
|
+
* @returns Resume token that can be used to resume this session later
|
|
1798
2531
|
*/
|
|
1799
2532
|
async disconnect(reason) {
|
|
1800
|
-
if (!this.#connected) return;
|
|
2533
|
+
if (!this.#connected) return void 0;
|
|
2534
|
+
let resumeToken;
|
|
1801
2535
|
try {
|
|
1802
|
-
await this.#connection.sendRequest(
|
|
2536
|
+
const result = await this.#connection.sendRequest(
|
|
1803
2537
|
CORE_METHODS.DISCONNECT,
|
|
1804
2538
|
reason ? { reason } : void 0
|
|
1805
2539
|
);
|
|
2540
|
+
resumeToken = result.resumeToken;
|
|
1806
2541
|
} finally {
|
|
2542
|
+
for (const stream of this.#acpStreams.values()) {
|
|
2543
|
+
await stream.close();
|
|
2544
|
+
}
|
|
2545
|
+
this.#acpStreams.clear();
|
|
1807
2546
|
for (const subscription of this.#subscriptions.values()) {
|
|
1808
2547
|
subscription._close();
|
|
1809
2548
|
}
|
|
@@ -1811,6 +2550,7 @@ var ClientConnection = class {
|
|
|
1811
2550
|
await this.#connection.close();
|
|
1812
2551
|
this.#connected = false;
|
|
1813
2552
|
}
|
|
2553
|
+
return resumeToken;
|
|
1814
2554
|
}
|
|
1815
2555
|
/**
|
|
1816
2556
|
* Whether the client is connected
|
|
@@ -1984,6 +2724,65 @@ var ClientConnection = class {
|
|
|
1984
2724
|
}
|
|
1985
2725
|
}
|
|
1986
2726
|
// ===========================================================================
|
|
2727
|
+
// ACP Streams
|
|
2728
|
+
// ===========================================================================
|
|
2729
|
+
/**
|
|
2730
|
+
* Create a virtual ACP stream connection to an agent.
|
|
2731
|
+
*
|
|
2732
|
+
* This allows clients to interact with ACP-compatible agents using the
|
|
2733
|
+
* familiar ACP interface while routing all messages through MAP.
|
|
2734
|
+
*
|
|
2735
|
+
* @param options - Stream configuration options
|
|
2736
|
+
* @returns ACPStreamConnection instance ready for initialize()
|
|
2737
|
+
*
|
|
2738
|
+
* @example
|
|
2739
|
+
* ```typescript
|
|
2740
|
+
* const acp = client.createACPStream({
|
|
2741
|
+
* targetAgent: 'coding-agent-1',
|
|
2742
|
+
* client: {
|
|
2743
|
+
* requestPermission: async (req) => ({
|
|
2744
|
+
* outcome: { outcome: 'selected', optionId: 'allow' }
|
|
2745
|
+
* }),
|
|
2746
|
+
* sessionUpdate: async (update) => {
|
|
2747
|
+
* console.log('Agent update:', update);
|
|
2748
|
+
* }
|
|
2749
|
+
* }
|
|
2750
|
+
* });
|
|
2751
|
+
*
|
|
2752
|
+
* await acp.initialize({
|
|
2753
|
+
* protocolVersion: 20241007,
|
|
2754
|
+
* clientInfo: { name: 'IDE', version: '1.0' }
|
|
2755
|
+
* });
|
|
2756
|
+
* const { sessionId } = await acp.newSession({ cwd: '/project', mcpServers: [] });
|
|
2757
|
+
* const result = await acp.prompt({
|
|
2758
|
+
* sessionId,
|
|
2759
|
+
* prompt: [{ type: 'text', text: 'Hello' }]
|
|
2760
|
+
* });
|
|
2761
|
+
*
|
|
2762
|
+
* await acp.close();
|
|
2763
|
+
* ```
|
|
2764
|
+
*/
|
|
2765
|
+
createACPStream(options) {
|
|
2766
|
+
const stream = new ACPStreamConnection(this, options);
|
|
2767
|
+
this.#acpStreams.set(stream.streamId, stream);
|
|
2768
|
+
stream.on("close", () => {
|
|
2769
|
+
this.#acpStreams.delete(stream.streamId);
|
|
2770
|
+
});
|
|
2771
|
+
return stream;
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Get an active ACP stream by ID.
|
|
2775
|
+
*/
|
|
2776
|
+
getACPStream(streamId) {
|
|
2777
|
+
return this.#acpStreams.get(streamId);
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Get all active ACP streams.
|
|
2781
|
+
*/
|
|
2782
|
+
get acpStreams() {
|
|
2783
|
+
return this.#acpStreams;
|
|
2784
|
+
}
|
|
2785
|
+
// ===========================================================================
|
|
1987
2786
|
// Subscriptions
|
|
1988
2787
|
// ===========================================================================
|
|
1989
2788
|
/**
|
|
@@ -2346,7 +3145,7 @@ var ClientConnection = class {
|
|
|
2346
3145
|
};
|
|
2347
3146
|
|
|
2348
3147
|
// src/connection/agent.ts
|
|
2349
|
-
var AgentConnection = class {
|
|
3148
|
+
var AgentConnection = class _AgentConnection {
|
|
2350
3149
|
#connection;
|
|
2351
3150
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
2352
3151
|
#options;
|
|
@@ -2373,6 +3172,66 @@ var AgentConnection = class {
|
|
|
2373
3172
|
}
|
|
2374
3173
|
}
|
|
2375
3174
|
// ===========================================================================
|
|
3175
|
+
// Static Factory Methods
|
|
3176
|
+
// ===========================================================================
|
|
3177
|
+
/**
|
|
3178
|
+
* Connect and register an agent via WebSocket URL.
|
|
3179
|
+
*
|
|
3180
|
+
* Handles:
|
|
3181
|
+
* - WebSocket creation and connection
|
|
3182
|
+
* - Stream wrapping
|
|
3183
|
+
* - Auto-configuration of createStream for reconnection
|
|
3184
|
+
* - Initial MAP protocol connect handshake
|
|
3185
|
+
* - Agent registration
|
|
3186
|
+
*
|
|
3187
|
+
* @param url - WebSocket URL (ws:// or wss://)
|
|
3188
|
+
* @param options - Connection and agent options
|
|
3189
|
+
* @returns Connected and registered AgentConnection instance
|
|
3190
|
+
*
|
|
3191
|
+
* @example
|
|
3192
|
+
* ```typescript
|
|
3193
|
+
* const agent = await AgentConnection.connect('ws://localhost:8080', {
|
|
3194
|
+
* name: 'Worker',
|
|
3195
|
+
* role: 'processor',
|
|
3196
|
+
* reconnection: true
|
|
3197
|
+
* });
|
|
3198
|
+
*
|
|
3199
|
+
* // Already registered, ready to work
|
|
3200
|
+
* agent.onMessage(handleMessage);
|
|
3201
|
+
* await agent.busy();
|
|
3202
|
+
* ```
|
|
3203
|
+
*/
|
|
3204
|
+
static async connect(url, options) {
|
|
3205
|
+
const parsedUrl = new URL(url);
|
|
3206
|
+
if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
|
|
3207
|
+
throw new Error(
|
|
3208
|
+
`Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
const timeout = options?.connectTimeout ?? 1e4;
|
|
3212
|
+
const ws = new WebSocket(url);
|
|
3213
|
+
await waitForOpen(ws, timeout);
|
|
3214
|
+
const stream = websocketStream(ws);
|
|
3215
|
+
const createStream = async () => {
|
|
3216
|
+
const newWs = new WebSocket(url);
|
|
3217
|
+
await waitForOpen(newWs, timeout);
|
|
3218
|
+
return websocketStream(newWs);
|
|
3219
|
+
};
|
|
3220
|
+
const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
|
|
3221
|
+
const agent = new _AgentConnection(stream, {
|
|
3222
|
+
name: options?.name,
|
|
3223
|
+
role: options?.role,
|
|
3224
|
+
capabilities: options?.capabilities,
|
|
3225
|
+
visibility: options?.visibility,
|
|
3226
|
+
parent: options?.parent,
|
|
3227
|
+
scopes: options?.scopes,
|
|
3228
|
+
createStream,
|
|
3229
|
+
reconnection
|
|
3230
|
+
});
|
|
3231
|
+
await agent.connect({ auth: options?.auth });
|
|
3232
|
+
return agent;
|
|
3233
|
+
}
|
|
3234
|
+
// ===========================================================================
|
|
2376
3235
|
// Connection Lifecycle
|
|
2377
3236
|
// ===========================================================================
|
|
2378
3237
|
/**
|
|
@@ -2385,6 +3244,7 @@ var AgentConnection = class {
|
|
|
2385
3244
|
participantId: options?.agentId,
|
|
2386
3245
|
name: this.#options.name,
|
|
2387
3246
|
capabilities: this.#options.capabilities,
|
|
3247
|
+
resumeToken: options?.resumeToken,
|
|
2388
3248
|
auth: options?.auth
|
|
2389
3249
|
};
|
|
2390
3250
|
const connectResult = await this.#connection.sendRequest(CORE_METHODS.CONNECT, connectParams);
|
|
@@ -2409,9 +3269,12 @@ var AgentConnection = class {
|
|
|
2409
3269
|
}
|
|
2410
3270
|
/**
|
|
2411
3271
|
* Disconnect from the MAP system
|
|
3272
|
+
* @param reason - Optional reason for disconnecting
|
|
3273
|
+
* @returns Resume token that can be used to resume this session later
|
|
2412
3274
|
*/
|
|
2413
3275
|
async disconnect(reason) {
|
|
2414
|
-
if (!this.#connected) return;
|
|
3276
|
+
if (!this.#connected) return void 0;
|
|
3277
|
+
let resumeToken;
|
|
2415
3278
|
try {
|
|
2416
3279
|
if (this.#agentId) {
|
|
2417
3280
|
await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_UNREGISTER, {
|
|
@@ -2419,10 +3282,11 @@ var AgentConnection = class {
|
|
|
2419
3282
|
reason
|
|
2420
3283
|
});
|
|
2421
3284
|
}
|
|
2422
|
-
await this.#connection.sendRequest(
|
|
3285
|
+
const result = await this.#connection.sendRequest(
|
|
2423
3286
|
CORE_METHODS.DISCONNECT,
|
|
2424
3287
|
reason ? { reason } : void 0
|
|
2425
3288
|
);
|
|
3289
|
+
resumeToken = result.resumeToken;
|
|
2426
3290
|
} finally {
|
|
2427
3291
|
for (const subscription of this.#subscriptions.values()) {
|
|
2428
3292
|
subscription._close();
|
|
@@ -2431,6 +3295,7 @@ var AgentConnection = class {
|
|
|
2431
3295
|
await this.#connection.close();
|
|
2432
3296
|
this.#connected = false;
|
|
2433
3297
|
}
|
|
3298
|
+
return resumeToken;
|
|
2434
3299
|
}
|
|
2435
3300
|
/**
|
|
2436
3301
|
* Whether the agent is connected
|
|
@@ -4350,6 +5215,423 @@ function canAgentMessageAgent(senderAgent, targetAgentId, context, config = DEFA
|
|
|
4350
5215
|
return false;
|
|
4351
5216
|
}
|
|
4352
5217
|
|
|
5218
|
+
// src/server/messages/address.ts
|
|
5219
|
+
var SEPARATOR = ":";
|
|
5220
|
+
var VALID_ADDRESS_TYPES = ["agent", "scope"];
|
|
5221
|
+
var InvalidAddressError = class extends Error {
|
|
5222
|
+
constructor(address, reason) {
|
|
5223
|
+
super(`Invalid address "${address}": ${reason}`);
|
|
5224
|
+
this.name = "InvalidAddressError";
|
|
5225
|
+
}
|
|
5226
|
+
};
|
|
5227
|
+
function formatAddress(type, id) {
|
|
5228
|
+
if (!id) {
|
|
5229
|
+
throw new InvalidAddressError("", "ID cannot be empty");
|
|
5230
|
+
}
|
|
5231
|
+
if (id.includes(SEPARATOR)) {
|
|
5232
|
+
throw new InvalidAddressError(id, "ID cannot contain colon separator");
|
|
5233
|
+
}
|
|
5234
|
+
return `${type}${SEPARATOR}${id}`;
|
|
5235
|
+
}
|
|
5236
|
+
function parseAddress(address) {
|
|
5237
|
+
const separatorIndex = address.indexOf(SEPARATOR);
|
|
5238
|
+
if (separatorIndex === -1) {
|
|
5239
|
+
throw new InvalidAddressError(address, "missing type prefix");
|
|
5240
|
+
}
|
|
5241
|
+
const type = address.slice(0, separatorIndex);
|
|
5242
|
+
const id = address.slice(separatorIndex + 1);
|
|
5243
|
+
if (!VALID_ADDRESS_TYPES.includes(type)) {
|
|
5244
|
+
throw new InvalidAddressError(address, `invalid type "${type}", must be agent or scope`);
|
|
5245
|
+
}
|
|
5246
|
+
if (!id) {
|
|
5247
|
+
throw new InvalidAddressError(address, "ID cannot be empty");
|
|
5248
|
+
}
|
|
5249
|
+
return {
|
|
5250
|
+
type,
|
|
5251
|
+
id
|
|
5252
|
+
};
|
|
5253
|
+
}
|
|
5254
|
+
function isAddress(address) {
|
|
5255
|
+
try {
|
|
5256
|
+
parseAddress(address);
|
|
5257
|
+
return true;
|
|
5258
|
+
} catch {
|
|
5259
|
+
return false;
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
function isAgentAddress(address) {
|
|
5263
|
+
try {
|
|
5264
|
+
const parsed = parseAddress(address);
|
|
5265
|
+
return parsed.type === "agent";
|
|
5266
|
+
} catch {
|
|
5267
|
+
return false;
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
function isScopeAddress(address) {
|
|
5271
|
+
try {
|
|
5272
|
+
const parsed = parseAddress(address);
|
|
5273
|
+
return parsed.type === "scope";
|
|
5274
|
+
} catch {
|
|
5275
|
+
return false;
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
function extractId(address) {
|
|
5279
|
+
try {
|
|
5280
|
+
const parsed = parseAddress(address);
|
|
5281
|
+
return parsed.id;
|
|
5282
|
+
} catch {
|
|
5283
|
+
return address;
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
function extractType(address) {
|
|
5287
|
+
try {
|
|
5288
|
+
const parsed = parseAddress(address);
|
|
5289
|
+
return parsed.type;
|
|
5290
|
+
} catch {
|
|
5291
|
+
return void 0;
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
function toAgent(agentId) {
|
|
5295
|
+
return formatAddress("agent", agentId);
|
|
5296
|
+
}
|
|
5297
|
+
function toScope(scopeId) {
|
|
5298
|
+
return formatAddress("scope", scopeId);
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5301
|
+
// src/acp/adapter.ts
|
|
5302
|
+
var ACPAgentAdapter = class {
|
|
5303
|
+
#mapAgent;
|
|
5304
|
+
#handler;
|
|
5305
|
+
#streamContexts = /* @__PURE__ */ new Map();
|
|
5306
|
+
#pendingClientRequests = /* @__PURE__ */ new Map();
|
|
5307
|
+
#clientRequestTimeout;
|
|
5308
|
+
/**
|
|
5309
|
+
* Create a new ACP agent adapter.
|
|
5310
|
+
*
|
|
5311
|
+
* @param mapAgent - The underlying MAP agent connection
|
|
5312
|
+
* @param handler - Handler implementing ACP agent methods
|
|
5313
|
+
* @param options - Optional configuration
|
|
5314
|
+
*/
|
|
5315
|
+
constructor(mapAgent, handler, options) {
|
|
5316
|
+
this.#mapAgent = mapAgent;
|
|
5317
|
+
this.#handler = handler;
|
|
5318
|
+
this.#clientRequestTimeout = options?.clientRequestTimeout ?? 3e4;
|
|
5319
|
+
mapAgent.onMessage((message) => {
|
|
5320
|
+
if (!message.payload || !isACPEnvelope(message.payload)) {
|
|
5321
|
+
return;
|
|
5322
|
+
}
|
|
5323
|
+
const envelope = message.payload;
|
|
5324
|
+
const { acp, acpContext } = envelope;
|
|
5325
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
5326
|
+
void this.#handleMessage(message);
|
|
5327
|
+
return;
|
|
5328
|
+
}
|
|
5329
|
+
if (acp.method) {
|
|
5330
|
+
this.#trackStreamContext(acpContext, message.from);
|
|
5331
|
+
}
|
|
5332
|
+
queueMicrotask(() => void this.#handleMessage(message));
|
|
5333
|
+
});
|
|
5334
|
+
}
|
|
5335
|
+
/**
|
|
5336
|
+
* Track stream context for a client request.
|
|
5337
|
+
* This is called synchronously so that hasStream() works immediately.
|
|
5338
|
+
*/
|
|
5339
|
+
#trackStreamContext(acpCtx, clientParticipantId) {
|
|
5340
|
+
if (!this.#streamContexts.has(acpCtx.streamId)) {
|
|
5341
|
+
this.#streamContexts.set(acpCtx.streamId, {
|
|
5342
|
+
clientParticipantId,
|
|
5343
|
+
sessionId: acpCtx.sessionId
|
|
5344
|
+
});
|
|
5345
|
+
} else if (acpCtx.sessionId) {
|
|
5346
|
+
const streamCtx = this.#streamContexts.get(acpCtx.streamId);
|
|
5347
|
+
streamCtx.sessionId = acpCtx.sessionId;
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
// ===========================================================================
|
|
5351
|
+
// Message Handling
|
|
5352
|
+
// ===========================================================================
|
|
5353
|
+
/**
|
|
5354
|
+
* Handle incoming messages from MAP.
|
|
5355
|
+
*/
|
|
5356
|
+
async #handleMessage(message) {
|
|
5357
|
+
if (!message.payload || !isACPEnvelope(message.payload)) {
|
|
5358
|
+
return;
|
|
5359
|
+
}
|
|
5360
|
+
const envelope = message.payload;
|
|
5361
|
+
const { acp, acpContext } = envelope;
|
|
5362
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
5363
|
+
const requestId = String(acp.id);
|
|
5364
|
+
const pending = this.#pendingClientRequests.get(requestId);
|
|
5365
|
+
if (pending) {
|
|
5366
|
+
clearTimeout(pending.timeout);
|
|
5367
|
+
this.#pendingClientRequests.delete(requestId);
|
|
5368
|
+
if (acp.error) {
|
|
5369
|
+
pending.reject(ACPError.fromResponse(acp.error));
|
|
5370
|
+
} else {
|
|
5371
|
+
pending.resolve(acp.result);
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
return;
|
|
5375
|
+
}
|
|
5376
|
+
if (acp.method) {
|
|
5377
|
+
await this.#handleClientRequest(
|
|
5378
|
+
acp.id,
|
|
5379
|
+
acp.method,
|
|
5380
|
+
acp.params,
|
|
5381
|
+
acpContext,
|
|
5382
|
+
message
|
|
5383
|
+
);
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
/**
|
|
5387
|
+
* Handle a client→agent request.
|
|
5388
|
+
*/
|
|
5389
|
+
async #handleClientRequest(requestId, method, params, acpCtx, originalMessage) {
|
|
5390
|
+
const ctx = {
|
|
5391
|
+
streamId: acpCtx.streamId,
|
|
5392
|
+
sessionId: acpCtx.sessionId,
|
|
5393
|
+
clientParticipantId: originalMessage.from
|
|
5394
|
+
};
|
|
5395
|
+
let result;
|
|
5396
|
+
let error;
|
|
5397
|
+
try {
|
|
5398
|
+
switch (method) {
|
|
5399
|
+
case ACP_METHODS.INITIALIZE:
|
|
5400
|
+
result = await this.#handler.initialize(
|
|
5401
|
+
params,
|
|
5402
|
+
ctx
|
|
5403
|
+
);
|
|
5404
|
+
break;
|
|
5405
|
+
case ACP_METHODS.AUTHENTICATE:
|
|
5406
|
+
if (!this.#handler.authenticate) {
|
|
5407
|
+
throw new ACPError(-32601, "Method not implemented: authenticate");
|
|
5408
|
+
}
|
|
5409
|
+
result = await this.#handler.authenticate(
|
|
5410
|
+
params,
|
|
5411
|
+
ctx
|
|
5412
|
+
);
|
|
5413
|
+
break;
|
|
5414
|
+
case ACP_METHODS.SESSION_NEW:
|
|
5415
|
+
result = await this.#handler.newSession(
|
|
5416
|
+
params,
|
|
5417
|
+
ctx
|
|
5418
|
+
);
|
|
5419
|
+
const newSessionResult = result;
|
|
5420
|
+
const streamContext = this.#streamContexts.get(acpCtx.streamId);
|
|
5421
|
+
if (streamContext) {
|
|
5422
|
+
streamContext.sessionId = newSessionResult.sessionId;
|
|
5423
|
+
}
|
|
5424
|
+
break;
|
|
5425
|
+
case ACP_METHODS.SESSION_LOAD:
|
|
5426
|
+
if (!this.#handler.loadSession) {
|
|
5427
|
+
throw new ACPError(-32601, "Method not implemented: session/load");
|
|
5428
|
+
}
|
|
5429
|
+
result = await this.#handler.loadSession(
|
|
5430
|
+
params,
|
|
5431
|
+
ctx
|
|
5432
|
+
);
|
|
5433
|
+
break;
|
|
5434
|
+
case ACP_METHODS.SESSION_SET_MODE:
|
|
5435
|
+
if (!this.#handler.setSessionMode) {
|
|
5436
|
+
throw new ACPError(-32601, "Method not implemented: session/set_mode");
|
|
5437
|
+
}
|
|
5438
|
+
result = await this.#handler.setSessionMode(
|
|
5439
|
+
params,
|
|
5440
|
+
ctx
|
|
5441
|
+
);
|
|
5442
|
+
break;
|
|
5443
|
+
case ACP_METHODS.SESSION_PROMPT:
|
|
5444
|
+
result = await this.#handler.prompt(params, ctx);
|
|
5445
|
+
break;
|
|
5446
|
+
case ACP_METHODS.SESSION_CANCEL:
|
|
5447
|
+
await this.#handler.cancel(params, ctx);
|
|
5448
|
+
return;
|
|
5449
|
+
default:
|
|
5450
|
+
throw new ACPError(-32601, `Unknown method: ${method}`);
|
|
5451
|
+
}
|
|
5452
|
+
} catch (e) {
|
|
5453
|
+
if (e instanceof ACPError) {
|
|
5454
|
+
error = e;
|
|
5455
|
+
} else {
|
|
5456
|
+
error = new ACPError(-32603, e.message);
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
if (requestId !== void 0) {
|
|
5460
|
+
const responseEnvelope = {
|
|
5461
|
+
acp: {
|
|
5462
|
+
jsonrpc: "2.0",
|
|
5463
|
+
id: requestId,
|
|
5464
|
+
...error ? { error: error.toErrorObject() } : { result }
|
|
5465
|
+
},
|
|
5466
|
+
acpContext: {
|
|
5467
|
+
streamId: acpCtx.streamId,
|
|
5468
|
+
sessionId: this.#streamContexts.get(acpCtx.streamId)?.sessionId ?? null,
|
|
5469
|
+
direction: "agent-to-client"
|
|
5470
|
+
}
|
|
5471
|
+
};
|
|
5472
|
+
this.#mapAgent.send(
|
|
5473
|
+
{ participant: originalMessage.from },
|
|
5474
|
+
responseEnvelope,
|
|
5475
|
+
{
|
|
5476
|
+
protocol: "acp",
|
|
5477
|
+
correlationId: originalMessage.id
|
|
5478
|
+
}
|
|
5479
|
+
).catch((err) => {
|
|
5480
|
+
console.error("ACP: Failed to send response:", err);
|
|
5481
|
+
});
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
// ===========================================================================
|
|
5485
|
+
// Agent→Client Communication
|
|
5486
|
+
// ===========================================================================
|
|
5487
|
+
/**
|
|
5488
|
+
* Send a session update notification to the client.
|
|
5489
|
+
*/
|
|
5490
|
+
async sendSessionUpdate(streamId, notification) {
|
|
5491
|
+
const streamCtx = this.#streamContexts.get(streamId);
|
|
5492
|
+
if (!streamCtx) {
|
|
5493
|
+
throw new Error(`Unknown stream: ${streamId}`);
|
|
5494
|
+
}
|
|
5495
|
+
const envelope = {
|
|
5496
|
+
acp: {
|
|
5497
|
+
jsonrpc: "2.0",
|
|
5498
|
+
method: ACP_METHODS.SESSION_UPDATE,
|
|
5499
|
+
params: notification
|
|
5500
|
+
},
|
|
5501
|
+
acpContext: {
|
|
5502
|
+
streamId,
|
|
5503
|
+
sessionId: streamCtx.sessionId,
|
|
5504
|
+
direction: "agent-to-client"
|
|
5505
|
+
}
|
|
5506
|
+
};
|
|
5507
|
+
await this.#mapAgent.send(
|
|
5508
|
+
{ participant: streamCtx.clientParticipantId },
|
|
5509
|
+
envelope,
|
|
5510
|
+
{ protocol: "acp" }
|
|
5511
|
+
);
|
|
5512
|
+
}
|
|
5513
|
+
/**
|
|
5514
|
+
* Send an agent→client request and wait for response.
|
|
5515
|
+
*/
|
|
5516
|
+
async #sendClientRequest(streamId, method, params) {
|
|
5517
|
+
const streamCtx = this.#streamContexts.get(streamId);
|
|
5518
|
+
if (!streamCtx) {
|
|
5519
|
+
throw new Error(`Unknown stream: ${streamId}`);
|
|
5520
|
+
}
|
|
5521
|
+
const correlationId = `agent-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
5522
|
+
const envelope = {
|
|
5523
|
+
acp: {
|
|
5524
|
+
jsonrpc: "2.0",
|
|
5525
|
+
id: correlationId,
|
|
5526
|
+
method,
|
|
5527
|
+
params
|
|
5528
|
+
},
|
|
5529
|
+
acpContext: {
|
|
5530
|
+
streamId,
|
|
5531
|
+
sessionId: streamCtx.sessionId,
|
|
5532
|
+
direction: "agent-to-client",
|
|
5533
|
+
pendingClientRequest: {
|
|
5534
|
+
requestId: correlationId,
|
|
5535
|
+
method,
|
|
5536
|
+
timeout: this.#clientRequestTimeout
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
};
|
|
5540
|
+
await this.#mapAgent.send(
|
|
5541
|
+
{ participant: streamCtx.clientParticipantId },
|
|
5542
|
+
envelope,
|
|
5543
|
+
{ protocol: "acp", correlationId }
|
|
5544
|
+
);
|
|
5545
|
+
return new Promise((resolve, reject) => {
|
|
5546
|
+
const timeoutHandle = setTimeout(() => {
|
|
5547
|
+
this.#pendingClientRequests.delete(correlationId);
|
|
5548
|
+
reject(new Error(`Client request timed out: ${method}`));
|
|
5549
|
+
}, this.#clientRequestTimeout);
|
|
5550
|
+
this.#pendingClientRequests.set(correlationId, {
|
|
5551
|
+
resolve,
|
|
5552
|
+
reject,
|
|
5553
|
+
timeout: timeoutHandle,
|
|
5554
|
+
method
|
|
5555
|
+
});
|
|
5556
|
+
});
|
|
5557
|
+
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Request permission from the client.
|
|
5560
|
+
*/
|
|
5561
|
+
async requestPermission(streamId, request) {
|
|
5562
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.REQUEST_PERMISSION, request);
|
|
5563
|
+
}
|
|
5564
|
+
/**
|
|
5565
|
+
* Read a text file from the client.
|
|
5566
|
+
*/
|
|
5567
|
+
async readTextFile(streamId, request) {
|
|
5568
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.FS_READ_TEXT_FILE, request);
|
|
5569
|
+
}
|
|
5570
|
+
/**
|
|
5571
|
+
* Write a text file on the client.
|
|
5572
|
+
*/
|
|
5573
|
+
async writeTextFile(streamId, request) {
|
|
5574
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.FS_WRITE_TEXT_FILE, request);
|
|
5575
|
+
}
|
|
5576
|
+
/**
|
|
5577
|
+
* Create a terminal on the client.
|
|
5578
|
+
*/
|
|
5579
|
+
async createTerminal(streamId, request) {
|
|
5580
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_CREATE, request);
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Get terminal output from the client.
|
|
5584
|
+
*/
|
|
5585
|
+
async terminalOutput(streamId, request) {
|
|
5586
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_OUTPUT, request);
|
|
5587
|
+
}
|
|
5588
|
+
/**
|
|
5589
|
+
* Release a terminal on the client.
|
|
5590
|
+
*/
|
|
5591
|
+
async releaseTerminal(streamId, request) {
|
|
5592
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_RELEASE, request);
|
|
5593
|
+
}
|
|
5594
|
+
/**
|
|
5595
|
+
* Wait for a terminal to exit on the client.
|
|
5596
|
+
*/
|
|
5597
|
+
async waitForTerminalExit(streamId, request) {
|
|
5598
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_WAIT_FOR_EXIT, request);
|
|
5599
|
+
}
|
|
5600
|
+
/**
|
|
5601
|
+
* Kill a terminal command on the client.
|
|
5602
|
+
*/
|
|
5603
|
+
async killTerminal(streamId, request) {
|
|
5604
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_KILL, request);
|
|
5605
|
+
}
|
|
5606
|
+
// ===========================================================================
|
|
5607
|
+
// Utility Methods
|
|
5608
|
+
// ===========================================================================
|
|
5609
|
+
/**
|
|
5610
|
+
* Get the current session ID for a stream.
|
|
5611
|
+
*/
|
|
5612
|
+
getSessionId(streamId) {
|
|
5613
|
+
return this.#streamContexts.get(streamId)?.sessionId ?? null;
|
|
5614
|
+
}
|
|
5615
|
+
/**
|
|
5616
|
+
* Get the client participant ID for a stream.
|
|
5617
|
+
*/
|
|
5618
|
+
getClientParticipantId(streamId) {
|
|
5619
|
+
return this.#streamContexts.get(streamId)?.clientParticipantId;
|
|
5620
|
+
}
|
|
5621
|
+
/**
|
|
5622
|
+
* Check if a stream is active.
|
|
5623
|
+
*/
|
|
5624
|
+
hasStream(streamId) {
|
|
5625
|
+
return this.#streamContexts.has(streamId);
|
|
5626
|
+
}
|
|
5627
|
+
/**
|
|
5628
|
+
* Remove a stream context (e.g., on disconnect).
|
|
5629
|
+
*/
|
|
5630
|
+
removeStream(streamId) {
|
|
5631
|
+
return this.#streamContexts.delete(streamId);
|
|
5632
|
+
}
|
|
5633
|
+
};
|
|
5634
|
+
|
|
4353
5635
|
Object.defineProperty(exports, "monotonicFactory", {
|
|
4354
5636
|
enumerable: true,
|
|
4355
5637
|
get: function () { return ulid.monotonicFactory; }
|
|
@@ -4358,6 +5640,12 @@ Object.defineProperty(exports, "ulid", {
|
|
|
4358
5640
|
enumerable: true,
|
|
4359
5641
|
get: function () { return ulid.ulid; }
|
|
4360
5642
|
});
|
|
5643
|
+
exports.ACPAgentAdapter = ACPAgentAdapter;
|
|
5644
|
+
exports.ACPError = ACPError;
|
|
5645
|
+
exports.ACPStreamConnection = ACPStreamConnection;
|
|
5646
|
+
exports.ACP_ERROR_CODES = ACP_ERROR_CODES;
|
|
5647
|
+
exports.ACP_METHODS = ACP_METHODS;
|
|
5648
|
+
exports.ACP_PROTOCOL_VERSION = ACP_PROTOCOL_VERSION;
|
|
4361
5649
|
exports.AGENT_ERROR_CODES = AGENT_ERROR_CODES;
|
|
4362
5650
|
exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
|
|
4363
5651
|
exports.AUTH_METHODS = AUTH_METHODS;
|
|
@@ -4392,6 +5680,7 @@ exports.FederatedAddressSchema = FederatedAddressSchema;
|
|
|
4392
5680
|
exports.FederationOutageBuffer = FederationOutageBuffer;
|
|
4393
5681
|
exports.GatewayConnection = GatewayConnection;
|
|
4394
5682
|
exports.HierarchicalAddressSchema = HierarchicalAddressSchema;
|
|
5683
|
+
exports.InvalidAddressError = InvalidAddressError;
|
|
4395
5684
|
exports.JSONRPC_VERSION = JSONRPC_VERSION;
|
|
4396
5685
|
exports.JsonRpcVersionSchema = JsonRpcVersionSchema;
|
|
4397
5686
|
exports.LIFECYCLE_METHODS = LIFECYCLE_METHODS;
|
|
@@ -4475,6 +5764,7 @@ exports.canSeeAgent = canSeeAgent;
|
|
|
4475
5764
|
exports.canSeeScope = canSeeScope;
|
|
4476
5765
|
exports.canSendToScope = canSendToScope;
|
|
4477
5766
|
exports.compareUlid = compareUlid;
|
|
5767
|
+
exports.createACPStream = createACPStream;
|
|
4478
5768
|
exports.createErrorResponse = createErrorResponse;
|
|
4479
5769
|
exports.createEvent = createEvent;
|
|
4480
5770
|
exports.createFederationEnvelope = createFederationEnvelope;
|
|
@@ -4485,15 +5775,26 @@ exports.createStreamPair = createStreamPair;
|
|
|
4485
5775
|
exports.createSubscription = createSubscription;
|
|
4486
5776
|
exports.createSuccessResponse = createSuccessResponse;
|
|
4487
5777
|
exports.deepMergePermissions = deepMergePermissions;
|
|
5778
|
+
exports.extractId = extractId;
|
|
5779
|
+
exports.extractType = extractType;
|
|
4488
5780
|
exports.filterVisibleAgents = filterVisibleAgents;
|
|
4489
5781
|
exports.filterVisibleEvents = filterVisibleEvents;
|
|
4490
5782
|
exports.filterVisibleScopes = filterVisibleScopes;
|
|
5783
|
+
exports.formatAddress = formatAddress;
|
|
4491
5784
|
exports.getEnvelopeRoutingInfo = getEnvelopeRoutingInfo;
|
|
4492
5785
|
exports.getMethodInfo = getMethodInfo;
|
|
4493
5786
|
exports.getMethodsByCategory = getMethodsByCategory;
|
|
4494
5787
|
exports.getRequiredCapabilities = getRequiredCapabilities;
|
|
4495
5788
|
exports.hasCapability = hasCapability;
|
|
4496
5789
|
exports.hasRequiredCapabilities = hasRequiredCapabilities;
|
|
5790
|
+
exports.isACPEnvelope = isACPEnvelope;
|
|
5791
|
+
exports.isACPErrorResponse = isACPErrorResponse;
|
|
5792
|
+
exports.isACPNotification = isACPNotification;
|
|
5793
|
+
exports.isACPRequest = isACPRequest;
|
|
5794
|
+
exports.isACPResponse = isACPResponse;
|
|
5795
|
+
exports.isACPSuccessResponse = isACPSuccessResponse;
|
|
5796
|
+
exports.isAddress = isAddress;
|
|
5797
|
+
exports.isAgentAddress = isAgentAddress;
|
|
4497
5798
|
exports.isAgentExposed = isAgentExposed;
|
|
4498
5799
|
exports.isBroadcastAddress = isBroadcastAddress;
|
|
4499
5800
|
exports.isDirectAddress = isDirectAddress;
|
|
@@ -4513,14 +5814,18 @@ exports.isValidEnvelope = isValidEnvelope;
|
|
|
4513
5814
|
exports.isValidUlid = isValidUlid;
|
|
4514
5815
|
exports.mapVisibilityToRule = mapVisibilityToRule;
|
|
4515
5816
|
exports.ndJsonStream = ndJsonStream;
|
|
5817
|
+
exports.parseAddress = parseAddress;
|
|
4516
5818
|
exports.processFederationEnvelope = processFederationEnvelope;
|
|
4517
5819
|
exports.resolveAgentPermissions = resolveAgentPermissions;
|
|
4518
5820
|
exports.retryable = retryable;
|
|
4519
5821
|
exports.sleep = sleep;
|
|
4520
5822
|
exports.sortCausalOrder = sortCausalOrder;
|
|
5823
|
+
exports.toAgent = toAgent;
|
|
5824
|
+
exports.toScope = toScope;
|
|
4521
5825
|
exports.ulidTimestamp = ulidTimestamp;
|
|
4522
5826
|
exports.unwrapEnvelope = unwrapEnvelope;
|
|
4523
5827
|
exports.validateCausalOrder = validateCausalOrder;
|
|
5828
|
+
exports.waitForOpen = waitForOpen;
|
|
4524
5829
|
exports.websocketStream = websocketStream;
|
|
4525
5830
|
exports.withPayload = withPayload;
|
|
4526
5831
|
exports.withRetry = withRetry;
|