@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.
- package/AGENTS.md +35 -0
- package/README.md +22 -10
- package/dist/agent-repositories.d.ts +15 -0
- package/dist/agent-repositories.d.ts.map +1 -0
- package/dist/app-server-management.d.ts +10 -7
- package/dist/app-server-management.d.ts.map +1 -1
- package/dist/app-server-models.d.ts +4 -0
- package/dist/app-server-models.d.ts.map +1 -0
- package/dist/app-server-session.d.ts +3 -3
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-base.d.ts +5 -0
- package/dist/client-base.d.ts.map +1 -1
- package/dist/client-entry.js +3386 -639
- package/dist/client-entry.js.map +85 -13
- package/dist/cloud-client.d.ts +7 -0
- package/dist/cloud-client.d.ts.map +1 -0
- package/dist/cloud-management.d.ts +21 -18
- package/dist/cloud-management.d.ts.map +1 -1
- package/dist/cloud-session.d.ts +7 -5
- 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 +3400 -653
- package/dist/index.js.map +86 -14
- package/dist/management-types.d.ts +85 -64
- package/dist/management-types.d.ts.map +1 -1
- package/dist/management.d.ts +11 -9
- package/dist/management.d.ts.map +1 -1
- package/dist/remote-session-protocol.d.ts +1 -1
- package/dist/remote-session-protocol.d.ts.map +1 -1
- package/dist/remote.d.ts +4 -5
- package/dist/remote.d.ts.map +1 -1
- package/dist/repositories.d.ts +3 -2
- package/dist/repositories.d.ts.map +1 -1
- package/dist/types.d.ts +22 -34
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/agent-repositories.ts +159 -0
- package/src/app-server-management.ts +56 -116
- package/src/app-server-models.ts +20 -0
- package/src/app-server-session.ts +71 -146
- package/src/client-base.ts +72 -20
- package/src/cloud-client.ts +46 -0
- package/src/cloud-management.ts +128 -275
- package/src/cloud-session.ts +168 -267
- package/src/index.ts +6 -0
- package/src/management-types.ts +107 -64
- package/src/management.ts +46 -39
- package/src/remote-session-protocol.ts +6 -2
- package/src/remote.ts +18 -63
- package/src/repositories.ts +26 -99
- package/src/types.ts +22 -35
package/src/cloud-session.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type Letta from "@letta-ai/letta-client";
|
|
2
|
+
import { APIError } from "@letta-ai/letta-client/core/error";
|
|
3
|
+
import type { AgentCreateParams } from "@letta-ai/letta-client/resources/agents/agents";
|
|
1
4
|
import {
|
|
2
5
|
createAppServerClient,
|
|
3
6
|
type AppServerClient,
|
|
@@ -11,6 +14,12 @@ import {
|
|
|
11
14
|
registerAppServerControlRequestHandler,
|
|
12
15
|
} from "./app-server-session.js";
|
|
13
16
|
import { createCloudStatusTransportConstructor } from "./cloud-status-transport.js";
|
|
17
|
+
import { CloudManagementTransport } from "./cloud-management.js";
|
|
18
|
+
import {
|
|
19
|
+
createCloudClient,
|
|
20
|
+
getCloudApiKey,
|
|
21
|
+
normalizeCloudApiBaseUrl,
|
|
22
|
+
} from "./cloud-client.js";
|
|
14
23
|
import {
|
|
15
24
|
RemoteEnvironmentClient,
|
|
16
25
|
type RemoteEnvironmentTarget,
|
|
@@ -38,7 +47,6 @@ import {
|
|
|
38
47
|
validateCloudSandboxOptions,
|
|
39
48
|
} from "./cloud-sandbox.js";
|
|
40
49
|
|
|
41
|
-
const DEFAULT_CLOUD_API_BASE_URL = "https://api.letta.com";
|
|
42
50
|
const DEFAULT_TURN_TIMEOUT_MS = 120_000;
|
|
43
51
|
const DEFAULT_PING_INTERVAL_MS = 30_000;
|
|
44
52
|
const DEFAULT_SANDBOX_TTL_MINUTES = 5;
|
|
@@ -48,8 +56,6 @@ const DEFAULT_REPOSITORY_ATTACH_TIMEOUT_MS = 10_000;
|
|
|
48
56
|
const DEFAULT_REPOSITORY_ATTACH_POLL_INTERVAL_MS = 250;
|
|
49
57
|
const SDK_AGENT_ORIGIN = "@letta-ai/letta-agent-sdk";
|
|
50
58
|
|
|
51
|
-
type FetchLike = typeof fetch;
|
|
52
|
-
|
|
53
59
|
type CloudRuntimeStartResponse = ProtocolMessage & {
|
|
54
60
|
type: "runtime_start_response";
|
|
55
61
|
success: boolean;
|
|
@@ -126,37 +132,6 @@ export class CloudManagedSandboxExpiredError extends Error {
|
|
|
126
132
|
|
|
127
133
|
type CloudSessionMode = Extract<RuntimeSessionMode, { kind: "session" }>;
|
|
128
134
|
|
|
129
|
-
type CloudAgentRepository = {
|
|
130
|
-
id: string;
|
|
131
|
-
name?: string;
|
|
132
|
-
is_primary?: boolean;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
function getDefaultApiKey(): string | undefined {
|
|
136
|
-
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })
|
|
137
|
-
.process?.env;
|
|
138
|
-
return env?.LETTA_API_KEY ?? env?.LETTA_CLOUD_API_KEY;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function bearerTokenFromHeaders(headers: Record<string, string> | undefined): string | undefined {
|
|
142
|
-
const authorization = headers?.Authorization ?? headers?.authorization;
|
|
143
|
-
if (!authorization) return undefined;
|
|
144
|
-
const match = /^Bearer\s+(.+)$/i.exec(authorization);
|
|
145
|
-
return match?.[1];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function getCloudApiKey(options: LettaCodeCloudClientOptions): string | undefined {
|
|
149
|
-
return options.apiKey ?? bearerTokenFromHeaders(options.headers) ?? getDefaultApiKey();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function getFetch(fetchOverride?: FetchLike): FetchLike {
|
|
153
|
-
const resolved = fetchOverride ?? globalThis.fetch;
|
|
154
|
-
if (!resolved) {
|
|
155
|
-
throw new Error("No fetch implementation available for cloud backend.");
|
|
156
|
-
}
|
|
157
|
-
return resolved.bind(globalThis) as FetchLike;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
135
|
function getWebSocketConstructor(
|
|
161
136
|
websocketOverride?: LettaCodeSocketConstructor,
|
|
162
137
|
): LettaCodeSocketConstructor {
|
|
@@ -169,26 +144,6 @@ function getWebSocketConstructor(
|
|
|
169
144
|
return resolved;
|
|
170
145
|
}
|
|
171
146
|
|
|
172
|
-
function normalizeCloudApiBaseUrl(url: string | undefined): string {
|
|
173
|
-
const parsed = new URL(url ?? DEFAULT_CLOUD_API_BASE_URL);
|
|
174
|
-
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
175
|
-
parsed.search = "";
|
|
176
|
-
parsed.hash = "";
|
|
177
|
-
return parsed.toString().replace(/\/$/, "");
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function cloudHeaders(options: LettaCodeCloudClientOptions): Record<string, string> {
|
|
181
|
-
const headers: Record<string, string> = {
|
|
182
|
-
"Content-Type": "application/json",
|
|
183
|
-
...(options.headers ?? {}),
|
|
184
|
-
};
|
|
185
|
-
const apiKey = getCloudApiKey(options);
|
|
186
|
-
if (apiKey && !headers.Authorization && !headers.authorization) {
|
|
187
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
188
|
-
}
|
|
189
|
-
return headers;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
147
|
function cloudWebSocketHeaders(options: LettaCodeCloudClientOptions): Record<string, string> | undefined {
|
|
193
148
|
const headers = { ...(options.headers ?? {}) };
|
|
194
149
|
delete headers.authorization;
|
|
@@ -198,45 +153,8 @@ function cloudWebSocketHeaders(options: LettaCodeCloudClientOptions): Record<str
|
|
|
198
153
|
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
199
154
|
}
|
|
200
155
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!text) return null;
|
|
204
|
-
try {
|
|
205
|
-
return JSON.parse(text) as unknown;
|
|
206
|
-
} catch {
|
|
207
|
-
return text;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function responseErrorMessage(body: unknown, fallback: string): string {
|
|
212
|
-
if (body && typeof body === "object") {
|
|
213
|
-
const record = body as Record<string, unknown>;
|
|
214
|
-
const message = record.message ?? record.error ?? record.detail;
|
|
215
|
-
const reasonText = record.reason_text;
|
|
216
|
-
const pieces = [message, reasonText]
|
|
217
|
-
.filter((value): value is string => typeof value === "string" && value.length > 0);
|
|
218
|
-
if (pieces.length > 0) return pieces.join(": ");
|
|
219
|
-
}
|
|
220
|
-
return fallback;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function assertOkResponse(response: Response, body: unknown, action: string, url?: string): void {
|
|
224
|
-
if (!response.ok) {
|
|
225
|
-
const detail = responseErrorMessage(body, `HTTP ${response.status}`);
|
|
226
|
-
const parts = [`${action} failed`, detail];
|
|
227
|
-
if (url) parts.push(`URL: ${url}`);
|
|
228
|
-
if (response.status === 401 || response.status === 403) {
|
|
229
|
-
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })
|
|
230
|
-
.process?.env;
|
|
231
|
-
const hasKey = !!(env?.LETTA_API_KEY ?? env?.LETTA_CLOUD_API_KEY);
|
|
232
|
-
parts.push(
|
|
233
|
-
hasKey
|
|
234
|
-
? "Authentication failed — the API key may be invalid or lack permissions for this resource."
|
|
235
|
-
: "No API key found. Set LETTA_API_KEY (or pass apiKey in client options).",
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
throw new Error(parts.join(" — "));
|
|
239
|
-
}
|
|
156
|
+
function isNotFound(error: unknown): boolean {
|
|
157
|
+
return error instanceof APIError && error.status === 404;
|
|
240
158
|
}
|
|
241
159
|
|
|
242
160
|
function validatePositiveInteger(value: number | undefined, name: string): void {
|
|
@@ -357,31 +275,15 @@ function externalToolsByName(tools: AnyAgentTool[] | undefined): Map<string, Any
|
|
|
357
275
|
}
|
|
358
276
|
|
|
359
277
|
export async function createCloudAgent(
|
|
360
|
-
|
|
278
|
+
client: Letta,
|
|
361
279
|
agentOptions: CreateAgentOptions,
|
|
362
280
|
): Promise<string> {
|
|
363
281
|
const body = await createAgentBody(agentOptions);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// the router (GET tolerates the slash; POST does not).
|
|
367
|
-
const response = await getFetch(clientOptions.fetch)(
|
|
368
|
-
`${normalizeCloudApiBaseUrl(clientOptions.apiBaseUrl)}/v1/agents`,
|
|
369
|
-
{
|
|
370
|
-
method: "POST",
|
|
371
|
-
headers: cloudHeaders(clientOptions),
|
|
372
|
-
body: JSON.stringify(body),
|
|
373
|
-
},
|
|
374
|
-
);
|
|
375
|
-
const responseBody = await parseJsonResponse(response);
|
|
376
|
-
assertOkResponse(response, responseBody, "Cloud create agent");
|
|
377
|
-
const agentId =
|
|
378
|
-
responseBody && typeof responseBody === "object"
|
|
379
|
-
? (responseBody as { id?: unknown }).id
|
|
380
|
-
: undefined;
|
|
381
|
-
if (typeof agentId !== "string" || agentId.length === 0) {
|
|
282
|
+
const agent = await client.agents.create(body as AgentCreateParams);
|
|
283
|
+
if (typeof agent.id !== "string" || agent.id.length === 0) {
|
|
382
284
|
throw new Error("Cloud create agent response did not include an agent id.");
|
|
383
285
|
}
|
|
384
|
-
return
|
|
286
|
+
return agent.id;
|
|
385
287
|
}
|
|
386
288
|
|
|
387
289
|
export function assertCloudSessionOptionsSupported(
|
|
@@ -404,24 +306,36 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
404
306
|
private sandboxRefreshInFlight: Promise<void> | null = null;
|
|
405
307
|
private sandboxLifecycleClosing = false;
|
|
406
308
|
private attachedRepositoryIds = new Set<string>();
|
|
309
|
+
private repositoryIdsRequiringCleanupRecompile = new Set<string>();
|
|
310
|
+
private readonly repositoryManagement: CloudManagementTransport;
|
|
407
311
|
private readonly cloudMode: CloudSessionMode;
|
|
408
312
|
|
|
409
313
|
constructor(
|
|
410
314
|
private readonly cloudOptions: LettaCodeCloudClientOptions,
|
|
411
315
|
mode: CloudSessionMode,
|
|
316
|
+
private readonly apiClient: Letta = createCloudClient(cloudOptions),
|
|
412
317
|
) {
|
|
413
318
|
super(mode, {
|
|
414
319
|
label: "cloud",
|
|
415
320
|
requestTimeoutMs: cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS,
|
|
416
321
|
});
|
|
417
322
|
this.cloudMode = mode;
|
|
323
|
+
this.repositoryManagement = new CloudManagementTransport(apiClient);
|
|
418
324
|
const tools = mode.options.tools;
|
|
419
325
|
this.externalTools = externalToolsByName(tools);
|
|
420
326
|
}
|
|
421
327
|
|
|
422
328
|
protected override async initializeRuntimeController(): Promise<RuntimeSessionInit> {
|
|
423
329
|
const resolved = await this.resolveRuntime();
|
|
424
|
-
const connection = await this.resolveConnectionForRuntime(resolved.runtime)
|
|
330
|
+
const connection = await this.resolveConnectionForRuntime(resolved.runtime).catch(
|
|
331
|
+
async (error: unknown) => {
|
|
332
|
+
await this.cleanupSessionRepositories(
|
|
333
|
+
resolved.runtime.agent_id,
|
|
334
|
+
resolved.runtime.conversation_id,
|
|
335
|
+
);
|
|
336
|
+
throw error;
|
|
337
|
+
},
|
|
338
|
+
);
|
|
425
339
|
this.connectionId = connection.connectionId;
|
|
426
340
|
|
|
427
341
|
const apiKey = getCloudApiKey(this.cloudOptions);
|
|
@@ -488,7 +402,10 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
488
402
|
this.removeControlRequestHandler = null;
|
|
489
403
|
client.close();
|
|
490
404
|
await this.cleanupManagedSandbox();
|
|
491
|
-
await this.cleanupSessionRepositories(
|
|
405
|
+
await this.cleanupSessionRepositories(
|
|
406
|
+
resolved.runtime.agent_id,
|
|
407
|
+
resolved.runtime.conversation_id,
|
|
408
|
+
);
|
|
492
409
|
throw error;
|
|
493
410
|
}
|
|
494
411
|
}
|
|
@@ -545,7 +462,12 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
545
462
|
this.removeControlRequestHandler?.();
|
|
546
463
|
this.removeControlRequestHandler = null;
|
|
547
464
|
void this.cleanupManagedSandbox();
|
|
548
|
-
if (this.runtime?.agent_id)
|
|
465
|
+
if (this.runtime?.agent_id) {
|
|
466
|
+
void this.cleanupSessionRepositories(
|
|
467
|
+
this.runtime.agent_id,
|
|
468
|
+
this.runtime.conversation_id,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
549
471
|
}
|
|
550
472
|
|
|
551
473
|
private async resolveRuntime(): Promise<{ runtime: RuntimeScope }> {
|
|
@@ -566,29 +488,41 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
566
488
|
);
|
|
567
489
|
}
|
|
568
490
|
|
|
569
|
-
|
|
491
|
+
const shouldRecompileRepositories =
|
|
492
|
+
await this.attachSessionRepositories(agentId);
|
|
570
493
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
494
|
+
try {
|
|
495
|
+
if (this.cloudMode.newConversation) {
|
|
496
|
+
const conversation = await this.createConversation(agentId);
|
|
497
|
+
conversationId = conversation.id;
|
|
498
|
+
} else if (this.cloudMode.defaultConversation) {
|
|
499
|
+
conversationId = "default";
|
|
500
|
+
}
|
|
577
501
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
502
|
+
if (!conversationId) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
"Letta Cloud createSession()/resumeSession() requires an agent id or conversation id.",
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (shouldRecompileRepositories) {
|
|
509
|
+
await this.recompileSystemPrompt(agentId, conversationId);
|
|
510
|
+
}
|
|
583
511
|
|
|
584
|
-
|
|
512
|
+
return { runtime: { agent_id: agentId, conversation_id: conversationId } };
|
|
513
|
+
} catch (error) {
|
|
514
|
+
await this.cleanupSessionRepositories(agentId, conversationId);
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
585
517
|
}
|
|
586
518
|
|
|
587
|
-
private async attachSessionRepositories(agentId: string): Promise<
|
|
519
|
+
private async attachSessionRepositories(agentId: string): Promise<boolean> {
|
|
588
520
|
const resources = this.repositoryResources();
|
|
589
|
-
if (resources.length === 0) return;
|
|
521
|
+
if (resources.length === 0) return false;
|
|
590
522
|
|
|
591
|
-
const existing = await this.listAgentRepositories(
|
|
523
|
+
const existing = await this.repositoryManagement.listAgentRepositories(
|
|
524
|
+
agentId,
|
|
525
|
+
);
|
|
592
526
|
const existingIds = new Set(existing.map((repository) => repository.id));
|
|
593
527
|
|
|
594
528
|
try {
|
|
@@ -596,7 +530,11 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
596
530
|
if (existingIds.has(resource.repositoryId) || this.attachedRepositoryIds.has(resource.repositoryId)) {
|
|
597
531
|
continue;
|
|
598
532
|
}
|
|
599
|
-
await this.
|
|
533
|
+
await this.repositoryManagement.attachAgentRepository(
|
|
534
|
+
agentId,
|
|
535
|
+
resource.repositoryId,
|
|
536
|
+
undefined,
|
|
537
|
+
);
|
|
600
538
|
await this.waitForAgentRepository(agentId, resource.repositoryId);
|
|
601
539
|
this.attachedRepositoryIds.add(resource.repositoryId);
|
|
602
540
|
}
|
|
@@ -604,18 +542,70 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
604
542
|
await this.cleanupSessionRepositories(agentId);
|
|
605
543
|
throw error;
|
|
606
544
|
}
|
|
545
|
+
|
|
546
|
+
// Recompile even when every requested repository was already linked: the
|
|
547
|
+
// target conversation may still have been compiled before those links
|
|
548
|
+
// existed. One recompile after the full batch produces the desired state.
|
|
549
|
+
const shouldRecompile = resources.some(
|
|
550
|
+
(resource) => resource.recompile !== false,
|
|
551
|
+
);
|
|
552
|
+
this.repositoryIdsRequiringCleanupRecompile = new Set(
|
|
553
|
+
resources
|
|
554
|
+
.filter(
|
|
555
|
+
(resource) =>
|
|
556
|
+
resource.recompile !== false &&
|
|
557
|
+
this.attachedRepositoryIds.has(resource.repositoryId),
|
|
558
|
+
)
|
|
559
|
+
.map((resource) => resource.repositoryId),
|
|
560
|
+
);
|
|
561
|
+
return shouldRecompile;
|
|
607
562
|
}
|
|
608
563
|
|
|
609
|
-
private async cleanupSessionRepositories(
|
|
564
|
+
private async cleanupSessionRepositories(
|
|
565
|
+
agentId: string,
|
|
566
|
+
conversationId?: string,
|
|
567
|
+
): Promise<void> {
|
|
610
568
|
const repositoryIds = [...this.attachedRepositoryIds];
|
|
569
|
+
const repositoryIdsRequiringRecompile =
|
|
570
|
+
this.repositoryIdsRequiringCleanupRecompile;
|
|
611
571
|
this.attachedRepositoryIds.clear();
|
|
612
|
-
|
|
572
|
+
this.repositoryIdsRequiringCleanupRecompile = new Set<string>();
|
|
573
|
+
const recompileAfterDetach = await Promise.all(repositoryIds.map(async (repositoryId) => {
|
|
613
574
|
try {
|
|
614
|
-
await this.
|
|
575
|
+
await this.repositoryManagement.detachAgentRepository(
|
|
576
|
+
agentId,
|
|
577
|
+
repositoryId,
|
|
578
|
+
);
|
|
579
|
+
return repositoryIdsRequiringRecompile.has(repositoryId);
|
|
615
580
|
} catch {
|
|
616
581
|
// Best-effort cleanup: only repositories this SDK session attached are removed.
|
|
582
|
+
return false;
|
|
617
583
|
}
|
|
618
584
|
}));
|
|
585
|
+
// Best-effort recompile after detach so remaining conversations drop stale
|
|
586
|
+
// repository projections. Errors are swallowed because this runs on close.
|
|
587
|
+
if (recompileAfterDetach.some(Boolean)) {
|
|
588
|
+
try {
|
|
589
|
+
await this.recompileSystemPrompt(agentId, conversationId);
|
|
590
|
+
} catch {
|
|
591
|
+
// Best-effort: cleanup must not throw.
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private async recompileSystemPrompt(
|
|
597
|
+
agentId: string,
|
|
598
|
+
conversationId?: string,
|
|
599
|
+
): Promise<void> {
|
|
600
|
+
const usesAgentPrompt = !conversationId || conversationId === "default";
|
|
601
|
+
if (usesAgentPrompt) {
|
|
602
|
+
await this.repositoryManagement.recompileAgentSystemPrompt(agentId);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
await this.repositoryManagement.recompileConversationSystemPrompt(
|
|
606
|
+
agentId,
|
|
607
|
+
conversationId,
|
|
608
|
+
);
|
|
619
609
|
}
|
|
620
610
|
|
|
621
611
|
private repositoryResources(): RepositoryResource[] {
|
|
@@ -636,30 +626,12 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
636
626
|
return result;
|
|
637
627
|
}
|
|
638
628
|
|
|
639
|
-
private async listAgentRepositories(agentId: string): Promise<CloudAgentRepository[]> {
|
|
640
|
-
const fetchImpl = getFetch(this.cloudOptions.fetch);
|
|
641
|
-
const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
|
|
642
|
-
const response = await fetchImpl(
|
|
643
|
-
`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/repositories`,
|
|
644
|
-
{ headers: cloudHeaders(this.cloudOptions) },
|
|
645
|
-
);
|
|
646
|
-
const body = await parseJsonResponse(response);
|
|
647
|
-
assertOkResponse(response, body, "Cloud list agent repositories");
|
|
648
|
-
if (!body || typeof body !== "object") return [];
|
|
649
|
-
const repositories = (body as Record<string, unknown>).repositories;
|
|
650
|
-
return Array.isArray(repositories)
|
|
651
|
-
? repositories.filter((repository): repository is CloudAgentRepository => (
|
|
652
|
-
repository !== null &&
|
|
653
|
-
typeof repository === "object" &&
|
|
654
|
-
typeof (repository as Record<string, unknown>).id === "string"
|
|
655
|
-
))
|
|
656
|
-
: [];
|
|
657
|
-
}
|
|
658
|
-
|
|
659
629
|
private async waitForAgentRepository(agentId: string, repositoryId: string): Promise<void> {
|
|
660
630
|
const deadline = Date.now() + DEFAULT_REPOSITORY_ATTACH_TIMEOUT_MS;
|
|
661
631
|
while (true) {
|
|
662
|
-
const repositories = await this.listAgentRepositories(
|
|
632
|
+
const repositories = await this.repositoryManagement.listAgentRepositories(
|
|
633
|
+
agentId,
|
|
634
|
+
);
|
|
663
635
|
if (repositories.some((repository) => repository.id === repositoryId)) return;
|
|
664
636
|
if (Date.now() >= deadline) {
|
|
665
637
|
throw new Error(`Cloud attach agent repository did not become visible for ${agentId}: ${repositoryId}`);
|
|
@@ -668,64 +640,24 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
668
640
|
}
|
|
669
641
|
}
|
|
670
642
|
|
|
671
|
-
private async linkAgentRepository(agentId: string, repositoryId: string): Promise<void> {
|
|
672
|
-
const fetchImpl = getFetch(this.cloudOptions.fetch);
|
|
673
|
-
const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
|
|
674
|
-
const response = await fetchImpl(
|
|
675
|
-
`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/repositories`,
|
|
676
|
-
{
|
|
677
|
-
method: "POST",
|
|
678
|
-
headers: cloudHeaders(this.cloudOptions),
|
|
679
|
-
body: JSON.stringify({ repository_id: repositoryId }),
|
|
680
|
-
},
|
|
681
|
-
);
|
|
682
|
-
const body = await parseJsonResponse(response);
|
|
683
|
-
assertOkResponse(response, body, "Cloud attach agent repository");
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
private async unlinkAgentRepository(agentId: string, repositoryId: string): Promise<void> {
|
|
687
|
-
const fetchImpl = getFetch(this.cloudOptions.fetch);
|
|
688
|
-
const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
|
|
689
|
-
const response = await fetchImpl(
|
|
690
|
-
`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/repositories/${encodeURIComponent(repositoryId)}`,
|
|
691
|
-
{ method: "DELETE", headers: cloudHeaders(this.cloudOptions) },
|
|
692
|
-
);
|
|
693
|
-
const body = await parseJsonResponse(response);
|
|
694
|
-
if (response.status === 404) return;
|
|
695
|
-
assertOkResponse(response, body, "Cloud detach agent repository");
|
|
696
|
-
}
|
|
697
|
-
|
|
698
643
|
private async createConversation(agentId: string): Promise<{ id: string; agent_id?: string }> {
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
const url = new URL(`${baseUrl}/v1/conversations/`);
|
|
702
|
-
url.searchParams.set("agent_id", agentId);
|
|
703
|
-
const response = await fetchImpl(url, {
|
|
704
|
-
method: "POST",
|
|
705
|
-
headers: cloudHeaders(this.cloudOptions),
|
|
706
|
-
body: JSON.stringify({}),
|
|
644
|
+
const conversation = await this.apiClient.conversations.create({
|
|
645
|
+
agent_id: agentId,
|
|
707
646
|
});
|
|
708
|
-
|
|
709
|
-
assertOkResponse(response, body, "Cloud createSession()", url.toString());
|
|
710
|
-
if (!isCloudConversation(body)) {
|
|
647
|
+
if (!isCloudConversation(conversation)) {
|
|
711
648
|
throw new Error("Cloud createSession() response did not include a conversation id.");
|
|
712
649
|
}
|
|
713
|
-
return { id:
|
|
650
|
+
return { id: conversation.id, agent_id: conversation.agent_id };
|
|
714
651
|
}
|
|
715
652
|
|
|
716
653
|
private async retrieveConversation(conversationId: string): Promise<{ id: string; agent_id?: string }> {
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
const response = await fetchImpl(
|
|
720
|
-
`${baseUrl}/v1/conversations/${encodeURIComponent(conversationId)}`,
|
|
721
|
-
{ headers: cloudHeaders(this.cloudOptions) },
|
|
654
|
+
const conversation = await this.apiClient.conversations.retrieve(
|
|
655
|
+
conversationId,
|
|
722
656
|
);
|
|
723
|
-
|
|
724
|
-
assertOkResponse(response, body, "Cloud resumeSession()", `${baseUrl}/v1/conversations/${encodeURIComponent(conversationId)}`);
|
|
725
|
-
if (!isCloudConversation(body)) {
|
|
657
|
+
if (!isCloudConversation(conversation)) {
|
|
726
658
|
throw new Error(`Cloud resumeSession() could not retrieve conversation ${conversationId}.`);
|
|
727
659
|
}
|
|
728
|
-
return { id:
|
|
660
|
+
return { id: conversation.id, agent_id: conversation.agent_id };
|
|
729
661
|
}
|
|
730
662
|
|
|
731
663
|
private async resolveConnectionForRuntime(
|
|
@@ -787,25 +719,19 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
787
719
|
agentId: string,
|
|
788
720
|
conversationId?: string,
|
|
789
721
|
): Promise<ManagedCloudSandbox> {
|
|
790
|
-
const fetchImpl = getFetch(this.cloudOptions.fetch);
|
|
791
|
-
const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
|
|
792
722
|
const sandboxOptions = this.resolvedSandboxOptions();
|
|
793
723
|
const githubRepositories = sandboxOptions.githubRepositories;
|
|
794
|
-
const
|
|
795
|
-
|
|
724
|
+
const body = await this.apiClient.post<unknown>(
|
|
725
|
+
`/v1/agents/${encodeURIComponent(agentId)}/sandboxes`,
|
|
796
726
|
{
|
|
797
|
-
|
|
798
|
-
headers: cloudHeaders(this.cloudOptions),
|
|
799
|
-
body: JSON.stringify({
|
|
727
|
+
body: {
|
|
800
728
|
...(conversationId ? { conversationId } : {}),
|
|
801
729
|
...(githubRepositories && githubRepositories.length > 0
|
|
802
730
|
? { githubRepositories }
|
|
803
731
|
: {}),
|
|
804
|
-
}
|
|
732
|
+
},
|
|
805
733
|
},
|
|
806
734
|
);
|
|
807
|
-
const body = await parseJsonResponse(response);
|
|
808
|
-
assertOkResponse(response, body, "Cloud create managed sandbox", `${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sandboxes`);
|
|
809
735
|
if (!isCloudAgentSandbox(body)) {
|
|
810
736
|
throw new Error("Cloud create managed sandbox response did not include sandbox connection details.");
|
|
811
737
|
}
|
|
@@ -863,22 +789,17 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
863
789
|
}
|
|
864
790
|
|
|
865
791
|
private async refreshManagedSandboxOnce(sandbox: ManagedCloudSandbox): Promise<void> {
|
|
866
|
-
const fetchImpl = getFetch(this.cloudOptions.fetch);
|
|
867
|
-
const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
|
|
868
|
-
|
|
869
792
|
if (sandbox.conversationId) {
|
|
870
793
|
// Conversation-scoped sandboxes refresh by id — no "latest active"
|
|
871
794
|
// indirection, so ownership changes are structurally impossible.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if (response.status === 404) {
|
|
881
|
-
await parseJsonResponse(response);
|
|
795
|
+
let body: unknown;
|
|
796
|
+
try {
|
|
797
|
+
body = await this.apiClient.post(
|
|
798
|
+
`/v1/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/refresh`,
|
|
799
|
+
{ body: { ttlMinutes: sandbox.ttlMinutes } },
|
|
800
|
+
);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
if (!isNotFound(error)) throw error;
|
|
882
803
|
// The current control and stream WebSockets are bound to this
|
|
883
804
|
// sandbox's ephemeral connection id. Recreating only the sandbox would
|
|
884
805
|
// leave both sockets targeting the dead connection, so surface a
|
|
@@ -889,24 +810,18 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
889
810
|
sandbox.conversationId,
|
|
890
811
|
);
|
|
891
812
|
}
|
|
892
|
-
const body = await parseJsonResponse(response);
|
|
893
|
-
assertOkResponse(response, body, "Cloud refresh managed sandbox");
|
|
894
813
|
if (!isCloudAgentSandboxRefresh(body) || !body.success) {
|
|
895
814
|
throw new Error("Cloud refresh managed sandbox response did not confirm refresh.");
|
|
896
815
|
}
|
|
897
816
|
return;
|
|
898
817
|
}
|
|
899
818
|
|
|
900
|
-
const
|
|
901
|
-
|
|
819
|
+
const body = await this.apiClient.post<unknown>(
|
|
820
|
+
`/v1/agents/${encodeURIComponent(sandbox.agentId)}/sandboxes/refresh`,
|
|
902
821
|
{
|
|
903
|
-
|
|
904
|
-
headers: cloudHeaders(this.cloudOptions),
|
|
905
|
-
body: JSON.stringify({ ttlMinutes: sandbox.ttlMinutes }),
|
|
822
|
+
body: { ttlMinutes: sandbox.ttlMinutes },
|
|
906
823
|
},
|
|
907
824
|
);
|
|
908
|
-
const body = await parseJsonResponse(response);
|
|
909
|
-
assertOkResponse(response, body, "Cloud refresh managed sandbox", `${baseUrl}/v1/agents/${encodeURIComponent(sandbox.agentId)}/sandboxes/refresh`);
|
|
910
825
|
if (!isCloudAgentSandboxRefresh(body) || !body.success) {
|
|
911
826
|
throw new Error("Cloud refresh managed sandbox response did not confirm refresh.");
|
|
912
827
|
}
|
|
@@ -918,23 +833,17 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
918
833
|
}
|
|
919
834
|
|
|
920
835
|
private async terminateManagedSandbox(sandbox: ManagedCloudSandbox): Promise<void> {
|
|
921
|
-
const fetchImpl = getFetch(this.cloudOptions.fetch);
|
|
922
|
-
const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
|
|
923
|
-
|
|
924
836
|
if (sandbox.conversationId) {
|
|
925
837
|
// Terminate exactly this sandbox — the agent-scoped DELETE targets the
|
|
926
838
|
// agent's "latest active" sandbox, which may not be ours.
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const body = await parseJsonResponse(response);
|
|
936
|
-
if (response.status === 404) return;
|
|
937
|
-
assertOkResponse(response, body, "Cloud terminate managed sandbox");
|
|
839
|
+
try {
|
|
840
|
+
await this.apiClient.post(
|
|
841
|
+
`/v1/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/terminate`,
|
|
842
|
+
{ body: {} },
|
|
843
|
+
);
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (!isNotFound(error)) throw error;
|
|
846
|
+
}
|
|
938
847
|
return;
|
|
939
848
|
}
|
|
940
849
|
|
|
@@ -942,16 +851,13 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
942
851
|
// "latest active" sandbox the DELETE will target is still ours.
|
|
943
852
|
await this.refreshManagedSandbox(sandbox);
|
|
944
853
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
const body = await parseJsonResponse(response);
|
|
953
|
-
if (response.status === 404) return;
|
|
954
|
-
assertOkResponse(response, body, "Cloud terminate managed sandbox", `${baseUrl}/v1/agents/${encodeURIComponent(sandbox.agentId)}/sandboxes`);
|
|
854
|
+
try {
|
|
855
|
+
await this.apiClient.delete(
|
|
856
|
+
`/v1/agents/${encodeURIComponent(sandbox.agentId)}/sandboxes`,
|
|
857
|
+
);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
if (!isNotFound(error)) throw error;
|
|
860
|
+
}
|
|
955
861
|
}
|
|
956
862
|
|
|
957
863
|
private async waitForManagedSandboxConnection(
|
|
@@ -1027,12 +933,7 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
1027
933
|
}
|
|
1028
934
|
|
|
1029
935
|
private remoteEnvironmentClient(): RemoteEnvironmentClient {
|
|
1030
|
-
return new RemoteEnvironmentClient({
|
|
1031
|
-
baseUrl: this.cloudOptions.apiBaseUrl,
|
|
1032
|
-
apiKey: getCloudApiKey(this.cloudOptions),
|
|
1033
|
-
headers: this.cloudOptions.headers,
|
|
1034
|
-
fetch: this.cloudOptions.fetch,
|
|
1035
|
-
});
|
|
936
|
+
return new RemoteEnvironmentClient({}, this.apiClient);
|
|
1036
937
|
}
|
|
1037
938
|
|
|
1038
939
|
private effectiveEnvironment(): LettaCodeEnvironment | undefined {
|
package/src/index.ts
CHANGED
|
@@ -145,6 +145,10 @@ export type {
|
|
|
145
145
|
AnyAgentTool,
|
|
146
146
|
} from "./types.js";
|
|
147
147
|
export type {
|
|
148
|
+
AgentRepositoriesClient,
|
|
149
|
+
AgentRepository,
|
|
150
|
+
AgentRepositoryRecompileTarget,
|
|
151
|
+
AttachAgentRepositoryOptions,
|
|
148
152
|
AgentsClient,
|
|
149
153
|
ConversationsClient,
|
|
150
154
|
LettaAgent,
|
|
@@ -158,6 +162,8 @@ export type {
|
|
|
158
162
|
UpdateConversationOptions,
|
|
159
163
|
ConversationMessagesOptions,
|
|
160
164
|
ConversationMessagesResult,
|
|
165
|
+
DetachAgentRepositoryOptions,
|
|
166
|
+
AgentRepositoryPermissions,
|
|
161
167
|
} from "./management-types.js";
|
|
162
168
|
|
|
163
169
|
export { RepositoriesClient } from "./repositories.js";
|