@letta-ai/letta-agent-sdk 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/AGENTS.md +42 -0
  2. package/README.md +101 -1
  3. package/dist/app-server-session.d.ts.map +1 -1
  4. package/dist/client-base.d.ts +7 -0
  5. package/dist/client-base.d.ts.map +1 -1
  6. package/dist/client-entry.d.ts +3 -0
  7. package/dist/client-entry.d.ts.map +1 -1
  8. package/dist/client-entry.js +816 -138
  9. package/dist/client-entry.js.map +16 -14
  10. package/dist/cloud-session.d.ts +8 -1
  11. package/dist/cloud-session.d.ts.map +1 -1
  12. package/dist/computers.d.ts +67 -0
  13. package/dist/computers.d.ts.map +1 -0
  14. package/dist/index.d.ts +4 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +817 -139
  17. package/dist/index.js.map +17 -15
  18. package/dist/remote-client-session-core.d.ts +13 -16
  19. package/dist/remote-client-session-core.d.ts.map +1 -1
  20. package/dist/remote-session-protocol.d.ts +11 -0
  21. package/dist/remote-session-protocol.d.ts.map +1 -1
  22. package/dist/remote-turn-coordinator.d.ts +6 -1
  23. package/dist/remote-turn-coordinator.d.ts.map +1 -1
  24. package/dist/remote.d.ts +6 -1
  25. package/dist/remote.d.ts.map +1 -1
  26. package/dist/transcript-accumulator.d.ts +133 -0
  27. package/dist/transcript-accumulator.d.ts.map +1 -0
  28. package/dist/types.d.ts +41 -21
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/validation.d.ts.map +1 -1
  31. package/package.json +2 -2
  32. package/src/app-server-session.ts +9 -0
  33. package/src/client-base.ts +66 -19
  34. package/src/client-entry.ts +23 -0
  35. package/src/cloud-session.ts +112 -45
  36. package/src/computers.ts +132 -0
  37. package/src/index.ts +24 -0
  38. package/src/remote-client-session-core.ts +103 -27
  39. package/src/remote-session-protocol.ts +24 -0
  40. package/src/remote-turn-coordinator.ts +11 -2
  41. package/src/remote.ts +41 -12
  42. package/src/transcript-accumulator.ts +823 -0
  43. package/src/types.ts +43 -18
  44. package/src/validation.ts +16 -0
@@ -3073,7 +3073,7 @@ function isAppServerInfoResponseMessage(message) {
3073
3073
  return false;
3074
3074
  }
3075
3075
  const capabilityRecord = capabilities;
3076
- 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";
3076
+ 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" && (capabilityRecord.runtime_external_tools_update === undefined || typeof capabilityRecord.runtime_external_tools_update === "boolean") && typeof capabilityRecord.split_channels === "boolean";
3077
3077
  }
3078
3078
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
3079
3079
  var WEBSOCKET_OPEN_STATE = 1;
@@ -3316,6 +3316,16 @@ class AppServerClient {
3316
3316
  predicate: (message) => message.type === "runtime_start_response"
3317
3317
  });
3318
3318
  }
