@letta-ai/letta-agent-sdk 0.3.2 → 0.3.3
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 +47 -0
- package/README.md +17 -0
- package/dist/client-entry.js +800 -732
- package/dist/client-entry.js.map +8 -5
- package/dist/cloud-sandbox.d.ts +33 -0
- package/dist/cloud-sandbox.d.ts.map +1 -0
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +799 -732
- package/dist/index.js.map +9 -6
- package/dist/remote-client-session-core.d.ts +6 -93
- package/dist/remote-client-session-core.d.ts.map +1 -1
- package/dist/remote-session-protocol.d.ts +130 -0
- package/dist/remote-session-protocol.d.ts.map +1 -0
- package/dist/remote-turn-coordinator.d.ts +49 -0
- package/dist/remote-turn-coordinator.d.ts.map +1 -0
- package/dist/types.d.ts +2 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/app-server-management.ts +641 -0
- package/src/app-server-session.ts +948 -0
- package/src/cli-resolver.ts +46 -0
- package/src/client-base.ts +482 -0
- package/src/client-entry.ts +31 -0
- package/src/client.ts +138 -0
- package/src/cloud-management.ts +360 -0
- package/src/cloud-sandbox.ts +117 -0
- package/src/cloud-session.ts +1313 -0
- package/src/index.ts +440 -0
- package/src/interactiveToolPolicy.ts +62 -0
- package/src/local-app-server-session.ts +39 -0
- package/src/local-app-server.ts +137 -0
- package/src/management-types.ts +133 -0
- package/src/management.ts +206 -0
- package/src/protocol.ts +249 -0
- package/src/remote-client-session-core.ts +786 -0
- package/src/remote-session-protocol.ts +660 -0
- package/src/remote-turn-coordinator.ts +505 -0
- package/src/remote.ts +177 -0
- package/src/repositories.ts +340 -0
- package/src/request-ids.ts +33 -0
- package/src/session.ts +1638 -0
- package/src/stream-events.ts +88 -0
- package/src/tool-helpers.ts +147 -0
- package/src/transport.ts +484 -0
- package/src/types.ts +1328 -0
- package/src/validation.ts +223 -0
- package/src/websocket.ts +22 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the Letta Code CLI entrypoint used by SDK-owned transports.
|
|
12
|
+
*
|
|
13
|
+
* Resolution order intentionally matches the historical subprocess transport:
|
|
14
|
+
* explicit LETTA_CLI_PATH first, then the installed @letta-ai/letta-code package,
|
|
15
|
+
* then local development fallbacks.
|
|
16
|
+
*/
|
|
17
|
+
export function findLettaCli(): string {
|
|
18
|
+
const envPath = process.env.LETTA_CLI_PATH;
|
|
19
|
+
if (envPath && existsSync(envPath)) {
|
|
20
|
+
return envPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
return require.resolve("@letta-ai/letta-code");
|
|
25
|
+
} catch {
|
|
26
|
+
// Fall through to local development paths.
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const localPaths = [
|
|
30
|
+
join(__dirname, "..", "node_modules", "@letta-ai", "letta-code", "letta.js"),
|
|
31
|
+
join(__dirname, "../../@letta-ai/letta-code/letta.js"),
|
|
32
|
+
join(__dirname, "../../../letta-code-prod/letta.js"),
|
|
33
|
+
join(__dirname, "../../../letta-code/letta.js"),
|
|
34
|
+
join(__dirname, "..", "..", "letta-code", "letta.js"),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
for (const path of localPaths) {
|
|
38
|
+
if (existsSync(path)) {
|
|
39
|
+
return path;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new Error(
|
|
44
|
+
"Could not find Letta Code CLI. Set LETTA_CLI_PATH environment variable or install @letta-ai/letta-code.",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { RepositoriesClient } from "./repositories.js";
|
|
2
|
+
import { AppServerManagementTransport } from "./app-server-management.js";
|
|
3
|
+
import {
|
|
4
|
+
AppServerSession,
|
|
5
|
+
assertRemoteSessionOptionsSupported,
|
|
6
|
+
type AppServerSessionOptions,
|
|
7
|
+
} from "./app-server-session.js";
|
|
8
|
+
import { CloudManagementTransport } from "./cloud-management.js";
|
|
9
|
+
import {
|
|
10
|
+
CloudEnvironmentSession,
|
|
11
|
+
assertCloudSessionOptionsSupported,
|
|
12
|
+
createCloudAgent,
|
|
13
|
+
validateCloudClientOptions,
|
|
14
|
+
} from "./cloud-session.js";
|
|
15
|
+
import {
|
|
16
|
+
createAgentsClient,
|
|
17
|
+
createConversationsClient,
|
|
18
|
+
createModelsClient,
|
|
19
|
+
type ManagementTransport,
|
|
20
|
+
} from "./management.js";
|
|
21
|
+
import type {
|
|
22
|
+
AgentsClient,
|
|
23
|
+
ConversationsClient,
|
|
24
|
+
ModelsClient,
|
|
25
|
+
} from "./management-types.js";
|
|
26
|
+
import type {
|
|
27
|
+
CreateAgentOptions,
|
|
28
|
+
CreateSessionOptions,
|
|
29
|
+
LettaCodeBackend,
|
|
30
|
+
LettaCodeClientOptions,
|
|
31
|
+
LettaCodeClientSessionOptions,
|
|
32
|
+
LettaCodeEnvironment,
|
|
33
|
+
LettaCodeRemoteClientOptions,
|
|
34
|
+
LettaCodeLocalClientOptions,
|
|
35
|
+
LettaCodeCloudClientOptions,
|
|
36
|
+
LettaCodeSession,
|
|
37
|
+
SDKResultMessage,
|
|
38
|
+
SendMessage,
|
|
39
|
+
} from "./types.js";
|
|
40
|
+
import {
|
|
41
|
+
validateCreateAgentOptions,
|
|
42
|
+
validateCreateSessionOptions,
|
|
43
|
+
} from "./validation.js";
|
|
44
|
+
|
|
45
|
+
const VALID_BACKENDS = new Set<LettaCodeBackend>([
|
|
46
|
+
"local",
|
|
47
|
+
"remote",
|
|
48
|
+
"cloud",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
function isLettaCodeBackend(value: string): value is LettaCodeBackend {
|
|
52
|
+
return VALID_BACKENDS.has(value as LettaCodeBackend);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getOptionsEnvironment(
|
|
56
|
+
options: LettaCodeClientOptions,
|
|
57
|
+
): LettaCodeEnvironment | undefined {
|
|
58
|
+
if ("environment" in options) {
|
|
59
|
+
return options.environment;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function stripCloudExecutionOptions(
|
|
65
|
+
options: LettaCodeClientSessionOptions,
|
|
66
|
+
): CreateSessionOptions {
|
|
67
|
+
const sessionOptions = { ...options };
|
|
68
|
+
delete sessionOptions.environment;
|
|
69
|
+
delete sessionOptions.sandbox;
|
|
70
|
+
return sessionOptions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasRepositoryResources(options: LettaCodeClientSessionOptions): boolean {
|
|
74
|
+
return options.resources !== undefined && options.resources.length > 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function hasCreateAgentEnvironment(options: CreateAgentOptions): boolean {
|
|
78
|
+
return "environment" in (options as Record<string, unknown>);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function looksLikeConversationId(id: string): boolean {
|
|
82
|
+
return id.startsWith("conv-") || id.startsWith("local-conv-");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type TurnSession = LettaCodeSession & {
|
|
86
|
+
runTurn(message: SendMessage): Promise<SDKResultMessage>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Top-level Letta Agent SDK client.
|
|
91
|
+
*
|
|
92
|
+
* `backend` selects how the SDK reaches or runs the Letta Code harness.
|
|
93
|
+
* `local` spawns an SDK-owned Letta Code app-server and speaks the websocket
|
|
94
|
+
* protocol by default, with an explicit stdio fallback for legacy flows.
|
|
95
|
+
* `remote` connects to a user-managed Letta Code app-server websocket endpoint.
|
|
96
|
+
* `cloud` uses agents hosted on Letta Cloud, with an explicit remote
|
|
97
|
+
* environment or SDK-managed sandbox.
|
|
98
|
+
*/
|
|
99
|
+
export class LettaAgentClientBase {
|
|
100
|
+
readonly backend: LettaCodeBackend;
|
|
101
|
+
readonly environment: LettaCodeEnvironment | undefined;
|
|
102
|
+
readonly agents: AgentsClient;
|
|
103
|
+
readonly conversations: ConversationsClient;
|
|
104
|
+
readonly models: ModelsClient;
|
|
105
|
+
protected readonly options: LettaCodeClientOptions;
|
|
106
|
+
private repositoriesClient: RepositoriesClient | null = null;
|
|
107
|
+
private managementTransport: ManagementTransport | null = null;
|
|
108
|
+
|
|
109
|
+
constructor(options: LettaCodeClientOptions = {}) {
|
|
110
|
+
const backend = options.backend ?? "local";
|
|
111
|
+
if (!isLettaCodeBackend(backend)) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Invalid Letta Code backend '${String(backend)}'. Valid values: local, remote, cloud.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.backend = backend;
|
|
118
|
+
this.environment = getOptionsEnvironment(options);
|
|
119
|
+
this.options = options;
|
|
120
|
+
this.agents = createAgentsClient(() => this.getManagementTransport());
|
|
121
|
+
this.conversations = createConversationsClient(
|
|
122
|
+
() => this.getManagementTransport(),
|
|
123
|
+
);
|
|
124
|
+
this.models = createModelsClient(() => this.getManagementTransport());
|
|
125
|
+
|
|
126
|
+
if (this.backend === "local" && this.environment !== undefined) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
'LettaAgentClient environment is only valid with backend: "cloud".',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (this.backend === "remote" && this.environment !== undefined) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
'LettaAgentClient environment is only valid with backend: "cloud"; remote url selects the app-server runtime.',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (this.backend !== "cloud" && (options as { sandbox?: unknown }).sandbox !== undefined) {
|
|
137
|
+
throw new Error('LettaAgentClient sandbox options are only valid with backend: "cloud".');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.backend === "local") {
|
|
141
|
+
const localOptions = options as LettaCodeLocalClientOptions;
|
|
142
|
+
if (
|
|
143
|
+
localOptions.transport !== undefined &&
|
|
144
|
+
localOptions.transport !== "app-server" &&
|
|
145
|
+
localOptions.transport !== "stdio"
|
|
146
|
+
) {
|
|
147
|
+
throw new Error("Invalid local transport. Valid values: app-server, stdio.");
|
|
148
|
+
}
|
|
149
|
+
const requestTimeoutMs = localOptions.appServer?.requestTimeoutMs;
|
|
150
|
+
if (
|
|
151
|
+
requestTimeoutMs !== undefined &&
|
|
152
|
+
(!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)
|
|
153
|
+
) {
|
|
154
|
+
throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
|
|
155
|
+
}
|
|
156
|
+
const idleLingerMs = localOptions.appServer?.idleLingerMs;
|
|
157
|
+
if (
|
|
158
|
+
idleLingerMs !== undefined &&
|
|
159
|
+
(!Number.isInteger(idleLingerMs) || idleLingerMs < 0)
|
|
160
|
+
) {
|
|
161
|
+
throw new Error("Invalid appServer.idleLingerMs. Expected a non-negative integer.");
|
|
162
|
+
}
|
|
163
|
+
const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
|
|
164
|
+
if (
|
|
165
|
+
startupTimeoutMs !== undefined &&
|
|
166
|
+
(!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)
|
|
167
|
+
) {
|
|
168
|
+
throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (this.backend === "remote") {
|
|
173
|
+
if (!("url" in options) || typeof options.url !== "string" || options.url.length === 0) {
|
|
174
|
+
throw new Error("LettaAgentClient remote backend requires a non-empty url.");
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
options.requestTimeoutMs !== undefined &&
|
|
178
|
+
(!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)
|
|
179
|
+
) {
|
|
180
|
+
throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
options.idleLingerMs !== undefined &&
|
|
184
|
+
(!Number.isInteger(options.idleLingerMs) || options.idleLingerMs < 0)
|
|
185
|
+
) {
|
|
186
|
+
throw new Error("Invalid idleLingerMs. Expected a non-negative integer.");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.backend === "cloud") {
|
|
191
|
+
validateCloudClientOptions(options as LettaCodeCloudClientOptions);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create a new Letta Code agent with a default conversation.
|
|
197
|
+
*
|
|
198
|
+
* Environment/device selection is intentionally not part of the agent payload;
|
|
199
|
+
* it belongs to the client/session execution context.
|
|
200
|
+
*/
|
|
201
|
+
get repositories(): RepositoriesClient {
|
|
202
|
+
return this.getRepositoriesClient();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async createAgent(options: CreateAgentOptions = {}): Promise<string> {
|
|
206
|
+
if (hasCreateAgentEnvironment(options)) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"createAgent() does not accept environment. Set a client default or pass environment to resumeSession()/createSession().",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
validateCreateAgentOptions(options);
|
|
213
|
+
|
|
214
|
+
if (this.backend === "remote") {
|
|
215
|
+
const session = new AppServerSession(this.appServerSessionOptions(), {
|
|
216
|
+
kind: "create-agent",
|
|
217
|
+
options,
|
|
218
|
+
});
|
|
219
|
+
const initMsg = await session.initialize();
|
|
220
|
+
session.close();
|
|
221
|
+
return initMsg.agentId;
|
|
222
|
+
}
|
|
223
|
+
if (this.backend === "cloud") {
|
|
224
|
+
return createCloudAgent(this.cloudOptions(), options);
|
|
225
|
+
}
|
|
226
|
+
return this.createLocalAgent(options);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create a new conversation/session.
|
|
231
|
+
*
|
|
232
|
+
* Without an agent id, this uses the default/LRU agent, matching the legacy
|
|
233
|
+
* top-level createSession() helper.
|
|
234
|
+
*/
|
|
235
|
+
createSession(
|
|
236
|
+
agentIdOrOptions?: string | LettaCodeClientSessionOptions,
|
|
237
|
+
options: LettaCodeClientSessionOptions = {},
|
|
238
|
+
): LettaCodeSession {
|
|
239
|
+
const agentId =
|
|
240
|
+
typeof agentIdOrOptions === "string" ? agentIdOrOptions : undefined;
|
|
241
|
+
const resolvedOptions =
|
|
242
|
+
typeof agentIdOrOptions === "string" ? options : (agentIdOrOptions ?? {});
|
|
243
|
+
|
|
244
|
+
this.assertSessionBackend("createSession", resolvedOptions);
|
|
245
|
+
const sessionOptions = stripCloudExecutionOptions(resolvedOptions);
|
|
246
|
+
validateCreateSessionOptions(sessionOptions);
|
|
247
|
+
|
|
248
|
+
if (this.backend === "remote") {
|
|
249
|
+
if (!agentId) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
"App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
255
|
+
kind: "session",
|
|
256
|
+
agentId,
|
|
257
|
+
newConversation: true,
|
|
258
|
+
options: resolvedOptions,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (this.backend === "cloud") {
|
|
262
|
+
if (!agentId) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
"Letta Cloud createSession() requires an agent id. Call createAgent() first or pass an agent id.",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return new CloudEnvironmentSession(this.cloudOptions(), {
|
|
268
|
+
kind: "session",
|
|
269
|
+
agentId,
|
|
270
|
+
newConversation: true,
|
|
271
|
+
options: resolvedOptions,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return this.createLocalSession(agentId, resolvedOptions, sessionOptions);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Resume an existing agent default conversation or a specific conversation.
|
|
279
|
+
*
|
|
280
|
+
* `options.environment` overrides the client's default execution target for
|
|
281
|
+
* Letta Cloud sessions. Remote app-server URLs already select the runtime.
|
|
282
|
+
*/
|
|
283
|
+
resumeSession(
|
|
284
|
+
id: string,
|
|
285
|
+
options: LettaCodeClientSessionOptions = {},
|
|
286
|
+
): LettaCodeSession {
|
|
287
|
+
this.assertSessionBackend("resumeSession", options);
|
|
288
|
+
const sessionOptions = stripCloudExecutionOptions(options);
|
|
289
|
+
validateCreateSessionOptions(sessionOptions);
|
|
290
|
+
|
|
291
|
+
if (this.backend === "remote") {
|
|
292
|
+
if (looksLikeConversationId(id)) {
|
|
293
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
294
|
+
kind: "session",
|
|
295
|
+
conversationId: id,
|
|
296
|
+
options,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
300
|
+
kind: "session",
|
|
301
|
+
agentId: id,
|
|
302
|
+
defaultConversation: true,
|
|
303
|
+
options,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
if (this.backend === "cloud") {
|
|
307
|
+
if (looksLikeConversationId(id)) {
|
|
308
|
+
return new CloudEnvironmentSession(this.cloudOptions(), {
|
|
309
|
+
kind: "session",
|
|
310
|
+
conversationId: id,
|
|
311
|
+
options,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return new CloudEnvironmentSession(this.cloudOptions(), {
|
|
315
|
+
kind: "session",
|
|
316
|
+
agentId: id,
|
|
317
|
+
defaultConversation: true,
|
|
318
|
+
options,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return this.resumeLocalSession(id, options, sessionOptions);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** One-shot prompt convenience helper using a new conversation. */
|
|
325
|
+
async prompt(
|
|
326
|
+
message: SendMessage,
|
|
327
|
+
agentId?: string,
|
|
328
|
+
options: LettaCodeClientSessionOptions = {},
|
|
329
|
+
): Promise<SDKResultMessage> {
|
|
330
|
+
const session = agentId
|
|
331
|
+
? this.createSession(agentId, options)
|
|
332
|
+
: this.createSession(options);
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
return await (session as TurnSession).runTurn(message);
|
|
336
|
+
} finally {
|
|
337
|
+
session.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private assertSessionBackend(
|
|
342
|
+
action: string,
|
|
343
|
+
options: LettaCodeClientSessionOptions,
|
|
344
|
+
): void {
|
|
345
|
+
const effectiveEnvironment = options.environment ?? this.environment;
|
|
346
|
+
if (this.backend === "local") {
|
|
347
|
+
if (effectiveEnvironment !== undefined) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`${action}() environment overrides are only valid with backend: "cloud".`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (options.sandbox !== undefined) {
|
|
353
|
+
throw new Error(`${action}() sandbox options are only valid with backend: "cloud".`);
|
|
354
|
+
}
|
|
355
|
+
if (hasRepositoryResources(options)) {
|
|
356
|
+
throw new Error(`${action}() repository resources are only valid with backend: "cloud".`);
|
|
357
|
+
}
|
|
358
|
+
if (!this.useLegacyLocalStdio()) {
|
|
359
|
+
assertRemoteSessionOptionsSupported(action, options);
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (this.backend === "remote") {
|
|
365
|
+
if (options.environment !== undefined) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`${action}() environment overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (options.sandbox !== undefined) {
|
|
371
|
+
throw new Error(`${action}() sandbox options are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
|
|
372
|
+
}
|
|
373
|
+
if (hasRepositoryResources(options)) {
|
|
374
|
+
throw new Error(`${action}() repository resources are only valid with backend: "cloud".`);
|
|
375
|
+
}
|
|
376
|
+
assertRemoteSessionOptionsSupported(action, options);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (this.backend === "cloud") {
|
|
380
|
+
const cloudOptions = this.cloudOptions();
|
|
381
|
+
if (cloudOptions.environment !== undefined && options.sandbox !== undefined) {
|
|
382
|
+
throw new Error(`Letta Cloud ${action}() cannot specify sandbox options when the client has a default environment.`);
|
|
383
|
+
}
|
|
384
|
+
if (cloudOptions.sandbox !== undefined && options.environment !== undefined) {
|
|
385
|
+
throw new Error(`Letta Cloud ${action}() cannot specify an environment when the client has default sandbox options.`);
|
|
386
|
+
}
|
|
387
|
+
assertCloudSessionOptionsSupported(action, options);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
throw new Error(
|
|
391
|
+
`LettaAgentClient backend '${this.backend}' is not implemented yet. ${action} currently supports backend 'local' only.`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
protected useLegacyLocalStdio(): boolean {
|
|
396
|
+
return (this.options as LettaCodeLocalClientOptions).transport === "stdio";
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
protected createLocalAgent(_options: CreateAgentOptions): Promise<string> {
|
|
400
|
+
throw this.localBackendUnavailableError();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
protected createLocalSession(
|
|
404
|
+
_agentId: string | undefined,
|
|
405
|
+
_options: LettaCodeClientSessionOptions,
|
|
406
|
+
_sessionOptions: CreateSessionOptions,
|
|
407
|
+
): LettaCodeSession {
|
|
408
|
+
throw this.localBackendUnavailableError();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
protected resumeLocalSession(
|
|
412
|
+
_id: string,
|
|
413
|
+
_options: LettaCodeClientSessionOptions,
|
|
414
|
+
_sessionOptions: CreateSessionOptions,
|
|
415
|
+
): LettaCodeSession {
|
|
416
|
+
throw this.localBackendUnavailableError();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
protected localBackendUnavailableError(): Error {
|
|
420
|
+
return new Error(
|
|
421
|
+
'The portable "@letta-ai/letta-agent-sdk/client" entry point supports backend: "remote" and backend: "cloud" only. Import from "@letta-ai/letta-agent-sdk" for local execution.',
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
protected createLocalManagementTransport(): ManagementTransport {
|
|
426
|
+
throw this.localBackendUnavailableError();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private remoteOptions(): LettaCodeRemoteClientOptions {
|
|
430
|
+
if (this.backend !== "remote") {
|
|
431
|
+
throw new Error("Remote options requested for non-remote backend.");
|
|
432
|
+
}
|
|
433
|
+
return this.options as LettaCodeRemoteClientOptions;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private appServerSessionOptions(): AppServerSessionOptions {
|
|
437
|
+
return {
|
|
438
|
+
...this.remoteOptions(),
|
|
439
|
+
beforeConnect: () => this.releaseIdleManagementConnection(),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private getRepositoriesClient(): RepositoriesClient {
|
|
444
|
+
if (this.backend !== "cloud") {
|
|
445
|
+
throw new Error('client.repositories is only available with backend: "cloud".');
|
|
446
|
+
}
|
|
447
|
+
this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
|
|
448
|
+
return this.repositoriesClient;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* The app-server accepts a single control client at a time. Before a session
|
|
453
|
+
* connects, wait for this client's management work to settle and relinquish
|
|
454
|
+
* its pooled connection; management reconnects lazily on its next call.
|
|
455
|
+
*/
|
|
456
|
+
protected async releaseIdleManagementConnection(): Promise<void> {
|
|
457
|
+
await this.managementTransport?.releaseIdleConnection?.();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private getManagementTransport(): ManagementTransport {
|
|
461
|
+
if (this.managementTransport) return this.managementTransport;
|
|
462
|
+
if (this.backend === "remote") {
|
|
463
|
+
this.managementTransport = new AppServerManagementTransport(
|
|
464
|
+
this.remoteOptions(),
|
|
465
|
+
);
|
|
466
|
+
} else if (this.backend === "cloud") {
|
|
467
|
+
this.managementTransport = new CloudManagementTransport(
|
|
468
|
+
this.cloudOptions(),
|
|
469
|
+
);
|
|
470
|
+
} else {
|
|
471
|
+
this.managementTransport = this.createLocalManagementTransport();
|
|
472
|
+
}
|
|
473
|
+
return this.managementTransport;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private cloudOptions(): LettaCodeCloudClientOptions {
|
|
477
|
+
if (this.backend !== "cloud") {
|
|
478
|
+
throw new Error('Letta Cloud options requested for non-"cloud" backend.');
|
|
479
|
+
}
|
|
480
|
+
return this.options as LettaCodeCloudClientOptions;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LettaAgentClientBase } from "./client-base.js";
|
|
2
|
+
import type {
|
|
3
|
+
LettaCodeCloudClientOptions,
|
|
4
|
+
LettaCodeRemoteClientOptions,
|
|
5
|
+
} from "./types.js";
|
|
6
|
+
|
|
7
|
+
export type PortableLettaAgentClientOptions =
|
|
8
|
+
| LettaCodeRemoteClientOptions
|
|
9
|
+
| LettaCodeCloudClientOptions;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Portable Letta Agent SDK client for browsers and React Native.
|
|
13
|
+
*
|
|
14
|
+
* This entry point intentionally excludes local process execution. Use the
|
|
15
|
+
* package root from Node.js when `backend: "local"` is required.
|
|
16
|
+
*/
|
|
17
|
+
export class LettaAgentClient extends LettaAgentClientBase {
|
|
18
|
+
constructor(options: PortableLettaAgentClientOptions) {
|
|
19
|
+
super(options);
|
|
20
|
+
if (this.backend === "local") {
|
|
21
|
+
throw this.localBackendUnavailableError();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { CloudManagedSandboxExpiredError } from "./cloud-session.js";
|
|
27
|
+
export { RepositoriesClient } from "./repositories.js";
|
|
28
|
+
export type * from "./management-types.js";
|
|
29
|
+
export { extractStreamTextDelta } from "./stream-events.js";
|
|
30
|
+
export { createReactNativeWebSocketConstructor } from "./websocket.js";
|
|
31
|
+
export type * from "./types.js";
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { LettaAgentClientBase } from "./client-base.js";
|
|
2
|
+
import { AppServerManagementTransport } from "./app-server-management.js";
|
|
3
|
+
import type { ManagementTransport } from "./management.js";
|
|
4
|
+
import { createLocalAppServerSession } from "./local-app-server-session.js";
|
|
5
|
+
import { startLocalAppServer } from "./local-app-server.js";
|
|
6
|
+
import { Session } from "./session.js";
|
|
7
|
+
import type {
|
|
8
|
+
CreateAgentOptions,
|
|
9
|
+
CreateSessionOptions,
|
|
10
|
+
LettaCodeClientSessionOptions,
|
|
11
|
+
LettaCodeLocalClientOptions,
|
|
12
|
+
LettaCodeSession,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
|
|
15
|
+
export class LettaAgentClient extends LettaAgentClientBase {
|
|
16
|
+
protected override createLocalManagementTransport(): ManagementTransport {
|
|
17
|
+
if (this.useLegacyLocalStdio()) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'client.agents and client.conversations require the local "app-server" transport.',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const localOptions = (
|
|
23
|
+
this.options as LettaCodeLocalClientOptions
|
|
24
|
+
).appServer;
|
|
25
|
+
return new AppServerManagementTransport({
|
|
26
|
+
...(localOptions?.url !== undefined
|
|
27
|
+
? { url: localOptions.url }
|
|
28
|
+
: {
|
|
29
|
+
connect: () =>
|
|
30
|
+
startLocalAppServer({
|
|
31
|
+
listen: localOptions?.listen,
|
|
32
|
+
backend: localOptions?.harnessBackend ?? "local",
|
|
33
|
+
startupTimeoutMs: localOptions?.startupTimeoutMs,
|
|
34
|
+
}),
|
|
35
|
+
}),
|
|
36
|
+
...(localOptions?.WebSocket !== undefined
|
|
37
|
+
? { WebSocket: localOptions.WebSocket }
|
|
38
|
+
: {}),
|
|
39
|
+
...(localOptions?.requestTimeoutMs !== undefined
|
|
40
|
+
? { requestTimeoutMs: localOptions.requestTimeoutMs }
|
|
41
|
+
: {}),
|
|
42
|
+
...(localOptions?.idleLingerMs !== undefined
|
|
43
|
+
? { idleLingerMs: localOptions.idleLingerMs }
|
|
44
|
+
: {}),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected override async createLocalAgent(
|
|
49
|
+
options: CreateAgentOptions,
|
|
50
|
+
): Promise<string> {
|
|
51
|
+
if (!this.useLegacyLocalStdio()) {
|
|
52
|
+
const localOptions = this.options as LettaCodeLocalClientOptions;
|
|
53
|
+
const session = createLocalAppServerSession(
|
|
54
|
+
localOptions.appServer,
|
|
55
|
+
{
|
|
56
|
+
kind: "create-agent",
|
|
57
|
+
options,
|
|
58
|
+
},
|
|
59
|
+
() => this.releaseIdleManagementConnection(),
|
|
60
|
+
);
|
|
61
|
+
const initMsg = await session.initialize();
|
|
62
|
+
session.close();
|
|
63
|
+
return initMsg.agentId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const session = new Session({ ...options, createOnly: true });
|
|
67
|
+
const initMsg = await session.initialize();
|
|
68
|
+
session.close();
|
|
69
|
+
return initMsg.agentId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected override createLocalSession(
|
|
73
|
+
agentId: string | undefined,
|
|
74
|
+
options: LettaCodeClientSessionOptions,
|
|
75
|
+
sessionOptions: CreateSessionOptions,
|
|
76
|
+
): LettaCodeSession {
|
|
77
|
+
if (!this.useLegacyLocalStdio() && agentId) {
|
|
78
|
+
const localOptions = this.options as LettaCodeLocalClientOptions;
|
|
79
|
+
return createLocalAppServerSession(
|
|
80
|
+
localOptions.appServer,
|
|
81
|
+
{
|
|
82
|
+
kind: "session",
|
|
83
|
+
agentId,
|
|
84
|
+
newConversation: true,
|
|
85
|
+
options,
|
|
86
|
+
},
|
|
87
|
+
() => this.releaseIdleManagementConnection(),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (agentId) {
|
|
91
|
+
return new Session({ ...sessionOptions, agentId, newConversation: true });
|
|
92
|
+
}
|
|
93
|
+
return new Session({ ...sessionOptions, newConversation: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
protected override resumeLocalSession(
|
|
97
|
+
id: string,
|
|
98
|
+
options: LettaCodeClientSessionOptions,
|
|
99
|
+
sessionOptions: CreateSessionOptions,
|
|
100
|
+
): LettaCodeSession {
|
|
101
|
+
if (!this.useLegacyLocalStdio()) {
|
|
102
|
+
const localOptions = this.options as LettaCodeLocalClientOptions;
|
|
103
|
+
if (looksLikeConversationId(id)) {
|
|
104
|
+
return createLocalAppServerSession(
|
|
105
|
+
localOptions.appServer,
|
|
106
|
+
{
|
|
107
|
+
kind: "session",
|
|
108
|
+
conversationId: id,
|
|
109
|
+
options,
|
|
110
|
+
},
|
|
111
|
+
() => this.releaseIdleManagementConnection(),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return createLocalAppServerSession(
|
|
115
|
+
localOptions.appServer,
|
|
116
|
+
{
|
|
117
|
+
kind: "session",
|
|
118
|
+
agentId: id,
|
|
119
|
+
defaultConversation: true,
|
|
120
|
+
options,
|
|
121
|
+
},
|
|
122
|
+
() => this.releaseIdleManagementConnection(),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (looksLikeConversationId(id)) {
|
|
126
|
+
return new Session({ ...sessionOptions, conversationId: id });
|
|
127
|
+
}
|
|
128
|
+
return new Session({
|
|
129
|
+
...sessionOptions,
|
|
130
|
+
agentId: id,
|
|
131
|
+
defaultConversation: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function looksLikeConversationId(id: string): boolean {
|
|
137
|
+
return id.startsWith("conv-") || id.startsWith("local-conv-");
|
|
138
|
+
}
|