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

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 (52) hide show
  1. package/AGENTS.md +35 -0
  2. package/README.md +22 -10
  3. package/dist/agent-repositories.d.ts +15 -0
  4. package/dist/agent-repositories.d.ts.map +1 -0
  5. package/dist/app-server-management.d.ts +10 -7
  6. package/dist/app-server-management.d.ts.map +1 -1
  7. package/dist/app-server-models.d.ts +4 -0
  8. package/dist/app-server-models.d.ts.map +1 -0
  9. package/dist/app-server-session.d.ts +3 -3
  10. package/dist/app-server-session.d.ts.map +1 -1
  11. package/dist/client-base.d.ts +5 -0
  12. package/dist/client-base.d.ts.map +1 -1
  13. package/dist/client-entry.js +3386 -639
  14. package/dist/client-entry.js.map +85 -13
  15. package/dist/cloud-client.d.ts +7 -0
  16. package/dist/cloud-client.d.ts.map +1 -0
  17. package/dist/cloud-management.d.ts +21 -18
  18. package/dist/cloud-management.d.ts.map +1 -1
  19. package/dist/cloud-session.d.ts +7 -5
  20. package/dist/cloud-session.d.ts.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3400 -653
  24. package/dist/index.js.map +86 -14
  25. package/dist/management-types.d.ts +85 -64
  26. package/dist/management-types.d.ts.map +1 -1
  27. package/dist/management.d.ts +11 -9
  28. package/dist/management.d.ts.map +1 -1
  29. package/dist/remote-session-protocol.d.ts +1 -1
  30. package/dist/remote-session-protocol.d.ts.map +1 -1
  31. package/dist/remote.d.ts +4 -5
  32. package/dist/remote.d.ts.map +1 -1
  33. package/dist/repositories.d.ts +3 -2
  34. package/dist/repositories.d.ts.map +1 -1
  35. package/dist/types.d.ts +22 -34
  36. package/dist/types.d.ts.map +1 -1
  37. package/package.json +3 -2
  38. package/src/agent-repositories.ts +159 -0
  39. package/src/app-server-management.ts +56 -116
  40. package/src/app-server-models.ts +20 -0
  41. package/src/app-server-session.ts +71 -146
  42. package/src/client-base.ts +72 -20
  43. package/src/cloud-client.ts +46 -0
  44. package/src/cloud-management.ts +128 -275
  45. package/src/cloud-session.ts +168 -267
  46. package/src/index.ts +6 -0
  47. package/src/management-types.ts +107 -64
  48. package/src/management.ts +46 -39
  49. package/src/remote-session-protocol.ts +6 -2
  50. package/src/remote.ts +18 -63
  51. package/src/repositories.ts +26 -99
  52. package/src/types.ts +22 -35
