@letta-ai/letta-agent-sdk 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -701,7 +701,56 @@ function createAppServerClient(options) {
701
701
  return new AppServerClient(options);
702
702
  }
703
703
 
704
+ // src/request-ids.ts
705
+ var processRequestCounter = 0;
706
+ function createRequestIdGenerator() {
707
+ const nonce = Math.random().toString(36).slice(2, 10);
708
+ return (prefix = "req") => `${prefix}-${nonce}-${++processRequestCounter}`;
709
+ }
710
+ function applyUniqueRequestIds(client) {
711
+ client.nextRequestId = createRequestIdGenerator();
712
+ return client;
713
+ }
714
+
704
715
  // src/app-server-management.ts
716
+ var DEFAULT_IDLE_LINGER_MS = 250;
717
+ var registeredTransportsByUrl = new Map;
718
+ function appServerUrlKey(value) {
719
+ try {
720
+ const url = new URL(value);
721
+ if (url.protocol === "http:")
722
+ url.protocol = "ws:";
723
+ if (url.protocol === "https:")
724
+ url.protocol = "wss:";
725
+ url.hash = "";
726
+ url.searchParams.delete("channel");
727
+ return url.toString();
728
+ } catch {
729
+ return value;
730
+ }
731
+ }
732
+ function registerManagementTransport(url, transport) {
733
+ const key = appServerUrlKey(url);
734
+ const registration = { transport };
735
+ const transports = registeredTransportsByUrl.get(key) ?? new Set;
736
+ transports.add(registration);
737
+ registeredTransportsByUrl.set(key, transports);
738
+ return () => {
739
+ transports.delete(registration);
740
+ if (transports.size === 0) {
741
+ registeredTransportsByUrl.delete(key);
742
+ }
743
+ };
744
+ }
745
+ async function releaseAppServerManagementConnections(url) {
746
+ const key = appServerUrlKey(url);
747
+ while (true) {
748
+ const transports = [...registeredTransportsByUrl.get(key) ?? []];
749
+ if (transports.length === 0)
750
+ return;
751
+ await Promise.all(transports.map(({ transport }) => transport.releaseIdleConnection()));
752
+ }
753
+ }
705
754
  function ensureResponse(response, value, fallback) {
706
755
  if (!response.success || value == null) {
707
756
  throw new Error(response.error ?? fallback);
@@ -737,8 +786,16 @@ function stringRecord(value) {
737
786
 
738
787
  class AppServerManagementTransport {
739
788
  options;
789
+ connectionPromise = null;
790
+ releasePromise = null;
791
+ closingConnections = new Set;
792
+ inFlightRequests = 0;
793
+ idleWaiters = new Set;
794
+ idleTimer = null;
795
+ idleLingerMs;
740
796
  constructor(options) {
741
797
  this.options = options;
798
+ this.idleLingerMs = options.idleLingerMs ?? DEFAULT_IDLE_LINGER_MS;
742
799
  }
743
800
  async listAgents(query) {
744
801
  const response = await this.request("agent_list", { query }, "agent_list_response");
@@ -805,38 +862,206 @@ class AppServerManagementTransport {
805
862
  }
806
863
  return { messages: response.messages };
807
864
  }
865
+ releaseIdleConnection() {
866
+ this.clearIdleTimer();
867
+ if (this.releasePromise)
868
+ return this.releasePromise;
869
+ const release = this.releaseConnectionWhenIdle();
870
+ this.releasePromise = release;
871
+ release.then(() => {
872
+ if (this.releasePromise === release) {
873
+ this.releasePromise = null;
874
+ }
875
+ }, () => {
876
+ if (this.releasePromise === release) {
877
+ this.releasePromise = null;
878
+ }
879
+ });
880
+ return release;
881
+ }
808
882
  async request(type, body, responseType) {
809
- const ownedConnection = this.options.url ? null : await this.options.connect?.();
883
+ if (this.releasePromise) {
884
+ await this.releasePromise;
885
+ }
886
+ if (this.closingConnections.size > 0) {
887
+ await Promise.all([...this.closingConnections]);
888
+ }
889
+ this.clearIdleTimer();
890
+ this.inFlightRequests += 1;
891
+ try {
892
+ const { client } = await this.acquireConnection();
893
+ const command = {
894
+ type,
895
+ request_id: client.nextRequestId(type),
896
+ ...body
897
+ };
898
+ const response = await client.request(command, {
899
+ predicate: (message) => message.type === responseType
900
+ });
901
+ return response;
902
+ } finally {
903
+ this.inFlightRequests -= 1;
904
+ if (this.inFlightRequests === 0) {
905
+ const waiters = [...this.idleWaiters];
906
+ this.idleWaiters.clear();
907
+ for (const resolve of waiters)
908
+ resolve();
909
+ if (!this.releasePromise) {
910
+ this.scheduleIdleRelease();
911
+ }
912
+ }
913
+ }
914
+ }
915
+ async releaseConnectionWhenIdle() {
916
+ if (this.inFlightRequests > 0) {
917
+ await new Promise((resolve) => {
918
+ this.idleWaiters.add(resolve);
919
+ });
920
+ }
921
+ this.clearIdleTimer();
922
+ const promise = this.connectionPromise;
923
+ try {
924
+ if (promise) {
925
+ this.connectionPromise = null;
926
+ await this.trackClosingConnection(await promise);
927
+ }
928
+ if (this.closingConnections.size > 0) {
929
+ await Promise.all([...this.closingConnections]);
930
+ }
931
+ } catch {}
932
+ }
933
+ acquireConnection() {
934
+ if (this.connectionPromise)
935
+ return this.connectionPromise;
936
+ const promise = this.openConnection().then((connection) => {
937
+ connection.detachDisconnect = connection.client.onDisconnect(() => {
938
+ this.discardConnection(promise, connection);
939
+ });
940
+ return connection;
941
+ }, (error) => {
942
+ if (this.connectionPromise === promise) {
943
+ this.connectionPromise = null;
944
+ }
945
+ throw error;
946
+ });
947
+ this.connectionPromise = promise;
948
+ return promise;
949
+ }
950
+ async openConnection() {
951
+ const ownedConnection = this.options.url ? null : await this.options.connect?.() ?? null;
810
952
  const url = this.options.url ?? ownedConnection?.url;
811
953
  if (!url) {
812
954
  throw new Error("App-server management requires a url or connect hook.");
813
955
  }
956
+ const unregister = registerManagementTransport(url, this);
814
957
  let client = null;
815
958
  try {
816
- client = createAppServerClient({
959
+ client = applyUniqueRequestIds(createAppServerClient({
817
960
  url,
818
961
  ...this.options.authToken !== undefined ? { authToken: this.options.authToken } : {},
819
962
  ...this.options.WebSocket ? {
820
963
  WebSocket: this.options.WebSocket
821
964
  } : {},
822
965
  ...this.options.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.options.requestTimeoutMs } : {}
823
- });
966
+ }));
824
967
  await client.connect();
825
- const command = {
826
- type,
827
- request_id: client.nextRequestId(type),
828
- ...body
829
- };
830
- const response = await client.request(command, {
831
- predicate: (message) => message.type === responseType
832
- });
833
- return response;
834
- } finally {
835
- client?.close();
836
- ownedConnection?.close();
968
+ } catch (error) {
969
+ try {
970
+ if (client) {
971
+ const controlClosed = waitForSocketClose(client.control);
972
+ client.close();
973
+ ownedConnection?.close();
974
+ await controlClosed;
975
+ } else {
976
+ ownedConnection?.close();
977
+ }
978
+ } catch {} finally {
979
+ unregister();
980
+ }
981
+ throw error;
837
982
  }
983
+ return {
984
+ client,
985
+ ownedConnection,
986
+ detachDisconnect: () => {},
987
+ unregister
988
+ };
989
+ }
990
+ discardConnection(promise, connection) {
991
+ if (this.connectionPromise === promise) {
992
+ this.connectionPromise = null;
993
+ this.clearIdleTimer();
994
+ }
995
+ this.trackClosingConnection(connection);
996
+ }
997
+ trackClosingConnection(connection) {
998
+ const closing = closeConnection(connection);
999
+ this.closingConnections.add(closing);
1000
+ closing.then(() => this.closingConnections.delete(closing), () => this.closingConnections.delete(closing));
1001
+ return closing;
1002
+ }
1003
+ scheduleIdleRelease() {
1004
+ if (!this.connectionPromise)
1005
+ return;
1006
+ this.clearIdleTimer();
1007
+ const timer = setTimeout(() => {
1008
+ this.idleTimer = null;
1009
+ this.releaseIdleConnection();
1010
+ }, this.idleLingerMs);
1011
+ this.idleTimer = timer;
1012
+ timer.unref?.();
1013
+ }
1014
+ clearIdleTimer() {
1015
+ if (this.idleTimer === null)
1016
+ return;
1017
+ clearTimeout(this.idleTimer);
1018
+ this.idleTimer = null;
1019
+ }
1020
+ }
1021
+ async function closeConnection(connection) {
1022
+ connection.detachDisconnect();
1023
+ const controlClosed = waitForSocketClose(connection.client.control);
1024
+ try {
1025
+ connection.client.close();
1026
+ connection.ownedConnection?.close();
1027
+ await controlClosed;
1028
+ } finally {
1029
+ connection.unregister();
838
1030
  }
839
1031
  }
1032
+ function waitForSocketClose(socket) {
1033
+ if (socket.readyState === 3)
1034
+ return Promise.resolve();
1035
+ return new Promise((resolve) => {
1036
+ let settled = false;
1037
+ let detach = () => {};
1038
+ const finish = () => {
1039
+ if (settled)
1040
+ return;
1041
+ settled = true;
1042
+ clearTimeout(timeout);
1043
+ detach();
1044
+ resolve();
1045
+ };
1046
+ if (socket.addEventListener) {
1047
+ socket.addEventListener("close", finish);
1048
+ detach = () => socket.removeEventListener?.("close", finish);
1049
+ } else if (socket.once) {
1050
+ socket.once("close", finish);
1051
+ detach = () => socket.off?.("close", finish);
1052
+ } else if (socket.on) {
1053
+ socket.on("close", finish);
1054
+ detach = () => socket.off?.("close", finish);
1055
+ } else {
1056
+ resolve();
1057
+ return;
1058
+ }
1059
+ const timeout = setTimeout(finish, 1000);
1060
+ timeout.unref?.();
1061
+ if (socket.readyState === 3)
1062
+ finish();
1063
+ });
1064
+ }
840
1065
 
841
1066
  // node_modules/@letta-ai/letta-code/dist/agent-presets.js
842
1067
  var LETTA_CODE_ORIGIN_TAG = "origin:letta-code";
@@ -5914,6 +6139,7 @@ class RemoteClientSessionCore {
5914
6139
  _modelSettings = null;
5915
6140
  label;
5916
6141
  requestTimeoutMs;
6142
+ initializePromise = null;
5917
6143
  streamQueue = [];
5918
6144
  streamResolvers = [];
5919
6145
  removeMessageHandler = null;
@@ -5931,43 +6157,72 @@ class RemoteClientSessionCore {
5931
6157
  this.requestTimeoutMs = config.requestTimeoutMs;
5932
6158
  }
5933
6159
  async initialize() {
6160
+ if (this.closed) {
6161
+ throw new Error("Session is closed");
6162
+ }
6163
+ if (this.initializePromise) {
6164
+ return this.initializePromise;
6165
+ }
5934
6166
  if (this.initialized) {
5935
6167
  throw new Error("Session already initialized");
5936
6168
  }
6169
+ const attempt = this.performInitialize();
6170
+ const memo = attempt.catch((error) => {
6171
+ this.cleanupFailedInitialize();
6172
+ throw error;
6173
+ }).finally(() => {
6174
+ if (this.initializePromise === memo) {
6175
+ this.initializePromise = null;
6176
+ }
6177
+ });
6178
+ this.initializePromise = memo;
6179
+ return memo;
6180
+ }
6181
+ async performInitialize() {
6182
+ const init = await this.initializeRuntimeController();
6183
+ this.controller = init.controller;
6184
+ this.runtime = init.runtime;
6185
+ this._agentId = init.runtime.agent_id;
6186
+ this._conversationId = init.runtime.conversation_id;
6187
+ this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
6188
+ this._modelSettings = init.modelSettings ?? null;
6189
+ this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
6190
+ this.toolNames = init.tools;
6191
+ this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
6192
+ await this.afterRuntimeInitialized();
6193
+ await this.applyPostInitializeOptions();
5937
6194
  if (this.closed) {
5938
6195
  throw new Error("Session is closed");
5939
6196
  }
5940
- try {
5941
- const init = await this.initializeRuntimeController();
5942
- this.controller = init.controller;
5943
- this.runtime = init.runtime;
5944
- this._agentId = init.runtime.agent_id;
5945
- this._conversationId = init.runtime.conversation_id;
5946
- this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
5947
- this._modelSettings = init.modelSettings ?? null;
5948
- this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
5949
- this.toolNames = init.tools;
5950
- this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
5951
- this.initialized = true;
5952
- await this.afterRuntimeInitialized();
5953
- await this.applyPostInitializeOptions();
5954
- const initMessage = {
5955
- type: "init",
5956
- agentId: init.runtime.agent_id,
5957
- sessionId: this._sessionId,
5958
- conversationId: init.runtime.conversation_id,
5959
- model: this._model
5960
- };
5961
- if (this.toolNames !== undefined)
5962
- initMessage.tools = this.toolNames;
5963
- if (init.skillSources !== undefined) {
5964
- initMessage.skillSources = init.skillSources;
5965
- }
5966
- return initMessage;
5967
- } catch (error) {
5968
- this.close();
5969
- throw error;
6197
+ this.initialized = true;
6198
+ const initMessage = {
6199
+ type: "init",
6200
+ agentId: init.runtime.agent_id,
6201
+ sessionId: this._sessionId,
6202
+ conversationId: init.runtime.conversation_id,
6203
+ model: this._model
6204
+ };
6205
+ if (this.toolNames !== undefined)
6206
+ initMessage.tools = this.toolNames;
6207
+ if (init.skillSources !== undefined) {
6208
+ initMessage.skillSources = init.skillSources;
5970
6209
  }
6210
+ return initMessage;
6211
+ }
6212
+ cleanupFailedInitialize() {
6213
+ this.removeMessageHandler?.();
6214
+ this.removeMessageHandler = null;
6215
+ this.controller?.close();
6216
+ this.controller = null;
6217
+ this.onCoreClose();
6218
+ this.runtime = null;
6219
+ this._agentId = null;
6220
+ this._conversationId = null;
6221
+ this._sessionId = null;
6222
+ this._modelSettings = null;
6223
+ this._model = "";
6224
+ this.toolNames = undefined;
6225
+ this.initialized = false;
5971
6226
  }
5972
6227
  async send(message) {
5973
6228
  if (!this.initialized) {
@@ -6064,6 +6319,12 @@ class RemoteClientSessionCore {
6064
6319
  if (!this.initialized) {
6065
6320
  await this.initialize();
6066
6321
  }
6322
+ if (!this.controller || !this.runtime) {
6323
+ throw new Error("Session is not initialized");
6324
+ }
6325
+ return this.applyModelUpdate(update);
6326
+ }
6327
+ async applyModelUpdate(update) {
6067
6328
  if (!this.controller || !this.runtime) {
6068
6329
  throw new Error("Session is not initialized");
6069
6330
  }
@@ -6480,7 +6741,7 @@ class RemoteClientSessionCore {
6480
6741
  if (this.mode.kind !== "session")
6481
6742
  return;
6482
6743
  if (this.mode.options.model !== undefined || this.mode.options.reasoningEffort !== undefined) {
6483
- await this.updateModel({
6744
+ await this.applyModelUpdate({
6484
6745
  ...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
6485
6746
  ...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
6486
6747
  });
@@ -7243,13 +7504,15 @@ class AppServerSession extends RemoteClientSessionCore {
7243
7504
  return options.memfs !== false;
7244
7505
  }
7245
7506
  async initializeRuntimeController() {
7507
+ await this.remoteOptions.beforeConnect?.();
7246
7508
  const url = await this.resolveAppServerUrl();
7247
- const client = createAppServerClient({
7509
+ await releaseAppServerManagementConnections(url);
7510
+ const client = applyUniqueRequestIds(createAppServerClient({
7248
7511
  url,
7249
7512
  ...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
7250
7513
  ...this.remoteOptions.WebSocket ? { WebSocket: this.remoteOptions.WebSocket } : {},
7251
7514
  ...this.remoteOptions.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.remoteOptions.requestTimeoutMs } : {}
7252
- });
7515
+ }));
7253
7516
  this.removeControlRequestHandler = registerAppServerControlRequestHandler({
7254
7517
  client,
7255
7518
  getRuntime: () => this.runtime,
@@ -8120,14 +8383,14 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
8120
8383
  apiKey,
8121
8384
  authMode: this.cloudOptions.webSocketAuth ?? "header"
8122
8385
  });
8123
- const client = createAppServerClient({
8386
+ const client = applyUniqueRequestIds(createAppServerClient({
8124
8387
  url,
8125
8388
  WebSocket: createCloudStatusWebSocketConstructor({
8126
8389
  cloudOptions: this.cloudOptions,
8127
8390
  runtime: resolved.runtime
8128
8391
  }),
8129
8392
  requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
8130
- });
8393
+ }));
8131
8394
  this.removeControlRequestHandler = registerAppServerControlRequestHandler({
8132
8395
  client,
8133
8396
  getRuntime: () => this.runtime,
@@ -8865,6 +9128,10 @@ class LettaAgentClientBase {
8865
9128
  if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
8866
9129
  throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
8867
9130
  }
9131
+ const idleLingerMs = localOptions.appServer?.idleLingerMs;
9132
+ if (idleLingerMs !== undefined && (!Number.isInteger(idleLingerMs) || idleLingerMs < 0)) {
9133
+ throw new Error("Invalid appServer.idleLingerMs. Expected a non-negative integer.");
9134
+ }
8868
9135
  const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
8869
9136
  if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
8870
9137
  throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
@@ -8877,6 +9144,9 @@ class LettaAgentClientBase {
8877
9144
  if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
8878
9145
  throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
8879
9146
  }
9147
+ if (options.idleLingerMs !== undefined && (!Number.isInteger(options.idleLingerMs) || options.idleLingerMs < 0)) {
9148
+ throw new Error("Invalid idleLingerMs. Expected a non-negative integer.");
9149
+ }
8880
9150
  }
8881
9151
  if (this.backend === "cloud") {
8882
9152
  validateCloudClientOptions(options);
@@ -8891,7 +9161,7 @@ class LettaAgentClientBase {
8891
9161
  }
8892
9162
  validateCreateAgentOptions(options);
8893
9163
  if (this.backend === "remote") {
8894
- const session = new AppServerSession(this.remoteOptions(), {
9164
+ const session = new AppServerSession(this.appServerSessionOptions(), {
8895
9165
  kind: "create-agent",
8896
9166
  options
8897
9167
  });
@@ -8914,7 +9184,7 @@ class LettaAgentClientBase {
8914
9184
  if (!agentId) {
8915
9185
  throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
8916
9186
  }
8917
- return new AppServerSession(this.remoteOptions(), {
9187
+ return new AppServerSession(this.appServerSessionOptions(), {
8918
9188
  kind: "session",
8919
9189
  agentId,
8920
9190
  newConversation: true,
@@ -8940,13 +9210,13 @@ class LettaAgentClientBase {
8940
9210
  validateCreateSessionOptions(sessionOptions);
8941
9211
  if (this.backend === "remote") {
8942
9212
  if (looksLikeConversationId(id)) {
8943
- return new AppServerSession(this.remoteOptions(), {
9213
+ return new AppServerSession(this.appServerSessionOptions(), {
8944
9214
  kind: "session",
8945
9215
  conversationId: id,
8946
9216
  options
8947
9217
  });
8948
9218
  }
8949
- return new AppServerSession(this.remoteOptions(), {
9219
+ return new AppServerSession(this.appServerSessionOptions(), {
8950
9220
  kind: "session",
8951
9221
  agentId: id,
8952
9222
  defaultConversation: true,
@@ -9045,6 +9315,12 @@ class LettaAgentClientBase {
9045
9315
  }
9046
9316
  return this.options;
9047
9317
  }
9318
+ appServerSessionOptions() {
9319
+ return {
9320
+ ...this.remoteOptions(),
9321
+ beforeConnect: () => this.releaseIdleManagementConnection()
9322
+ };
9323
+ }
9048
9324
  getRepositoriesClient() {
9049
9325
  if (this.backend !== "cloud") {
9050
9326
  throw new Error('client.repositories is only available with backend: "cloud".');
@@ -9052,6 +9328,9 @@ class LettaAgentClientBase {
9052
9328
  this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
9053
9329
  return this.repositoriesClient;
9054
9330
  }
9331
+ async releaseIdleManagementConnection() {
9332
+ await this.managementTransport?.releaseIdleConnection?.();
9333
+ }
9055
9334
  getManagementTransport() {
9056
9335
  if (this.managementTransport)
9057
9336
  return this.managementTransport;
@@ -9201,7 +9480,7 @@ ${output.trim()}` : ""}`));
9201
9480
  }
9202
9481
 
9203
9482
  // src/local-app-server-session.ts
9204
- function createLocalAppServerSession(options, mode) {
9483
+ function createLocalAppServerSession(options, mode, beforeConnect) {
9205
9484
  const appServer = options ?? {};
9206
9485
  const sessionOptions = {
9207
9486
  ...appServer.url !== undefined ? { url: appServer.url } : {
@@ -9214,7 +9493,8 @@ function createLocalAppServerSession(options, mode) {
9214
9493
  },
9215
9494
  ...appServer.WebSocket !== undefined ? { WebSocket: appServer.WebSocket } : {},
9216
9495
  ...appServer.requestTimeoutMs !== undefined ? { requestTimeoutMs: appServer.requestTimeoutMs } : {},
9217
- ...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {}
9496
+ ...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {},
9497
+ ...beforeConnect ? { beforeConnect } : {}
9218
9498
  };
9219
9499
  return new AppServerSession(sessionOptions, mode);
9220
9500
  }
@@ -9614,6 +9894,8 @@ class Session {
9614
9894
  _sessionId = null;
9615
9895
  _conversationId = null;
9616
9896
  initialized = false;
9897
+ closed = false;
9898
+ initializePromise = null;
9617
9899
  externalTools = new Map;
9618
9900
  streamQueue = [];
9619
9901
  streamResolvers = [];
@@ -9636,9 +9918,28 @@ class Session {
9636
9918
  }
9637
9919
  }
9638
9920
  async initialize() {
9921
+ if (this.closed) {
9922
+ throw new Error("Session is closed");
9923
+ }
9924
+ if (this.initializePromise) {
9925
+ return this.initializePromise;
9926
+ }
9639
9927
  if (this.initialized) {
9640
9928
  throw new Error("Session already initialized");
9641
9929
  }
9930
+ const attempt = this.performInitialize();
9931
+ const memo = attempt.catch((error) => {
9932
+ this.cleanupFailedInitialize();
9933
+ throw error;
9934
+ }).finally(() => {
9935
+ if (this.initializePromise === memo) {
9936
+ this.initializePromise = null;
9937
+ }
9938
+ });
9939
+ this.initializePromise = memo;
9940
+ return memo;
9941
+ }
9942
+ async performInitialize() {
9642
9943
  sessionLog("init", "connecting transport...");
9643
9944
  await this.transport.connect();
9644
9945
  sessionLog("init", "transport connected, sending initialize request");
@@ -9663,11 +9964,14 @@ class Session {
9663
9964
  this._agentId = initMsg.agent_id;
9664
9965
  this._sessionId = initMsg.session_id;
9665
9966
  this._conversationId = initMsg.conversation_id;
9666
- this.initialized = true;
9667
- this.startBackgroundPump();
9668
9967
  if (this.externalTools.size > 0) {
9669
9968
  await this.registerExternalTools();
9670
9969
  }
9970
+ if (this.closed) {
9971
+ throw new Error("Session is closed");
9972
+ }
9973
+ this.initialized = true;
9974
+ this.startBackgroundPump();
9671
9975
  const allTools = [
9672
9976
  ...initMsg.tools,
9673
9977
  ...Array.from(this.externalTools.keys())
@@ -9698,6 +10002,16 @@ ${stderr}` : "";
9698
10002
  sessionLog("init", `ERROR: transport closed before init message received${detail}`);
9699
10003
  throw new Error(`Failed to initialize session - no init message received${detail}`);
9700
10004
  }
10005
+ cleanupFailedInitialize() {
10006
+ this.transport.close();
10007
+ this.closed = true;
10008
+ this.initialized = false;
10009
+ this._agentId = null;
10010
+ this._sessionId = null;
10011
+ this._conversationId = null;
10012
+ this.pumpClosed = true;
10013
+ this.resolveAllStreamWaiters(null);
10014
+ }
9701
10015
  async send(message) {
9702
10016
  if (!this.initialized) {
9703
10017
  sessionLog("send", "auto-initializing (not yet initialized)");
@@ -10374,6 +10688,9 @@ ${stderr}` : "";
10374
10688
  throw new Error("updateToolset() is not supported by this session. Use a Cloud, Remote, or local agent session.");
10375
10689
  }
10376
10690
  close() {
10691
+ if (this.closed)
10692
+ return;
10693
+ this.closed = true;
10377
10694
  sessionLog("close", `closing session (agent=${this._agentId}, conversation=${this._conversationId})`);
10378
10695
  this.transport.close();
10379
10696
  this.pumpClosed = true;
@@ -10571,7 +10888,8 @@ class LettaAgentClient extends LettaAgentClientBase {
10571
10888
  })
10572
10889
  },
10573
10890
  ...localOptions?.WebSocket !== undefined ? { WebSocket: localOptions.WebSocket } : {},
10574
- ...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {}
10891
+ ...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {},
10892
+ ...localOptions?.idleLingerMs !== undefined ? { idleLingerMs: localOptions.idleLingerMs } : {}
10575
10893
  });
10576
10894
  }
10577
10895
  async createLocalAgent(options) {
@@ -10580,7 +10898,7 @@ class LettaAgentClient extends LettaAgentClientBase {
10580
10898
  const session2 = createLocalAppServerSession(localOptions.appServer, {
10581
10899
  kind: "create-agent",
10582
10900
  options
10583
- });
10901
+ }, () => this.releaseIdleManagementConnection());
10584
10902
  const initMsg2 = await session2.initialize();
10585
10903
  session2.close();
10586
10904
  return initMsg2.agentId;
@@ -10598,7 +10916,7 @@ class LettaAgentClient extends LettaAgentClientBase {
10598
10916
  agentId,
10599
10917
  newConversation: true,
10600
10918
  options
10601
- });
10919
+ }, () => this.releaseIdleManagementConnection());
10602
10920
  }
10603
10921
  if (agentId) {
10604
10922
  return new Session({ ...sessionOptions, agentId, newConversation: true });
@@ -10613,14 +10931,14 @@ class LettaAgentClient extends LettaAgentClientBase {
10613
10931
  kind: "session",
10614
10932
  conversationId: id,
10615
10933
  options
10616
- });
10934
+ }, () => this.releaseIdleManagementConnection());
10617
10935
  }
10618
10936
  return createLocalAppServerSession(localOptions.appServer, {
10619
10937
  kind: "session",
10620
10938
  agentId: id,
10621
10939
  defaultConversation: true,
10622
10940
  options
10623
- });
10941
+ }, () => this.releaseIdleManagementConnection());
10624
10942
  }
10625
10943
  if (looksLikeConversationId2(id)) {
10626
10944
  return new Session({ ...sessionOptions, conversationId: id });
@@ -10888,4 +11206,4 @@ export {
10888
11206
  CloudManagedSandboxExpiredError
10889
11207
  };
10890
11208
 
10891
- //# debugId=D44861B7D6BE1C4C64756E2164756E21
11209
+ //# debugId=98DBA625B202A81364756E2164756E21