@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
@@ -0,0 +1,159 @@
1
+ import type {
2
+ AgentRepositoriesClient,
3
+ AgentRepository,
4
+ AgentRepositoryRecompileTarget,
5
+ AttachAgentRepositoryOptions,
6
+ DetachAgentRepositoryOptions,
7
+ AgentRepositoryPermissions,
8
+ } from "./management-types.js";
9
+
10
+ const DEFAULT_VISIBILITY_TIMEOUT_MS = 10_000;
11
+ const DEFAULT_VISIBILITY_POLL_INTERVAL_MS = 100;
12
+
13
+ export interface AgentRepositoriesTransport {
14
+ listAgentRepositories(agentId: string): Promise<AgentRepository[]>;
15
+ attachAgentRepository(
16
+ agentId: string,
17
+ repositoryId: string,
18
+ permissions: AgentRepositoryPermissions | undefined,
19
+ ): Promise<AgentRepository>;
20
+ detachAgentRepository(
21
+ agentId: string,
22
+ repositoryId: string,
23
+ ): Promise<void>;
24
+ recompileAgentSystemPrompt(agentId: string): Promise<void>;
25
+ }
26
+
27
+ type TransportProvider = () => AgentRepositoriesTransport;
28
+
29
+ type VisibilityOptions = {
30
+ timeoutMs?: number;
31
+ pollIntervalMs?: number;
32
+ };
33
+
34
+ function assertNonEmptyId(value: string, name: string): void {
35
+ if (typeof value !== "string" || value.trim().length === 0) {
36
+ throw new Error(`Invalid ${name}. Expected a non-empty string.`);
37
+ }
38
+ }
39
+
40
+ function assertPermissions(
41
+ permissions: AgentRepositoryPermissions | undefined,
42
+ ): void {
43
+ if (
44
+ permissions !== undefined &&
45
+ permissions !== "read" &&
46
+ permissions !== "read_write"
47
+ ) {
48
+ throw new Error(
49
+ `Invalid repository permissions '${String(permissions)}'. Expected "read" or "read_write".`,
50
+ );
51
+ }
52
+ }
53
+
54
+ function assertRecompileTarget(
55
+ recompile: AgentRepositoryRecompileTarget | undefined,
56
+ ): void {
57
+ if (recompile !== undefined && recompile !== "default" && recompile !== false) {
58
+ throw new Error(
59
+ `Invalid repository recompile target '${String(recompile)}'. Expected "default" or false.`,
60
+ );
61
+ }
62
+ }
63
+
64
+ function sleep(ms: number): Promise<void> {
65
+ return new Promise((resolve) => setTimeout(resolve, ms));
66
+ }
67
+
68
+ async function waitForRepositoryState(
69
+ transport: AgentRepositoriesTransport,
70
+ agentId: string,
71
+ repositoryId: string,
72
+ attached: boolean,
73
+ permissions: AgentRepositoryPermissions | undefined,
74
+ options: VisibilityOptions,
75
+ ): Promise<void> {
76
+ const deadline = Date.now() +
77
+ (options.timeoutMs ?? DEFAULT_VISIBILITY_TIMEOUT_MS);
78
+ while (true) {
79
+ const repositories = await transport.listAgentRepositories(agentId);
80
+ const repository = repositories.find(
81
+ (repository) => repository.id === repositoryId,
82
+ );
83
+ const isDesiredState = attached
84
+ ? repository !== undefined && repository.permissions === permissions
85
+ : repository === undefined;
86
+ if (isDesiredState) return;
87
+ if (Date.now() >= deadline) {
88
+ const action = attached ? "attach" : "detach";
89
+ throw new Error(
90
+ `Cloud ${action} agent repository did not become visible for ${agentId}: ${repositoryId}`,
91
+ );
92
+ }
93
+ await sleep(
94
+ options.pollIntervalMs ?? DEFAULT_VISIBILITY_POLL_INTERVAL_MS,
95
+ );
96
+ }
97
+ }
98
+
99
+ export function createAgentRepositoriesClient(
100
+ transportProvider: TransportProvider,
101
+ visibilityOptions: VisibilityOptions = {},
102
+ ): AgentRepositoriesClient {
103
+ return {
104
+ list: async (agentId) => {
105
+ assertNonEmptyId(agentId, "agent id");
106
+ return transportProvider().listAgentRepositories(agentId);
107
+ },
108
+ attach: async (
109
+ agentId: string,
110
+ repositoryId: string,
111
+ options: AttachAgentRepositoryOptions = {},
112
+ ) => {
113
+ assertNonEmptyId(agentId, "agent id");
114
+ assertNonEmptyId(repositoryId, "repository id");
115
+ assertPermissions(options.permissions);
116
+ assertRecompileTarget(options.recompile);
117
+ const transport = transportProvider();
118
+ const repository = await transport.attachAgentRepository(
119
+ agentId,
120
+ repositoryId,
121
+ options.permissions,
122
+ );
123
+ await waitForRepositoryState(
124
+ transport,
125
+ agentId,
126
+ repositoryId,
127
+ true,
128
+ repository.permissions,
129
+ visibilityOptions,
130
+ );
131
+ if (options.recompile !== false) {
132
+ await transport.recompileAgentSystemPrompt(agentId);
133
+ }
134
+ return repository;
135
+ },
136
+ detach: async (
137
+ agentId: string,
138
+ repositoryId: string,
139
+ options: DetachAgentRepositoryOptions = {},
140
+ ) => {
141
+ assertNonEmptyId(agentId, "agent id");
142
+ assertNonEmptyId(repositoryId, "repository id");
143
+ assertRecompileTarget(options.recompile);
144
+ const transport = transportProvider();
145
+ await transport.detachAgentRepository(agentId, repositoryId);
146
+ await waitForRepositoryState(
147
+ transport,
148
+ agentId,
149
+ repositoryId,
150
+ false,
151
+ undefined,
152
+ visibilityOptions,
153
+ );
154
+ if (options.recompile !== false) {
155
+ await transport.recompileAgentSystemPrompt(agentId);
156
+ }
157
+ },
158
+ };
159
+ }
@@ -1,59 +1,43 @@
1
1
  import {
2
2
  createAppServerClient,
3
3
  type AppServerClient,
4
- type AppServerRequestCommandWithId,
4
+ type AppServerRawResponse,
5
5
  type AppServerSocketConstructor,
6
6
  } from "@letta-ai/letta-code/app-server-client";