3319
+ runtimeExternalToolsUpdate(command, options = {}) {
3320
+ return this.request({
3321
+ type: "runtime_external_tools_update",
3322
+ request_id: command.request_id ?? this.nextRequestId("runtime-external-tools"),
3323
+ ...command
3324
+ }, {
3325
+ ...options,
3326
+ predicate: (message) => message.type === "runtime_external_tools_update_response"
3327
+ });
3328
+ }
3319
3329
  sync(command, options = {}) {
3320
3330
  return this.request({
3321
3331
  type: "sync",
@@ -3836,12 +3846,7 @@ You **MUST** be proactive in creating crons when work extends beyond the current
3836
3846
 
3837
3847
  **Cost**: Self-invocation is critical, but expensive. Default to the longest interval that still serves the user. Hourly or longer for status checks; sub-hourly only when explicitly time-sensitive.
3838
3848
 
3839
- Creating crons:
3840
- - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
3841
- - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
3842
- Always include \`--name\`, \`--description\`, and \`--prompt\`. \`$AGENT_ID\` is automatically injected into the shell environment, and \`letta cron\` uses it by default, so you do not need to specify which agent to invoke unless overriding the current agent intentionally.
3843
-
3844
- Where crons run: for cloud agents, schedules default to durable Cloud schedules that fire from the cloud and execute in your cloud sandbox — they survive local shutdown, so this is the right default. If the scheduled work must run on a specific computer (e.g. it needs that computer's filesystem or local services), add \`--computer <deviceId>\` (from \`letta environments list\`) to keep the durable Cloud schedule but execute on that computer, with sandbox fallback if it is offline. Use \`--runner local\` only when that fallback is unacceptable; local schedules only fire while a Letta session is running on that computer.
3849
+ The mechanics — flags, where schedules run and execute, timezone handling — live in the scheduling-tasks skill. Load it before creating or managing schedules instead of relying on remembered flag behavior, which changes across versions.
3845
3850
 
3846
3851
  # Harness Architecture
3847
3852
 
@@ -4018,12 +4023,7 @@ You **MUST** be proactive in creating crons when work extends beyond the current
4018
4023
 
4019
4024
  **Cost**: Self-invocation is critical, but expensive. Default to the longest interval that still serves the user. Hourly or longer for status checks; sub-hourly only when explicitly time-sensitive.
4020
4025
 
4021
- Creating crons:
4022
- - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
4023
- - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
4024
- Always include \`--name\`, \`--description\`, and \`--prompt\`. \`$AGENT_ID\` is automatically injected into the shell environment, and \`letta cron\` uses it by default, so you do not need to specify which agent to invoke unless overriding the current agent intentionally.
4025
-
4026
- Where crons run: for cloud agents, schedules default to durable Cloud schedules that fire from the cloud and execute in your cloud sandbox — they survive local shutdown, so this is the right default. If the scheduled work must run on a specific computer (e.g. it needs that computer's filesystem or local services), add \`--computer <deviceId>\` (from \`letta environments list\`) to keep the durable Cloud schedule but execute on that computer, with sandbox fallback if it is offline. Use \`--runner local\` only when that fallback is unacceptable; local schedules only fire while a Letta session is running on that computer.
4026
+ The mechanics — flags, where schedules run and execute, timezone handling — live in the scheduling-tasks skill. Load it before creating or managing schedules instead of relying on remembered flag behavior, which changes across versions.
4027
4027
 
4028
4028
  # Harness Architecture
4029
4029
 
@@ -9050,6 +9050,20 @@ function toSessionDeviceStatus(status) {
9050
9050
  function normalizeSendMessage(message) {
9051
9051
  return message;
9052
9052
  }
9053
+ function normalizeCallerOtid(otid) {
9054
+ if (otid === undefined)
9055
+ return;
9056
+ if (typeof otid !== "string" || otid.trim() === "") {
9057
+ throw new Error("send() otid must be a non-empty string");
9058
+ }
9059
+ return otid;
9060
+ }
9061
+ function turnSendOptions(turn) {
9062
+ return {
9063
+ clientMessageId: turn.clientMessageId,
9064
+ ...turn.otid !== undefined ? { otid: turn.otid } : {}
9065
+ };
9066
+ }
9053
9067
 
9054
9068
  // src/remote-turn-coordinator.ts
9055
9069
  class RemoteTurnCoordinator {
@@ -9078,11 +9092,13 @@ class RemoteTurnCoordinator {
9078
9092
  hasInFlightTurn() {
9079
9093
  return this.activeTurn !== null || this.pendingTurns.length > 0;
9080
9094
  }
9081
- trackSentTurn(runtime) {
9095
+ trackSentTurn(runtime, callerOtid) {
9096
+ const otid = normalizeCallerOtid(callerOtid);
9082
9097
  const turn = {
9083
9098
  id: ++this.nextTurnId,
9084
9099
  runtime,
9085
- clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
9100
+ ...otid !== undefined ? { otid } : {},
9101
+ clientMessageId: otid ?? `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
9086
9102
  queuedAt: Date.now(),
9087
9103
  startedAt: 0,
9088
9104
  assistantText: "",
@@ -9493,6 +9509,9 @@ class RemoteClientSessionCore {
9493
9509
  initializePromise = null;
9494
9510
  removeMessageHandler = null;
9495
9511
  detachTransportDisconnect = null;
9512
+ idleTransportDisconnected = false;
9513
+ transportRecoveryPromise = null;
9514
+ transportDisconnectGeneration = 0;
9496
9515
  turns;
9497
9516
  toolNames;
9498
9517
  deviceStatusListeners = new Set;
@@ -9581,19 +9600,25 @@ class RemoteClientSessionCore {
9581
9600
  this.toolNames = undefined;
9582
9601
  this.initialized = false;
9583
9602
  }
9584
- async send(message) {
9603
+ async send(message, options) {
9604
+ if (this.closed)
9605
+ throw new Error("Session is closed");
9585
9606
  if (!this.initialized) {
9586
9607
  await this.initialize();
9587
9608
  }
9609
+ await this.recoverIdleTransportIfNeeded();
9588
9610
  if (!this.controller || !this.runtime) {
9589
9611
  throw new Error("Session is not initialized");
9590
9612
  }
9591
9613
  await this.beforeTurn();
9592
- const turn = this.turns.trackSentTurn(this.runtime);
9614
+ const controller = this.controller;
9615
+ const runtime = this.runtime;
9616
+ if (!controller || !runtime) {
9617
+ throw new Error("Session transport disconnected before the turn was sent");
9618
+ }
9619
+ const turn = this.turns.trackSentTurn(runtime, options?.otid);
9593
9620
  try {
9594
- this.controller.sendTurnMessage(this.runtime, message, {
9595
- clientMessageId: turn.clientMessageId
9596
- });
9621
+ controller.sendTurnMessage(runtime, message, turnSendOptions(turn));
9597
9622
  } catch (error) {
9598
9623
  this.turns.removeTrackedTurn(turn);
9599
9624
  throw error;
@@ -9672,6 +9697,9 @@ class RemoteClientSessionCore {
9672
9697
  return this.controller.listModels();
9673
9698
  }
9674
9699
  async updateModel(update) {
9700
+ if (this.mode.kind === "session" && this.mode.options.stateless === true) {
9701
+ throw new Error("updateModel() is unavailable in a stateless session because it changes persisted agent configuration.");
9702
+ }
9675
9703
  if (!this.initialized) {
9676
9704
  await this.initialize();
9677
9705
  }
@@ -9866,6 +9894,7 @@ class RemoteClientSessionCore {
9866
9894
  this.deviceStatusListeners.clear();
9867
9895
  this.controller?.close();
9868
9896
  this.controller = null;
9897
+ this.idleTransportDisconnected = false;
9869
9898
  this.onCoreClose();
9870
9899
  }
9871
9900
  get agentId() {
@@ -9918,15 +9947,78 @@ class RemoteClientSessionCore {
9918
9947
  }, { predicate: (message) => message.type === "update_toolset_response" });
9919
9948
  ensureSuccess(response, "Failed to update toolset");
9920
9949
  }
9921
- watchTransportDisconnect(client) {
9950
+ watchTransportDisconnect(client, options = {}) {
9922
9951
  this.detachTransportDisconnect?.();
9923
9952
  this.detachTransportDisconnect = client.onDisconnect(() => {
9924
9953
  if (this.closed)
9925
9954
  return;
9955
+ this.transportDisconnectGeneration += 1;
9956
+ if (options.recoverWhenIdle && !this.turns.hasInFlightTurn()) {
9957
+ this.detachTransportDisconnect?.();
9958
+ this.detachTransportDisconnect = null;
9959
+ this.removeMessageHandler?.();
9960
+ this.removeMessageHandler = null;
9961
+ this.controller?.close();
9962
+ this.controller = null;
9963
+ const error = new Error(`${this.label} connection closed before send`);
9964
+ for (const cancel of [...this.deviceStatusRefreshCancels])
9965
+ cancel(error);
9966
+ this.deviceStatusRefreshCancels.clear();
9967
+ this.onIdleTransportDisconnect();
9968
+ this.idleTransportDisconnected = true;
9969
+ return;
9970
+ }
9926
9971
  this.turns.closeWithError(`The ${this.label} connection closed unexpectedly; resume the conversation to continue.`);
9927
9972
  this.close();
9928
9973
  });
9929
9974
  }
9975
+ async recoverIdleTransportIfNeeded() {
9976
+ if (!this.idleTransportDisconnected)
9977
+ return;
9978
+ if (this.transportRecoveryPromise) {
9979
+ await this.transportRecoveryPromise;
9980
+ return;
9981
+ }
9982
+ const runtime = this.runtime;
9983
+ if (!runtime)
9984
+ throw new Error("Session transport disconnected without a runtime");
9985
+ const generation = this.transportDisconnectGeneration;
9986
+ const recovery = this.recoverIdleTransport(runtime).then(async (init) => {
9987
+ if (this.closed || generation !== this.transportDisconnectGeneration) {
9988
+ init.controller.close();
9989
+ this.onRecoveredTransportDiscarded();
9990
+ throw new Error(`${this.label} connection closed during recovery`);
9991
+ }
9992
+ if (init.runtime.agent_id !== runtime.agent_id || init.runtime.conversation_id !== runtime.conversation_id) {
9993
+ init.controller.close();
9994
+ this.onRecoveredTransportDiscarded();
9995
+ throw new Error(`${this.label} transport recovered a different runtime`);
9996
+ }
9997
+ this.controller = init.controller;
9998
+ this.runtime = init.runtime;
9999
+ this._modelSettings = init.modelSettings ?? this._modelSettings;
10000
+ if (typeof init.model === "string" && init.model)
10001
+ this._model = init.model;
10002
+ if (init.tools !== undefined)
10003
+ this.toolNames = init.tools;
10004
+ this.removeMessageHandler = this.controller.onMessage((message) => {
10005
+ if (this.runtime)
10006
+ this.turns.handleProtocolMessage(message, this.runtime);
10007
+ });
10008
+ this.idleTransportDisconnected = false;
10009
+ await this.afterRuntimeInitialized();
10010
+ }).finally(() => {
10011
+ if (this.transportRecoveryPromise === recovery)
10012
+ this.transportRecoveryPromise = null;
10013
+ });
10014
+ this.transportRecoveryPromise = recovery;
10015
+ await recovery;
10016
+ }
10017
+ async recoverIdleTransport(_runtime) {
10018
+ throw new Error(`${this.label} sessions do not support idle transport recovery`);
10019
+ }
10020
+ onIdleTransportDisconnect() {}
10021
+ onRecoveredTransportDiscarded() {}
9930
10022
  async afterRuntimeInitialized() {}
9931
10023
  async beforeTurn() {}
9932
10024
  onCoreClose() {}
@@ -10008,7 +10100,8 @@ class RemoteClientSessionCore {
10008
10100
  ensureSuccess(response, "Failed to enable memfs");
10009
10101
  }
10010
10102
  const dreamingSettings = resolveDreamingSettings(options.dreaming);
10011
- if (dreamingSettings) {
10103
+ const isStatelessSession = this.mode.kind === "session" && this.mode.options.stateless === true;
10104
+ if (dreamingSettings && !isStatelessSession) {
10012
10105
  const response = await this.controller.request("set_reflection_settings", {
10013
10106
  runtime: this.runtime,
10014
10107
  settings: dreamingSettings,
@@ -10274,7 +10367,8 @@ class AppServerRuntimeController {
10274
10367
  {
10275
10368
  role: "user",
10276
10369
  content: normalizeSendMessage(message),
10277
- client_message_id: options.clientMessageId
10370
+ client_message_id: options.clientMessageId,
10371
+ ...options.otid !== undefined ? { otid: options.otid } : {}
10278
10372
  }
10279
10373
  ]
10280
10374
  };
@@ -10501,6 +10595,9 @@ class AppServerSession extends RemoteClientSessionCore {
10501
10595
  command.mode = mode;
10502
10596
  if (options.cwd !== undefined)
10503
10597
  command.cwd = options.cwd;
10598
+ if (this.mode.kind === "session" && this.mode.options.stateless === true) {
10599
+ command.stateless = true;
10600
+ }
10504
10601
  if (options.skillSources !== undefined) {
10505
10602
  command.skill_sources = [...new Set(options.skillSources)];
10506
10603
  }
@@ -10670,6 +10767,132 @@ class CloudManagementTransport {
10670
10767
  }
10671
10768
  }
10672
10769
 
10770
+ // src/remote.ts
10771
+ function ensureOnline(environment, target) {
10772
+ if (!environment.connectionId) {
10773
+ const label = "deviceId" in target ? target.deviceId : ("environmentId" in target) ? target.environmentId : ("connectionName" in target) ? target.connectionName : environment.deviceId;
10774
+ throw new Error(`Computer is offline: ${label}`);
10775
+ }
10776
+ return {
10777
+ connectionId: environment.connectionId,
10778
+ environment,
10779
+ target
10780
+ };
10781
+ }
10782
+
10783
+ class RemoteEnvironmentClient {
10784
+ client;
10785
+ constructor(options = {}, client = createCloudClient({
10786
+ backend: "cloud",
10787
+ apiBaseUrl: options.baseUrl,
10788
+ apiKey: options.apiKey,
10789
+ headers: options.headers,
10790
+ fetch: options.fetch
10791
+ })) {
10792
+ this.client = client;
10793
+ }
10794
+ async listEnvironments(options = {}) {
10795
+ return await this.client.environments.list({
10796
+ after: options.after,
10797
+ limit: options.limit === undefined ? undefined : String(options.limit),
10798
+ onlineOnly: options.onlineOnly === undefined ? undefined : String(options.onlineOnly)
10799
+ });
10800
+ }
10801
+ async getEnvironmentByDeviceId(deviceId) {
10802
+ return await this.client.environments.retrieve(deviceId);
10803
+ }
10804
+ async resolveEnvironment(target) {
10805
+ if ("connectionId" in target) {
10806
+ return { connectionId: target.connectionId, target };
10807
+ }
10808
+ if ("deviceId" in target) {
10809
+ return ensureOnline(await this.getEnvironmentByDeviceId(target.deviceId), target);
10810
+ }
10811
+ const connections = [];
10812
+ let after;
10813
+ do {
10814
+ const page = await this.listEnvironments({ limit: 100, after });
10815
+ connections.push(...page.connections);
10816
+ if (!page.hasNextPage || page.connections.length === 0)
10817
+ break;
10818
+ after = page.connections.at(-1)?.id;
10819
+ } while (after !== undefined);
10820
+ if ("environmentId" in target) {
10821
+ const match = connections.find((env) => env.id === target.environmentId);
10822
+ if (!match) {
10823
+ throw new Error(`Remote environment not found: ${target.environmentId}`);
10824
+ }
10825
+ return ensureOnline(match, target);
10826
+ }
10827
+ const matches = connections.filter((env) => env.connectionName === target.connectionName);
10828
+ if (matches.length === 0) {
10829
+ throw new Error(`Computer not found: ${target.connectionName}`);
10830
+ }
10831
+ const onlineMatches = matches.filter((environment) => environment.connectionId !== null);
10832
+ if (onlineMatches.length === 0) {
10833
+ throw new Error(`Computer is offline: ${target.connectionName}`);
10834
+ }
10835
+ if (onlineMatches.length > 1) {
10836
+ const candidates = onlineMatches.map((environment) => `${environment.connectionName} (${environment.deviceId}, online)`).join(", ");
10837
+ throw new Error(`Computer name is ambiguous: ${target.connectionName}. Matches: ${candidates}. Select one by deviceId.`);
10838
+ }
10839
+ return ensureOnline(onlineMatches[0], target);
10840
+ }
10841
+ }
10842
+
10843
+ // src/computers.ts
10844
+ function selectorToRemoteTarget(selector) {
10845
+ if (typeof selector === "string")
10846
+ return { connectionName: selector };
10847
+ if ("name" in selector)
10848
+ return { connectionName: selector.name };
10849
+ if ("id" in selector)
10850
+ return { environmentId: selector.id };
10851
+ if ("connectionId" in selector) {
10852
+ return { connectionId: selector.connectionId };
10853
+ }
10854
+ return { deviceId: selector.deviceId };
10855
+ }
10856
+ function toComputer(environment) {
10857
+ return {
10858
+ id: environment.id,
10859
+ deviceId: environment.deviceId,
10860
+ connectionId: environment.connectionId,
10861
+ name: environment.connectionName,
10862
+ status: environment.connectionId ? "online" : "offline",
10863
+ connectedAt: environment.connectedAt,
10864
+ lastSeenAt: environment.lastSeenAt,
10865
+ metadata: environment.metadata
10866
+ };
10867
+ }
10868
+
10869
+ class ComputersClientImpl {
10870
+ environments;
10871
+ constructor(client) {
10872
+ this.environments = new RemoteEnvironmentClient({}, client);
10873
+ }
10874
+ async list(options = {}) {
10875
+ const result = await this.environments.listEnvironments(options);
10876
+ return {
10877
+ computers: result.connections.map(toComputer),
10878
+ hasNextPage: result.hasNextPage
10879
+ };
10880
+ }
10881
+ async get(deviceId) {
10882
+ if (typeof deviceId !== "string" || deviceId.trim().length === 0) {
10883
+ throw new Error("Invalid deviceId. Expected a non-empty string.");
10884
+ }
10885
+ return toComputer(await this.environments.getEnvironmentByDeviceId(deviceId));
10886
+ }
10887
+ async resolve(selector) {
10888
+ const result = await this.environments.resolveEnvironment(selectorToRemoteTarget(selector));
10889
+ return {
10890
+ connectionId: result.connectionId,
10891
+ computer: result.environment ? toComputer(result.environment) : undefined
10892
+ };
10893
+ }
10894
+ }
10895
+
10673
10896
  // src/cloud-status-transport.ts
10674
10897
  var CONNECTING = 0;
10675
10898
  var OPEN = 1;
@@ -10886,66 +11109,6 @@ function createCloudStatusTransportConstructor(options) {
10886
11109
  };
10887
11110
  }
10888
11111
 
10889
- // src/remote.ts
10890
- function ensureOnline(environment, target) {
10891
- if (!environment.connectionId) {
10892
- const label = "deviceId" in target ? target.deviceId : ("environmentId" in target) ? target.environmentId : ("connectionName" in target) ? target.connectionName : environment.deviceId;
10893
- throw new Error(`Remote environment is offline: ${label}`);
10894
- }
10895
- return {
10896
- connectionId: environment.connectionId,
10897
- environment,
10898
- target
10899
- };
10900
- }
10901
-
10902
- class RemoteEnvironmentClient {
10903
- client;
10904
- constructor(options = {}, client = createCloudClient({
10905
- backend: "cloud",
10906
- apiBaseUrl: options.baseUrl,
10907
- apiKey: options.apiKey,
10908
- headers: options.headers,
10909
- fetch: options.fetch
10910
- })) {
10911
- this.client = client;
10912
- }
10913
- async listEnvironments() {
10914
- return await this.client.environments.list();
10915
- }
10916
- async getEnvironmentByDeviceId(deviceId) {
10917
- return await this.client.environments.retrieve(deviceId);
10918
- }
10919
- async resolveEnvironment(target) {
10920
- if ("connectionId" in target) {
10921
- return { connectionId: target.connectionId, target };
10922
- }
10923
- if ("deviceId" in target) {
10924
- return ensureOnline(await this.getEnvironmentByDeviceId(target.deviceId), target);
10925
- }
10926
- const { connections } = await this.listEnvironments();
10927
- if ("environmentId" in target) {
10928
- const match2 = connections.find((env) => env.id === target.environmentId);
10929
- if (!match2) {
10930
- throw new Error(`Remote environment not found: ${target.environmentId}`);
10931
- }
10932
- return ensureOnline(match2, target);
10933
- }
10934
- const matches = connections.filter((env) => env.connectionName === target.connectionName);
10935
- if (matches.length === 0) {
10936
- throw new Error(`Remote environment not found: ${target.connectionName}`);
10937
- }
10938
- if (matches.length > 1) {
10939
- throw new Error(`Remote environment name is ambiguous: ${target.connectionName}`);
10940
- }
10941
- const match = matches[0];
10942
- if (!match) {
10943
- throw new Error(`Remote environment not found: ${target.connectionName}`);
10944
- }
10945
- return ensureOnline(match, target);
10946
- }
10947
- }
10948
-
10949
11112
  // src/cloud-sandbox.ts
10950
11113
  var MIN_TTL_MINUTES = 1;
10951
11114
  var MAX_TTL_MINUTES = 60;
@@ -11044,8 +11207,12 @@ function validatePositiveInteger3(value, name) {
11044
11207
  function validateCloudClientOptions(options) {
11045
11208
  validatePositiveInteger3(options.requestTimeoutMs, "requestTimeoutMs");
11046
11209
  validateCloudSandboxOptions(options.sandbox, "sandbox");
11047
- if (options.environment !== undefined && options.sandbox !== undefined) {
11048
- throw new Error("Letta Cloud sessions cannot specify both environment and sandbox options.");
11210
+ if (options.computer !== undefined && options.environment !== undefined) {
11211
+ throw new Error("Letta Cloud clients cannot specify both computer and deprecated environment.");
11212
+ }
11213
+ if ((options.computer !== undefined || options.environment !== undefined) && options.sandbox !== undefined) {
11214
+ const field = options.computer !== undefined ? "computer" : "environment";
11215
+ throw new Error(`Letta Cloud sessions cannot specify both ${field} and sandbox options.`);
11049
11216
  }
11050
11217
  if (options.webSocketAuth !== undefined && options.webSocketAuth !== "header" && options.webSocketAuth !== "query") {
11051
11218
  throw new Error("Invalid webSocketAuth. Valid values: header, query.");
@@ -11162,12 +11329,30 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11162
11329
  throw error;
11163
11330
  });
11164
11331
  this.connectionId = connection.connectionId;
11332
+ const options = this.currentOptions();
11333
+ this.mcpBridge = await connectMcpServers("mcpServers" in options ? options.mcpServers : undefined, {
11334
+ cwd: options.cwd,
11335
+ reservedToolNames: this.externalTools.keys()
11336
+ });
11337
+ for (const tool of this.mcpBridge.tools) {
11338
+ this.externalTools.set(tool.name, tool);
11339
+ }
11340
+ try {
11341
+ return await this.openCloudTransport(resolved.runtime, connection.connectionId);
11342
+ } catch (error) {
11343
+ await this.closeMcpBridge();
11344
+ await this.cleanupManagedSandbox();
11345
+ await this.cleanupSessionRepositories(resolved.runtime.agent_id, resolved.runtime.conversation_id);
11346
+ throw error;
11347
+ }
11348
+ }
11349
+ async openCloudTransport(runtime, connectionId) {
11165
11350
  const apiKey = getCloudApiKey(this.cloudOptions);
11166
11351
  const url = buildCloudStatusWebSocketUrl({
11167
11352
  apiBaseUrl: this.cloudOptions.apiBaseUrl,
11168
- connectionId: connection.connectionId,
11169
- agentId: resolved.runtime.agent_id,
11170
- conversationId: resolved.runtime.conversation_id,
11353
+ connectionId,
11354
+ agentId: runtime.agent_id,
11355
+ conversationId: runtime.conversation_id,
11171
11356
  apiKey,
11172
11357
  authMode: this.cloudOptions.webSocketAuth ?? "header"
11173
11358
  });
@@ -11178,33 +11363,19 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11178
11363
  WebSocket: getWebSocketConstructor(this.cloudOptions.WebSocket),
11179
11364
  ...(this.cloudOptions.webSocketAuth ?? "header") === "header" ? { headers: cloudWebSocketHeaders(this.cloudOptions) } : {},
11180
11365
  pingIntervalMs: this.cloudOptions.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS,
11181
- runtime: resolved.runtime
11366
+ runtime
11182
11367
  }),
11183
11368
  requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
11184
11369
  }));
11185
11370
  const options = this.currentOptions();
11186
- this.mcpBridge = await connectMcpServers("mcpServers" in options ? options.mcpServers : undefined, {
11187
- cwd: options.cwd,
11188
- reservedToolNames: this.externalTools.keys()
11189
- });
11190
- for (const tool of this.mcpBridge.tools) {
11191
- this.externalTools.set(tool.name, tool);
11192
- }
11193
- this.removeControlRequestHandler = registerAppServerControlRequestHandler({
11194
- client,
11195
- getRuntime: () => this.runtime,
11196
- getOptions: () => this.currentOptions()
11197
- });
11198
- if (this.externalTools.size > 0) {
11199
- this.removeExternalToolHandler = client.onExternalToolCall(createExternalToolCallHandler(this.externalTools));
11200
- }
11371
+ this.installTransportHandlers(client);
11201
11372
  try {
11202
11373
  await client.connect();
11203
- const response = await this.startCloudRuntime(client, resolved.runtime);
11374
+ const response = await this.startCloudRuntime(client, runtime);
11204
11375
  if (!response.success || !response.runtime) {
11205
11376
  throw new Error(response.error ?? "Failed to start Cloud status runtime");
11206
11377
  }
11207
- this.watchTransportDisconnect(client);
11378
+ this.watchTransportDisconnect(client, { recoverWhenIdle: true });
11208
11379
  const tools = agentToolNames(response.agent);
11209
11380
  const skillSources = options.skillSources;
11210
11381
  const clientToolset = "toolset" in options ? options.toolset : undefined;
@@ -11222,17 +11393,51 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11222
11393
  ...skillSources !== undefined ? { skillSources: [...skillSources] } : {}
11223
11394
  };
11224
11395
  } catch (error) {
11225
- this.removeExternalToolHandler?.();
11226
- this.removeExternalToolHandler = null;
11227
- this.removeControlRequestHandler?.();
11228
- this.removeControlRequestHandler = null;
11229
- await this.closeMcpBridge();
11396
+ this.cleanupTransportHandlers();
11230
11397
  client.close();
11231
- await this.cleanupManagedSandbox();
11232
- await this.cleanupSessionRepositories(resolved.runtime.agent_id, resolved.runtime.conversation_id);
11233
11398
  throw error;
11234
11399
  }
11235
11400
  }
11401
+ installTransportHandlers(client) {
11402
+ this.cleanupTransportHandlers();
11403
+ this.removeControlRequestHandler = registerAppServerControlRequestHandler({
11404
+ client,
11405
+ getRuntime: () => this.runtime,
11406
+ getOptions: () => this.currentOptions()
11407
+ });
11408
+ if (this.externalTools.size > 0) {
11409
+ this.removeExternalToolHandler = client.onExternalToolCall(createExternalToolCallHandler(this.externalTools));
11410
+ }
11411
+ }
11412
+ cleanupTransportHandlers() {
11413
+ this.removeExternalToolHandler?.();
11414
+ this.removeExternalToolHandler = null;
11415
+ this.removeControlRequestHandler?.();
11416
+ this.removeControlRequestHandler = null;
11417
+ }
11418
+ async recoverIdleTransport(runtime) {
11419
+ const connection = await this.resolveRecoveryConnection(runtime);
11420
+ this.connectionId = connection.connectionId;
11421
+ return this.openCloudTransport(runtime, connection.connectionId);
11422
+ }
11423
+ async resolveRecoveryConnection(runtime) {
11424
+ const sandbox = this.managedSandbox;
11425
+ if (sandbox) {
11426
+ await this.refreshManagedSandbox(sandbox);
11427
+ return this.waitForManagedSandboxConnection(sandbox);
11428
+ }
11429
+ const environment = this.effectiveEnvironment();
11430
+ if (!environment) {
11431
+ throw new Error("Cloud idle transport recovery lost its execution target");
11432
+ }
11433
+ return this.resolveExplicitConnection(environment);
11434
+ }
11435
+ onIdleTransportDisconnect() {
11436
+ this.cleanupTransportHandlers();
11437
+ }
11438
+ onRecoveredTransportDiscarded() {
11439
+ this.cleanupTransportHandlers();
11440
+ }
11236
11441
  async startCloudRuntime(client, runtime) {
11237
11442
  const options = this.currentOptions();
11238
11443
  const command = {
@@ -11250,6 +11455,8 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11250
11455
  command.mode = mode;
11251
11456
  if (options.cwd !== undefined)
11252
11457
  command.cwd = options.cwd;
11458
+ if (this.cloudMode.options.stateless === true)
11459
+ command.stateless = true;
11253
11460
  if (options.skillSources !== undefined) {
11254
11461
  command.skill_sources = [...new Set(options.skillSources)];
11255
11462
  }
@@ -11275,10 +11482,7 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11275
11482
  await this.refreshManagedSandbox(sandbox);
11276
11483
  }
11277
11484
  onCoreClose() {
11278
- this.removeExternalToolHandler?.();
11279
- this.removeExternalToolHandler = null;
11280
- this.removeControlRequestHandler?.();
11281
- this.removeControlRequestHandler = null;
11485
+ this.cleanupTransportHandlers();
11282
11486
  this.mcpCleanup = this.closeMcpBridge();
11283
11487
  this.cleanupManagedSandbox();
11284
11488
  if (this.runtime?.agent_id) {
@@ -11602,8 +11806,8 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11602
11806
  return new RemoteEnvironmentClient({}, this.apiClient);
11603
11807
  }
11604
11808
  effectiveEnvironment() {
11605
- const modeEnvironment = this.mode.kind === "session" ? this.mode.options.environment : undefined;
11606
- return modeEnvironment ?? this.cloudOptions.environment;
11809
+ const modeComputer = this.mode.kind === "session" ? this.mode.options.computer ?? this.mode.options.environment : undefined;
11810
+ return modeComputer ?? this.cloudOptions.computer ?? this.cloudOptions.environment;
11607
11811
  }
11608
11812
  effectiveSandboxOptions() {
11609
11813
  const modeSandbox = this.mode.kind === "session" ? this.mode.options.sandbox : undefined;
@@ -11857,6 +12061,20 @@ function validateMcpServers(servers) {
11857
12061
  }
11858
12062
  }
11859
12063
  function validateCreateSessionOptions(options) {
12064
+ if (options.stateless !== undefined && typeof options.stateless !== "boolean") {
12065
+ throw new Error("Invalid stateless. Expected a boolean.");
12066
+ }
12067
+ if (options.stateless) {
12068
+ const persistedOption = [
12069
+ ["model", options.model],
12070
+ ["reasoningEffort", options.reasoningEffort],
12071
+ ["dreaming", options.dreaming],
12072
+ ["resources", options.resources]
12073
+ ].find(([, value]) => value !== undefined);
12074
+ if (persistedOption) {
12075
+ throw new Error(`stateless sessions cannot set ${persistedOption[0]} because it changes persisted agent configuration.`);
12076
+ }
12077
+ }
11860
12078
  validateClientToolset(options.toolset);
11861
12079
  validateSkillSources(options.skillSources);
11862
12080
  validateMcpServers(options.mcpServers);
@@ -11903,14 +12121,17 @@ var VALID_BACKENDS = new Set([
11903
12121
  function isLettaCodeBackend(value) {
11904
12122
  return VALID_BACKENDS.has(value);
11905
12123
  }
11906
- function getOptionsEnvironment(options) {
11907
- if ("environment" in options) {
11908
- return options.environment;
12124
+ function getOptionsComputer(options) {
12125
+ if (!("computer" in options) && !("environment" in options))
12126
+ return;
12127
+ if (options.computer !== undefined && options.environment !== undefined) {
12128
+ throw new Error('Specify either "computer" or deprecated "environment", not both.');
11909
12129
  }
11910
- return;
12130
+ return options.computer ?? options.environment;
11911
12131
  }
11912
12132
  function stripCloudExecutionOptions(options) {
11913
12133
  const sessionOptions = { ...options };
12134
+ delete sessionOptions.computer;
11914
12135
  delete sessionOptions.environment;
11915
12136
  delete sessionOptions.sandbox;
11916
12137
  delete sessionOptions.filesystemConfinement;
@@ -11928,12 +12149,14 @@ function looksLikeConversationId(id) {
11928
12149
 
11929
12150
  class LettaAgentClientBase {
11930
12151
  backend;
12152
+ computer;
11931
12153
  environment;
11932
12154
  agents;
11933
12155
  conversations;
11934
12156
  models;
11935
12157
  options;
11936
12158
  repositoriesClient = null;
12159
+ computersClient = null;
11937
12160
  agentRepositoriesClient = null;
11938
12161
  cloudClient = null;
11939
12162
  managementTransport = null;
@@ -11943,16 +12166,19 @@ class LettaAgentClientBase {
11943
12166
  throw new Error(`Invalid Letta Code backend '${String(backend)}'. Valid values: local, remote, cloud.`);
11944
12167
  }
11945
12168
  this.backend = backend;
11946
- this.environment = getOptionsEnvironment(options);
12169
+ this.computer = getOptionsComputer(options);
12170
+ this.environment = this.computer;
11947
12171
  this.options = options;
11948
12172
  this.agents = createAgentsClient(() => this.getManagementTransport(), () => this.getAgentRepositoriesClient());
11949
12173
  this.conversations = createConversationsClient(() => this.getManagementTransport());
11950
12174
  this.models = createModelsClient(() => this.getManagementTransport());
11951
- if (this.backend === "local" && this.environment !== undefined) {
11952
- throw new Error('LettaAgentClient environment is only valid with backend: "cloud".');
12175
+ if (this.backend === "local" && this.computer !== undefined) {
12176
+ const field = "computer" in options && options.computer !== undefined ? "computer" : "environment";
12177
+ throw new Error(`LettaAgentClient ${field} is only valid with backend: "cloud".`);
11953
12178
  }
11954
- if (this.backend === "remote" && this.environment !== undefined) {
11955
- throw new Error('LettaAgentClient environment is only valid with backend: "cloud"; remote url selects the app-server runtime.');
12179
+ if (this.backend === "remote" && this.computer !== undefined) {
12180
+ const field = "computer" in options && options.computer !== undefined ? "computer" : "environment";
12181
+ throw new Error(`LettaAgentClient ${field} is only valid with backend: "cloud"; remote url selects the app-server runtime.`);
11956
12182
  }
11957
12183
  if (this.backend !== "cloud" && options.sandbox !== undefined) {
11958
12184
  throw new Error('LettaAgentClient sandbox options are only valid with backend: "cloud".');
@@ -11986,6 +12212,9 @@ class LettaAgentClientBase {
11986
12212
  get repositories() {
11987
12213
  return this.getRepositoriesClient();
11988
12214
  }
12215
+ get computers() {
12216
+ return this.getComputersClient();
12217
+ }
11989
12218
  async createAgent(options = {}) {
11990
12219
  if (hasCreateAgentEnvironment(options)) {
11991
12220
  throw new Error("createAgent() does not accept environment. Set a client default or pass environment to resumeSession()/createSession().");
@@ -12075,13 +12304,17 @@ class LettaAgentClientBase {
12075
12304
  }
12076
12305
  }
12077
12306
  assertSessionBackend(action, options) {
12307
+ if (options.computer !== undefined && options.environment !== undefined) {
12308
+ throw new Error(`${action}() cannot specify both computer and deprecated environment.`);
12309
+ }
12078
12310
  if (options.filesystemConfinement !== undefined && options.filesystemConfinement !== "memory") {
12079
12311
  throw new Error(`Invalid filesystemConfinement '${String(options.filesystemConfinement)}'. Valid value: memory.`);
12080
12312
  }
12081
- const effectiveEnvironment = options.environment ?? this.environment;
12313
+ const effectiveComputer = options.computer ?? options.environment ?? this.computer;
12082
12314
  if (this.backend === "local") {
12083
- if (effectiveEnvironment !== undefined) {
12084
- throw new Error(`${action}() environment overrides are only valid with backend: "cloud".`);
12315
+ if (effectiveComputer !== undefined) {
12316
+ const field = options.computer !== undefined ? "computer" : "environment";
12317
+ throw new Error(`${action}() ${field} overrides are only valid with backend: "cloud".`);
12085
12318
  }
12086
12319
  if (options.sandbox !== undefined) {
12087
12320
  throw new Error(`${action}() sandbox options are only valid with backend: "cloud".`);
@@ -12101,8 +12334,9 @@ class LettaAgentClientBase {
12101
12334
  if (options.filesystemConfinement !== undefined) {
12102
12335
  throw new Error(`${action}() filesystemConfinement requires an SDK-owned local app-server process.`);
12103
12336
  }
12104
- if (options.environment !== undefined) {
12105
- throw new Error(`${action}() environment overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
12337
+ if (options.computer !== undefined || options.environment !== undefined) {
12338
+ const field = options.computer !== undefined ? "computer" : "environment";
12339
+ throw new Error(`${action}() ${field} overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
12106
12340
  }
12107
12341
  if (options.sandbox !== undefined) {
12108
12342
  throw new Error(`${action}() sandbox options are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
@@ -12117,11 +12351,13 @@ class LettaAgentClientBase {
12117
12351
  throw new Error(`${action}() filesystemConfinement is only supported with backend: "local".`);
12118
12352
  }
12119
12353
  const cloudOptions = this.cloudOptions();
12120
- if (cloudOptions.environment !== undefined && options.sandbox !== undefined) {
12121
- throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default environment.`);
12354
+ if (this.computer !== undefined && options.sandbox !== undefined) {
12355
+ const field = cloudOptions.computer !== undefined ? "computer" : "environment";
12356
+ throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default ${field}.`);
12122
12357
  }
12123
- if (cloudOptions.sandbox !== undefined && options.environment !== undefined) {
12124
- throw new Error(`Letta Cloud ${action}() cannot specify an environment when the client has default sandbox options.`);
12358
+ if (cloudOptions.sandbox !== undefined && (options.computer !== undefined || options.environment !== undefined)) {
12359
+ const field = options.computer !== undefined ? "a computer" : "an environment";
12360
+ throw new Error(`Letta Cloud ${action}() cannot specify ${field} when the client has default sandbox options.`);
12125
12361
  }
12126
12362
  assertCloudSessionOptionsSupported(action, options);
12127
12363
  return;
@@ -12152,6 +12388,13 @@ class LettaAgentClientBase {
12152
12388
  appServerSessionOptions() {
12153
12389
  return this.remoteOptions();
12154
12390
  }
12391
+ getComputersClient() {
12392
+ if (this.backend !== "cloud") {
12393
+ throw new Error('client.computers is only available with backend: "cloud".');
12394
+ }
12395
+ this.computersClient ??= new ComputersClientImpl(this.getCloudClient());
12396
+ return this.computersClient;
12397
+ }
12155
12398
  getRepositoriesClient() {
12156
12399
  if (this.backend !== "cloud") {
12157
12400
  throw new Error('client.repositories is only available with backend: "cloud".');
@@ -12260,6 +12503,440 @@ function extractStreamTextDelta(event) {
12260
12503
  }
12261
12504
  return null;
12262
12505
  }
12506
+ // src/transcript-accumulator.ts
12507
+ var SEP = ":";
12508
+ var MAX_TRACKED_RUNS = 64;
12509
+ var ANONYMOUS_RUN = "";
12510
+ function familyOtidAlias(kind, otid) {
12511
+ return `${kind}${SEP}otid${SEP}${otid}`;
12512
+ }
12513
+ function familyUuidAlias(kind, uuid) {
12514
+ return `${kind}${SEP}uuid${SEP}${uuid}`;
12515
+ }
12516
+ function toolRowKey(toolCallId) {
12517
+ return `tool_call${SEP}id${SEP}${toolCallId}`;
12518
+ }
12519
+ function asRecord(value) {
12520
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
12521
+ }
12522
+ function readString(record, field) {
12523
+ const value = record[field];
12524
+ return typeof value === "string" && value.length > 0 ? value : undefined;
12525
+ }
12526
+ function readNumber(record, field) {
12527
+ const value = record[field];
12528
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12529
+ }
12530
+ function parseJsonObject(raw) {
12531
+ const trimmed = raw.trim();
12532
+ if (trimmed.length === 0)
12533
+ return;
12534
+ try {
12535
+ return asRecord(JSON.parse(trimmed));
12536
+ } catch {
12537
+ return;
12538
+ }
12539
+ }
12540
+ function isRawArgumentsWrapper(input, rawArguments) {
12541
+ if (!input)
12542
+ return false;
12543
+ const keys = Object.keys(input);
12544
+ if (keys.length !== 1 || keys[0] !== "raw")
12545
+ return false;
12546
+ const wrapped = input.raw;
12547
+ if (typeof wrapped !== "string")
12548
+ return false;
12549
+ return rawArguments === undefined || wrapped === rawArguments;
12550
+ }
12551
+ function toolStatus(row) {
12552
+ if (row.result)
12553
+ return "complete";
12554
+ return row.argumentsComplete ? "ready" : "streaming";
12555
+ }
12556
+ function mergeToolArguments(previous, merge) {
12557
+ let { rawArguments, toolInput, argumentsComplete } = previous;
12558
+ const fragment = merge.rawArguments;
12559
+ const wrapped = isRawArgumentsWrapper(merge.toolInput, fragment);
12560
+ if (fragment !== undefined && fragment.length > 0) {
12561
+ const whole = parseJsonObject(fragment);
12562
+ if (whole) {
12563
+ return { rawArguments: fragment, toolInput: whole, argumentsComplete: true };
12564
+ }
12565
+ if (argumentsComplete) {
12566
+ return previous;
12567
+ }
12568
+ if (merge.mode === "whole") {
12569
+ return { rawArguments: rawArguments ?? fragment, toolInput, argumentsComplete };
12570
+ }
12571
+ rawArguments = (rawArguments ?? "") + fragment;
12572
+ const parsed = parseJsonObject(rawArguments);
12573
+ if (parsed) {
12574
+ return { rawArguments, toolInput: parsed, argumentsComplete: true };
12575
+ }
12576
+ return { rawArguments, toolInput, argumentsComplete: false };
12577
+ }
12578
+ if (!wrapped && merge.toolInput) {
12579
+ const keys = Object.keys(merge.toolInput);
12580
+ if (keys.length > 0) {
12581
+ return { rawArguments, toolInput: merge.toolInput, argumentsComplete: true };
12582
+ }
12583
+ if (rawArguments === undefined) {
12584
+ return { rawArguments, toolInput, argumentsComplete: true };
12585
+ }
12586
+ }
12587
+ return previous;
12588
+ }
12589
+
12590
+ class TranscriptAccumulatorImpl {
12591
+ byKey = new Map;
12592
+ aliasByOtid = new Map;
12593
+ aliasByUuid = new Map;
12594
+ seqThresholds = new Map;
12595
+ snapshot = null;
12596
+ anonymousCounter = 0;
12597
+ apply(message) {
12598
+ switch (message.type) {
12599
+ case "assistant":
12600
+ case "reasoning":
12601
+ this.applyText({
12602
+ kind: message.type,
12603
+ text: message.content,
12604
+ uuid: message.uuid,
12605
+ otid: typeof message.otid === "string" ? message.otid : undefined,
12606
+ runId: message.runId,
12607
+ seqId: message.seqId
12608
+ });
12609
+ break;
12610
+ case "tool_call":
12611
+ this.mergeToolCall({
12612
+ toolCallId: message.toolCallId,
12613
+ toolName: message.toolName,
12614
+ toolInput: message.toolInput,
12615
+ rawArguments: message.rawArguments,
12616
+ uuid: message.uuid,
12617
+ runId: message.runId,
12618
+ mode: "fragment"
12619
+ });
12620
+ break;
12621
+ case "tool_result":
12622
+ this.mergeToolResult({
12623
+ toolCallId: message.toolCallId,
12624
+ content: message.content,
12625
+ isError: message.isError,
12626
+ uuid: message.uuid,
12627
+ runId: message.runId
12628
+ });
12629
+ break;
12630
+ case "stream_event":
12631
+ this.applyStreamEvent(message);
12632
+ break;
12633
+ default:
12634
+ break;
12635
+ }
12636
+ return this.rows();
12637
+ }
12638
+ rebase(page, options) {
12639
+ const messages = normalizeHistoryPage(page, options?.order);
12640
+ if (messages.length === 0)
12641
+ return this.rows();
12642
+ const historyKeys = [];
12643
+ const seen = new Set;
12644
+ for (const message of messages) {
12645
+ const key = this.applyHistoryMessage(message);
12646
+ if (!key || seen.has(key))
12647
+ continue;
12648
+ seen.add(key);
12649
+ historyKeys.push(key);
12650
+ }
12651
+ this.reorder(historyKeys);
12652
+ return this.rows();
12653
+ }
12654
+ rows() {
12655
+ if (!this.snapshot) {
12656
+ this.snapshot = Array.from(this.byKey.values());
12657
+ }
12658
+ return this.snapshot;
12659
+ }
12660
+ reset() {
12661
+ this.byKey.clear();
12662
+ this.aliasByOtid.clear();
12663
+ this.aliasByUuid.clear();
12664
+ this.seqThresholds.clear();
12665
+ this.anonymousCounter = 0;
12666
+ this.snapshot = null;
12667
+ }
12668
+ isReplay(runId, seqId) {
12669
+ if (seqId === undefined)
12670
+ return false;
12671
+ const bucket = runId ?? ANONYMOUS_RUN;
12672
+ const threshold = this.seqThresholds.get(bucket);
12673
+ if (threshold !== undefined && seqId <= threshold)
12674
+ return true;
12675
+ this.rememberSeq(bucket, seqId);
12676
+ return false;
12677
+ }
12678
+ rememberSeq(bucket, seqId) {
12679
+ const threshold = this.seqThresholds.get(bucket);
12680
+ if (threshold !== undefined && threshold >= seqId)
12681
+ return;
12682
+ this.seqThresholds.set(bucket, seqId);
12683
+ while (this.seqThresholds.size > MAX_TRACKED_RUNS) {
12684
+ const oldest = this.seqThresholds.keys().next();
12685
+ if (oldest.done)
12686
+ break;
12687
+ this.seqThresholds.delete(oldest.value);
12688
+ }
12689
+ }
12690
+ applyText(slice) {
12691
+ if (this.isReplay(slice.runId, slice.seqId))
12692
+ return;
12693
+ this.writeText(slice, "append");
12694
+ }
12695
+ writeText(slice, write) {
12696
+ const key = this.resolveTextKey(slice.kind, slice.uuid, slice.otid);
12697
+ const existing = this.byKey.get(key);
12698
+ const previous = existing && existing.kind === slice.kind ? existing : undefined;
12699
+ this.setRow(key, {
12700
+ kind: slice.kind,
12701
+ key,
12702
+ text: write === "append" ? (previous?.text ?? "") + slice.text : slice.text,
12703
+ uuid: previous?.uuid ?? slice.uuid,
12704
+ otid: slice.otid ?? previous?.otid,
12705
+ runId: slice.runId ?? previous?.runId,
12706
+ seqId: maxDefined(slice.seqId, previous?.seqId)
12707
+ });
12708
+ return key;
12709
+ }
12710
+ resolveTextKey(kind, uuid, otid) {
12711
+ const otidAlias = otid ? familyOtidAlias(kind, otid) : undefined;
12712
+ const uuidAlias = uuid ? familyUuidAlias(kind, uuid) : undefined;
12713
+ const fromOtid = otidAlias ? this.aliasByOtid.get(otidAlias) : undefined;
12714
+ const fromUuid = uuidAlias ? this.aliasByUuid.get(uuidAlias) : undefined;
12715
+ let key = fromOtid ?? fromUuid;
12716
+ if (key && !fromOtid && otid) {
12717
+ const existing = this.byKey.get(key);
12718
+ if (existing && existing.otid !== undefined && existing.otid !== otid) {
12719
+ key = undefined;
12720
+ }
12721
+ }
12722
+ if (!key) {
12723
+ key = otidAlias ?? uuidAlias ?? `${kind}${SEP}auto${SEP}${++this.anonymousCounter}`;
12724
+ }
12725
+ if (otidAlias)
12726
+ this.aliasByOtid.set(otidAlias, key);
12727
+ if (uuidAlias)
12728
+ this.aliasByUuid.set(uuidAlias, key);
12729
+ return key;
12730
+ }
12731
+ mergeToolCall(merge) {
12732
+ const key = toolRowKey(merge.toolCallId);
12733
+ const existing = this.byKey.get(key);
12734
+ const previous = existing && existing.kind === "tool_call" ? existing : undefined;
12735
+ const args = mergeToolArguments({
12736
+ rawArguments: previous?.rawArguments,
12737
+ toolInput: previous?.toolInput ?? {},
12738
+ argumentsComplete: previous?.argumentsComplete ?? false
12739
+ }, merge);
12740
+ const toolName = merge.toolName && merge.toolName !== "?" ? merge.toolName : previous?.toolName ?? merge.toolName ?? "?";
12741
+ const next = {
12742
+ kind: "tool_call",
12743
+ key,
12744
+ toolCallId: merge.toolCallId,
12745
+ toolName,
12746
+ toolInput: args.toolInput,
12747
+ ...args.rawArguments !== undefined ? { rawArguments: args.rawArguments } : {},
12748
+ argumentsComplete: args.argumentsComplete,
12749
+ ...previous?.result ? { result: previous.result } : {},
12750
+ status: "streaming",
12751
+ uuid: previous?.uuid ?? merge.uuid,
12752
+ runId: merge.runId ?? previous?.runId,
12753
+ seqId: maxDefined(merge.seqId, previous?.seqId)
12754
+ };
12755
+ next.status = toolStatus(next);
12756
+ this.setRow(key, next);
12757
+ return key;
12758
+ }
12759
+ mergeToolResult(merge) {
12760
+ const key = toolRowKey(merge.toolCallId);
12761
+ const existing = this.byKey.get(key);
12762
+ const previous = existing && existing.kind === "tool_call" ? existing : undefined;
12763
+ this.setRow(key, {
12764
+ kind: "tool_call",
12765
+ key,
12766
+ toolCallId: merge.toolCallId,
12767
+ toolName: previous?.toolName ?? "?",
12768
+ toolInput: previous?.toolInput ?? {},
12769
+ ...previous?.rawArguments !== undefined ? { rawArguments: previous.rawArguments } : {},
12770
+ argumentsComplete: previous?.argumentsComplete ?? false,
12771
+ result: {
12772
+ content: merge.content,
12773
+ isError: merge.isError,
12774
+ ...merge.uuid ? { uuid: merge.uuid } : {}
12775
+ },
12776
+ status: "complete",
12777
+ uuid: previous?.uuid,
12778
+ runId: merge.runId ?? previous?.runId,
12779
+ seqId: maxDefined(merge.seqId, previous?.seqId)
12780
+ });
12781
+ return key;
12782
+ }
12783
+ applyStreamEvent(message) {
12784
+ const delta = extractStreamTextDelta(message.event);
12785
+ if (!delta)
12786
+ return;
12787
+ const payload = asRecord(message.event);
12788
+ const otid = payload ? readString(payload, "otid") : undefined;
12789
+ const payloadId = payload ? readString(payload, "id") : undefined;
12790
+ const identified = otid !== undefined || payloadId !== undefined;
12791
+ this.applyText({
12792
+ kind: delta.kind,
12793
+ text: delta.text,
12794
+ uuid: identified ? payloadId : `${delta.kind}${SEP}live`,
12795
+ otid,
12796
+ runId: payload ? readString(payload, "run_id") : undefined,
12797
+ seqId: payload ? readNumber(payload, "seq_id") : undefined
12798
+ });
12799
+ }
12800
+ applyHistoryMessage(message) {
12801
+ const record = asRecord(message);
12802
+ if (!record)
12803
+ return;
12804
+ const messageType = readString(record, "message_type");
12805
+ if (!messageType)
12806
+ return;
12807
+ const uuid = readString(record, "id");
12808
+ const otid = readString(record, "otid");
12809
+ const runId = readString(record, "run_id");
12810
+ const seqId = readNumber(record, "seq_id");
12811
+ if (seqId !== undefined) {
12812
+ this.rememberSeq(runId ?? ANONYMOUS_RUN, seqId);
12813
+ }
12814
+ switch (messageType) {
12815
+ case "user_message":
12816
+ case "assistant_message": {
12817
+ const text = extractTextFromContent(record.content);
12818
+ if (text === null)
12819
+ return;
12820
+ return this.writeText({
12821
+ kind: messageType === "user_message" ? "user" : "assistant",
12822
+ text,
12823
+ uuid,
12824
+ otid,
12825
+ runId,
12826
+ seqId
12827
+ }, "replace");
12828
+ }
12829
+ case "reasoning_message": {
12830
+ const text = typeof record.reasoning === "string" ? record.reasoning : extractTextFromContent(record.content);
12831
+ if (text === null || text === undefined)
12832
+ return;
12833
+ return this.writeText({ kind: "reasoning", text, uuid, otid, runId, seqId }, "replace");
12834
+ }
12835
+ case "tool_call_message":
12836
+ case "approval_request_message": {
12837
+ const toolCall = firstToolCall(record);
12838
+ if (!toolCall)
12839
+ return;
12840
+ const fn = asRecord(toolCall.function);
12841
+ const toolCallId = (typeof toolCall.tool_call_id === "string" ? toolCall.tool_call_id : undefined) ?? (typeof toolCall.id === "string" ? toolCall.id : undefined);
12842
+ if (!toolCallId)
12843
+ return;
12844
+ const args = toolCall.arguments ?? fn?.arguments;
12845
+ return this.mergeToolCall({
12846
+ toolCallId,
12847
+ toolName: (typeof toolCall.name === "string" ? toolCall.name : undefined) ?? (typeof fn?.name === "string" ? fn.name : undefined),
12848
+ toolInput: asRecord(args),
12849
+ rawArguments: typeof args === "string" ? args : undefined,
12850
+ uuid,
12851
+ runId,
12852
+ seqId,
12853
+ mode: "whole"
12854
+ });
12855
+ }
12856
+ case "tool_return_message": {
12857
+ const toolReturn = firstToolReturn(record) ?? record;
12858
+ const toolCallId = readString(record, "tool_call_id") ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
12859
+ if (!toolCallId)
12860
+ return;
12861
+ const content = extractTextFromContent(record.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
12862
+ const status = readString(record, "status") ?? (typeof toolReturn.status === "string" ? toolReturn.status : undefined);
12863
+ return this.mergeToolResult({
12864
+ toolCallId,
12865
+ content,
12866
+ isError: status === "error",
12867
+ uuid,
12868
+ runId,
12869
+ seqId
12870
+ });
12871
+ }
12872
+ default:
12873
+ return;
12874
+ }
12875
+ }
12876
+ setRow(key, row) {
12877
+ this.byKey.set(key, row);
12878
+ this.snapshot = null;
12879
+ }
12880
+ reorder(historyKeys) {
12881
+ if (historyKeys.length === 0)
12882
+ return;
12883
+ const historySet = new Set(historyKeys);
12884
+ const reordered = new Map;
12885
+ for (const key of historyKeys) {
12886
+ const row = this.byKey.get(key);
12887
+ if (row)
12888
+ reordered.set(key, row);
12889
+ }
12890
+ for (const [key, row] of this.byKey) {
12891
+ if (historySet.has(key))
12892
+ continue;
12893
+ reordered.set(key, row);
12894
+ }
12895
+ this.byKey = reordered;
12896
+ this.snapshot = null;
12897
+ }
12898
+ }
12899
+ function maxDefined(next, previous) {
12900
+ if (next === undefined)
12901
+ return previous;
12902
+ if (previous === undefined)
12903
+ return next;
12904
+ return Math.max(next, previous);
12905
+ }
12906
+ function normalizeHistoryPage(page, order) {
12907
+ const messages = Array.isArray(page) ? page : page.messages ?? [];
12908
+ if (messages.length < 2)
12909
+ return messages;
12910
+ const descending = order ? order === "desc" : detectDescending(messages);
12911
+ return descending ? [...messages].reverse() : messages;
12912
+ }
12913
+ function detectDescending(messages) {
12914
+ const seqIds = [];
12915
+ const dates = [];
12916
+ for (const message of messages) {
12917
+ const record = asRecord(message);
12918
+ if (!record)
12919
+ continue;
12920
+ const seqId = readNumber(record, "seq_id");
12921
+ if (seqId !== undefined)
12922
+ seqIds.push(seqId);
12923
+ const date = readString(record, "date");
12924
+ if (!date)
12925
+ continue;
12926
+ const parsed = Date.parse(date);
12927
+ if (!Number.isNaN(parsed))
12928
+ dates.push(parsed);
12929
+ }
12930
+ const ordered = seqIds.length >= 2 ? seqIds : dates;
12931
+ if (ordered.length < 2)
12932
+ return false;
12933
+ const first = ordered[0];
12934
+ const last = ordered[ordered.length - 1];
12935
+ return last < first;
12936
+ }
12937
+ function createTranscriptAccumulator() {
12938
+ return new TranscriptAccumulatorImpl;
12939
+ }
12263
12940
  // src/websocket.ts
12264
12941
  function createReactNativeWebSocketConstructor(WebSocket) {
12265
12942
  class ReactNativeWebSocketAdapter {
@@ -12281,10 +12958,11 @@ class LettaAgentClient extends LettaAgentClientBase {
12281
12958
  }
12282
12959
  export {
12283
12960
  extractStreamTextDelta,
12961
+ createTranscriptAccumulator,
12284
12962
  createReactNativeWebSocketConstructor,
12285
12963
  RepositoriesClient,
12286
12964
  LettaAgentClient,
12287
12965
  CloudManagedSandboxExpiredError
12288
12966
  };
12289
12967
 
12290
- //# debugId=B03015C0FF639CFA64756E2164756E21
12968
+ //# debugId=EF77573F8BD0F2C464756E2164756E21