@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.
Files changed (55) hide show
  1. package/AGENTS.md +0 -3
  2. package/README.md +12 -0
  3. package/dist/app-server-management.d.ts +3 -48
  4. package/dist/app-server-management.d.ts.map +1 -1
  5. package/dist/app-server-session.d.ts +0 -6
  6. package/dist/app-server-session.d.ts.map +1 -1
  7. package/dist/client-base.d.ts +7 -15
  8. package/dist/client-base.d.ts.map +1 -1
  9. package/dist/client-entry.js +369 -468
  10. package/dist/client-entry.js.map +13 -12
  11. package/dist/client.d.ts +3 -3
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/cloud-session.d.ts.map +1 -1
  14. package/dist/cloud-status-transport.d.ts +53 -0
  15. package/dist/cloud-status-transport.d.ts.map +1 -0
  16. package/dist/index.d.ts +7 -16
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +896 -2019
  19. package/dist/index.js.map +18 -18
  20. package/dist/local-app-server-session.d.ts +1 -1
  21. package/dist/local-app-server-session.d.ts.map +1 -1
  22. package/dist/local-app-server.d.ts +11 -0
  23. package/dist/local-app-server.d.ts.map +1 -1
  24. package/dist/management.d.ts +0 -7
  25. package/dist/management.d.ts.map +1 -1
  26. package/dist/remote-session-protocol.d.ts +2 -0
  27. package/dist/remote-session-protocol.d.ts.map +1 -1
  28. package/dist/remote-turn-coordinator.d.ts.map +1 -1
  29. package/dist/types.d.ts +57 -66
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/validation.d.ts.map +1 -1
  32. package/package.json +2 -2
  33. package/src/app-server-management.ts +20 -256
  34. package/src/app-server-session.ts +0 -32
  35. package/src/client-base.ts +47 -73
  36. package/src/client.ts +31 -70
  37. package/src/cloud-session.ts +9 -267
  38. package/src/cloud-status-transport.ts +305 -0
  39. package/src/index.ts +11 -29
  40. package/src/local-app-server-session.ts +13 -3
  41. package/src/local-app-server.ts +97 -3
  42. package/src/management.ts +0 -7
  43. package/src/remote-session-protocol.ts +14 -0
  44. package/src/remote-turn-coordinator.ts +20 -2
  45. package/src/types.ts +64 -111
  46. package/src/validation.ts +21 -6
  47. package/dist/protocol.d.ts +0 -205
  48. package/dist/protocol.d.ts.map +0 -1
  49. package/dist/session.d.ts +0 -155
  50. package/dist/session.d.ts.map +0 -1
  51. package/dist/transport.d.ts +0 -53
  52. package/dist/transport.d.ts.map +0 -1
  53. package/src/protocol.ts +0 -249
  54. package/src/session.ts +0 -1638
  55. 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 resolveAppServerChannelUrl(url, channel) {
278
+ function resolveAppServerUrl(url) {
285
279
  const parsed = normalizeBaseUrl(url);
286
- parsed.searchParams.set("channel", channel);
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.control = new WebSocket(resolveAppServerChannelUrl(options.url, "control"), socketOptions);
418
- this.stream = new WebSocket(resolveAppServerChannelUrl(options.url, "stream"), socketOptions);
419
- attachSocketListener(this.control, "message", (event) => {
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.stream, "message", (event) => {
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 Promise.all([
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.control.close();
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.control.send(JSON.stringify(command));
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.clearIdleTimer();
891
- this.inFlightRequests += 1;
892
- try {
893
- const { client } = await this.acquireConnection();
894
- const command = {
895
- type,
896
- request_id: client.nextRequestId(type),
897
- ...body
898
- };
899
- const response = await client.request(command, {
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 {} finally {
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
- const controlClosed = waitForSocketClose(connection.client.control);
1025
- try {
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 { type: "assistant", content, uuid, runId };
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 { type: "reasoning", content, uuid, runId };
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: createCloudStatusWebSocketConstructor({
8452
- cloudOptions: this.cloudOptions,
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
- if (options.memfsStartup !== undefined) {
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 (localOptions.transport !== undefined && localOptions.transport !== "app-server" && localOptions.transport !== "stdio") {
9192
- throw new Error("Invalid local transport. Valid values: app-server, stdio.");
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(agentIdOrOptions, options = {}) {
9245
- const agentId = typeof agentIdOrOptions === "string" ? agentIdOrOptions : undefined;
9246
- const resolvedOptions = typeof agentIdOrOptions === "string" ? options : agentIdOrOptions ?? {};
9247
- this.assertSessionBackend("createSession", resolvedOptions);
9248
- const sessionOptions = stripCloudExecutionOptions(resolvedOptions);
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: resolvedOptions
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: resolvedOptions
9150
+ options
9270
9151
  });
9271
9152
  }
9272
- return this.createLocalSession(agentId, resolvedOptions, sessionOptions);
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, sessionOptions);
9189
+ return this.resumeLocalSession(id, options);
9309
9190
  }
9310
9191
  async prompt(message, agentId, options = {}) {
9311
- const session = agentId ? this.createSession(agentId, options) : this.createSession(options);
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 (!this.useLegacyLocalStdio()) {
9331
- assertRemoteSessionOptionsSupported(action, options);
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, _sessionOptions) {
9256
+ createLocalSession(_agentId, _options) {
9368
9257
  throw this.localBackendUnavailableError();
9369
9258
  }
9370
- resumeLocalSession(_id, _options, _sessionOptions) {
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 as createRequire2 } from "node:module";
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 = createRequire2(import.meta.url);
9653
+ var require2 = createRequire(import.meta.url);
9430
9654
  var __filename2 = fileURLToPath(import.meta.url);
9431
- var __dirname2 = dirname(__filename2);
9655
+ var __dirname2 = dirname3(__filename2);
9432
9656
  function findLettaCli() {
9433
9657
  const envPath = process.env.LETTA_CLI_PATH;
9434
- if (envPath && existsSync(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
- join(__dirname2, "..", "node_modules", "@letta-ai", "letta-code", "letta.js"),
9442
- join(__dirname2, "../../@letta-ai/letta-code/letta.js"),
9443
- join(__dirname2, "../../../letta-code-prod/letta.js"),
9444
- join(__dirname2, "../../../letta-code/letta.js"),
9445
- join(__dirname2, "..", "..", "letta-code", "letta.js")
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 (existsSync(path)) {
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 args = buildLocalAppServerArgs(cliPath, options);
9766
+ const processSpec = buildLocalAppServerProcess(cliPath, options);
9488
9767
  const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
9489
- return new Promise((resolve, reject) => {
9490
- const child = spawn(process.execPath, args, {
9768
+ return new Promise((resolve3, reject) => {
9769
+ const child = spawn(processSpec.command, processSpec.args, {
9491
9770
  stdio: ["ignore", "pipe", "pipe"],
9492
- env: { ...process.env, ...options.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
- resolve({
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, beforeConnect) {
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/transport.ts
9570
- import { spawn as spawn2 } from "node:child_process";
9571
- import { createInterface } from "node:readline";
9572
- function sdkLog(tag, ...args) {
9573
- if (process.env.DEBUG_SDK)
9574
- console.error(`[SDK-Transport] [${tag}]`, ...args);
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
- var SDK_AGENT_ORIGIN_TAG = "origin:letta-code";
9577
- function shouldApplyNewAgentDefaults(options) {
9578
- return options.createOnly === true;
9901
+ function looksLikeConversationId2(id) {
9902
+ return id.startsWith("conv-") || id.startsWith("local-conv-");
9579
9903
  }
9580
- function includeSdkAgentOriginTag(tags) {
9581
- const normalizedTags = [];
9582
- let hasOriginTag = false;
9583
- for (const tag of tags ?? []) {
9584
- if (tag === SDK_AGENT_ORIGIN_TAG) {
9585
- if (hasOriginTag)
9586
- continue;
9587
- hasOriginTag = true;
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 normalizedTags;
9911
+ return ReactNativeWebSocketAdapter;
9595
9912
  }
9596
- function buildCliArgs(options) {
9597
- const args = [
9598
- "--output-format",
9599
- "stream-json",
9600
- "--input-format",
9601
- "stream-json"
9602
- ];
9603
- const applyNewAgentDefaults = shouldApplyNewAgentDefaults(options);
9604
- if (options.conversationId) {
9605
- args.push("--conversation", options.conversationId);
9606
- } else if (options.agentId) {
9607
- args.push("--agent", options.agentId);
9608
- if (options.newConversation) {
9609
- args.push("--new");
9610
- } else if (options.defaultConversation) {
9611
- args.push("--conversation", "default");
9612
- }
9613
- } else if (options.createOnly) {
9614
- args.push("--new-agent");
9615
- } else if (options.newConversation) {
9616
- args.push("--new");
9617
- }
9618
- if (options.model) {
9619
- args.push("-m", options.model);
9620
- }
9621
- if (options.includePartialMessages) {
9622
- args.push("--include-partial-messages");
9623
- }
9624
- if (options.embedding) {
9625
- args.push("--embedding", options.embedding);
9626
- }
9627
- if (options.systemPrompt !== undefined) {
9628
- if (typeof options.systemPrompt === "string") {
9629
- const validPresets = [
9630
- "default",
9631
- "letta-claude",
9632
- "letta-codex",
9633
- "letta-gemini",
9634
- "claude",
9635
- "codex",
9636
- "gemini"
9637
- ];
9638
- if (validPresets.includes(options.systemPrompt)) {
9639
- args.push("--system", options.systemPrompt);
9640
- } else {
9641
- args.push("--system-custom", options.systemPrompt);
9642
- }
9643
- } else {
9644
- args.push("--system", options.systemPrompt.preset);
9645
- if (options.systemPrompt.append) {
9646
- args.push("--system-append", options.systemPrompt.append);
9647
- }
9648
- }
9913
+ // src/stream-events.ts
9914
+ function extractTextFromContent2(content) {
9915
+ if (typeof content === "string") {
9916
+ return content;
9649
9917
  }
9650
- if (options.memory !== undefined && !options.agentId) {
9651
- if (options.memory.length === 0) {
9652
- args.push("--init-blocks", "");
9653
- } else {
9654
- const presetNames = [];
9655
- const memoryBlocksJson = [];
9656
- for (const item of options.memory) {
9657
- if (typeof item === "string") {
9658
- presetNames.push(item);
9659
- } else if ("blockId" in item) {
9660
- memoryBlocksJson.push(item);
9661
- } else {
9662
- memoryBlocksJson.push(item);
9663
- }
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 (memoryBlocksJson.length > 0) {
9666
- args.push("--memory-blocks", JSON.stringify(memoryBlocksJson));
9667
- if (presetNames.length > 0) {
9668
- console.warn("[letta-agent-sdk] Using custom memory blocks. " + `Preset blocks are ignored when custom blocks are provided: ${presetNames.join(", ")}`);
9669
- }
9670
- } else if (presetNames.length > 0) {
9671
- args.push("--init-blocks", presetNames.join(","));
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 (!options.agentId) {
9676
- if (options.persona !== undefined) {
9677
- args.push("--block-value", `persona=${options.persona}`);
9678
- }
9679
- if (options.human !== undefined) {
9680
- args.push("--block-value", `human=${options.human}`);
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
- const mode = normalizePermissionMode(options.permissionMode);
9684
- if (mode !== undefined) {
9685
- args.push("--permission-mode", mode);
9686
- }
9687
- if (options.allowedTools) {
9688
- args.push("--allowedTools", options.allowedTools.join(","));
9689
- }
9690
- if (options.disallowedTools) {
9691
- args.push("--disallowedTools", options.disallowedTools.join(","));
9692
- }
9693
- const tags = applyNewAgentDefaults ? includeSdkAgentOriginTag(options.tags) : options.tags;
9694
- if (tags && tags.length > 0) {
9695
- args.push("--tags", tags.join(","));
9942
+ return null;
9943
+ }
9944
+ function extractStreamTextDelta(event) {
9945
+ if (!event || typeof event !== "object") {
9946
+ return null;
9696
9947
  }
9697
- if (applyNewAgentDefaults) {
9698
- args.push("--memfs");
9699
- if (options.baseTools !== undefined) {
9700
- args.push("--base-tools", options.baseTools.length > 0 ? options.baseTools.join(",") : "none");
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
- if (options.skillSources !== undefined) {
9704
- const sources = [...new Set(options.skillSources)];
9705
- if (sources.length === 0) {
9706
- args.push("--no-skills");
9707
- } else {
9708
- args.push("--skill-sources", sources.join(","));
9955
+ if (typeof delta.text === "string" && delta.text.length > 0) {
9956
+ return { kind: "assistant", text: delta.text };
9709
9957
  }
9710
9958
  }
9711
- if (options.systemInfoReminder === false) {
9712
- args.push("--no-system-info-reminder");
9713
- }
9714
- if (options.dreaming?.trigger !== undefined) {
9715
- args.push("--reflection-trigger", options.dreaming.trigger);
9716
- }
9717
- if (options.dreaming?.behavior !== undefined) {
9718
- args.push("--reflection-behavior", options.dreaming.behavior);
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 (options.dreaming?.stepCount !== undefined) {
9721
- args.push("--reflection-step-count", String(options.dreaming.stepCount));
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 args;
9724
- }
9725
-
9726
- class SubprocessTransport {
9727
- options;
9728
- process = null;
9729
- stdout = null;
9730
- messageQueue = [];
9731
- messageResolvers = [];
9732
- closed = false;
9733
- agentId;
9734
- wireMessageCount = 0;
9735
- lastMessageAt = 0;
9736
- stderrLines = [];
9737
- constructor(options = {}) {
9738
- this.options = options;
9739
- }
9740
- async connect() {
9741
- const args = this.buildArgs();
9742
- const cliPath = await this.findCli();
9743
- sdkLog("connect", `CLI: ${cliPath}`);
9744
- sdkLog("connect", `args: ${args.join(" ")}`);
9745
- sdkLog("connect", `cwd: ${this.options.cwd || process.cwd()}`);
9746
- sdkLog("connect", `permissionMode: ${this.options.permissionMode || "default"}`);
9747
- this.process = spawn2("node", [cliPath, ...args], {
9748
- cwd: this.options.cwd || process.cwd(),
9749
- stdio: ["pipe", "pipe", "pipe"],
9750
- env: { ...process.env }
9751
- });
9752
- const pid = this.process.pid;
9753
- sdkLog("connect", `CLI process spawned, pid=${pid}`);
9754
- if (!this.process.stdout || !this.process.stdin) {
9755
- throw new Error("Failed to create subprocess pipes");
9756
- }
9757
- this.stdout = createInterface({
9758
- input: this.process.stdout,
9759
- crlfDelay: Infinity
9760
- });
9761
- this.stdout.on("line", (line) => {
9762
- if (!line.trim())
9763
- return;
9764
- try {
9765
- const msg = JSON.parse(line);
9766
- this.handleMessage(msg);
9767
- } catch {
9768
- sdkLog("stdout", `[non-JSON] ${line.slice(0, 500)}`);
9769
- }
9770
- });
9771
- if (this.process.stderr) {
9772
- this.process.stderr.on("data", (data) => {
9773
- const msg = data.toString().trim();
9774
- if (msg) {
9775
- console.error("[letta-agent-sdk] CLI stderr:", msg);
9776
- this.stderrLines.push(msg);
9777
- }
9778
- });
9779
- }
9780
- this.process.on("close", (code, signal) => {
9781
- if (code !== 0 && code !== null) {
9782
- console.error(`[letta-agent-sdk] CLI process exited with code ${code}`);
9783
- }
9784
- sdkLog("close", `CLI process exited: pid=${pid} code=${code} signal=${signal} wireMessages=${this.wireMessageCount} msSinceLastMsg=${this.lastMessageAt ? Date.now() - this.lastMessageAt : 0} pendingResolvers=${this.messageResolvers.length} queueLen=${this.messageQueue.length}`);
9785
- this.closed = true;
9786
- for (const resolve of this.messageResolvers) {
9787
- resolve(null);
9788
- }
9789
- this.messageResolvers = [];
9790
- });
9791
- this.process.on("error", (err) => {
9792
- console.error("[letta-agent-sdk] CLI process error:", err);
9793
- this.closed = true;
9794
- });
9795
- }
9796
- async write(data) {
9797
- if (!this.process?.stdin || this.closed) {
9798
- const err = new Error(`Transport not connected (closed=${this.closed}, pid=${this.process?.pid}, stdin=${!!this.process?.stdin})`);
9799
- sdkLog("write", err.message);
9800
- throw err;
9801
- }
9802
- const payload = data;
9803
- sdkLog("write", `type=${payload.type} subtype=${payload.request?.subtype || payload.response?.subtype || "N/A"}`);
9804
- this.process.stdin.write(JSON.stringify(data) + `
9805
- `);
9806
- }
9807
- async read() {
9808
- if (this.messageQueue.length > 0) {
9809
- return this.messageQueue.shift();
9810
- }
9811
- if (this.closed) {
9812
- sdkLog("read", `returning null (closed), total wireMessages=${this.wireMessageCount}`);
9813
- return null;
9814
- }
9815
- sdkLog("read", `waiting for next message (resolvers=${this.messageResolvers.length + 1}, queue=${this.messageQueue.length})`);
9816
- return new Promise((resolve) => {
9817
- this.messageResolvers.push(resolve);
9818
- });
9819
- }
9820
- async* messages() {
9821
- while (true) {
9822
- const msg = await this.read();
9823
- if (msg === null) {
9824
- sdkLog("messages", `iterator ending (closed=${this.closed}, wireMessages=${this.wireMessageCount})`);
9825
- break;
9826
- }
9827
- yield msg;
9828
- }
9829
- }
9830
- close() {
9831
- sdkLog("close", `explicit close called (wireMessages=${this.wireMessageCount}, pendingResolvers=${this.messageResolvers.length}, pid=${this.process?.pid})`);
9832
- if (this.process) {
9833
- this.process.stdin?.end();
9834
- this.process.kill();
9835
- this.process = null;
9836
- }
9837
- this.closed = true;
9838
- for (const resolve of this.messageResolvers) {
9839
- resolve(null);
9840
- }
9841
- this.messageResolvers = [];
9842
- }
9843
- get isClosed() {
9844
- return this.closed;
9845
- }
9846
- getStderr() {
9847
- return this.stderrLines.join(`
9848
- `);
9849
- }
9850
- handleMessage(msg) {
9851
- this.wireMessageCount++;
9852
- this.lastMessageAt = Date.now();
9853
- const wirePayload = msg;
9854
- const msgType = wirePayload.message_type || wirePayload.subtype || "";
9855
- sdkLog("wire", `#${this.wireMessageCount} type=${msg.type} ${msgType ? `msg_type=${msgType}` : ""} resolvers=${this.messageResolvers.length} queue=${this.messageQueue.length}`);
9856
- if (msg.type === "result") {
9857
- const result = wirePayload;
9858
- sdkLog("wire", `RESULT: subtype=${result.subtype} stop_reason=${result.stop_reason || "N/A"} duration=${result.duration_ms}ms resultLen=${result.result?.length || 0}`);
9859
- }
9860
- if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
9861
- this.agentId = msg.agent_id;
9862
- sdkLog("wire", `INIT: agent_id=${this.agentId}`);
9863
- }
9864
- if (msg.type === "control_request") {
9865
- const req = wirePayload;
9866
- sdkLog("wire", `CONTROL_REQUEST: id=${req.request_id} subtype=${req.request?.subtype} tool=${req.request?.tool_name || "N/A"}`);
9867
- }
9868
- if (this.messageResolvers.length > 0) {
9869
- const resolve = this.messageResolvers.shift();
9870
- resolve(msg);
9871
- } else {
9872
- this.messageQueue.push(msg);
9873
- }
9874
- }
9875
- buildArgs() {
9876
- return buildCliArgs(this.options);
9877
- }
9878
- async findCli() {
9879
- const { existsSync: existsSync2 } = await import("node:fs");
9880
- const { dirname: dirname2, join: join2 } = await import("node:path");
9881
- const { fileURLToPath: fileURLToPath2 } = await import("node:url");
9882
- if (process.env.LETTA_CLI_PATH && existsSync2(process.env.LETTA_CLI_PATH)) {
9883
- return process.env.LETTA_CLI_PATH;
9884
- }
9885
- try {
9886
- const { createRequire: createRequire3 } = await import("node:module");
9887
- const require3 = createRequire3(import.meta.url);
9888
- const resolved = require3.resolve("@letta-ai/letta-code");
9889
- if (existsSync2(resolved)) {
9890
- return resolved;
9891
- }
9892
- } catch {}
9893
- const __filename3 = fileURLToPath2(import.meta.url);
9894
- const __dirname3 = dirname2(__filename3);
9895
- const localPaths = [
9896
- join2(__dirname3, "../../@letta-ai/letta-code/letta.js"),
9897
- join2(__dirname3, "../../../letta-code-prod/letta.js"),
9898
- join2(__dirname3, "../../../letta-code/letta.js")
9899
- ];
9900
- for (const p of localPaths) {
9901
- if (existsSync2(p)) {
9902
- return p;
9903
- }
9904
- }
9905
- throw new Error("Letta Code CLI not found. Set LETTA_CLI_PATH or install @letta-ai/letta-code.");
9906
- }
9907
- }
9908
-
9909
- // src/session.ts
9910
- function sessionLog(tag, ...args) {
9911
- if (process.env.DEBUG_SDK)
9912
- console.error(`[SDK-Session] [${tag}]`, ...args);
9913
- }
9914
- var MAX_BUFFERED_STREAM_MESSAGES = 100;
9915
- var DEFAULT_MAX_APPROVAL_RECOVERY_ATTEMPTS = 1;
9916
- var DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS = 5000;
9917
- var KNOWN_SDK_ERROR_CODES2 = new Set([
9918
- "approval_conflict",
9919
- "approval_conflict_terminal",
9920
- "protocol_error",
9921
- "error",
9922
- "llm_api_error",
9923
- "max_steps",
9924
- "interrupted",
9925
- "stream_closed"
9926
- ]);
9927
- function isKnownSdkErrorCode(value) {
9928
- return KNOWN_SDK_ERROR_CODES2.has(value);
9929
- }
9930
- function toSdkErrorCode2(value) {
9931
- if (!value || value.length === 0)
9932
- return;
9933
- return isKnownSdkErrorCode(value) ? value : undefined;
9934
- }
9935
- function isUnknownControlSubtypeError(errorMessage, subtype) {
9936
- if (!errorMessage)
9937
- return false;
9938
- return errorMessage.toLowerCase().includes(`unknown control request subtype: ${subtype}`.toLowerCase());
9939
- }
9940
- function extractApiErrorDetail(apiError) {
9941
- const detail = apiError?.detail;
9942
- if (typeof detail !== "string")
9943
- return;
9944
- const trimmed = detail.trim();
9945
- return trimmed.length > 0 ? trimmed : undefined;
9946
- }
9947
- function isApprovalConflictSignal2(params) {
9948
- if (params.stopReason === "requires_approval")
9949
- return true;
9950
- const haystack = [params.detail, params.message].filter((value) => typeof value === "string" && value.length > 0).join(`
9951
- `).toLowerCase();
9952
- if (!haystack)
9953
- return false;
9954
- return haystack.includes("waiting for approval on a tool call") || haystack.includes("cannot send a new message") || haystack.includes("requires_approval");
9955
- }
9956
-
9957
- class Session {
9958
- options;
9959
- transport;
9960
- _agentId = null;
9961
- _sessionId = null;
9962
- _conversationId = null;
9963
- initialized = false;
9964
- closed = false;
9965
- initializePromise = null;
9966
- externalTools = new Map;
9967
- streamQueue = [];
9968
- streamResolvers = [];
9969
- pumpPromise = null;
9970
- pumpClosed = false;
9971
- droppedStreamMessages = 0;
9972
- sendGeneration = 0;
9973
- lastCompletedRunIds = new Set;
9974
- controlResponseWaiters = new Map;
9975
- constructor(options = {}) {
9976
- this.options = options;
9977
- if (options.reasoningEffort !== undefined) {
9978
- throw new Error("reasoningEffort is not supported by this session. Create a Cloud, Remote, or local agent session before setting reasoning effort.");
9979
- }
9980
- this.transport = new SubprocessTransport(options);
9981
- if (options.tools) {
9982
- for (const tool of options.tools) {
9983
- this.externalTools.set(tool.name, tool);
9984
- }
9985
- }
9986
- }
9987
- async initialize() {
9988
- if (this.closed) {
9989
- throw new Error("Session is closed");
9990
- }
9991
- if (this.initializePromise) {
9992
- return this.initializePromise;
9993
- }
9994
- if (this.initialized) {
9995
- throw new Error("Session already initialized");
9996
- }
9997
- const attempt = this.performInitialize();
9998
- const memo = attempt.catch((error) => {
9999
- this.cleanupFailedInitialize();
10000
- throw error;
10001
- }).finally(() => {
10002
- if (this.initializePromise === memo) {
10003
- this.initializePromise = null;
10004
- }
10005
- });
10006
- this.initializePromise = memo;
10007
- return memo;
10008
- }
10009
- async performInitialize() {
10010
- sessionLog("init", "connecting transport...");
10011
- await this.transport.connect();
10012
- sessionLog("init", "transport connected, sending initialize request");
10013
- await this.transport.write({
10014
- type: "control_request",
10015
- request_id: "init_1",
10016
- request: { subtype: "initialize" }
10017
- });
10018
- sessionLog("init", "waiting for init message from CLI...");
10019
- for await (const msg of this.transport.messages()) {
10020
- sessionLog("init", `received wire message: type=${msg.type}`);
10021
- if (msg.type === "control_request") {
10022
- const handled = await this.handleControlRequest(msg);
10023
- if (!handled) {
10024
- const wireMsgAny = msg;
10025
- sessionLog("init", `DROPPED unsupported control_request: subtype=${wireMsgAny.request?.subtype || "N/A"}`);
10026
- }
10027
- continue;
10028
- }
10029
- if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
10030
- const initMsg = msg;
10031
- this._agentId = initMsg.agent_id;
10032
- this._sessionId = initMsg.session_id;
10033
- this._conversationId = initMsg.conversation_id;
10034
- if (this.externalTools.size > 0) {
10035
- await this.registerExternalTools();
10036
- }
10037
- if (this.closed) {
10038
- throw new Error("Session is closed");
10039
- }
10040
- this.initialized = true;
10041
- this.startBackgroundPump();
10042
- const allTools = [
10043
- ...initMsg.tools,
10044
- ...Array.from(this.externalTools.keys())
10045
- ];
10046
- sessionLog("init", `initialized: agent=${initMsg.agent_id} conversation=${initMsg.conversation_id} model=${initMsg.model} tools=${allTools.length} (${this.externalTools.size} external)`);
10047
- return {
10048
- type: "init",
10049
- agentId: initMsg.agent_id,
10050
- sessionId: initMsg.session_id,
10051
- conversationId: initMsg.conversation_id,
10052
- model: initMsg.model,
10053
- tools: allTools,
10054
- memfsEnabled: initMsg.memfs_enabled,
10055
- skillSources: initMsg.skill_sources,
10056
- systemInfoReminderEnabled: initMsg.system_info_reminder_enabled,
10057
- dreaming: initMsg.reflection_trigger && initMsg.reflection_behavior && typeof initMsg.reflection_step_count === "number" ? {
10058
- trigger: initMsg.reflection_trigger,
10059
- behavior: initMsg.reflection_behavior,
10060
- stepCount: initMsg.reflection_step_count
10061
- } : undefined
10062
- };
10063
- }
10064
- }
10065
- const stderr = this.transport.getStderr();
10066
- const detail = stderr ? `
10067
- CLI stderr:
10068
- ${stderr}` : "";
10069
- sessionLog("init", `ERROR: transport closed before init message received${detail}`);
10070
- throw new Error(`Failed to initialize session - no init message received${detail}`);
10071
- }
10072
- cleanupFailedInitialize() {
10073
- this.transport.close();
10074
- this.closed = true;
10075
- this.initialized = false;
10076
- this._agentId = null;
10077
- this._sessionId = null;
10078
- this._conversationId = null;
10079
- this.pumpClosed = true;
10080
- this.resolveAllStreamWaiters(null);
10081
- }
10082
- async send(message) {
10083
- if (!this.initialized) {
10084
- sessionLog("send", "auto-initializing (not yet initialized)");
10085
- await this.initialize();
10086
- }
10087
- const preview = typeof message === "string" ? message.slice(0, 100) : Array.isArray(message) ? `[multimodal: ${message.length} parts]` : String(message).slice(0, 100);
10088
- sessionLog("send", `sending message: ${preview}${typeof message === "string" && message.length > 100 ? "..." : ""}`);
10089
- if (this.streamQueue.length > 0) {
10090
- sessionLog("send", `clearing ${this.streamQueue.length} stale messages from previous turn`);
10091
- this.streamQueue.length = 0;
10092
- }
10093
- await this.transport.write({
10094
- type: "user",
10095
- message: { role: "user", content: message }
10096
- });
10097
- this.sendGeneration++;
10098
- sessionLog("send", `message written to transport (generation=${this.sendGeneration})`);
10099
- }
10100
- async runTurn(message, options = {}) {
10101
- const maxApprovalRecoveryAttempts = this.resolveMaxApprovalRecoveryAttempts(options);
10102
- const recoveryTimeoutMs = this.resolveApprovalRecoveryTimeoutMs(options);
10103
- let recoveryAttempts = 0;
10104
- let result = await this.runSingleTurn(message);
10105
- while (!result.success && result.approvalConflict === true && recoveryAttempts < maxApprovalRecoveryAttempts) {
10106
- recoveryAttempts += 1;
10107
- const recovery = await this.recoverPendingApprovals({
10108
- timeoutMs: recoveryTimeoutMs
10109
- });
10110
- if (!recovery.recovered) {
10111
- return this.toTerminalApprovalConflictResult(result, {
10112
- recoveryAttempts,
10113
- detail: recovery.detail
10114
- });
10115
- }
10116
- result = await this.runSingleTurn(message);
10117
- }
10118
- if (!result.success && result.approvalConflict === true) {
10119
- return this.toTerminalApprovalConflictResult(result, { recoveryAttempts });
10120
- }
10121
- if (recoveryAttempts > 0) {
10122
- result.recoveryAttempts = recoveryAttempts;
10123
- }
10124
- return result;
10125
- }
10126
- async recoverPendingApprovals(options = {}) {
10127
- if (!this.initialized) {
10128
- await this.initialize();
10129
- }
10130
- const timeoutMs = options.timeoutMs ?? this.options.approvalRecoveryTimeoutMs ?? DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS;
10131
- if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
10132
- throw new Error("Invalid approval recovery timeout. Expected a positive integer.");
10133
- }
10134
- const requestId = `recover-approvals-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
10135
- let resp;
10136
- try {
10137
- resp = await this.requestControlResponse(requestId, {
10138
- subtype: "recover_pending_approvals",
10139
- ...this._agentId ? { agent_id: this._agentId } : {},
10140
- ...this._conversationId ? { conversation_id: this._conversationId } : {}
10141
- }, { timeoutMs });
10142
- } catch (error) {
10143
- const detail = error instanceof Error ? error.message : String(error);
10144
- return {
10145
- recovered: false,
10146
- unsupported: false,
10147
- detail
10148
- };
10149
- }
10150
- if (resp.subtype === "error") {
10151
- const detail = resp.error ?? "recover_pending_approvals failed";
10152
- return {
10153
- recovered: false,
10154
- pendingApproval: true,
10155
- unsupported: isUnknownControlSubtypeError(detail, "recover_pending_approvals"),
10156
- detail
10157
- };
10158
- }
10159
- return {
10160
- recovered: true,
10161
- pendingApproval: false,
10162
- unsupported: false
10163
- };
10164
- }
10165
- resolveMaxApprovalRecoveryAttempts(options) {
10166
- const value = options.maxApprovalRecoveryAttempts ?? this.options.maxApprovalRecoveryAttempts ?? DEFAULT_MAX_APPROVAL_RECOVERY_ATTEMPTS;
10167
- if (!Number.isInteger(value) || value < 0) {
10168
- throw new Error("Invalid maxApprovalRecoveryAttempts. Expected a non-negative integer.");
10169
- }
10170
- return value;
10171
- }
10172
- resolveApprovalRecoveryTimeoutMs(options) {
10173
- const value = options.recoveryTimeoutMs ?? this.options.approvalRecoveryTimeoutMs ?? DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS;
10174
- if (!Number.isInteger(value) || value <= 0) {
10175
- throw new Error("Invalid approval recovery timeout. Expected a positive integer.");
10176
- }
10177
- return value;
10178
- }
10179
- toTerminalApprovalConflictResult(result, options) {
10180
- return {
10181
- ...result,
10182
- success: false,
10183
- error: "approval_conflict_terminal",
10184
- errorCode: "approval_conflict_terminal",
10185
- approvalConflict: true,
10186
- recoverable: false,
10187
- recoveryAttempts: options.recoveryAttempts,
10188
- errorDetail: options.detail || result.errorDetail || "Approval conflict remained unresolved after bounded SDK recovery."
10189
- };
10190
- }
10191
- async runSingleTurn(message) {
10192
- await this.send(message);
10193
- let latestApprovalConflictDetail;
10194
- let latestNonConflictErrorDetail;
10195
- for await (const msg of this.stream()) {
10196
- if (msg.type === "error") {
10197
- const detail = msg.errorDetail || msg.message;
10198
- if (msg.approvalConflict) {
10199
- latestApprovalConflictDetail = detail;
10200
- } else {
10201
- latestNonConflictErrorDetail = detail;
10202
- }
10203
- continue;
10204
- }
10205
- if (msg.type === "result") {
10206
- if (!msg.success && !msg.errorDetail) {
10207
- if (msg.approvalConflict && latestApprovalConflictDetail) {
10208
- return {
10209
- ...msg,
10210
- errorDetail: latestApprovalConflictDetail
10211
- };
10212
- }
10213
- if (!msg.approvalConflict && latestNonConflictErrorDetail) {
10214
- return {
10215
- ...msg,
10216
- errorDetail: latestNonConflictErrorDetail
10217
- };
10218
- }
10219
- }
10220
- return msg;
10221
- }
10222
- }
10223
- return {
10224
- type: "result",
10225
- success: false,
10226
- error: "stream_closed",
10227
- errorCode: "stream_closed",
10228
- recoverable: false,
10229
- errorDetail: "Stream ended before terminal result",
10230
- durationMs: 0,
10231
- conversationId: this._conversationId
10232
- };
10233
- }
10234
- async* stream() {
10235
- const streamStart = Date.now();
10236
- const minGeneration = this.sendGeneration;
10237
- let yieldCount = 0;
10238
- let staleCount = 0;
10239
- let staleRunIdCount = 0;
10240
- let gotResult = false;
10241
- const currentStreamRunIds = new Set;
10242
- const staleRunIds = new Set(this.lastCompletedRunIds);
10243
- const approvalConflictDetailsByRunId = new Map;
10244
- let latestApprovalConflictDetail;
10245
- this.startBackgroundPump();
10246
- sessionLog("stream", `starting stream (agent=${this._agentId}, conversation=${this._conversationId}, generation=${minGeneration})`);
10247
- while (true) {
10248
- const bufferedMsg = await this.nextBufferedMessage();
10249
- if (!bufferedMsg) {
10250
- break;
10251
- }
10252
- if (bufferedMsg.generation < minGeneration) {
10253
- staleCount++;
10254
- sessionLog("stream", `discarding stale message: type=${bufferedMsg.message.type} generation=${bufferedMsg.generation} (current=${minGeneration})`);
10255
- continue;
10256
- }
10257
- if (bufferedMsg.runId && staleRunIds.has(bufferedMsg.runId)) {
10258
- staleRunIdCount++;
10259
- sessionLog("stream", `discarding stale message: type=${bufferedMsg.message.type} runId=${bufferedMsg.runId}`);
10260
- continue;
10261
- }
10262
- if (bufferedMsg.runId) {
10263
- currentStreamRunIds.add(bufferedMsg.runId);
10264
- }
10265
- const sdkMsg = bufferedMsg.message;
10266
- if (sdkMsg.type === "error" && sdkMsg.approvalConflict) {
10267
- const detail = sdkMsg.errorDetail || sdkMsg.message;
10268
- if (detail) {
10269
- latestApprovalConflictDetail = detail;
10270
- if (sdkMsg.runId) {
10271
- approvalConflictDetailsByRunId.set(sdkMsg.runId, detail);
10272
- }
10273
- }
10274
- }
10275
- let normalizedMsg = sdkMsg;
10276
- if (sdkMsg.type === "result" && !sdkMsg.success) {
10277
- let detail = sdkMsg.errorDetail;
10278
- if (!detail && Array.isArray(sdkMsg.runIds)) {
10279
- for (const runId of sdkMsg.runIds) {
10280
- const fromRun = approvalConflictDetailsByRunId.get(runId);
10281
- if (fromRun) {
10282
- detail = fromRun;
10283
- break;
10284
- }
10285
- }
10286
- }
10287
- if (!detail) {
10288
- detail = latestApprovalConflictDetail;
10289
- }
10290
- const approvalConflict = sdkMsg.approvalConflict === true || isApprovalConflictSignal2({
10291
- detail,
10292
- message: sdkMsg.errorCode || sdkMsg.error,
10293
- stopReason: sdkMsg.stopReason
10294
- });
10295
- if (approvalConflict) {
10296
- const normalizedErrorCode = !sdkMsg.errorCode || sdkMsg.errorCode === "error" ? "approval_conflict" : sdkMsg.errorCode;
10297
- const normalizedError = !sdkMsg.error || sdkMsg.error === "error" ? normalizedErrorCode : sdkMsg.error;
10298
- normalizedMsg = {
10299
- ...sdkMsg,
10300
- approvalConflict: true,
10301
- recoverable: sdkMsg.recoverable ?? true,
10302
- ...detail ? { errorDetail: detail } : {},
10303
- errorCode: normalizedErrorCode,
10304
- error: normalizedError
10305
- };
10306
- } else if (!sdkMsg.errorCode && sdkMsg.error) {
10307
- const errorCode = toSdkErrorCode2(sdkMsg.error);
10308
- if (errorCode) {
10309
- normalizedMsg = {
10310
- ...sdkMsg,
10311
- errorCode
10312
- };
10313
- }
10314
- }
10315
- }
10316
- yieldCount++;
10317
- sessionLog("stream", `yield #${yieldCount}: type=${normalizedMsg.type}${normalizedMsg.type === "result" ? ` success=${normalizedMsg.success} error=${normalizedMsg.error || "none"}` : ""}`);
10318
- yield normalizedMsg;
10319
- if (normalizedMsg.type === "result") {
10320
- gotResult = true;
10321
- this.updateCompletedRunIds(normalizedMsg.runIds, currentStreamRunIds);
10322
- break;
10323
- }
10324
- }
10325
- const elapsed = Date.now() - streamStart;
10326
- sessionLog("stream", `stream ended: duration=${elapsed}ms yielded=${yieldCount} staleFiltered=${staleCount} staleRunIdFiltered=${staleRunIdCount} dropped=${this.droppedStreamMessages} gotResult=${gotResult}`);
10327
- if (!gotResult) {
10328
- sessionLog("stream", "WARNING: stream ended WITHOUT a result message -- transport may have closed unexpectedly");
10329
- }
10330
- }
10331
- startBackgroundPump() {
10332
- if (this.pumpPromise) {
10333
- return;
10334
- }
10335
- this.pumpClosed = false;
10336
- this.pumpPromise = this.runBackgroundPump().catch((err) => {
10337
- sessionLog("pump", `ERROR: ${err instanceof Error ? err.message : String(err)}`);
10338
- }).finally(() => {
10339
- this.pumpClosed = true;
10340
- this.resolveAllStreamWaiters(null);
10341
- });
10342
- }
10343
- async runBackgroundPump() {
10344
- sessionLog("pump", "background pump started");
10345
- const indexToToolCallId = new Map;
10346
- for await (const wireMsg of this.transport.messages()) {
10347
- const wireMsgAny = wireMsg;
10348
- if (wireMsg.type === "control_request") {
10349
- const handled = await this.handleControlRequest(wireMsg);
10350
- if (!handled) {
10351
- sessionLog("pump", `DROPPED unsupported control_request: subtype=${wireMsgAny.request?.subtype || "N/A"}`);
10352
- }
10353
- continue;
10354
- }
10355
- if (wireMsg.type === "control_response") {
10356
- const respMsg = wireMsg;
10357
- const requestId = respMsg.response?.request_id;
10358
- if (requestId && this.controlResponseWaiters.has(requestId)) {
10359
- const resolve = this.controlResponseWaiters.get(requestId);
10360
- this.controlResponseWaiters.delete(requestId);
10361
- resolve(respMsg.response);
10362
- } else {
10363
- sessionLog("pump", `DROPPED unmatched control_response: request_id=${requestId ?? "N/A"}`);
10364
- }
10365
- continue;
10366
- }
10367
- const messageType = wireMsgAny.message_type;
10368
- if (wireMsg.type === "message" && (messageType === "tool_call_message" || messageType === "approval_request_message")) {
10369
- const toolCalls = wireMsgAny.tool_calls;
10370
- const toolCall = wireMsgAny.tool_call;
10371
- const tc = toolCalls?.[0] || toolCall;
10372
- if (tc) {
10373
- const fnObj = tc.function;
10374
- const tcId = tc.tool_call_id ?? tc.id;
10375
- const tcIndex = tc.index;
10376
- if (tcId && tcIndex !== undefined) {
10377
- indexToToolCallId.set(tcIndex, tcId);
10378
- } else if (!tcId && tcIndex !== undefined) {
10379
- const resolvedId = indexToToolCallId.get(tcIndex);
10380
- if (resolvedId) {
10381
- if (fnObj) {
10382
- tc.id = resolvedId;
10383
- } else {
10384
- tc.tool_call_id = resolvedId;
10385
- }
10386
- }
10387
- }
10388
- }
10389
- }
10390
- const sdkMsg = this.transformMessage(wireMsg);
10391
- if (sdkMsg) {
10392
- this.enqueueStreamMessage(sdkMsg);
10393
- } else {
10394
- sessionLog("pump", `DROPPED wire message: type=${wireMsg.type} message_type=${wireMsgAny.message_type || "N/A"} subtype=${wireMsgAny.subtype || "N/A"}`);
10395
- }
10396
- }
10397
- sessionLog("pump", "background pump ended");
10398
- }
10399
- async handleControlRequest(controlReq) {
10400
- const subtype = controlReq.request.subtype;
10401
- sessionLog("pump", `control_request: subtype=${subtype} tool=${controlReq.request.tool_name || "N/A"}`);
10402
- if (subtype === "can_use_tool") {
10403
- await this.handleCanUseTool(controlReq.request_id, controlReq.request);
10404
- return true;
10405
- }
10406
- if (subtype === "execute_external_tool") {
10407
- const rawReq = controlReq.request;
10408
- await this.handleExecuteExternalTool(controlReq.request_id, {
10409
- subtype: "execute_external_tool",
10410
- tool_call_id: rawReq.tool_call_id,
10411
- tool_name: rawReq.tool_name,
10412
- input: rawReq.input
10413
- });
10414
- return true;
10415
- }
10416
- return false;
10417
- }
10418
- enqueueStreamMessage(msg) {
10419
- const bufferedMsg = {
10420
- message: msg,
10421
- generation: this.sendGeneration,
10422
- runId: this.getMessageRunId(msg)
10423
- };
10424
- if (this.streamResolvers.length > 0) {
10425
- const resolve = this.streamResolvers.shift();
10426
- resolve(bufferedMsg);
10427
- return;
10428
- }
10429
- if (this.streamQueue.length >= MAX_BUFFERED_STREAM_MESSAGES) {
10430
- this.streamQueue.shift();
10431
- this.droppedStreamMessages++;
10432
- sessionLog("pump", `stream queue overflow: dropped oldest message (total_dropped=${this.droppedStreamMessages}, max=${MAX_BUFFERED_STREAM_MESSAGES})`);
10433
- }
10434
- this.streamQueue.push(bufferedMsg);
10435
- }
10436
- async nextBufferedMessage() {
10437
- if (this.streamQueue.length > 0) {
10438
- return this.streamQueue.shift();
10439
- }
10440
- if (this.pumpClosed) {
10441
- return null;
10442
- }
10443
- return new Promise((resolve) => {
10444
- this.streamResolvers.push(resolve);
10445
- });
10446
- }
10447
- resolveAllStreamWaiters(msg) {
10448
- for (const resolve of this.streamResolvers) {
10449
- resolve(msg);
10450
- }
10451
- this.streamResolvers = [];
10452
- for (const resolve of this.controlResponseWaiters.values()) {
10453
- resolve({ subtype: "error", error: "session closed" });
10454
- }
10455
- this.controlResponseWaiters.clear();
10456
- }
10457
- getMessageRunId(msg) {
10458
- switch (msg.type) {
10459
- case "assistant":
10460
- case "tool_call":
10461
- case "tool_result":
10462
- case "reasoning":
10463
- case "error":
10464
- case "retry":
10465
- return msg.runId;
10466
- default:
10467
- return;
10468
- }
10469
- }
10470
- updateCompletedRunIds(resultRunIds, streamedRunIds) {
10471
- const nextRunIds = new Set;
10472
- if (Array.isArray(resultRunIds)) {
10473
- for (const runId of resultRunIds) {
10474
- if (runId) {
10475
- nextRunIds.add(runId);
10476
- }
10477
- }
10478
- }
10479
- for (const runId of streamedRunIds) {
10480
- if (runId) {
10481
- nextRunIds.add(runId);
10482
- }
10483
- }
10484
- this.lastCompletedRunIds = nextRunIds;
10485
- }
10486
- async registerExternalTools() {
10487
- const toolDefs = Array.from(this.externalTools.values()).map((tool) => ({
10488
- name: tool.name,
10489
- label: tool.label,
10490
- description: tool.description,
10491
- parameters: this.schemaToJsonSchema(tool.parameters)
10492
- }));
10493
- sessionLog("registerTools", `registering ${toolDefs.length} external tools: ${toolDefs.map((t) => t.name).join(", ")}`);
10494
- await this.transport.write({
10495
- type: "control_request",
10496
- request_id: `register_tools_${Date.now()}`,
10497
- request: {
10498
- subtype: "register_external_tools",
10499
- tools: toolDefs
10500
- }
10501
- });
10502
- }
10503
- schemaToJsonSchema(schema) {
10504
- if (schema && typeof schema === "object") {
10505
- const s = schema;
10506
- return {
10507
- type: s.type,
10508
- properties: s.properties,
10509
- required: s.required,
10510
- additionalProperties: s.additionalProperties,
10511
- description: s.description
10512
- };
10513
- }
10514
- return { type: "object" };
10515
- }
10516
- async handleExecuteExternalTool(requestId, req) {
10517
- const tool = this.externalTools.get(req.tool_name);
10518
- if (!tool) {
10519
- sessionLog("executeExternalTool", `ERROR: unknown tool ${req.tool_name}`);
10520
- await this.transport.write({
10521
- type: "control_response",
10522
- response: {
10523
- subtype: "external_tool_result",
10524
- request_id: requestId,
10525
- tool_call_id: req.tool_call_id,
10526
- content: [{ type: "text", text: `Unknown external tool: ${req.tool_name}` }],
10527
- is_error: true
10528
- }
10529
- });
10530
- return;
10531
- }
10532
- try {
10533
- sessionLog("executeExternalTool", `executing ${req.tool_name} (call_id=${req.tool_call_id})`);
10534
- const result = await tool.execute(req.tool_call_id, req.input);
10535
- await this.transport.write({
10536
- type: "control_response",
10537
- response: {
10538
- subtype: "external_tool_result",
10539
- request_id: requestId,
10540
- tool_call_id: req.tool_call_id,
10541
- content: result.content,
10542
- is_error: false
10543
- }
10544
- });
10545
- sessionLog("executeExternalTool", `${req.tool_name} completed successfully`);
10546
- } catch (err) {
10547
- const errorMessage = err instanceof Error ? err.message : String(err);
10548
- sessionLog("executeExternalTool", `${req.tool_name} failed: ${errorMessage}`);
10549
- await this.transport.write({
10550
- type: "control_response",
10551
- response: {
10552
- subtype: "external_tool_result",
10553
- request_id: requestId,
10554
- tool_call_id: req.tool_call_id,
10555
- content: [{ type: "text", text: `Tool execution error: ${errorMessage}` }],
10556
- is_error: true
10557
- }
10558
- });
10559
- }
10560
- }
10561
- async handleCanUseTool(requestId, req) {
10562
- let response;
10563
- const toolName = req.tool_name;
10564
- const hasCallback = typeof this.options.canUseTool === "function";
10565
- const toolNeedsRuntimeUserInput = requiresRuntimeUserInput(toolName);
10566
- const autoAllowWithoutCallback = isHeadlessAutoAllowTool(toolName);
10567
- sessionLog("canUseTool", `tool=${toolName} mode=${this.options.permissionMode || "standard"} requestId=${requestId}`);
10568
- if (toolNeedsRuntimeUserInput && !hasCallback) {
10569
- response = {
10570
- behavior: "deny",
10571
- message: "No canUseTool callback registered",
10572
- interrupt: false
10573
- };
10574
- } else if (isUnrestrictedPermissionMode(this.options.permissionMode) && !toolNeedsRuntimeUserInput) {
10575
- sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (unrestricted)`);
10576
- response = {
10577
- behavior: "allow",
10578
- updatedInput: null,
10579
- updatedPermissions: []
10580
- };
10581
- } else if (hasCallback) {
10582
- try {
10583
- const result = await this.options.canUseTool(toolName, req.input, buildCanUseToolContext(req, requestId));
10584
- if (result.behavior === "allow") {
10585
- response = {
10586
- behavior: "allow",
10587
- updatedInput: result.updatedInput ?? null,
10588
- updatedPermissions: []
10589
- };
10590
- } else {
10591
- response = {
10592
- behavior: "deny",
10593
- message: result.message ?? "Denied by canUseTool callback",
10594
- interrupt: false
10595
- };
10596
- }
10597
- } catch (err) {
10598
- response = {
10599
- behavior: "deny",
10600
- message: err instanceof Error ? err.message : "Callback error",
10601
- interrupt: false
10602
- };
10603
- }
10604
- } else if (autoAllowWithoutCallback) {
10605
- sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (default behavior)`);
10606
- response = {
10607
- behavior: "allow",
10608
- updatedInput: null,
10609
- updatedPermissions: []
10610
- };
10611
- } else {
10612
- response = {
10613
- behavior: "deny",
10614
- message: "No canUseTool callback registered",
10615
- interrupt: false
10616
- };
10617
- }
10618
- const responseBehavior = "behavior" in response ? response.behavior : "unknown";
10619
- sessionLog("canUseTool", `responding: requestId=${requestId} behavior=${responseBehavior}`);
10620
- await this.transport.write({
10621
- type: "control_response",
10622
- response: {
10623
- subtype: "success",
10624
- request_id: requestId,
10625
- response
10626
- }
10627
- });
10628
- sessionLog("canUseTool", `response sent for ${toolName}`);
10629
- }
10630
- async abort() {
10631
- if (!this.initialized || this.pumpClosed)
10632
- return;
10633
- sessionLog("abort", `aborting session (agent=${this._agentId})`);
10634
- await this.transport.write({
10635
- type: "control_request",
10636
- request_id: `interrupt-${Date.now()}`,
10637
- request: { subtype: "interrupt" }
10638
- });
10639
- }
10640
- async requestControlResponse(requestId, request, options = {}) {
10641
- const responsePromise = new Promise((resolve) => {
10642
- this.controlResponseWaiters.set(requestId, resolve);
10643
- });
10644
- try {
10645
- await this.transport.write({
10646
- type: "control_request",
10647
- request_id: requestId,
10648
- request
10649
- });
10650
- } catch (error) {
10651
- this.controlResponseWaiters.delete(requestId);
10652
- throw error;
10653
- }
10654
- let timeoutHandle;
10655
- try {
10656
- if (typeof options.timeoutMs === "number") {
10657
- return await Promise.race([
10658
- responsePromise,
10659
- new Promise((_, reject) => {
10660
- timeoutHandle = setTimeout(() => {
10661
- this.controlResponseWaiters.delete(requestId);
10662
- reject(new Error(`Timed out waiting for control_response (${requestId})`));
10663
- }, options.timeoutMs);
10664
- })
10665
- ]);
10666
- }
10667
- return await responsePromise;
10668
- } finally {
10669
- if (timeoutHandle) {
10670
- clearTimeout(timeoutHandle);
10671
- }
10672
- }
10673
- }
10674
- async listMessages(options = {}) {
10675
- if (!this.initialized) {
10676
- throw new Error("Session must be initialized before calling listMessages()");
10677
- }
10678
- const requestId = `list-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
10679
- const resp = await this.requestControlResponse(requestId, {
10680
- subtype: "list_messages",
10681
- ...options.conversationId ? { conversation_id: options.conversationId } : {},
10682
- ...options.before ? { before: options.before } : {},
10683
- ...options.after ? { after: options.after } : {},
10684
- ...options.order ? { order: options.order } : {},
10685
- ...options.limit !== undefined ? { limit: options.limit } : {}
10686
- });
10687
- if (!resp) {
10688
- throw new Error("Session closed before listMessages response arrived");
10689
- }
10690
- if (resp.subtype === "error") {
10691
- throw new Error(resp.error ?? "listMessages failed");
10692
- }
10693
- const payload = resp.response;
10694
- return {
10695
- messages: payload?.messages ?? [],
10696
- nextBefore: payload?.next_before ?? null,
10697
- hasMore: payload?.has_more ?? false
10698
- };
10699
- }
10700
- async listModels() {
10701
- throw new Error("listModels() is not supported by this session. Use a Cloud, Remote, or local agent session.");
10702
- }
10703
- async sendCommand(_command, _options) {
10704
- throw new Error("sendCommand() is not supported by this session. Use a Cloud, Remote, or local agent session.");
10705
- }
10706
- async updateModel(_update) {
10707
- throw new Error("updateModel() is not supported by this session. Use a Cloud, Remote, or local agent session.");
10708
- }
10709
- async changeDeviceState(_updates) {
10710
- throw new Error("changeDeviceState() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
10711
- }
10712
- async removeQueuedMessage(_itemId) {
10713
- throw new Error("removeQueuedMessage() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
10714
- }
10715
- async getDeviceStatus(_options) {
10716
- throw new Error("getDeviceStatus() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
10717
- }
10718
- onDeviceStatus(_listener) {
10719
- throw new Error("onDeviceStatus() is not supported by the legacy stdio transport. Use a Cloud, Remote, or local app-server session.");
10720
- }
10721
- async bootstrapState(options = {}) {
10722
- if (!this.initialized) {
10723
- throw new Error("Session must be initialized before calling bootstrapState()");
10724
- }
10725
- const requestId = `bootstrap-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
10726
- const resp = await this.requestControlResponse(requestId, {
10727
- subtype: "bootstrap_session_state",
10728
- ...options.limit !== undefined ? { limit: options.limit } : {},
10729
- ...options.order ? { order: options.order } : {}
10730
- });
10731
- if (!resp) {
10732
- throw new Error("Session closed before bootstrapState response arrived");
10733
- }
10734
- if (resp.subtype === "error") {
10735
- throw new Error(resp.error ?? "bootstrapState failed");
10736
- }
10737
- const payload = resp.response;
10738
- const state = {
10739
- agentId: payload?.agent_id ?? this._agentId ?? "",
10740
- conversationId: payload?.conversation_id ?? this._conversationId ?? "",
10741
- model: payload?.model,
10742
- memfsEnabled: payload?.memfs_enabled ?? false,
10743
- messages: payload?.messages ?? [],
10744
- nextBefore: payload?.next_before ?? null,
10745
- hasMore: payload?.has_more ?? false,
10746
- hasPendingApproval: payload?.has_pending_approval ?? false
10747
- };
10748
- if (payload?.tools !== undefined)
10749
- state.tools = payload.tools;
10750
- if (payload?.timings !== undefined)
10751
- state.timings = payload.timings;
10752
- return state;
10753
- }
10754
- async updateToolset(_toolsetPreference) {
10755
- throw new Error("updateToolset() is not supported by this session. Use a Cloud, Remote, or local agent session.");
10756
- }
10757
- close() {
10758
- if (this.closed)
10759
- return;
10760
- this.closed = true;
10761
- sessionLog("close", `closing session (agent=${this._agentId}, conversation=${this._conversationId})`);
10762
- this.transport.close();
10763
- this.pumpClosed = true;
10764
- this.resolveAllStreamWaiters(null);
10765
- }
10766
- get agentId() {
10767
- return this._agentId;
10768
- }
10769
- get sessionId() {
10770
- return this._sessionId;
10771
- }
10772
- get conversationId() {
10773
- return this._conversationId;
10774
- }
10775
- async[Symbol.asyncDispose]() {
10776
- this.close();
10777
- }
10778
- transformMessage(wireMsg) {
10779
- if (wireMsg.type === "system" && "subtype" in wireMsg && wireMsg.subtype === "init") {
10780
- const msg = wireMsg;
10781
- return {
10782
- type: "init",
10783
- agentId: msg.agent_id,
10784
- sessionId: msg.session_id,
10785
- conversationId: msg.conversation_id,
10786
- model: msg.model,
10787
- tools: msg.tools,
10788
- memfsEnabled: msg.memfs_enabled,
10789
- skillSources: msg.skill_sources,
10790
- systemInfoReminderEnabled: msg.system_info_reminder_enabled,
10791
- dreaming: msg.reflection_trigger && msg.reflection_behavior && typeof msg.reflection_step_count === "number" ? {
10792
- trigger: msg.reflection_trigger,
10793
- behavior: msg.reflection_behavior,
10794
- stepCount: msg.reflection_step_count
10795
- } : undefined
10796
- };
10797
- }
10798
- if (wireMsg.type === "message" && "message_type" in wireMsg) {
10799
- const msg = wireMsg;
10800
- const runId = msg.run_id || undefined;
10801
- if (msg.message_type === "assistant_message" && msg.content) {
10802
- return {
10803
- type: "assistant",
10804
- content: msg.content,
10805
- uuid: msg.uuid,
10806
- runId
10807
- };
10808
- }
10809
- if (msg.message_type === "tool_call_message" || msg.message_type === "approval_request_message") {
10810
- const toolCallRaw = msg.tool_calls?.[0] || msg.tool_call;
10811
- if (toolCallRaw) {
10812
- const fnObj = toolCallRaw.function;
10813
- const toolCallId = toolCallRaw.tool_call_id ?? toolCallRaw.id;
10814
- if (!toolCallId) {
10815
- const detail = `Missing tool_call_id in ${msg.message_type} (uuid=${msg.uuid || "unknown"})`;
10816
- sessionLog("transform", detail);
10817
- return {
10818
- type: "error",
10819
- message: detail,
10820
- errorCode: "protocol_error",
10821
- stopReason: "protocol_error",
10822
- runId,
10823
- apiError: {
10824
- error_type: "protocol_error",
10825
- detail,
10826
- message_type: msg.message_type
10827
- },
10828
- recoverable: false,
10829
- errorDetail: detail
10830
- };
10831
- }
10832
- const toolName = toolCallRaw.name ?? fnObj?.name ?? "?";
10833
- const toolArgs = toolCallRaw.arguments ?? fnObj?.arguments ?? "";
10834
- let toolInput = {};
10835
- try {
10836
- toolInput = JSON.parse(toolArgs);
10837
- } catch {
10838
- toolInput = { raw: toolArgs };
10839
- }
10840
- return {
10841
- type: "tool_call",
10842
- toolCallId,
10843
- toolName,
10844
- toolInput,
10845
- rawArguments: toolArgs || undefined,
10846
- uuid: msg.uuid,
10847
- runId
10848
- };
10849
- }
10850
- }
10851
- if (msg.message_type === "tool_return_message" && msg.tool_call_id) {
10852
- return {
10853
- type: "tool_result",
10854
- toolCallId: msg.tool_call_id,
10855
- content: msg.tool_return || "",
10856
- isError: msg.status === "error",
10857
- uuid: msg.uuid,
10858
- runId
10859
- };
10860
- }
10861
- if (msg.message_type === "reasoning_message" && msg.reasoning) {
10862
- return {
10863
- type: "reasoning",
10864
- content: msg.reasoning,
10865
- uuid: msg.uuid,
10866
- runId
10867
- };
10868
- }
10869
- }
10870
- if (wireMsg.type === "stream_event") {
10871
- const msg = wireMsg;
10872
- const eventPayload = msg.event ?? {};
10873
- return {
10874
- type: "stream_event",
10875
- event: eventPayload,
10876
- uuid: msg.uuid
10877
- };
10878
- }
10879
- if (wireMsg.type === "result") {
10880
- const msg = wireMsg;
10881
- const runIds = Array.isArray(msg.run_ids) ? msg.run_ids.filter((id) => typeof id === "string") : undefined;
10882
- const approvalConflict = isApprovalConflictSignal2({
10883
- stopReason: msg.stop_reason
10884
- });
10885
- const legacyError = msg.subtype !== "success" ? approvalConflict && msg.subtype === "error" ? "approval_conflict" : msg.subtype : undefined;
10886
- const errorCode = toSdkErrorCode2(legacyError);
10887
- return {
10888
- type: "result",
10889
- success: msg.subtype === "success",
10890
- result: msg.result,
10891
- error: legacyError,
10892
- errorCode,
10893
- ...approvalConflict ? {
10894
- approvalConflict: true,
10895
- recoverable: true
10896
- } : {},
10897
- stopReason: msg.stop_reason,
10898
- durationMs: msg.duration_ms,
10899
- totalCostUsd: msg.total_cost_usd,
10900
- conversationId: msg.conversation_id,
10901
- runIds
10902
- };
10903
- }
10904
- if (wireMsg.type === "error") {
10905
- const msg = wireMsg;
10906
- const errorDetail = extractApiErrorDetail(msg.api_error);
10907
- const approvalConflict = isApprovalConflictSignal2({
10908
- detail: errorDetail,
10909
- message: msg.message,
10910
- stopReason: msg.stop_reason
10911
- });
10912
- const errorCode = approvalConflict ? "approval_conflict" : toSdkErrorCode2(msg.stop_reason);
10913
- return {
10914
- type: "error",
10915
- message: msg.message,
10916
- ...errorCode ? { errorCode } : {},
10917
- ...approvalConflict ? {
10918
- approvalConflict: true,
10919
- recoverable: true
10920
- } : {},
10921
- ...errorDetail ? { errorDetail } : {},
10922
- stopReason: msg.stop_reason,
10923
- runId: msg.run_id,
10924
- apiError: msg.api_error
10925
- };
10926
- }
10927
- if (wireMsg.type === "retry") {
10928
- const msg = wireMsg;
10929
- return {
10930
- type: "retry",
10931
- reason: msg.reason,
10932
- attempt: msg.attempt,
10933
- maxAttempts: msg.max_attempts,
10934
- delayMs: msg.delay_ms,
10935
- runId: msg.run_id
10936
- };
10937
- }
10938
- return null;
10939
- }
10940
- }
10941
-
10942
- // 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
- if (agentId) {
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 = agentId ? createSession(agentId) : createSession();
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=01C64A8CE3FD2BF964756E2164756E21
10153
+ //# debugId=3A7EA512AEF37C3B64756E2164756E21