@letta-ai/letta-agent-sdk 0.3.3 → 0.5.1
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/AGENTS.md +0 -3
- package/README.md +8 -1
- package/dist/app-server-management.d.ts +3 -48
- package/dist/app-server-management.d.ts.map +1 -1
- package/dist/app-server-session.d.ts +0 -6
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-base.d.ts +7 -15
- package/dist/client-base.d.ts.map +1 -1
- package/dist/client-entry.js +428 -486
- package/dist/client-entry.js.map +13 -12
- package/dist/client.d.ts +3 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/cloud-session.d.ts +2 -0
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/cloud-status-transport.d.ts +53 -0
- package/dist/cloud-status-transport.d.ts.map +1 -0
- package/dist/index.d.ts +7 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +911 -1993
- package/dist/index.js.map +18 -18
- package/dist/local-app-server-session.d.ts +1 -1
- package/dist/local-app-server-session.d.ts.map +1 -1
- package/dist/local-app-server.d.ts +11 -0
- package/dist/local-app-server.d.ts.map +1 -1
- package/dist/management.d.ts +0 -7
- package/dist/management.d.ts.map +1 -1
- package/dist/remote-session-protocol.d.ts +2 -0
- package/dist/remote-session-protocol.d.ts.map +1 -1
- package/dist/remote-turn-coordinator.d.ts.map +1 -1
- package/dist/types.d.ts +65 -66
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/app-server-management.ts +20 -256
- package/src/app-server-session.ts +0 -32
- package/src/client-base.ts +47 -73
- package/src/client.ts +31 -70
- package/src/cloud-session.ts +113 -287
- package/src/cloud-status-transport.ts +305 -0
- package/src/index.ts +11 -29
- package/src/local-app-server-session.ts +13 -3
- package/src/local-app-server.ts +97 -3
- package/src/management.ts +0 -7
- package/src/remote-session-protocol.ts +18 -0
- package/src/remote-turn-coordinator.ts +20 -2
- package/src/types.ts +72 -111
- package/src/validation.ts +21 -6
- package/dist/protocol.d.ts +0 -205
- package/dist/protocol.d.ts.map +0 -1
- package/dist/session.d.ts +0 -155
- package/dist/session.d.ts.map +0 -1
- package/dist/transport.d.ts +0 -53
- package/dist/transport.d.ts.map +0 -1
- package/src/protocol.ts +0 -249
- package/src/session.ts +0 -1638
- package/src/transport.ts +0 -484
package/dist/index.js
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
// Generated bundle. Canonical modules are published under src/ and maintained in the letta-agent-sdk repository.
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
-
for (let key of __getOwnPropNames(mod))
|
|
12
|
-
if (!__hasOwnProp.call(to, key))
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: () => mod[key],
|
|
15
|
-
enumerable: true
|
|
16
|
-
});
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
2
|
|
|
21
3
|
// src/repositories.ts
|
|
22
4
|
var DEFAULT_CLOUD_API_BASE_URL = "https://api.letta.com";
|
|
@@ -262,6 +244,18 @@ class RepositoriesClient {
|
|
|
262
244
|
}
|
|
263
245
|
|
|
264
246
|
// node_modules/@letta-ai/letta-code/dist/app-server-client.js
|
|
247
|
+
function isAppServerInfoResponseMessage(message) {
|
|
248
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const candidate = message;
|
|
252
|
+
const capabilities = candidate.capabilities;
|
|
253
|
+
if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const capabilityRecord = capabilities;
|
|
257
|
+
return candidate.type === "app_server_info_response" && typeof candidate.request_id === "string" && candidate.request_id.length > 0 && candidate.success === true && (candidate.backend === "local" || candidate.backend === "api") && typeof candidate.letta_code_version === "string" && typeof candidate.protocol_version === "number" && Number.isInteger(candidate.protocol_version) && typeof capabilityRecord.agent_management === "boolean" && typeof capabilityRecord.conversation_management === "boolean" && typeof capabilityRecord.memory_management === "boolean" && typeof capabilityRecord.runtime_start === "boolean" && typeof capabilityRecord.split_channels === "boolean";
|
|
258
|
+
}
|
|
265
259
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
266
260
|
var WEBSOCKET_OPEN_STATE = 1;
|
|
267
261
|
function getGlobalWebSocket() {
|
|
@@ -281,9 +275,9 @@ function normalizeBaseUrl(url) {
|
|
|
281
275
|
}
|
|
282
276
|
return parsed;
|
|
283
277
|
}
|
|
284
|
-
function
|
|
278
|
+
function resolveAppServerUrl(url) {
|
|
285
279
|
const parsed = normalizeBaseUrl(url);
|
|
286
|
-
parsed.searchParams.
|
|
280
|
+
parsed.searchParams.delete("channel");
|
|
287
281
|
return parsed.toString();
|
|
288
282
|
}
|
|
289
283
|
function attachSocketListener(socket, type, listener) {
|
|
@@ -396,6 +390,7 @@ function streamDeltaErrorMessage(message) {
|
|
|
396
390
|
}
|
|
397
391
|
|
|
398
392
|
class AppServerClient {
|
|
393
|
+
socket;
|
|
399
394
|
control;
|
|
400
395
|
stream;
|
|
401
396
|
requestTimeoutMs;
|
|
@@ -414,26 +409,18 @@ class AppServerClient {
|
|
|
414
409
|
}
|
|
415
410
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
416
411
|
const socketOptions = appServerSocketOptions(options.authToken);
|
|
417
|
-
this.
|
|
418
|
-
this.
|
|
419
|
-
|
|
412
|
+
this.socket = new WebSocket(resolveAppServerUrl(options.url), socketOptions);
|
|
413
|
+
this.control = this.socket;
|
|
414
|
+
this.stream = this.socket;
|
|
415
|
+
attachSocketListener(this.socket, "message", (event) => {
|
|
420
416
|
this.handleMessage(event, "control");
|
|
421
417
|
});
|
|
422
|
-
attachSocketListener(this.
|
|
423
|
-
this.handleMessage(event, "stream");
|
|
424
|
-
});
|
|
425
|
-
attachSocketListener(this.control, "close", (event) => {
|
|
418
|
+
attachSocketListener(this.socket, "close", (event) => {
|
|
426
419
|
this.handleDisconnect("control", event);
|
|
427
420
|
});
|
|
428
|
-
attachSocketListener(this.stream, "close", (event) => {
|
|
429
|
-
this.handleDisconnect("stream", event);
|
|
430
|
-
});
|
|
431
421
|
}
|
|
432
422
|
async connect() {
|
|
433
|
-
await
|
|
434
|
-
waitForSocketOpen(this.control),
|
|
435
|
-
waitForSocketOpen(this.stream)
|
|
436
|
-
]);
|
|
423
|
+
await waitForSocketOpen(this.socket);
|
|
437
424
|
return this;
|
|
438
425
|
}
|
|
439
426
|
close() {
|
|
@@ -441,8 +428,7 @@ class AppServerClient {
|
|
|
441
428
|
return;
|
|
442
429
|
this.explicitlyClosed = true;
|
|
443
430
|
this.rejectAllPending("App-server client closed");
|
|
444
|
-
this.
|
|
445
|
-
this.stream.close();
|
|
431
|
+
this.socket.close();
|
|
446
432
|
}
|
|
447
433
|
onMessage(handler) {
|
|
448
434
|
this.messageHandlers.add(handler);
|
|
@@ -461,10 +447,38 @@ class AppServerClient {
|
|
|
461
447
|
return `${prefix}-${this.nextRequestNumber}`;
|
|
462
448
|
}
|
|
463
449
|
send(command) {
|
|
450
|
+
this.writeCommand(command);
|
|
451
|
+
}
|
|
452
|
+
writeCommand(command) {
|
|
464
453
|
for (const handler of this.sendHandlers) {
|
|
465
454
|
handler(command);
|
|
466
455
|
}
|
|
467
|
-
this.
|
|
456
|
+
this.socket.send(JSON.stringify(command));
|
|
457
|
+
}
|
|
458
|
+
sendRaw(command) {
|
|
459
|
+
this.writeCommand(command);
|
|
460
|
+
}
|
|
461
|
+
requestRaw(command, options) {
|
|
462
|
+
const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
const timeout = setTimeout(() => {
|
|
465
|
+
this.pending.delete(command.request_id);
|
|
466
|
+
reject(new Error(`Timed out waiting for ${command.request_id}`));
|
|
467
|
+
}, timeoutMs);
|
|
468
|
+
this.pending.set(command.request_id, {
|
|
469
|
+
resolve: (message) => resolve(message),
|
|
470
|
+
reject,
|
|
471
|
+
predicate: options.predicate,
|
|
472
|
+
timeout
|
|
473
|
+
});
|
|
474
|
+
try {
|
|
475
|
+
this.sendRaw(command);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
clearTimeout(timeout);
|
|
478
|
+
this.pending.delete(command.request_id);
|
|
479
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
480
|
+
}
|
|
481
|
+
});
|
|
468
482
|
}
|
|
469
483
|
request(commandOrType, bodyOrOptions = {}, maybeOptions = {}) {
|
|
470
484
|
const isTypeRequest = typeof commandOrType === "string";
|
|
@@ -495,6 +509,15 @@ class AppServerClient {
|
|
|
495
509
|
}
|
|
496
510
|
});
|
|
497
511
|
}
|
|
512
|
+
info(options = {}) {
|
|
513
|
+
return this.request({
|
|
514
|
+
type: "app_server_info",
|
|
515
|
+
request_id: this.nextRequestId("app-server-info")
|
|
516
|
+
}, {
|
|
517
|
+
...options,
|
|
518
|
+
predicate: isAppServerInfoResponseMessage
|
|
519
|
+
});
|
|
520
|
+
}
|
|
498
521
|
runtimeStart(command, options = {}) {
|
|
499
522
|
return this.request({
|
|
500
523
|
type: "runtime_start",
|
|
@@ -714,44 +737,6 @@ function applyUniqueRequestIds(client) {
|
|
|
714
737
|
}
|
|
715
738
|
|
|
716
739
|
// src/app-server-management.ts
|
|
717
|
-
var DEFAULT_IDLE_LINGER_MS = 250;
|
|
718
|
-
var registeredTransportsByUrl = new Map;
|
|
719
|
-
function appServerUrlKey(value) {
|
|
720
|
-
try {
|
|
721
|
-
const url = new URL(value);
|
|
722
|
-
if (url.protocol === "http:")
|
|
723
|
-
url.protocol = "ws:";
|
|
724
|
-
if (url.protocol === "https:")
|
|
725
|
-
url.protocol = "wss:";
|
|
726
|
-
url.hash = "";
|
|
727
|
-
url.searchParams.delete("channel");
|
|
728
|
-
return url.toString();
|
|
729
|
-
} catch {
|
|
730
|
-
return value;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function registerManagementTransport(url, transport) {
|
|
734
|
-
const key = appServerUrlKey(url);
|
|
735
|
-
const registration = { transport };
|
|
736
|
-
const transports = registeredTransportsByUrl.get(key) ?? new Set;
|
|
737
|
-
transports.add(registration);
|
|
738
|
-
registeredTransportsByUrl.set(key, transports);
|
|
739
|
-
return () => {
|
|
740
|
-
transports.delete(registration);
|
|
741
|
-
if (transports.size === 0) {
|
|
742
|
-
registeredTransportsByUrl.delete(key);
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
async function releaseAppServerManagementConnections(url) {
|
|
747
|
-
const key = appServerUrlKey(url);
|
|
748
|
-
while (true) {
|
|
749
|
-
const transports = [...registeredTransportsByUrl.get(key) ?? []];
|
|
750
|
-
if (transports.length === 0)
|
|
751
|
-
return;
|
|
752
|
-
await Promise.all(transports.map(({ transport }) => transport.releaseIdleConnection()));
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
740
|
function ensureResponse(response, value, fallback) {
|
|
756
741
|
if (!response.success || value == null) {
|
|
757
742
|
throw new Error(response.error ?? fallback);
|
|
@@ -788,15 +773,9 @@ function stringRecord(value) {
|
|
|
788
773
|
class AppServerManagementTransport {
|
|
789
774
|
options;
|
|
790
775
|
connectionPromise = null;
|
|
791
|
-
releasePromise = null;
|
|
792
776
|
closingConnections = new Set;
|
|
793
|
-
inFlightRequests = 0;
|
|
794
|
-
idleWaiters = new Set;
|
|
795
|
-
idleTimer = null;
|
|
796
|
-
idleLingerMs;
|
|
797
777
|
constructor(options) {
|
|
798
778
|
this.options = options;
|
|
799
|
-
this.idleLingerMs = options.idleLingerMs ?? DEFAULT_IDLE_LINGER_MS;
|
|
800
779
|
}
|
|
801
780
|
async listAgents(query) {
|
|
802
781
|
const response = await this.request("agent_list", { query }, "agent_list_response");
|
|
@@ -863,73 +842,20 @@ class AppServerManagementTransport {
|
|
|
863
842
|
}
|
|
864
843
|
return { messages: response.messages };
|
|
865
844
|
}
|
|
866
|
-
releaseIdleConnection() {
|
|
867
|
-
this.clearIdleTimer();
|
|
868
|
-
if (this.releasePromise)
|
|
869
|
-
return this.releasePromise;
|
|
870
|
-
const release = this.releaseConnectionWhenIdle();
|
|
871
|
-
this.releasePromise = release;
|
|
872
|
-
release.then(() => {
|
|
873
|
-
if (this.releasePromise === release) {
|
|
874
|
-
this.releasePromise = null;
|
|
875
|
-
}
|
|
876
|
-
}, () => {
|
|
877
|
-
if (this.releasePromise === release) {
|
|
878
|
-
this.releasePromise = null;
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
return release;
|
|
882
|
-
}
|
|
883
845
|
async request(type, body, responseType) {
|
|
884
|
-
if (this.releasePromise) {
|
|
885
|
-
await this.releasePromise;
|
|
886
|
-
}
|
|
887
846
|
if (this.closingConnections.size > 0) {
|
|
888
847
|
await Promise.all([...this.closingConnections]);
|
|
889
848
|
}
|
|
890
|
-
this.
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
predicate: (message) => message.type === responseType
|
|
901
|
-
});
|
|
902
|
-
return response;
|
|
903
|
-
} finally {
|
|
904
|
-
this.inFlightRequests -= 1;
|
|
905
|
-
if (this.inFlightRequests === 0) {
|
|
906
|
-
const waiters = [...this.idleWaiters];
|
|
907
|
-
this.idleWaiters.clear();
|
|
908
|
-
for (const resolve of waiters)
|
|
909
|
-
resolve();
|
|
910
|
-
if (!this.releasePromise) {
|
|
911
|
-
this.scheduleIdleRelease();
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
async releaseConnectionWhenIdle() {
|
|
917
|
-
if (this.inFlightRequests > 0) {
|
|
918
|
-
await new Promise((resolve) => {
|
|
919
|
-
this.idleWaiters.add(resolve);
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
this.clearIdleTimer();
|
|
923
|
-
const promise = this.connectionPromise;
|
|
924
|
-
try {
|
|
925
|
-
if (promise) {
|
|
926
|
-
this.connectionPromise = null;
|
|
927
|
-
await this.trackClosingConnection(await promise);
|
|
928
|
-
}
|
|
929
|
-
if (this.closingConnections.size > 0) {
|
|
930
|
-
await Promise.all([...this.closingConnections]);
|
|
931
|
-
}
|
|
932
|
-
} catch {}
|
|
849
|
+
const { client } = await this.acquireConnection();
|
|
850
|
+
const command = {
|
|
851
|
+
type,
|
|
852
|
+
request_id: client.nextRequestId(type),
|
|
853
|
+
...body
|
|
854
|
+
};
|
|
855
|
+
const response = await client.request(command, {
|
|
856
|
+
predicate: (message) => message.type === responseType
|
|
857
|
+
});
|
|
858
|
+
return response;
|
|
933
859
|
}
|
|
934
860
|
acquireConnection() {
|
|
935
861
|
if (this.connectionPromise)
|
|
@@ -954,7 +880,6 @@ class AppServerManagementTransport {
|
|
|
954
880
|
if (!url) {
|
|
955
881
|
throw new Error("App-server management requires a url or connect hook.");
|
|
956
882
|
}
|
|
957
|
-
const unregister = registerManagementTransport(url, this);
|
|
958
883
|
let client = null;
|
|
959
884
|
try {
|
|
960
885
|
client = applyUniqueRequestIds(createAppServerClient({
|
|
@@ -969,29 +894,23 @@ class AppServerManagementTransport {
|
|
|
969
894
|
} catch (error) {
|
|
970
895
|
try {
|
|
971
896
|
if (client) {
|
|
972
|
-
const controlClosed = waitForSocketClose(client.control);
|
|
973
897
|
client.close();
|
|
974
898
|
ownedConnection?.close();
|
|
975
|
-
await controlClosed;
|
|
976
899
|
} else {
|
|
977
900
|
ownedConnection?.close();
|
|
978
901
|
}
|
|
979
|
-
} catch {}
|
|
980
|
-
unregister();
|
|
981
|
-
}
|
|
902
|
+
} catch {}
|
|
982
903
|
throw error;
|
|
983
904
|
}
|
|
984
905
|
return {
|
|
985
906
|
client,
|
|
986
907
|
ownedConnection,
|
|
987
|
-
detachDisconnect: () => {}
|
|
988
|
-
unregister
|
|
908
|
+
detachDisconnect: () => {}
|
|
989
909
|
};
|
|
990
910
|
}
|
|
991
911
|
discardConnection(promise, connection) {
|
|
992
912
|
if (this.connectionPromise === promise) {
|
|
993
913
|
this.connectionPromise = null;
|
|
994
|
-
this.clearIdleTimer();
|
|
995
914
|
}
|
|
996
915
|
this.trackClosingConnection(connection);
|
|
997
916
|
}
|
|
@@ -1001,67 +920,11 @@ class AppServerManagementTransport {
|
|
|
1001
920
|
closing.then(() => this.closingConnections.delete(closing), () => this.closingConnections.delete(closing));
|
|
1002
921
|
return closing;
|
|
1003
922
|
}
|
|
1004
|
-
scheduleIdleRelease() {
|
|
1005
|
-
if (!this.connectionPromise)
|
|
1006
|
-
return;
|
|
1007
|
-
this.clearIdleTimer();
|
|
1008
|
-
const timer = setTimeout(() => {
|
|
1009
|
-
this.idleTimer = null;
|
|
1010
|
-
this.releaseIdleConnection();
|
|
1011
|
-
}, this.idleLingerMs);
|
|
1012
|
-
this.idleTimer = timer;
|
|
1013
|
-
timer.unref?.();
|
|
1014
|
-
}
|
|
1015
|
-
clearIdleTimer() {
|
|
1016
|
-
if (this.idleTimer === null)
|
|
1017
|
-
return;
|
|
1018
|
-
clearTimeout(this.idleTimer);
|
|
1019
|
-
this.idleTimer = null;
|
|
1020
|
-
}
|
|
1021
923
|
}
|
|
1022
924
|
async function closeConnection(connection) {
|
|
1023
925
|
connection.detachDisconnect();
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
connection.client.close();
|
|
1027
|
-
connection.ownedConnection?.close();
|
|
1028
|
-
await controlClosed;
|
|
1029
|
-
} finally {
|
|
1030
|
-
connection.unregister();
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
function waitForSocketClose(socket) {
|
|
1034
|
-
if (socket.readyState === 3)
|
|
1035
|
-
return Promise.resolve();
|
|
1036
|
-
return new Promise((resolve) => {
|
|
1037
|
-
let settled = false;
|
|
1038
|
-
let detach = () => {};
|
|
1039
|
-
const finish = () => {
|
|
1040
|
-
if (settled)
|
|
1041
|
-
return;
|
|
1042
|
-
settled = true;
|
|
1043
|
-
clearTimeout(timeout);
|
|
1044
|
-
detach();
|
|
1045
|
-
resolve();
|
|
1046
|
-
};
|
|
1047
|
-
if (socket.addEventListener) {
|
|
1048
|
-
socket.addEventListener("close", finish);
|
|
1049
|
-
detach = () => socket.removeEventListener?.("close", finish);
|
|
1050
|
-
} else if (socket.once) {
|
|
1051
|
-
socket.once("close", finish);
|
|
1052
|
-
detach = () => socket.off?.("close", finish);
|
|
1053
|
-
} else if (socket.on) {
|
|
1054
|
-
socket.on("close", finish);
|
|
1055
|
-
detach = () => socket.off?.("close", finish);
|
|
1056
|
-
} else {
|
|
1057
|
-
resolve();
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
const timeout = setTimeout(finish, 1000);
|
|
1061
|
-
timeout.unref?.();
|
|
1062
|
-
if (socket.readyState === 3)
|
|
1063
|
-
finish();
|
|
1064
|
-
});
|
|
926
|
+
connection.client.close();
|
|
927
|
+
connection.ownedConnection?.close();
|
|
1065
928
|
}
|
|
1066
929
|
|
|
1067
930
|
// node_modules/@letta-ai/letta-code/dist/agent-presets.js
|
|
@@ -5968,6 +5831,12 @@ function streamDeltaMessageType2(delta) {
|
|
|
5968
5831
|
function streamDeltaRunId2(delta) {
|
|
5969
5832
|
return typeof delta.run_id === "string" ? delta.run_id : undefined;
|
|
5970
5833
|
}
|
|
5834
|
+
function streamDeltaOtid(delta) {
|
|
5835
|
+
return typeof delta.otid === "string" || delta.otid === null ? delta.otid : undefined;
|
|
5836
|
+
}
|
|
5837
|
+
function streamDeltaSeqId(delta) {
|
|
5838
|
+
return typeof delta.seq_id === "number" ? delta.seq_id : undefined;
|
|
5839
|
+
}
|
|
5971
5840
|
function streamDeltaStopReason2(delta) {
|
|
5972
5841
|
return typeof delta.stop_reason === "string" ? delta.stop_reason : undefined;
|
|
5973
5842
|
}
|
|
@@ -6118,6 +5987,7 @@ function toSessionDeviceStatus(status) {
|
|
|
6118
5987
|
isProcessing: status.is_processing,
|
|
6119
5988
|
permissionMode,
|
|
6120
5989
|
workingDirectory: status.current_working_directory,
|
|
5990
|
+
memoryDirectory: typeof status.memory_directory === "string" ? status.memory_directory : null,
|
|
6121
5991
|
pendingControlRequests: pendingControlRequests(status),
|
|
6122
5992
|
raw: { ...status }
|
|
6123
5993
|
};
|
|
@@ -6377,6 +6247,8 @@ class RemoteTurnCoordinator {
|
|
|
6377
6247
|
transformStreamDelta(delta) {
|
|
6378
6248
|
const messageType = typeof delta.message_type === "string" ? delta.message_type : undefined;
|
|
6379
6249
|
const runId = typeof delta.run_id === "string" ? delta.run_id : undefined;
|
|
6250
|
+
const otid = streamDeltaOtid(delta);
|
|
6251
|
+
const seqId = streamDeltaSeqId(delta);
|
|
6380
6252
|
const uuid = typeof delta.id === "string" ? delta.id : `${this.label}-${++this.messageCounter}`;
|
|
6381
6253
|
if (messageType === "assistant_message") {
|
|
6382
6254
|
const content = extractTextFromContent(delta.content);
|
|
@@ -6384,13 +6256,27 @@ class RemoteTurnCoordinator {
|
|
|
6384
6256
|
return null;
|
|
6385
6257
|
if (this.activeTurn)
|
|
6386
6258
|
this.activeTurn.assistantText += content;
|
|
6387
|
-
return {
|
|
6259
|
+
return {
|
|
6260
|
+
type: "assistant",
|
|
6261
|
+
content,
|
|
6262
|
+
uuid,
|
|
6263
|
+
...otid !== undefined ? { otid } : {},
|
|
6264
|
+
...seqId !== undefined ? { seqId } : {},
|
|
6265
|
+
runId
|
|
6266
|
+
};
|
|
6388
6267
|
}
|
|
6389
6268
|
if (messageType === "reasoning_message") {
|
|
6390
6269
|
const content = typeof delta.reasoning === "string" ? delta.reasoning : extractTextFromContent(delta.content);
|
|
6391
6270
|
if (!content)
|
|
6392
6271
|
return null;
|
|
6393
|
-
return {
|
|
6272
|
+
return {
|
|
6273
|
+
type: "reasoning",
|
|
6274
|
+
content,
|
|
6275
|
+
uuid,
|
|
6276
|
+
...otid !== undefined ? { otid } : {},
|
|
6277
|
+
...seqId !== undefined ? { seqId } : {},
|
|
6278
|
+
runId
|
|
6279
|
+
};
|
|
6394
6280
|
}
|
|
6395
6281
|
if (messageType === "tool_call_message" || messageType === "approval_request_message") {
|
|
6396
6282
|
const toolCall = firstToolCall(delta);
|
|
@@ -7093,26 +6979,6 @@ function assertRemoteCreateAgentOptionsSupported(options) {
|
|
|
7093
6979
|
throw new Error("App-server createAgent() does not yet support dreaming.behavior overrides.");
|
|
7094
6980
|
}
|
|
7095
6981
|
}
|
|
7096
|
-
function assertRemoteSessionOptionsSupported(action, options) {
|
|
7097
|
-
if (options.systemPrompt !== undefined) {
|
|
7098
|
-
throw new Error(`App-server ${action}() does not yet support systemPrompt overrides for existing agents.`);
|
|
7099
|
-
}
|
|
7100
|
-
if (options.disallowedTools !== undefined) {
|
|
7101
|
-
throw new Error(`App-server ${action}() does not yet support disallowedTools.`);
|
|
7102
|
-
}
|
|
7103
|
-
if (options.systemInfoReminder !== undefined) {
|
|
7104
|
-
throw new Error(`App-server ${action}() does not yet support systemInfoReminder overrides.`);
|
|
7105
|
-
}
|
|
7106
|
-
if (options.dreaming?.behavior !== undefined) {
|
|
7107
|
-
throw new Error(`App-server ${action}() does not yet support dreaming.behavior overrides.`);
|
|
7108
|
-
}
|
|
7109
|
-
if (options.memfsStartup !== undefined) {
|
|
7110
|
-
throw new Error(`App-server ${action}() does not support memfsStartup.`);
|
|
7111
|
-
}
|
|
7112
|
-
if (options.includePartialMessages !== undefined) {
|
|
7113
|
-
throw new Error(`App-server ${action}() streams app-server deltas directly and does not support includePartialMessages.`);
|
|
7114
|
-
}
|
|
7115
|
-
}
|
|
7116
6982
|
function normalizeMemoryBlock(block) {
|
|
7117
6983
|
const normalized = { ...block };
|
|
7118
6984
|
if (normalized.value === undefined && typeof normalized.content === "string") {
|
|
@@ -7543,9 +7409,7 @@ class AppServerSession extends RemoteClientSessionCore {
|
|
|
7543
7409
|
return options.memfs !== false;
|
|
7544
7410
|
}
|
|
7545
7411
|
async initializeRuntimeController() {
|
|
7546
|
-
await this.remoteOptions.beforeConnect?.();
|
|
7547
7412
|
const url = await this.resolveAppServerUrl();
|
|
7548
|
-
await releaseAppServerManagementConnections(url);
|
|
7549
7413
|
const client = applyUniqueRequestIds(createAppServerClient({
|
|
7550
7414
|
url,
|
|
7551
7415
|
...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
|
|
@@ -7855,6 +7719,222 @@ class CloudManagementTransport {
|
|
|
7855
7719
|
}
|
|
7856
7720
|
}
|
|
7857
7721
|
|
|
7722
|
+
// src/cloud-status-transport.ts
|
|
7723
|
+
var CONNECTING = 0;
|
|
7724
|
+
var OPEN = 1;
|
|
7725
|
+
var CLOSING = 2;
|
|
7726
|
+
var CLOSED = 3;
|
|
7727
|
+
var MAX_IDEMPOTENCY_KEYS = 1000;
|
|
7728
|
+
function addSocketListener(socket, type, listener) {
|
|
7729
|
+
if (socket.addEventListener) {
|
|
7730
|
+
socket.addEventListener(type, listener);
|
|
7731
|
+
return () => socket.removeEventListener?.(type, listener);
|
|
7732
|
+
}
|
|
7733
|
+
if (socket.on) {
|
|
7734
|
+
socket.on(type, listener);
|
|
7735
|
+
return () => socket.off?.(type, listener);
|
|
7736
|
+
}
|
|
7737
|
+
throw new Error("WebSocket implementation does not support event listeners.");
|
|
7738
|
+
}
|
|
7739
|
+
function messageEventData(event) {
|
|
7740
|
+
if (typeof event === "string")
|
|
7741
|
+
return event;
|
|
7742
|
+
if (!event || typeof event !== "object")
|
|
7743
|
+
return null;
|
|
7744
|
+
const data = event.data;
|
|
7745
|
+
if (typeof data === "string")
|
|
7746
|
+
return data;
|
|
7747
|
+
if (data instanceof ArrayBuffer) {
|
|
7748
|
+
return new TextDecoder().decode(data);
|
|
7749
|
+
}
|
|
7750
|
+
if (data instanceof Uint8Array) {
|
|
7751
|
+
return new TextDecoder().decode(data);
|
|
7752
|
+
}
|
|
7753
|
+
return null;
|
|
7754
|
+
}
|
|
7755
|
+
function channelUrl(url, channel) {
|
|
7756
|
+
const parsed = new URL(url);
|
|
7757
|
+
parsed.searchParams.set("channel", channel);
|
|
7758
|
+
return parsed.toString();
|
|
7759
|
+
}
|
|
7760
|
+
|
|
7761
|
+
class CloudStatusTransport {
|
|
7762
|
+
options;
|
|
7763
|
+
controlSocket;
|
|
7764
|
+
streamSocket;
|
|
7765
|
+
state = CONNECTING;
|
|
7766
|
+
openedChannels = new Set;
|
|
7767
|
+
listeners = new Map;
|
|
7768
|
+
removers = [];
|
|
7769
|
+
seenIdempotencyKeys = new Set;
|
|
7770
|
+
seenIdempotencyOrder = [];
|
|
7771
|
+
lastEventSeq = null;
|
|
7772
|
+
pingTimer = null;
|
|
7773
|
+
closeEmitted = false;
|
|
7774
|
+
constructor(options) {
|
|
7775
|
+
this.options = options;
|
|
7776
|
+
const socketOptions = options.headers ? { headers: options.headers } : undefined;
|
|
7777
|
+
this.controlSocket = new options.WebSocket(channelUrl(options.url, "control"), socketOptions);
|
|
7778
|
+
this.streamSocket = new options.WebSocket(channelUrl(options.url, "stream"), socketOptions);
|
|
7779
|
+
this.bindSocket("control", this.controlSocket);
|
|
7780
|
+
this.bindSocket("stream", this.streamSocket);
|
|
7781
|
+
this.startPing();
|
|
7782
|
+
}
|
|
7783
|
+
get readyState() {
|
|
7784
|
+
return this.state;
|
|
7785
|
+
}
|
|
7786
|
+
send(data) {
|
|
7787
|
+
if (this.state !== OPEN || this.controlSocket.readyState !== OPEN) {
|
|
7788
|
+
throw new Error("Cloud status control socket is not open");
|
|
7789
|
+
}
|
|
7790
|
+
this.controlSocket.send(data);
|
|
7791
|
+
}
|
|
7792
|
+
close() {
|
|
7793
|
+
if (this.state === CLOSED || this.state === CLOSING)
|
|
7794
|
+
return;
|
|
7795
|
+
this.state = CLOSING;
|
|
7796
|
+
this.stopPing();
|
|
7797
|
+
this.closeUnderlyingSockets();
|
|
7798
|
+
this.finishClose({});
|
|
7799
|
+
}
|
|
7800
|
+
addEventListener(type, listener) {
|
|
7801
|
+
let listeners = this.listeners.get(type);
|
|
7802
|
+
if (!listeners) {
|
|
7803
|
+
listeners = new Set;
|
|
7804
|
+
this.listeners.set(type, listeners);
|
|
7805
|
+
}
|
|
7806
|
+
listeners.add(listener);
|
|
7807
|
+
}
|
|
7808
|
+
removeEventListener(type, listener) {
|
|
7809
|
+
this.listeners.get(type)?.delete(listener);
|
|
7810
|
+
}
|
|
7811
|
+
bindSocket(channel, socket) {
|
|
7812
|
+
this.removers.push(addSocketListener(socket, "open", () => {
|
|
7813
|
+
this.openedChannels.add(channel);
|
|
7814
|
+
if (this.openedChannels.size === 2 && this.state === CONNECTING) {
|
|
7815
|
+
this.state = OPEN;
|
|
7816
|
+
this.emit("open", {});
|
|
7817
|
+
}
|
|
7818
|
+
}), addSocketListener(socket, "message", (event) => {
|
|
7819
|
+
if (this.shouldForwardMessage(channel, socket, event)) {
|
|
7820
|
+
this.emit("message", event);
|
|
7821
|
+
}
|
|
7822
|
+
}), addSocketListener(socket, "error", (event) => {
|
|
7823
|
+
this.emit("error", event);
|
|
7824
|
+
}), addSocketListener(socket, "close", (event) => {
|
|
7825
|
+
if (this.state !== CLOSED) {
|
|
7826
|
+
this.state = CLOSING;
|
|
7827
|
+
this.stopPing();
|
|
7828
|
+
this.closeUnderlyingSockets(socket);
|
|
7829
|
+
this.finishClose(event);
|
|
7830
|
+
}
|
|
7831
|
+
}));
|
|
7832
|
+
}
|
|
7833
|
+
shouldForwardMessage(channel, socket, event) {
|
|
7834
|
+
const data = messageEventData(event);
|
|
7835
|
+
if (!data)
|
|
7836
|
+
return true;
|
|
7837
|
+
let message;
|
|
7838
|
+
try {
|
|
7839
|
+
message = JSON.parse(data);
|
|
7840
|
+
} catch {
|
|
7841
|
+
return true;
|
|
7842
|
+
}
|
|
7843
|
+
this.ackIfSequenced(socket, message);
|
|
7844
|
+
if (channel === "control" && message.type === "stream_delta") {
|
|
7845
|
+
return false;
|
|
7846
|
+
}
|
|
7847
|
+
if (this.isDuplicate(message))
|
|
7848
|
+
return false;
|
|
7849
|
+
this.trackEventSequence(message);
|
|
7850
|
+
return true;
|
|
7851
|
+
}
|
|
7852
|
+
ackIfSequenced(socket, message) {
|
|
7853
|
+
if (typeof message.seq !== "number")
|
|
7854
|
+
return;
|
|
7855
|
+
this.sendCommand(socket, { type: "ack", seq: message.seq });
|
|
7856
|
+
}
|
|
7857
|
+
isDuplicate(message) {
|
|
7858
|
+
const key = typeof message.idempotency_key === "string" ? message.idempotency_key : null;
|
|
7859
|
+
if (!key)
|
|
7860
|
+
return false;
|
|
7861
|
+
if (this.seenIdempotencyKeys.has(key))
|
|
7862
|
+
return true;
|
|
7863
|
+
this.seenIdempotencyKeys.add(key);
|
|
7864
|
+
this.seenIdempotencyOrder.push(key);
|
|
7865
|
+
while (this.seenIdempotencyOrder.length > MAX_IDEMPOTENCY_KEYS) {
|
|
7866
|
+
const oldest = this.seenIdempotencyOrder.shift();
|
|
7867
|
+
if (oldest)
|
|
7868
|
+
this.seenIdempotencyKeys.delete(oldest);
|
|
7869
|
+
}
|
|
7870
|
+
return false;
|
|
7871
|
+
}
|
|
7872
|
+
trackEventSequence(message) {
|
|
7873
|
+
if (typeof message.event_seq !== "number")
|
|
7874
|
+
return;
|
|
7875
|
+
if (this.lastEventSeq !== null && message.event_seq > this.lastEventSeq + 1) {
|
|
7876
|
+
this.sendCommand(this.controlSocket, {
|
|
7877
|
+
type: "sync",
|
|
7878
|
+
runtime: this.options.runtime,
|
|
7879
|
+
recover_approvals: true,
|
|
7880
|
+
force_device_status: true
|
|
7881
|
+
});
|
|
7882
|
+
}
|
|
7883
|
+
if (this.lastEventSeq === null || message.event_seq > this.lastEventSeq) {
|
|
7884
|
+
this.lastEventSeq = message.event_seq;
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
sendCommand(socket, command) {
|
|
7888
|
+
if (socket.readyState !== OPEN)
|
|
7889
|
+
return;
|
|
7890
|
+
try {
|
|
7891
|
+
socket.send(JSON.stringify(command));
|
|
7892
|
+
} catch {}
|
|
7893
|
+
}
|
|
7894
|
+
startPing() {
|
|
7895
|
+
this.pingTimer = setInterval(() => {
|
|
7896
|
+
this.sendCommand(this.controlSocket, { type: "ping" });
|
|
7897
|
+
this.sendCommand(this.streamSocket, { type: "ping" });
|
|
7898
|
+
}, this.options.pingIntervalMs);
|
|
7899
|
+
this.pingTimer.unref?.();
|
|
7900
|
+
}
|
|
7901
|
+
stopPing() {
|
|
7902
|
+
if (!this.pingTimer)
|
|
7903
|
+
return;
|
|
7904
|
+
clearInterval(this.pingTimer);
|
|
7905
|
+
this.pingTimer = null;
|
|
7906
|
+
}
|
|
7907
|
+
closeUnderlyingSockets(except) {
|
|
7908
|
+
for (const socket of [this.controlSocket, this.streamSocket]) {
|
|
7909
|
+
if (socket !== except && (socket.readyState === CONNECTING || socket.readyState === OPEN)) {
|
|
7910
|
+
socket.close();
|
|
7911
|
+
}
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
7914
|
+
finishClose(event) {
|
|
7915
|
+
if (this.closeEmitted)
|
|
7916
|
+
return;
|
|
7917
|
+
this.closeEmitted = true;
|
|
7918
|
+
this.state = CLOSED;
|
|
7919
|
+
this.stopPing();
|
|
7920
|
+
for (const remove of this.removers.splice(0))
|
|
7921
|
+
remove();
|
|
7922
|
+
this.emit("close", event);
|
|
7923
|
+
}
|
|
7924
|
+
emit(type, event) {
|
|
7925
|
+
for (const listener of this.listeners.get(type) ?? []) {
|
|
7926
|
+
listener(event);
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
}
|
|
7930
|
+
function createCloudStatusTransportConstructor(options) {
|
|
7931
|
+
return class CloudStatusTransportSocket extends CloudStatusTransport {
|
|
7932
|
+
constructor(_url, _socketOptions) {
|
|
7933
|
+
super(options);
|
|
7934
|
+
}
|
|
7935
|
+
};
|
|
7936
|
+
}
|
|
7937
|
+
|
|
7858
7938
|
// src/remote.ts
|
|
7859
7939
|
function getDefaultApiKey2() {
|
|
7860
7940
|
if (typeof process === "undefined") {
|
|
@@ -8163,251 +8243,48 @@ function buildCloudStatusWebSocketUrl(params) {
|
|
|
8163
8243
|
}
|
|
8164
8244
|
return base.toString();
|
|
8165
8245
|
}
|
|
8166
|
-
function
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8246
|
+
function isCloudConversation(value) {
|
|
8247
|
+
return Boolean(value && typeof value === "object" && typeof value.id === "string");
|
|
8248
|
+
}
|
|
8249
|
+
function isCloudAgentSandbox(value) {
|
|
8250
|
+
return Boolean(value && typeof value === "object" && typeof value.sandboxId === "string" && typeof value.deviceId === "string" && typeof value.connectionName === "string");
|
|
8251
|
+
}
|
|
8252
|
+
function isCloudAgentSandboxRefresh(value) {
|
|
8253
|
+
return Boolean(value && typeof value === "object" && typeof value.success === "boolean" && typeof value.sandboxId === "string" && typeof value.ttlMinutes === "number");
|
|
8254
|
+
}
|
|
8255
|
+
function isRetryableManagedSandboxResolveError(error) {
|
|
8256
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8257
|
+
return message.includes("Remote environment is offline") || message.toLowerCase().includes("not found") || message.includes("(404)");
|
|
8258
|
+
}
|
|
8259
|
+
function sleep(ms) {
|
|
8260
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8261
|
+
}
|
|
8262
|
+
function externalToolsByName(tools) {
|
|
8263
|
+
const result = new Map;
|
|
8264
|
+
for (const tool of tools ?? []) {
|
|
8265
|
+
result.set(tool.name, tool);
|
|
8170
8266
|
}
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8267
|
+
return result;
|
|
8268
|
+
}
|
|
8269
|
+
async function createCloudAgent(clientOptions, agentOptions) {
|
|
8270
|
+
const body = await createAgentBody(agentOptions);
|
|
8271
|
+
const response = await getFetch3(clientOptions.fetch)(`${normalizeCloudApiBaseUrl2(clientOptions.apiBaseUrl)}/v1/agents`, {
|
|
8272
|
+
method: "POST",
|
|
8273
|
+
headers: cloudHeaders2(clientOptions),
|
|
8274
|
+
body: JSON.stringify(body)
|
|
8275
|
+
});
|
|
8276
|
+
const responseBody = await parseJsonResponse3(response);
|
|
8277
|
+
assertOkResponse2(response, responseBody, "Cloud create agent");
|
|
8278
|
+
const agentId = responseBody && typeof responseBody === "object" ? responseBody.id : undefined;
|
|
8279
|
+
if (typeof agentId !== "string" || agentId.length === 0) {
|
|
8280
|
+
throw new Error("Cloud create agent response did not include an agent id.");
|
|
8174
8281
|
}
|
|
8175
|
-
|
|
8282
|
+
return agentId;
|
|
8176
8283
|
}
|
|
8177
|
-
function
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
const data = event.data;
|
|
8182
|
-
if (typeof data === "string")
|
|
8183
|
-
return data;
|
|
8184
|
-
if (data instanceof ArrayBuffer) {
|
|
8185
|
-
return new TextDecoder().decode(data);
|
|
8186
|
-
}
|
|
8187
|
-
if (data instanceof Uint8Array) {
|
|
8188
|
-
return new TextDecoder().decode(data);
|
|
8189
|
-
}
|
|
8190
|
-
}
|
|
8191
|
-
return null;
|
|
8192
|
-
}
|
|
8193
|
-
function createCloudStatusWebSocketConstructor(params) {
|
|
8194
|
-
const WebSocketCtor = getWebSocketConstructor(params.cloudOptions.WebSocket);
|
|
8195
|
-
const authMode = params.cloudOptions.webSocketAuth ?? "header";
|
|
8196
|
-
const cloudHeaders3 = authMode === "header" ? cloudWebSocketHeaders(params.cloudOptions) : undefined;
|
|
8197
|
-
const pingIntervalMs = params.cloudOptions.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
|
|
8198
|
-
const state = {
|
|
8199
|
-
runtime: params.runtime,
|
|
8200
|
-
seenIdempotencyKeys: new Set,
|
|
8201
|
-
seenIdempotencyOrder: [],
|
|
8202
|
-
lastEventSeq: null,
|
|
8203
|
-
controlSocket: null
|
|
8204
|
-
};
|
|
8205
|
-
|
|
8206
|
-
class CloudStatusSocketAdapter {
|
|
8207
|
-
socket;
|
|
8208
|
-
channel;
|
|
8209
|
-
listenerRemovers = new Map;
|
|
8210
|
-
pingTimer = null;
|
|
8211
|
-
removeCloseListener = null;
|
|
8212
|
-
constructor(url, options) {
|
|
8213
|
-
this.channel = new URL(url).searchParams.get("channel");
|
|
8214
|
-
const mergedHeaders = authMode === "header" ? {
|
|
8215
|
-
...options?.headers ?? {},
|
|
8216
|
-
...cloudHeaders3 ?? {}
|
|
8217
|
-
} : undefined;
|
|
8218
|
-
const socketOptions = mergedHeaders && Object.keys(mergedHeaders).length > 0 ? { headers: mergedHeaders } : undefined;
|
|
8219
|
-
this.socket = new WebSocketCtor(url, socketOptions);
|
|
8220
|
-
if (this.channel === "control") {
|
|
8221
|
-
state.controlSocket = this;
|
|
8222
|
-
}
|
|
8223
|
-
this.removeCloseListener = addSocketListener(this.socket, "close", () => {
|
|
8224
|
-
this.stopPing();
|
|
8225
|
-
if (state.controlSocket === this)
|
|
8226
|
-
state.controlSocket = null;
|
|
8227
|
-
});
|
|
8228
|
-
this.startPing(pingIntervalMs);
|
|
8229
|
-
}
|
|
8230
|
-
get readyState() {
|
|
8231
|
-
return this.socket.readyState;
|
|
8232
|
-
}
|
|
8233
|
-
send(data) {
|
|
8234
|
-
this.socket.send(data);
|
|
8235
|
-
}
|
|
8236
|
-
close() {
|
|
8237
|
-
this.stopPing();
|
|
8238
|
-
this.removeCloseListener?.();
|
|
8239
|
-
this.removeCloseListener = null;
|
|
8240
|
-
if (state.controlSocket === this)
|
|
8241
|
-
state.controlSocket = null;
|
|
8242
|
-
this.socket.close();
|
|
8243
|
-
}
|
|
8244
|
-
addEventListener(type, listener) {
|
|
8245
|
-
const wrapped = type === "message" ? (event) => {
|
|
8246
|
-
if (this.handleIncomingMessage(event)) {
|
|
8247
|
-
listener(event);
|
|
8248
|
-
}
|
|
8249
|
-
} : listener;
|
|
8250
|
-
const remove = addSocketListener(this.socket, type, wrapped);
|
|
8251
|
-
let typeRemovers = this.listenerRemovers.get(type);
|
|
8252
|
-
if (!typeRemovers) {
|
|
8253
|
-
typeRemovers = new Map;
|
|
8254
|
-
this.listenerRemovers.set(type, typeRemovers);
|
|
8255
|
-
}
|
|
8256
|
-
typeRemovers.set(listener, remove);
|
|
8257
|
-
}
|
|
8258
|
-
removeEventListener(type, listener) {
|
|
8259
|
-
const typeRemovers = this.listenerRemovers.get(type);
|
|
8260
|
-
const remove = typeRemovers?.get(listener);
|
|
8261
|
-
if (!remove)
|
|
8262
|
-
return;
|
|
8263
|
-
remove();
|
|
8264
|
-
typeRemovers?.delete(listener);
|
|
8265
|
-
if (typeRemovers?.size === 0) {
|
|
8266
|
-
this.listenerRemovers.delete(type);
|
|
8267
|
-
}
|
|
8268
|
-
}
|
|
8269
|
-
handleIncomingMessage(event) {
|
|
8270
|
-
const data = messageEventData(event);
|
|
8271
|
-
if (!data)
|
|
8272
|
-
return true;
|
|
8273
|
-
let message;
|
|
8274
|
-
try {
|
|
8275
|
-
message = JSON.parse(data);
|
|
8276
|
-
} catch {
|
|
8277
|
-
return true;
|
|
8278
|
-
}
|
|
8279
|
-
this.ackIfSequenced(message);
|
|
8280
|
-
if (this.channel === "control" && message.type === "stream_delta") {
|
|
8281
|
-
return false;
|
|
8282
|
-
}
|
|
8283
|
-
if (this.isDuplicate(message))
|
|
8284
|
-
return false;
|
|
8285
|
-
this.trackEventSeq(message);
|
|
8286
|
-
return true;
|
|
8287
|
-
}
|
|
8288
|
-
ackIfSequenced(message) {
|
|
8289
|
-
if (typeof message.seq !== "number")
|
|
8290
|
-
return;
|
|
8291
|
-
this.sendCloudCommand({ type: "ack", seq: message.seq });
|
|
8292
|
-
}
|
|
8293
|
-
isDuplicate(message) {
|
|
8294
|
-
const idempotencyKey = typeof message.idempotency_key === "string" ? message.idempotency_key : undefined;
|
|
8295
|
-
if (!idempotencyKey)
|
|
8296
|
-
return false;
|
|
8297
|
-
if (state.seenIdempotencyKeys.has(idempotencyKey))
|
|
8298
|
-
return true;
|
|
8299
|
-
state.seenIdempotencyKeys.add(idempotencyKey);
|
|
8300
|
-
state.seenIdempotencyOrder.push(idempotencyKey);
|
|
8301
|
-
while (state.seenIdempotencyOrder.length > 1000) {
|
|
8302
|
-
const oldest = state.seenIdempotencyOrder.shift();
|
|
8303
|
-
if (oldest)
|
|
8304
|
-
state.seenIdempotencyKeys.delete(oldest);
|
|
8305
|
-
}
|
|
8306
|
-
return false;
|
|
8307
|
-
}
|
|
8308
|
-
trackEventSeq(message) {
|
|
8309
|
-
if (typeof message.event_seq !== "number")
|
|
8310
|
-
return;
|
|
8311
|
-
if (state.lastEventSeq !== null && message.event_seq > state.lastEventSeq + 1) {
|
|
8312
|
-
this.sendSync(true);
|
|
8313
|
-
}
|
|
8314
|
-
if (state.lastEventSeq === null || message.event_seq > state.lastEventSeq) {
|
|
8315
|
-
state.lastEventSeq = message.event_seq;
|
|
8316
|
-
}
|
|
8317
|
-
}
|
|
8318
|
-
sendSync(recoverApprovals) {
|
|
8319
|
-
const target = state.controlSocket?.readyState === 1 ? state.controlSocket : this;
|
|
8320
|
-
target.sendCloudCommand({
|
|
8321
|
-
type: "sync",
|
|
8322
|
-
runtime: state.runtime,
|
|
8323
|
-
recover_approvals: recoverApprovals,
|
|
8324
|
-
force_device_status: true
|
|
8325
|
-
});
|
|
8326
|
-
}
|
|
8327
|
-
sendCloudCommand(command) {
|
|
8328
|
-
if (this.readyState !== 1)
|
|
8329
|
-
return;
|
|
8330
|
-
try {
|
|
8331
|
-
this.socket.send(JSON.stringify(command));
|
|
8332
|
-
} catch {}
|
|
8333
|
-
}
|
|
8334
|
-
startPing(intervalMs) {
|
|
8335
|
-
if (this.pingTimer)
|
|
8336
|
-
return;
|
|
8337
|
-
this.pingTimer = setInterval(() => {
|
|
8338
|
-
this.sendCloudCommand({ type: "ping" });
|
|
8339
|
-
}, intervalMs);
|
|
8340
|
-
this.pingTimer.unref?.();
|
|
8341
|
-
}
|
|
8342
|
-
stopPing() {
|
|
8343
|
-
if (!this.pingTimer)
|
|
8344
|
-
return;
|
|
8345
|
-
clearInterval(this.pingTimer);
|
|
8346
|
-
this.pingTimer = null;
|
|
8347
|
-
}
|
|
8348
|
-
}
|
|
8349
|
-
return CloudStatusSocketAdapter;
|
|
8350
|
-
}
|
|
8351
|
-
function isCloudConversation(value) {
|
|
8352
|
-
return Boolean(value && typeof value === "object" && typeof value.id === "string");
|
|
8353
|
-
}
|
|
8354
|
-
function isCloudAgentSandbox(value) {
|
|
8355
|
-
return Boolean(value && typeof value === "object" && typeof value.sandboxId === "string" && typeof value.deviceId === "string" && typeof value.connectionName === "string");
|
|
8356
|
-
}
|
|
8357
|
-
function isCloudAgentSandboxRefresh(value) {
|
|
8358
|
-
return Boolean(value && typeof value === "object" && typeof value.success === "boolean" && typeof value.sandboxId === "string" && typeof value.ttlMinutes === "number");
|
|
8359
|
-
}
|
|
8360
|
-
function isRetryableManagedSandboxResolveError(error) {
|
|
8361
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
8362
|
-
return message.includes("Remote environment is offline") || message.toLowerCase().includes("not found") || message.includes("(404)");
|
|
8363
|
-
}
|
|
8364
|
-
function sleep(ms) {
|
|
8365
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8366
|
-
}
|
|
8367
|
-
function externalToolsByName(tools) {
|
|
8368
|
-
const result = new Map;
|
|
8369
|
-
for (const tool of tools ?? []) {
|
|
8370
|
-
result.set(tool.name, tool);
|
|
8371
|
-
}
|
|
8372
|
-
return result;
|
|
8373
|
-
}
|
|
8374
|
-
async function createCloudAgent(clientOptions, agentOptions) {
|
|
8375
|
-
const body = await createAgentBody(agentOptions);
|
|
8376
|
-
const response = await getFetch3(clientOptions.fetch)(`${normalizeCloudApiBaseUrl2(clientOptions.apiBaseUrl)}/v1/agents`, {
|
|
8377
|
-
method: "POST",
|
|
8378
|
-
headers: cloudHeaders2(clientOptions),
|
|
8379
|
-
body: JSON.stringify(body)
|
|
8380
|
-
});
|
|
8381
|
-
const responseBody = await parseJsonResponse3(response);
|
|
8382
|
-
assertOkResponse2(response, responseBody, "Cloud create agent");
|
|
8383
|
-
const agentId = responseBody && typeof responseBody === "object" ? responseBody.id : undefined;
|
|
8384
|
-
if (typeof agentId !== "string" || agentId.length === 0) {
|
|
8385
|
-
throw new Error("Cloud create agent response did not include an agent id.");
|
|
8386
|
-
}
|
|
8387
|
-
return agentId;
|
|
8388
|
-
}
|
|
8389
|
-
function assertCloudSessionOptionsSupported(action, options) {
|
|
8390
|
-
validateCloudSandboxOptions(options.sandbox, "sandbox");
|
|
8391
|
-
if (options.environment !== undefined && options.sandbox !== undefined) {
|
|
8392
|
-
throw new Error(`Letta Cloud ${action}() cannot specify both environment and sandbox options.`);
|
|
8393
|
-
}
|
|
8394
|
-
if (options.systemPrompt !== undefined) {
|
|
8395
|
-
throw new Error(`Letta Cloud ${action}() cannot rewrite an existing agent's systemPrompt from the SDK adapter yet.`);
|
|
8396
|
-
}
|
|
8397
|
-
if (options.disallowedTools !== undefined) {
|
|
8398
|
-
throw new Error(`Letta Cloud ${action}() has not wired disallowedTools to the remote device protocol yet.`);
|
|
8399
|
-
}
|
|
8400
|
-
if (options.systemInfoReminder !== undefined) {
|
|
8401
|
-
throw new Error(`Letta Cloud ${action}() has not wired systemInfoReminder to the remote device protocol yet.`);
|
|
8402
|
-
}
|
|
8403
|
-
if (options.dreaming?.behavior !== undefined) {
|
|
8404
|
-
throw new Error(`Letta Cloud ${action}() does not yet support dreaming.behavior overrides.`);
|
|
8405
|
-
}
|
|
8406
|
-
if (options.memfsStartup !== undefined) {
|
|
8407
|
-
throw new Error(`Letta Cloud ${action}() does not support memfsStartup.`);
|
|
8408
|
-
}
|
|
8409
|
-
if (options.includePartialMessages !== undefined) {
|
|
8410
|
-
throw new Error(`Letta Cloud ${action}() streams Remote Client deltas directly; includePartialMessages is not a separate toggle.`);
|
|
8284
|
+
function assertCloudSessionOptionsSupported(action, options) {
|
|
8285
|
+
validateCloudSandboxOptions(options.sandbox, "sandbox");
|
|
8286
|
+
if (options.environment !== undefined && options.sandbox !== undefined) {
|
|
8287
|
+
throw new Error(`Letta Cloud ${action}() cannot specify both environment and sandbox options.`);
|
|
8411
8288
|
}
|
|
8412
8289
|
}
|
|
8413
8290
|
|
|
@@ -8422,6 +8299,7 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8422
8299
|
sandboxRefreshInFlight = null;
|
|
8423
8300
|
sandboxLifecycleClosing = false;
|
|
8424
8301
|
attachedRepositoryIds = new Set;
|
|
8302
|
+
repositoryIdsRequiringCleanupRecompile = new Set;
|
|
8425
8303
|
cloudMode;
|
|
8426
8304
|
constructor(cloudOptions, mode) {
|
|
8427
8305
|
super(mode, {
|
|
@@ -8435,7 +8313,10 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8435
8313
|
}
|
|
8436
8314
|
async initializeRuntimeController() {
|
|
8437
8315
|
const resolved = await this.resolveRuntime();
|
|
8438
|
-
const connection = await this.resolveConnectionForRuntime(resolved.runtime)
|
|
8316
|
+
const connection = await this.resolveConnectionForRuntime(resolved.runtime).catch(async (error) => {
|
|
8317
|
+
await this.cleanupSessionRepositories(resolved.runtime.agent_id, resolved.runtime.conversation_id);
|
|
8318
|
+
throw error;
|
|
8319
|
+
});
|
|
8439
8320
|
this.connectionId = connection.connectionId;
|
|
8440
8321
|
const apiKey = getCloudApiKey2(this.cloudOptions);
|
|
8441
8322
|
const url = buildCloudStatusWebSocketUrl({
|
|
@@ -8448,8 +8329,11 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8448
8329
|
});
|
|
8449
8330
|
const client = applyUniqueRequestIds(createAppServerClient({
|
|
8450
8331
|
url,
|
|
8451
|
-
WebSocket:
|
|
8452
|
-
|
|
8332
|
+
WebSocket: createCloudStatusTransportConstructor({
|
|
8333
|
+
url,
|
|
8334
|
+
WebSocket: getWebSocketConstructor(this.cloudOptions.WebSocket),
|
|
8335
|
+
...(this.cloudOptions.webSocketAuth ?? "header") === "header" ? { headers: cloudWebSocketHeaders(this.cloudOptions) } : {},
|
|
8336
|
+
pingIntervalMs: this.cloudOptions.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS,
|
|
8453
8337
|
runtime: resolved.runtime
|
|
8454
8338
|
}),
|
|
8455
8339
|
requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
|
|
@@ -8487,7 +8371,7 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8487
8371
|
this.removeControlRequestHandler = null;
|
|
8488
8372
|
client.close();
|
|
8489
8373
|
await this.cleanupManagedSandbox();
|
|
8490
|
-
await this.cleanupSessionRepositories(resolved.runtime.agent_id);
|
|
8374
|
+
await this.cleanupSessionRepositories(resolved.runtime.agent_id, resolved.runtime.conversation_id);
|
|
8491
8375
|
throw error;
|
|
8492
8376
|
}
|
|
8493
8377
|
}
|
|
@@ -8538,8 +8422,9 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8538
8422
|
this.removeControlRequestHandler?.();
|
|
8539
8423
|
this.removeControlRequestHandler = null;
|
|
8540
8424
|
this.cleanupManagedSandbox();
|
|
8541
|
-
if (this.runtime?.agent_id)
|
|
8542
|
-
this.cleanupSessionRepositories(this.runtime.agent_id);
|
|
8425
|
+
if (this.runtime?.agent_id) {
|
|
8426
|
+
this.cleanupSessionRepositories(this.runtime.agent_id, this.runtime.conversation_id);
|
|
8427
|
+
}
|
|
8543
8428
|
}
|
|
8544
8429
|
async resolveRuntime() {
|
|
8545
8430
|
let agentId = this.cloudMode.agentId;
|
|
@@ -8554,22 +8439,30 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8554
8439
|
if (!agentId) {
|
|
8555
8440
|
throw new Error("Letta Cloud createSession()/resumeSession() requires an agent id or conversation id.");
|
|
8556
8441
|
}
|
|
8557
|
-
await this.attachSessionRepositories(agentId);
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8442
|
+
const shouldRecompileRepositories = await this.attachSessionRepositories(agentId);
|
|
8443
|
+
try {
|
|
8444
|
+
if (this.cloudMode.newConversation) {
|
|
8445
|
+
const conversation = await this.createConversation(agentId);
|
|
8446
|
+
conversationId = conversation.id;
|
|
8447
|
+
} else if (this.cloudMode.defaultConversation) {
|
|
8448
|
+
conversationId = "default";
|
|
8449
|
+
}
|
|
8450
|
+
if (!conversationId) {
|
|
8451
|
+
throw new Error("Letta Cloud createSession()/resumeSession() requires an agent id or conversation id.");
|
|
8452
|
+
}
|
|
8453
|
+
if (shouldRecompileRepositories) {
|
|
8454
|
+
await this.recompileSystemPrompt(agentId, conversationId);
|
|
8455
|
+
}
|
|
8456
|
+
return { runtime: { agent_id: agentId, conversation_id: conversationId } };
|
|
8457
|
+
} catch (error) {
|
|
8458
|
+
await this.cleanupSessionRepositories(agentId, conversationId);
|
|
8459
|
+
throw error;
|
|
8566
8460
|
}
|
|
8567
|
-
return { runtime: { agent_id: agentId, conversation_id: conversationId } };
|
|
8568
8461
|
}
|
|
8569
8462
|
async attachSessionRepositories(agentId) {
|
|
8570
8463
|
const resources = this.repositoryResources();
|
|
8571
8464
|
if (resources.length === 0)
|
|
8572
|
-
return;
|
|
8465
|
+
return false;
|
|
8573
8466
|
const existing = await this.listAgentRepositories(agentId);
|
|
8574
8467
|
const existingIds = new Set(existing.map((repository) => repository.id));
|
|
8575
8468
|
try {
|
|
@@ -8585,15 +8478,42 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8585
8478
|
await this.cleanupSessionRepositories(agentId);
|
|
8586
8479
|
throw error;
|
|
8587
8480
|
}
|
|
8481
|
+
const shouldRecompile = resources.some((resource) => resource.recompile !== false);
|
|
8482
|
+
this.repositoryIdsRequiringCleanupRecompile = new Set(resources.filter((resource) => resource.recompile !== false && this.attachedRepositoryIds.has(resource.repositoryId)).map((resource) => resource.repositoryId));
|
|
8483
|
+
return shouldRecompile;
|
|
8588
8484
|
}
|
|
8589
|
-
async cleanupSessionRepositories(agentId) {
|
|
8485
|
+
async cleanupSessionRepositories(agentId, conversationId) {
|
|
8590
8486
|
const repositoryIds = [...this.attachedRepositoryIds];
|
|
8487
|
+
const repositoryIdsRequiringRecompile = this.repositoryIdsRequiringCleanupRecompile;
|
|
8591
8488
|
this.attachedRepositoryIds.clear();
|
|
8592
|
-
|
|
8489
|
+
this.repositoryIdsRequiringCleanupRecompile = new Set;
|
|
8490
|
+
const recompileAfterDetach = await Promise.all(repositoryIds.map(async (repositoryId) => {
|
|
8593
8491
|
try {
|
|
8594
8492
|
await this.unlinkAgentRepository(agentId, repositoryId);
|
|
8595
|
-
|
|
8493
|
+
return repositoryIdsRequiringRecompile.has(repositoryId);
|
|
8494
|
+
} catch {
|
|
8495
|
+
return false;
|
|
8496
|
+
}
|
|
8596
8497
|
}));
|
|
8498
|
+
if (recompileAfterDetach.some(Boolean)) {
|
|
8499
|
+
try {
|
|
8500
|
+
await this.recompileSystemPrompt(agentId, conversationId);
|
|
8501
|
+
} catch {}
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
async recompileSystemPrompt(agentId, conversationId) {
|
|
8505
|
+
const fetchImpl = getFetch3(this.cloudOptions.fetch);
|
|
8506
|
+
const baseUrl = normalizeCloudApiBaseUrl2(this.cloudOptions.apiBaseUrl);
|
|
8507
|
+
const usesAgentPrompt = !conversationId || conversationId === "default";
|
|
8508
|
+
const path = usesAgentPrompt ? `/v1/agents/${encodeURIComponent(agentId)}/recompile` : `/v1/conversations/${encodeURIComponent(conversationId)}/recompile`;
|
|
8509
|
+
const url = `${baseUrl}${path}`;
|
|
8510
|
+
const response = await fetchImpl(url, {
|
|
8511
|
+
method: "POST",
|
|
8512
|
+
headers: cloudHeaders2(this.cloudOptions),
|
|
8513
|
+
body: JSON.stringify(usesAgentPrompt ? {} : { agent_id: agentId })
|
|
8514
|
+
});
|
|
8515
|
+
const body = await parseJsonResponse3(response);
|
|
8516
|
+
assertOkResponse2(response, body, "Cloud recompile system prompt", url);
|
|
8597
8517
|
}
|
|
8598
8518
|
repositoryResources() {
|
|
8599
8519
|
const resources = this.cloudMode.options.resources ?? [];
|
|
@@ -9037,7 +8957,23 @@ function validateApprovalRecoveryOptions(options) {
|
|
|
9037
8957
|
}
|
|
9038
8958
|
}
|
|
9039
8959
|
function validateRemovedSessionOptions(options) {
|
|
9040
|
-
|
|
8960
|
+
const removedOptions = options;
|
|
8961
|
+
if (removedOptions.systemPrompt !== undefined) {
|
|
8962
|
+
throw new Error("systemPrompt is not supported when opening an existing agent session.");
|
|
8963
|
+
}
|
|
8964
|
+
if (removedOptions.disallowedTools !== undefined) {
|
|
8965
|
+
throw new Error("disallowedTools is not supported when opening an existing agent session.");
|
|
8966
|
+
}
|
|
8967
|
+
if (removedOptions.systemInfoReminder !== undefined) {
|
|
8968
|
+
throw new Error("systemInfoReminder is not supported when opening an existing agent session.");
|
|
8969
|
+
}
|
|
8970
|
+
if (removedOptions.includePartialMessages !== undefined) {
|
|
8971
|
+
throw new Error("includePartialMessages is not supported by app-server sessions.");
|
|
8972
|
+
}
|
|
8973
|
+
if (options.dreaming && "behavior" in options.dreaming && options.dreaming.behavior !== undefined) {
|
|
8974
|
+
throw new Error("dreaming.behavior is not supported when opening an existing agent session.");
|
|
8975
|
+
}
|
|
8976
|
+
if (removedOptions.memfsStartup !== undefined) {
|
|
9041
8977
|
throw new Error("memfsStartup is not supported by the SDK.");
|
|
9042
8978
|
}
|
|
9043
8979
|
}
|
|
@@ -9087,9 +9023,6 @@ function validateReasoningEffort(value) {
|
|
|
9087
9023
|
}
|
|
9088
9024
|
}
|
|
9089
9025
|
function validateCreateSessionOptions(options) {
|
|
9090
|
-
if (options.systemPrompt !== undefined) {
|
|
9091
|
-
validateSystemPromptPreset(options.systemPrompt);
|
|
9092
|
-
}
|
|
9093
9026
|
validateSkillSources(options.skillSources);
|
|
9094
9027
|
validateReasoningEffort(options.reasoningEffort);
|
|
9095
9028
|
validateDreamingOptions(options.dreaming);
|
|
@@ -9145,6 +9078,7 @@ function stripCloudExecutionOptions(options) {
|
|
|
9145
9078
|
const sessionOptions = { ...options };
|
|
9146
9079
|
delete sessionOptions.environment;
|
|
9147
9080
|
delete sessionOptions.sandbox;
|
|
9081
|
+
delete sessionOptions.filesystemConfinement;
|
|
9148
9082
|
return sessionOptions;
|
|
9149
9083
|
}
|
|
9150
9084
|
function hasRepositoryResources(options) {
|
|
@@ -9188,17 +9122,13 @@ class LettaAgentClientBase {
|
|
|
9188
9122
|
}
|
|
9189
9123
|
if (this.backend === "local") {
|
|
9190
9124
|
const localOptions = options;
|
|
9191
|
-
if (
|
|
9192
|
-
throw new Error("
|
|
9125
|
+
if ("transport" in localOptions) {
|
|
9126
|
+
throw new Error("Local transport selection has been removed. The local backend always uses the app-server protocol.");
|
|
9193
9127
|
}
|
|
9194
9128
|
const requestTimeoutMs = localOptions.appServer?.requestTimeoutMs;
|
|
9195
9129
|
if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
|
|
9196
9130
|
throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
|
|
9197
9131
|
}
|
|
9198
|
-
const idleLingerMs = localOptions.appServer?.idleLingerMs;
|
|
9199
|
-
if (idleLingerMs !== undefined && (!Number.isInteger(idleLingerMs) || idleLingerMs < 0)) {
|
|
9200
|
-
throw new Error("Invalid appServer.idleLingerMs. Expected a non-negative integer.");
|
|
9201
|
-
}
|
|
9202
9132
|
const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
|
|
9203
9133
|
if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
|
|
9204
9134
|
throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
|
|
@@ -9211,9 +9141,6 @@ class LettaAgentClientBase {
|
|
|
9211
9141
|
if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
|
|
9212
9142
|
throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
|
|
9213
9143
|
}
|
|
9214
|
-
if (options.idleLingerMs !== undefined && (!Number.isInteger(options.idleLingerMs) || options.idleLingerMs < 0)) {
|
|
9215
|
-
throw new Error("Invalid idleLingerMs. Expected a non-negative integer.");
|
|
9216
|
-
}
|
|
9217
9144
|
}
|
|
9218
9145
|
if (this.backend === "cloud") {
|
|
9219
9146
|
validateCloudClientOptions(options);
|
|
@@ -9241,40 +9168,35 @@ class LettaAgentClientBase {
|
|
|
9241
9168
|
}
|
|
9242
9169
|
return this.createLocalAgent(options);
|
|
9243
9170
|
}
|
|
9244
|
-
createSession(
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
const sessionOptions = stripCloudExecutionOptions(
|
|
9171
|
+
createSession(agentId, options = {}) {
|
|
9172
|
+
if (typeof agentId !== "string" || agentId.length === 0) {
|
|
9173
|
+
throw new Error("createSession() requires a non-empty agent id.");
|
|
9174
|
+
}
|
|
9175
|
+
const sessionOptions = stripCloudExecutionOptions(options);
|
|
9249
9176
|
validateCreateSessionOptions(sessionOptions);
|
|
9177
|
+
this.assertSessionBackend("createSession", options);
|
|
9250
9178
|
if (this.backend === "remote") {
|
|
9251
|
-
if (!agentId) {
|
|
9252
|
-
throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
|
|
9253
|
-
}
|
|
9254
9179
|
return new AppServerSession(this.appServerSessionOptions(), {
|
|
9255
9180
|
kind: "session",
|
|
9256
9181
|
agentId,
|
|
9257
9182
|
newConversation: true,
|
|
9258
|
-
options
|
|
9183
|
+
options
|
|
9259
9184
|
});
|
|
9260
9185
|
}
|
|
9261
9186
|
if (this.backend === "cloud") {
|
|
9262
|
-
if (!agentId) {
|
|
9263
|
-
throw new Error("Letta Cloud createSession() requires an agent id. Call createAgent() first or pass an agent id.");
|
|
9264
|
-
}
|
|
9265
9187
|
return new CloudEnvironmentSession(this.cloudOptions(), {
|
|
9266
9188
|
kind: "session",
|
|
9267
9189
|
agentId,
|
|
9268
9190
|
newConversation: true,
|
|
9269
|
-
options
|
|
9191
|
+
options
|
|
9270
9192
|
});
|
|
9271
9193
|
}
|
|
9272
|
-
return this.createLocalSession(agentId,
|
|
9194
|
+
return this.createLocalSession(agentId, options);
|
|
9273
9195
|
}
|
|
9274
9196
|
resumeSession(id, options = {}) {
|
|
9275
|
-
this.assertSessionBackend("resumeSession", options);
|
|
9276
9197
|
const sessionOptions = stripCloudExecutionOptions(options);
|
|
9277
9198
|
validateCreateSessionOptions(sessionOptions);
|
|
9199
|
+
this.assertSessionBackend("resumeSession", options);
|
|
9278
9200
|
if (this.backend === "remote") {
|
|
9279
9201
|
if (looksLikeConversationId(id)) {
|
|
9280
9202
|
return new AppServerSession(this.appServerSessionOptions(), {
|
|
@@ -9305,10 +9227,10 @@ class LettaAgentClientBase {
|
|
|
9305
9227
|
options
|
|
9306
9228
|
});
|
|
9307
9229
|
}
|
|
9308
|
-
return this.resumeLocalSession(id, options
|
|
9230
|
+
return this.resumeLocalSession(id, options);
|
|
9309
9231
|
}
|
|
9310
9232
|
async prompt(message, agentId, options = {}) {
|
|
9311
|
-
const session =
|
|
9233
|
+
const session = this.createSession(agentId, options);
|
|
9312
9234
|
try {
|
|
9313
9235
|
return await session.runTurn(message);
|
|
9314
9236
|
} finally {
|
|
@@ -9316,6 +9238,9 @@ class LettaAgentClientBase {
|
|
|
9316
9238
|
}
|
|
9317
9239
|
}
|
|
9318
9240
|
assertSessionBackend(action, options) {
|
|
9241
|
+
if (options.filesystemConfinement !== undefined && options.filesystemConfinement !== "memory") {
|
|
9242
|
+
throw new Error(`Invalid filesystemConfinement '${String(options.filesystemConfinement)}'. Valid value: memory.`);
|
|
9243
|
+
}
|
|
9319
9244
|
const effectiveEnvironment = options.environment ?? this.environment;
|
|
9320
9245
|
if (this.backend === "local") {
|
|
9321
9246
|
if (effectiveEnvironment !== undefined) {
|
|
@@ -9327,12 +9252,18 @@ class LettaAgentClientBase {
|
|
|
9327
9252
|
if (hasRepositoryResources(options)) {
|
|
9328
9253
|
throw new Error(`${action}() repository resources are only valid with backend: "cloud".`);
|
|
9329
9254
|
}
|
|
9330
|
-
if (
|
|
9331
|
-
|
|
9255
|
+
if (options.filesystemConfinement !== undefined) {
|
|
9256
|
+
const localOptions = this.options;
|
|
9257
|
+
if (localOptions.appServer?.url !== undefined) {
|
|
9258
|
+
throw new Error(`${action}() filesystemConfinement requires an SDK-owned local app-server process.`);
|
|
9259
|
+
}
|
|
9332
9260
|
}
|
|
9333
9261
|
return;
|
|
9334
9262
|
}
|
|
9335
9263
|
if (this.backend === "remote") {
|
|
9264
|
+
if (options.filesystemConfinement !== undefined) {
|
|
9265
|
+
throw new Error(`${action}() filesystemConfinement requires an SDK-owned local app-server process.`);
|
|
9266
|
+
}
|
|
9336
9267
|
if (options.environment !== undefined) {
|
|
9337
9268
|
throw new Error(`${action}() environment overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
|
|
9338
9269
|
}
|
|
@@ -9342,10 +9273,12 @@ class LettaAgentClientBase {
|
|
|
9342
9273
|
if (hasRepositoryResources(options)) {
|
|
9343
9274
|
throw new Error(`${action}() repository resources are only valid with backend: "cloud".`);
|
|
9344
9275
|
}
|
|
9345
|
-
assertRemoteSessionOptionsSupported(action, options);
|
|
9346
9276
|
return;
|
|
9347
9277
|
}
|
|
9348
9278
|
if (this.backend === "cloud") {
|
|
9279
|
+
if (options.filesystemConfinement !== undefined) {
|
|
9280
|
+
throw new Error(`${action}() filesystemConfinement is only supported with backend: "local".`);
|
|
9281
|
+
}
|
|
9349
9282
|
const cloudOptions = this.cloudOptions();
|
|
9350
9283
|
if (cloudOptions.environment !== undefined && options.sandbox !== undefined) {
|
|
9351
9284
|
throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default environment.`);
|
|
@@ -9358,16 +9291,13 @@ class LettaAgentClientBase {
|
|
|
9358
9291
|
}
|
|
9359
9292
|
throw new Error(`LettaAgentClient backend '${this.backend}' is not implemented yet. ${action} currently supports backend 'local' only.`);
|
|
9360
9293
|
}
|
|
9361
|
-
useLegacyLocalStdio() {
|
|
9362
|
-
return this.options.transport === "stdio";
|
|
9363
|
-
}
|
|
9364
9294
|
createLocalAgent(_options) {
|
|
9365
9295
|
throw this.localBackendUnavailableError();
|
|
9366
9296
|
}
|
|
9367
|
-
createLocalSession(_agentId, _options
|
|
9297
|
+
createLocalSession(_agentId, _options) {
|
|
9368
9298
|
throw this.localBackendUnavailableError();
|
|
9369
9299
|
}
|
|
9370
|
-
resumeLocalSession(_id, _options
|
|
9300
|
+
resumeLocalSession(_id, _options) {
|
|
9371
9301
|
throw this.localBackendUnavailableError();
|
|
9372
9302
|
}
|
|
9373
9303
|
localBackendUnavailableError() {
|
|
@@ -9383,10 +9313,7 @@ class LettaAgentClientBase {
|
|
|
9383
9313
|
return this.options;
|
|
9384
9314
|
}
|
|
9385
9315
|
appServerSessionOptions() {
|
|
9386
|
-
return
|
|
9387
|
-
...this.remoteOptions(),
|
|
9388
|
-
beforeConnect: () => this.releaseIdleManagementConnection()
|
|
9389
|
-
};
|
|
9316
|
+
return this.remoteOptions();
|
|
9390
9317
|
}
|
|
9391
9318
|
getRepositoriesClient() {
|
|
9392
9319
|
if (this.backend !== "cloud") {
|
|
@@ -9395,9 +9322,6 @@ class LettaAgentClientBase {
|
|
|
9395
9322
|
this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
|
|
9396
9323
|
return this.repositoriesClient;
|
|
9397
9324
|
}
|
|
9398
|
-
async releaseIdleManagementConnection() {
|
|
9399
|
-
await this.managementTransport?.releaseIdleConnection?.();
|
|
9400
|
-
}
|
|
9401
9325
|
getManagementTransport() {
|
|
9402
9326
|
if (this.managementTransport)
|
|
9403
9327
|
return this.managementTransport;
|
|
@@ -9420,32 +9344,373 @@ class LettaAgentClientBase {
|
|
|
9420
9344
|
|
|
9421
9345
|
// src/local-app-server.ts
|
|
9422
9346
|
import { spawn } from "node:child_process";
|
|
9347
|
+
import { homedir as homedir4 } from "node:os";
|
|
9348
|
+
import { join as join7 } from "node:path";
|
|
9349
|
+
|
|
9350
|
+
// node_modules/@letta-ai/letta-code/dist/memory-confinement.js
|
|
9351
|
+
import { homedir as homedir3 } from "node:os";
|
|
9352
|
+
import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
|
|
9353
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
9354
|
+
import { homedir as homedir2 } from "node:os";
|
|
9355
|
+
import { basename, dirname, isAbsolute, join as join2, resolve } from "node:path";
|
|
9356
|
+
import { posix, win32 } from "node:path";
|
|
9357
|
+
import { homedir } from "node:os";
|
|
9358
|
+
import { join } from "node:path";
|
|
9359
|
+
import { spawnSync } from "node:child_process";
|
|
9360
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
9361
|
+
import { delimiter, isAbsolute as isAbsolute2, join as join4 } from "node:path";
|
|
9362
|
+
var SANDBOX_ENV_VAR = "LETTA_SANDBOX";
|
|
9363
|
+
function buildFsSandboxPolicy(options) {
|
|
9364
|
+
return {
|
|
9365
|
+
baseWritableRoots: normalizeRoots(options.baseWritableRoots ?? []),
|
|
9366
|
+
deniedRoots: normalizeRoots(options.deniedRoots ?? []),
|
|
9367
|
+
readonlyRoots: normalizeRoots(options.readonlyRoots ?? []),
|
|
9368
|
+
writableRoots: normalizeRoots(options.writableRoots ?? []),
|
|
9369
|
+
restrictWrites: options.restrictWrites ?? false
|
|
9370
|
+
};
|
|
9371
|
+
}
|
|
9372
|
+
function normalizeSandboxPath(path) {
|
|
9373
|
+
const trimmed = path.trim();
|
|
9374
|
+
const absolute = posix.isAbsolute(trimmed) || win32.isAbsolute(trimmed) ? trimmed : posix.resolve("/", trimmed);
|
|
9375
|
+
const forward = absolute.replace(/\\/g, "/");
|
|
9376
|
+
return forward.replace(/\/+$/, "") || "/";
|
|
9377
|
+
}
|
|
9378
|
+
function normalizeRoots(roots) {
|
|
9379
|
+
const seen = new Set;
|
|
9380
|
+
for (const root of roots) {
|
|
9381
|
+
if (!root || !root.trim())
|
|
9382
|
+
continue;
|
|
9383
|
+
seen.add(normalizeSandboxPath(root));
|
|
9384
|
+
}
|
|
9385
|
+
return [...seen];
|
|
9386
|
+
}
|
|
9387
|
+
var LOCAL_BACKEND_DIR_ENV = "LETTA_LOCAL_BACKEND_DIR";
|
|
9388
|
+
function getLocalBackendStorageDir(homeDir = homedir(), env = process.env) {
|
|
9389
|
+
return env[LOCAL_BACKEND_DIR_ENV] ?? join(homeDir, ".letta", "lc-local-backend");
|
|
9390
|
+
}
|
|
9391
|
+
function getLocalBackendCrossAgentTreeRoot(storageDir = getLocalBackendStorageDir()) {
|
|
9392
|
+
return join(storageDir, "memfs");
|
|
9393
|
+
}
|
|
9394
|
+
function getDefaultAgentsTreeRoot(homeDir = homedir2()) {
|
|
9395
|
+
return canonicalizeRoot(join2(homeDir, ".letta", "agents"));
|
|
9396
|
+
}
|
|
9397
|
+
function getCrossBackendAgentsTreeRoots(options = {}) {
|
|
9398
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
9399
|
+
const localBackendStorageDir = options.localBackendStorageDir ?? getLocalBackendStorageDir(homeDir, options.env ?? process.env);
|
|
9400
|
+
return [
|
|
9401
|
+
getDefaultAgentsTreeRoot(homeDir),
|
|
9402
|
+
canonicalizeRoot(getLocalBackendCrossAgentTreeRoot(localBackendStorageDir))
|
|
9403
|
+
];
|
|
9404
|
+
}
|
|
9405
|
+
function getLettaHomeRoot(homeDir = homedir2()) {
|
|
9406
|
+
return canonicalizeRoot(join2(homeDir, ".letta"));
|
|
9407
|
+
}
|
|
9408
|
+
function canonicalizeRoot(input) {
|
|
9409
|
+
const abs = isAbsolute(input) ? input : resolve(input);
|
|
9410
|
+
let dir = abs;
|
|
9411
|
+
const tail = [];
|
|
9412
|
+
while (!existsSync(dir)) {
|
|
9413
|
+
tail.unshift(basename(dir));
|
|
9414
|
+
const parent = dirname(dir);
|
|
9415
|
+
if (parent === dir) {
|
|
9416
|
+
return normalizeSandboxPath(abs);
|
|
9417
|
+
}
|
|
9418
|
+
dir = parent;
|
|
9419
|
+
}
|
|
9420
|
+
try {
|
|
9421
|
+
const real = realpathSync(dir);
|
|
9422
|
+
return normalizeSandboxPath(tail.length ? join2(real, ...tail) : real);
|
|
9423
|
+
} catch {
|
|
9424
|
+
return normalizeSandboxPath(abs);
|
|
9425
|
+
}
|
|
9426
|
+
}
|
|
9427
|
+
function isWithinRoot(path, root) {
|
|
9428
|
+
return path === root || path.startsWith(`${root}/`);
|
|
9429
|
+
}
|
|
9430
|
+
function isAncestorOfRoot(path, root) {
|
|
9431
|
+
const prefix = path === "/" ? "/" : `${path}/`;
|
|
9432
|
+
return root.startsWith(prefix);
|
|
9433
|
+
}
|
|
9434
|
+
function isTreeOrAncestorOfTree(path, canonicalTrees) {
|
|
9435
|
+
return canonicalTrees.some((tree) => path === tree || isAncestorOfRoot(path, tree));
|
|
9436
|
+
}
|
|
9437
|
+
function resolveAgentsTreeRootsInput(roots) {
|
|
9438
|
+
return roots?.length ? roots.map(canonicalizeRoot) : getCrossBackendAgentsTreeRoots();
|
|
9439
|
+
}
|
|
9440
|
+
function deriveSelfAgentRootsForTrees(memoryRoots, agentsTreeRoots = getCrossBackendAgentsTreeRoots()) {
|
|
9441
|
+
const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);
|
|
9442
|
+
const out = new Set;
|
|
9443
|
+
for (const root of memoryRoots) {
|
|
9444
|
+
const canon = canonicalizeRoot(root);
|
|
9445
|
+
const containingTree = canonicalTrees.find((tree) => canon !== tree && isWithinRoot(canon, tree));
|
|
9446
|
+
if (containingTree) {
|
|
9447
|
+
const leaf = basename(canon);
|
|
9448
|
+
const parentLeaf = basename(dirname(canon));
|
|
9449
|
+
out.add(leaf === "memory" || leaf === "memory-worktrees" ? dirname(canon) : parentLeaf === "memory-worktrees" || parentLeaf === "memory" && leaf === ".git" ? dirname(dirname(canon)) : canon);
|
|
9450
|
+
continue;
|
|
9451
|
+
}
|
|
9452
|
+
if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {
|
|
9453
|
+
out.add(canon);
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
return [...out];
|
|
9457
|
+
}
|
|
9458
|
+
function deriveWritableMemoryRootsForTrees(memoryRoots, agentsTreeRoots) {
|
|
9459
|
+
const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);
|
|
9460
|
+
const out = new Set;
|
|
9461
|
+
for (const root of memoryRoots) {
|
|
9462
|
+
const canon = canonicalizeRoot(root);
|
|
9463
|
+
if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {
|
|
9464
|
+
out.add(canon);
|
|
9465
|
+
}
|
|
9466
|
+
}
|
|
9467
|
+
return [...out];
|
|
9468
|
+
}
|
|
9469
|
+
function buildMemorySubagentSandboxPolicy(input) {
|
|
9470
|
+
const agentsTreeRoots = resolveAgentsTreeRootsInput(input.agentsTreeRoots);
|
|
9471
|
+
const baseWritableRoots = [
|
|
9472
|
+
getLettaHomeRoot(),
|
|
9473
|
+
...input.harnessWritableRoots ?? []
|
|
9474
|
+
].map(canonicalizeRoot);
|
|
9475
|
+
return buildFsSandboxPolicy({
|
|
9476
|
+
baseWritableRoots,
|
|
9477
|
+
deniedRoots: agentsTreeRoots,
|
|
9478
|
+
readonlyRoots: [
|
|
9479
|
+
...deriveSelfAgentRootsForTrees(input.memoryRoots, agentsTreeRoots),
|
|
9480
|
+
...(input.readonlyRoots ?? []).map(canonicalizeRoot)
|
|
9481
|
+
],
|
|
9482
|
+
writableRoots: deriveWritableMemoryRootsForTrees(input.memoryRoots, agentsTreeRoots),
|
|
9483
|
+
restrictWrites: true
|
|
9484
|
+
});
|
|
9485
|
+
}
|
|
9486
|
+
var BWRAP_BIN = "bwrap";
|
|
9487
|
+
function buildBwrapArgs(policy) {
|
|
9488
|
+
const args = [];
|
|
9489
|
+
args.push(policy.restrictWrites ? "--ro-bind" : "--bind", "/", "/");
|
|
9490
|
+
args.push("--dev", "/dev");
|
|
9491
|
+
args.push("--proc", "/proc");
|
|
9492
|
+
for (const root of policy.baseWritableRoots) {
|
|
9493
|
+
args.push("--bind-try", root, root);
|
|
9494
|
+
}
|
|
9495
|
+
for (const root of policy.deniedRoots) {
|
|
9496
|
+
args.push("--tmpfs", root);
|
|
9497
|
+
}
|
|
9498
|
+
for (const root of policy.readonlyRoots) {
|
|
9499
|
+
args.push("--ro-bind-try", root, root);
|
|
9500
|
+
}
|
|
9501
|
+
for (const root of policy.writableRoots) {
|
|
9502
|
+
args.push("--bind-try", root, root);
|
|
9503
|
+
}
|
|
9504
|
+
args.push("--die-with-parent");
|
|
9505
|
+
return args;
|
|
9506
|
+
}
|
|
9507
|
+
var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
|
|
9508
|
+
function buildSeatbeltProfile(policy) {
|
|
9509
|
+
const defines = [];
|
|
9510
|
+
const lines = ["(version 1)", "(allow default)"];
|
|
9511
|
+
if (policy.restrictWrites) {
|
|
9512
|
+
lines.push('(deny file-write* (subpath "/"))');
|
|
9513
|
+
lines.push('(allow file-write* (subpath "/dev"))');
|
|
9514
|
+
}
|
|
9515
|
+
policy.baseWritableRoots.forEach((root, i) => {
|
|
9516
|
+
const name = `BASEWRITABLE_${i}`;
|
|
9517
|
+
defines.push({ name, value: root });
|
|
9518
|
+
lines.push(`(allow file-write* (subpath (param "${name}")))`);
|
|
9519
|
+
});
|
|
9520
|
+
policy.deniedRoots.forEach((root, i) => {
|
|
9521
|
+
const name = `DENIED_${i}`;
|
|
9522
|
+
defines.push({ name, value: root });
|
|
9523
|
+
lines.push(`(deny file-read* file-write* (subpath (param "${name}")))`);
|
|
9524
|
+
lines.push(`(allow file-read-metadata (literal (param "${name}")))`);
|
|
9525
|
+
});
|
|
9526
|
+
policy.writableRoots.forEach((root, i) => {
|
|
9527
|
+
const name = `WRITABLE_${i}`;
|
|
9528
|
+
defines.push({ name, value: root });
|
|
9529
|
+
lines.push(`(allow file-read* file-write* (subpath (param "${name}")))`);
|
|
9530
|
+
});
|
|
9531
|
+
policy.readonlyRoots.forEach((root, i) => {
|
|
9532
|
+
const name = `READONLY_${i}`;
|
|
9533
|
+
defines.push({ name, value: root });
|
|
9534
|
+
lines.push(`(allow file-read* (subpath (param "${name}")))`);
|
|
9535
|
+
});
|
|
9536
|
+
return { profile: `${lines.join(`
|
|
9537
|
+
`)}
|
|
9538
|
+
`, defines };
|
|
9539
|
+
}
|
|
9540
|
+
function buildSeatbeltArgs(policy) {
|
|
9541
|
+
const { profile, defines } = buildSeatbeltProfile(policy);
|
|
9542
|
+
const args = ["-p", profile];
|
|
9543
|
+
for (const { name, value } of defines) {
|
|
9544
|
+
args.push(`-D${name}=${value}`);
|
|
9545
|
+
}
|
|
9546
|
+
return args;
|
|
9547
|
+
}
|
|
9548
|
+
function wrapLauncher(launcher, policy, options) {
|
|
9549
|
+
if (!options.backend)
|
|
9550
|
+
return null;
|
|
9551
|
+
if (launcher.length === 0)
|
|
9552
|
+
return null;
|
|
9553
|
+
switch (options.backend) {
|
|
9554
|
+
case "seatbelt":
|
|
9555
|
+
return [
|
|
9556
|
+
SANDBOX_EXEC_PATH,
|
|
9557
|
+
...buildSeatbeltArgs(policy),
|
|
9558
|
+
"--",
|
|
9559
|
+
...launcher
|
|
9560
|
+
];
|
|
9561
|
+
case "bwrap":
|
|
9562
|
+
return [
|
|
9563
|
+
options.bwrapPath ?? BWRAP_BIN,
|
|
9564
|
+
...buildBwrapArgs(policy),
|
|
9565
|
+
"--",
|
|
9566
|
+
...launcher
|
|
9567
|
+
];
|
|
9568
|
+
}
|
|
9569
|
+
}
|
|
9570
|
+
function normalizeRoot(path) {
|
|
9571
|
+
const trimmed = path.trim();
|
|
9572
|
+
const expanded = trimmed.startsWith("~/") ? join3(homedir3(), trimmed.slice(2)) : trimmed.startsWith("$HOME/") ? join3(homedir3(), trimmed.slice(6)) : trimmed;
|
|
9573
|
+
return resolve2(expanded);
|
|
9574
|
+
}
|
|
9575
|
+
function resolveWritableMemoryRoots(env) {
|
|
9576
|
+
const roots = new Set;
|
|
9577
|
+
for (const value of [env.MEMORY_DIR, env.LETTA_MEMORY_DIR]) {
|
|
9578
|
+
if (!value?.trim())
|
|
9579
|
+
continue;
|
|
9580
|
+
const root = normalizeRoot(value);
|
|
9581
|
+
roots.add(root);
|
|
9582
|
+
if (basename2(root) === "memory") {
|
|
9583
|
+
roots.add(join3(dirname2(root), "memory-worktrees"));
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
return [...roots];
|
|
9587
|
+
}
|
|
9588
|
+
function customHarnessWritableRoots(env) {
|
|
9589
|
+
return [env.LETTA_LOCAL_BACKEND_DIR, env.LETTA_TRANSCRIPT_ROOT].filter((value) => Boolean(value?.trim())).map(normalizeRoot);
|
|
9590
|
+
}
|
|
9591
|
+
function createMemoryConfinementLauncherWithAvailability(input, availability) {
|
|
9592
|
+
if (input.launcher.length === 0) {
|
|
9593
|
+
throw new Error("Memory confinement requires a non-empty launcher.");
|
|
9594
|
+
}
|
|
9595
|
+
const memoryRoots = resolveWritableMemoryRoots(input.env);
|
|
9596
|
+
if (memoryRoots.length === 0) {
|
|
9597
|
+
throw new Error("Memory confinement requires MEMORY_DIR or LETTA_MEMORY_DIR.");
|
|
9598
|
+
}
|
|
9599
|
+
if (!availability.backend) {
|
|
9600
|
+
throw new Error(`Memory confinement is unavailable: ${availability.reason}.`);
|
|
9601
|
+
}
|
|
9602
|
+
const localBackendStorageDir = input.env.LETTA_LOCAL_BACKEND_DIR?.trim() || undefined;
|
|
9603
|
+
const policy = buildMemorySubagentSandboxPolicy({
|
|
9604
|
+
memoryRoots,
|
|
9605
|
+
agentsTreeRoots: getCrossBackendAgentsTreeRoots({
|
|
9606
|
+
env: input.env,
|
|
9607
|
+
localBackendStorageDir
|
|
9608
|
+
}),
|
|
9609
|
+
harnessWritableRoots: customHarnessWritableRoots(input.env)
|
|
9610
|
+
});
|
|
9611
|
+
const launcher = wrapLauncher(input.launcher, policy, {
|
|
9612
|
+
backend: availability.backend,
|
|
9613
|
+
bwrapPath: availability.bwrapPath
|
|
9614
|
+
});
|
|
9615
|
+
if (!launcher) {
|
|
9616
|
+
throw new Error("Memory confinement could not wrap the launcher.");
|
|
9617
|
+
}
|
|
9618
|
+
return {
|
|
9619
|
+
launcher,
|
|
9620
|
+
env: { ...input.env, [SANDBOX_ENV_VAR]: availability.backend },
|
|
9621
|
+
backend: availability.backend
|
|
9622
|
+
};
|
|
9623
|
+
}
|
|
9624
|
+
var cached = null;
|
|
9625
|
+
var warnedUnavailableContexts = new Set;
|
|
9626
|
+
function detectSandboxBackend(options = {}) {
|
|
9627
|
+
const platform = options.platform ?? process.platform;
|
|
9628
|
+
if (!options.force && cached && !options.platform) {
|
|
9629
|
+
return cached;
|
|
9630
|
+
}
|
|
9631
|
+
const result = probe(platform);
|
|
9632
|
+
if (!options.platform) {
|
|
9633
|
+
cached = result;
|
|
9634
|
+
}
|
|
9635
|
+
return result;
|
|
9636
|
+
}
|
|
9637
|
+
function probe(platform) {
|
|
9638
|
+
if (platform === "darwin") {
|
|
9639
|
+
if (existsSync2(SANDBOX_EXEC_PATH)) {
|
|
9640
|
+
return { backend: "seatbelt", reason: "sandbox-exec available" };
|
|
9641
|
+
}
|
|
9642
|
+
return {
|
|
9643
|
+
backend: null,
|
|
9644
|
+
reason: `${SANDBOX_EXEC_PATH} not found`
|
|
9645
|
+
};
|
|
9646
|
+
}
|
|
9647
|
+
if (platform === "linux") {
|
|
9648
|
+
return probeBwrap();
|
|
9649
|
+
}
|
|
9650
|
+
return {
|
|
9651
|
+
backend: null,
|
|
9652
|
+
reason: `no filesystem sandbox backend for platform "${platform}"`
|
|
9653
|
+
};
|
|
9654
|
+
}
|
|
9655
|
+
function probeBwrap() {
|
|
9656
|
+
const bwrapPath = resolveExecutableOnPath("bwrap");
|
|
9657
|
+
if (!bwrapPath) {
|
|
9658
|
+
return { backend: null, reason: "bwrap not found on PATH" };
|
|
9659
|
+
}
|
|
9660
|
+
const version = spawnSync(bwrapPath, ["--version"], { timeout: 5000 });
|
|
9661
|
+
if (version.error || version.status !== 0) {
|
|
9662
|
+
return { backend: null, reason: "bwrap not found on PATH" };
|
|
9663
|
+
}
|
|
9664
|
+
const userns = spawnSync(bwrapPath, ["--ro-bind", "/", "/", "--unshare-user", "/bin/true"], { timeout: 5000 });
|
|
9665
|
+
if (userns.error || userns.status !== 0) {
|
|
9666
|
+
return {
|
|
9667
|
+
backend: null,
|
|
9668
|
+
reason: "bwrap present but user namespaces are unavailable"
|
|
9669
|
+
};
|
|
9670
|
+
}
|
|
9671
|
+
return { backend: "bwrap", bwrapPath, reason: "bwrap available" };
|
|
9672
|
+
}
|
|
9673
|
+
function resolveExecutableOnPath(executable, envPath = process.env.PATH) {
|
|
9674
|
+
if (isAbsolute2(executable) && existsSync2(executable))
|
|
9675
|
+
return executable;
|
|
9676
|
+
for (const dir of (envPath ?? "").split(delimiter)) {
|
|
9677
|
+
if (!dir)
|
|
9678
|
+
continue;
|
|
9679
|
+
const candidate = join4(dir, executable);
|
|
9680
|
+
if (existsSync2(candidate))
|
|
9681
|
+
return candidate;
|
|
9682
|
+
}
|
|
9683
|
+
return null;
|
|
9684
|
+
}
|
|
9685
|
+
function createMemoryConfinementLauncher(input) {
|
|
9686
|
+
return createMemoryConfinementLauncherWithAvailability(input, detectSandboxBackend());
|
|
9687
|
+
}
|
|
9423
9688
|
|
|
9424
9689
|
// src/cli-resolver.ts
|
|
9425
|
-
import { existsSync } from "node:fs";
|
|
9426
|
-
import { createRequire
|
|
9427
|
-
import { dirname, join } from "node:path";
|
|
9690
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
9691
|
+
import { createRequire } from "node:module";
|
|
9692
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
9428
9693
|
import { fileURLToPath } from "node:url";
|
|
9429
|
-
var require2 =
|
|
9694
|
+
var require2 = createRequire(import.meta.url);
|
|
9430
9695
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
9431
|
-
var __dirname2 =
|
|
9696
|
+
var __dirname2 = dirname3(__filename2);
|
|
9432
9697
|
function findLettaCli() {
|
|
9433
9698
|
const envPath = process.env.LETTA_CLI_PATH;
|
|
9434
|
-
if (envPath &&
|
|
9699
|
+
if (envPath && existsSync3(envPath)) {
|
|
9435
9700
|
return envPath;
|
|
9436
9701
|
}
|
|
9437
9702
|
try {
|
|
9438
9703
|
return require2.resolve("@letta-ai/letta-code");
|
|
9439
9704
|
} catch {}
|
|
9440
9705
|
const localPaths = [
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9706
|
+
join5(__dirname2, "..", "node_modules", "@letta-ai", "letta-code", "letta.js"),
|
|
9707
|
+
join5(__dirname2, "../../@letta-ai/letta-code/letta.js"),
|
|
9708
|
+
join5(__dirname2, "../../../letta-code-prod/letta.js"),
|
|
9709
|
+
join5(__dirname2, "../../../letta-code/letta.js"),
|
|
9710
|
+
join5(__dirname2, "..", "..", "letta-code", "letta.js")
|
|
9446
9711
|
];
|
|
9447
9712
|
for (const path of localPaths) {
|
|
9448
|
-
if (
|
|
9713
|
+
if (existsSync3(path)) {
|
|
9449
9714
|
return path;
|
|
9450
9715
|
}
|
|
9451
9716
|
}
|
|
@@ -9456,6 +9721,38 @@ function findLettaCli() {
|
|
|
9456
9721
|
var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0";
|
|
9457
9722
|
var DEFAULT_STARTUP_TIMEOUT_MS = 30000;
|
|
9458
9723
|
var LISTENING_RE = /^Listening on\s+(ws:\/\/\S+)\s*$/m;
|
|
9724
|
+
function targetHomeDirectory(env) {
|
|
9725
|
+
if (process.platform === "win32") {
|
|
9726
|
+
const profile = env.USERPROFILE?.trim();
|
|
9727
|
+
if (profile)
|
|
9728
|
+
return profile;
|
|
9729
|
+
const drive = env.HOMEDRIVE?.trim();
|
|
9730
|
+
const path = env.HOMEPATH?.trim();
|
|
9731
|
+
if (drive && path)
|
|
9732
|
+
return `${drive}${path}`;
|
|
9733
|
+
return homedir4();
|
|
9734
|
+
}
|
|
9735
|
+
return env.HOME?.trim() || homedir4();
|
|
9736
|
+
}
|
|
9737
|
+
function withDefaultMemoryDirectory(env, options) {
|
|
9738
|
+
const explicitMemoryDir = options.env?.MEMORY_DIR?.trim();
|
|
9739
|
+
const explicitLettaMemoryDir = options.env?.LETTA_MEMORY_DIR?.trim();
|
|
9740
|
+
const scopedEnv = { ...env };
|
|
9741
|
+
if (explicitMemoryDir || explicitLettaMemoryDir) {
|
|
9742
|
+
if (!explicitMemoryDir)
|
|
9743
|
+
delete scopedEnv.MEMORY_DIR;
|
|
9744
|
+
if (!explicitLettaMemoryDir)
|
|
9745
|
+
delete scopedEnv.LETTA_MEMORY_DIR;
|
|
9746
|
+
return scopedEnv;
|
|
9747
|
+
}
|
|
9748
|
+
delete scopedEnv.MEMORY_DIR;
|
|
9749
|
+
delete scopedEnv.LETTA_MEMORY_DIR;
|
|
9750
|
+
if (!options.agentId)
|
|
9751
|
+
return scopedEnv;
|
|
9752
|
+
const homeDir = targetHomeDirectory(env);
|
|
9753
|
+
const memoryDir = options.backend === "api" ? join7(homeDir, ".letta", "agents", options.agentId, "memory") : join7(env.LETTA_LOCAL_BACKEND_DIR?.trim() || join7(homeDir, ".letta", "lc-local-backend"), "memfs", options.agentId, "memory");
|
|
9754
|
+
return { ...scopedEnv, MEMORY_DIR: memoryDir };
|
|
9755
|
+
}
|
|
9459
9756
|
function appendLine(buffer, chunk) {
|
|
9460
9757
|
return buffer + String(chunk);
|
|
9461
9758
|
}
|
|
@@ -9472,6 +9769,29 @@ function buildLocalAppServerArgs(cliPath, options = {}) {
|
|
|
9472
9769
|
options.listen ?? DEFAULT_LISTEN_URL
|
|
9473
9770
|
];
|
|
9474
9771
|
}
|
|
9772
|
+
function buildLocalAppServerProcess(cliPath, options = {}, confineMemory = createMemoryConfinementLauncher) {
|
|
9773
|
+
const env = { ...process.env, ...options.env ?? {} };
|
|
9774
|
+
const launcher = [
|
|
9775
|
+
process.execPath,
|
|
9776
|
+
...buildLocalAppServerArgs(cliPath, options)
|
|
9777
|
+
];
|
|
9778
|
+
if (options.filesystemConfinement !== "memory") {
|
|
9779
|
+
return {
|
|
9780
|
+
command: launcher[0],
|
|
9781
|
+
args: launcher.slice(1),
|
|
9782
|
+
env
|
|
9783
|
+
};
|
|
9784
|
+
}
|
|
9785
|
+
const confined = confineMemory({
|
|
9786
|
+
launcher,
|
|
9787
|
+
env: withDefaultMemoryDirectory(env, options)
|
|
9788
|
+
});
|
|
9789
|
+
return {
|
|
9790
|
+
command: confined.launcher[0],
|
|
9791
|
+
args: confined.launcher.slice(1),
|
|
9792
|
+
env: confined.env
|
|
9793
|
+
};
|
|
9794
|
+
}
|
|
9475
9795
|
function terminateProcess(child) {
|
|
9476
9796
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
9477
9797
|
return;
|
|
@@ -9484,12 +9804,12 @@ function terminateProcess(child) {
|
|
|
9484
9804
|
}
|
|
9485
9805
|
function startLocalAppServer(options = {}) {
|
|
9486
9806
|
const cliPath = options.cliPath ?? findLettaCli();
|
|
9487
|
-
const
|
|
9807
|
+
const processSpec = buildLocalAppServerProcess(cliPath, options);
|
|
9488
9808
|
const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
9489
|
-
return new Promise((
|
|
9490
|
-
const child = spawn(
|
|
9809
|
+
return new Promise((resolve3, reject) => {
|
|
9810
|
+
const child = spawn(processSpec.command, processSpec.args, {
|
|
9491
9811
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9492
|
-
env:
|
|
9812
|
+
env: processSpec.env
|
|
9493
9813
|
});
|
|
9494
9814
|
let settled = false;
|
|
9495
9815
|
let output = "";
|
|
@@ -9513,7 +9833,7 @@ function startLocalAppServer(options = {}) {
|
|
|
9513
9833
|
return;
|
|
9514
9834
|
settled = true;
|
|
9515
9835
|
cleanup();
|
|
9516
|
-
|
|
9836
|
+
resolve3({
|
|
9517
9837
|
url,
|
|
9518
9838
|
close: () => terminateProcess(child)
|
|
9519
9839
|
});
|
|
@@ -9547,7 +9867,7 @@ ${output.trim()}` : ""}`));
|
|
|
9547
9867
|
}
|
|
9548
9868
|
|
|
9549
9869
|
// src/local-app-server-session.ts
|
|
9550
|
-
function createLocalAppServerSession(options, mode
|
|
9870
|
+
function createLocalAppServerSession(options, mode) {
|
|
9551
9871
|
const appServer = options ?? {};
|
|
9552
9872
|
const sessionOptions = {
|
|
9553
9873
|
...appServer.url !== undefined ? { url: appServer.url } : {
|
|
@@ -9555,1396 +9875,21 @@ function createLocalAppServerSession(options, mode, beforeConnect) {
|
|
|
9555
9875
|
listen: appServer.listen,
|
|
9556
9876
|
backend: appServer.harnessBackend ?? "local",
|
|
9557
9877
|
startupTimeoutMs: appServer.startupTimeoutMs,
|
|
9558
|
-
env: sessionEnv
|
|
9878
|
+
env: sessionEnv,
|
|
9879
|
+
filesystemConfinement: mode.kind === "session" ? mode.options.filesystemConfinement : undefined,
|
|
9880
|
+
agentId: mode.kind === "session" && "agentId" in mode ? mode.agentId : undefined
|
|
9559
9881
|
})
|
|
9560
9882
|
},
|
|
9561
9883
|
...appServer.WebSocket !== undefined ? { WebSocket: appServer.WebSocket } : {},
|
|
9562
9884
|
...appServer.requestTimeoutMs !== undefined ? { requestTimeoutMs: appServer.requestTimeoutMs } : {},
|
|
9563
|
-
...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {}
|
|
9564
|
-
...beforeConnect ? { beforeConnect } : {}
|
|
9885
|
+
...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {}
|
|
9565
9886
|
};
|
|
9566
9887
|
return new AppServerSession(sessionOptions, mode);
|
|
9567
9888
|
}
|
|
9568
9889
|
|
|
9569
|
-
// src/transport.ts
|
|
9570
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
9571
|
-
import { createInterface } from "node:readline";
|
|
9572
|
-
function sdkLog(tag, ...args) {
|
|
9573
|
-
if (process.env.DEBUG_SDK)
|
|
9574
|
-
console.error(`[SDK-Transport] [${tag}]`, ...args);
|
|
9575
|
-
}
|
|
9576
|
-
var SDK_AGENT_ORIGIN_TAG = "origin:letta-code";
|
|
9577
|
-
function shouldApplyNewAgentDefaults(options) {
|
|
9578
|
-
return options.createOnly === true;
|
|
9579
|
-
}
|
|
9580
|
-
function includeSdkAgentOriginTag(tags) {
|
|
9581
|
-
const normalizedTags = [];
|
|
9582
|
-
let hasOriginTag = false;
|
|
9583
|
-
for (const tag of tags ?? []) {
|
|
9584
|
-
if (tag === SDK_AGENT_ORIGIN_TAG) {
|
|
9585
|
-
if (hasOriginTag)
|
|
9586
|
-
continue;
|
|
9587
|
-
hasOriginTag = true;
|
|
9588
|
-
}
|
|
9589
|
-
normalizedTags.push(tag);
|
|
9590
|
-
}
|
|
9591
|
-
if (!hasOriginTag) {
|
|
9592
|
-
normalizedTags.push(SDK_AGENT_ORIGIN_TAG);
|
|
9593
|
-
}
|
|
9594
|
-
return normalizedTags;
|
|
9595
|
-
}
|
|
9596
|
-
function buildCliArgs(options) {
|
|
9597
|
-
const args = [
|
|
9598
|
-
"--output-format",
|
|
9599
|
-
"stream-json",
|
|
9600
|
-
"--input-format",
|
|
9601
|
-
"stream-json"
|
|
9602
|
-
];
|
|
9603
|
-
const applyNewAgentDefaults = shouldApplyNewAgentDefaults(options);
|
|
9604
|
-
if (options.conversationId) {
|
|
9605
|
-
args.push("--conversation", options.conversationId);
|
|
9606
|
-
} else if (options.agentId) {
|
|
9607
|
-
args.push("--agent", options.agentId);
|
|
9608
|
-
if (options.newConversation) {
|
|
9609
|
-
args.push("--new");
|
|
9610
|
-
} else if (options.defaultConversation) {
|
|
9611
|
-
args.push("--conversation", "default");
|
|
9612
|
-
}
|
|
9613
|
-
} else if (options.createOnly) {
|
|
9614
|
-
args.push("--new-agent");
|
|
9615
|
-
} else if (options.newConversation) {
|
|
9616
|
-
args.push("--new");
|
|
9617
|
-
}
|
|
9618
|
-
if (options.model) {
|
|
9619
|
-
args.push("-m", options.model);
|
|
9620
|
-
}
|
|
9621
|
-
if (options.includePartialMessages) {
|
|
9622
|
-
args.push("--include-partial-messages");
|
|
9623
|
-
}
|
|
9624
|
-
if (options.embedding) {
|
|
9625
|
-
args.push("--embedding", options.embedding);
|
|
9626
|
-
}
|
|
9627
|
-
if (options.systemPrompt !== undefined) {
|
|
9628
|
-
if (typeof options.systemPrompt === "string") {
|
|
9629
|
-
const validPresets = [
|
|
9630
|
-
"default",
|
|
9631
|
-
"letta-claude",
|
|
9632
|
-
"letta-codex",
|
|
9633
|
-
"letta-gemini",
|
|
9634
|
-
"claude",
|
|
9635
|
-
"codex",
|
|
9636
|
-
"gemini"
|
|
9637
|
-
];
|
|
9638
|
-
if (validPresets.includes(options.systemPrompt)) {
|
|
9639
|
-
args.push("--system", options.systemPrompt);
|
|
9640
|
-
} else {
|
|
9641
|
-
args.push("--system-custom", options.systemPrompt);
|
|
9642
|
-
}
|
|
9643
|
-
} else {
|
|
9644
|
-
args.push("--system", options.systemPrompt.preset);
|
|
9645
|
-
if (options.systemPrompt.append) {
|
|
9646
|
-
args.push("--system-append", options.systemPrompt.append);
|
|
9647
|
-
}
|
|
9648
|
-
}
|
|
9649
|
-
}
|
|
9650
|
-
if (options.memory !== undefined && !options.agentId) {
|
|
9651
|
-
if (options.memory.length === 0) {
|
|
9652
|
-
args.push("--init-blocks", "");
|
|
9653
|
-
} else {
|
|
9654
|
-
const presetNames = [];
|
|
9655
|
-
const memoryBlocksJson = [];
|
|
9656
|
-
for (const item of options.memory) {
|
|
9657
|
-
if (typeof item === "string") {
|
|
9658
|
-
presetNames.push(item);
|
|
9659
|
-
} else if ("blockId" in item) {
|
|
9660
|
-
memoryBlocksJson.push(item);
|
|
9661
|
-
} else {
|
|
9662
|
-
memoryBlocksJson.push(item);
|
|
9663
|
-
}
|
|
9664
|
-
}
|
|
9665
|
-
if (memoryBlocksJson.length > 0) {
|
|
9666
|
-
args.push("--memory-blocks", JSON.stringify(memoryBlocksJson));
|
|
9667
|
-
if (presetNames.length > 0) {
|
|
9668
|
-
console.warn("[letta-agent-sdk] Using custom memory blocks. " + `Preset blocks are ignored when custom blocks are provided: ${presetNames.join(", ")}`);
|
|
9669
|
-
}
|
|
9670
|
-
} else if (presetNames.length > 0) {
|
|
9671
|
-
args.push("--init-blocks", presetNames.join(","));
|
|
9672
|
-
}
|
|
9673
|
-
}
|
|
9674
|
-
}
|
|
9675
|
-
if (!options.agentId) {
|
|
9676
|
-
if (options.persona !== undefined) {
|
|
9677
|
-
args.push("--block-value", `persona=${options.persona}`);
|
|
9678
|
-
}
|
|
9679
|
-
if (options.human !== undefined) {
|
|
9680
|
-
args.push("--block-value", `human=${options.human}`);
|
|
9681
|
-
}
|
|
9682
|
-
}
|
|
9683
|
-
const mode = normalizePermissionMode(options.permissionMode);
|
|
9684
|
-
if (mode !== undefined) {
|
|
9685
|
-
args.push("--permission-mode", mode);
|
|
9686
|
-
}
|
|
9687
|
-
if (options.allowedTools) {
|
|
9688
|
-
args.push("--allowedTools", options.allowedTools.join(","));
|
|
9689
|
-
}
|
|
9690
|
-
if (options.disallowedTools) {
|
|
9691
|
-
args.push("--disallowedTools", options.disallowedTools.join(","));
|
|
9692
|
-
}
|
|
9693
|
-
const tags = applyNewAgentDefaults ? includeSdkAgentOriginTag(options.tags) : options.tags;
|
|
9694
|
-
if (tags && tags.length > 0) {
|
|
9695
|
-
args.push("--tags", tags.join(","));
|
|
9696
|
-
}
|
|
9697
|
-
if (applyNewAgentDefaults) {
|
|
9698
|
-
args.push("--memfs");
|
|
9699
|
-
if (options.baseTools !== undefined) {
|
|
9700
|
-
args.push("--base-tools", options.baseTools.length > 0 ? options.baseTools.join(",") : "none");
|
|
9701
|
-
}
|
|
9702
|
-
}
|
|
9703
|
-
if (options.skillSources !== undefined) {
|
|
9704
|
-
const sources = [...new Set(options.skillSources)];
|
|
9705
|
-
if (sources.length === 0) {
|
|
9706
|
-
args.push("--no-skills");
|
|
9707
|
-
} else {
|
|
9708
|
-
args.push("--skill-sources", sources.join(","));
|
|
9709
|
-
}
|
|
9710
|
-
}
|
|
9711
|
-
if (options.systemInfoReminder === false) {
|
|
9712
|
-
args.push("--no-system-info-reminder");
|
|
9713
|
-
}
|
|
9714
|
-
if (options.dreaming?.trigger !== undefined) {
|
|
9715
|
-
args.push("--reflection-trigger", options.dreaming.trigger);
|
|
9716
|
-
}
|
|
9717
|
-
if (options.dreaming?.behavior !== undefined) {
|
|
9718
|
-
args.push("--reflection-behavior", options.dreaming.behavior);
|
|
9719
|
-
}
|
|
9720
|
-
if (options.dreaming?.stepCount !== undefined) {
|
|
9721
|
-
args.push("--reflection-step-count", String(options.dreaming.stepCount));
|
|
9722
|
-
}
|
|
9723
|
-
return args;
|
|
9724
|
-
}
|
|
9725
|
-
|
|
9726
|
-
class SubprocessTransport {
|
|
9727
|
-
options;
|
|
9728
|
-
process = null;
|
|
9729
|
-
stdout = null;
|
|
9730
|
-
messageQueue = [];
|
|
9731
|
-
messageResolvers = [];
|
|
9732
|
-
closed = false;
|
|
9733
|
-
agentId;
|
|
9734
|
-
wireMessageCount = 0;
|
|
9735
|
-
lastMessageAt = 0;
|
|
9736
|
-
stderrLines = [];
|
|
9737
|
-
constructor(options = {}) {
|
|
9738
|
-
this.options = options;
|
|
9739
|
-
}
|
|
9740
|
-
async connect() {
|
|
9741
|
-
const args = this.buildArgs();
|
|
9742
|
-
const cliPath = await this.findCli();
|
|
9743
|
-
sdkLog("connect", `CLI: ${cliPath}`);
|
|
9744
|
-
sdkLog("connect", `args: ${args.join(" ")}`);
|
|
9745
|
-
sdkLog("connect", `cwd: ${this.options.cwd || process.cwd()}`);
|
|
9746
|
-
sdkLog("connect", `permissionMode: ${this.options.permissionMode || "default"}`);
|
|
9747
|
-
this.process = spawn2("node", [cliPath, ...args], {
|
|
9748
|
-
cwd: this.options.cwd || process.cwd(),
|
|
9749
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
9750
|
-
env: { ...process.env }
|
|
9751
|
-
});
|
|
9752
|
-
const pid = this.process.pid;
|
|
9753
|
-
sdkLog("connect", `CLI process spawned, pid=${pid}`);
|
|
9754
|
-
if (!this.process.stdout || !this.process.stdin) {
|
|
9755
|
-
throw new Error("Failed to create subprocess pipes");
|
|
9756
|
-
}
|
|
9757
|
-
this.stdout = createInterface({
|
|
9758
|
-
input: this.process.stdout,
|
|
9759
|
-
crlfDelay: Infinity
|
|
9760
|
-
});
|
|
9761
|
-
this.stdout.on("line", (line) => {
|
|
9762
|
-
if (!line.trim())
|
|
9763
|
-
return;
|
|
9764
|
-
try {
|
|
9765
|
-
const msg = JSON.parse(line);
|
|
9766
|
-
this.handleMessage(msg);
|
|
9767
|
-
} catch {
|
|
9768
|
-
sdkLog("stdout", `[non-JSON] ${line.slice(0, 500)}`);
|
|
9769
|
-
}
|
|
9770
|
-
});
|
|
9771
|
-
if (this.process.stderr) {
|
|
9772
|
-
this.process.stderr.on("data", (data) => {
|
|
9773
|
-
const msg = data.toString().trim();
|
|
9774
|
-
if (msg) {
|
|
9775
|
-
console.error("[letta-agent-sdk] CLI stderr:", msg);
|
|
9776
|
-
this.stderrLines.push(msg);
|
|
9777
|
-
}
|
|
9778
|
-
});
|
|
9779
|
-
}
|
|
9780
|
-
this.process.on("close", (code, signal) => {
|
|
9781
|
-
if (code !== 0 && code !== null) {
|
|
9782
|
-
console.error(`[letta-agent-sdk] CLI process exited with code ${code}`);
|
|
9783
|
-
}
|
|
9784
|
-
sdkLog("close", `CLI process exited: pid=${pid} code=${code} signal=${signal} wireMessages=${this.wireMessageCount} msSinceLastMsg=${this.lastMessageAt ? Date.now() - this.lastMessageAt : 0} pendingResolvers=${this.messageResolvers.length} queueLen=${this.messageQueue.length}`);
|
|
9785
|
-
this.closed = true;
|
|
9786
|
-
for (const resolve of this.messageResolvers) {
|
|
9787
|
-
resolve(null);
|
|
9788
|
-
}
|
|
9789
|
-
this.messageResolvers = [];
|
|
9790
|
-
});
|
|
9791
|
-
this.process.on("error", (err) => {
|
|
9792
|
-
console.error("[letta-agent-sdk] CLI process error:", err);
|
|
9793
|
-
this.closed = true;
|
|
9794
|
-
});
|
|
9795
|
-
}
|
|
9796
|
-
async write(data) {
|
|
9797
|
-
if (!this.process?.stdin || this.closed) {
|
|
9798
|
-
const err = new Error(`Transport not connected (closed=${this.closed}, pid=${this.process?.pid}, stdin=${!!this.process?.stdin})`);
|
|
9799
|
-
sdkLog("write", err.message);
|
|
9800
|
-
throw err;
|
|
9801
|
-
}
|
|
9802
|
-
const payload = data;
|
|
9803
|
-
sdkLog("write", `type=${payload.type} subtype=${payload.request?.subtype || payload.response?.subtype || "N/A"}`);
|
|
9804
|
-
this.process.stdin.write(JSON.stringify(data) + `
|
|
9805
|
-
`);
|
|
9806
|
-
}
|
|
9807
|
-
async read() {
|
|
9808
|
-
if (this.messageQueue.length > 0) {
|
|
9809
|
-
return this.messageQueue.shift();
|
|
9810
|
-
}
|
|
9811
|
-
if (this.closed) {
|
|
9812
|
-
sdkLog("read", `returning null (closed), total wireMessages=${this.wireMessageCount}`);
|
|
9813
|
-
return null;
|
|
9814
|
-
}
|
|
9815
|
-
sdkLog("read", `waiting for next message (resolvers=${this.messageResolvers.length + 1}, queue=${this.messageQueue.length})`);
|
|
9816
|
-
return new Promise((resolve) => {
|
|
9817
|
-
this.messageResolvers.push(resolve);
|
|
9818
|
-
});
|
|
9819
|
-
}
|
|
9820
|
-
async* messages() {
|
|
9821
|
-
while (true) {
|
|
9822
|
-
const msg = await this.read();
|
|
9823
|
-
if (msg === null) {
|
|
9824
|
-
sdkLog("messages", `iterator ending (closed=${this.closed}, wireMessages=${this.wireMessageCount})`);
|
|
9825
|
-
break;
|
|
9826
|
-
}
|
|
9827
|
-
yield msg;
|
|
9828
|
-
}
|
|
9829
|
-
}
|
|
9830
|
-
close() {
|
|
9831
|
-
sdkLog("close", `explicit close called (wireMessages=${this.wireMessageCount}, pendingResolvers=${this.messageResolvers.length}, pid=${this.process?.pid})`);
|
|
9832
|
-
if (this.process) {
|
|
9833
|
-
this.process.stdin?.end();
|
|
9834
|
-
this.process.kill();
|
|
9835
|
-
this.process = null;
|
|
9836
|
-
}
|
|
9837
|
-
this.closed = true;
|
|
9838
|
-
for (const resolve of this.messageResolvers) {
|
|
9839
|
-
resolve(null);
|
|
9840
|
-
}
|
|
9841
|
-
this.messageResolvers = [];
|
|
9842
|
-
}
|
|
9843
|
-
get isClosed() {
|
|
9844
|
-
return this.closed;
|
|
9845
|
-
}
|
|
9846
|
-
getStderr() {
|
|
9847
|
-
return this.stderrLines.join(`
|
|
9848
|
-
`);
|
|
9849
|
-
}
|
|
9850
|
-
handleMessage(msg) {
|
|
9851
|
-
this.wireMessageCount++;
|
|
9852
|
-
this.lastMessageAt = Date.now();
|
|
9853
|
-
const wirePayload = msg;
|
|
9854
|
-
const msgType = wirePayload.message_type || wirePayload.subtype || "";
|
|
9855
|
-
sdkLog("wire", `#${this.wireMessageCount} type=${msg.type} ${msgType ? `msg_type=${msgType}` : ""} resolvers=${this.messageResolvers.length} queue=${this.messageQueue.length}`);
|
|
9856
|
-
if (msg.type === "result") {
|
|
9857
|
-
const result = wirePayload;
|
|
9858
|
-
sdkLog("wire", `RESULT: subtype=${result.subtype} stop_reason=${result.stop_reason || "N/A"} duration=${result.duration_ms}ms resultLen=${result.result?.length || 0}`);
|
|
9859
|
-
}
|
|
9860
|
-
if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
|
|
9861
|
-
this.agentId = msg.agent_id;
|
|
9862
|
-
sdkLog("wire", `INIT: agent_id=${this.agentId}`);
|
|
9863
|
-
}
|
|
9864
|
-
if (msg.type === "control_request") {
|
|
9865
|
-
const req = wirePayload;
|
|
9866
|
-
sdkLog("wire", `CONTROL_REQUEST: id=${req.request_id} subtype=${req.request?.subtype} tool=${req.request?.tool_name || "N/A"}`);
|
|
9867
|
-
}
|
|
9868
|
-
if (this.messageResolvers.length > 0) {
|
|
9869
|
-
const resolve = this.messageResolvers.shift();
|
|
9870
|
-
resolve(msg);
|
|
9871
|
-
} else {
|
|
9872
|
-
this.messageQueue.push(msg);
|
|
9873
|
-
}
|
|
9874
|
-
}
|
|
9875
|
-
buildArgs() {
|
|
9876
|
-
return buildCliArgs(this.options);
|
|
9877
|
-
}
|
|
9878
|
-
async findCli() {
|
|
9879
|
-
const { existsSync: existsSync2 } = await import("node:fs");
|
|
9880
|
-
const { dirname: dirname2, join: join2 } = await import("node:path");
|
|
9881
|
-
const { fileURLToPath: fileURLToPath2 } = await import("node:url");
|
|
9882
|
-
if (process.env.LETTA_CLI_PATH && existsSync2(process.env.LETTA_CLI_PATH)) {
|
|
9883
|
-
return process.env.LETTA_CLI_PATH;
|
|
9884
|
-
}
|
|
9885
|
-
try {
|
|
9886
|
-
const { createRequire: createRequire3 } = await import("node:module");
|
|
9887
|
-
const require3 = createRequire3(import.meta.url);
|
|
9888
|
-
const resolved = require3.resolve("@letta-ai/letta-code");
|
|
9889
|
-
if (existsSync2(resolved)) {
|
|
9890
|
-
return resolved;
|
|
9891
|
-
}
|
|
9892
|
-
} catch {}
|
|
9893
|
-
const __filename3 = fileURLToPath2(import.meta.url);
|
|
9894
|
-
const __dirname3 = dirname2(__filename3);
|
|
9895
|
-
const localPaths = [
|
|
9896
|
-
join2(__dirname3, "../../@letta-ai/letta-code/letta.js"),
|
|
9897
|
-
join2(__dirname3, "../../../letta-code-prod/letta.js"),
|
|
9898
|
-
join2(__dirname3, "../../../letta-code/letta.js")
|
|
9899
|
-
];
|
|
9900
|
-
for (const p of localPaths) {
|
|
9901
|
-
if (existsSync2(p)) {
|
|
9902
|
-
return p;
|
|
9903
|
-
}
|
|
9904
|
-
}
|
|
9905
|
-
throw new Error("Letta Code CLI not found. Set LETTA_CLI_PATH or install @letta-ai/letta-code.");
|
|
9906
|
-
}
|
|
9907
|
-
}
|
|
9908
|
-
|
|
9909
|
-
// src/session.ts
|
|
9910
|
-
function sessionLog(tag, ...args) {
|
|
9911
|
-
if (process.env.DEBUG_SDK)
|
|
9912
|
-
console.error(`[SDK-Session] [${tag}]`, ...args);
|
|
9913
|
-
}
|
|
9914
|
-
var MAX_BUFFERED_STREAM_MESSAGES = 100;
|
|
9915
|
-
var DEFAULT_MAX_APPROVAL_RECOVERY_ATTEMPTS = 1;
|
|
9916
|
-
var DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS = 5000;
|
|
9917
|
-
var KNOWN_SDK_ERROR_CODES2 = new Set([
|
|
9918
|
-
"approval_conflict",
|
|
9919
|
-
"approval_conflict_terminal",
|
|
9920
|
-
"protocol_error",
|
|
9921
|
-
"error",
|
|
9922
|
-
"llm_api_error",
|
|
9923
|
-
"max_steps",
|
|
9924
|
-
"interrupted",
|
|
9925
|
-
"stream_closed"
|
|
9926
|
-
]);
|
|
9927
|
-
function isKnownSdkErrorCode(value) {
|
|
9928
|
-
return KNOWN_SDK_ERROR_CODES2.has(value);
|
|
9929
|
-
}
|
|
9930
|
-
function toSdkErrorCode2(value) {
|
|
9931
|
-
if (!value || value.length === 0)
|
|
9932
|
-
return;
|
|
9933
|
-
return isKnownSdkErrorCode(value) ? value : undefined;
|
|
9934
|
-
}
|
|
9935
|
-
function isUnknownControlSubtypeError(errorMessage, subtype) {
|
|
9936
|
-
if (!errorMessage)
|
|
9937
|
-
return false;
|
|
9938
|
-
return errorMessage.toLowerCase().includes(`unknown control request subtype: ${subtype}`.toLowerCase());
|
|
9939
|
-
}
|
|
9940
|
-
function extractApiErrorDetail(apiError) {
|
|
9941
|
-
const detail = apiError?.detail;
|
|
9942
|
-
if (typeof detail !== "string")
|
|
9943
|
-
return;
|
|
9944
|
-
const trimmed = detail.trim();
|
|
9945
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
9946
|
-
}
|
|
9947
|
-
function isApprovalConflictSignal2(params) {
|
|
9948
|
-
if (params.stopReason === "requires_approval")
|
|
9949
|
-
return true;
|
|
9950
|
-
const haystack = [params.detail, params.message].filter((value) => typeof value === "string" && value.length > 0).join(`
|
|
9951
|
-
`).toLowerCase();
|
|
9952
|
-
if (!haystack)
|
|
9953
|
-
return false;
|
|
9954
|
-
return haystack.includes("waiting for approval on a tool call") || haystack.includes("cannot send a new message") || haystack.includes("requires_approval");
|
|
9955
|
-
}
|
|
9956
|
-
|
|
9957
|
-
class Session {
|
|
9958
|
-
options;
|
|
9959
|
-
transport;
|
|
9960
|
-
_agentId = null;
|
|
9961
|
-
_sessionId = null;
|
|
9962
|
-
_conversationId = null;
|
|
9963
|
-
initialized = false;
|
|
9964
|
-
closed = false;
|
|
9965
|
-
initializePromise = null;
|
|
9966
|
-
externalTools = new Map;
|
|
9967
|
-
streamQueue = [];
|
|
9968
|
-
streamResolvers = [];
|
|
9969
|
-
pumpPromise = null;
|
|
9970
|
-
pumpClosed = false;
|
|
9971
|
-
droppedStreamMessages = 0;
|
|
9972
|
-
sendGeneration = 0;
|
|
9973
|
-
lastCompletedRunIds = new Set;
|
|
9974
|
-
controlResponseWaiters = new Map;
|
|
9975
|
-
constructor(options = {}) {
|
|
9976
|
-
this.options = options;
|
|
9977
|
-
if (options.reasoningEffort !== undefined) {
|
|
9978
|
-
throw new Error("reasoningEffort is not supported by this session. Create a Cloud, Remote, or local agent session before setting reasoning effort.");
|
|
9979
|
-
}
|
|
9980
|
-
this.transport = new SubprocessTransport(options);
|
|
9981
|
-
if (options.tools) {
|
|
9982
|
-
for (const tool of options.tools) {
|
|
9983
|
-
this.externalTools.set(tool.name, tool);
|
|
9984
|
-
}
|
|
9985
|
-
}
|
|
9986
|
-
}
|
|
9987
|
-
async initialize() {
|
|
9988
|
-
if (this.closed) {
|
|
9989
|
-
throw new Error("Session is closed");
|
|
9990
|
-
}
|
|
9991
|
-
if (this.initializePromise) {
|
|
9992
|
-
return this.initializePromise;
|
|
9993
|
-
}
|
|
9994
|
-
if (this.initialized) {
|
|
9995
|
-
throw new Error("Session already initialized");
|
|
9996
|
-
}
|
|
9997
|
-
const attempt = this.performInitialize();
|
|
9998
|
-
const memo = attempt.catch((error) => {
|
|
9999
|
-
this.cleanupFailedInitialize();
|
|
10000
|
-
throw error;
|
|
10001
|
-
}).finally(() => {
|
|
10002
|
-
if (this.initializePromise === memo) {
|
|
10003
|
-
this.initializePromise = null;
|
|
10004
|
-
}
|
|
10005
|
-
});
|
|
10006
|
-
this.initializePromise = memo;
|
|
10007
|
-
return memo;
|
|
10008
|
-
}
|
|
10009
|
-
async performInitialize() {
|
|
10010
|
-
sessionLog("init", "connecting transport...");
|
|
10011
|
-
await this.transport.connect();
|
|
10012
|
-
sessionLog("init", "transport connected, sending initialize request");
|
|
10013
|
-
await this.transport.write({
|
|
10014
|
-
type: "control_request",
|
|
10015
|
-
request_id: "init_1",
|
|
10016
|
-
request: { subtype: "initialize" }
|
|
10017
|
-
});
|
|
10018
|
-
sessionLog("init", "waiting for init message from CLI...");
|
|
10019
|
-
for await (const msg of this.transport.messages()) {
|
|
10020
|
-
sessionLog("init", `received wire message: type=${msg.type}`);
|
|
10021
|
-
if (msg.type === "control_request") {
|
|
10022
|
-
const handled = await this.handleControlRequest(msg);
|
|
10023
|
-
if (!handled) {
|
|
10024
|
-
const wireMsgAny = msg;
|
|
10025
|
-
sessionLog("init", `DROPPED unsupported control_request: subtype=${wireMsgAny.request?.subtype || "N/A"}`);
|
|
10026
|
-
}
|
|
10027
|
-
continue;
|
|
10028
|
-
}
|
|
10029
|
-
if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
|
|
10030
|
-
const initMsg = msg;
|
|
10031
|
-
this._agentId = initMsg.agent_id;
|
|
10032
|
-
this._sessionId = initMsg.session_id;
|
|
10033
|
-
this._conversationId = initMsg.conversation_id;
|
|
10034
|
-
if (this.externalTools.size > 0) {
|
|
10035
|
-
await this.registerExternalTools();
|
|
10036
|
-
}
|
|
10037
|
-
if (this.closed) {
|
|
10038
|
-
throw new Error("Session is closed");
|
|
10039
|
-
}
|
|
10040
|
-
this.initialized = true;
|
|
10041
|
-
this.startBackgroundPump();
|
|
10042
|
-
const allTools = [
|
|
10043
|
-
...initMsg.tools,
|
|
10044
|
-
...Array.from(this.externalTools.keys())
|
|
10045
|
-
];
|
|
10046
|
-
sessionLog("init", `initialized: agent=${initMsg.agent_id} conversation=${initMsg.conversation_id} model=${initMsg.model} tools=${allTools.length} (${this.externalTools.size} external)`);
|
|
10047
|
-
return {
|
|
10048
|
-
type: "init",
|
|
10049
|
-
agentId: initMsg.agent_id,
|
|
10050
|
-
sessionId: initMsg.session_id,
|
|
10051
|
-
conversationId: initMsg.conversation_id,
|
|
10052
|
-
model: initMsg.model,
|
|
10053
|
-
tools: allTools,
|
|
10054
|
-
memfsEnabled: initMsg.memfs_enabled,
|
|
10055
|
-
skillSources: initMsg.skill_sources,
|
|
10056
|
-
systemInfoReminderEnabled: initMsg.system_info_reminder_enabled,
|
|
10057
|
-
dreaming: initMsg.reflection_trigger && initMsg.reflection_behavior && typeof initMsg.reflection_step_count === "number" ? {
|
|
10058
|
-
trigger: initMsg.reflection_trigger,
|
|
10059
|
-
behavior: initMsg.reflection_behavior,
|
|
10060
|
-
stepCount: initMsg.reflection_step_count
|
|
10061
|
-
} : undefined
|
|
10062
|
-
};
|
|
10063
|
-
}
|
|
10064
|
-
}
|
|
10065
|
-
const stderr = this.transport.getStderr();
|
|
10066
|
-
const detail = stderr ? `
|
|
10067
|
-
CLI stderr:
|
|
10068
|
-
${stderr}` : "";
|
|
10069
|
-
sessionLog("init", `ERROR: transport closed before init message received${detail}`);
|
|
10070
|
-
throw new Error(`Failed to initialize session - no init message received${detail}`);
|
|
10071
|
-
}
|
|
10072
|
-
cleanupFailedInitialize() {
|
|
10073
|
-
this.transport.close();
|
|
10074
|
-
this.closed = true;
|
|
10075
|
-
this.initialized = false;
|
|
10076
|
-
this._agentId = null;
|
|
10077
|
-
this._sessionId = null;
|
|
10078
|
-
this._conversationId = null;
|
|
10079
|
-
this.pumpClosed = true;
|
|
10080
|
-
this.resolveAllStreamWaiters(null);
|
|
10081
|
-
}
|
|
10082
|
-
async send(message) {
|
|
10083
|
-
if (!this.initialized) {
|
|
10084
|
-
sessionLog("send", "auto-initializing (not yet initialized)");
|
|
10085
|
-
await this.initialize();
|
|
10086
|
-
}
|
|
10087
|
-
const preview = typeof message === "string" ? message.slice(0, 100) : Array.isArray(message) ? `[multimodal: ${message.length} parts]` : String(message).slice(0, 100);
|
|
10088
|
-
sessionLog("send", `sending message: ${preview}${typeof message === "string" && message.length > 100 ? "..." : ""}`);
|
|
10089
|
-
if (this.streamQueue.length > 0) {
|
|
10090
|
-
sessionLog("send", `clearing ${this.streamQueue.length} stale messages from previous turn`);
|
|
10091
|
-
this.streamQueue.length = 0;
|
|
10092
|
-
}
|
|
10093
|
-
await this.transport.write({
|
|
10094
|
-
type: "user",
|
|
10095
|
-
message: { role: "user", content: message }
|
|
10096
|
-
});
|
|
10097
|
-
this.sendGeneration++;
|
|
10098
|
-
sessionLog("send", `message written to transport (generation=${this.sendGeneration})`);
|
|
10099
|
-
}
|
|
10100
|
-
async runTurn(message, options = {}) {
|
|
10101
|
-
const maxApprovalRecoveryAttempts = this.resolveMaxApprovalRecoveryAttempts(options);
|
|
10102
|
-
const recoveryTimeoutMs = this.resolveApprovalRecoveryTimeoutMs(options);
|
|
10103
|
-
let recoveryAttempts = 0;
|
|
10104
|
-
let result = await this.runSingleTurn(message);
|
|
10105
|
-
while (!result.success && result.approvalConflict === true && recoveryAttempts < maxApprovalRecoveryAttempts) {
|
|
10106
|
-
recoveryAttempts += 1;
|
|
10107
|
-
const recovery = await this.recoverPendingApprovals({
|
|
10108
|
-
timeoutMs: recoveryTimeoutMs
|
|
10109
|
-
});
|
|
10110
|
-
if (!recovery.recovered) {
|
|
10111
|
-
return this.toTerminalApprovalConflictResult(result, {
|
|
10112
|
-
recoveryAttempts,
|
|
10113
|
-
detail: recovery.detail
|
|
10114
|
-
});
|
|
10115
|
-
}
|
|
10116
|
-
result = await this.runSingleTurn(message);
|
|
10117
|
-
}
|
|
10118
|
-
if (!result.success && result.approvalConflict === true) {
|
|
10119
|
-
return this.toTerminalApprovalConflictResult(result, { recoveryAttempts });
|
|
10120
|
-
}
|
|
10121
|
-
if (recoveryAttempts > 0) {
|
|
10122
|
-
result.recoveryAttempts = recoveryAttempts;
|
|
10123
|
-
}
|
|
10124
|
-
return result;
|
|
10125
|
-
}
|
|
10126
|
-
async recoverPendingApprovals(options = {}) {
|
|
10127
|
-
if (!this.initialized) {
|
|
10128
|
-
await this.initialize();
|
|
10129
|
-
}
|
|
10130
|
-
const timeoutMs = options.timeoutMs ?? this.options.approvalRecoveryTimeoutMs ?? DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS;
|
|
10131
|
-
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
10132
|
-
throw new Error("Invalid approval recovery timeout. Expected a positive integer.");
|
|
10133
|
-
}
|
|
10134
|
-
const requestId = `recover-approvals-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
10135
|
-
let resp;
|
|
10136
|
-
try {
|
|
10137
|
-
resp = await this.requestControlResponse(requestId, {
|
|
10138
|
-
subtype: "recover_pending_approvals",
|
|
10139
|
-
...this._agentId ? { agent_id: this._agentId } : {},
|
|
10140
|
-
...this._conversationId ? { conversation_id: this._conversationId } : {}
|
|
10141
|
-
}, { timeoutMs });
|
|
10142
|
-
} catch (error) {
|
|
10143
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
10144
|
-
return {
|
|
10145
|
-
recovered: false,
|
|
10146
|
-
unsupported: false,
|
|
10147
|
-
detail
|
|
10148
|
-
};
|
|
10149
|
-
}
|
|
10150
|
-
if (resp.subtype === "error") {
|
|
10151
|
-
const detail = resp.error ?? "recover_pending_approvals failed";
|
|
10152
|
-
return {
|
|
10153
|
-
recovered: false,
|
|
10154
|
-
pendingApproval: true,
|
|
10155
|
-
unsupported: isUnknownControlSubtypeError(detail, "recover_pending_approvals"),
|
|
10156
|
-
detail
|
|
10157
|
-
};
|
|
10158
|
-
}
|
|
10159
|
-
return {
|
|
10160
|
-
recovered: true,
|
|
10161
|
-
pendingApproval: false,
|
|
10162
|
-
unsupported: false
|
|
10163
|
-
};
|
|
10164
|
-
}
|
|
10165
|
-
resolveMaxApprovalRecoveryAttempts(options) {
|
|
10166
|
-
const value = options.maxApprovalRecoveryAttempts ?? this.options.maxApprovalRecoveryAttempts ?? DEFAULT_MAX_APPROVAL_RECOVERY_ATTEMPTS;
|
|
10167
|
-
if (!Number.isInteger(value) || value < 0) {
|
|
10168
|
-
throw new Error("Invalid maxApprovalRecoveryAttempts. Expected a non-negative integer.");
|
|
10169
|
-
}
|
|
10170
|
-
return value;
|
|
10171
|
-
}
|
|
10172
|
-
resolveApprovalRecoveryTimeoutMs(options) {
|
|
10173
|
-
const value = options.recoveryTimeoutMs ?? this.options.approvalRecoveryTimeoutMs ?? DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS;
|
|
10174
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
10175
|
-
throw new Error("Invalid approval recovery timeout. Expected a positive integer.");
|
|
10176
|
-
}
|
|
10177
|
-
return value;
|
|
10178
|
-
}
|
|
10179
|
-
toTerminalApprovalConflictResult(result, options) {
|
|
10180
|
-
return {
|
|
10181
|
-
...result,
|
|
10182
|
-
success: false,
|
|
10183
|
-
error: "approval_conflict_terminal",
|
|
10184
|
-
errorCode: "approval_conflict_terminal",
|
|
10185
|
-
approvalConflict: true,
|
|
10186
|
-
recoverable: false,
|
|
10187
|
-
recoveryAttempts: options.recoveryAttempts,
|
|
10188
|
-
errorDetail: options.detail || result.errorDetail || "Approval conflict remained unresolved after bounded SDK recovery."
|
|
10189
|
-
};
|
|
10190
|
-
}
|
|
10191
|
-
async runSingleTurn(message) {
|
|
10192
|
-
await this.send(message);
|
|
10193
|
-
let latestApprovalConflictDetail;
|
|
10194
|
-
let latestNonConflictErrorDetail;
|
|
10195
|
-
for await (const msg of this.stream()) {
|
|
10196
|
-
if (msg.type === "error") {
|
|
10197
|
-
const detail = msg.errorDetail || msg.message;
|
|
10198
|
-
if (msg.approvalConflict) {
|
|
10199
|
-
latestApprovalConflictDetail = detail;
|
|
10200
|
-
} else {
|
|
10201
|
-
latestNonConflictErrorDetail = detail;
|
|
10202
|
-
}
|
|
10203
|
-
continue;
|
|
10204
|
-
}
|
|
10205
|
-
if (msg.type === "result") {
|
|
10206
|
-
if (!msg.success && !msg.errorDetail) {
|
|
10207
|
-
if (msg.approvalConflict && latestApprovalConflictDetail) {
|
|
10208
|
-
return {
|
|
10209
|
-
...msg,
|
|
10210
|
-
errorDetail: latestApprovalConflictDetail
|
|
10211
|
-
};
|
|
10212
|
-
}
|
|
10213
|
-
if (!msg.approvalConflict && latestNonConflictErrorDetail) {
|
|
10214
|
-
return {
|
|
10215
|
-
...msg,
|
|
10216
|
-
errorDetail: latestNonConflictErrorDetail
|
|
10217
|
-
};
|
|
10218
|
-
}
|
|
10219
|
-
}
|
|
10220
|
-
return msg;
|
|
10221
|
-
}
|
|
10222
|
-
}
|
|
10223
|
-
return {
|
|
10224
|
-
type: "result",
|
|
10225
|
-
success: false,
|
|
10226
|
-
error: "stream_closed",
|
|
10227
|
-
errorCode: "stream_closed",
|
|
10228
|
-
recoverable: false,
|
|
10229
|
-
errorDetail: "Stream ended before terminal result",
|
|
10230
|
-
durationMs: 0,
|
|
10231
|
-
conversationId: this._conversationId
|
|
10232
|
-
};
|
|
10233
|
-
}
|
|
10234
|
-
async* stream() {
|
|
10235
|
-
const streamStart = Date.now();
|
|
10236
|
-
const minGeneration = this.sendGeneration;
|
|
10237
|
-
let yieldCount = 0;
|
|
10238
|
-
let staleCount = 0;
|
|
10239
|
-
let staleRunIdCount = 0;
|
|
10240
|
-
let gotResult = false;
|
|
10241
|
-
const currentStreamRunIds = new Set;
|
|
10242
|
-
const staleRunIds = new Set(this.lastCompletedRunIds);
|
|
10243
|
-
const approvalConflictDetailsByRunId = new Map;
|
|
10244
|
-
let latestApprovalConflictDetail;
|
|
10245
|
-
this.startBackgroundPump();
|
|
10246
|
-
sessionLog("stream", `starting stream (agent=${this._agentId}, conversation=${this._conversationId}, generation=${minGeneration})`);
|
|
10247
|
-
while (true) {
|
|
10248
|
-
const bufferedMsg = await this.nextBufferedMessage();
|
|
10249
|
-
if (!bufferedMsg) {
|
|
10250
|
-
break;
|
|
10251
|
-
}
|
|
10252
|
-
if (bufferedMsg.generation < minGeneration) {
|
|
10253
|
-
staleCount++;
|
|
10254
|
-
sessionLog("stream", `discarding stale message: type=${bufferedMsg.message.type} generation=${bufferedMsg.generation} (current=${minGeneration})`);
|
|
10255
|
-
continue;
|
|
10256
|
-
}
|
|
10257
|
-
if (bufferedMsg.runId && staleRunIds.has(bufferedMsg.runId)) {
|
|
10258
|
-
staleRunIdCount++;
|
|
10259
|
-
sessionLog("stream", `discarding stale message: type=${bufferedMsg.message.type} runId=${bufferedMsg.runId}`);
|
|
10260
|
-
continue;
|
|
10261
|
-
}
|
|
10262
|
-
if (bufferedMsg.runId) {
|
|
10263
|
-
currentStreamRunIds.add(bufferedMsg.runId);
|
|
10264
|
-
}
|
|
10265
|
-
const sdkMsg = bufferedMsg.message;
|
|
10266
|
-
if (sdkMsg.type === "error" && sdkMsg.approvalConflict) {
|
|
10267
|
-
const detail = sdkMsg.errorDetail || sdkMsg.message;
|
|
10268
|
-
if (detail) {
|
|
10269
|
-
latestApprovalConflictDetail = detail;
|
|
10270
|
-
if (sdkMsg.runId) {
|
|
10271
|
-
approvalConflictDetailsByRunId.set(sdkMsg.runId, detail);
|
|
10272
|
-
}
|
|
10273
|
-
}
|
|
10274
|
-
}
|
|
10275
|
-
let normalizedMsg = sdkMsg;
|
|
10276
|
-
if (sdkMsg.type === "result" && !sdkMsg.success) {
|
|
10277
|
-
let detail = sdkMsg.errorDetail;
|
|
10278
|
-
if (!detail && Array.isArray(sdkMsg.runIds)) {
|
|
10279
|
-
for (const runId of sdkMsg.runIds) {
|
|
10280
|
-
const fromRun = approvalConflictDetailsByRunId.get(runId);
|
|
10281
|
-
if (fromRun) {
|
|
10282
|
-
detail = fromRun;
|
|
10283
|
-
break;
|
|
10284
|
-
}
|
|
10285
|
-
}
|
|
10286
|
-
}
|
|
10287
|
-
if (!detail) {
|
|
10288
|
-
detail = latestApprovalConflictDetail;
|
|
10289
|
-
}
|
|
10290
|
-
const approvalConflict = sdkMsg.approvalConflict === true || isApprovalConflictSignal2({
|
|
10291
|
-
detail,
|
|
10292
|
-
message: sdkMsg.errorCode || sdkMsg.error,
|
|
10293
|
-
stopReason: sdkMsg.stopReason
|
|
10294
|
-
});
|
|
10295
|
-
if (approvalConflict) {
|
|
10296
|
-
const normalizedErrorCode = !sdkMsg.errorCode || sdkMsg.errorCode === "error" ? "approval_conflict" : sdkMsg.errorCode;
|
|
10297
|
-
const normalizedError = !sdkMsg.error || sdkMsg.error === "error" ? normalizedErrorCode : sdkMsg.error;
|
|
10298
|
-
normalizedMsg = {
|
|
10299
|
-
...sdkMsg,
|
|
10300
|
-
approvalConflict: true,
|
|
10301
|
-
recoverable: sdkMsg.recoverable ?? true,
|
|
10302
|
-
...detail ? { errorDetail: detail } : {},
|
|
10303
|
-
errorCode: normalizedErrorCode,
|
|
10304
|
-
error: normalizedError
|
|
10305
|
-
};
|
|
10306
|
-
} else if (!sdkMsg.errorCode && sdkMsg.error) {
|
|
10307
|
-
const errorCode = toSdkErrorCode2(sdkMsg.error);
|
|
10308
|
-
if (errorCode) {
|
|
10309
|
-
normalizedMsg = {
|
|
10310
|
-
...sdkMsg,
|
|
10311
|
-
errorCode
|
|
10312
|
-
};
|
|
10313
|
-
}
|
|
10314
|
-
}
|
|
10315
|
-
}
|
|
10316
|
-
yieldCount++;
|
|
10317
|
-
sessionLog("stream", `yield #${yieldCount}: type=${normalizedMsg.type}${normalizedMsg.type === "result" ? ` success=${normalizedMsg.success} error=${normalizedMsg.error || "none"}` : ""}`);
|
|
10318
|
-
yield normalizedMsg;
|
|
10319
|
-
if (normalizedMsg.type === "result") {
|
|
10320
|
-
gotResult = true;
|
|
10321
|
-
this.updateCompletedRunIds(normalizedMsg.runIds, currentStreamRunIds);
|
|
10322
|
-
break;
|
|
10323
|
-
}
|
|
10324
|
-
}
|
|
10325
|
-
const elapsed = Date.now() - streamStart;
|
|
10326
|
-
sessionLog("stream", `stream ended: duration=${elapsed}ms yielded=${yieldCount} staleFiltered=${staleCount} staleRunIdFiltered=${staleRunIdCount} dropped=${this.droppedStreamMessages} gotResult=${gotResult}`);
|
|
10327
|
-
if (!gotResult) {
|
|
10328
|
-
sessionLog("stream", "WARNING: stream ended WITHOUT a result message -- transport may have closed unexpectedly");
|
|
10329
|
-
}
|
|
10330
|
-
}
|
|
10331
|
-
startBackgroundPump() {
|
|
10332
|
-
if (this.pumpPromise) {
|
|
10333
|
-
return;
|
|
10334
|
-
}
|
|
10335
|
-
this.pumpClosed = false;
|
|
10336
|
-
this.pumpPromise = this.runBackgroundPump().catch((err) => {
|
|
10337
|
-
sessionLog("pump", `ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
|
10338
|
-
}).finally(() => {
|
|
10339
|
-
this.pumpClosed = true;
|
|
10340
|
-
this.resolveAllStreamWaiters(null);
|
|
10341
|
-
});
|
|
10342
|
-
}
|
|
10343
|
-
async runBackgroundPump() {
|
|
10344
|
-
sessionLog("pump", "background pump started");
|
|
10345
|
-
const indexToToolCallId = new Map;
|
|
10346
|
-
for await (const wireMsg of this.transport.messages()) {
|
|
10347
|
-
const wireMsgAny = wireMsg;
|
|
10348
|
-
if (wireMsg.type === "control_request") {
|
|
10349
|
-
const handled = await this.handleControlRequest(wireMsg);
|
|
10350
|
-
if (!handled) {
|
|
10351
|
-
sessionLog("pump", `DROPPED unsupported control_request: subtype=${wireMsgAny.request?.subtype || "N/A"}`);
|
|
10352
|
-
}
|
|
10353
|
-
continue;
|
|
10354
|
-
}
|
|
10355
|
-
if (wireMsg.type === "control_response") {
|
|
10356
|
-
const respMsg = wireMsg;
|
|
10357
|
-
const requestId = respMsg.response?.request_id;
|
|
10358
|
-
if (requestId && this.controlResponseWaiters.has(requestId)) {
|
|
10359
|
-
const resolve = this.controlResponseWaiters.get(requestId);
|
|
10360
|
-
this.controlResponseWaiters.delete(requestId);
|
|
10361
|
-
resolve(respMsg.response);
|
|
10362
|
-
} else {
|
|
10363
|
-
sessionLog("pump", `DROPPED unmatched control_response: request_id=${requestId ?? "N/A"}`);
|
|
10364
|
-
}
|
|
10365
|
-
continue;
|
|
10366
|
-
}
|
|
10367
|
-
const messageType = wireMsgAny.message_type;
|
|
10368
|
-
if (wireMsg.type === "message" && (messageType === "tool_call_message" || messageType === "approval_request_message")) {
|
|
10369
|
-
const toolCalls = wireMsgAny.tool_calls;
|
|
10370
|
-
const toolCall = wireMsgAny.tool_call;
|
|
10371
|
-
const tc = toolCalls?.[0] || toolCall;
|
|
10372
|
-
if (tc) {
|
|
10373
|
-
const fnObj = tc.function;
|
|
10374
|
-
const tcId = tc.tool_call_id ?? tc.id;
|
|
10375
|
-
const tcIndex = tc.index;
|
|
10376
|
-
if (tcId && tcIndex !== undefined) {
|
|
10377
|
-
indexToToolCallId.set(tcIndex, tcId);
|
|
10378
|
-
} else if (!tcId && tcIndex !== undefined) {
|
|
10379
|
-
const resolvedId = indexToToolCallId.get(tcIndex);
|
|
10380
|
-
if (resolvedId) {
|
|
10381
|
-
if (fnObj) {
|
|
10382
|
-
tc.id = resolvedId;
|
|
10383
|
-
} else {
|
|
10384
|
-
tc.tool_call_id = resolvedId;
|
|
10385
|
-
}
|
|
10386
|
-
}
|
|
10387
|
-
}
|
|
10388
|
-
}
|
|
10389
|
-
}
|
|
10390
|
-
const sdkMsg = this.transformMessage(wireMsg);
|
|
10391
|
-
if (sdkMsg) {
|
|
10392
|
-
this.enqueueStreamMessage(sdkMsg);
|
|
10393
|
-
} else {
|
|
10394
|
-
sessionLog("pump", `DROPPED wire message: type=${wireMsg.type} message_type=${wireMsgAny.message_type || "N/A"} subtype=${wireMsgAny.subtype || "N/A"}`);
|
|
10395
|
-
}
|
|
10396
|
-
}
|
|
10397
|
-
sessionLog("pump", "background pump ended");
|
|
10398
|
-
}
|
|
10399
|
-
async handleControlRequest(controlReq) {
|
|
10400
|
-
const subtype = controlReq.request.subtype;
|
|
10401
|
-
sessionLog("pump", `control_request: subtype=${subtype} tool=${controlReq.request.tool_name || "N/A"}`);
|
|
10402
|
-
if (subtype === "can_use_tool") {
|
|
10403
|
-
await this.handleCanUseTool(controlReq.request_id, controlReq.request);
|
|
10404
|
-
return true;
|
|
10405
|
-
}
|
|
10406
|
-
if (subtype === "execute_external_tool") {
|
|
10407
|
-
const rawReq = controlReq.request;
|
|
10408
|
-
await this.handleExecuteExternalTool(controlReq.request_id, {
|
|
10409
|
-
subtype: "execute_external_tool",
|
|
10410
|
-
tool_call_id: rawReq.tool_call_id,
|
|
10411
|
-
tool_name: rawReq.tool_name,
|
|
10412
|
-
input: rawReq.input
|
|
10413
|
-
});
|
|
10414
|
-
return true;
|
|
10415
|
-
}
|
|
10416
|
-
return false;
|
|
10417
|
-
}
|
|
10418
|
-
enqueueStreamMessage(msg) {
|
|
10419
|
-
const bufferedMsg = {
|
|
10420
|
-
message: msg,
|
|
10421
|
-
generation: this.sendGeneration,
|
|
10422
|
-
runId: this.getMessageRunId(msg)
|
|
10423
|
-
};
|
|
10424
|
-
if (this.streamResolvers.length > 0) {
|
|
10425
|
-
const resolve = this.streamResolvers.shift();
|
|
10426
|
-
resolve(bufferedMsg);
|
|
10427
|
-
return;
|
|
10428
|
-
}
|
|
10429
|
-
if (this.streamQueue.length >= MAX_BUFFERED_STREAM_MESSAGES) {
|
|
10430
|
-
this.streamQueue.shift();
|
|
10431
|
-
this.droppedStreamMessages++;
|
|
10432
|
-
sessionLog("pump", `stream queue overflow: dropped oldest message (total_dropped=${this.droppedStreamMessages}, max=${MAX_BUFFERED_STREAM_MESSAGES})`);
|
|
10433
|
-
}
|
|
10434
|
-
this.streamQueue.push(bufferedMsg);
|
|
10435
|
-
}
|
|
10436
|
-
async nextBufferedMessage() {
|
|
10437
|
-
if (this.streamQueue.length > 0) {
|
|
10438
|
-
return this.streamQueue.shift();
|
|
10439
|
-
}
|
|
10440
|
-
if (this.pumpClosed) {
|
|
10441
|
-
return null;
|
|
10442
|
-
}
|
|
10443
|
-
return new Promise((resolve) => {
|
|
10444
|
-
this.streamResolvers.push(resolve);
|
|
10445
|
-
});
|
|
10446
|
-
}
|
|
10447
|
-
resolveAllStreamWaiters(msg) {
|
|
10448
|
-
for (const resolve of this.streamResolvers) {
|
|
10449
|
-
resolve(msg);
|
|
10450
|
-
}
|
|
10451
|
-
this.streamResolvers = [];
|
|
10452
|
-
for (const resolve of this.controlResponseWaiters.values()) {
|
|
10453
|
-
resolve({ subtype: "error", error: "session closed" });
|
|
10454
|
-
}
|
|
10455
|
-
this.controlResponseWaiters.clear();
|
|
10456
|
-
}
|
|
10457
|
-
getMessageRunId(msg) {
|
|
10458
|
-
switch (msg.type) {
|
|
10459
|
-
case "assistant":
|
|
10460
|
-
case "tool_call":
|
|
10461
|
-
case "tool_result":
|
|
10462
|
-
case "reasoning":
|
|
10463
|
-
case "error":
|
|
10464
|
-
case "retry":
|
|
10465
|
-
return msg.runId;
|
|
10466
|
-
default:
|
|
10467
|
-
return;
|
|
10468
|
-
}
|
|
10469
|
-
}
|
|
10470
|
-
updateCompletedRunIds(resultRunIds, streamedRunIds) {
|
|
10471
|
-
const nextRunIds = new Set;
|
|
10472
|
-
if (Array.isArray(resultRunIds)) {
|
|
10473
|
-
for (const runId of resultRunIds) {
|
|
10474
|
-
if (runId) {
|
|
10475
|
-
nextRunIds.add(runId);
|
|
10476
|
-
}
|
|
10477
|
-
}
|
|
10478
|
-
}
|
|
10479
|
-
for (const runId of streamedRunIds) {
|
|
10480
|
-
if (runId) {
|
|
10481
|
-
nextRunIds.add(runId);
|
|
10482
|
-
}
|
|
10483
|
-
}
|
|
10484
|
-
this.lastCompletedRunIds = nextRunIds;
|
|
10485
|
-
}
|
|
10486
|
-
async registerExternalTools() {
|
|
10487
|
-
const toolDefs = Array.from(this.externalTools.values()).map((tool) => ({
|
|
10488
|
-
name: tool.name,
|
|
10489
|
-
label: tool.label,
|
|
10490
|
-
description: tool.description,
|
|
10491
|
-
parameters: this.schemaToJsonSchema(tool.parameters)
|
|
10492
|
-
}));
|
|
10493
|
-
sessionLog("registerTools", `registering ${toolDefs.length} external tools: ${toolDefs.map((t) => t.name).join(", ")}`);
|
|
10494
|
-
await this.transport.write({
|
|
10495
|
-
type: "control_request",
|
|
10496
|
-
request_id: `register_tools_${Date.now()}`,
|
|
10497
|
-
request: {
|
|
10498
|
-
subtype: "register_external_tools",
|
|
10499
|
-
tools: toolDefs
|
|
10500
|
-
}
|
|
10501
|
-
});
|
|
10502
|
-
}
|
|
10503
|
-
schemaToJsonSchema(schema) {
|
|
10504
|
-
if (schema && typeof schema === "object") {
|
|
10505
|
-
const s = schema;
|
|
10506
|
-
return {
|
|
10507
|
-
type: s.type,
|
|
10508
|
-
properties: s.properties,
|
|
10509
|
-
required: s.required,
|
|
10510
|
-
additionalProperties: s.additionalProperties,
|
|
10511
|
-
description: s.description
|
|
10512
|
-
};
|
|
10513
|
-
}
|
|
10514
|
-
return { type: "object" };
|
|
10515
|
-
}
|
|
10516
|
-
async handleExecuteExternalTool(requestId, req) {
|
|
10517
|
-
const tool = this.externalTools.get(req.tool_name);
|
|
10518
|
-
if (!tool) {
|
|
10519
|
-
sessionLog("executeExternalTool", `ERROR: unknown tool ${req.tool_name}`);
|
|
10520
|
-
await this.transport.write({
|
|
10521
|
-
type: "control_response",
|
|
10522
|
-
response: {
|
|
10523
|
-
subtype: "external_tool_result",
|
|
10524
|
-
request_id: requestId,
|
|
10525
|
-
tool_call_id: req.tool_call_id,
|
|
10526
|
-
content: [{ type: "text", text: `Unknown external tool: ${req.tool_name}` }],
|
|
10527
|
-
is_error: true
|
|
10528
|
-
}
|
|
10529
|
-
});
|
|
10530
|
-
return;
|
|
10531
|
-
}
|
|
10532
|
-
try {
|
|
10533
|
-
sessionLog("executeExternalTool", `executing ${req.tool_name} (call_id=${req.tool_call_id})`);
|
|
10534
|
-
const result = await tool.execute(req.tool_call_id, req.input);
|
|
10535
|
-
await this.transport.write({
|
|
10536
|
-
type: "control_response",
|
|
10537
|
-
response: {
|
|
10538
|
-
subtype: "external_tool_result",
|
|
10539
|
-
request_id: requestId,
|
|
10540
|
-
tool_call_id: req.tool_call_id,
|
|
10541
|
-
content: result.content,
|
|
10542
|
-
is_error: false
|
|
10543
|
-
}
|
|
10544
|
-
});
|
|
10545
|
-
sessionLog("executeExternalTool", `${req.tool_name} completed successfully`);
|
|
10546
|
-
} catch (err) {
|
|
10547
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
10548
|
-
sessionLog("executeExternalTool", `${req.tool_name} failed: ${errorMessage}`);
|
|
10549
|
-
await this.transport.write({
|
|
10550
|
-
type: "control_response",
|
|
10551
|
-
response: {
|
|
10552
|
-
subtype: "external_tool_result",
|
|
10553
|
-
request_id: requestId,
|
|
10554
|
-
tool_call_id: req.tool_call_id,
|
|
10555
|
-
content: [{ type: "text", text: `Tool execution error: ${errorMessage}` }],
|
|
10556
|
-
is_error: true
|
|
10557
|
-
}
|
|
10558
|
-
});
|
|
10559
|
-
}
|
|
10560
|
-
}
|
|
10561
|
-
async handleCanUseTool(requestId, req) {
|
|
10562
|
-
let response;
|
|
10563
|
-
const toolName = req.tool_name;
|
|
10564
|
-
const hasCallback = typeof this.options.canUseTool === "function";
|
|
10565
|
-
const toolNeedsRuntimeUserInput = requiresRuntimeUserInput(toolName);
|
|
10566
|
-
const autoAllowWithoutCallback = isHeadlessAutoAllowTool(toolName);
|
|
10567
|
-
sessionLog("canUseTool", `tool=${toolName} mode=${this.options.permissionMode || "standard"} requestId=${requestId}`);
|
|
10568
|
-
if (toolNeedsRuntimeUserInput && !hasCallback) {
|
|
10569
|
-
response = {
|
|
10570
|
-
behavior: "deny",
|
|
10571
|
-
message: "No canUseTool callback registered",
|
|
10572
|
-
interrupt: false
|
|
10573
|
-
};
|
|
10574
|
-
} else if (isUnrestrictedPermissionMode(this.options.permissionMode) && !toolNeedsRuntimeUserInput) {
|
|
10575
|
-
sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (unrestricted)`);
|
|
10576
|
-
response = {
|
|
10577
|
-
behavior: "allow",
|
|
10578
|
-
updatedInput: null,
|
|
10579
|
-
updatedPermissions: []
|
|
10580
|
-
};
|
|
10581
|
-
} else if (hasCallback) {
|
|
10582
|
-
try {
|
|
10583
|
-
const result = await this.options.canUseTool(toolName, req.input, buildCanUseToolContext(req, requestId));
|
|
10584
|
-
if (result.behavior === "allow") {
|
|
10585
|
-
response = {
|
|
10586
|
-
behavior: "allow",
|
|
10587
|
-
updatedInput: result.updatedInput ?? null,
|
|
10588
|
-
updatedPermissions: []
|
|
10589
|
-
};
|
|
10590
|
-
} else {
|
|
10591
|
-
response = {
|
|
10592
|
-
behavior: "deny",
|
|
10593
|
-
message: result.message ?? "Denied by canUseTool callback",
|
|
10594
|
-
interrupt: false
|
|
10595
|
-
};
|
|
10596
|
-
}
|
|
10597
|
-
} catch (err) {
|
|
10598
|
-
response = {
|
|
10599
|
-
behavior: "deny",
|
|
10600
|
-
message: err instanceof Error ? err.message : "Callback error",
|
|
10601
|
-
interrupt: false
|
|
10602
|
-
};
|
|
10603
|
-
}
|
|
10604
|
-
} else if (autoAllowWithoutCallback) {
|
|
10605
|
-
sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (default behavior)`);
|
|
10606
|
-
response = {
|
|
10607
|
-
behavior: "allow",
|
|
10608
|
-
updatedInput: null,
|
|
10609
|
-
updatedPermissions: []
|
|
10610
|
-
};
|
|
10611
|
-
} else {
|
|
10612
|
-
response = {
|
|
10613
|
-
behavior: "deny",
|
|
10614
|
-
message: "No canUseTool callback registered",
|
|
10615
|
-
interrupt: false
|
|
10616
|
-
};
|
|
10617
|
-
}
|
|
10618
|
-
const responseBehavior = "behavior" in response ? response.behavior : "unknown";
|
|
10619
|
-
sessionLog("canUseTool", `responding: requestId=${requestId} behavior=${responseBehavior}`);
|
|
10620
|
-
await this.transport.write({
|
|
10621
|
-
type: "control_response",
|
|
10622
|
-
response: {
|
|
10623
|
-
subtype: "success",
|
|
10624
|
-
request_id: requestId,
|
|
10625
|
-
response
|
|
10626
|
-
}
|
|
10627
|
-
});
|
|
10628
|
-
sessionLog("canUseTool", `response sent for ${toolName}`);
|
|
10629
|
-
}
|
|
10630
|
-
async abort() {
|
|
10631
|
-
if (!this.initialized || this.pumpClosed)
|
|
10632
|
-
return;
|
|
10633
|
-
sessionLog("abort", `aborting session (agent=${this._agentId})`);
|
|
10634
|
-
await this.transport.write({
|
|
10635
|
-
type: "control_request",
|
|
10636
|
-
request_id: `interrupt-${Date.now()}`,
|
|
10637
|
-
request: { subtype: "interrupt" }
|
|
10638
|
-
});
|
|
10639
|
-
}
|
|
10640
|
-
async requestControlResponse(requestId, request, options = {}) {
|
|
10641
|
-
const responsePromise = new Promise((resolve) => {
|
|
10642
|
-
this.controlResponseWaiters.set(requestId, resolve);
|
|
10643
|
-
});
|
|
10644
|
-
try {
|
|
10645
|
-
await this.transport.write({
|
|
10646
|
-
type: "control_request",
|
|
10647
|
-
request_id: requestId,
|
|
10648
|
-
request
|
|
10649
|
-
});
|
|
10650
|
-
} catch (error) {
|
|
10651
|
-
this.controlResponseWaiters.delete(requestId);
|
|
10652
|
-
throw error;
|
|
10653
|
-
}
|
|
10654
|
-
let timeoutHandle;
|
|
10655
|
-
try {
|
|
10656
|
-
if (typeof options.timeoutMs === "number") {
|
|
10657
|
-
return await Promise.race([
|
|
10658
|
-
responsePromise,
|
|
10659
|
-
new Promise((_, reject) => {
|
|
10660
|
-
timeoutHandle = setTimeout(() => {
|
|
10661
|
-
this.controlResponseWaiters.delete(requestId);
|
|
10662
|
-
reject(new Error(`Timed out waiting for control_response (${requestId})`));
|
|
10663
|
-
}, options.timeoutMs);
|
|
10664
|
-
})
|
|
10665
|
-
]);
|
|
10666
|
-
}
|
|
10667
|
-
return await responsePromise;
|
|
10668
|
-
} finally {
|
|
10669
|
-
if (timeoutHandle) {
|
|
10670
|
-
clearTimeout(timeoutHandle);
|
|
10671
|
-
}
|
|
10672
|
-
}
|
|
10673
|
-
}
|
|
10674
|
-
async listMessages(options = {}) {
|
|
10675
|
-
if (!this.initialized) {
|
|
10676
|
-
throw new Error("Session must be initialized before calling listMessages()");
|
|
10677
|
-
}
|
|
10678
|
-
const requestId = `list-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
10679
|
-
const resp = await this.requestControlResponse(requestId, {
|
|
10680
|
-
subtype: "list_messages",
|
|
10681
|
-
...options.conversationId ? { conversation_id: options.conversationId } : {},
|
|
10682
|
-
...options.before ? { before: options.before } : {},
|
|
10683
|
-
...options.after ? { after: options.after } : {},
|
|
10684
|
-
...options.order ? { order: options.order } : {},
|
|
10685
|
-
...options.limit !== undefined ? { limit: options.limit } : {}
|
|
10686
|
-
});
|
|
10687
|
-
if (!resp) {
|
|
10688
|
-
throw new Error("Session closed before listMessages response arrived");
|
|
10689
|
-
}
|
|
10690
|
-
if (resp.subtype === "error") {
|
|
10691
|
-
throw new Error(resp.error ?? "listMessages failed");
|
|
10692
|
-
}
|
|
10693
|
-
const payload = resp.response;
|
|
10694
|
-
return {
|
|
10695
|
-
messages: payload?.messages ?? [],
|
|
10696
|
-
nextBefore: payload?.next_before ?? null,
|
|
10697
|
-
hasMore: payload?.has_more ?? false
|
|
10698
|
-
};
|
|
10699
|
-
}
|
|
10700
|
-
async listModels() {
|
|
10701
|
-
throw new Error("listModels() is not supported by this session. Use a Cloud, Remote, or local agent session.");
|
|
10702
|
-
}
|
|
10703
|
-
async sendCommand(_command, _options) {
|
|
10704
|
-
throw new Error("sendCommand() is not supported by this session. Use a Cloud, Remote, or local agent session.");
|
|
10705
|
-
}
|
|
10706
|
-
async updateModel(_update) {
|
|
10707
|
-
throw new Error("updateModel() is not supported by this session. Use a Cloud, Remote, or local agent session.");
|
|
10708
|
-
}
|
|
10709
|
-
async changeDeviceState(_updates) {
|
|
10710
|
-
throw new Error("changeDeviceState() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
|
|
10711
|
-
}
|
|
10712
|
-
async removeQueuedMessage(_itemId) {
|
|
10713
|
-
throw new Error("removeQueuedMessage() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
|
|
10714
|
-
}
|
|
10715
|
-
async getDeviceStatus(_options) {
|
|
10716
|
-
throw new Error("getDeviceStatus() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
|
|
10717
|
-
}
|
|
10718
|
-
onDeviceStatus(_listener) {
|
|
10719
|
-
throw new Error("onDeviceStatus() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
|
|
10720
|
-
}
|
|
10721
|
-
async bootstrapState(options = {}) {
|
|
10722
|
-
if (!this.initialized) {
|
|
10723
|
-
throw new Error("Session must be initialized before calling bootstrapState()");
|
|
10724
|
-
}
|
|
10725
|
-
const requestId = `bootstrap-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
10726
|
-
const resp = await this.requestControlResponse(requestId, {
|
|
10727
|
-
subtype: "bootstrap_session_state",
|
|
10728
|
-
...options.limit !== undefined ? { limit: options.limit } : {},
|
|
10729
|
-
...options.order ? { order: options.order } : {}
|
|
10730
|
-
});
|
|
10731
|
-
if (!resp) {
|
|
10732
|
-
throw new Error("Session closed before bootstrapState response arrived");
|
|
10733
|
-
}
|
|
10734
|
-
if (resp.subtype === "error") {
|
|
10735
|
-
throw new Error(resp.error ?? "bootstrapState failed");
|
|
10736
|
-
}
|
|
10737
|
-
const payload = resp.response;
|
|
10738
|
-
const state = {
|
|
10739
|
-
agentId: payload?.agent_id ?? this._agentId ?? "",
|
|
10740
|
-
conversationId: payload?.conversation_id ?? this._conversationId ?? "",
|
|
10741
|
-
model: payload?.model,
|
|
10742
|
-
memfsEnabled: payload?.memfs_enabled ?? false,
|
|
10743
|
-
messages: payload?.messages ?? [],
|
|
10744
|
-
nextBefore: payload?.next_before ?? null,
|
|
10745
|
-
hasMore: payload?.has_more ?? false,
|
|
10746
|
-
hasPendingApproval: payload?.has_pending_approval ?? false
|
|
10747
|
-
};
|
|
10748
|
-
if (payload?.tools !== undefined)
|
|
10749
|
-
state.tools = payload.tools;
|
|
10750
|
-
if (payload?.timings !== undefined)
|
|
10751
|
-
state.timings = payload.timings;
|
|
10752
|
-
return state;
|
|
10753
|
-
}
|
|
10754
|
-
async updateToolset(_toolsetPreference) {
|
|
10755
|
-
throw new Error("updateToolset() is not supported by this session. Use a Cloud, Remote, or local agent session.");
|
|
10756
|
-
}
|
|
10757
|
-
close() {
|
|
10758
|
-
if (this.closed)
|
|
10759
|
-
return;
|
|
10760
|
-
this.closed = true;
|
|
10761
|
-
sessionLog("close", `closing session (agent=${this._agentId}, conversation=${this._conversationId})`);
|
|
10762
|
-
this.transport.close();
|
|
10763
|
-
this.pumpClosed = true;
|
|
10764
|
-
this.resolveAllStreamWaiters(null);
|
|
10765
|
-
}
|
|
10766
|
-
get agentId() {
|
|
10767
|
-
return this._agentId;
|
|
10768
|
-
}
|
|
10769
|
-
get sessionId() {
|
|
10770
|
-
return this._sessionId;
|
|
10771
|
-
}
|
|
10772
|
-
get conversationId() {
|
|
10773
|
-
return this._conversationId;
|
|
10774
|
-
}
|
|
10775
|
-
async[Symbol.asyncDispose]() {
|
|
10776
|
-
this.close();
|
|
10777
|
-
}
|
|
10778
|
-
transformMessage(wireMsg) {
|
|
10779
|
-
if (wireMsg.type === "system" && "subtype" in wireMsg && wireMsg.subtype === "init") {
|
|
10780
|
-
const msg = wireMsg;
|
|
10781
|
-
return {
|
|
10782
|
-
type: "init",
|
|
10783
|
-
agentId: msg.agent_id,
|
|
10784
|
-
sessionId: msg.session_id,
|
|
10785
|
-
conversationId: msg.conversation_id,
|
|
10786
|
-
model: msg.model,
|
|
10787
|
-
tools: msg.tools,
|
|
10788
|
-
memfsEnabled: msg.memfs_enabled,
|
|
10789
|
-
skillSources: msg.skill_sources,
|
|
10790
|
-
systemInfoReminderEnabled: msg.system_info_reminder_enabled,
|
|
10791
|
-
dreaming: msg.reflection_trigger && msg.reflection_behavior && typeof msg.reflection_step_count === "number" ? {
|
|
10792
|
-
trigger: msg.reflection_trigger,
|
|
10793
|
-
behavior: msg.reflection_behavior,
|
|
10794
|
-
stepCount: msg.reflection_step_count
|
|
10795
|
-
} : undefined
|
|
10796
|
-
};
|
|
10797
|
-
}
|
|
10798
|
-
if (wireMsg.type === "message" && "message_type" in wireMsg) {
|
|
10799
|
-
const msg = wireMsg;
|
|
10800
|
-
const runId = msg.run_id || undefined;
|
|
10801
|
-
if (msg.message_type === "assistant_message" && msg.content) {
|
|
10802
|
-
return {
|
|
10803
|
-
type: "assistant",
|
|
10804
|
-
content: msg.content,
|
|
10805
|
-
uuid: msg.uuid,
|
|
10806
|
-
runId
|
|
10807
|
-
};
|
|
10808
|
-
}
|
|
10809
|
-
if (msg.message_type === "tool_call_message" || msg.message_type === "approval_request_message") {
|
|
10810
|
-
const toolCallRaw = msg.tool_calls?.[0] || msg.tool_call;
|
|
10811
|
-
if (toolCallRaw) {
|
|
10812
|
-
const fnObj = toolCallRaw.function;
|
|
10813
|
-
const toolCallId = toolCallRaw.tool_call_id ?? toolCallRaw.id;
|
|
10814
|
-
if (!toolCallId) {
|
|
10815
|
-
const detail = `Missing tool_call_id in ${msg.message_type} (uuid=${msg.uuid || "unknown"})`;
|
|
10816
|
-
sessionLog("transform", detail);
|
|
10817
|
-
return {
|
|
10818
|
-
type: "error",
|
|
10819
|
-
message: detail,
|
|
10820
|
-
errorCode: "protocol_error",
|
|
10821
|
-
stopReason: "protocol_error",
|
|
10822
|
-
runId,
|
|
10823
|
-
apiError: {
|
|
10824
|
-
error_type: "protocol_error",
|
|
10825
|
-
detail,
|
|
10826
|
-
message_type: msg.message_type
|
|
10827
|
-
},
|
|
10828
|
-
recoverable: false,
|
|
10829
|
-
errorDetail: detail
|
|
10830
|
-
};
|
|
10831
|
-
}
|
|
10832
|
-
const toolName = toolCallRaw.name ?? fnObj?.name ?? "?";
|
|
10833
|
-
const toolArgs = toolCallRaw.arguments ?? fnObj?.arguments ?? "";
|
|
10834
|
-
let toolInput = {};
|
|
10835
|
-
try {
|
|
10836
|
-
toolInput = JSON.parse(toolArgs);
|
|
10837
|
-
} catch {
|
|
10838
|
-
toolInput = { raw: toolArgs };
|
|
10839
|
-
}
|
|
10840
|
-
return {
|
|
10841
|
-
type: "tool_call",
|
|
10842
|
-
toolCallId,
|
|
10843
|
-
toolName,
|
|
10844
|
-
toolInput,
|
|
10845
|
-
rawArguments: toolArgs || undefined,
|
|
10846
|
-
uuid: msg.uuid,
|
|
10847
|
-
runId
|
|
10848
|
-
};
|
|
10849
|
-
}
|
|
10850
|
-
}
|
|
10851
|
-
if (msg.message_type === "tool_return_message" && msg.tool_call_id) {
|
|
10852
|
-
return {
|
|
10853
|
-
type: "tool_result",
|
|
10854
|
-
toolCallId: msg.tool_call_id,
|
|
10855
|
-
content: msg.tool_return || "",
|
|
10856
|
-
isError: msg.status === "error",
|
|
10857
|
-
uuid: msg.uuid,
|
|
10858
|
-
runId
|
|
10859
|
-
};
|
|
10860
|
-
}
|
|
10861
|
-
if (msg.message_type === "reasoning_message" && msg.reasoning) {
|
|
10862
|
-
return {
|
|
10863
|
-
type: "reasoning",
|
|
10864
|
-
content: msg.reasoning,
|
|
10865
|
-
uuid: msg.uuid,
|
|
10866
|
-
runId
|
|
10867
|
-
};
|
|
10868
|
-
}
|
|
10869
|
-
}
|
|
10870
|
-
if (wireMsg.type === "stream_event") {
|
|
10871
|
-
const msg = wireMsg;
|
|
10872
|
-
const eventPayload = msg.event ?? {};
|
|
10873
|
-
return {
|
|
10874
|
-
type: "stream_event",
|
|
10875
|
-
event: eventPayload,
|
|
10876
|
-
uuid: msg.uuid
|
|
10877
|
-
};
|
|
10878
|
-
}
|
|
10879
|
-
if (wireMsg.type === "result") {
|
|
10880
|
-
const msg = wireMsg;
|
|
10881
|
-
const runIds = Array.isArray(msg.run_ids) ? msg.run_ids.filter((id) => typeof id === "string") : undefined;
|
|
10882
|
-
const approvalConflict = isApprovalConflictSignal2({
|
|
10883
|
-
stopReason: msg.stop_reason
|
|
10884
|
-
});
|
|
10885
|
-
const legacyError = msg.subtype !== "success" ? approvalConflict && msg.subtype === "error" ? "approval_conflict" : msg.subtype : undefined;
|
|
10886
|
-
const errorCode = toSdkErrorCode2(legacyError);
|
|
10887
|
-
return {
|
|
10888
|
-
type: "result",
|
|
10889
|
-
success: msg.subtype === "success",
|
|
10890
|
-
result: msg.result,
|
|
10891
|
-
error: legacyError,
|
|
10892
|
-
errorCode,
|
|
10893
|
-
...approvalConflict ? {
|
|
10894
|
-
approvalConflict: true,
|
|
10895
|
-
recoverable: true
|
|
10896
|
-
} : {},
|
|
10897
|
-
stopReason: msg.stop_reason,
|
|
10898
|
-
durationMs: msg.duration_ms,
|
|
10899
|
-
totalCostUsd: msg.total_cost_usd,
|
|
10900
|
-
conversationId: msg.conversation_id,
|
|
10901
|
-
runIds
|
|
10902
|
-
};
|
|
10903
|
-
}
|
|
10904
|
-
if (wireMsg.type === "error") {
|
|
10905
|
-
const msg = wireMsg;
|
|
10906
|
-
const errorDetail = extractApiErrorDetail(msg.api_error);
|
|
10907
|
-
const approvalConflict = isApprovalConflictSignal2({
|
|
10908
|
-
detail: errorDetail,
|
|
10909
|
-
message: msg.message,
|
|
10910
|
-
stopReason: msg.stop_reason
|
|
10911
|
-
});
|
|
10912
|
-
const errorCode = approvalConflict ? "approval_conflict" : toSdkErrorCode2(msg.stop_reason);
|
|
10913
|
-
return {
|
|
10914
|
-
type: "error",
|
|
10915
|
-
message: msg.message,
|
|
10916
|
-
...errorCode ? { errorCode } : {},
|
|
10917
|
-
...approvalConflict ? {
|
|
10918
|
-
approvalConflict: true,
|
|
10919
|
-
recoverable: true
|
|
10920
|
-
} : {},
|
|
10921
|
-
...errorDetail ? { errorDetail } : {},
|
|
10922
|
-
stopReason: msg.stop_reason,
|
|
10923
|
-
runId: msg.run_id,
|
|
10924
|
-
apiError: msg.api_error
|
|
10925
|
-
};
|
|
10926
|
-
}
|
|
10927
|
-
if (wireMsg.type === "retry") {
|
|
10928
|
-
const msg = wireMsg;
|
|
10929
|
-
return {
|
|
10930
|
-
type: "retry",
|
|
10931
|
-
reason: msg.reason,
|
|
10932
|
-
attempt: msg.attempt,
|
|
10933
|
-
maxAttempts: msg.max_attempts,
|
|
10934
|
-
delayMs: msg.delay_ms,
|
|
10935
|
-
runId: msg.run_id
|
|
10936
|
-
};
|
|
10937
|
-
}
|
|
10938
|
-
return null;
|
|
10939
|
-
}
|
|
10940
|
-
}
|
|
10941
|
-
|
|
10942
9890
|
// src/client.ts
|
|
10943
9891
|
class LettaAgentClient extends LettaAgentClientBase {
|
|
10944
9892
|
createLocalManagementTransport() {
|
|
10945
|
-
if (this.useLegacyLocalStdio()) {
|
|
10946
|
-
throw new Error('client.agents and client.conversations require the local "app-server" transport.');
|
|
10947
|
-
}
|
|
10948
9893
|
const localOptions = this.options.appServer;
|
|
10949
9894
|
return new AppServerManagementTransport({
|
|
10950
9895
|
...localOptions?.url !== undefined ? { url: localOptions.url } : {
|
|
@@ -10955,65 +9900,42 @@ class LettaAgentClient extends LettaAgentClientBase {
|
|
|
10955
9900
|
})
|
|
10956
9901
|
},
|
|
10957
9902
|
...localOptions?.WebSocket !== undefined ? { WebSocket: localOptions.WebSocket } : {},
|
|
10958
|
-
...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {}
|
|
10959
|
-
...localOptions?.idleLingerMs !== undefined ? { idleLingerMs: localOptions.idleLingerMs } : {}
|
|
9903
|
+
...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {}
|
|
10960
9904
|
});
|
|
10961
9905
|
}
|
|
10962
9906
|
async createLocalAgent(options) {
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
}, () => this.releaseIdleManagementConnection());
|
|
10969
|
-
const initMsg2 = await session2.initialize();
|
|
10970
|
-
session2.close();
|
|
10971
|
-
return initMsg2.agentId;
|
|
10972
|
-
}
|
|
10973
|
-
const session = new Session({ ...options, createOnly: true });
|
|
9907
|
+
const localOptions = this.options;
|
|
9908
|
+
const session = createLocalAppServerSession(localOptions.appServer, {
|
|
9909
|
+
kind: "create-agent",
|
|
9910
|
+
options
|
|
9911
|
+
});
|
|
10974
9912
|
const initMsg = await session.initialize();
|
|
10975
9913
|
session.close();
|
|
10976
9914
|
return initMsg.agentId;
|
|
10977
9915
|
}
|
|
10978
|
-
createLocalSession(agentId, options
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
}, () => this.releaseIdleManagementConnection());
|
|
10987
|
-
}
|
|
10988
|
-
if (agentId) {
|
|
10989
|
-
return new Session({ ...sessionOptions, agentId, newConversation: true });
|
|
10990
|
-
}
|
|
10991
|
-
return new Session({ ...sessionOptions, newConversation: true });
|
|
9916
|
+
createLocalSession(agentId, options) {
|
|
9917
|
+
const localOptions = this.options;
|
|
9918
|
+
return createLocalAppServerSession(localOptions.appServer, {
|
|
9919
|
+
kind: "session",
|
|
9920
|
+
agentId,
|
|
9921
|
+
newConversation: true,
|
|
9922
|
+
options
|
|
9923
|
+
});
|
|
10992
9924
|
}
|
|
10993
|
-
resumeLocalSession(id, options
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
if (looksLikeConversationId2(id)) {
|
|
10997
|
-
return createLocalAppServerSession(localOptions.appServer, {
|
|
10998
|
-
kind: "session",
|
|
10999
|
-
conversationId: id,
|
|
11000
|
-
options
|
|
11001
|
-
}, () => this.releaseIdleManagementConnection());
|
|
11002
|
-
}
|
|
9925
|
+
resumeLocalSession(id, options) {
|
|
9926
|
+
const localOptions = this.options;
|
|
9927
|
+
if (looksLikeConversationId2(id)) {
|
|
11003
9928
|
return createLocalAppServerSession(localOptions.appServer, {
|
|
11004
9929
|
kind: "session",
|
|
11005
|
-
|
|
11006
|
-
defaultConversation: true,
|
|
9930
|
+
conversationId: id,
|
|
11007
9931
|
options
|
|
11008
|
-
}
|
|
11009
|
-
}
|
|
11010
|
-
if (looksLikeConversationId2(id)) {
|
|
11011
|
-
return new Session({ ...sessionOptions, conversationId: id });
|
|
9932
|
+
});
|
|
11012
9933
|
}
|
|
11013
|
-
return
|
|
11014
|
-
|
|
9934
|
+
return createLocalAppServerSession(localOptions.appServer, {
|
|
9935
|
+
kind: "session",
|
|
11015
9936
|
agentId: id,
|
|
11016
|
-
defaultConversation: true
|
|
9937
|
+
defaultConversation: true,
|
|
9938
|
+
options
|
|
11017
9939
|
});
|
|
11018
9940
|
}
|
|
11019
9941
|
}
|
|
@@ -11190,17 +10112,14 @@ async function createAgent(options = {}) {
|
|
|
11190
10112
|
}
|
|
11191
10113
|
function createSession(agentId, options = {}) {
|
|
11192
10114
|
validateCreateSessionOptions(options);
|
|
11193
|
-
|
|
11194
|
-
return new LettaAgentClient().createSession(agentId, options);
|
|
11195
|
-
}
|
|
11196
|
-
return new LettaAgentClient({ backend: "local", transport: "stdio" }).createSession(options);
|
|
10115
|
+
return new LettaAgentClient().createSession(agentId, options);
|
|
11197
10116
|
}
|
|
11198
10117
|
function resumeSession(id, options = {}) {
|
|
11199
10118
|
validateCreateSessionOptions(options);
|
|
11200
10119
|
return new LettaAgentClient().resumeSession(id, options);
|
|
11201
10120
|
}
|
|
11202
|
-
async function prompt(message, agentId) {
|
|
11203
|
-
const session =
|
|
10121
|
+
async function prompt(message, agentId, options = {}) {
|
|
10122
|
+
const session = createSession(agentId, options);
|
|
11204
10123
|
try {
|
|
11205
10124
|
return await session.runTurn(message);
|
|
11206
10125
|
} finally {
|
|
@@ -11267,10 +10186,9 @@ export {
|
|
|
11267
10186
|
createSession,
|
|
11268
10187
|
createReactNativeWebSocketConstructor,
|
|
11269
10188
|
createAgent,
|
|
11270
|
-
Session,
|
|
11271
10189
|
RepositoriesClient,
|
|
11272
10190
|
LettaAgentClient,
|
|
11273
10191
|
CloudManagedSandboxExpiredError
|
|
11274
10192
|
};
|
|
11275
10193
|
|
|
11276
|
-
//# debugId=
|
|
10194
|
+
//# debugId=31B7FC9589C9EB7964756E2164756E21
|