@letta-ai/letta-agent-sdk 0.3.0 → 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.
@@ -682,18 +682,101 @@ function createAppServerClient(options) {
682
682
  return new AppServerClient(options);
683
683
  }
684
684
 
685
+ // src/request-ids.ts
686
+ var processRequestCounter = 0;
687
+ function createRequestIdGenerator() {
688
+ const nonce = Math.random().toString(36).slice(2, 10);
689
+ return (prefix = "req") => `${prefix}-${nonce}-${++processRequestCounter}`;
690
+ }
691
+ function applyUniqueRequestIds(client) {
692
+ client.nextRequestId = createRequestIdGenerator();
693
+ return client;
694
+ }
695
+
685
696
  // src/app-server-management.ts
697
+ var DEFAULT_IDLE_LINGER_MS = 250;
698
+ var registeredTransportsByUrl = new Map;
699
+ function appServerUrlKey(value) {
700
+ try {
701
+ const url = new URL(value);
702
+ if (url.protocol === "http:")
703
+ url.protocol = "ws:";
704
+ if (url.protocol === "https:")
705
+ url.protocol = "wss:";
706
+ url.hash = "";
707
+ url.searchParams.delete("channel");
708
+ return url.toString();
709
+ } catch {
710
+ return value;
711
+ }
712
+ }
713
+ function registerManagementTransport(url, transport) {
714
+ const key = appServerUrlKey(url);
715
+ const registration = { transport };
716
+ const transports = registeredTransportsByUrl.get(key) ?? new Set;
717
+ transports.add(registration);
718
+ registeredTransportsByUrl.set(key, transports);
719
+ return () => {
720
+ transports.delete(registration);
721
+ if (transports.size === 0) {
722
+ registeredTransportsByUrl.delete(key);
723
+ }
724
+ };
725
+ }
726
+ async function releaseAppServerManagementConnections(url) {
727
+ const key = appServerUrlKey(url);
728
+ while (true) {
729
+ const transports = [...registeredTransportsByUrl.get(key) ?? []];
730
+ if (transports.length === 0)
731
+ return;
732
+ await Promise.all(transports.map(({ transport }) => transport.releaseIdleConnection()));
733
+ }
734
+ }
686
735
  function ensureResponse(response, value, fallback) {
687
736
  if (!response.success || value == null) {
688
737
  throw new Error(response.error ?? fallback);
689
738
  }
690
739
  return value;
691
740
  }
741
+ function modelEntries(value) {
742
+ if (!Array.isArray(value))
743
+ return [];
744
+ return value.flatMap((entry) => {
745
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
746
+ return [];
747
+ const record = entry;
748
+ if (typeof record.id !== "string" || typeof record.handle !== "string" || typeof record.label !== "string" || typeof record.description !== "string") {
749
+ return [];
750
+ }
751
+ return [{
752
+ ...record,
753
+ id: record.id,
754
+ handle: record.handle,
755
+ label: record.label,
756
+ description: record.description
757
+ }];
758
+ });
759
+ }
760
+ function stringRecord(value) {
761
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
762
+ return;
763
+ }
764
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
765
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
766
+ }
692
767
 