7
7
  import type {
8
- ManagementQuery,
9
- ManagementTransport,
10
- } from "./management.js";
8
+ AgentDeleteResponseMessage,
9
+ AgentListResponseMessage,
10
+ AgentRetrieveResponseMessage,
11
+ AgentUpdateResponseMessage,
12
+ ConversationCreateResponseMessage,
13
+ ConversationListResponseMessage,
14
+ ConversationMessagesListResponseMessage,
15
+ ConversationRetrieveResponseMessage,
16
+ ConversationUpdateResponseMessage,
17
+ ListModelsResponseMessage,
18
+ } from "@letta-ai/letta-code/app-server-protocol";
19
+ import type {
20
+ AgentListParams,
21
+ AgentUpdateParams,
22
+ } from "@letta-ai/letta-client/resources/agents/agents";
23
+ import type {
24
+ ConversationCreateParams,
25
+ ConversationListParams,
26
+ ConversationUpdateParams,
27
+ } from "@letta-ai/letta-client/resources/conversations/conversations";
28
+ import type { MessageListParams } from "@letta-ai/letta-client/resources/conversations/messages";
29
+ import { normalizeAppServerModels } from "./app-server-models.js";
30
+ import type { ManagementTransport } from "./management.js";
11
31
  import { applyUniqueRequestIds } from "./request-ids.js";
12
32
  import type {
13
33
  ConversationMessagesResult,
14
34
  LettaAgent,
15
35
  LettaConversation,
16
36
  } from "./management-types.js";
17
- import type {
18
- LettaCodeModelEntry,
19
- LettaCodeRemoteClientOptions,
20
- ListModelsResult,
21
- } from "./types.js";
37
+ import type { LettaCodeRemoteClientOptions, ListModelsResult } from "./types.js";
22
38
 
23
39
  type OwnedConnection = { url: string; close(): void };
24
40
 
25
- type ManagementResponse = {
26
- type: string;
27
- success: boolean;
28
- error?: string;
29
- };
30
-
31
- type AgentListResponse = ManagementResponse & {
32
- agents: LettaAgent[];
33
- };
34
-
35
- type AgentResponse = ManagementResponse & {
36
- agent: LettaAgent | null;
37
- };
38
-
39
- type ConversationListResponse = ManagementResponse & {
40
- conversations: LettaConversation[];
41
- };
42
-
43
- type ConversationResponse = ManagementResponse & {
44
- conversation: LettaConversation | null;
45
- };
46
-
47
- type ConversationMessagesResponse = ManagementResponse & {
48
- messages: Record<string, unknown>[];
49
- };
50
-
51
- type ListModelsResponse = ManagementResponse & {
52
- entries?: unknown;
53
- available_handles?: unknown;
54
- byok_provider_aliases?: unknown;
55
- };
56
-
57
41
  export type AppServerManagementOptions =