@@ -1,3 +1,5 @@
1
+ import type Letta from "@letta-ai/letta-client";
2
+ import { createCloudClient } from "./cloud-client.js";
1
3
  import type {
2
4
  CreateRepositoryFileParams,
3
5
  CreateRepositoryParams,
@@ -17,81 +19,6 @@ import type {
17
19
  UpdateRepositoryFileParams,
18
20
  } from "./types.js";
19
21
 
20
- const DEFAULT_CLOUD_API_BASE_URL = "https://api.letta.com";
21
-
22
- type FetchLike = typeof fetch;
23
-
24
- function getDefaultApiKey(): string | undefined {
25
- const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })
26
- .process?.env;
27
- return env?.LETTA_API_KEY ?? env?.LETTA_CLOUD_API_KEY;
28
- }
29
-
30
- function bearerTokenFromHeaders(headers: Record<string, string> | undefined): string | undefined {
31
- const authorization = headers?.Authorization ?? headers?.authorization;
32
- if (!authorization) return undefined;
33
- const match = /^Bearer\s+(.+)$/i.exec(authorization);
34
- return match?.[1];
35
- }
36
-
37
- function getCloudApiKey(options: LettaCodeCloudClientOptions): string | undefined {
38
- return options.apiKey ?? bearerTokenFromHeaders(options.headers) ?? getDefaultApiKey();
39
- }
40
-
41
- function getFetch(fetchOverride?: FetchLike): FetchLike {
42
- const resolved = fetchOverride ?? globalThis.fetch;
43
- if (!resolved) throw new Error("No fetch implementation available for cloud backend.");
44
- return resolved.bind(globalThis) as FetchLike;
45
- }
46
-
47
- function normalizeCloudApiBaseUrl(url: string | undefined): string {
48
- const parsed = new URL(url ?? DEFAULT_CLOUD_API_BASE_URL);
49
- parsed.pathname = parsed.pathname.replace(/\/+$/, "");
50
- parsed.search = "";
51
- parsed.hash = "";
52
- return parsed.toString().replace(/\/$/, "");
53
- }
54
-
55
- function cloudHeaders(options: LettaCodeCloudClientOptions): Record<string, string> {
56
- const headers: Record<string, string> = {
57
- "Content-Type": "application/json",
58
- ...(options.headers ?? {}),
59
- };
60
- const apiKey = getCloudApiKey(options);
61
- if (apiKey && !headers.Authorization && !headers.authorization) {
62
- headers.Authorization = `Bearer ${apiKey}`;
63
- }
64
- return headers;
65
- }
66
-
67
- async function parseJsonResponse(response: Response): Promise<unknown> {
68
- const text = await response.text();
69
- if (!text) return null;
70
- try {
71
- return JSON.parse(text) as unknown;
72
- } catch {
73
- return text;
74
- }
75
- }
76
-
77
- function responseErrorMessage(body: unknown, fallback: string): string {
78
- if (body && typeof body === "object") {
79
- const record = body as Record<string, unknown>;
80
- const message = record.message ?? record.error ?? record.detail;
81
- const reasonText = record.reason_text;
82
- const pieces = [message, reasonText]
83
- .filter((value): value is string => typeof value === "string" && value.length > 0);
84
- if (pieces.length > 0) return pieces.join(": ");
85
- }
86
- return fallback;
87
- }
88
-
89
- function assertOkResponse(response: Response, body: unknown, action: string): void {
90
- if (!response.ok) {
91
- throw new Error(responseErrorMessage(body, `${action} failed with HTTP ${response.status}`));
92
- }
93
- }
94
-
95
22
  function optionalString(value: unknown): string | undefined {
96
23
  return typeof value === "string" ? value : undefined;
97
24
  }
@@ -131,17 +58,20 @@ function addOptionalSearchParam(url: URL, key: string, value: string | number |
131
58
  }
132
59
 
