@letta-ai/letta-agent-sdk 0.3.3 → 0.5.1

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