@letta-ai/letta-agent-sdk 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/AGENTS.md +44 -0
  2. package/README.md +29 -0
  3. package/dist/app-server-management.d.ts +3 -48
  4. package/dist/app-server-management.d.ts.map +1 -1
  5. package/dist/app-server-session.d.ts +0 -6
  6. package/dist/app-server-session.d.ts.map +1 -1
  7. package/dist/client-base.d.ts +7 -15
  8. package/dist/client-base.d.ts.map +1 -1
  9. package/dist/client-entry.js +979 -1010
  10. package/dist/client-entry.js.map +15 -11
  11. package/dist/client.d.ts +3 -3
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/cloud-sandbox.d.ts +33 -0
  14. package/dist/cloud-sandbox.d.ts.map +1 -0
  15. package/dist/cloud-session.d.ts.map +1 -1
  16. package/dist/cloud-status-transport.d.ts +53 -0
  17. package/dist/cloud-status-transport.d.ts.map +1 -0
  18. package/dist/index.d.ts +7 -16
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3242 -4298
  21. package/dist/index.js.map +20 -17
  22. package/dist/local-app-server-session.d.ts +1 -1
  23. package/dist/local-app-server-session.d.ts.map +1 -1
  24. package/dist/local-app-server.d.ts +11 -0
  25. package/dist/local-app-server.d.ts.map +1 -1
  26. package/dist/management.d.ts +0 -7
  27. package/dist/management.d.ts.map +1 -1
  28. package/dist/remote-client-session-core.d.ts +6 -93
  29. package/dist/remote-client-session-core.d.ts.map +1 -1
  30. package/dist/remote-session-protocol.d.ts +132 -0
  31. package/dist/remote-session-protocol.d.ts.map +1 -0
  32. package/dist/remote-turn-coordinator.d.ts +49 -0
  33. package/dist/remote-turn-coordinator.d.ts.map +1 -0
  34. package/dist/types.d.ts +59 -86
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/validation.d.ts.map +1 -1
  37. package/package.json +6 -3
  38. package/src/app-server-management.ts +405 -0
  39. package/src/app-server-session.ts +916 -0
  40. package/src/cli-resolver.ts +46 -0
  41. package/src/client-base.ts +456 -0
  42. package/src/client-entry.ts +31 -0
  43. package/src/client.ts +99 -0
  44. package/src/cloud-management.ts +360 -0
  45. package/src/cloud-sandbox.ts +117 -0
  46. package/src/cloud-session.ts +1055 -0
  47. package/src/cloud-status-transport.ts +305 -0
  48. package/src/index.ts +422 -0
  49. package/src/interactiveToolPolicy.ts +62 -0
  50. package/src/local-app-server-session.ts +49 -0
  51. package/src/local-app-server.ts +231 -0
  52. package/src/management-types.ts +133 -0
  53. package/src/management.ts +199 -0
  54. package/src/remote-client-session-core.ts +786 -0
  55. package/src/remote-session-protocol.ts +674 -0
  56. package/src/remote-turn-coordinator.ts +523 -0
  57. package/src/remote.ts +177 -0
  58. package/src/repositories.ts +340 -0
  59. package/src/request-ids.ts +33 -0
  60. package/src/stream-events.ts +88 -0
  61. package/src/tool-helpers.ts +147 -0
  62. package/src/types.ts +1281 -0
  63. package/src/validation.ts +238 -0
  64. package/src/websocket.ts +22 -0
  65. package/dist/protocol.d.ts +0 -205
  66. package/dist/protocol.d.ts.map +0 -1
  67. package/dist/session.d.ts +0 -155
  68. package/dist/session.d.ts.map +0 -1
  69. package/dist/transport.d.ts +0 -53
  70. package/dist/transport.d.ts.map +0 -1