693
768
  class AppServerManagementTransport {
694
769
  options;
770
+ connectionPromise = null;
771
+ releasePromise = null;
772
+ closingConnections = new Set;
773
+ inFlightRequests = 0;
774
+ idleWaiters = new Set;
775
+ idleTimer = null;
776
+ idleLingerMs;
695
777
  constructor(options) {
696
778
  this.options = options;
779
+ this.idleLingerMs = options.idleLingerMs ?? DEFAULT_IDLE_LINGER_MS;
697
780
  }
698
781
  async listAgents(query) {
699
782
  const response = await this.request("agent_list", { query }, "agent_list_response");
@@ -710,6 +793,30 @@ class AppServerManagementTransport {
710
793
  const response = await this.request("agent_update", { agent_id: agentId, body }, "agent_update_response");
711
794
  return ensureResponse(response, response.agent, `Failed to update agent ${agentId}.`);
712
795
  }
796
+ async deleteAgent(agentId) {
797
+ const response = await this.request("agent_delete", { agent_id: agentId }, "agent_delete_response");
798
+ if (!response.success) {
799
+ throw new Error(response.error ?? `Failed to delete agent ${agentId}.`);
800
+ }
801
+ }
802
+ async listModels() {
803
+ const response = await this.request("list_models", {}, "list_models_response");
804
+ if (!response.success) {
805
+ throw new Error(response.error ?? "Failed to list models.");
806
+ }
807
+ const result = {
808
+ entries: modelEntries(response.entries)
809
+ };
810
+ if (response.available_handles === null) {
811
+ result.availableHandles = null;
812
+ } else if (Array.isArray(response.available_handles)) {
813
+ result.availableHandles = response.available_handles.filter((handle) => typeof handle === "string");
814
+ }
815
+ const aliases = stringRecord(response.byok_provider_aliases);
816
+ if (aliases)
817
+ result.byokProviderAliases = aliases;
818
+ return result;
819
+ }
713
820
  async listConversations(query) {
714
821
  const response = await this.request("conversation_list", { query }, "conversation_list_response");
715
822
  if (!response.success) {
@@ -736,38 +843,206 @@ class AppServerManagementTransport {
736
843
  }
737
844
  return { messages: response.messages };
738
845
  }
846
+ releaseIdleConnection() {
847
+ this.clearIdleTimer();
848
+ if (this.releasePromise)
849
+ return this.releasePromise;
850
+ const release = this.releaseConnectionWhenIdle();
851
+ this.releasePromise = release;
852
+ release.then(() => {
853
+ if (this.releasePromise === release) {
854
+ this.releasePromise = null;
855
+ }
856
+ }, () => {
857
+ if (this.releasePromise === release) {
858
+ this.releasePromise = null;
859
+ }
860
+ });
861
+ return release;
862
+ }
739
863
  async request(type, body, responseType) {
740
- const ownedConnection = this.options.url ? null : await this.options.connect?.();
864
+ if (this.releasePromise) {
865
+ await this.releasePromise;
866
+ }
867
+ if (this.closingConnections.size > 0) {
868
+ await Promise.all([...this.closingConnections]);
869
+ }
870
+ this.clearIdleTimer();
871
+ this.inFlightRequests += 1;
872
+ try {
873
+ const { client } = await this.acquireConnection();
874
+ const command = {
875
+ type,
876
+ request_id: client.nextRequestId(type),
877
+ ...body
878
+ };
879
+ const response = await client.request(command, {
880
+ predicate: (message) => message.type === responseType
881
+ });
882
+ return response;
883
+ } finally {
884
+ this.inFlightRequests -= 1;
885
+ if (this.inFlightRequests === 0) {
886
+ const waiters = [...this.idleWaiters];
887
+ this.idleWaiters.clear();
888
+ for (const resolve of waiters)
889
+ resolve();
890
+ if (!this.releasePromise) {
891
+ this.scheduleIdleRelease();
892
+ }
893
+ }
894
+ }
895
+ }
896
+ async releaseConnectionWhenIdle() {
897
+ if (this.inFlightRequests > 0) {
898
+ await new Promise((resolve) => {
899
+ this.idleWaiters.add(resolve);
900
+ });
901
+ }
902
+ this.clearIdleTimer();
903
+ const promise = this.connectionPromise;
904
+ try {
905
+ if (promise) {
906
+ this.connectionPromise = null;
907
+ await this.trackClosingConnection(await promise);
908
+ }
909
+ if (this.closingConnections.size > 0) {
910
+ await Promise.all([...this.closingConnections]);
911
+ }
912
+ } catch {}
913
+ }
914
+ acquireConnection() {
915
+ if (this.connectionPromise)
916
+ return this.connectionPromise;
917
+ const promise = this.openConnection().then((connection) => {
918
+ connection.detachDisconnect = connection.client.onDisconnect(() => {
919
+ this.discardConnection(promise, connection);
920
+ });
921
+ return connection;
922
+ }, (error) => {
923
+ if (this.connectionPromise === promise) {
924
+ this.connectionPromise = null;
925
+ }
926
+ throw error;
927
+ });
928
+ this.connectionPromise = promise;
929
+ return promise;
930
+ }
931
+ async openConnection() {
932
+ const ownedConnection = this.options.url ? null : await this.options.connect?.() ?? null;
741
933
  const url = this.options.url ?? ownedConnection?.url;
742
934
  if (!url) {
743
935
  throw new Error("App-server management requires a url or connect hook.");
744
936
  }
937
+ const unregister = registerManagementTransport(url, this);
745
938
  let client = null;
746
939
  try {
747
- client = createAppServerClient({
940
+ client = applyUniqueRequestIds(createAppServerClient({
748
941
  url,
749
942
  ...this.options.authToken !== undefined ? { authToken: this.options.authToken } : {},
750
943
  ...this.options.WebSocket ? {
751
944
  WebSocket: this.options.WebSocket
752
945
  } : {},
753
946
  ...this.options.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.options.requestTimeoutMs } : {}
754
- });
947
+ }));
755
948
  await client.connect();
756
- const command = {
757
- type,
758
- request_id: client.nextRequestId(type),
759
- ...body
760
- };
761
- const response = await client.request(command, {
762
- predicate: (message) => message.type === responseType
763
- });
764
- return response;
765
- } finally {
766
- client?.close();
767
- ownedConnection?.close();
949
+ } catch (error) {
950
+ try {
951
+ if (client) {
952
+ const controlClosed = waitForSocketClose(client.control);
953
+ client.close();
954
+ ownedConnection?.close();
955
+ await controlClosed;
956
+ } else {
957
+ ownedConnection?.close();
958
+ }
959
+ } catch {} finally {
960
+ unregister();
961
+ }
962
+ throw error;
768
963
  }
964
+ return {
965
+ client,
966
+ ownedConnection,
967
+ detachDisconnect: () => {},
968
+ unregister
969
+ };
970
+ }
971
+ discardConnection(promise, connection) {
972
+ if (this.connectionPromise === promise) {
973
+ this.connectionPromise = null;
974
+ this.clearIdleTimer();
975
+ }
976
+ this.trackClosingConnection(connection);
977
+ }
978
+ trackClosingConnection(connection) {
979
+ const closing = closeConnection(connection);
980
+ this.closingConnections.add(closing);
981
+ closing.then(() => this.closingConnections.delete(closing), () => this.closingConnections.delete(closing));
982
+ return closing;
983
+ }
984
+ scheduleIdleRelease() {
985
+ if (!this.connectionPromise)
986
+ return;
987
+ this.clearIdleTimer();
988
+ const timer = setTimeout(() => {
989
+ this.idleTimer = null;
990
+ this.releaseIdleConnection();
991
+ }, this.idleLingerMs);
992
+ this.idleTimer = timer;
993
+ timer.unref?.();
994
+ }
995
+ clearIdleTimer() {
996
+ if (this.idleTimer === null)
997
+ return;
998
+ clearTimeout(this.idleTimer);
999
+ this.idleTimer = null;
1000
+ }
1001
+ }
1002
+ async function closeConnection(connection) {
1003
+ connection.detachDisconnect();
1004
+ const controlClosed = waitForSocketClose(connection.client.control);
1005
+ try {
1006
+ connection.client.close();
1007
+ connection.ownedConnection?.close();
1008
+ await controlClosed;
1009
+ } finally {
1010
+ connection.unregister();
769
1011
  }
770
1012
  }
1013
+ function waitForSocketClose(socket) {
1014
+ if (socket.readyState === 3)
1015
+ return Promise.resolve();
1016
+ return new Promise((resolve) => {
1017
+ let settled = false;
1018
+ let detach = () => {};
1019
+ const finish = () => {
1020
+ if (settled)
1021
+ return;
1022
+ settled = true;
1023
+ clearTimeout(timeout);
1024
+ detach();
1025
+ resolve();
1026
+ };
1027
+ if (socket.addEventListener) {
1028
+ socket.addEventListener("close", finish);
1029
+ detach = () => socket.removeEventListener?.("close", finish);
1030
+ } else if (socket.once) {
1031
+ socket.once("close", finish);
1032
+ detach = () => socket.off?.("close", finish);
1033
+ } else if (socket.on) {
1034
+ socket.on("close", finish);
1035
+ detach = () => socket.off?.("close", finish);
1036
+ } else {
1037
+ resolve();
1038
+ return;
1039
+ }
1040
+ const timeout = setTimeout(finish, 1000);
1041
+ timeout.unref?.();
1042
+ if (socket.readyState === 3)
1043
+ finish();
1044
+ });
1045
+ }
771
1046
 
772
1047
  // node_modules/@letta-ai/letta-code/dist/agent-presets.js
773
1048
  var LETTA_CODE_ORIGIN_TAG = "origin:letta-code";
@@ -5405,6 +5680,36 @@ function requiresRuntimeUserInput(toolName) {
5405
5680
  function isHeadlessAutoAllowTool(toolName) {
5406
5681
  return HEADLESS_AUTO_ALLOW_TOOLS.has(toolName);
5407
5682
  }
5683
+ function normalizePermissionSuggestions(value) {
5684
+ if (!Array.isArray(value))
5685
+ return;
5686
+ const suggestions = [];
5687
+ for (const entry of value) {
5688
+ if (!entry || typeof entry !== "object")
5689
+ continue;
5690
+ const record = entry;
5691
+ if (typeof record.id === "string" && typeof record.text === "string") {
5692
+ suggestions.push({ id: record.id, text: record.text });
5693
+ }
5694
+ }
5695
+ return suggestions;
5696
+ }
5697
+ function buildCanUseToolContext(request, requestId) {
5698
+ const context = {};
5699
+ if (typeof requestId === "string")
5700
+ context.requestId = requestId;
5701
+ if (typeof request.tool_call_id === "string")
5702
+ context.toolCallId = request.tool_call_id;
5703
+ const suggestions = normalizePermissionSuggestions(request.permission_suggestions);
5704
+ if (suggestions !== undefined)
5705
+ context.permissionSuggestions = suggestions;
5706
+ if (typeof request.blocked_path === "string" || request.blocked_path === null) {
5707
+ context.blockedPath = request.blocked_path;
5708
+ }
5709
+ if (Array.isArray(request.diffs))
5710
+ context.diffs = request.diffs;
5711
+ return context;
5712
+ }
5408
5713
 
5409
5714
  // src/remote-client-session-core.ts
5410
5715
  var FAILURE_STOP_REASONS = new Set([
@@ -5682,6 +5987,121 @@ function queueItems(message) {
5682
5987
  ];
5683
5988
  });
5684
5989
  }
5990
+ function deviceStatusRecord(message) {
5991
+ if (message.type !== "update_device_status")
5992
+ return null;
5993
+ const status = message.device_status;
5994
+ return status && typeof status === "object" && !Array.isArray(status) ? status : null;
5995
+ }
5996
+ function pendingControlRequests(status) {
5997
+ const pending = status.pending_control_requests;
5998
+ if (!Array.isArray(pending))
5999
+ return [];
6000
+ return pending.flatMap((item) => {
6001
+ if (!item || typeof item !== "object" || Array.isArray(item))
6002
+ return [];
6003
+ const record = item;
6004
+ if (typeof record.request_id !== "string")
6005
+ return [];
6006
+ const request = record.request && typeof record.request === "object" && !Array.isArray(record.request) ? record.request : null;
6007
+ if (!request || typeof request.tool_name !== "string")
6008
+ return [];
6009
+ const entry = {
6010
+ requestId: record.request_id,
6011
+ toolName: request.tool_name,
6012
+ permissionSuggestions: permissionSuggestions(request.permission_suggestions),
6013
+ blockedPath: typeof request.blocked_path === "string" || request.blocked_path === null ? request.blocked_path : null
6014
+ };
6015
+ if (typeof request.tool_call_id === "string")
6016
+ entry.toolCallId = request.tool_call_id;
6017
+ if (request.input && typeof request.input === "object" && !Array.isArray(request.input)) {
6018
+ entry.toolInput = request.input;
6019
+ }
6020
+ const previews = diffPreviews(request.diffs);
6021
+ if (previews !== undefined)
6022
+ entry.diffs = previews;
6023
+ return [entry];
6024
+ });
6025
+ }
6026
+ function permissionSuggestions(value) {
6027
+ if (!Array.isArray(value))
6028
+ return [];
6029
+ return value.flatMap((item) => {
6030
+ if (!item || typeof item !== "object" || Array.isArray(item))
6031
+ return [];
6032
+ const record = item;
6033
+ return typeof record.id === "string" && typeof record.text === "string" ? [{ id: record.id, text: record.text }] : [];
6034
+ });
6035
+ }
6036
+ function diffPreviews(value) {
6037
+ if (!Array.isArray(value))
6038
+ return;
6039
+ return value.flatMap((item) => {
6040
+ if (!item || typeof item !== "object" || Array.isArray(item))
6041
+ return [];
6042
+ const record = item;
6043
+ if (record.mode === "advanced" && typeof record.fileName === "string" && Array.isArray(record.hunks)) {
6044
+ return [{
6045
+ mode: "advanced",
6046
+ fileName: record.fileName,
6047
+ hunks: diffHunks(record.hunks)
6048
+ }];
6049
+ }
6050
+ if ((record.mode === "fallback" || record.mode === "unpreviewable") && typeof record.fileName === "string" && typeof record.reason === "string") {
6051
+ return [{
6052
+ mode: record.mode,
6053
+ fileName: record.fileName,
6054
+ reason: record.reason
6055
+ }];
6056
+ }
6057
+ return [];
6058
+ });
6059
+ }
6060
+ function diffHunks(value) {
6061
+ return value.flatMap((item) => {
6062
+ if (!item || typeof item !== "object" || Array.isArray(item))
6063
+ return [];
6064
+ const record = item;
6065
+ if (typeof record.oldStart !== "number" || typeof record.oldLines !== "number" || typeof record.newStart !== "number" || typeof record.newLines !== "number" || !Array.isArray(record.lines)) {
6066
+ return [];
6067
+ }
6068
+ const lines = [];
6069
+ for (const line of record.lines) {
6070
+ if (!line || typeof line !== "object" || Array.isArray(line))
6071
+ continue;
6072
+ const lineRecord = line;
6073
+ const type = lineRecord.type;
6074
+ if (type !== "context" && type !== "add" && type !== "remove" || typeof lineRecord.content !== "string") {
6075
+ continue;
6076
+ }
6077
+ lines.push({
6078
+ type,
6079
+ content: lineRecord.content
6080
+ });
6081
+ }
6082
+ return [{
6083
+ oldStart: record.oldStart,
6084
+ oldLines: record.oldLines,
6085
+ newStart: record.newStart,
6086
+ newLines: record.newLines,
6087
+ lines
6088
+ }];
6089
+ });
6090
+ }
6091
+ function toSessionDeviceStatus(status) {
6092
+ const permissionMode = normalizePermissionMode(status.current_permission_mode);
6093
+ if (typeof status.is_online !== "boolean" || typeof status.is_processing !== "boolean" || permissionMode === undefined || !(typeof status.current_working_directory === "string" || status.current_working_directory === null)) {
6094
+ return null;
6095
+ }
6096
+ return {
6097
+ isOnline: status.is_online,
6098
+ isProcessing: status.is_processing,
6099
+ permissionMode,
6100
+ workingDirectory: status.current_working_directory,
6101
+ pendingControlRequests: pendingControlRequests(status),
6102
+ raw: { ...status }
6103
+ };
6104
+ }
5685
6105
  function normalizeSendMessage(message) {
5686
6106
  return message;
5687
6107
  }
@@ -5700,6 +6120,7 @@ class RemoteClientSessionCore {
5700
6120
  _modelSettings = null;
5701
6121
  label;
5702
6122
  requestTimeoutMs;
6123
+ initializePromise = null;
5703
6124
  streamQueue = [];
5704
6125
  streamResolvers = [];
5705
6126
  removeMessageHandler = null;
@@ -5709,49 +6130,80 @@ class RemoteClientSessionCore {
5709
6130
  messageCounter = 0;
5710
6131
  clientMessageCounter = 0;
5711
6132
  toolNames;
6133
+ deviceStatusListeners = new Set;
6134
+ deviceStatusRefreshCancels = new Set;
5712
6135
  constructor(mode, config) {
5713
6136
  this.mode = mode;
5714
6137
  this.label = config.label;
5715
6138
  this.requestTimeoutMs = config.requestTimeoutMs;
5716
6139
  }
5717
6140
  async initialize() {
6141
+ if (this.closed) {
6142
+ throw new Error("Session is closed");
6143
+ }
6144
+ if (this.initializePromise) {
6145
+ return this.initializePromise;
6146
+ }
5718
6147
  if (this.initialized) {
5719
6148
  throw new Error("Session already initialized");
5720
6149
  }
6150
+ const attempt = this.performInitialize();
6151
+ const memo = attempt.catch((error) => {
6152
+ this.cleanupFailedInitialize();
6153
+ throw error;
6154
+ }).finally(() => {
6155
+ if (this.initializePromise === memo) {
6156
+ this.initializePromise = null;
6157
+ }
6158
+ });
6159
+ this.initializePromise = memo;
6160
+ return memo;
6161
+ }
6162
+ async performInitialize() {
6163
+ const init = await this.initializeRuntimeController();
6164
+ this.controller = init.controller;
6165
+ this.runtime = init.runtime;
6166
+ this._agentId = init.runtime.agent_id;
6167
+ this._conversationId = init.runtime.conversation_id;
6168
+ this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
6169
+ this._modelSettings = init.modelSettings ?? null;
6170
+ this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
6171
+ this.toolNames = init.tools;
6172
+ this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
6173
+ await this.afterRuntimeInitialized();
6174
+ await this.applyPostInitializeOptions();
5721
6175
  if (this.closed) {
5722
6176
  throw new Error("Session is closed");
5723
6177
  }
5724
- try {
5725
- const init = await this.initializeRuntimeController();
5726
- this.controller = init.controller;
5727
- this.runtime = init.runtime;
5728
- this._agentId = init.runtime.agent_id;
5729
- this._conversationId = init.runtime.conversation_id;
5730
- this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
5731
- this._modelSettings = init.modelSettings ?? null;
5732
- this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
5733
- this.toolNames = init.tools;
5734
- this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
5735
- this.initialized = true;
5736
- await this.afterRuntimeInitialized();
5737
- await this.applyPostInitializeOptions();
5738
- const initMessage = {
5739
- type: "init",
5740
- agentId: init.runtime.agent_id,
5741
- sessionId: this._sessionId,
5742
- conversationId: init.runtime.conversation_id,
5743
- model: this._model
5744
- };
5745
- if (this.toolNames !== undefined)
5746
- initMessage.tools = this.toolNames;
5747
- if (init.skillSources !== undefined) {
5748
- initMessage.skillSources = init.skillSources;
5749
- }
5750
- return initMessage;
5751
- } catch (error) {
5752
- this.close();
5753
- throw error;
6178
+ this.initialized = true;
6179
+ const initMessage = {
6180
+ type: "init",
6181
+ agentId: init.runtime.agent_id,
6182
+ sessionId: this._sessionId,
6183
+ conversationId: init.runtime.conversation_id,
6184
+ model: this._model
6185
+ };
6186
+ if (this.toolNames !== undefined)
6187
+ initMessage.tools = this.toolNames;
6188
+ if (init.skillSources !== undefined) {
6189
+ initMessage.skillSources = init.skillSources;
5754
6190
  }
6191
+ return initMessage;
6192
+ }
6193
+ cleanupFailedInitialize() {
6194
+ this.removeMessageHandler?.();
6195
+ this.removeMessageHandler = null;
6196
+ this.controller?.close();
6197
+ this.controller = null;
6198
+ this.onCoreClose();
6199
+ this.runtime = null;
6200
+ this._agentId = null;
6201
+ this._conversationId = null;
6202
+ this._sessionId = null;
6203
+ this._modelSettings = null;
6204
+ this._model = "";
6205
+ this.toolNames = undefined;
6206
+ this.initialized = false;
5755
6207
  }
5756
6208
  async send(message) {
5757
6209
  if (!this.initialized) {
@@ -5848,6 +6300,12 @@ class RemoteClientSessionCore {
5848
6300
  if (!this.initialized) {
5849
6301
  await this.initialize();
5850
6302
  }
6303
+ if (!this.controller || !this.runtime) {
6304
+ throw new Error("Session is not initialized");
6305
+ }
6306
+ return this.applyModelUpdate(update);
6307
+ }
6308
+ async applyModelUpdate(update) {
5851
6309
  if (!this.controller || !this.runtime) {
5852
6310
  throw new Error("Session is not initialized");
5853
6311
  }
@@ -5899,6 +6357,86 @@ class RemoteClientSessionCore {
5899
6357
  removed: response.success
5900
6358
  };
5901
6359
  }
6360
+ async getDeviceStatus(options = {}) {
6361
+ if (!this.initialized) {
6362
+ await this.initialize();
6363
+ }
6364
+ if (!this.controller || !this.runtime) {
6365
+ throw new Error("Session is not initialized");
6366
+ }
6367
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs ?? 30000;
6368
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
6369
+ throw new Error("Invalid device status timeout. Expected a positive integer.");
6370
+ }
6371
+ return this.refreshDeviceStatus(timeoutMs);
6372
+ }
6373
+ onDeviceStatus(listener) {
6374
+ if (typeof listener !== "function") {
6375
+ throw new Error("Invalid device status listener. Expected a function.");
6376
+ }
6377
+ this.deviceStatusListeners.add(listener);
6378
+ return () => {
6379
+ this.deviceStatusListeners.delete(listener);
6380
+ };
6381
+ }
6382
+ refreshDeviceStatus(timeoutMs) {
6383
+ const controller = this.controller;
6384
+ const runtime = this.runtime;
6385
+ if (!controller || !runtime) {
6386
+ return Promise.reject(new Error("Session is not initialized"));
6387
+ }
6388
+ return new Promise((resolve, reject) => {
6389
+ let status = null;
6390
+ let syncAcknowledged = false;
6391
+ let settled = false;
6392
+ const cleanup = () => {
6393
+ clearTimeout(timer);
6394
+ unsubscribe();
6395
+ this.deviceStatusRefreshCancels.delete(cancel);
6396
+ };
6397
+ const rejectOnce = (error) => {
6398
+ if (settled)
6399
+ return;
6400
+ settled = true;
6401
+ cleanup();
6402
+ reject(error);
6403
+ };
6404
+ const resolveIfComplete = () => {
6405
+ if (settled || !syncAcknowledged || !status)
6406
+ return;
6407
+ settled = true;
6408
+ cleanup();
6409
+ resolve(status);
6410
+ };
6411
+ const cancel = (error) => rejectOnce(error);
6412
+ const timer = setTimeout(() => {
6413
+ rejectOnce(new Error(`Timed out waiting for ${this.label} device status`));
6414
+ }, timeoutMs);
6415
+ timer.unref?.();
6416
+ const unsubscribe = this.onDeviceStatus((nextStatus) => {
6417
+ status = nextStatus;
6418
+ resolveIfComplete();
6419
+ });
6420
+ this.deviceStatusRefreshCancels.add(cancel);
6421
+ controller.request("sync", {
6422
+ runtime,
6423
+ recover_approvals: false,
6424
+ force_device_status: true
6425
+ }, {
6426
+ timeoutMs,
6427
+ predicate: (message) => message.type === "sync_response"
6428
+ }).then((response) => {
6429
+ if (response.success === false) {
6430
+ rejectOnce(new Error(typeof response.error === "string" ? response.error : `Failed to refresh ${this.label} device status`));
6431
+ return;
6432
+ }
6433
+ syncAcknowledged = true;
6434
+ resolveIfComplete();
6435
+ }, (error) => {
6436
+ rejectOnce(error instanceof Error ? error : new Error(String(error)));
6437
+ });
6438
+ });
6439
+ }
5902
6440
  async listMessages(options = {}) {
5903
6441
  if (!this.initialized) {
5904
6442
  await this.initialize();
@@ -5951,6 +6489,11 @@ class RemoteClientSessionCore {
5951
6489
  }
5952
6490
  this.activeTurn = null;
5953
6491
  this.pendingTurns.length = 0;
6492
+ for (const cancel of [...this.deviceStatusRefreshCancels]) {
6493
+ cancel(new Error(`Session closed while waiting for ${this.label} device status`));
6494
+ }
6495
+ this.deviceStatusRefreshCancels.clear();
6496
+ this.deviceStatusListeners.clear();
5954
6497
  this.controller?.close();
5955
6498
  this.controller = null;
5956
6499
  this.onCoreClose();
@@ -6179,7 +6722,7 @@ class RemoteClientSessionCore {
6179
6722
  if (this.mode.kind !== "session")
6180
6723
  return;
6181
6724
  if (this.mode.options.model !== undefined || this.mode.options.reasoningEffort !== undefined) {
6182
- await this.updateModel({
6725
+ await this.applyModelUpdate({
6183
6726
  ...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
6184
6727
  ...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
6185
6728
  });
@@ -6188,6 +6731,18 @@ class RemoteClientSessionCore {
6188
6731
  handleProtocolMessage = (message) => {
6189
6732
  if (!this.runtime || !sameRuntime2(message, this.runtime))
6190
6733
  return;
6734
+ const deviceStatus = deviceStatusRecord(message);
6735
+ if (deviceStatus) {
6736
+ const status = toSessionDeviceStatus(deviceStatus);
6737
+ if (!status)
6738
+ return;
6739
+ for (const listener of [...this.deviceStatusListeners]) {
6740
+ try {
6741
+ listener(status);
6742
+ } catch {}
6743
+ }
6744
+ return;
6745
+ }
6191
6746
  if (message.type === "update_queue") {
6192
6747
  const sdkMessage2 = {
6193
6748
  type: "queue_update",
@@ -6617,7 +7172,7 @@ function createExternalToolCallHandler(externalTools) {
6617
7172
  };
6618
7173
  };
6619
7174
  }
6620
- async function resolveAppServerToolApproval(options, toolName, toolInput) {
7175
+ async function resolveAppServerToolApproval(options, toolName, toolInput, context) {
6621
7176
  const hasCallback = typeof options.canUseTool === "function";
6622
7177
  const toolNeedsRuntimeUserInput = requiresRuntimeUserInput(toolName);
6623
7178
  if (toolNeedsRuntimeUserInput && !hasCallback) {
@@ -6632,7 +7187,7 @@ async function resolveAppServerToolApproval(options, toolName, toolInput) {
6632
7187
  }
6633
7188
  if (hasCallback) {
6634
7189
  try {
6635
- const result = await options.canUseTool(toolName, toolInput);
7190
+ const result = await options.canUseTool(toolName, toolInput, context);
6636
7191
  if (result.behavior === "allow") {
6637
7192
  return {
6638
7193
  behavior: "allow",
@@ -6692,7 +7247,7 @@ function toAppServerApprovalDecision(decision) {
6692
7247
  selected_permission_suggestion_ids: selectedPermissionSuggestionIds
6693
7248
  };
6694
7249
  }
6695
- function stringRecord(value) {
7250
+ function stringRecord2(value) {
6696
7251
  if (!value || typeof value !== "object" || Array.isArray(value))
6697
7252
  return;
6698
7253
  const record = {};
@@ -6738,7 +7293,7 @@ function normalizeListModelsResponse(response) {
6738
7293
  } else if (Array.isArray(response.available_handles)) {
6739
7294
  result.availableHandles = response.available_handles.filter((handle) => typeof handle === "string");
6740
7295
  }
6741
- const aliases = stringRecord(response.byok_provider_aliases);
7296
+ const aliases = stringRecord2(response.byok_provider_aliases);
6742
7297
  if (aliases)
6743
7298
  result.byokProviderAliases = aliases;
6744
7299
  return result;
@@ -6793,7 +7348,7 @@ async function respondToAppServerControlRequest(config, message) {
6793
7348
  return;
6794
7349
  const toolName = typeof requestRecord.tool_name === "string" ? requestRecord.tool_name : "unknown";
6795
7350
  const toolInput = requestRecord.input && typeof requestRecord.input === "object" && !Array.isArray(requestRecord.input) ? requestRecord.input : {};
6796
- const decision = await resolveAppServerToolApproval(config.getOptions(), toolName, toolInput);
7351
+ const decision = await resolveAppServerToolApproval(config.getOptions(), toolName, toolInput, buildCanUseToolContext(requestRecord, requestId));
6797
7352
  config.client.input({
6798
7353
  runtime,
6799
7354
  payload: {
@@ -6930,13 +7485,15 @@ class AppServerSession extends RemoteClientSessionCore {
6930
7485
  return options.memfs !== false;
6931
7486
  }
6932
7487
  async initializeRuntimeController() {
7488
+ await this.remoteOptions.beforeConnect?.();
6933
7489
  const url = await this.resolveAppServerUrl();
6934
- const client = createAppServerClient({
7490
+ await releaseAppServerManagementConnections(url);
7491
+ const client = applyUniqueRequestIds(createAppServerClient({
6935
7492
  url,
6936
7493
  ...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
6937
7494
  ...this.remoteOptions.WebSocket ? { WebSocket: this.remoteOptions.WebSocket } : {},
6938
7495
  ...this.remoteOptions.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.remoteOptions.requestTimeoutMs } : {}
6939
- });
7496
+ }));
6940
7497
  this.removeControlRequestHandler = registerAppServerControlRequestHandler({
6941
7498
  client,
6942
7499
  getRuntime: () => this.runtime,
@@ -7136,6 +7693,19 @@ function asArray(body, action) {
7136
7693
  }
7137
7694
  return body;
7138
7695
  }
7696
+ function cloudModelEntry(raw) {
7697
+ if (typeof raw.handle !== "string" || raw.handle.length === 0) {
7698
+ return null;
7699
+ }
7700
+ const label = typeof raw.display_name === "string" ? raw.display_name : typeof raw.name === "string" ? raw.name : raw.handle;
7701
+ return {
7702
+ ...raw,
7703
+ id: typeof raw.id === "string" && raw.id.length > 0 ? raw.id : raw.handle,
7704
+ handle: raw.handle,
7705
+ label,
7706
+ description: typeof raw.description === "string" ? raw.description : ""
7707
+ };
7708
+ }
7139
7709
 
7140
7710
  class CloudManagementTransport {
7141
7711
  options;
@@ -7151,6 +7721,20 @@ class CloudManagementTransport {
7151
7721
  updateAgent(agentId, body) {
7152
7722
  return this.requestObject(`/v1/agents/${encodeURIComponent(agentId)}`, "PATCH", body, "Cloud update agent");
7153
7723
  }
7724
+ async deleteAgent(agentId) {
7725
+ await this.requestUrl(this.url(`/v1/agents/${encodeURIComponent(agentId)}`), "DELETE", undefined, "Cloud delete agent");
7726
+ }
7727
+ async listModels() {
7728
+ const entries = await this.getArray("/v1/models", {}, "Cloud list models");
7729
+ const normalized = entries.flatMap((entry) => {
7730
+ const model = cloudModelEntry(entry);
7731
+ return model ? [model] : [];
7732
+ });
7733
+ return {
7734
+ entries: normalized,
7735
+ availableHandles: normalized.map((entry) => entry.handle)
7736
+ };
7737
+ }
7154
7738
  listConversations(query) {
7155
7739
  return this.getArray("/v1/conversations/", query, "Cloud list conversations");
7156
7740
  }
@@ -7780,14 +8364,14 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
7780
8364
  apiKey,
7781
8365
  authMode: this.cloudOptions.webSocketAuth ?? "header"
7782
8366
  });
7783
- const client = createAppServerClient({
8367
+ const client = applyUniqueRequestIds(createAppServerClient({
7784
8368
  url,
7785
8369
  WebSocket: createCloudStatusWebSocketConstructor({
7786
8370
  cloudOptions: this.cloudOptions,
7787
8371
  runtime: resolved.runtime
7788
8372
  }),
7789
8373
  requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
7790
- });
8374
+ }));
7791
8375
  this.removeControlRequestHandler = registerAppServerControlRequestHandler({
7792
8376
  client,
7793
8377
  getRuntime: () => this.runtime,
@@ -8235,6 +8819,11 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
8235
8819
  function definedEntries(values) {
8236
8820
  return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
8237
8821
  }
8822
+ function assertNonEmptyId(value, name) {
8823
+ if (typeof value !== "string" || value.trim().length === 0) {
8824
+ throw new Error(`Invalid ${name}. Expected a non-empty string.`);
8825
+ }
8826
+ }
8238
8827
  function agentListQuery(options) {
8239
8828
  const orderBy = options.orderBy?.replace(/[A-Z]/g, (character) => `_${character.toLowerCase()}`);
8240
8829
  return {
@@ -8307,7 +8896,16 @@ function createAgentsClient(transport) {
8307
8896
  return {
8308
8897
  list: (options = {}) => transport().listAgents(agentListQuery(options)),
8309
8898
  retrieve: (agentId) => transport().retrieveAgent(agentId),
8310
- update: (agentId, options) => transport().updateAgent(agentId, agentUpdateBody(options))
8899
+ update: (agentId, options) => transport().updateAgent(agentId, agentUpdateBody(options)),
8900
+ delete: async (agentId) => {
8901
+ assertNonEmptyId(agentId, "agent id");
8902
+ await transport().deleteAgent(agentId);
8903
+ }
8904
+ };
8905
+ }
8906
+ function createModelsClient(transport) {
8907
+ return {
8908
+ list: () => transport().listModels()
8311
8909
  };
8312
8910
  }
8313
8911
  function createConversationsClient(transport) {
@@ -8478,6 +9076,7 @@ class LettaAgentClientBase {
8478
9076
  environment;
8479
9077
  agents;
8480
9078
  conversations;
9079
+ models;
8481
9080
  options;
8482
9081
  repositoriesClient = null;
8483
9082
  managementTransport = null;
@@ -8491,6 +9090,7 @@ class LettaAgentClientBase {
8491
9090
  this.options = options;
8492
9091
  this.agents = createAgentsClient(() => this.getManagementTransport());
8493
9092
  this.conversations = createConversationsClient(() => this.getManagementTransport());
9093
+ this.models = createModelsClient(() => this.getManagementTransport());
8494
9094
  if (this.backend === "local" && this.environment !== undefined) {
8495
9095
  throw new Error('LettaAgentClient environment is only valid with backend: "cloud".');
8496
9096
  }
@@ -8509,6 +9109,10 @@ class LettaAgentClientBase {
8509
9109
  if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
8510
9110
  throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
8511
9111
  }
9112
+ const idleLingerMs = localOptions.appServer?.idleLingerMs;
9113
+ if (idleLingerMs !== undefined && (!Number.isInteger(idleLingerMs) || idleLingerMs < 0)) {
9114
+ throw new Error("Invalid appServer.idleLingerMs. Expected a non-negative integer.");
9115
+ }
8512
9116
  const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
8513
9117
  if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
8514
9118
  throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
@@ -8521,6 +9125,9 @@ class LettaAgentClientBase {
8521
9125
  if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
8522
9126
  throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
8523
9127
  }
9128
+ if (options.idleLingerMs !== undefined && (!Number.isInteger(options.idleLingerMs) || options.idleLingerMs < 0)) {
9129
+ throw new Error("Invalid idleLingerMs. Expected a non-negative integer.");
9130
+ }
8524
9131
  }
8525
9132
  if (this.backend === "cloud") {
8526
9133
  validateCloudClientOptions(options);
@@ -8535,7 +9142,7 @@ class LettaAgentClientBase {
8535
9142
  }
8536
9143
  validateCreateAgentOptions(options);
8537
9144
  if (this.backend === "remote") {
8538
- const session = new AppServerSession(this.remoteOptions(), {
9145
+ const session = new AppServerSession(this.appServerSessionOptions(), {
8539
9146
  kind: "create-agent",
8540
9147
  options
8541
9148
  });
@@ -8558,7 +9165,7 @@ class LettaAgentClientBase {
8558
9165
  if (!agentId) {
8559
9166
  throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
8560
9167
  }
8561
- return new AppServerSession(this.remoteOptions(), {
9168
+ return new AppServerSession(this.appServerSessionOptions(), {
8562
9169
  kind: "session",
8563
9170
  agentId,
8564
9171
  newConversation: true,
@@ -8584,13 +9191,13 @@ class LettaAgentClientBase {
8584
9191
  validateCreateSessionOptions(sessionOptions);
8585
9192
  if (this.backend === "remote") {
8586
9193
  if (looksLikeConversationId(id)) {
8587
- return new AppServerSession(this.remoteOptions(), {
9194
+ return new AppServerSession(this.appServerSessionOptions(), {
8588
9195
  kind: "session",
8589
9196
  conversationId: id,
8590
9197
  options
8591
9198
  });
8592
9199
  }
8593
- return new AppServerSession(this.remoteOptions(), {
9200
+ return new AppServerSession(this.appServerSessionOptions(), {
8594
9201
  kind: "session",
8595
9202
  agentId: id,
8596
9203
  defaultConversation: true,
@@ -8689,6 +9296,12 @@ class LettaAgentClientBase {
8689
9296
  }
8690
9297
  return this.options;
8691
9298
  }
9299
+ appServerSessionOptions() {
9300
+ return {
9301
+ ...this.remoteOptions(),
9302
+ beforeConnect: () => this.releaseIdleManagementConnection()
9303
+ };
9304
+ }
8692
9305
  getRepositoriesClient() {
8693
9306
  if (this.backend !== "cloud") {
8694
9307
  throw new Error('client.repositories is only available with backend: "cloud".');
@@ -8696,6 +9309,9 @@ class LettaAgentClientBase {
8696
9309
  this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
8697
9310
  return this.repositoriesClient;
8698
9311
  }
9312
+ async releaseIdleManagementConnection() {
9313
+ await this.managementTransport?.releaseIdleConnection?.();
9314
+ }
8699
9315
  getManagementTransport() {
8700
9316
  if (this.managementTransport)
8701
9317
  return this.managementTransport;
@@ -8803,4 +9419,4 @@ export {
8803
9419
  CloudManagedSandboxExpiredError
8804
9420
  };
8805
9421
 
8806
- //# debugId=1456942A1FCDB34964756E2164756E21
9422
+ //# debugId=EACEDA2AA17A075B64756E2164756E21