@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.
- package/AGENTS.md +42 -0
- package/README.md +101 -1
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-base.d.ts +7 -0
- package/dist/client-base.d.ts.map +1 -1
- package/dist/client-entry.d.ts +3 -0
- package/dist/client-entry.d.ts.map +1 -1
- package/dist/client-entry.js +816 -138
- package/dist/client-entry.js.map +16 -14
- package/dist/cloud-session.d.ts +8 -1
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/computers.d.ts +67 -0
- package/dist/computers.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +817 -139
- package/dist/index.js.map +17 -15
- package/dist/remote-client-session-core.d.ts +13 -16
- package/dist/remote-client-session-core.d.ts.map +1 -1
- package/dist/remote-session-protocol.d.ts +11 -0
- package/dist/remote-session-protocol.d.ts.map +1 -1
- package/dist/remote-turn-coordinator.d.ts +6 -1
- package/dist/remote-turn-coordinator.d.ts.map +1 -1
- package/dist/remote.d.ts +6 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/transcript-accumulator.d.ts +133 -0
- package/dist/transcript-accumulator.d.ts.map +1 -0
- package/dist/types.d.ts +41 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/app-server-session.ts +9 -0
- package/src/client-base.ts +66 -19
- package/src/client-entry.ts +23 -0
- package/src/cloud-session.ts +112 -45
- package/src/computers.ts +132 -0
- package/src/index.ts +24 -0
- package/src/remote-client-session-core.ts +103 -27
- package/src/remote-session-protocol.ts +24 -0
- package/src/remote-turn-coordinator.ts +11 -2
- package/src/remote.ts +41 -12
- package/src/transcript-accumulator.ts +823 -0
- package/src/types.ts +43 -18
- package/src/validation.ts +16 -0
package/dist/client-entry.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
11048
|
-
throw new Error("Letta Cloud
|
|
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
|
|
11169
|
-
agentId:
|
|
11170
|
-
conversationId:
|
|
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
|
|
11366
|
+
runtime
|
|
11182
11367
|
}),
|
|
11183
11368
|
requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
|
|
11184
11369
|
}));
|
|
11185
11370
|
const options = this.currentOptions();
|
|
11186
|
-
this.
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
|
11606
|
-
return
|
|
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
|
|
11907
|
-
if ("environment" in options)
|
|
11908
|
-
return
|
|
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.
|
|
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.
|
|
11952
|
-
|
|
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.
|
|
11955
|
-
|
|
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
|
|
12313
|
+
const effectiveComputer = options.computer ?? options.environment ?? this.computer;
|
|
12082
12314
|
if (this.backend === "local") {
|
|
12083
|
-
if (
|
|
12084
|
-
|
|
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
|
-
|
|
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 (
|
|
12121
|
-
|
|
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
|
-
|
|
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=
|
|
12968
|
+
//# debugId=EF77573F8BD0F2C464756E2164756E21
|