package/src/index.ts ADDED
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Letta Agent SDK
3
+ *
4
+ * Programmatic control of Letta Code CLI with persistent agent memory.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { LettaAgentClient, createAgent, createSession, resumeSession, prompt } from '@letta-ai/letta-agent-sdk';
9
+ *
10
+ * const client = new LettaAgentClient({ backend: 'local' });
11
+ * const agentId = await client.createAgent();
12
+ * const clientSession = client.resumeSession(agentId);
13
+ *
14
+ * // Create a new agent explicitly
15
+ * const agentId = await createAgent();
16
+ *
17
+ * // Resume default conversation on an agent
18
+ * const session = resumeSession(agentId);
19
+ *
20
+ * // Resume specific conversation
21
+ * const session = resumeSession('conv-xxx');
22
+ *
23
+ * // Create new conversation on specific agent
24
+ * const session = createSession(agentId);
25
+ *
26
+ * // One-shot prompt in a new conversation
27
+ * const result = await prompt('Hello', agentId);
28
+ * ```
29
+ */
30
+
31
+ import { LettaAgentClient } from "./client.js";
32
+ import type {
33
+ CreateSessionOptions,
34
+ CreateAgentOptions,
35
+ LettaCodeSession,
36
+ SDKInitMessage,
37
+ SDKResultMessage,
38
+ SendMessage,
39
+ } from "./types.js";
40
+ import { validateCreateSessionOptions, validateCreateAgentOptions } from "./validation.js";
41
+
42
+ // Re-export types
43
+ export type {
44
+ CreateSessionOptions,
45
+ CreateAgentOptions,
46
+ LettaCodePersonalityId,
47
+ LettaCodeBackend,
48
+ LettaCodeEnvironment,
49
+ LettaCodeLocalClientOptions,
50
+ LettaCodeLocalAppServerOptions,
51
+ LettaCodeRemoteClientOptions,
52
+ LettaCodeCloudClientOptions,
53
+ LettaCodeCloudSandboxOptions,
54
+ GitHubRepositoryRef,
55
+ LettaCodeClientOptions,
56
+ LettaCodeClientSessionOptions,
57
+ LettaCodeSession,
58
+ LettaCodeSocketLike,
59
+ LettaCodeSocketConstructor,
60
+ LettaCodeReactNativeSocketConstructor,
61
+ SDKMessage,
62
+ SDKInitMessage,
63
+ SDKAssistantMessage,
64
+ SDKToolCallMessage,
65
+ SDKToolResultMessage,
66
+ SDKReasoningMessage,
67
+ SDKResultMessage,
68
+ SDKErrorCode,
69
+ SDKStreamEventMessage,
70
+ SDKStreamEventPayload,
71
+ SDKStreamEventDeltaPayload,
72
+ SDKStreamEventMessagePayload,
73
+ SDKUnknownStreamEventPayload,
74
+ SDKErrorMessage,
75
+ SDKRetryMessage,
76
+ SDKQueueItem,
77
+ SDKQueueUpdateMessage,
78
+ SDKLoopStatusMessage,
79
+ SDKProtocolMessage,
80
+ SDKProtocolCommand,
81
+ SendCommandOptions,
82
+ RunTurnOptions,
83
+ RecoverPendingApprovalsOptions,
84
+ RecoverPendingApprovalsResult,
85
+ ChangeDeviceStateOptions,
86
+ RemoveQueuedMessageResult,
87
+ GetDeviceStatusOptions,
88
+ SessionDeviceStatus,
89
+ SessionPendingControlRequest,
90
+ SessionPermissionSuggestion,
91
+ SessionDiffHunkLine,
92
+ SessionDiffHunk,
93
+ SessionDiffPreview,
94
+ SkillSource,
95
+ DreamingOptions,
96
+ SessionDreamingOptions,
97
+ DreamingTrigger,
98
+ DreamingBehavior,
99
+ EffectiveDreamingSettings,
100
+ PermissionMode,
101
+ ReasoningEffort,
102
+ CanUseToolCallback,
103
+ CanUseToolContext,
104
+ CanUseToolPermissionSuggestion,
105
+ CanUseToolResponse,
106
+ CanUseToolResponseAllow,
107
+ CanUseToolResponseDeny,
108
+ // Multimodal content types
109
+ TextContent,
110
+ ImageContent,
111
+ MessageContentItem,
112
+ SendMessage,
113
+ // List messages API
114
+ ListMessagesOptions,
115
+ ListMessagesResult,
116
+ ListModelsResult,
117
+ LettaCodeModelEntry,
118
+ UpdateModelOptions,
119
+ UpdateModelResult,
120
+ Repository,
121
+ CreateRepositoryParams,
122
+ ListRepositoriesParams,
123
+ ListRepositoriesResult,
124
+ RepositoryResource,
125
+ RepositoryFileEntry,
126
+ ListRepositoryFilesParams,
127
+ ListRepositoryFilesResult,
128
+ CreateRepositoryFileParams,
129
+ RepositoryFile,
130
+ UpdateRepositoryFileParams,
131
+ RepositoryFileMutationResult,
132
+ DeleteRepositoryFileParams,
133
+ DeleteRepositoryFileResult,
134
+ RepositoryVersion,
135
+ ListRepositoryVersionsParams,
136
+ GetRepositoryVersionParams,
137
+ // Bootstrap API
138
+ BootstrapStateOptions,
139
+ BootstrapStateResult,
140
+ // Tool types
141
+ AgentTool,
142
+ AgentToolResult,
143
+ AgentToolResultContent,
144
+ AgentToolUpdateCallback,
145
+ AnyAgentTool,
146
+ } from "./types.js";
147
+ export type {
148
+ AgentsClient,
149
+ ConversationsClient,
150
+ LettaAgent,
151
+ LettaConversation,
152
+ LettaConversationMessage,
153
+ ModelsClient,
154
+ ListAgentsOptions,
155
+ UpdateAgentOptions,
156
+ ListConversationsOptions,
157
+ CreateConversationOptions,
158
+ UpdateConversationOptions,
159
+ ConversationMessagesOptions,
160
+ ConversationMessagesResult,
161
+ } from "./management-types.js";
162
+
163
+ export { RepositoriesClient } from "./repositories.js";
164
+ export { LettaAgentClient } from "./client.js";
165
+ export { CloudManagedSandboxExpiredError } from "./cloud-session.js";
166
+ export { createReactNativeWebSocketConstructor } from "./websocket.js";
167
+
168
+ export { extractStreamTextDelta } from "./stream-events.js";
169
+
170
+ // Tool helpers
171
+ export {
172
+ jsonResult,
173
+ readStringParam,
174
+ readNumberParam,
175
+ readBooleanParam,
176
+ readStringArrayParam,
177
+ } from "./tool-helpers.js";
178
+
179
+ /**
180
+ * Create a new agent with a default conversation.
181
+ * Returns the agentId which can be used with resumeSession or createSession.
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * // Create agent with default settings.
186
+ * const agentId = await createAgent();
187
+ *
188
+ * // Create agent with custom memory
189
+ * const agentId = await createAgent({
190
+ * memory: ['persona', 'project'],
191
+ * persona: 'You are a helpful coding assistant',
192
+ * model: 'claude-sonnet-4',
193
+ * tags: ['project:docs']
194
+ * });
195
+ *
196
+ * // Then resume the default conversation:
197
+ * const session = resumeSession(agentId);
198
+ * ```
199
+ */
200
+ export async function createAgent(options: CreateAgentOptions = {}): Promise<string> {
201
+ validateCreateAgentOptions(options);
202
+ return new LettaAgentClient().createAgent(options);
203
+ }
204
+
205
+ /**
206
+ * Create a new conversation (session).
207
+ *
208
+ * Creates a new conversation on the specified agent.
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * // New conversation on specific agent
213
+ * await using session = createSession(agentId);
214
+ * ```
215
+ */
216
+ export function createSession(
217
+ agentId: string,
218
+ options: CreateSessionOptions = {},
219
+ ): LettaCodeSession {
220
+ validateCreateSessionOptions(options);
221
+ return new LettaAgentClient().createSession(agentId, options);
222
+ }
223
+
224
+ /**
225
+ * Resume an existing session.
226
+ *
227
+ * - Pass an agent ID (agent-xxx) to resume the default conversation
228
+ * - Pass a conversation ID (conv-xxx) to resume a specific conversation
229
+ *
230
+ * The default conversation always exists after createAgent, so you can:
231
+ * `createAgent()` → `resumeSession(agentId)` without needing createSession first.
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * // Resume default conversation
236
+ * await using session = resumeSession(agentId);
237
+ *
238
+ * // Resume specific conversation
239
+ * await using session = resumeSession('conv-xxx');
240
+ * ```
241
+ */
242
+ export function resumeSession(
243
+ id: string,
244
+ options: CreateSessionOptions = {},
245
+ ): LettaCodeSession {
246
+ validateCreateSessionOptions(options);
247
+ return new LettaAgentClient().resumeSession(id, options);
248
+ }
249
+
250
+ /**
251
+ * One-shot prompt convenience function.
252
+ *
253
+ * Uses the specified agent in a new conversation.
254
+ * - Uses a short-lived session and returns the final turn result.
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * const result = await prompt('What is the capital of France?', agentId); // specific agent
259
+ * ```
260
+ */
261
+ type TurnSession = LettaCodeSession & {
262
+ runTurn(message: SendMessage): Promise<SDKResultMessage>;
263
+ };
264
+
265
+ type InitializableSession = LettaCodeSession & {
266
+ initialize(): Promise<SDKInitMessage>;
267
+ };
268
+
269
+ export async function prompt(
270
+ message: SendMessage,
271
+ agentId: string,
272
+ options: CreateSessionOptions = {},
273
+ ): Promise<SDKResultMessage> {
274
+ const session = createSession(agentId, options);
275
+
276
+ try {
277
+ return await (session as TurnSession).runTurn(message);
278
+ } finally {
279
+ session.close();
280
+ }
281
+ }
282
+
283
+ // ═══════════════════════════════════════════════════════════════
284
+ // SESSIONLESS APIs
285
+ // ═══════════════════════════════════════════════════════════════
286
+
287
+ import type { ListMessagesOptions, ListMessagesResult } from "./types.js";
288
+
289
+ /**
290
+ * Fetch conversation messages without requiring a pre-existing session.
291
+ *
292
+ * Creates a transient CLI subprocess, fetches the requested message page, and
293
+ * closes the subprocess. Useful for prefetching conversation histories before
294
+ * opening a full session (e.g. desktop sidebar warm-up).
295
+ *
296
+ * Routing follows the same agent/conversation semantics as session history:
297
+ * - Pass a conv-xxx conversationId to read a specific conversation.
298
+ * - Omit conversationId to read the agent's default conversation.
299
+ *
300
+ * @param agentId - Agent ID to fetch messages for.
301
+ * @param options - Pagination / filtering options (same as ListMessagesOptions).
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * // Prefetch default conversation
306
+ * const { messages } = await listMessagesDirect(agentId);
307
+ *
308
+ * // Prefetch a specific conversation
309
+ * const { messages, hasMore, nextBefore } = await listMessagesDirect(agentId, {
310
+ * conversationId: 'conv-abc',
311
+ * limit: 20,
312
+ * order: 'desc',
313
+ * });
314
+ * ```
315
+ */
316
+ export async function listMessagesDirect(
317
+ agentId: string,
318
+ options: ListMessagesOptions = {},
319
+ ): Promise<ListMessagesResult> {
320
+ // resumeSession uses --default which maps to the agent's default conversation.
321
+ // The session is transient: we only need it long enough to list messages.
322
+ const session = new LettaAgentClient().resumeSession(agentId, {
323
+ permissionMode: "unrestricted",
324
+ });
325
+ await (session as InitializableSession).initialize();
326
+ try {
327
+ return await session.listMessages(options);
328
+ } finally {
329
+ session.close();
330
+ }
331
+ }
332
+
333
+ // ═══════════════════════════════════════════════════════════════
334
+ // IMAGE HELPERS
335
+ // ═══════════════════════════════════════════════════════════════
336
+
337
+ import { readFileSync } from "node:fs";
338
+ import type { ImageContent } from "./types.js";
339
+
340
+ /**
341
+ * Create image content from a file path.
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * await session.send([
346
+ * { type: "text", text: "What's in this image?" },
347
+ * imageFromFile("./screenshot.png")
348
+ * ]);
349
+ * ```
350
+ */
351
+ export function imageFromFile(filePath: string): ImageContent {
352
+ const data = readFileSync(filePath).toString("base64");
353
+ const ext = filePath.toLowerCase();
354
+ const media_type: ImageContent["source"]["media_type"] =
355
+ ext.endsWith(".png") ? "image/png"
356
+ : ext.endsWith(".gif") ? "image/gif"
357
+ : ext.endsWith(".webp") ? "image/webp"
358
+ : "image/jpeg";
359
+
360
+ return {
361
+ type: "image",
362
+ source: { type: "base64", media_type, data }
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Create image content from base64 data.
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * const base64 = fs.readFileSync("image.png").toString("base64");
372
+ * await session.send([
373
+ * { type: "text", text: "Describe this" },
374
+ * imageFromBase64(base64, "image/png")
375
+ * ]);
376
+ * ```
377
+ */
378
+ export function imageFromBase64(
379
+ data: string,
380
+ media_type: ImageContent["source"]["media_type"] = "image/png"
381
+ ): ImageContent {
382
+ return {
383
+ type: "image",
384
+ source: { type: "base64", media_type, data }
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Create image content from a URL.
390
+ * Fetches the image and converts to base64.
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * const img = await imageFromURL("https://example.com/image.png");
395
+ * await session.send([
396
+ * { type: "text", text: "What's this?" },
397
+ * img
398
+ * ]);
399
+ * ```
400
+ */
401
+ export async function imageFromURL(url: string): Promise<ImageContent> {
402
+ const response = await fetch(url);
403
+ const buffer = await response.arrayBuffer();
404
+ const data = Buffer.from(buffer).toString("base64");
405
+
406
+ // Detect media type from content-type header or URL
407
+ const contentType = response.headers.get("content-type");
408
+ let media_type: ImageContent["source"]["media_type"] = "image/png";
409
+
410
+ if (contentType?.includes("jpeg") || contentType?.includes("jpg") || url.match(/\.jpe?g$/i)) {
411
+ media_type = "image/jpeg";
412
+ } else if (contentType?.includes("gif") || url.endsWith(".gif")) {
413
+ media_type = "image/gif";
414
+ } else if (contentType?.includes("webp") || url.endsWith(".webp")) {
415
+ media_type = "image/webp";
416
+ }
417
+
418
+ return {
419
+ type: "image",
420
+ source: { type: "base64", media_type, data }
421
+ };
422
+ }
@@ -0,0 +1,62 @@
1
+ // Interactive tool policy for SDK permission callbacks.
2
+ // Centralizes behavior so transport/session logic doesn't hardcode names inline.
3
+
4
+ import type { CanUseToolContext, CanUseToolPermissionSuggestion } from "./types.js";
5
+
6
+ const INTERACTIVE_APPROVAL_TOOLS = new Set([
7
+ "AskUserQuestion",
8
+ "EnterPlanMode",
9
+ "ExitPlanMode",
10
+ ]);
11
+
12
+ const RUNTIME_USER_INPUT_TOOLS = new Set(["AskUserQuestion", "ExitPlanMode"]);
13
+
14
+ const HEADLESS_AUTO_ALLOW_TOOLS = new Set(["EnterPlanMode"]);
15
+
16
+ export function isInteractiveApprovalTool(toolName: string): boolean {
17
+ return INTERACTIVE_APPROVAL_TOOLS.has(toolName);
18
+ }
19
+
20
+ export function requiresRuntimeUserInput(toolName: string): boolean {
21
+ return RUNTIME_USER_INPUT_TOOLS.has(toolName);
22
+ }
23
+
24
+ export function isHeadlessAutoAllowTool(toolName: string): boolean {
25
+ return HEADLESS_AUTO_ALLOW_TOOLS.has(toolName);
26
+ }
27
+
28
+ function normalizePermissionSuggestions(
29
+ value: unknown,
30
+ ): CanUseToolPermissionSuggestion[] | undefined {
31
+ if (!Array.isArray(value)) return undefined;
32
+ const suggestions: CanUseToolPermissionSuggestion[] = [];
33
+ for (const entry of value) {
34
+ if (!entry || typeof entry !== "object") continue;
35
+ const record = entry as Record<string, unknown>;
36
+ if (typeof record.id === "string" && typeof record.text === "string") {
37
+ suggestions.push({ id: record.id, text: record.text });
38
+ }
39
+ }
40
+ return suggestions;
41
+ }
42
+
43
+ /**
44
+ * Build the {@link CanUseToolContext} passed to canUseTool callbacks from a raw
45
+ * `can_use_tool` control request body. Fields absent from the wire request are
46
+ * left undefined so callbacks can distinguish "not provided" from empty values.
47
+ */
48
+ export function buildCanUseToolContext(
49
+ request: Record<string, unknown>,
50
+ requestId?: string,
51
+ ): CanUseToolContext {
52
+ const context: CanUseToolContext = {};
53
+ if (typeof requestId === "string") context.requestId = requestId;
54
+ if (typeof request.tool_call_id === "string") context.toolCallId = request.tool_call_id;
55
+ const suggestions = normalizePermissionSuggestions(request.permission_suggestions);
56
+ if (suggestions !== undefined) context.permissionSuggestions = suggestions;
57
+ if (typeof request.blocked_path === "string" || request.blocked_path === null) {
58
+ context.blockedPath = request.blocked_path as string | null;
59
+ }
60
+ if (Array.isArray(request.diffs)) context.diffs = request.diffs as unknown[];
61
+ return context;
62
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ AppServerSession,
3
+ type AppServerSessionMode,
4
+ type AppServerSessionOptions,
5
+ } from "./app-server-session.js";
6
+ import { startLocalAppServer } from "./local-app-server.js";
7
+ import type {
8
+ LettaCodeClientSessionOptions,
9
+ LettaCodeLocalAppServerOptions,
10
+ } from "./types.js";
11
+
12
+ export function createLocalAppServerSession(
13
+ options: LettaCodeLocalAppServerOptions | undefined,
14
+ mode: AppServerSessionMode,
15
+ ): AppServerSession {
16
+ const appServer = options ?? {};
17
+ const sessionOptions: AppServerSessionOptions = {
18
+ ...(appServer.url !== undefined
19
+ ? { url: appServer.url }
20
+ : {
21
+ connect: (sessionEnv?: Record<string, string>) =>
22
+ startLocalAppServer({
23
+ listen: appServer.listen,
24
+ backend: appServer.harnessBackend ?? "local",
25
+ startupTimeoutMs: appServer.startupTimeoutMs,
26
+ env: sessionEnv,
27
+ filesystemConfinement:
28
+ mode.kind === "session"
29
+ ? (mode.options as LettaCodeClientSessionOptions)
30
+ .filesystemConfinement
31
+ : undefined,
32
+ agentId:
33
+ mode.kind === "session" && "agentId" in mode
34
+ ? mode.agentId
35
+ : undefined,
36
+ }),
37
+ }),
38
+ ...(appServer.WebSocket !== undefined
39
+ ? { WebSocket: appServer.WebSocket }
40
+ : {}),
41
+ ...(appServer.requestTimeoutMs !== undefined
42
+ ? { requestTimeoutMs: appServer.requestTimeoutMs }
43
+ : {}),
44
+ ...(appServer.pinGlobalAgent !== undefined
45
+ ? { pinGlobalAgent: appServer.pinGlobalAgent }
46
+ : {}),
47
+ };
48
+ return new AppServerSession(sessionOptions, mode);
49
+ }