@letta-ai/letta-agent-sdk 0.3.3 → 0.5.0
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 +12 -0
- 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 +369 -468
- 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.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 +896 -2019
- 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 +57 -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 +9 -267
- 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 +14 -0
- package/src/remote-turn-coordinator.ts +20 -2
- package/src/types.ts +64 -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
|
}
|
|
@@ -6377,6 +6246,8 @@ class RemoteTurnCoordinator {
|
|
|
6377
6246
|
transformStreamDelta(delta) {
|
|
6378
6247
|
const messageType = typeof delta.message_type === "string" ? delta.message_type : undefined;
|
|
6379
6248
|
const runId = typeof delta.run_id === "string" ? delta.run_id : undefined;
|
|
6249
|
+
const otid = streamDeltaOtid(delta);
|
|
6250
|
+
const seqId = streamDeltaSeqId(delta);
|
|
6380
6251
|
const uuid = typeof delta.id === "string" ? delta.id : `${this.label}-${++this.messageCounter}`;
|
|
6381
6252
|
if (messageType === "assistant_message") {
|
|
6382
6253
|
const content = extractTextFromContent(delta.content);
|
|
@@ -6384,13 +6255,27 @@ class RemoteTurnCoordinator {
|
|
|
6384
6255
|
return null;
|
|
6385
6256
|
if (this.activeTurn)
|
|
6386
6257
|
this.activeTurn.assistantText += content;
|
|
6387
|
-
return {
|
|
6258
|
+
return {
|
|
6259
|
+
type: "assistant",
|
|
6260
|
+
content,
|
|
6261
|
+
uuid,
|
|
6262
|
+
...otid !== undefined ? { otid } : {},
|
|
6263
|
+
...seqId !== undefined ? { seqId } : {},
|
|
6264
|
+
runId
|
|
6265
|
+
};
|
|
6388
6266
|
}
|
|
6389
6267
|
if (messageType === "reasoning_message") {
|
|
6390
6268
|
const content = typeof delta.reasoning === "string" ? delta.reasoning : extractTextFromContent(delta.content);
|
|
6391
6269
|
if (!content)
|
|
6392
6270
|
return null;
|
|
6393
|
-
return {
|
|
6271
|
+
return {
|
|
6272
|
+
type: "reasoning",
|
|
6273
|
+
content,
|
|
6274
|
+
uuid,
|
|
6275
|
+
...otid !== undefined ? { otid } : {},
|
|
6276
|
+
...seqId !== undefined ? { seqId } : {},
|
|
6277
|
+
runId
|
|
6278
|
+
};
|
|
6394
6279
|
}
|
|
6395
6280
|
if (messageType === "tool_call_message" || messageType === "approval_request_message") {
|
|
6396
6281
|
const toolCall = firstToolCall(delta);
|
|
@@ -7093,26 +6978,6 @@ function assertRemoteCreateAgentOptionsSupported(options) {
|
|
|
7093
6978
|
throw new Error("App-server createAgent() does not yet support dreaming.behavior overrides.");
|
|
7094
6979
|
}
|
|
7095
6980
|
}
|
|
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
6981
|
function normalizeMemoryBlock(block) {
|
|
7117
6982
|
const normalized = { ...block };
|
|
7118
6983
|
if (normalized.value === undefined && typeof normalized.content === "string") {
|
|
@@ -7543,9 +7408,7 @@ class AppServerSession extends RemoteClientSessionCore {
|
|
|
7543
7408
|
return options.memfs !== false;
|
|
7544
7409
|
}
|
|
7545
7410
|
async initializeRuntimeController() {
|
|
7546
|
-
await this.remoteOptions.beforeConnect?.();
|
|
7547
7411
|
const url = await this.resolveAppServerUrl();
|
|
7548
|
-
await releaseAppServerManagementConnections(url);
|
|
7549
7412
|
const client = applyUniqueRequestIds(createAppServerClient({
|
|
7550
7413
|
url,
|
|
7551
7414
|
...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
|
|
@@ -7855,6 +7718,222 @@ class CloudManagementTransport {
|
|
|
7855
7718
|
}
|
|
7856
7719
|
}
|
|
7857
7720
|
|
|
7721
|
+
// src/cloud-status-transport.ts
|
|
7722
|
+
var CONNECTING = 0;
|
|
7723
|
+
var OPEN = 1;
|
|
7724
|
+
var CLOSING = 2;
|
|
7725
|
+
var CLOSED = 3;
|
|
7726
|
+
var MAX_IDEMPOTENCY_KEYS = 1000;
|
|
7727
|
+
function addSocketListener(socket, type, listener) {
|
|
7728
|
+
if (socket.addEventListener) {
|
|
7729
|
+
socket.addEventListener(type, listener);
|
|
7730
|
+
return () => socket.removeEventListener?.(type, listener);
|
|
7731
|
+
}
|
|
7732
|
+
if (socket.on) {
|
|
7733
|
+
socket.on(type, listener);
|
|
7734
|
+
return () => socket.off?.(type, listener);
|
|
7735
|
+
}
|
|
7736
|
+
throw new Error("WebSocket implementation does not support event listeners.");
|
|
7737
|
+
}
|
|
7738
|
+
function messageEventData(event) {
|
|
7739
|
+
if (typeof event === "string")
|
|
7740
|
+
return event;
|
|
7741
|
+
if (!event || typeof event !== "object")
|
|
7742
|
+
return null;
|
|
7743
|
+
const data = event.data;
|
|
7744
|
+
if (typeof data === "string")
|
|
7745
|
+
return data;
|
|
7746
|
+
if (data instanceof ArrayBuffer) {
|
|
7747
|
+
return new TextDecoder().decode(data);
|
|
7748
|
+
}
|
|
7749
|
+
if (data instanceof Uint8Array) {
|
|
7750
|
+
return new TextDecoder().decode(data);
|
|
7751
|
+
}
|
|
7752
|
+
return null;
|
|
7753
|
+
}
|
|
7754
|
+
function channelUrl(url, channel) {
|
|
7755
|
+
const parsed = new URL(url);
|
|
7756
|
+
parsed.searchParams.set("channel", channel);
|
|
7757
|
+
return parsed.toString();
|
|
7758
|
+
}
|
|
7759
|
+
|
|
7760
|
+
class CloudStatusTransport {
|
|
7761
|
+
options;
|
|
7762
|
+
controlSocket;
|
|
7763
|
+
streamSocket;
|
|
7764
|
+
state = CONNECTING;
|
|
7765
|
+
openedChannels = new Set;
|
|
7766
|
+
listeners = new Map;
|
|
7767
|
+
removers = [];
|
|
7768
|
+
seenIdempotencyKeys = new Set;
|
|
7769
|
+
seenIdempotencyOrder = [];
|
|
7770
|
+
lastEventSeq = null;
|
|
7771
|
+
pingTimer = null;
|
|
7772
|
+
closeEmitted = false;
|
|
7773
|
+
constructor(options) {
|
|
7774
|
+
this.options = options;
|
|
7775
|
+
const socketOptions = options.headers ? { headers: options.headers } : undefined;
|
|
7776
|
+
this.controlSocket = new options.WebSocket(channelUrl(options.url, "control"), socketOptions);
|
|
7777
|
+
this.streamSocket = new options.WebSocket(channelUrl(options.url, "stream"), socketOptions);
|
|
7778
|
+
this.bindSocket("control", this.controlSocket);
|
|
7779
|
+
this.bindSocket("stream", this.streamSocket);
|
|
7780
|
+
this.startPing();
|
|
7781
|
+
}
|
|
7782
|
+
get readyState() {
|
|
7783
|
+
return this.state;
|
|
7784
|
+
}
|
|
7785
|
+
send(data) {
|
|
7786
|
+
if (this.state !== OPEN || this.controlSocket.readyState !== OPEN) {
|
|
7787
|
+
throw new Error("Cloud status control socket is not open");
|
|
7788
|
+
}
|
|
7789
|
+
this.controlSocket.send(data);
|
|
7790
|
+
}
|
|
7791
|
+
close() {
|
|
7792
|
+
if (this.state === CLOSED || this.state === CLOSING)
|
|
7793
|
+
return;
|
|
7794
|
+
this.state = CLOSING;
|
|
7795
|
+
this.stopPing();
|
|
7796
|
+
this.closeUnderlyingSockets();
|
|
7797
|
+
this.finishClose({});
|
|
7798
|
+
}
|
|
7799
|
+
addEventListener(type, listener) {
|
|
7800
|
+
let listeners = this.listeners.get(type);
|
|
7801
|
+
if (!listeners) {
|
|
7802
|
+
listeners = new Set;
|
|
7803
|
+
this.listeners.set(type, listeners);
|
|
7804
|
+
}
|
|
7805
|
+
listeners.add(listener);
|
|
7806
|
+
}
|
|
7807
|
+
removeEventListener(type, listener) {
|
|
7808
|
+
this.listeners.get(type)?.delete(listener);
|
|
7809
|
+
}
|
|
7810
|
+
bindSocket(channel, socket) {
|
|
7811
|
+
this.removers.push(addSocketListener(socket, "open", () => {
|
|
7812
|
+
this.openedChannels.add(channel);
|
|
7813
|
+
if (this.openedChannels.size === 2 && this.state === CONNECTING) {
|
|
7814
|
+
this.state = OPEN;
|
|
7815
|
+
this.emit("open", {});
|
|
7816
|
+
}
|
|
7817
|
+
}), addSocketListener(socket, "message", (event) => {
|
|
7818
|
+
if (this.shouldForwardMessage(channel, socket, event)) {
|
|
7819
|
+
this.emit("message", event);
|
|
7820
|
+
}
|
|
7821
|
+
}), addSocketListener(socket, "error", (event) => {
|
|
7822
|
+
this.emit("error", event);
|
|
7823
|
+
}), addSocketListener(socket, "close", (event) => {
|
|
7824
|
+
if (this.state !== CLOSED) {
|
|
7825
|
+
this.state = CLOSING;
|
|
7826
|
+
this.stopPing();
|
|
7827
|
+
this.closeUnderlyingSockets(socket);
|
|
7828
|
+
this.finishClose(event);
|
|
7829
|
+
}
|
|
7830
|
+
}));
|
|
7831
|
+
}
|
|
7832
|
+
shouldForwardMessage(channel, socket, event) {
|
|
7833
|
+
const data = messageEventData(event);
|
|
7834
|
+
if (!data)
|
|
7835
|
+
return true;
|
|
7836
|
+
let message;
|
|
7837
|
+
try {
|
|
7838
|
+
message = JSON.parse(data);
|
|
7839
|
+
} catch {
|
|
7840
|
+
return true;
|
|
7841
|
+
}
|
|
7842
|
+
this.ackIfSequenced(socket, message);
|
|
7843
|
+
if (channel === "control" && message.type === "stream_delta") {
|
|
7844
|
+
return false;
|
|
7845
|
+
}
|
|
7846
|
+
if (this.isDuplicate(message))
|
|
7847
|
+
return false;
|
|
7848
|
+
this.trackEventSequence(message);
|
|
7849
|
+
return true;
|
|
7850
|
+
}
|
|
7851
|
+
ackIfSequenced(socket, message) {
|
|
7852
|
+
if (typeof message.seq !== "number")
|
|
7853
|
+
return;
|
|
7854
|
+
this.sendCommand(socket, { type: "ack", seq: message.seq });
|
|
7855
|
+
}
|
|
7856
|
+
isDuplicate(message) {
|
|
7857
|
+
const key = typeof message.idempotency_key === "string" ? message.idempotency_key : null;
|
|
7858
|
+
if (!key)
|
|
7859
|
+
return false;
|
|
7860
|
+
if (this.seenIdempotencyKeys.has(key))
|
|
7861
|
+
return true;
|
|
7862
|
+
this.seenIdempotencyKeys.add(key);
|
|
7863
|
+
this.seenIdempotencyOrder.push(key);
|
|
7864
|
+
while (this.seenIdempotencyOrder.length > MAX_IDEMPOTENCY_KEYS) {
|
|
7865
|
+
const oldest = this.seenIdempotencyOrder.shift();
|
|
7866
|
+
if (oldest)
|
|
7867
|
+
this.seenIdempotencyKeys.delete(oldest);
|
|
7868
|
+
}
|
|
7869
|
+
return false;
|
|
7870
|
+
}
|
|
7871
|
+
trackEventSequence(message) {
|
|
7872
|
+
if (typeof message.event_seq !== "number")
|
|
7873
|
+
return;
|
|
7874
|
+
if (this.lastEventSeq !== null && message.event_seq > this.lastEventSeq + 1) {
|
|
7875
|
+
this.sendCommand(this.controlSocket, {
|
|
7876
|
+
type: "sync",
|
|
7877
|
+
runtime: this.options.runtime,
|
|
7878
|
+
recover_approvals: true,
|
|
7879
|
+
force_device_status: true
|
|
7880
|
+
});
|
|
7881
|
+
}
|
|
7882
|
+
if (this.lastEventSeq === null || message.event_seq > this.lastEventSeq) {
|
|
7883
|
+
this.lastEventSeq = message.event_seq;
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
sendCommand(socket, command) {
|
|
7887
|
+
if (socket.readyState !== OPEN)
|
|
7888
|
+
return;
|
|
7889
|
+
try {
|
|
7890
|
+
socket.send(JSON.stringify(command));
|
|
7891
|
+
} catch {}
|
|
7892
|
+
}
|
|
7893
|
+
startPing() {
|
|
7894
|
+
this.pingTimer = setInterval(() => {
|
|
7895
|
+
this.sendCommand(this.controlSocket, { type: "ping" });
|
|
7896
|
+
this.sendCommand(this.streamSocket, { type: "ping" });
|
|
7897
|
+
}, this.options.pingIntervalMs);
|
|
7898
|
+
this.pingTimer.unref?.();
|
|
7899
|
+
}
|
|
7900
|
+
stopPing() {
|
|
7901
|
+
if (!this.pingTimer)
|
|
7902
|
+
return;
|
|
7903
|
+
clearInterval(this.pingTimer);
|
|
7904
|
+
this.pingTimer = null;
|
|
7905
|
+
}
|
|
7906
|
+
closeUnderlyingSockets(except) {
|
|
7907
|
+
for (const socket of [this.controlSocket, this.streamSocket]) {
|
|
7908
|
+
if (socket !== except && (socket.readyState === CONNECTING || socket.readyState === OPEN)) {
|
|
7909
|
+
socket.close();
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
}
|
|
7913
|
+
finishClose(event) {
|
|
7914
|
+
if (this.closeEmitted)
|
|
7915
|
+
return;
|
|
7916
|
+
this.closeEmitted = true;
|
|
7917
|
+
this.state = CLOSED;
|
|
7918
|
+
this.stopPing();
|
|
7919
|
+
for (const remove of this.removers.splice(0))
|
|
7920
|
+
remove();
|
|
7921
|
+
this.emit("close", event);
|
|
7922
|
+
}
|
|
7923
|
+
emit(type, event) {
|
|
7924
|
+
for (const listener of this.listeners.get(type) ?? []) {
|
|
7925
|
+
listener(event);
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
function createCloudStatusTransportConstructor(options) {
|
|
7930
|
+
return class CloudStatusTransportSocket extends CloudStatusTransport {
|
|
7931
|
+
constructor(_url, _socketOptions) {
|
|
7932
|
+
super(options);
|
|
7933
|
+
}
|
|
7934
|
+
};
|
|
7935
|
+
}
|
|
7936
|
+
|
|
7858
7937
|
// src/remote.ts
|
|
7859
7938
|
function getDefaultApiKey2() {
|
|
7860
7939
|
if (typeof process === "undefined") {
|
|
@@ -8163,191 +8242,6 @@ function buildCloudStatusWebSocketUrl(params) {
|
|
|
8163
8242
|
}
|
|
8164
8243
|
return base.toString();
|
|
8165
8244
|
}
|
|
8166
|
-
function addSocketListener(socket, type, listener) {
|
|
8167
|
-
if (socket.addEventListener) {
|
|
8168
|
-
socket.addEventListener(type, listener);
|
|
8169
|
-
return () => socket.removeEventListener?.(type, listener);
|
|
8170
|
-
}
|
|
8171
|
-
if (socket.on) {
|
|
8172
|
-
socket.on(type, listener);
|
|
8173
|
-
return () => socket.off?.(type, listener);
|
|
8174
|
-
}
|
|
8175
|
-
throw new Error("WebSocket implementation does not support event listeners.");
|
|
8176
|
-
}
|
|
8177
|
-
function messageEventData(event) {
|
|
8178
|
-
if (typeof event === "string")
|
|
8179
|
-
return event;
|
|
8180
|
-
if (event && typeof event === "object") {
|
|
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
8245
|
function isCloudConversation(value) {
|
|
8352
8246
|
return Boolean(value && typeof value === "object" && typeof value.id === "string");
|
|
8353
8247
|
}
|
|
@@ -8391,24 +8285,6 @@ function assertCloudSessionOptionsSupported(action, options) {
|
|
|
8391
8285
|
if (options.environment !== undefined && options.sandbox !== undefined) {
|
|
8392
8286
|
throw new Error(`Letta Cloud ${action}() cannot specify both environment and sandbox options.`);
|
|
8393
8287
|
}
|
|
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.`);
|
|
8411
|
-
}
|
|
8412
8288
|
}
|
|
8413
8289
|
|
|
8414
8290
|
class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
@@ -8448,8 +8324,11 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8448
8324
|
});
|
|
8449
8325
|
const client = applyUniqueRequestIds(createAppServerClient({
|
|
8450
8326
|
url,
|
|
8451
|
-
WebSocket:
|
|
8452
|
-
|
|
8327
|
+
WebSocket: createCloudStatusTransportConstructor({
|
|
8328
|
+
url,
|
|
8329
|
+
WebSocket: getWebSocketConstructor(this.cloudOptions.WebSocket),
|
|
8330
|
+
...(this.cloudOptions.webSocketAuth ?? "header") === "header" ? { headers: cloudWebSocketHeaders(this.cloudOptions) } : {},
|
|
8331
|
+
pingIntervalMs: this.cloudOptions.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS,
|
|
8453
8332
|
runtime: resolved.runtime
|
|
8454
8333
|
}),
|
|
8455
8334
|
requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
|
|
@@ -9037,7 +8916,23 @@ function validateApprovalRecoveryOptions(options) {
|
|
|
9037
8916
|
}
|
|
9038
8917
|
}
|
|
9039
8918
|
function validateRemovedSessionOptions(options) {
|
|
9040
|
-
|
|
8919
|
+
const removedOptions = options;
|
|
8920
|
+
if (removedOptions.systemPrompt !== undefined) {
|
|
8921
|
+
throw new Error("systemPrompt is not supported when opening an existing agent session.");
|
|
8922
|
+
}
|
|
8923
|
+
if (removedOptions.disallowedTools !== undefined) {
|
|
8924
|
+
throw new Error("disallowedTools is not supported when opening an existing agent session.");
|
|
8925
|
+
}
|
|
8926
|
+
if (removedOptions.systemInfoReminder !== undefined) {
|
|
8927
|
+
throw new Error("systemInfoReminder is not supported when opening an existing agent session.");
|
|
8928
|
+
}
|
|
8929
|
+
if (removedOptions.includePartialMessages !== undefined) {
|
|
8930
|
+
throw new Error("includePartialMessages is not supported by app-server sessions.");
|
|
8931
|
+
}
|
|
8932
|
+
if (options.dreaming && "behavior" in options.dreaming && options.dreaming.behavior !== undefined) {
|
|
8933
|
+
throw new Error("dreaming.behavior is not supported when opening an existing agent session.");
|
|
8934
|
+
}
|
|
8935
|
+
if (removedOptions.memfsStartup !== undefined) {
|
|
9041
8936
|
throw new Error("memfsStartup is not supported by the SDK.");
|
|
9042
8937
|
}
|
|
9043
8938
|
}
|
|
@@ -9087,9 +8982,6 @@ function validateReasoningEffort(value) {
|
|
|
9087
8982
|
}
|
|
9088
8983
|
}
|
|
9089
8984
|
function validateCreateSessionOptions(options) {
|
|
9090
|
-
if (options.systemPrompt !== undefined) {
|
|
9091
|
-
validateSystemPromptPreset(options.systemPrompt);
|
|
9092
|
-
}
|
|
9093
8985
|
validateSkillSources(options.skillSources);
|
|
9094
8986
|
validateReasoningEffort(options.reasoningEffort);
|
|
9095
8987
|
validateDreamingOptions(options.dreaming);
|
|
@@ -9145,6 +9037,7 @@ function stripCloudExecutionOptions(options) {
|
|
|
9145
9037
|
const sessionOptions = { ...options };
|
|
9146
9038
|
delete sessionOptions.environment;
|
|
9147
9039
|
delete sessionOptions.sandbox;
|
|
9040
|
+
delete sessionOptions.filesystemConfinement;
|
|
9148
9041
|
return sessionOptions;
|
|
9149
9042
|
}
|
|
9150
9043
|
function hasRepositoryResources(options) {
|
|
@@ -9188,17 +9081,13 @@ class LettaAgentClientBase {
|
|
|
9188
9081
|
}
|
|
9189
9082
|
if (this.backend === "local") {
|
|
9190
9083
|
const localOptions = options;
|
|
9191
|
-
if (
|
|
9192
|
-
throw new Error("
|
|
9084
|
+
if ("transport" in localOptions) {
|
|
9085
|
+
throw new Error("Local transport selection has been removed. The local backend always uses the app-server protocol.");
|
|
9193
9086
|
}
|
|
9194
9087
|
const requestTimeoutMs = localOptions.appServer?.requestTimeoutMs;
|
|
9195
9088
|
if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
|
|
9196
9089
|
throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
|
|
9197
9090
|
}
|
|
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
9091
|
const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
|
|
9203
9092
|
if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
|
|
9204
9093
|
throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
|
|
@@ -9211,9 +9100,6 @@ class LettaAgentClientBase {
|
|
|
9211
9100
|
if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
|
|
9212
9101
|
throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
|
|
9213
9102
|
}
|
|
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
9103
|
}
|
|
9218
9104
|
if (this.backend === "cloud") {
|
|
9219
9105
|
validateCloudClientOptions(options);
|
|
@@ -9241,40 +9127,35 @@ class LettaAgentClientBase {
|
|
|
9241
9127
|
}
|
|
9242
9128
|
return this.createLocalAgent(options);
|
|
9243
9129
|
}
|
|
9244
|
-
createSession(
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
const sessionOptions = stripCloudExecutionOptions(
|
|
9130
|
+
createSession(agentId, options = {}) {
|
|
9131
|
+
if (typeof agentId !== "string" || agentId.length === 0) {
|
|
9132
|
+
throw new Error("createSession() requires a non-empty agent id.");
|
|
9133
|
+
}
|
|
9134
|
+
const sessionOptions = stripCloudExecutionOptions(options);
|
|
9249
9135
|
validateCreateSessionOptions(sessionOptions);
|
|
9136
|
+
this.assertSessionBackend("createSession", options);
|
|
9250
9137
|
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
9138
|
return new AppServerSession(this.appServerSessionOptions(), {
|
|
9255
9139
|
kind: "session",
|
|
9256
9140
|
agentId,
|
|
9257
9141
|
newConversation: true,
|
|
9258
|
-
options
|
|
9142
|
+
options
|
|
9259
9143
|
});
|
|
9260
9144
|
}
|
|
9261
9145
|
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
9146
|
return new CloudEnvironmentSession(this.cloudOptions(), {
|
|
9266
9147
|
kind: "session",
|
|
9267
9148
|
agentId,
|
|
9268
9149
|
newConversation: true,
|
|
9269
|
-
options
|
|
9150
|
+
options
|
|
9270
9151
|
});
|
|
9271
9152
|
}
|
|
9272
|
-
return this.createLocalSession(agentId,
|
|
9153
|
+
return this.createLocalSession(agentId, options);
|
|
9273
9154
|
}
|
|
9274
9155
|
resumeSession(id, options = {}) {
|
|
9275
|
-
this.assertSessionBackend("resumeSession", options);
|
|
9276
9156
|
const sessionOptions = stripCloudExecutionOptions(options);
|
|
9277
9157
|
validateCreateSessionOptions(sessionOptions);
|
|
9158
|
+
this.assertSessionBackend("resumeSession", options);
|
|
9278
9159
|
if (this.backend === "remote") {
|
|
9279
9160
|
if (looksLikeConversationId(id)) {
|
|
9280
9161
|
return new AppServerSession(this.appServerSessionOptions(), {
|
|
@@ -9305,10 +9186,10 @@ class LettaAgentClientBase {
|
|
|
9305
9186
|
options
|
|
9306
9187
|
});
|
|
9307
9188
|
}
|
|
9308
|
-
return this.resumeLocalSession(id, options
|
|
9189
|
+
return this.resumeLocalSession(id, options);
|
|
9309
9190
|
}
|
|
9310
9191
|
async prompt(message, agentId, options = {}) {
|
|
9311
|
-
const session =
|
|
9192
|
+
const session = this.createSession(agentId, options);
|
|
9312
9193
|
try {
|
|
9313
9194
|
return await session.runTurn(message);
|
|
9314
9195
|
} finally {
|
|
@@ -9316,6 +9197,9 @@ class LettaAgentClientBase {
|
|
|
9316
9197
|
}
|
|
9317
9198
|
}
|
|
9318
9199
|
assertSessionBackend(action, options) {
|
|
9200
|
+
if (options.filesystemConfinement !== undefined && options.filesystemConfinement !== "memory") {
|
|
9201
|
+
throw new Error(`Invalid filesystemConfinement '${String(options.filesystemConfinement)}'. Valid value: memory.`);
|
|
9202
|
+
}
|
|
9319
9203
|
const effectiveEnvironment = options.environment ?? this.environment;
|
|
9320
9204
|
if (this.backend === "local") {
|
|
9321
9205
|
if (effectiveEnvironment !== undefined) {
|
|
@@ -9327,12 +9211,18 @@ class LettaAgentClientBase {
|
|
|
9327
9211
|
if (hasRepositoryResources(options)) {
|
|
9328
9212
|
throw new Error(`${action}() repository resources are only valid with backend: "cloud".`);
|
|
9329
9213
|
}
|
|
9330
|
-
if (
|
|
9331
|
-
|
|
9214
|
+
if (options.filesystemConfinement !== undefined) {
|
|
9215
|
+
const localOptions = this.options;
|
|
9216
|
+
if (localOptions.appServer?.url !== undefined) {
|
|
9217
|
+
throw new Error(`${action}() filesystemConfinement requires an SDK-owned local app-server process.`);
|
|
9218
|
+
}
|
|
9332
9219
|
}
|
|
9333
9220
|
return;
|
|
9334
9221
|
}
|
|
9335
9222
|
if (this.backend === "remote") {
|
|
9223
|
+
if (options.filesystemConfinement !== undefined) {
|
|
9224
|
+
throw new Error(`${action}() filesystemConfinement requires an SDK-owned local app-server process.`);
|
|
9225
|
+
}
|
|
9336
9226
|
if (options.environment !== undefined) {
|
|
9337
9227
|
throw new Error(`${action}() environment overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
|
|
9338
9228
|
}
|
|
@@ -9342,10 +9232,12 @@ class LettaAgentClientBase {
|
|
|
9342
9232
|
if (hasRepositoryResources(options)) {
|
|
9343
9233
|
throw new Error(`${action}() repository resources are only valid with backend: "cloud".`);
|
|
9344
9234
|
}
|
|
9345
|
-
assertRemoteSessionOptionsSupported(action, options);
|
|
9346
9235
|
return;
|
|
9347
9236
|
}
|
|
9348
9237
|
if (this.backend === "cloud") {
|
|
9238
|
+
if (options.filesystemConfinement !== undefined) {
|
|
9239
|
+
throw new Error(`${action}() filesystemConfinement is only supported with backend: "local".`);
|
|
9240
|
+
}
|
|
9349
9241
|
const cloudOptions = this.cloudOptions();
|
|
9350
9242
|
if (cloudOptions.environment !== undefined && options.sandbox !== undefined) {
|
|
9351
9243
|
throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default environment.`);
|
|
@@ -9358,16 +9250,13 @@ class LettaAgentClientBase {
|
|
|
9358
9250
|
}
|
|
9359
9251
|
throw new Error(`LettaAgentClient backend '${this.backend}' is not implemented yet. ${action} currently supports backend 'local' only.`);
|
|
9360
9252
|
}
|
|
9361
|
-
useLegacyLocalStdio() {
|
|
9362
|
-
return this.options.transport === "stdio";
|
|
9363
|
-
}
|
|
9364
9253
|
createLocalAgent(_options) {
|
|
9365
9254
|
throw this.localBackendUnavailableError();
|
|
9366
9255
|
}
|
|
9367
|
-
createLocalSession(_agentId, _options
|
|
9256
|
+
createLocalSession(_agentId, _options) {
|
|
9368
9257
|
throw this.localBackendUnavailableError();
|
|
9369
9258
|
}
|
|
9370
|
-
resumeLocalSession(_id, _options
|
|
9259
|
+
resumeLocalSession(_id, _options) {
|
|
9371
9260
|
throw this.localBackendUnavailableError();
|
|
9372
9261
|
}
|
|
9373
9262
|
localBackendUnavailableError() {
|
|
@@ -9383,10 +9272,7 @@ class LettaAgentClientBase {
|
|
|
9383
9272
|
return this.options;
|
|
9384
9273
|
}
|
|
9385
9274
|
appServerSessionOptions() {
|
|
9386
|
-
return
|
|
9387
|
-
...this.remoteOptions(),
|
|
9388
|
-
beforeConnect: () => this.releaseIdleManagementConnection()
|
|
9389
|
-
};
|
|
9275
|
+
return this.remoteOptions();
|
|
9390
9276
|
}
|
|
9391
9277
|
getRepositoriesClient() {
|
|
9392
9278
|
if (this.backend !== "cloud") {
|
|
@@ -9395,9 +9281,6 @@ class LettaAgentClientBase {
|
|
|
9395
9281
|
this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
|
|
9396
9282
|
return this.repositoriesClient;
|
|
9397
9283
|
}
|
|
9398
|
-
async releaseIdleManagementConnection() {
|
|
9399
|
-
await this.managementTransport?.releaseIdleConnection?.();
|
|
9400
|
-
}
|
|
9401
9284
|
getManagementTransport() {
|
|
9402
9285
|
if (this.managementTransport)
|
|
9403
9286
|
return this.managementTransport;
|
|
@@ -9420,32 +9303,373 @@ class LettaAgentClientBase {
|
|
|
9420
9303
|
|
|
9421
9304
|
// src/local-app-server.ts
|
|
9422
9305
|
import { spawn } from "node:child_process";
|
|
9306
|
+
import { homedir as homedir4 } from "node:os";
|
|
9307
|
+
import { join as join7 } from "node:path";
|
|
9308
|
+
|
|
9309
|
+
// node_modules/@letta-ai/letta-code/dist/memory-confinement.js
|
|
9310
|
+
import { homedir as homedir3 } from "node:os";
|
|
9311
|
+
import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
|
|
9312
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
9313
|
+
import { homedir as homedir2 } from "node:os";
|
|
9314
|
+
import { basename, dirname, isAbsolute, join as join2, resolve } from "node:path";
|
|
9315
|
+
import { posix, win32 } from "node:path";
|
|
9316
|
+
import { homedir } from "node:os";
|
|
9317
|
+
import { join } from "node:path";
|
|
9318
|
+
import { spawnSync } from "node:child_process";
|
|
9319
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
9320
|
+
import { delimiter, isAbsolute as isAbsolute2, join as join4 } from "node:path";
|
|
9321
|
+
var SANDBOX_ENV_VAR = "LETTA_SANDBOX";
|
|
9322
|
+
function buildFsSandboxPolicy(options) {
|
|
9323
|
+
return {
|
|
9324
|
+
baseWritableRoots: normalizeRoots(options.baseWritableRoots ?? []),
|
|
9325
|
+
deniedRoots: normalizeRoots(options.deniedRoots ?? []),
|
|
9326
|
+
readonlyRoots: normalizeRoots(options.readonlyRoots ?? []),
|
|
9327
|
+
writableRoots: normalizeRoots(options.writableRoots ?? []),
|
|
9328
|
+
restrictWrites: options.restrictWrites ?? false
|
|
9329
|
+
};
|
|
9330
|
+
}
|
|
9331
|
+
function normalizeSandboxPath(path) {
|
|
9332
|
+
const trimmed = path.trim();
|
|
9333
|
+
const absolute = posix.isAbsolute(trimmed) || win32.isAbsolute(trimmed) ? trimmed : posix.resolve("/", trimmed);
|
|
9334
|
+
const forward = absolute.replace(/\\/g, "/");
|
|
9335
|
+
return forward.replace(/\/+$/, "") || "/";
|
|
9336
|
+
}
|
|
9337
|
+
function normalizeRoots(roots) {
|
|
9338
|
+
const seen = new Set;
|
|
9339
|
+
for (const root of roots) {
|
|
9340
|
+
if (!root || !root.trim())
|
|
9341
|
+
continue;
|
|
9342
|
+
seen.add(normalizeSandboxPath(root));
|
|
9343
|
+
}
|
|
9344
|
+
return [...seen];
|
|
9345
|
+
}
|
|
9346
|
+
var LOCAL_BACKEND_DIR_ENV = "LETTA_LOCAL_BACKEND_DIR";
|
|
9347
|
+
function getLocalBackendStorageDir(homeDir = homedir(), env = process.env) {
|
|
9348
|
+
return env[LOCAL_BACKEND_DIR_ENV] ?? join(homeDir, ".letta", "lc-local-backend");
|
|
9349
|
+
}
|
|
9350
|
+
function getLocalBackendCrossAgentTreeRoot(storageDir = getLocalBackendStorageDir()) {
|
|
9351
|
+
return join(storageDir, "memfs");
|
|
9352
|
+
}
|
|
9353
|
+
function getDefaultAgentsTreeRoot(homeDir = homedir2()) {
|
|
9354
|
+
return canonicalizeRoot(join2(homeDir, ".letta", "agents"));
|
|
9355
|
+
}
|
|
9356
|
+
function getCrossBackendAgentsTreeRoots(options = {}) {
|
|
9357
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
9358
|
+
const localBackendStorageDir = options.localBackendStorageDir ?? getLocalBackendStorageDir(homeDir, options.env ?? process.env);
|
|
9359
|
+
return [
|
|
9360
|
+
getDefaultAgentsTreeRoot(homeDir),
|
|
9361
|
+
canonicalizeRoot(getLocalBackendCrossAgentTreeRoot(localBackendStorageDir))
|
|
9362
|
+
];
|
|
9363
|
+
}
|
|
9364
|
+
function getLettaHomeRoot(homeDir = homedir2()) {
|
|
9365
|
+
return canonicalizeRoot(join2(homeDir, ".letta"));
|
|
9366
|
+
}
|
|
9367
|
+
function canonicalizeRoot(input) {
|
|
9368
|
+
const abs = isAbsolute(input) ? input : resolve(input);
|
|
9369
|
+
let dir = abs;
|
|
9370
|
+
const tail = [];
|
|
9371
|
+
while (!existsSync(dir)) {
|
|
9372
|
+
tail.unshift(basename(dir));
|
|
9373
|
+
const parent = dirname(dir);
|
|
9374
|
+
if (parent === dir) {
|
|
9375
|
+
return normalizeSandboxPath(abs);
|
|
9376
|
+
}
|
|
9377
|
+
dir = parent;
|
|
9378
|
+
}
|
|
9379
|
+
try {
|
|
9380
|
+
const real = realpathSync(dir);
|
|
9381
|
+
return normalizeSandboxPath(tail.length ? join2(real, ...tail) : real);
|
|
9382
|
+
} catch {
|
|
9383
|
+
return normalizeSandboxPath(abs);
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
function isWithinRoot(path, root) {
|
|
9387
|
+
return path === root || path.startsWith(`${root}/`);
|
|
9388
|
+
}
|
|
9389
|
+
function isAncestorOfRoot(path, root) {
|
|
9390
|
+
const prefix = path === "/" ? "/" : `${path}/`;
|
|
9391
|
+
return root.startsWith(prefix);
|
|
9392
|
+
}
|
|
9393
|
+
function isTreeOrAncestorOfTree(path, canonicalTrees) {
|
|
9394
|
+
return canonicalTrees.some((tree) => path === tree || isAncestorOfRoot(path, tree));
|
|
9395
|
+
}
|
|
9396
|
+
function resolveAgentsTreeRootsInput(roots) {
|
|
9397
|
+
return roots?.length ? roots.map(canonicalizeRoot) : getCrossBackendAgentsTreeRoots();
|
|
9398
|
+
}
|
|
9399
|
+
function deriveSelfAgentRootsForTrees(memoryRoots, agentsTreeRoots = getCrossBackendAgentsTreeRoots()) {
|
|
9400
|
+
const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);
|
|
9401
|
+
const out = new Set;
|
|
9402
|
+
for (const root of memoryRoots) {
|
|
9403
|
+
const canon = canonicalizeRoot(root);
|
|
9404
|
+
const containingTree = canonicalTrees.find((tree) => canon !== tree && isWithinRoot(canon, tree));
|
|
9405
|
+
if (containingTree) {
|
|
9406
|
+
const leaf = basename(canon);
|
|
9407
|
+
const parentLeaf = basename(dirname(canon));
|
|
9408
|
+
out.add(leaf === "memory" || leaf === "memory-worktrees" ? dirname(canon) : parentLeaf === "memory-worktrees" || parentLeaf === "memory" && leaf === ".git" ? dirname(dirname(canon)) : canon);
|
|
9409
|
+
continue;
|
|
9410
|
+
}
|
|
9411
|
+
if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {
|
|
9412
|
+
out.add(canon);
|
|
9413
|
+
}
|
|
9414
|
+
}
|
|
9415
|
+
return [...out];
|
|
9416
|
+
}
|
|
9417
|
+
function deriveWritableMemoryRootsForTrees(memoryRoots, agentsTreeRoots) {
|
|
9418
|
+
const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);
|
|
9419
|
+
const out = new Set;
|
|
9420
|
+
for (const root of memoryRoots) {
|
|
9421
|
+
const canon = canonicalizeRoot(root);
|
|
9422
|
+
if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {
|
|
9423
|
+
out.add(canon);
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
return [...out];
|
|
9427
|
+
}
|
|
9428
|
+
function buildMemorySubagentSandboxPolicy(input) {
|
|
9429
|
+
const agentsTreeRoots = resolveAgentsTreeRootsInput(input.agentsTreeRoots);
|
|
9430
|
+
const baseWritableRoots = [
|
|
9431
|
+
getLettaHomeRoot(),
|
|
9432
|
+
...input.harnessWritableRoots ?? []
|
|
9433
|
+
].map(canonicalizeRoot);
|
|
9434
|
+
return buildFsSandboxPolicy({
|
|
9435
|
+
baseWritableRoots,
|
|
9436
|
+
deniedRoots: agentsTreeRoots,
|
|
9437
|
+
readonlyRoots: [
|
|
9438
|
+
...deriveSelfAgentRootsForTrees(input.memoryRoots, agentsTreeRoots),
|
|
9439
|
+
...(input.readonlyRoots ?? []).map(canonicalizeRoot)
|
|
9440
|
+
],
|
|
9441
|
+
writableRoots: deriveWritableMemoryRootsForTrees(input.memoryRoots, agentsTreeRoots),
|
|
9442
|
+
restrictWrites: true
|
|
9443
|
+
});
|
|
9444
|
+
}
|
|
9445
|
+
var BWRAP_BIN = "bwrap";
|
|
9446
|
+
function buildBwrapArgs(policy) {
|
|
9447
|
+
const args = [];
|
|
9448
|
+
args.push(policy.restrictWrites ? "--ro-bind" : "--bind", "/", "/");
|
|
9449
|
+
args.push("--dev", "/dev");
|
|
9450
|
+
args.push("--proc", "/proc");
|
|
9451
|
+
for (const root of policy.baseWritableRoots) {
|
|
9452
|
+
args.push("--bind-try", root, root);
|
|
9453
|
+
}
|
|
9454
|
+
for (const root of policy.deniedRoots) {
|
|
9455
|
+
args.push("--tmpfs", root);
|
|
9456
|
+
}
|
|
9457
|
+
for (const root of policy.readonlyRoots) {
|
|
9458
|
+
args.push("--ro-bind-try", root, root);
|
|
9459
|
+
}
|
|
9460
|
+
for (const root of policy.writableRoots) {
|
|
9461
|
+
args.push("--bind-try", root, root);
|
|
9462
|
+
}
|
|
9463
|
+
args.push("--die-with-parent");
|
|
9464
|
+
return args;
|
|
9465
|
+
}
|
|
9466
|
+
var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
|
|
9467
|
+
function buildSeatbeltProfile(policy) {
|
|
9468
|
+
const defines = [];
|
|
9469
|
+
const lines = ["(version 1)", "(allow default)"];
|
|
9470
|
+
if (policy.restrictWrites) {
|
|
9471
|
+
lines.push('(deny file-write* (subpath "/"))');
|
|
9472
|
+
lines.push('(allow file-write* (subpath "/dev"))');
|
|
9473
|
+
}
|
|
9474
|
+
policy.baseWritableRoots.forEach((root, i) => {
|
|
9475
|
+
const name = `BASEWRITABLE_${i}`;
|
|
9476
|
+
defines.push({ name, value: root });
|
|
9477
|
+
lines.push(`(allow file-write* (subpath (param "${name}")))`);
|
|
9478
|
+
});
|
|
9479
|
+
policy.deniedRoots.forEach((root, i) => {
|
|
9480
|
+
const name = `DENIED_${i}`;
|
|
9481
|
+
defines.push({ name, value: root });
|
|
9482
|
+
lines.push(`(deny file-read* file-write* (subpath (param "${name}")))`);
|
|
9483
|
+
lines.push(`(allow file-read-metadata (literal (param "${name}")))`);
|
|
9484
|
+
});
|
|
9485
|
+
policy.writableRoots.forEach((root, i) => {
|
|
9486
|
+
const name = `WRITABLE_${i}`;
|
|
9487
|
+
defines.push({ name, value: root });
|
|
9488
|
+
lines.push(`(allow file-read* file-write* (subpath (param "${name}")))`);
|
|
9489
|
+
});
|
|
9490
|
+
policy.readonlyRoots.forEach((root, i) => {
|
|
9491
|
+
const name = `READONLY_${i}`;
|
|
9492
|
+
defines.push({ name, value: root });
|
|
9493
|
+
lines.push(`(allow file-read* (subpath (param "${name}")))`);
|
|
9494
|
+
});
|
|
9495
|
+
return { profile: `${lines.join(`
|
|
9496
|
+
`)}
|
|
9497
|
+
`, defines };
|
|
9498
|
+
}
|
|
9499
|
+
function buildSeatbeltArgs(policy) {
|
|
9500
|
+
const { profile, defines } = buildSeatbeltProfile(policy);
|
|
9501
|
+
const args = ["-p", profile];
|
|
9502
|
+
for (const { name, value } of defines) {
|
|
9503
|
+
args.push(`-D${name}=${value}`);
|
|
9504
|
+
}
|
|
9505
|
+
return args;
|
|
9506
|
+
}
|
|
9507
|
+
function wrapLauncher(launcher, policy, options) {
|
|
9508
|
+
if (!options.backend)
|
|
9509
|
+
return null;
|
|
9510
|
+
if (launcher.length === 0)
|
|
9511
|
+
return null;
|
|
9512
|
+
switch (options.backend) {
|
|
9513
|
+
case "seatbelt":
|
|
9514
|
+
return [
|
|
9515
|
+
SANDBOX_EXEC_PATH,
|
|
9516
|
+
...buildSeatbeltArgs(policy),
|
|
9517
|
+
"--",
|
|
9518
|
+
...launcher
|
|
9519
|
+
];
|
|
9520
|
+
case "bwrap":
|
|
9521
|
+
return [
|
|
9522
|
+
options.bwrapPath ?? BWRAP_BIN,
|
|
9523
|
+
...buildBwrapArgs(policy),
|
|
9524
|
+
"--",
|
|
9525
|
+
...launcher
|
|
9526
|
+
];
|
|
9527
|
+
}
|
|
9528
|
+
}
|
|
9529
|
+
function normalizeRoot(path) {
|
|
9530
|
+
const trimmed = path.trim();
|
|
9531
|
+
const expanded = trimmed.startsWith("~/") ? join3(homedir3(), trimmed.slice(2)) : trimmed.startsWith("$HOME/") ? join3(homedir3(), trimmed.slice(6)) : trimmed;
|
|
9532
|
+
return resolve2(expanded);
|
|
9533
|
+
}
|
|
9534
|
+
function resolveWritableMemoryRoots(env) {
|
|
9535
|
+
const roots = new Set;
|
|
9536
|
+
for (const value of [env.MEMORY_DIR, env.LETTA_MEMORY_DIR]) {
|
|
9537
|
+
if (!value?.trim())
|
|
9538
|
+
continue;
|
|
9539
|
+
const root = normalizeRoot(value);
|
|
9540
|
+
roots.add(root);
|
|
9541
|
+
if (basename2(root) === "memory") {
|
|
9542
|
+
roots.add(join3(dirname2(root), "memory-worktrees"));
|
|
9543
|
+
}
|
|
9544
|
+
}
|
|
9545
|
+
return [...roots];
|
|
9546
|
+
}
|
|
9547
|
+
function customHarnessWritableRoots(env) {
|
|
9548
|
+
return [env.LETTA_LOCAL_BACKEND_DIR, env.LETTA_TRANSCRIPT_ROOT].filter((value) => Boolean(value?.trim())).map(normalizeRoot);
|
|
9549
|
+
}
|
|
9550
|
+
function createMemoryConfinementLauncherWithAvailability(input, availability) {
|
|
9551
|
+
if (input.launcher.length === 0) {
|
|
9552
|
+
throw new Error("Memory confinement requires a non-empty launcher.");
|
|
9553
|
+
}
|
|
9554
|
+
const memoryRoots = resolveWritableMemoryRoots(input.env);
|
|
9555
|
+
if (memoryRoots.length === 0) {
|
|
9556
|
+
throw new Error("Memory confinement requires MEMORY_DIR or LETTA_MEMORY_DIR.");
|
|
9557
|
+
}
|
|
9558
|
+
if (!availability.backend) {
|
|
9559
|
+
throw new Error(`Memory confinement is unavailable: ${availability.reason}.`);
|
|
9560
|
+
}
|
|
9561
|
+
const localBackendStorageDir = input.env.LETTA_LOCAL_BACKEND_DIR?.trim() || undefined;
|
|
9562
|
+
const policy = buildMemorySubagentSandboxPolicy({
|
|
9563
|
+
memoryRoots,
|
|
9564
|
+
agentsTreeRoots: getCrossBackendAgentsTreeRoots({
|
|
9565
|
+
env: input.env,
|
|
9566
|
+
localBackendStorageDir
|
|
9567
|
+
}),
|
|
9568
|
+
harnessWritableRoots: customHarnessWritableRoots(input.env)
|
|
9569
|
+
});
|
|
9570
|
+
const launcher = wrapLauncher(input.launcher, policy, {
|
|
9571
|
+
backend: availability.backend,
|
|
9572
|
+
bwrapPath: availability.bwrapPath
|
|
9573
|
+
});
|
|
9574
|
+
if (!launcher) {
|
|
9575
|
+
throw new Error("Memory confinement could not wrap the launcher.");
|
|
9576
|
+
}
|
|
9577
|
+
return {
|
|
9578
|
+
launcher,
|
|
9579
|
+
env: { ...input.env, [SANDBOX_ENV_VAR]: availability.backend },
|
|
9580
|
+
backend: availability.backend
|
|
9581
|
+
};
|
|
9582
|
+
}
|
|
9583
|
+
var cached = null;
|
|
9584
|
+
var warnedUnavailableContexts = new Set;
|
|
9585
|
+
function detectSandboxBackend(options = {}) {
|
|
9586
|
+
const platform = options.platform ?? process.platform;
|
|
9587
|
+
if (!options.force && cached && !options.platform) {
|
|
9588
|
+
return cached;
|
|
9589
|
+
}
|
|
9590
|
+
const result = probe(platform);
|
|
9591
|
+
if (!options.platform) {
|
|
9592
|
+
cached = result;
|
|
9593
|
+
}
|
|
9594
|
+
return result;
|
|
9595
|
+
}
|
|
9596
|
+
function probe(platform) {
|
|
9597
|
+
if (platform === "darwin") {
|
|
9598
|
+
if (existsSync2(SANDBOX_EXEC_PATH)) {
|
|
9599
|
+
return { backend: "seatbelt", reason: "sandbox-exec available" };
|
|
9600
|
+
}
|
|
9601
|
+
return {
|
|
9602
|
+
backend: null,
|
|
9603
|
+
reason: `${SANDBOX_EXEC_PATH} not found`
|
|
9604
|
+
};
|
|
9605
|
+
}
|
|
9606
|
+
if (platform === "linux") {
|
|
9607
|
+
return probeBwrap();
|
|
9608
|
+
}
|
|
9609
|
+
return {
|
|
9610
|
+
backend: null,
|
|
9611
|
+
reason: `no filesystem sandbox backend for platform "${platform}"`
|
|
9612
|
+
};
|
|
9613
|
+
}
|
|
9614
|
+
function probeBwrap() {
|
|
9615
|
+
const bwrapPath = resolveExecutableOnPath("bwrap");
|
|
9616
|
+
if (!bwrapPath) {
|
|
9617
|
+
return { backend: null, reason: "bwrap not found on PATH" };
|
|
9618
|
+
}
|
|
9619
|
+
const version = spawnSync(bwrapPath, ["--version"], { timeout: 5000 });
|
|
9620
|
+
if (version.error || version.status !== 0) {
|
|
9621
|
+
return { backend: null, reason: "bwrap not found on PATH" };
|
|
9622
|
+
}
|
|
9623
|
+
const userns = spawnSync(bwrapPath, ["--ro-bind", "/", "/", "--unshare-user", "/bin/true"], { timeout: 5000 });
|
|
9624
|
+
if (userns.error || userns.status !== 0) {
|
|
9625
|
+
return {
|
|
9626
|
+
backend: null,
|
|
9627
|
+
reason: "bwrap present but user namespaces are unavailable"
|
|
9628
|
+
};
|
|
9629
|
+
}
|
|
9630
|
+
return { backend: "bwrap", bwrapPath, reason: "bwrap available" };
|
|
9631
|
+
}
|
|
9632
|
+
function resolveExecutableOnPath(executable, envPath = process.env.PATH) {
|
|
9633
|
+
if (isAbsolute2(executable) && existsSync2(executable))
|
|
9634
|
+
return executable;
|
|
9635
|
+
for (const dir of (envPath ?? "").split(delimiter)) {
|
|
9636
|
+
if (!dir)
|
|
9637
|
+
continue;
|
|
9638
|
+
const candidate = join4(dir, executable);
|
|
9639
|
+
if (existsSync2(candidate))
|
|
9640
|
+
return candidate;
|
|
9641
|
+
}
|
|
9642
|
+
return null;
|
|
9643
|
+
}
|
|
9644
|
+
function createMemoryConfinementLauncher(input) {
|
|
9645
|
+
return createMemoryConfinementLauncherWithAvailability(input, detectSandboxBackend());
|
|
9646
|
+
}
|
|
9423
9647
|
|
|
9424
9648
|
// src/cli-resolver.ts
|
|
9425
|
-
import { existsSync } from "node:fs";
|
|
9426
|
-
import { createRequire
|
|
9427
|
-
import { dirname, join } from "node:path";
|
|
9649
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
9650
|
+
import { createRequire } from "node:module";
|
|
9651
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
9428
9652
|
import { fileURLToPath } from "node:url";
|
|
9429
|
-
var require2 =
|
|
9653
|
+
var require2 = createRequire(import.meta.url);
|
|
9430
9654
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
9431
|
-
var __dirname2 =
|
|
9655
|
+
var __dirname2 = dirname3(__filename2);
|
|
9432
9656
|
function findLettaCli() {
|
|
9433
9657
|
const envPath = process.env.LETTA_CLI_PATH;
|
|
9434
|
-
if (envPath &&
|
|
9658
|
+
if (envPath && existsSync3(envPath)) {
|
|
9435
9659
|
return envPath;
|
|
9436
9660
|
}
|
|
9437
9661
|
try {
|
|
9438
9662
|
return require2.resolve("@letta-ai/letta-code");
|
|
9439
9663
|
} catch {}
|
|
9440
9664
|
const localPaths = [
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9665
|
+
join5(__dirname2, "..", "node_modules", "@letta-ai", "letta-code", "letta.js"),
|
|
9666
|
+
join5(__dirname2, "../../@letta-ai/letta-code/letta.js"),
|
|
9667
|
+
join5(__dirname2, "../../../letta-code-prod/letta.js"),
|
|
9668
|
+
join5(__dirname2, "../../../letta-code/letta.js"),
|
|
9669
|
+
join5(__dirname2, "..", "..", "letta-code", "letta.js")
|
|
9446
9670
|
];
|
|
9447
9671
|
for (const path of localPaths) {
|
|
9448
|
-
if (
|
|
9672
|
+
if (existsSync3(path)) {
|
|
9449
9673
|
return path;
|
|
9450
9674
|
}
|
|
9451
9675
|
}
|
|
@@ -9456,6 +9680,38 @@ function findLettaCli() {
|
|
|
9456
9680
|
var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0";
|
|
9457
9681
|
var DEFAULT_STARTUP_TIMEOUT_MS = 30000;
|
|
9458
9682
|
var LISTENING_RE = /^Listening on\s+(ws:\/\/\S+)\s*$/m;
|
|
9683
|
+
function targetHomeDirectory(env) {
|
|
9684
|
+
if (process.platform === "win32") {
|
|
9685
|
+
const profile = env.USERPROFILE?.trim();
|
|
9686
|
+
if (profile)
|
|
9687
|
+
return profile;
|
|
9688
|
+
const drive = env.HOMEDRIVE?.trim();
|
|
9689
|
+
const path = env.HOMEPATH?.trim();
|
|
9690
|
+
if (drive && path)
|
|
9691
|
+
return `${drive}${path}`;
|
|
9692
|
+
return homedir4();
|
|
9693
|
+
}
|
|
9694
|
+
return env.HOME?.trim() || homedir4();
|
|
9695
|
+
}
|
|
9696
|
+
function withDefaultMemoryDirectory(env, options) {
|
|
9697
|
+
const explicitMemoryDir = options.env?.MEMORY_DIR?.trim();
|
|
9698
|
+
const explicitLettaMemoryDir = options.env?.LETTA_MEMORY_DIR?.trim();
|
|
9699
|
+
const scopedEnv = { ...env };
|
|
9700
|
+
if (explicitMemoryDir || explicitLettaMemoryDir) {
|
|
9701
|
+
if (!explicitMemoryDir)
|
|
9702
|
+
delete scopedEnv.MEMORY_DIR;
|
|
9703
|
+
if (!explicitLettaMemoryDir)
|
|
9704
|
+
delete scopedEnv.LETTA_MEMORY_DIR;
|
|
9705
|
+
return scopedEnv;
|
|
9706
|
+
}
|
|
9707
|
+
delete scopedEnv.MEMORY_DIR;
|
|
9708
|
+
delete scopedEnv.LETTA_MEMORY_DIR;
|
|
9709
|
+
if (!options.agentId)
|
|
9710
|
+
return scopedEnv;
|
|
9711
|
+
const homeDir = targetHomeDirectory(env);
|
|
9712
|
+
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");
|
|
9713
|
+
return { ...scopedEnv, MEMORY_DIR: memoryDir };
|
|
9714
|
+
}
|
|
9459
9715
|
function appendLine(buffer, chunk) {
|
|
9460
9716
|
return buffer + String(chunk);
|
|
9461
9717
|
}
|
|
@@ -9472,6 +9728,29 @@ function buildLocalAppServerArgs(cliPath, options = {}) {
|
|
|
9472
9728
|
options.listen ?? DEFAULT_LISTEN_URL
|
|
9473
9729
|
];
|
|
9474
9730
|
}
|
|
9731
|
+
function buildLocalAppServerProcess(cliPath, options = {}, confineMemory = createMemoryConfinementLauncher) {
|
|
9732
|
+
const env = { ...process.env, ...options.env ?? {} };
|
|
9733
|
+
const launcher = [
|
|
9734
|
+
process.execPath,
|
|
9735
|
+
...buildLocalAppServerArgs(cliPath, options)
|
|
9736
|
+
];
|
|
9737
|
+
if (options.filesystemConfinement !== "memory") {
|
|
9738
|
+
return {
|
|
9739
|
+
command: launcher[0],
|
|
9740
|
+
args: launcher.slice(1),
|
|
9741
|
+
env
|
|
9742
|
+
};
|
|
9743
|
+
}
|
|
9744
|
+
const confined = confineMemory({
|
|
9745
|
+
launcher,
|
|
9746
|
+
env: withDefaultMemoryDirectory(env, options)
|
|
9747
|
+
});
|
|
9748
|
+
return {
|
|
9749
|
+
command: confined.launcher[0],
|
|
9750
|
+
args: confined.launcher.slice(1),
|
|
9751
|
+
env: confined.env
|
|
9752
|
+
};
|
|
9753
|
+
}
|
|
9475
9754
|
function terminateProcess(child) {
|
|
9476
9755
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
9477
9756
|
return;
|
|
@@ -9484,12 +9763,12 @@ function terminateProcess(child) {
|
|
|
9484
9763
|
}
|
|
9485
9764
|
function startLocalAppServer(options = {}) {
|
|
9486
9765
|
const cliPath = options.cliPath ?? findLettaCli();
|
|
9487
|
-
const
|
|
9766
|
+
const processSpec = buildLocalAppServerProcess(cliPath, options);
|
|
9488
9767
|
const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
9489
|
-
return new Promise((
|
|
9490
|
-
const child = spawn(
|
|
9768
|
+
return new Promise((resolve3, reject) => {
|
|
9769
|
+
const child = spawn(processSpec.command, processSpec.args, {
|
|
9491
9770
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9492
|
-
env:
|
|
9771
|
+
env: processSpec.env
|
|
9493
9772
|
});
|
|
9494
9773
|
let settled = false;
|
|
9495
9774
|
let output = "";
|
|
@@ -9513,7 +9792,7 @@ function startLocalAppServer(options = {}) {
|
|
|
9513
9792
|
return;
|
|
9514
9793
|
settled = true;
|
|
9515
9794
|
cleanup();
|
|
9516
|
-
|
|
9795
|
+
resolve3({
|
|
9517
9796
|
url,
|
|
9518
9797
|
close: () => terminateProcess(child)
|
|
9519
9798
|
});
|
|
@@ -9547,7 +9826,7 @@ ${output.trim()}` : ""}`));
|
|
|
9547
9826
|
}
|
|
9548
9827
|
|
|
9549
9828
|
// src/local-app-server-session.ts
|
|
9550
|
-
function createLocalAppServerSession(options, mode
|
|
9829
|
+
function createLocalAppServerSession(options, mode) {
|
|
9551
9830
|
const appServer = options ?? {};
|
|
9552
9831
|
const sessionOptions = {
|
|
9553
9832
|
...appServer.url !== undefined ? { url: appServer.url } : {
|
|
@@ -9555,1540 +9834,142 @@ function createLocalAppServerSession(options, mode, beforeConnect) {
|
|
|
9555
9834
|
listen: appServer.listen,
|
|
9556
9835
|
backend: appServer.harnessBackend ?? "local",
|
|
9557
9836
|
startupTimeoutMs: appServer.startupTimeoutMs,
|
|
9558
|
-
env: sessionEnv
|
|
9837
|
+
env: sessionEnv,
|
|
9838
|
+
filesystemConfinement: mode.kind === "session" ? mode.options.filesystemConfinement : undefined,
|
|
9839
|
+
agentId: mode.kind === "session" && "agentId" in mode ? mode.agentId : undefined
|
|
9559
9840
|
})
|
|
9560
9841
|
},
|
|
9561
9842
|
...appServer.WebSocket !== undefined ? { WebSocket: appServer.WebSocket } : {},
|
|
9562
9843
|
...appServer.requestTimeoutMs !== undefined ? { requestTimeoutMs: appServer.requestTimeoutMs } : {},
|
|
9563
|
-
...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {}
|
|
9564
|
-
...beforeConnect ? { beforeConnect } : {}
|
|
9844
|
+
...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {}
|
|
9565
9845
|
};
|
|
9566
9846
|
return new AppServerSession(sessionOptions, mode);
|
|
9567
9847
|
}
|
|
9568
9848
|
|
|
9569
|
-
// src/
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9849
|
+
// src/client.ts
|
|
9850
|
+
class LettaAgentClient extends LettaAgentClientBase {
|
|
9851
|
+
createLocalManagementTransport() {
|
|
9852
|
+
const localOptions = this.options.appServer;
|
|
9853
|
+
return new AppServerManagementTransport({
|
|
9854
|
+
...localOptions?.url !== undefined ? { url: localOptions.url } : {
|
|
9855
|
+
connect: () => startLocalAppServer({
|
|
9856
|
+
listen: localOptions?.listen,
|
|
9857
|
+
backend: localOptions?.harnessBackend ?? "local",
|
|
9858
|
+
startupTimeoutMs: localOptions?.startupTimeoutMs
|
|
9859
|
+
})
|
|
9860
|
+
},
|
|
9861
|
+
...localOptions?.WebSocket !== undefined ? { WebSocket: localOptions.WebSocket } : {},
|
|
9862
|
+
...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {}
|
|
9863
|
+
});
|
|
9864
|
+
}
|
|
9865
|
+
async createLocalAgent(options) {
|
|
9866
|
+
const localOptions = this.options;
|
|
9867
|
+
const session = createLocalAppServerSession(localOptions.appServer, {
|
|
9868
|
+
kind: "create-agent",
|
|
9869
|
+
options
|
|
9870
|
+
});
|
|
9871
|
+
const initMsg = await session.initialize();
|
|
9872
|
+
session.close();
|
|
9873
|
+
return initMsg.agentId;
|
|
9874
|
+
}
|
|
9875
|
+
createLocalSession(agentId, options) {
|
|
9876
|
+
const localOptions = this.options;
|
|
9877
|
+
return createLocalAppServerSession(localOptions.appServer, {
|
|
9878
|
+
kind: "session",
|
|
9879
|
+
agentId,
|
|
9880
|
+
newConversation: true,
|
|
9881
|
+
options
|
|
9882
|
+
});
|
|
9883
|
+
}
|
|
9884
|
+
resumeLocalSession(id, options) {
|
|
9885
|
+
const localOptions = this.options;
|
|
9886
|
+
if (looksLikeConversationId2(id)) {
|
|
9887
|
+
return createLocalAppServerSession(localOptions.appServer, {
|
|
9888
|
+
kind: "session",
|
|
9889
|
+
conversationId: id,
|
|
9890
|
+
options
|
|
9891
|
+
});
|
|
9892
|
+
}
|
|
9893
|
+
return createLocalAppServerSession(localOptions.appServer, {
|
|
9894
|
+
kind: "session",
|
|
9895
|
+
agentId: id,
|
|
9896
|
+
defaultConversation: true,
|
|
9897
|
+
options
|
|
9898
|
+
});
|
|
9899
|
+
}
|
|
9575
9900
|
}
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
return options.createOnly === true;
|
|
9901
|
+
function looksLikeConversationId2(id) {
|
|
9902
|
+
return id.startsWith("conv-") || id.startsWith("local-conv-");
|
|
9579
9903
|
}
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
if (hasOriginTag)
|
|
9586
|
-
continue;
|
|
9587
|
-
hasOriginTag = true;
|
|
9904
|
+
// src/websocket.ts
|
|
9905
|
+
function createReactNativeWebSocketConstructor(WebSocket) {
|
|
9906
|
+
class ReactNativeWebSocketAdapter {
|
|
9907
|
+
constructor(url, options) {
|
|
9908
|
+
return new WebSocket(url, undefined, options);
|
|
9588
9909
|
}
|
|
9589
|
-
normalizedTags.push(tag);
|
|
9590
|
-
}
|
|
9591
|
-
if (!hasOriginTag) {
|
|
9592
|
-
normalizedTags.push(SDK_AGENT_ORIGIN_TAG);
|
|
9593
9910
|
}
|
|
9594
|
-
return
|
|
9911
|
+
return ReactNativeWebSocketAdapter;
|
|
9595
9912
|
}
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
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
|
-
}
|
|
9913
|
+
// src/stream-events.ts
|
|
9914
|
+
function extractTextFromContent2(content) {
|
|
9915
|
+
if (typeof content === "string") {
|
|
9916
|
+
return content;
|
|
9649
9917
|
}
|
|
9650
|
-
if (
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
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
|
-
}
|
|
9918
|
+
if (Array.isArray(content)) {
|
|
9919
|
+
const pieces = [];
|
|
9920
|
+
for (const part of content) {
|
|
9921
|
+
if (typeof part === "string") {
|
|
9922
|
+
pieces.push(part);
|
|
9923
|
+
continue;
|
|
9664
9924
|
}
|
|
9665
|
-
if (
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
args.push("--init-blocks", presetNames.join(","));
|
|
9925
|
+
if (!part || typeof part !== "object") {
|
|
9926
|
+
continue;
|
|
9927
|
+
}
|
|
9928
|
+
const rec = part;
|
|
9929
|
+
if (typeof rec.text === "string") {
|
|
9930
|
+
pieces.push(rec.text);
|
|
9672
9931
|
}
|
|
9673
9932
|
}
|
|
9933
|
+
const joined = pieces.join("");
|
|
9934
|
+
return joined.length > 0 ? joined : null;
|
|
9674
9935
|
}
|
|
9675
|
-
if (
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
if (options.human !== undefined) {
|
|
9680
|
-
args.push("--block-value", `human=${options.human}`);
|
|
9936
|
+
if (content && typeof content === "object") {
|
|
9937
|
+
const rec = content;
|
|
9938
|
+
if (typeof rec.text === "string") {
|
|
9939
|
+
return rec.text;
|
|
9681
9940
|
}
|
|
9682
9941
|
}
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
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(","));
|
|
9942
|
+
return null;
|
|
9943
|
+
}
|
|
9944
|
+
function extractStreamTextDelta(event) {
|
|
9945
|
+
if (!event || typeof event !== "object") {
|
|
9946
|
+
return null;
|
|
9696
9947
|
}
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9948
|
+
const rec = event;
|
|
9949
|
+
const maybeDelta = rec.delta;
|
|
9950
|
+
if (maybeDelta && typeof maybeDelta === "object") {
|
|
9951
|
+
const delta = maybeDelta;
|
|
9952
|
+
if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
|
|
9953
|
+
return { kind: "reasoning", text: delta.reasoning };
|
|
9701
9954
|
}
|
|
9702
|
-
|
|
9703
|
-
|
|
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(","));
|
|
9955
|
+
if (typeof delta.text === "string" && delta.text.length > 0) {
|
|
9956
|
+
return { kind: "assistant", text: delta.text };
|
|
9709
9957
|
}
|
|
9710
9958
|
}
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
if (options.dreaming?.behavior !== undefined) {
|
|
9718
|
-
args.push("--reflection-behavior", options.dreaming.behavior);
|
|
9959
|
+
const messageType = rec.message_type;
|
|
9960
|
+
if (messageType === "reasoning_message") {
|
|
9961
|
+
const reasoningText = typeof rec.reasoning === "string" ? rec.reasoning : extractTextFromContent2(rec.content);
|
|
9962
|
+
if (reasoningText && reasoningText.length > 0) {
|
|
9963
|
+
return { kind: "reasoning", text: reasoningText };
|
|
9964
|
+
}
|
|
9719
9965
|
}
|
|
9720
|
-
if (
|
|
9721
|
-
|
|
9966
|
+
if (messageType === "assistant_message") {
|
|
9967
|
+
const assistantText = extractTextFromContent2(rec.content);
|
|
9968
|
+
if (assistantText && assistantText.length > 0) {
|
|
9969
|
+
return { kind: "assistant", text: assistantText };
|
|
9970
|
+
}
|
|
9722
9971
|
}
|
|
9723
|
-
return
|
|
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
|
-
// src/client.ts
|
|
10943
|
-
class LettaAgentClient extends LettaAgentClientBase {
|
|
10944
|
-
createLocalManagementTransport() {
|
|
10945
|
-
if (this.useLegacyLocalStdio()) {
|
|
10946
|
-
throw new Error('client.agents and client.conversations require the local "app-server" transport.');
|
|
10947
|
-
}
|
|
10948
|
-
const localOptions = this.options.appServer;
|
|
10949
|
-
return new AppServerManagementTransport({
|
|
10950
|
-
...localOptions?.url !== undefined ? { url: localOptions.url } : {
|
|
10951
|
-
connect: () => startLocalAppServer({
|
|
10952
|
-
listen: localOptions?.listen,
|
|
10953
|
-
backend: localOptions?.harnessBackend ?? "local",
|
|
10954
|
-
startupTimeoutMs: localOptions?.startupTimeoutMs
|
|
10955
|
-
})
|
|
10956
|
-
},
|
|
10957
|
-
...localOptions?.WebSocket !== undefined ? { WebSocket: localOptions.WebSocket } : {},
|
|
10958
|
-
...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {},
|
|
10959
|
-
...localOptions?.idleLingerMs !== undefined ? { idleLingerMs: localOptions.idleLingerMs } : {}
|
|
10960
|
-
});
|
|
10961
|
-
}
|
|
10962
|
-
async createLocalAgent(options) {
|
|
10963
|
-
if (!this.useLegacyLocalStdio()) {
|
|
10964
|
-
const localOptions = this.options;
|
|
10965
|
-
const session2 = createLocalAppServerSession(localOptions.appServer, {
|
|
10966
|
-
kind: "create-agent",
|
|
10967
|
-
options
|
|
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 });
|
|
10974
|
-
const initMsg = await session.initialize();
|
|
10975
|
-
session.close();
|
|
10976
|
-
return initMsg.agentId;
|
|
10977
|
-
}
|
|
10978
|
-
createLocalSession(agentId, options, sessionOptions) {
|
|
10979
|
-
if (!this.useLegacyLocalStdio() && agentId) {
|
|
10980
|
-
const localOptions = this.options;
|
|
10981
|
-
return createLocalAppServerSession(localOptions.appServer, {
|
|
10982
|
-
kind: "session",
|
|
10983
|
-
agentId,
|
|
10984
|
-
newConversation: true,
|
|
10985
|
-
options
|
|
10986
|
-
}, () => this.releaseIdleManagementConnection());
|
|
10987
|
-
}
|
|
10988
|
-
if (agentId) {
|
|
10989
|
-
return new Session({ ...sessionOptions, agentId, newConversation: true });
|
|
10990
|
-
}
|
|
10991
|
-
return new Session({ ...sessionOptions, newConversation: true });
|
|
10992
|
-
}
|
|
10993
|
-
resumeLocalSession(id, options, sessionOptions) {
|
|
10994
|
-
if (!this.useLegacyLocalStdio()) {
|
|
10995
|
-
const localOptions = this.options;
|
|
10996
|
-
if (looksLikeConversationId2(id)) {
|
|
10997
|
-
return createLocalAppServerSession(localOptions.appServer, {
|
|
10998
|
-
kind: "session",
|
|
10999
|
-
conversationId: id,
|
|
11000
|
-
options
|
|
11001
|
-
}, () => this.releaseIdleManagementConnection());
|
|
11002
|
-
}
|
|
11003
|
-
return createLocalAppServerSession(localOptions.appServer, {
|
|
11004
|
-
kind: "session",
|
|
11005
|
-
agentId: id,
|
|
11006
|
-
defaultConversation: true,
|
|
11007
|
-
options
|
|
11008
|
-
}, () => this.releaseIdleManagementConnection());
|
|
11009
|
-
}
|
|
11010
|
-
if (looksLikeConversationId2(id)) {
|
|
11011
|
-
return new Session({ ...sessionOptions, conversationId: id });
|
|
11012
|
-
}
|
|
11013
|
-
return new Session({
|
|
11014
|
-
...sessionOptions,
|
|
11015
|
-
agentId: id,
|
|
11016
|
-
defaultConversation: true
|
|
11017
|
-
});
|
|
11018
|
-
}
|
|
11019
|
-
}
|
|
11020
|
-
function looksLikeConversationId2(id) {
|
|
11021
|
-
return id.startsWith("conv-") || id.startsWith("local-conv-");
|
|
11022
|
-
}
|
|
11023
|
-
// src/websocket.ts
|
|
11024
|
-
function createReactNativeWebSocketConstructor(WebSocket) {
|
|
11025
|
-
class ReactNativeWebSocketAdapter {
|
|
11026
|
-
constructor(url, options) {
|
|
11027
|
-
return new WebSocket(url, undefined, options);
|
|
11028
|
-
}
|
|
11029
|
-
}
|
|
11030
|
-
return ReactNativeWebSocketAdapter;
|
|
11031
|
-
}
|
|
11032
|
-
// src/stream-events.ts
|
|
11033
|
-
function extractTextFromContent2(content) {
|
|
11034
|
-
if (typeof content === "string") {
|
|
11035
|
-
return content;
|
|
11036
|
-
}
|
|
11037
|
-
if (Array.isArray(content)) {
|
|
11038
|
-
const pieces = [];
|
|
11039
|
-
for (const part of content) {
|
|
11040
|
-
if (typeof part === "string") {
|
|
11041
|
-
pieces.push(part);
|
|
11042
|
-
continue;
|
|
11043
|
-
}
|
|
11044
|
-
if (!part || typeof part !== "object") {
|
|
11045
|
-
continue;
|
|
11046
|
-
}
|
|
11047
|
-
const rec = part;
|
|
11048
|
-
if (typeof rec.text === "string") {
|
|
11049
|
-
pieces.push(rec.text);
|
|
11050
|
-
}
|
|
11051
|
-
}
|
|
11052
|
-
const joined = pieces.join("");
|
|
11053
|
-
return joined.length > 0 ? joined : null;
|
|
11054
|
-
}
|
|
11055
|
-
if (content && typeof content === "object") {
|
|
11056
|
-
const rec = content;
|
|
11057
|
-
if (typeof rec.text === "string") {
|
|
11058
|
-
return rec.text;
|
|
11059
|
-
}
|
|
11060
|
-
}
|
|
11061
|
-
return null;
|
|
11062
|
-
}
|
|
11063
|
-
function extractStreamTextDelta(event) {
|
|
11064
|
-
if (!event || typeof event !== "object") {
|
|
11065
|
-
return null;
|
|
11066
|
-
}
|
|
11067
|
-
const rec = event;
|
|
11068
|
-
const maybeDelta = rec.delta;
|
|
11069
|
-
if (maybeDelta && typeof maybeDelta === "object") {
|
|
11070
|
-
const delta = maybeDelta;
|
|
11071
|
-
if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
|
|
11072
|
-
return { kind: "reasoning", text: delta.reasoning };
|
|
11073
|
-
}
|
|
11074
|
-
if (typeof delta.text === "string" && delta.text.length > 0) {
|
|
11075
|
-
return { kind: "assistant", text: delta.text };
|
|
11076
|
-
}
|
|
11077
|
-
}
|
|
11078
|
-
const messageType = rec.message_type;
|
|
11079
|
-
if (messageType === "reasoning_message") {
|
|
11080
|
-
const reasoningText = typeof rec.reasoning === "string" ? rec.reasoning : extractTextFromContent2(rec.content);
|
|
11081
|
-
if (reasoningText && reasoningText.length > 0) {
|
|
11082
|
-
return { kind: "reasoning", text: reasoningText };
|
|
11083
|
-
}
|
|
11084
|
-
}
|
|
11085
|
-
if (messageType === "assistant_message") {
|
|
11086
|
-
const assistantText = extractTextFromContent2(rec.content);
|
|
11087
|
-
if (assistantText && assistantText.length > 0) {
|
|
11088
|
-
return { kind: "assistant", text: assistantText };
|
|
11089
|
-
}
|
|
11090
|
-
}
|
|
11091
|
-
return null;
|
|
9972
|
+
return null;
|
|
11092
9973
|
}
|
|
11093
9974
|
// src/tool-helpers.ts
|
|
11094
9975
|
function jsonResult(payload) {
|
|
@@ -11190,17 +10071,14 @@ async function createAgent(options = {}) {
|
|
|
11190
10071
|
}
|
|
11191
10072
|
function createSession(agentId, options = {}) {
|
|
11192
10073
|
validateCreateSessionOptions(options);
|
|
11193
|
-
|
|
11194
|
-
return new LettaAgentClient().createSession(agentId, options);
|
|
11195
|
-
}
|
|
11196
|
-
return new LettaAgentClient({ backend: "local", transport: "stdio" }).createSession(options);
|
|
10074
|
+
return new LettaAgentClient().createSession(agentId, options);
|
|
11197
10075
|
}
|
|
11198
10076
|
function resumeSession(id, options = {}) {
|
|
11199
10077
|
validateCreateSessionOptions(options);
|
|
11200
10078
|
return new LettaAgentClient().resumeSession(id, options);
|
|
11201
10079
|
}
|
|
11202
|
-
async function prompt(message, agentId) {
|
|
11203
|
-
const session =
|
|
10080
|
+
async function prompt(message, agentId, options = {}) {
|
|
10081
|
+
const session = createSession(agentId, options);
|
|
11204
10082
|
try {
|
|
11205
10083
|
return await session.runTurn(message);
|
|
11206
10084
|
} finally {
|
|
@@ -11267,10 +10145,9 @@ export {
|
|
|
11267
10145
|
createSession,
|
|
11268
10146
|
createReactNativeWebSocketConstructor,
|
|
11269
10147
|
createAgent,
|
|
11270
|
-
Session,
|
|
11271
10148
|
RepositoriesClient,
|
|
11272
10149
|
LettaAgentClient,
|
|
11273
10150
|
CloudManagedSandboxExpiredError
|
|
11274
10151
|
};
|
|
11275
10152
|
|
|
11276
|
-
//# debugId=
|
|
10153
|
+
//# debugId=3A7EA512AEF37C3B64756E2164756E21
|