@multi-agent-protocol/sdk 0.0.4 → 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-Z76qC_Us.d.cts → index-DMcRbXOS.d.cts} +1226 -1
- package/dist/{index-Z76qC_Us.d.ts → index-DMcRbXOS.d.ts} +1226 -1
- package/dist/index.cjs +1032 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -15
- package/dist/index.d.ts +124 -15
- package/dist/index.js +1020 -1
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +650 -0
- 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 +650 -0
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
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
|
|
@@ -1801,12 +1802,633 @@ function sortCausalOrder(events) {
|
|
|
1801
1802
|
return result;
|
|
1802
1803
|
}
|
|
1803
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
|
+
|
|
1804
2425
|
// src/connection/client.ts
|
|
1805
2426
|
var ClientConnection = class _ClientConnection {
|
|
1806
2427
|
#connection;
|
|
1807
2428
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
1808
2429
|
#subscriptionStates = /* @__PURE__ */ new Map();
|
|
1809
2430
|
#reconnectionHandlers = /* @__PURE__ */ new Set();
|
|
2431
|
+
#acpStreams = /* @__PURE__ */ new Map();
|
|
1810
2432
|
#options;
|
|
1811
2433
|
#sessionId = null;
|
|
1812
2434
|
#serverCapabilities = null;
|
|
@@ -1917,6 +2539,10 @@ var ClientConnection = class _ClientConnection {
|
|
|
1917
2539
|
);
|
|
1918
2540
|
resumeToken = result.resumeToken;
|
|
1919
2541
|
} finally {
|
|
2542
|
+
for (const stream of this.#acpStreams.values()) {
|
|
2543
|
+
await stream.close();
|
|
2544
|
+
}
|
|
2545
|
+
this.#acpStreams.clear();
|
|
1920
2546
|
for (const subscription of this.#subscriptions.values()) {
|
|
1921
2547
|
subscription._close();
|
|
1922
2548
|
}
|
|
@@ -2098,6 +2724,65 @@ var ClientConnection = class _ClientConnection {
|
|
|
2098
2724
|
}
|
|
2099
2725
|
}
|
|
2100
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
|
+
// ===========================================================================
|
|
2101
2786
|
// Subscriptions
|
|
2102
2787
|
// ===========================================================================
|
|
2103
2788
|
/**
|
|
@@ -4613,6 +5298,340 @@ function toScope(scopeId) {
|
|
|
4613
5298
|
return formatAddress("scope", scopeId);
|
|
4614
5299
|
}
|
|
4615
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
|
+
|
|
4616
5635
|
Object.defineProperty(exports, "monotonicFactory", {
|
|
4617
5636
|
enumerable: true,
|
|
4618
5637
|
get: function () { return ulid.monotonicFactory; }
|
|
@@ -4621,6 +5640,12 @@ Object.defineProperty(exports, "ulid", {
|
|
|
4621
5640
|
enumerable: true,
|
|
4622
5641
|
get: function () { return ulid.ulid; }
|
|
4623
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;
|
|
4624
5649
|
exports.AGENT_ERROR_CODES = AGENT_ERROR_CODES;
|
|
4625
5650
|
exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
|
|
4626
5651
|
exports.AUTH_METHODS = AUTH_METHODS;
|
|
@@ -4739,6 +5764,7 @@ exports.canSeeAgent = canSeeAgent;
|
|
|
4739
5764
|
exports.canSeeScope = canSeeScope;
|
|
4740
5765
|
exports.canSendToScope = canSendToScope;
|
|
4741
5766
|
exports.compareUlid = compareUlid;
|
|
5767
|
+
exports.createACPStream = createACPStream;
|
|
4742
5768
|
exports.createErrorResponse = createErrorResponse;
|
|
4743
5769
|
exports.createEvent = createEvent;
|
|
4744
5770
|
exports.createFederationEnvelope = createFederationEnvelope;
|
|
@@ -4761,6 +5787,12 @@ exports.getMethodsByCategory = getMethodsByCategory;
|
|
|
4761
5787
|
exports.getRequiredCapabilities = getRequiredCapabilities;
|
|
4762
5788
|
exports.hasCapability = hasCapability;
|
|
4763
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;
|
|
4764
5796
|
exports.isAddress = isAddress;
|
|
4765
5797
|
exports.isAgentAddress = isAgentAddress;
|
|
4766
5798
|
exports.isAgentExposed = isAgentExposed;
|