133
60
  export class RepositoriesClient {
134
- constructor(private readonly options: LettaCodeCloudClientOptions) {}
61
+ constructor(
62
+ options: LettaCodeCloudClientOptions,
63
+ private readonly client: Letta = createCloudClient(options),
64
+ ) {}
135
65
 
136
66
  async create(params: CreateRepositoryParams): Promise<Repository> {
137
- return toRepository(await this.request("/v1/repositories", "POST", { name: params.name }, "Cloud create repository"));
67
+ return toRepository(await this.request("/v1/repositories", "POST", { name: params.name }));
138
68
  }
139
69
 
140
70
  async list(params: ListRepositoriesParams = {}): Promise<ListRepositoriesResult> {
141
71
  const url = this.url("/v1/repositories");
142
72
  addOptionalSearchParam(url, "limit", params.limit);
143
73
  addOptionalSearchParam(url, "offset", params.offset);
144
- const body = await this.requestUrl(url, "GET", undefined, "Cloud list repositories");
74
+ const body = await this.requestUrl(url, "GET", undefined);
145
75
  if (!body || typeof body !== "object") {
146
76
  throw new Error("Cloud list repositories response did not include repositories.");
147
77
  }
@@ -156,7 +86,7 @@ export class RepositoriesClient {
156
86
  }
157
87
 
158
88
  async get(repositoryId: string): Promise<Repository> {
159
- return toRepository(await this.request(`/v1/repositories/${encodeURIComponent(repositoryId)}`, "GET", undefined, "Cloud get repository"));
89
+ return toRepository(await this.request(`/v1/repositories/${encodeURIComponent(repositoryId)}`, "GET", undefined));
160
90
  }
161
91
 
162
92
  /**
@@ -169,7 +99,6 @@ export class RepositoriesClient {
169
99
  `/v1/repositories/${encodeURIComponent(repositoryId)}`,
170
100
  "DELETE",
171
101
  undefined,
172
- "Cloud delete repository",
173
102
  );
174
103
  }
175
104
 
@@ -182,7 +111,7 @@ export class RepositoriesClient {
182
111
  addOptionalSearchParam(url, "path_prefix", params.pathPrefix);
183
112
  addOptionalSearchParam(url, "depth", params.depth);
184
113
  addOptionalSearchParam(url, "ref", params.ref);
185
- const body = await this.requestUrl(url, "GET", undefined, "Cloud list repository files");
114
+ const body = await this.requestUrl(url, "GET", undefined);
186
115
  if (!body || typeof body !== "object") {
187
116
  throw new Error("Cloud list repository files response did not include files.");
188
117
  }
@@ -210,14 +139,13 @@ export class RepositoriesClient {
210
139
  `/v1/repositories/${encodeURIComponent(repositoryId)}/files`,
211
140
  "POST",
212
141
  { path: params.path, content: params.content },
213
- "Cloud create repository file",
214
142
  )),
215
143
 
216
144
  read: async (repositoryId: string, params: { path: string; ref?: string }): Promise<RepositoryFile> => {
217
145
  const url = this.url(`/v1/repositories/${encodeURIComponent(repositoryId)}/files/content`);
218
146
  url.searchParams.set("path", params.path);
219
147
  addOptionalSearchParam(url, "ref", params.ref);
220
- const body = await this.requestUrl(url, "GET", undefined, "Cloud read repository file");
148
+ const body = await this.requestUrl(url, "GET", undefined);
221
149
  if (!body || typeof body !== "object") {
222
150
  throw new Error("Cloud read repository file response did not include file content.");
223
151
  }
@@ -249,7 +177,6 @@ export class RepositoriesClient {
249
177
  }
250
178
  : {}),
251
179
  },
252
- "Cloud update repository file",
253
180
  )),
254
181
 
255
182
  delete: async (
@@ -260,7 +187,6 @@ export class RepositoriesClient {
260
187
  `/v1/repositories/${encodeURIComponent(repositoryId)}/files/content`,
261
188
  "DELETE",
262
189
  { path: params.path },
263
- "Cloud delete repository file",
264
190
  );
265
191
  if (!body || typeof body !== "object") {
266
192
  throw new Error("Cloud delete repository file response did not include delete details.");
@@ -278,7 +204,7 @@ export class RepositoriesClient {
278
204
  const url = this.url(`/v1/repositories/${encodeURIComponent(repositoryId)}/versions`);
279
205
  addOptionalSearchParam(url, "path", params.path);
280
206
  addOptionalSearchParam(url, "limit", params.limit);
281
- const body = await this.requestUrl(url, "GET", undefined, "Cloud list repository versions");
207
+ const body = await this.requestUrl(url, "GET", undefined);
282
208
  if (Array.isArray(body)) return body as RepositoryVersion[];
283
209
  if (body && typeof body === "object") {
284
210
  const record = body as Record<string, unknown>;
@@ -295,7 +221,7 @@ export class RepositoriesClient {
295
221
  ): Promise<RepositoryFile> => {
296
222
  const url = this.url(`/v1/repositories/${encodeURIComponent(repositoryId)}/versions/${encodeURIComponent(sha)}`);
297
223
  url.searchParams.set("path", params.path);
298
- const body = await this.requestUrl(url, "GET", undefined, "Cloud get repository version");
224
+ const body = await this.requestUrl(url, "GET", undefined);
299
225
  if (!body || typeof body !== "object") {
300
226
  throw new Error("Cloud get repository version response did not include file content.");
301
227
  }
@@ -310,31 +236,32 @@ export class RepositoriesClient {
310
236
  };
311
237
 
312
238
  private url(path: string): URL {
313
- return new URL(`${normalizeCloudApiBaseUrl(this.options.apiBaseUrl)}${path}`);
239
+ return new URL(`${this.client.baseURL}${path}`);
314
240
  }
315
241
 
316
242
  private async request(
317
243
  path: string,
318
244
  method: string,
319
245
  body: unknown,
320
- action: string,
321
246
  ): Promise<unknown> {
322
- return this.requestUrl(this.url(path), method, body, action);
247
+ return this.requestUrl(this.url(path), method, body);
323
248
  }
324
249
 
325
250
  private async requestUrl(
326
251
  url: URL,
327
252
  method: string,
328
253
  body: unknown,
329
- action: string,
330
254
  ): Promise<unknown> {
331
- const response = await getFetch(this.options.fetch)(url, {
332
- method,
333
- headers: cloudHeaders(this.options),
334
- ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
335
- });
336
- const parsed = await parseJsonResponse(response);
337
- assertOkResponse(response, parsed, action);
338
- return parsed;
255
+ const options = body !== undefined ? { body } : undefined;
256
+ switch (method) {
257
+ case "GET":
258
+ return this.client.get(url.toString(), options);
259
+ case "POST":
260
+ return this.client.post(url.toString(), options);
261
+ case "DELETE":
262
+ return this.client.delete(url.toString(), options);
263
+ default:
264
+ throw new Error(`Unsupported repository request method: ${method}`);
265
+ }
339
266
  }
340
267
  }
package/src/types.ts CHANGED
@@ -6,7 +6,11 @@
6
6
  */
7
7
 
8
8
  import type { PersonalityId } from "@letta-ai/letta-code/agent-presets";
9
+ import type { ListModelsResponseModelEntry } from "@letta-ai/letta-code/app-server-protocol";
10
+ import type { Message as LettaMessage } from "@letta-ai/letta-client/resources/agents/messages";
11
+ import type { CreateBlock } from "@letta-ai/letta-client/resources/blocks/blocks";
9
12
  import type { LettaCodeCloudSandboxOptions } from "./cloud-sandbox.js";
13
+ export type { CreateBlock } from "@letta-ai/letta-client/resources/blocks/blocks";
10
14
  export type {
11
15
  GitHubRepositoryRef,
12
16
  LettaCodeCloudSandboxOptions,
@@ -15,26 +19,6 @@ export type {
15
19
  /** Letta Code personality preset used to seed a new agent. */
16
20
  export type LettaCodePersonalityId = PersonalityId;
17
21
 
18
- /** Custom memory block definition accepted when creating agents. */
19
- export interface CreateBlock {
20
- label: string;
21
- value: string;
22
- base_template_id?: string | null;
23
- deployment_id?: string | null;
24
- description?: string | null;
25
- entity_id?: string | null;
26
- hidden?: boolean | null;
27
- is_template?: boolean;
28
- limit?: number;
29
- metadata?: Record<string, unknown> | null;
30
- preserve_on_migration?: boolean | null;
31
- project_id?: string | null;
32
- read_only?: boolean;
33
- tags?: string[] | null;
34
- template_id?: string | null;
35
- template_name?: string | null;
36
- }
37
-
38
22
  export interface LettaCodeSocketLike {
39
23
  readyState: number;
40
24
  send(data: string): void;
@@ -408,6 +392,12 @@ export interface ListRepositoriesResult {
408
392
  export interface RepositoryResource {
409
393
  type: "repository";
410
394
  repositoryId: string;
395
+ /**
396
+ * Whether to trigger a system-prompt recompile after attaching (and after
397
+ * detaching on cleanup) so the session's conversation does not retain
398
+ * stale repository projections. Defaults to `true`.
399
+ */
400
+ recompile?: boolean;
411
401
  }
412
402
 
413
403
  export interface RepositoryFileEntry {
@@ -558,16 +548,7 @@ export type ReasoningEffort =
558
548
  | "high"
559
549
  | "xhigh";
560
550
 
561
- export type LettaCodeModelEntry = Record<string, unknown> & {
562
- id: string;
563
- handle: string;
564
- label: string;
565
- description: string;
566
- isDefault?: boolean;
567
- isFeatured?: boolean;
568
- free?: boolean;
569
- updateArgs?: Record<string, unknown>;
570
- };
551
+ export type LettaCodeModelEntry = ListModelsResponseModelEntry;
571
552
 
572
553
  export interface ListModelsResult {
573
554
  entries: LettaCodeModelEntry[];
@@ -668,7 +649,11 @@ export interface CreateSessionOptions {
668
649
  */
669
650
  approvalRecoveryTimeoutMs?: number;
670
651
 
671
- /** Cloud repository resources to attach for the lifetime of the SDK session. */
652
+ /**
653
+ * Cloud repository resources to attach for the lifetime of the SDK session.
654
+ * For relationships that should outlive the session, use
655
+ * `client.agents.repositories.attach()` instead.
656
+ */
672
657
  resources?: RepositoryResource[];
673
658
 
674
659
  }
@@ -845,6 +830,8 @@ export interface SessionDeviceStatus {
845
830
  permissionMode: PermissionMode;
846
831
  /** Working directory currently applied to this runtime scope. */
847
832
  workingDirectory: string | null;
833
+ /** Agent memory checkout on the computer executing this session. */
834
+ memoryDirectory: string | null;
848
835
  /** Approvals the device is still waiting on (foreground-resume UI). */
849
836
  pendingControlRequests: SessionPendingControlRequest[];
850
837
  /** Full wire `device_status` payload as an escape hatch. */
@@ -985,7 +972,7 @@ export interface SDKInitMessage {
985
972
  export interface SDKAssistantMessage {
986
973
  type: "assistant";
987
974
  content: string;
988
- /** Legacy transport identifier. Prefer `otid` for message lineage. */
975
+ /** Top-level message ID when provided by the stream; otherwise an SDK-generated identifier. */
989
976
  uuid: string;
990
977
  /** Stable lineage key for this typed message slice, when provided. */
991
978
  otid?: string | null;
@@ -1020,7 +1007,7 @@ export interface SDKToolResultMessage {
1020
1007
  export interface SDKReasoningMessage {
1021
1008
  type: "reasoning";
1022
1009
  content: string;
1023
- /** Legacy transport identifier. Prefer `otid` for message lineage. */
1010
+ /** Top-level message ID when provided by the stream; otherwise an SDK-generated identifier. */
1024
1011
  uuid: string;
1025
1012
  /** Stable lineage key for this typed message slice, when provided. */
1026
1013
  otid?: string | null;
@@ -1211,7 +1198,7 @@ export interface ListMessagesOptions {
1211
1198
  * authoritative pagination answer.
1212
1199
  */
1213
1200
  export interface ListMessagesResult {
1214
- messages: unknown[];
1201
+ messages: LettaMessage[];
1215
1202
  /** ID of the oldest message in this page; use as `before` for the next page when present. */
1216
1203
  nextBefore?: string | null;
1217
1204
  /** Whether more pages exist in the requested direction, when known. */
@@ -1251,7 +1238,7 @@ export interface BootstrapStateResult {
1251
1238
  /** Whether memfs (git-backed memory) is enabled, when known. */
1252
1239
  memfsEnabled?: boolean;
1253
1240
  /** Initial history page (same shape as listMessages.messages). */
1254
- messages: unknown[];
1241
+ messages: LettaMessage[];
1255
1242
  /** Cursor to fetch older messages. Null when the backend knows there are no more pages. */
1256
1243
  nextBefore?: string | null;
1257
1244
  /** Whether more history pages exist, when known. */