@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
package/dist/index.js CHANGED
@@ -3091,7 +3091,7 @@ function isAppServerInfoResponseMessage(message) {
3091
3091
  return false;
3092
3092
  }
3093
3093
  const capabilityRecord = capabilities;
3094
- 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";
3094
+ 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";
3095
3095
  }
3096
3096
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
3097
3097
  var WEBSOCKET_OPEN_STATE = 1;
@@ -3334,6 +3334,16 @@ class AppServerClient {
3334
3334
  predicate: (message) => message.type === "runtime_start_response"
3335
3335
  });
3336
3336
  }
3337
+ runtimeExternalToolsUpdate(command, options = {}) {
3338
+ return this.request({
3339
+ type: "runtime_external_tools_update",
3340
+ request_id: command.request_id ?? this.nextRequestId("runtime-external-tools"),
3341
+ ...command
3342
+ }, {
3343
+ ...options,
3344
+ predicate: (message) => message.type === "runtime_external_tools_update_response"
3345
+ });
3346
+ }
3337
3347
  sync(command, options = {}) {
3338
3348
  return this.request({
3339
3349
  type: "sync",
@@ -3854,12 +3864,7 @@ You **MUST** be proactive in creating crons when work extends beyond the current
3854
3864
 
3855
3865
  **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.
3856
3866
 
3857
- Creating crons:
3858
- - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
3859
- - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
3860
- 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.
3861
-
3862
- 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.
3867
+ 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.
3863
3868
 
3864
3869
  # Harness Architecture
3865
3870
 
@@ -4036,12 +4041,7 @@ You **MUST** be proactive in creating crons when work extends beyond the current
4036
4041
 
4037
4042
  **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.
4038
4043
 
4039
- Creating crons:
4040
- - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
4041
- - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
4042
- 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.
4043
-
4044
- 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.
4044
+ 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.
4045
4045
 
4046
4046
  # Harness Architecture
4047
4047
 
@@ -9071,6 +9071,20 @@ function toSessionDeviceStatus(status) {
9071
9071
  function normalizeSendMessage(message) {
9072
9072
  return message;
9073
9073
  }
9074
+ function normalizeCallerOtid(otid) {
9075
+ if (otid === undefined)
9076
+ return;
9077
+ if (typeof otid !== "string" || otid.trim() === "") {
9078
+ throw new Error("send() otid must be a non-empty string");
9079
+ }
9080
+ return otid;
9081
+ }
9082
+ function turnSendOptions(turn) {
9083
+ return {
9084
+ clientMessageId: turn.clientMessageId,
9085
+ ...turn.otid !== undefined ? { otid: turn.otid } : {}
9086
+ };
9087
+ }
9074
9088
 
9075
9089
  // src/remote-turn-coordinator.ts
9076
9090
  class RemoteTurnCoordinator {
@@ -9099,11 +9113,13 @@ class RemoteTurnCoordinator {
9099
9113
  hasInFlightTurn() {
9100
9114
  return this.activeTurn !== null || this.pendingTurns.length > 0;
9101
9115
  }
9102
- trackSentTurn(runtime) {
9116
+ trackSentTurn(runtime, callerOtid) {
9117
+ const otid = normalizeCallerOtid(callerOtid);
9103
9118
  const turn = {
9104
9119
  id: ++this.nextTurnId,
9105
9120
  runtime,
9106
- clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
9121
+ ...otid !== undefined ? { otid } : {},
9122
+ clientMessageId: otid ?? `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
9107
9123
  queuedAt: Date.now(),
9108
9124
  startedAt: 0,
9109
9125
  assistantText: "",
@@ -9514,6 +9530,9 @@ class RemoteClientSessionCore {
9514
9530
  initializePromise = null;
9515
9531
  removeMessageHandler = null;
9516
9532
  detachTransportDisconnect = null;
9533
+ idleTransportDisconnected = false;
9534
+ transportRecoveryPromise = null;
9535
+ transportDisconnectGeneration = 0;
9517
9536
  turns;
9518
9537
  toolNames;
9519
9538
  deviceStatusListeners = new Set;
@@ -9602,19 +9621,25 @@ class RemoteClientSessionCore {
9602
9621
  this.toolNames = undefined;
9603
9622
  this.initialized = false;
9604
9623
  }
9605
- async send(message) {
9624
+ async send(message, options) {
9625
+ if (this.closed)
9626
+ throw new Error("Session is closed");
9606
9627
  if (!this.initialized) {
9607
9628
  await this.initialize();
9608
9629
  }
9630
+ await this.recoverIdleTransportIfNeeded();
9609
9631
  if (!this.controller || !this.runtime) {
9610
9632
  throw new Error("Session is not initialized");
9611
9633
  }
9612
9634
  await this.beforeTurn();
9613
- const turn = this.turns.trackSentTurn(this.runtime);
9635
+ const controller = this.controller;
9636
+ const runtime = this.runtime;
9637
+ if (!controller || !runtime) {
9638
+ throw new Error("Session transport disconnected before the turn was sent");
9639
+ }
9640
+ const turn = this.turns.trackSentTurn(runtime, options?.otid);
9614
9641
  try {
9615
- this.controller.sendTurnMessage(this.runtime, message, {
9616
- clientMessageId: turn.clientMessageId
9617
- });
9642
+ controller.sendTurnMessage(runtime, message, turnSendOptions(turn));
9618
9643
  } catch (error) {
9619
9644
  this.turns.removeTrackedTurn(turn);
9620
9645
  throw error;
@@ -9693,6 +9718,9 @@ class RemoteClientSessionCore {
9693
9718
  return this.controller.listModels();
9694
9719
  }
9695
9720
  async updateModel(update) {
9721
+ if (this.mode.kind === "session" && this.mode.options.stateless === true) {
9722
+ throw new Error("updateModel() is unavailable in a stateless session because it changes persisted agent configuration.");
9723
+ }
9696
9724
  if (!this.initialized) {
9697
9725
  await this.initialize();
9698
9726
  }
@@ -9887,6 +9915,7 @@ class RemoteClientSessionCore {
9887
9915
  this.deviceStatusListeners.clear();
9888
9916
  this.controller?.close();
9889
9917
  this.controller = null;
9918
+ this.idleTransportDisconnected = false;
9890
9919
  this.onCoreClose();
9891
9920
  }
9892
9921
  get agentId() {
@@ -9939,15 +9968,78 @@ class RemoteClientSessionCore {
9939
9968
  }, { predicate: (message) => message.type === "update_toolset_response" });
9940
9969
  ensureSuccess(response, "Failed to update toolset");
9941
9970
  }
9942
- watchTransportDisconnect(client) {
9971
+ watchTransportDisconnect(client, options = {}) {
9943
9972
  this.detachTransportDisconnect?.();
9944
9973
  this.detachTransportDisconnect = client.onDisconnect(() => {
9945
9974
  if (this.closed)
9946
9975
  return;
9976
+ this.transportDisconnectGeneration += 1;
9977
+ if (options.recoverWhenIdle && !this.turns.hasInFlightTurn()) {
9978
+ this.detachTransportDisconnect?.();
9979
+ this.detachTransportDisconnect = null;
9980
+ this.removeMessageHandler?.();
9981
+ this.removeMessageHandler = null;
9982
+ this.controller?.close();
9983
+ this.controller = null;
9984
+ const error = new Error(`${this.label} connection closed before send`);
9985
+ for (const cancel of [...this.deviceStatusRefreshCancels])
9986
+ cancel(error);
9987
+ this.deviceStatusRefreshCancels.clear();
9988
+ this.onIdleTransportDisconnect();
9989
+ this.idleTransportDisconnected = true;
9990
+ return;
9991
+ }
9947
9992
  this.turns.closeWithError(`The ${this.label} connection closed unexpectedly; resume the conversation to continue.`);
9948
9993
  this.close();
9949
9994
  });
9950
9995
  }
9996
+ async recoverIdleTransportIfNeeded() {
9997
+ if (!this.idleTransportDisconnected)
9998
+ return;
9999
+ if (this.transportRecoveryPromise) {
10000
+ await this.transportRecoveryPromise;
10001
+ return;
10002
+ }
10003
+ const runtime = this.runtime;
10004
+ if (!runtime)
10005
+ throw new Error("Session transport disconnected without a runtime");
10006
+ const generation = this.transportDisconnectGeneration;
10007
+ const recovery = this.recoverIdleTransport(runtime).then(async (init) => {
10008
+ if (this.closed || generation !== this.transportDisconnectGeneration) {
10009
+ init.controller.close();
10010
+ this.onRecoveredTransportDiscarded();
10011
+ throw new Error(`${this.label} connection closed during recovery`);
10012
+ }
10013
+ if (init.runtime.agent_id !== runtime.agent_id || init.runtime.conversation_id !== runtime.conversation_id) {
10014
+ init.controller.close();
10015
+ this.onRecoveredTransportDiscarded();
10016
+ throw new Error(`${this.label} transport recovered a different runtime`);
10017
+ }
10018
+ this.controller = init.controller;
10019
+ this.runtime = init.runtime;
10020
+ this._modelSettings = init.modelSettings ?? this._modelSettings;
10021
+ if (typeof init.model === "string" && init.model)
10022
+ this._model = init.model;
10023
+ if (init.tools !== undefined)
10024
+ this.toolNames = init.tools;
10025
+ this.removeMessageHandler = this.controller.onMessage((message) => {
10026
+ if (this.runtime)
10027
+ this.turns.handleProtocolMessage(message, this.runtime);
10028
+ });
10029
+ this.idleTransportDisconnected = false;
10030
+ await this.afterRuntimeInitialized();
10031
+ }).finally(() => {
10032
+ if (this.transportRecoveryPromise === recovery)
10033
+ this.transportRecoveryPromise = null;
10034
+ });
10035
+ this.transportRecoveryPromise = recovery;
10036
+ await recovery;
10037
+ }
10038
+ async recoverIdleTransport(_runtime) {
10039
+ throw new Error(`${this.label} sessions do not support idle transport recovery`);
10040
+ }
10041
+ onIdleTransportDisconnect() {}
10042
+ onRecoveredTransportDiscarded() {}
9951
10043
  async afterRuntimeInitialized() {}
9952
10044
  async beforeTurn() {}
9953
10045
  onCoreClose() {}
@@ -10029,7 +10121,8 @@ class RemoteClientSessionCore {
10029
10121
  ensureSuccess(response, "Failed to enable memfs");
10030
10122
  }
10031
10123
  const dreamingSettings = resolveDreamingSettings(options.dreaming);
10032
- if (dreamingSettings) {
10124
+ const isStatelessSession = this.mode.kind === "session" && this.mode.options.stateless === true;
10125
+ if (dreamingSettings && !isStatelessSession) {
10033
10126
  const response = await this.controller.request("set_reflection_settings", {
10034
10127
  runtime: this.runtime,
10035
10128
  settings: dreamingSettings,
@@ -10295,7 +10388,8 @@ class AppServerRuntimeController {
10295
10388
  {
10296
10389
  role: "user",
10297
10390
  content: normalizeSendMessage(message),
10298
- client_message_id: options.clientMessageId
10391
+ client_message_id: options.clientMessageId,
10392
+ ...options.otid !== undefined ? { otid: options.otid } : {}
10299
10393
  }
10300
10394
  ]
10301
10395
  };
@@ -10522,6 +10616,9 @@ class AppServerSession extends RemoteClientSessionCore {
10522
10616
  command.mode = mode;
10523
10617
  if (options.cwd !== undefined)
10524
10618
  command.cwd = options.cwd;
10619
+ if (this.mode.kind === "session" && this.mode.options.stateless === true) {
10620
+ command.stateless = true;
10621
+ }
10525
10622
  if (options.skillSources !== undefined) {
10526
10623
  command.skill_sources = [...new Set(options.skillSources)];
10527
10624
  }
@@ -10691,6 +10788,132 @@ class CloudManagementTransport {
10691
10788
  }
10692
10789
  }
10693
10790
 
10791
+ // src/remote.ts
10792
+ function ensureOnline(environment, target) {
10793
+ if (!environment.connectionId) {
10794
+ const label = "deviceId" in target ? target.deviceId : ("environmentId" in target) ? target.environmentId : ("connectionName" in target) ? target.connectionName : environment.deviceId;
10795
+ throw new Error(`Computer is offline: ${label}`);
10796
+ }
10797
+ return {
10798
+ connectionId: environment.connectionId,
10799
+ environment,
10800
+ target
10801
+ };
10802
+ }
10803
+
10804
+ class RemoteEnvironmentClient {
10805
+ client;
10806
+ constructor(options = {}, client = createCloudClient({
10807
+ backend: "cloud",
10808
+ apiBaseUrl: options.baseUrl,
10809
+ apiKey: options.apiKey,
10810
+ headers: options.headers,
10811
+ fetch: options.fetch
10812
+ })) {
10813
+ this.client = client;
10814
+ }
10815
+ async listEnvironments(options = {}) {
10816
+ return await this.client.environments.list({
10817
+ after: options.after,
10818
+ limit: options.limit === undefined ? undefined : String(options.limit),
10819
+ onlineOnly: options.onlineOnly === undefined ? undefined : String(options.onlineOnly)
10820
+ });
10821
+ }
10822
+ async getEnvironmentByDeviceId(deviceId) {
10823
+ return await this.client.environments.retrieve(deviceId);
10824
+ }
10825
+ async resolveEnvironment(target) {
10826
+ if ("connectionId" in target) {
10827
+ return { connectionId: target.connectionId, target };
10828
+ }
10829
+ if ("deviceId" in target) {
10830
+ return ensureOnline(await this.getEnvironmentByDeviceId(target.deviceId), target);
10831
+ }
10832
+ const connections = [];
10833
+ let after;
10834
+ do {
10835
+ const page = await this.listEnvironments({ limit: 100, after });
10836
+ connections.push(...page.connections);
10837
+ if (!page.hasNextPage || page.connections.length === 0)
10838
+ break;
10839
+ after = page.connections.at(-1)?.id;
10840
+ } while (after !== undefined);
10841
+ if ("environmentId" in target) {
10842
+ const match = connections.find((env) => env.id === target.environmentId);
10843
+ if (!match) {
10844
+ throw new Error(`Remote environment not found: ${target.environmentId}`);
10845
+ }
10846
+ return ensureOnline(match, target);
10847
+ }
10848
+ const matches = connections.filter((env) => env.connectionName === target.connectionName);
10849
+ if (matches.length === 0) {
10850
+ throw new Error(`Computer not found: ${target.connectionName}`);
10851
+ }
10852
+ const onlineMatches = matches.filter((environment) => environment.connectionId !== null);
10853
+ if (onlineMatches.length === 0) {
10854
+ throw new Error(`Computer is offline: ${target.connectionName}`);
10855
+ }
10856
+ if (onlineMatches.length > 1) {
10857
+ const candidates = onlineMatches.map((environment) => `${environment.connectionName} (${environment.deviceId}, online)`).join(", ");
10858
+ throw new Error(`Computer name is ambiguous: ${target.connectionName}. Matches: ${candidates}. Select one by deviceId.`);
10859
+ }
10860
+ return ensureOnline(onlineMatches[0], target);
10861
+ }
10862
+ }
10863
+
10864
+ // src/computers.ts
10865
+ function selectorToRemoteTarget(selector) {
10866
+ if (typeof selector === "string")
10867
+ return { connectionName: selector };
10868
+ if ("name" in selector)
10869
+ return { connectionName: selector.name };
10870
+ if ("id" in selector)
10871
+ return { environmentId: selector.id };
10872
+ if ("connectionId" in selector) {
10873
+ return { connectionId: selector.connectionId };
10874
+ }
10875
+ return { deviceId: selector.deviceId };
10876
+ }
10877
+ function toComputer(environment) {
10878
+ return {
10879
+ id: environment.id,
10880
+ deviceId: environment.deviceId,
10881
+ connectionId: environment.connectionId,
10882
+ name: environment.connectionName,
10883
+ status: environment.connectionId ? "online" : "offline",
10884
+ connectedAt: environment.connectedAt,
10885
+ lastSeenAt: environment.lastSeenAt,
10886
+ metadata: environment.metadata
10887
+ };
10888
+ }
10889
+
10890
+ class ComputersClientImpl {
10891
+ environments;
10892
+ constructor(client) {
10893
+ this.environments = new RemoteEnvironmentClient({}, client);
10894
+ }
10895
+ async list(options = {}) {
10896
+ const result = await this.environments.listEnvironments(options);
10897
+ return {
10898
+ computers: result.connections.map(toComputer),
10899
+ hasNextPage: result.hasNextPage
10900
+ };
10901
+ }
10902
+ async get(deviceId) {
10903
+ if (typeof deviceId !== "string" || deviceId.trim().length === 0) {
10904
+ throw new Error("Invalid deviceId. Expected a non-empty string.");
10905
+ }
10906
+ return toComputer(await this.environments.getEnvironmentByDeviceId(deviceId));
10907
+ }
10908
+ async resolve(selector) {
10909
+ const result = await this.environments.resolveEnvironment(selectorToRemoteTarget(selector));
10910
+ return {
10911
+ connectionId: result.connectionId,
10912
+ computer: result.environment ? toComputer(result.environment) : undefined
10913
+ };
10914
+ }
10915
+ }
10916
+
10694
10917
  // src/cloud-status-transport.ts
10695
10918
  var CONNECTING = 0;
10696
10919
  var OPEN = 1;
@@ -10907,66 +11130,6 @@ function createCloudStatusTransportConstructor(options) {
10907
11130
  };
10908
11131
  }
10909
11132
 
10910
- // src/remote.ts
10911
- function ensureOnline(environment, target) {
10912
- if (!environment.connectionId) {
10913
- const label = "deviceId" in target ? target.deviceId : ("environmentId" in target) ? target.environmentId : ("connectionName" in target) ? target.connectionName : environment.deviceId;
10914
- throw new Error(`Remote environment is offline: ${label}`);
10915
- }
10916
- return {
10917
- connectionId: environment.connectionId,
10918
- environment,
10919
- target
10920
- };
10921
- }
10922
-
10923
- class RemoteEnvironmentClient {
10924
- client;
10925
- constructor(options = {}, client = createCloudClient({
10926
- backend: "cloud",
10927
- apiBaseUrl: options.baseUrl,
10928
- apiKey: options.apiKey,
10929
- headers: options.headers,
10930
- fetch: options.fetch
10931
- })) {
10932
- this.client = client;
10933
- }
10934
- async listEnvironments() {
10935
- return await this.client.environments.list();
10936
- }
10937
- async getEnvironmentByDeviceId(deviceId) {
10938
- return await this.client.environments.retrieve(deviceId);
10939
- }
10940
- async resolveEnvironment(target) {
10941
- if ("connectionId" in target) {
10942
- return { connectionId: target.connectionId, target };
10943
- }
10944
- if ("deviceId" in target) {
10945
- return ensureOnline(await this.getEnvironmentByDeviceId(target.deviceId), target);
10946
- }
10947
- const { connections } = await this.listEnvironments();
10948
- if ("environmentId" in target) {
10949
- const match2 = connections.find((env) => env.id === target.environmentId);
10950
- if (!match2) {
10951
- throw new Error(`Remote environment not found: ${target.environmentId}`);
10952
- }
10953
- return ensureOnline(match2, target);
10954
- }
10955
- const matches = connections.filter((env) => env.connectionName === target.connectionName);
10956
- if (matches.length === 0) {
10957
- throw new Error(`Remote environment not found: ${target.connectionName}`);
10958
- }
10959
- if (matches.length > 1) {
10960
- throw new Error(`Remote environment name is ambiguous: ${target.connectionName}`);
10961
- }
10962
- const match = matches[0];
10963
- if (!match) {
10964
- throw new Error(`Remote environment not found: ${target.connectionName}`);
10965
- }
10966
- return ensureOnline(match, target);
10967
- }
10968
- }
10969
-
10970
11133
  // src/cloud-sandbox.ts
10971
11134
  var MIN_TTL_MINUTES = 1;
10972
11135
  var MAX_TTL_MINUTES = 60;
@@ -11065,8 +11228,12 @@ function validatePositiveInteger3(value, name) {
11065
11228
  function validateCloudClientOptions(options) {
11066
11229
  validatePositiveInteger3(options.requestTimeoutMs, "requestTimeoutMs");
11067
11230
  validateCloudSandboxOptions(options.sandbox, "sandbox");
11068
- if (options.environment !== undefined && options.sandbox !== undefined) {
11069
- throw new Error("Letta Cloud sessions cannot specify both environment and sandbox options.");
11231
+ if (options.computer !== undefined && options.environment !== undefined) {
11232
+ throw new Error("Letta Cloud clients cannot specify both computer and deprecated environment.");
11233
+ }
11234
+ if ((options.computer !== undefined || options.environment !== undefined) && options.sandbox !== undefined) {
11235
+ const field = options.computer !== undefined ? "computer" : "environment";
11236
+ throw new Error(`Letta Cloud sessions cannot specify both ${field} and sandbox options.`);
11070
11237
  }
11071
11238
  if (options.webSocketAuth !== undefined && options.webSocketAuth !== "header" && options.webSocketAuth !== "query") {
11072
11239
  throw new Error("Invalid webSocketAuth. Valid values: header, query.");
@@ -11183,12 +11350,30 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11183
11350
  throw error;
11184
11351
  });
11185
11352
  this.connectionId = connection.connectionId;
11353
+ const options = this.currentOptions();
11354
+ this.mcpBridge = await connectMcpServers("mcpServers" in options ? options.mcpServers : undefined, {
11355
+ cwd: options.cwd,
11356
+ reservedToolNames: this.externalTools.keys()
11357
+ });
11358
+ for (const tool of this.mcpBridge.tools) {
11359
+ this.externalTools.set(tool.name, tool);
11360
+ }
11361
+ try {
11362
+ return await this.openCloudTransport(resolved.runtime, connection.connectionId);
11363
+ } catch (error) {
11364
+ await this.closeMcpBridge();
11365
+ await this.cleanupManagedSandbox();
11366
+ await this.cleanupSessionRepositories(resolved.runtime.agent_id, resolved.runtime.conversation_id);
11367
+ throw error;
11368
+ }
11369
+ }
11370
+ async openCloudTransport(runtime, connectionId) {
11186
11371
  const apiKey = getCloudApiKey(this.cloudOptions);
11187
11372
  const url = buildCloudStatusWebSocketUrl({
11188
11373
  apiBaseUrl: this.cloudOptions.apiBaseUrl,
11189
- connectionId: connection.connectionId,
11190
- agentId: resolved.runtime.agent_id,
11191
- conversationId: resolved.runtime.conversation_id,
11374
+ connectionId,
11375
+ agentId: runtime.agent_id,
11376
+ conversationId: runtime.conversation_id,
11192
11377
  apiKey,
11193
11378
  authMode: this.cloudOptions.webSocketAuth ?? "header"
11194
11379
  });
@@ -11199,33 +11384,19 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11199
11384
  WebSocket: getWebSocketConstructor(this.cloudOptions.WebSocket),
11200
11385
  ...(this.cloudOptions.webSocketAuth ?? "header") === "header" ? { headers: cloudWebSocketHeaders(this.cloudOptions) } : {},
11201
11386
  pingIntervalMs: this.cloudOptions.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS,
11202
- runtime: resolved.runtime
11387
+ runtime
11203
11388
  }),
11204
11389
  requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
11205
11390
  }));
11206
11391
  const options = this.currentOptions();
11207
- this.mcpBridge = await connectMcpServers("mcpServers" in options ? options.mcpServers : undefined, {
11208
- cwd: options.cwd,
11209
- reservedToolNames: this.externalTools.keys()
11210
- });
11211
- for (const tool of this.mcpBridge.tools) {
11212
- this.externalTools.set(tool.name, tool);
11213
- }
11214
- this.removeControlRequestHandler = registerAppServerControlRequestHandler({
11215
- client,
11216
- getRuntime: () => this.runtime,
11217
- getOptions: () => this.currentOptions()
11218
- });
11219
- if (this.externalTools.size > 0) {
11220
- this.removeExternalToolHandler = client.onExternalToolCall(createExternalToolCallHandler(this.externalTools));
11221
- }
11392
+ this.installTransportHandlers(client);
11222
11393
  try {
11223
11394
  await client.connect();
11224
- const response = await this.startCloudRuntime(client, resolved.runtime);
11395
+ const response = await this.startCloudRuntime(client, runtime);
11225
11396
  if (!response.success || !response.runtime) {
11226
11397
  throw new Error(response.error ?? "Failed to start Cloud status runtime");
11227
11398
  }
11228
- this.watchTransportDisconnect(client);
11399
+ this.watchTransportDisconnect(client, { recoverWhenIdle: true });
11229
11400
  const tools = agentToolNames(response.agent);
11230
11401
  const skillSources = options.skillSources;
11231
11402
  const clientToolset = "toolset" in options ? options.toolset : undefined;
@@ -11243,17 +11414,51 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11243
11414
  ...skillSources !== undefined ? { skillSources: [...skillSources] } : {}
11244
11415
  };
11245
11416
  } catch (error) {
11246
- this.removeExternalToolHandler?.();
11247
- this.removeExternalToolHandler = null;
11248
- this.removeControlRequestHandler?.();
11249
- this.removeControlRequestHandler = null;
11250
- await this.closeMcpBridge();
11417
+ this.cleanupTransportHandlers();
11251
11418
  client.close();
11252
- await this.cleanupManagedSandbox();
11253
- await this.cleanupSessionRepositories(resolved.runtime.agent_id, resolved.runtime.conversation_id);
11254
11419
  throw error;
11255
11420
  }
11256
11421
  }
11422
+ installTransportHandlers(client) {
11423
+ this.cleanupTransportHandlers();
11424
+ this.removeControlRequestHandler = registerAppServerControlRequestHandler({
11425
+ client,
11426
+ getRuntime: () => this.runtime,
11427
+ getOptions: () => this.currentOptions()
11428
+ });
11429
+ if (this.externalTools.size > 0) {
11430
+ this.removeExternalToolHandler = client.onExternalToolCall(createExternalToolCallHandler(this.externalTools));
11431
+ }
11432
+ }
11433
+ cleanupTransportHandlers() {
11434
+ this.removeExternalToolHandler?.();
11435
+ this.removeExternalToolHandler = null;
11436
+ this.removeControlRequestHandler?.();
11437
+ this.removeControlRequestHandler = null;
11438
+ }
11439
+ async recoverIdleTransport(runtime) {
11440
+ const connection = await this.resolveRecoveryConnection(runtime);
11441
+ this.connectionId = connection.connectionId;
11442
+ return this.openCloudTransport(runtime, connection.connectionId);
11443
+ }
11444
+ async resolveRecoveryConnection(runtime) {
11445
+ const sandbox = this.managedSandbox;
11446
+ if (sandbox) {
11447
+ await this.refreshManagedSandbox(sandbox);
11448
+ return this.waitForManagedSandboxConnection(sandbox);
11449
+ }
11450
+ const environment = this.effectiveEnvironment();
11451
+ if (!environment) {
11452
+ throw new Error("Cloud idle transport recovery lost its execution target");
11453
+ }
11454
+ return this.resolveExplicitConnection(environment);
11455
+ }
11456
+ onIdleTransportDisconnect() {
11457
+ this.cleanupTransportHandlers();
11458
+ }
11459
+ onRecoveredTransportDiscarded() {
11460
+ this.cleanupTransportHandlers();
11461
+ }
11257
11462
  async startCloudRuntime(client, runtime) {
11258
11463
  const options = this.currentOptions();
11259
11464
  const command = {
@@ -11271,6 +11476,8 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11271
11476
  command.mode = mode;
11272
11477
  if (options.cwd !== undefined)
11273
11478
  command.cwd = options.cwd;
11479
+ if (this.cloudMode.options.stateless === true)
11480
+ command.stateless = true;
11274
11481
  if (options.skillSources !== undefined) {
11275
11482
  command.skill_sources = [...new Set(options.skillSources)];
11276
11483
  }
@@ -11296,10 +11503,7 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11296
11503
  await this.refreshManagedSandbox(sandbox);
11297
11504
  }
11298
11505
  onCoreClose() {
11299
- this.removeExternalToolHandler?.();
11300
- this.removeExternalToolHandler = null;
11301
- this.removeControlRequestHandler?.();
11302
- this.removeControlRequestHandler = null;
11506
+ this.cleanupTransportHandlers();
11303
11507
  this.mcpCleanup = this.closeMcpBridge();
11304
11508
  this.cleanupManagedSandbox();
11305
11509
  if (this.runtime?.agent_id) {
@@ -11623,8 +11827,8 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
11623
11827
  return new RemoteEnvironmentClient({}, this.apiClient);
11624
11828
  }
11625
11829
  effectiveEnvironment() {
11626
- const modeEnvironment = this.mode.kind === "session" ? this.mode.options.environment : undefined;
11627
- return modeEnvironment ?? this.cloudOptions.environment;
11830
+ const modeComputer = this.mode.kind === "session" ? this.mode.options.computer ?? this.mode.options.environment : undefined;
11831
+ return modeComputer ?? this.cloudOptions.computer ?? this.cloudOptions.environment;
11628
11832
  }
11629
11833
  effectiveSandboxOptions() {
11630
11834
  const modeSandbox = this.mode.kind === "session" ? this.mode.options.sandbox : undefined;
@@ -11878,6 +12082,20 @@ function validateMcpServers(servers) {
11878
12082
  }
11879
12083
  }
11880
12084
  function validateCreateSessionOptions(options) {
12085
+ if (options.stateless !== undefined && typeof options.stateless !== "boolean") {
12086
+ throw new Error("Invalid stateless. Expected a boolean.");
12087
+ }
12088
+ if (options.stateless) {
12089
+ const persistedOption = [
12090
+ ["model", options.model],
12091
+ ["reasoningEffort", options.reasoningEffort],
12092
+ ["dreaming", options.dreaming],
12093
+ ["resources", options.resources]
12094
+ ].find(([, value]) => value !== undefined);
12095
+ if (persistedOption) {
12096
+ throw new Error(`stateless sessions cannot set ${persistedOption[0]} because it changes persisted agent configuration.`);
12097
+ }
12098
+ }
11881
12099
  validateClientToolset(options.toolset);
11882
12100
  validateSkillSources(options.skillSources);
11883
12101
  validateMcpServers(options.mcpServers);
@@ -11924,14 +12142,17 @@ var VALID_BACKENDS = new Set([
11924
12142
  function isLettaCodeBackend(value) {
11925
12143
  return VALID_BACKENDS.has(value);
11926
12144
  }
11927
- function getOptionsEnvironment(options) {
11928
- if ("environment" in options) {
11929
- return options.environment;
12145
+ function getOptionsComputer(options) {
12146
+ if (!("computer" in options) && !("environment" in options))
12147
+ return;
12148
+ if (options.computer !== undefined && options.environment !== undefined) {
12149
+ throw new Error('Specify either "computer" or deprecated "environment", not both.');
11930
12150
  }
11931
- return;
12151
+ return options.computer ?? options.environment;
11932
12152
  }
11933
12153
  function stripCloudExecutionOptions(options) {
11934
12154
  const sessionOptions = { ...options };
12155
+ delete sessionOptions.computer;
11935
12156
  delete sessionOptions.environment;
11936
12157
  delete sessionOptions.sandbox;
11937
12158
  delete sessionOptions.filesystemConfinement;
@@ -11949,12 +12170,14 @@ function looksLikeConversationId(id) {
11949
12170
 
11950
12171
  class LettaAgentClientBase {
11951
12172
  backend;
12173
+ computer;
11952
12174
  environment;
11953
12175
  agents;
11954
12176
  conversations;
11955
12177
  models;
11956
12178
  options;
11957
12179
  repositoriesClient = null;
12180
+ computersClient = null;
11958
12181
  agentRepositoriesClient = null;
11959
12182
  cloudClient = null;
11960
12183
  managementTransport = null;
@@ -11964,16 +12187,19 @@ class LettaAgentClientBase {
11964
12187
  throw new Error(`Invalid Letta Code backend '${String(backend)}'. Valid values: local, remote, cloud.`);
11965
12188
  }
11966
12189
  this.backend = backend;
11967
- this.environment = getOptionsEnvironment(options);
12190
+ this.computer = getOptionsComputer(options);
12191
+ this.environment = this.computer;
11968
12192
  this.options = options;
11969
12193
  this.agents = createAgentsClient(() => this.getManagementTransport(), () => this.getAgentRepositoriesClient());
11970
12194
  this.conversations = createConversationsClient(() => this.getManagementTransport());
11971
12195
  this.models = createModelsClient(() => this.getManagementTransport());
11972
- if (this.backend === "local" && this.environment !== undefined) {
11973
- throw new Error('LettaAgentClient environment is only valid with backend: "cloud".');
12196
+ if (this.backend === "local" && this.computer !== undefined) {
12197
+ const field = "computer" in options && options.computer !== undefined ? "computer" : "environment";
12198
+ throw new Error(`LettaAgentClient ${field} is only valid with backend: "cloud".`);
11974
12199
  }
11975
- if (this.backend === "remote" && this.environment !== undefined) {
11976
- throw new Error('LettaAgentClient environment is only valid with backend: "cloud"; remote url selects the app-server runtime.');
12200
+ if (this.backend === "remote" && this.computer !== undefined) {
12201
+ const field = "computer" in options && options.computer !== undefined ? "computer" : "environment";
12202
+ throw new Error(`LettaAgentClient ${field} is only valid with backend: "cloud"; remote url selects the app-server runtime.`);
11977
12203
  }
11978
12204
  if (this.backend !== "cloud" && options.sandbox !== undefined) {
11979
12205
  throw new Error('LettaAgentClient sandbox options are only valid with backend: "cloud".');
@@ -12007,6 +12233,9 @@ class LettaAgentClientBase {
12007
12233
  get repositories() {
12008
12234
  return this.getRepositoriesClient();
12009
12235
  }
12236
+ get computers() {
12237
+ return this.getComputersClient();
12238
+ }
12010
12239
  async createAgent(options = {}) {
12011
12240
  if (hasCreateAgentEnvironment(options)) {
12012
12241
  throw new Error("createAgent() does not accept environment. Set a client default or pass environment to resumeSession()/createSession().");
@@ -12096,13 +12325,17 @@ class LettaAgentClientBase {
12096
12325
  }
12097
12326
  }
12098
12327
  assertSessionBackend(action, options) {
12328
+ if (options.computer !== undefined && options.environment !== undefined) {
12329
+ throw new Error(`${action}() cannot specify both computer and deprecated environment.`);
12330
+ }
12099
12331
  if (options.filesystemConfinement !== undefined && options.filesystemConfinement !== "memory") {
12100
12332
  throw new Error(`Invalid filesystemConfinement '${String(options.filesystemConfinement)}'. Valid value: memory.`);
12101
12333
  }
12102
- const effectiveEnvironment = options.environment ?? this.environment;
12334
+ const effectiveComputer = options.computer ?? options.environment ?? this.computer;
12103
12335
  if (this.backend === "local") {
12104
- if (effectiveEnvironment !== undefined) {
12105
- throw new Error(`${action}() environment overrides are only valid with backend: "cloud".`);
12336
+ if (effectiveComputer !== undefined) {
12337
+ const field = options.computer !== undefined ? "computer" : "environment";
12338
+ throw new Error(`${action}() ${field} overrides are only valid with backend: "cloud".`);
12106
12339
  }
12107
12340
  if (options.sandbox !== undefined) {
12108
12341
  throw new Error(`${action}() sandbox options are only valid with backend: "cloud".`);
@@ -12122,8 +12355,9 @@ class LettaAgentClientBase {
12122
12355
  if (options.filesystemConfinement !== undefined) {
12123
12356
  throw new Error(`${action}() filesystemConfinement requires an SDK-owned local app-server process.`);
12124
12357
  }
12125
- if (options.environment !== undefined) {
12126
- throw new Error(`${action}() environment overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
12358
+ if (options.computer !== undefined || options.environment !== undefined) {
12359
+ const field = options.computer !== undefined ? "computer" : "environment";
12360
+ throw new Error(`${action}() ${field} overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
12127
12361
  }
12128
12362
  if (options.sandbox !== undefined) {
12129
12363
  throw new Error(`${action}() sandbox options are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
@@ -12138,11 +12372,13 @@ class LettaAgentClientBase {
12138
12372
  throw new Error(`${action}() filesystemConfinement is only supported with backend: "local".`);
12139
12373
  }
12140
12374
  const cloudOptions = this.cloudOptions();
12141
- if (cloudOptions.environment !== undefined && options.sandbox !== undefined) {
12142
- throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default environment.`);
12375
+ if (this.computer !== undefined && options.sandbox !== undefined) {
12376
+ const field = cloudOptions.computer !== undefined ? "computer" : "environment";
12377
+ throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default ${field}.`);
12143
12378
  }
12144
- if (cloudOptions.sandbox !== undefined && options.environment !== undefined) {
12145
- throw new Error(`Letta Cloud ${action}() cannot specify an environment when the client has default sandbox options.`);
12379
+ if (cloudOptions.sandbox !== undefined && (options.computer !== undefined || options.environment !== undefined)) {
12380
+ const field = options.computer !== undefined ? "a computer" : "an environment";
12381
+ throw new Error(`Letta Cloud ${action}() cannot specify ${field} when the client has default sandbox options.`);
12146
12382
  }
12147
12383
  assertCloudSessionOptionsSupported(action, options);
12148
12384
  return;
@@ -12173,6 +12409,13 @@ class LettaAgentClientBase {
12173
12409
  appServerSessionOptions() {
12174
12410
  return this.remoteOptions();
12175
12411
  }
12412
+ getComputersClient() {
12413
+ if (this.backend !== "cloud") {
12414
+ throw new Error('client.computers is only available with backend: "cloud".');
12415
+ }
12416
+ this.computersClient ??= new ComputersClientImpl(this.getCloudClient());
12417
+ return this.computersClient;
12418
+ }
12176
12419
  getRepositoriesClient() {
12177
12420
  if (this.backend !== "cloud") {
12178
12421
  throw new Error('client.repositories is only available with backend: "cloud".');
@@ -29431,7 +29674,7 @@ class StreamableHTTPClientTransport {
29431
29674
  }
29432
29675
  var DEFAULT_CLIENT_INFO = {
29433
29676
  name: "letta-code",
29434
- version: "0.30.5"
29677
+ version: "0.30.11"
29435
29678
  };
29436
29679
  async function connectMcpServer(config2, options = {}) {
29437
29680
  let client = new Client(options.clientInfo ?? DEFAULT_CLIENT_INFO);
@@ -29749,6 +29992,440 @@ function extractStreamTextDelta(event) {
29749
29992
  }
29750
29993
  return null;
29751
29994
  }
29995
+ // src/transcript-accumulator.ts
29996
+ var SEP = ":";
29997
+ var MAX_TRACKED_RUNS = 64;
29998
+ var ANONYMOUS_RUN = "";
29999
+ function familyOtidAlias(kind, otid) {
30000
+ return `${kind}${SEP}otid${SEP}${otid}`;
30001
+ }
30002
+ function familyUuidAlias(kind, uuid2) {
30003
+ return `${kind}${SEP}uuid${SEP}${uuid2}`;
30004
+ }
30005
+ function toolRowKey(toolCallId) {
30006
+ return `tool_call${SEP}id${SEP}${toolCallId}`;
30007
+ }
30008
+ function asRecord(value) {
30009
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
30010
+ }
30011
+ function readString(record2, field) {
30012
+ const value = record2[field];
30013
+ return typeof value === "string" && value.length > 0 ? value : undefined;
30014
+ }
30015
+ function readNumber(record2, field) {
30016
+ const value = record2[field];
30017
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
30018
+ }
30019
+ function parseJsonObject(raw) {
30020
+ const trimmed = raw.trim();
30021
+ if (trimmed.length === 0)
30022
+ return;
30023
+ try {
30024
+ return asRecord(JSON.parse(trimmed));
30025
+ } catch {
30026
+ return;
30027
+ }
30028
+ }
30029
+ function isRawArgumentsWrapper(input, rawArguments) {
30030
+ if (!input)
30031
+ return false;
30032
+ const keys = Object.keys(input);
30033
+ if (keys.length !== 1 || keys[0] !== "raw")
30034
+ return false;
30035
+ const wrapped = input.raw;
30036
+ if (typeof wrapped !== "string")
30037
+ return false;
30038
+ return rawArguments === undefined || wrapped === rawArguments;
30039
+ }
30040
+ function toolStatus(row) {
30041
+ if (row.result)
30042
+ return "complete";
30043
+ return row.argumentsComplete ? "ready" : "streaming";
30044
+ }
30045
+ function mergeToolArguments(previous, merge2) {
30046
+ let { rawArguments, toolInput, argumentsComplete } = previous;
30047
+ const fragment = merge2.rawArguments;
30048
+ const wrapped = isRawArgumentsWrapper(merge2.toolInput, fragment);
30049
+ if (fragment !== undefined && fragment.length > 0) {
30050
+ const whole = parseJsonObject(fragment);
30051
+ if (whole) {
30052
+ return { rawArguments: fragment, toolInput: whole, argumentsComplete: true };
30053
+ }
30054
+ if (argumentsComplete) {
30055
+ return previous;
30056
+ }
30057
+ if (merge2.mode === "whole") {
30058
+ return { rawArguments: rawArguments ?? fragment, toolInput, argumentsComplete };
30059
+ }
30060
+ rawArguments = (rawArguments ?? "") + fragment;
30061
+ const parsed = parseJsonObject(rawArguments);
30062
+ if (parsed) {
30063
+ return { rawArguments, toolInput: parsed, argumentsComplete: true };
30064
+ }
30065
+ return { rawArguments, toolInput, argumentsComplete: false };
30066
+ }
30067
+ if (!wrapped && merge2.toolInput) {
30068
+ const keys = Object.keys(merge2.toolInput);
30069
+ if (keys.length > 0) {
30070
+ return { rawArguments, toolInput: merge2.toolInput, argumentsComplete: true };
30071
+ }
30072
+ if (rawArguments === undefined) {
30073
+ return { rawArguments, toolInput, argumentsComplete: true };
30074
+ }
30075
+ }
30076
+ return previous;
30077
+ }
30078
+
30079
+ class TranscriptAccumulatorImpl {
30080
+ byKey = new Map;
30081
+ aliasByOtid = new Map;
30082
+ aliasByUuid = new Map;
30083
+ seqThresholds = new Map;
30084
+ snapshot = null;
30085
+ anonymousCounter = 0;
30086
+ apply(message) {
30087
+ switch (message.type) {
30088
+ case "assistant":
30089
+ case "reasoning":
30090
+ this.applyText({
30091
+ kind: message.type,
30092
+ text: message.content,
30093
+ uuid: message.uuid,
30094
+ otid: typeof message.otid === "string" ? message.otid : undefined,
30095
+ runId: message.runId,
30096
+ seqId: message.seqId
30097
+ });
30098
+ break;
30099
+ case "tool_call":
30100
+ this.mergeToolCall({
30101
+ toolCallId: message.toolCallId,
30102
+ toolName: message.toolName,
30103
+ toolInput: message.toolInput,
30104
+ rawArguments: message.rawArguments,
30105
+ uuid: message.uuid,
30106
+ runId: message.runId,
30107
+ mode: "fragment"
30108
+ });
30109
+ break;
30110
+ case "tool_result":
30111
+ this.mergeToolResult({
30112
+ toolCallId: message.toolCallId,
30113
+ content: message.content,
30114
+ isError: message.isError,
30115
+ uuid: message.uuid,
30116
+ runId: message.runId
30117
+ });
30118
+ break;
30119
+ case "stream_event":
30120
+ this.applyStreamEvent(message);
30121
+ break;
30122
+ default:
30123
+ break;
30124
+ }
30125
+ return this.rows();
30126
+ }
30127
+ rebase(page, options) {
30128
+ const messages = normalizeHistoryPage(page, options?.order);
30129
+ if (messages.length === 0)
30130
+ return this.rows();
30131
+ const historyKeys = [];
30132
+ const seen = new Set;
30133
+ for (const message of messages) {
30134
+ const key = this.applyHistoryMessage(message);
30135
+ if (!key || seen.has(key))
30136
+ continue;
30137
+ seen.add(key);
30138
+ historyKeys.push(key);
30139
+ }
30140
+ this.reorder(historyKeys);
30141
+ return this.rows();
30142
+ }
30143
+ rows() {
30144
+ if (!this.snapshot) {
30145
+ this.snapshot = Array.from(this.byKey.values());
30146
+ }
30147
+ return this.snapshot;
30148
+ }
30149
+ reset() {
30150
+ this.byKey.clear();
30151
+ this.aliasByOtid.clear();
30152
+ this.aliasByUuid.clear();
30153
+ this.seqThresholds.clear();
30154
+ this.anonymousCounter = 0;
30155
+ this.snapshot = null;
30156
+ }
30157
+ isReplay(runId, seqId) {
30158
+ if (seqId === undefined)
30159
+ return false;
30160
+ const bucket = runId ?? ANONYMOUS_RUN;
30161
+ const threshold = this.seqThresholds.get(bucket);
30162
+ if (threshold !== undefined && seqId <= threshold)
30163
+ return true;
30164
+ this.rememberSeq(bucket, seqId);
30165
+ return false;
30166
+ }
30167
+ rememberSeq(bucket, seqId) {
30168
+ const threshold = this.seqThresholds.get(bucket);
30169
+ if (threshold !== undefined && threshold >= seqId)
30170
+ return;
30171
+ this.seqThresholds.set(bucket, seqId);
30172
+ while (this.seqThresholds.size > MAX_TRACKED_RUNS) {
30173
+ const oldest = this.seqThresholds.keys().next();
30174
+ if (oldest.done)
30175
+ break;
30176
+ this.seqThresholds.delete(oldest.value);
30177
+ }
30178
+ }
30179
+ applyText(slice) {
30180
+ if (this.isReplay(slice.runId, slice.seqId))
30181
+ return;
30182
+ this.writeText(slice, "append");
30183
+ }
30184
+ writeText(slice, write) {
30185
+ const key = this.resolveTextKey(slice.kind, slice.uuid, slice.otid);
30186
+ const existing = this.byKey.get(key);
30187
+ const previous = existing && existing.kind === slice.kind ? existing : undefined;
30188
+ this.setRow(key, {
30189
+ kind: slice.kind,
30190
+ key,
30191
+ text: write === "append" ? (previous?.text ?? "") + slice.text : slice.text,
30192
+ uuid: previous?.uuid ?? slice.uuid,
30193
+ otid: slice.otid ?? previous?.otid,
30194
+ runId: slice.runId ?? previous?.runId,
30195
+ seqId: maxDefined(slice.seqId, previous?.seqId)
30196
+ });
30197
+ return key;
30198
+ }
30199
+ resolveTextKey(kind, uuid2, otid) {
30200
+ const otidAlias = otid ? familyOtidAlias(kind, otid) : undefined;
30201
+ const uuidAlias = uuid2 ? familyUuidAlias(kind, uuid2) : undefined;
30202
+ const fromOtid = otidAlias ? this.aliasByOtid.get(otidAlias) : undefined;
30203
+ const fromUuid = uuidAlias ? this.aliasByUuid.get(uuidAlias) : undefined;
30204
+ let key = fromOtid ?? fromUuid;
30205
+ if (key && !fromOtid && otid) {
30206
+ const existing = this.byKey.get(key);
30207
+ if (existing && existing.otid !== undefined && existing.otid !== otid) {
30208
+ key = undefined;
30209
+ }
30210
+ }
30211
+ if (!key) {
30212
+ key = otidAlias ?? uuidAlias ?? `${kind}${SEP}auto${SEP}${++this.anonymousCounter}`;
30213
+ }
30214
+ if (otidAlias)
30215
+ this.aliasByOtid.set(otidAlias, key);
30216
+ if (uuidAlias)
30217
+ this.aliasByUuid.set(uuidAlias, key);
30218
+ return key;
30219
+ }
30220
+ mergeToolCall(merge2) {
30221
+ const key = toolRowKey(merge2.toolCallId);
30222
+ const existing = this.byKey.get(key);
30223
+ const previous = existing && existing.kind === "tool_call" ? existing : undefined;
30224
+ const args = mergeToolArguments({
30225
+ rawArguments: previous?.rawArguments,
30226
+ toolInput: previous?.toolInput ?? {},
30227
+ argumentsComplete: previous?.argumentsComplete ?? false
30228
+ }, merge2);
30229
+ const toolName = merge2.toolName && merge2.toolName !== "?" ? merge2.toolName : previous?.toolName ?? merge2.toolName ?? "?";
30230
+ const next = {
30231
+ kind: "tool_call",
30232
+ key,
30233
+ toolCallId: merge2.toolCallId,
30234
+ toolName,
30235
+ toolInput: args.toolInput,
30236
+ ...args.rawArguments !== undefined ? { rawArguments: args.rawArguments } : {},
30237
+ argumentsComplete: args.argumentsComplete,
30238
+ ...previous?.result ? { result: previous.result } : {},
30239
+ status: "streaming",
30240
+ uuid: previous?.uuid ?? merge2.uuid,
30241
+ runId: merge2.runId ?? previous?.runId,
30242
+ seqId: maxDefined(merge2.seqId, previous?.seqId)
30243
+ };
30244
+ next.status = toolStatus(next);
30245
+ this.setRow(key, next);
30246
+ return key;
30247
+ }
30248
+ mergeToolResult(merge2) {
30249
+ const key = toolRowKey(merge2.toolCallId);
30250
+ const existing = this.byKey.get(key);
30251
+ const previous = existing && existing.kind === "tool_call" ? existing : undefined;
30252
+ this.setRow(key, {
30253
+ kind: "tool_call",
30254
+ key,
30255
+ toolCallId: merge2.toolCallId,
30256
+ toolName: previous?.toolName ?? "?",
30257
+ toolInput: previous?.toolInput ?? {},
30258
+ ...previous?.rawArguments !== undefined ? { rawArguments: previous.rawArguments } : {},
30259
+ argumentsComplete: previous?.argumentsComplete ?? false,
30260
+ result: {
30261
+ content: merge2.content,
30262
+ isError: merge2.isError,
30263
+ ...merge2.uuid ? { uuid: merge2.uuid } : {}
30264
+ },
30265
+ status: "complete",
30266
+ uuid: previous?.uuid,
30267
+ runId: merge2.runId ?? previous?.runId,
30268
+ seqId: maxDefined(merge2.seqId, previous?.seqId)
30269
+ });
30270
+ return key;
30271
+ }
30272
+ applyStreamEvent(message) {
30273
+ const delta = extractStreamTextDelta(message.event);
30274
+ if (!delta)
30275
+ return;
30276
+ const payload = asRecord(message.event);
30277
+ const otid = payload ? readString(payload, "otid") : undefined;
30278
+ const payloadId = payload ? readString(payload, "id") : undefined;
30279
+ const identified = otid !== undefined || payloadId !== undefined;
30280
+ this.applyText({
30281
+ kind: delta.kind,
30282
+ text: delta.text,
30283
+ uuid: identified ? payloadId : `${delta.kind}${SEP}live`,
30284
+ otid,
30285
+ runId: payload ? readString(payload, "run_id") : undefined,
30286
+ seqId: payload ? readNumber(payload, "seq_id") : undefined
30287
+ });
30288
+ }
30289
+ applyHistoryMessage(message) {
30290
+ const record2 = asRecord(message);
30291
+ if (!record2)
30292
+ return;
30293
+ const messageType = readString(record2, "message_type");
30294
+ if (!messageType)
30295
+ return;
30296
+ const uuid2 = readString(record2, "id");
30297
+ const otid = readString(record2, "otid");
30298
+ const runId = readString(record2, "run_id");
30299
+ const seqId = readNumber(record2, "seq_id");
30300
+ if (seqId !== undefined) {
30301
+ this.rememberSeq(runId ?? ANONYMOUS_RUN, seqId);
30302
+ }
30303
+ switch (messageType) {
30304
+ case "user_message":
30305
+ case "assistant_message": {
30306
+ const text = extractTextFromContent(record2.content);
30307
+ if (text === null)
30308
+ return;
30309
+ return this.writeText({
30310
+ kind: messageType === "user_message" ? "user" : "assistant",
30311
+ text,
30312
+ uuid: uuid2,
30313
+ otid,
30314
+ runId,
30315
+ seqId
30316
+ }, "replace");
30317
+ }
30318
+ case "reasoning_message": {
30319
+ const text = typeof record2.reasoning === "string" ? record2.reasoning : extractTextFromContent(record2.content);
30320
+ if (text === null || text === undefined)
30321
+ return;
30322
+ return this.writeText({ kind: "reasoning", text, uuid: uuid2, otid, runId, seqId }, "replace");
30323
+ }
30324
+ case "tool_call_message":
30325
+ case "approval_request_message": {
30326
+ const toolCall = firstToolCall(record2);
30327
+ if (!toolCall)
30328
+ return;
30329
+ const fn = asRecord(toolCall.function);
30330
+ const toolCallId = (typeof toolCall.tool_call_id === "string" ? toolCall.tool_call_id : undefined) ?? (typeof toolCall.id === "string" ? toolCall.id : undefined);
30331
+ if (!toolCallId)
30332
+ return;
30333
+ const args = toolCall.arguments ?? fn?.arguments;
30334
+ return this.mergeToolCall({
30335
+ toolCallId,
30336
+ toolName: (typeof toolCall.name === "string" ? toolCall.name : undefined) ?? (typeof fn?.name === "string" ? fn.name : undefined),
30337
+ toolInput: asRecord(args),
30338
+ rawArguments: typeof args === "string" ? args : undefined,
30339
+ uuid: uuid2,
30340
+ runId,
30341
+ seqId,
30342
+ mode: "whole"
30343
+ });
30344
+ }
30345
+ case "tool_return_message": {
30346
+ const toolReturn = firstToolReturn(record2) ?? record2;
30347
+ const toolCallId = readString(record2, "tool_call_id") ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
30348
+ if (!toolCallId)
30349
+ return;
30350
+ const content = extractTextFromContent(record2.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
30351
+ const status = readString(record2, "status") ?? (typeof toolReturn.status === "string" ? toolReturn.status : undefined);
30352
+ return this.mergeToolResult({
30353
+ toolCallId,
30354
+ content,
30355
+ isError: status === "error",
30356
+ uuid: uuid2,
30357
+ runId,
30358
+ seqId
30359
+ });
30360
+ }
30361
+ default:
30362
+ return;
30363
+ }
30364
+ }
30365
+ setRow(key, row) {
30366
+ this.byKey.set(key, row);
30367
+ this.snapshot = null;
30368
+ }
30369
+ reorder(historyKeys) {
30370
+ if (historyKeys.length === 0)
30371
+ return;
30372
+ const historySet = new Set(historyKeys);
30373
+ const reordered = new Map;
30374
+ for (const key of historyKeys) {
30375
+ const row = this.byKey.get(key);
30376
+ if (row)
30377
+ reordered.set(key, row);
30378
+ }
30379
+ for (const [key, row] of this.byKey) {
30380
+ if (historySet.has(key))
30381
+ continue;
30382
+ reordered.set(key, row);
30383
+ }
30384
+ this.byKey = reordered;
30385
+ this.snapshot = null;
30386
+ }
30387
+ }
30388
+ function maxDefined(next, previous) {
30389
+ if (next === undefined)
30390
+ return previous;
30391
+ if (previous === undefined)
30392
+ return next;
30393
+ return Math.max(next, previous);
30394
+ }
30395
+ function normalizeHistoryPage(page, order) {
30396
+ const messages = Array.isArray(page) ? page : page.messages ?? [];
30397
+ if (messages.length < 2)
30398
+ return messages;
30399
+ const descending = order ? order === "desc" : detectDescending(messages);
30400
+ return descending ? [...messages].reverse() : messages;
30401
+ }
30402
+ function detectDescending(messages) {
30403
+ const seqIds = [];
30404
+ const dates = [];
30405
+ for (const message of messages) {
30406
+ const record2 = asRecord(message);
30407
+ if (!record2)
30408
+ continue;
30409
+ const seqId = readNumber(record2, "seq_id");
30410
+ if (seqId !== undefined)
30411
+ seqIds.push(seqId);
30412
+ const date4 = readString(record2, "date");
30413
+ if (!date4)
30414
+ continue;
30415
+ const parsed = Date.parse(date4);
30416
+ if (!Number.isNaN(parsed))
30417
+ dates.push(parsed);
30418
+ }
30419
+ const ordered = seqIds.length >= 2 ? seqIds : dates;
30420
+ if (ordered.length < 2)
30421
+ return false;
30422
+ const first = ordered[0];
30423
+ const last = ordered[ordered.length - 1];
30424
+ return last < first;
30425
+ }
30426
+ function createTranscriptAccumulator() {
30427
+ return new TranscriptAccumulatorImpl;
30428
+ }
29752
30429
  // src/tool-helpers.ts
29753
30430
  function jsonResult(payload) {
29754
30431
  return {
@@ -29921,6 +30598,7 @@ export {
29921
30598
  imageFromFile,
29922
30599
  imageFromBase64,
29923
30600
  extractStreamTextDelta,
30601
+ createTranscriptAccumulator,
29924
30602
  createSession,
29925
30603
  createReactNativeWebSocketConstructor,
29926
30604
  createAgent,
@@ -29929,4 +30607,4 @@ export {
29929
30607
  CloudManagedSandboxExpiredError
29930
30608
  };
29931
30609
 
29932
- //# debugId=9E741E442389C4A864756E2164756E21
30610
+ //# debugId=45035C35D5C51AFA64756E2164756E21