58
42
  Partial<LettaCodeRemoteClientOptions> & {
59
43
  url?: string;
@@ -77,39 +61,6 @@ function ensureResponse<T>(
77
61
  return value;
78
62
  }
79
63
 
80
- function modelEntries(value: unknown): LettaCodeModelEntry[] {
81
- if (!Array.isArray(value)) return [];
82
- return value.flatMap((entry) => {
83
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
84
- const record = entry as Record<string, unknown>;
85
- if (
86
- typeof record.id !== "string" ||
87
- typeof record.handle !== "string" ||
88
- typeof record.label !== "string" ||
89
- typeof record.description !== "string"
90
- ) {
91
- return [];
92
- }
93
- return [{
94
- ...record,
95
- id: record.id,
96
- handle: record.handle,
97
- label: record.label,
98
- description: record.description,
99
- }];
100
- });
101
- }
102
-
103
- function stringRecord(value: unknown): Record<string, string> | undefined {
104
- if (!value || typeof value !== "object" || Array.isArray(value)) {
105
- return undefined;
106
- }
107
- const entries = Object.entries(value as Record<string, unknown>).filter(
108
- (entry): entry is [string, string] => typeof entry[1] === "string",
109
- );
110
- return entries.length > 0 ? Object.fromEntries(entries) : undefined;
111
- }
112
-
113
64
  /**
114
65
  * Management transport that speaks the app-server control protocol.
115
66
  *
@@ -125,8 +76,8 @@ export class AppServerManagementTransport
125
76
 
126
77
  constructor(private readonly options: AppServerManagementOptions) {}
127
78
 
128
- async listAgents(query: ManagementQuery): Promise<LettaAgent[]> {
129
- const response = await this.request<AgentListResponse>(
79
+ async listAgents(query: AgentListParams): Promise<LettaAgent[]> {
80
+ const response = await this.request<AgentListResponseMessage>(
130
81
  "agent_list",
131
82
  { query },
132
83
  "agent_list_response",
@@ -138,7 +89,7 @@ export class AppServerManagementTransport
138
89
  }
139
90
 
140
91
  async retrieveAgent(agentId: string): Promise<LettaAgent> {
141
- const response = await this.request<AgentResponse>(
92
+ const response = await this.request<AgentRetrieveResponseMessage>(
142
93
  "agent_retrieve",
143
94
  { agent_id: agentId },
144
95
  "agent_retrieve_response",
@@ -152,9 +103,9 @@ export class AppServerManagementTransport
152
103
 
153
104
  async updateAgent(
154
105
  agentId: string,
155
- body: Record<string, unknown>,
106
+ body: AgentUpdateParams,
156
107
  ): Promise<LettaAgent> {
157
- const response = await this.request<AgentResponse>(
108
+ const response = await this.request<AgentUpdateResponseMessage>(
158
109
  "agent_update",
159
110
  { agent_id: agentId, body },
160
111
  "agent_update_response",
@@ -167,7 +118,7 @@ export class AppServerManagementTransport
167
118
  }
168
119
 
169
120
  async deleteAgent(agentId: string): Promise<void> {
170
- const response = await this.request<ManagementResponse>(
121
+ const response = await this.request<AgentDeleteResponseMessage>(
171
122
  "agent_delete",
172
123
  { agent_id: agentId },
173
124
  "agent_delete_response",
@@ -180,33 +131,18 @@ export class AppServerManagementTransport
180
131
  async listModels(): Promise<ListModelsResult> {
181
132
  // A bare list_models command (no runtime scope) is answered on the
182
133
  // control channel, so no session or conversation is required.
183
- const response = await this.request<ListModelsResponse>(
134
+ const response = await this.request<ListModelsResponseMessage>(
184
135
  "list_models",
185
136
  {},
186
137
  "list_models_response",
187
138
  );
188
- if (!response.success) {
189
- throw new Error(response.error ?? "Failed to list models.");
190
- }
191
- const result: ListModelsResult = {
192
- entries: modelEntries(response.entries),
193
- };
194
- if (response.available_handles === null) {
195
- result.availableHandles = null;
196
- } else if (Array.isArray(response.available_handles)) {
197
- result.availableHandles = response.available_handles.filter(
198
- (handle): handle is string => typeof handle === "string",
199
- );
200
- }
201
- const aliases = stringRecord(response.byok_provider_aliases);
202
- if (aliases) result.byokProviderAliases = aliases;
203
- return result;
139
+ return normalizeAppServerModels(response);
204
140
  }
205
141
 
206
142
  async listConversations(
207
- query: ManagementQuery,
143
+ query: ConversationListParams,
208
144
  ): Promise<LettaConversation[]> {
209
- const response = await this.request<ConversationListResponse>(
145
+ const response = await this.request<ConversationListResponseMessage>(
210
146
  "conversation_list",
211
147
  { query },
212
148
  "conversation_list_response",
@@ -220,7 +156,7 @@ export class AppServerManagementTransport
220
156
  async retrieveConversation(
221
157
  conversationId: string,
222
158
  ): Promise<LettaConversation> {
223
- const response = await this.request<ConversationResponse>(
159
+ const response = await this.request<ConversationRetrieveResponseMessage>(
224
160
  "conversation_retrieve",
225
161
  { conversation_id: conversationId },
226
162
  "conversation_retrieve_response",
@@ -233,9 +169,9 @@ export class AppServerManagementTransport
233
169
  }
234
170
 
235
171
  async createConversation(
236
- body: Record<string, unknown>,
172
+ body: ConversationCreateParams,
237
173
  ): Promise<LettaConversation> {
238
- const response = await this.request<ConversationResponse>(
174
+ const response = await this.request<ConversationCreateResponseMessage>(
239
175
  "conversation_create",
240
176
  { body },
241
177
  "conversation_create_response",
@@ -249,9 +185,9 @@ export class AppServerManagementTransport
249
185
 
250
186
  async updateConversation(
251
187
  conversationId: string,
252
- body: Record<string, unknown>,
188
+ body: ConversationUpdateParams,
253
189
  ): Promise<LettaConversation> {
254
- const response = await this.request<ConversationResponse>(
190
+ const response = await this.request<ConversationUpdateResponseMessage>(
255
191
  "conversation_update",
256
192
  { conversation_id: conversationId, body },
257
193
  "conversation_update_response",
@@ -265,10 +201,10 @@ export class AppServerManagementTransport
265
201
 
266
202
  async listConversationMessages(
267
203
  conversationId: string,
268
- query: ManagementQuery,
204
+ query: MessageListParams,
269
205
  ): Promise<ConversationMessagesResult> {
270
206
  const response =
271
- await this.request<ConversationMessagesResponse>(
207
+ await this.request<ConversationMessagesListResponseMessage>(
272
208
  "conversation_messages_list",
273
209
  { conversation_id: conversationId, query },
274
210
  "conversation_messages_list_response",
@@ -294,16 +230,20 @@ export class AppServerManagementTransport
294
230
  // counter, while connection identity keeps this pool independent from
295
231
  // session clients using the same app-server.
296
232
  const { client } = await this.acquireConnection();
297
- const command = {
298
- type,
299
- request_id: client.nextRequestId(type),
300
- ...body,
301
- } as AppServerRequestCommandWithId;
302
- const response = await client.request(command, {
303
- predicate: (message): message is typeof message =>
304
- message.type === responseType,
305
- });
306
- return response as unknown as TResponse;
233
+ return client.requestRaw<TResponse & AppServerRawResponse>(
234
+ {
235
+ type,
236
+ request_id: client.nextRequestId(type),
237
+ ...body,
238
+ },
239
+ {
240
+ predicate: (message): message is TResponse & AppServerRawResponse =>
241
+ message !== null &&
242
+ typeof message === "object" &&
243
+ "type" in message &&
244
+ message.type === responseType,
245
+ },
246
+ );
307
247
  }
308
248
 
309
249
  private acquireConnection(): Promise<ActiveConnection> {
@@ -0,0 +1,20 @@
1
+ import type { ListModelsResponseMessage } from "@letta-ai/letta-code/app-server-protocol";
2
+ import type { ListModelsResult } from "./types.js";
3
+
4
+ export function normalizeAppServerModels(
5
+ response: ListModelsResponseMessage,
6
+ ): ListModelsResult {
7
+ if (!response.success) {
8
+ throw new Error(response.error ?? "listModels failed");
9
+ }
10
+
11
+ return {
12
+ entries: response.entries,
13
+ ...(response.available_handles !== undefined
14
+ ? { availableHandles: response.available_handles }
15
+ : {}),
16
+ ...(response.byok_provider_aliases !== undefined
17
+ ? { byokProviderAliases: response.byok_provider_aliases }
18
+ : {}),
19
+ };
20
+ }