@letta-ai/letta-agent-sdk 0.2.1

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 (39) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +240 -0
  3. package/dist/app-server-session.d.ts +86 -0
  4. package/dist/app-server-session.d.ts.map +1 -0
  5. package/dist/cli-resolver.d.ts +9 -0
  6. package/dist/cli-resolver.d.ts.map +1 -0
  7. package/dist/client.d.ts +47 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/cloud-session.d.ts +45 -0
  10. package/dist/cloud-session.d.ts.map +1 -0
  11. package/dist/index.d.ts +167 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +4994 -0
  14. package/dist/index.js.map +24 -0
  15. package/dist/interactiveToolPolicy.d.ts +4 -0
  16. package/dist/interactiveToolPolicy.d.ts.map +1 -0
  17. package/dist/local-app-server.d.ts +17 -0
  18. package/dist/local-app-server.d.ts.map +1 -0
  19. package/dist/protocol.d.ts +205 -0
  20. package/dist/protocol.d.ts.map +1 -0
  21. package/dist/remote-client-session-core.d.ts +140 -0
  22. package/dist/remote-client-session-core.d.ts.map +1 -0
  23. package/dist/remote.d.ts +57 -0
  24. package/dist/remote.d.ts.map +1 -0
  25. package/dist/session.d.ts +142 -0
  26. package/dist/session.d.ts.map +1 -0
  27. package/dist/stream-events.d.ts +15 -0
  28. package/dist/stream-events.d.ts.map +1 -0
  29. package/dist/tests/advanced-session.d.ts +10 -0
  30. package/dist/tests/advanced-session.d.ts.map +1 -0
  31. package/dist/tool-helpers.d.ts +47 -0
  32. package/dist/tool-helpers.d.ts.map +1 -0
  33. package/dist/transport.d.ts +53 -0
  34. package/dist/transport.d.ts.map +1 -0
  35. package/dist/types.d.ts +759 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/validation.d.ts +15 -0
  38. package/dist/validation.d.ts.map +1 -0
  39. package/package.json +45 -0
package/dist/index.js ADDED
@@ -0,0 +1,4994 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/transport.ts
21
+ import { spawn } from "node:child_process";
22
+ import { createInterface } from "node:readline";
23
+ function sdkLog(tag, ...args) {
24
+ if (process.env.DEBUG_SDK)
25
+ console.error(`[SDK-Transport] [${tag}]`, ...args);
26
+ }
27
+ var SDK_AGENT_ORIGIN_TAG = "origin:letta-code";
28
+ function shouldApplyNewAgentDefaults(options) {
29
+ return options.createOnly === true;
30
+ }
31
+ function includeSdkAgentOriginTag(tags) {
32
+ const normalizedTags = [];
33
+ let hasOriginTag = false;
34
+ for (const tag of tags ?? []) {
35
+ if (tag === SDK_AGENT_ORIGIN_TAG) {
36
+ if (hasOriginTag)
37
+ continue;
38
+ hasOriginTag = true;
39
+ }
40
+ normalizedTags.push(tag);
41
+ }
42
+ if (!hasOriginTag) {
43
+ normalizedTags.push(SDK_AGENT_ORIGIN_TAG);
44
+ }
45
+ return normalizedTags;
46
+ }
47
+ function buildCliArgs(options) {
48
+ const args = [
49
+ "--output-format",
50
+ "stream-json",
51
+ "--input-format",
52
+ "stream-json"
53
+ ];
54
+ const applyNewAgentDefaults = shouldApplyNewAgentDefaults(options);
55
+ if (options.conversationId) {
56
+ args.push("--conversation", options.conversationId);
57
+ } else if (options.agentId) {
58
+ args.push("--agent", options.agentId);
59
+ if (options.newConversation) {
60
+ args.push("--new");
61
+ } else if (options.defaultConversation) {
62
+ args.push("--conversation", "default");
63
+ }
64
+ } else if (options.createOnly) {
65
+ args.push("--new-agent");
66
+ } else if (options.newConversation) {
67
+ args.push("--new");
68
+ }
69
+ if (options.model) {
70
+ args.push("-m", options.model);
71
+ }
72
+ if (options.includePartialMessages) {
73
+ args.push("--include-partial-messages");
74
+ }
75
+ if (options.embedding) {
76
+ args.push("--embedding", options.embedding);
77
+ }
78
+ if (options.systemPrompt !== undefined) {
79
+ if (typeof options.systemPrompt === "string") {
80
+ const validPresets = [
81
+ "default",
82
+ "letta-claude",
83
+ "letta-codex",
84
+ "letta-gemini",
85
+ "claude",
86
+ "codex",
87
+ "gemini"
88
+ ];
89
+ if (validPresets.includes(options.systemPrompt)) {
90
+ args.push("--system", options.systemPrompt);
91
+ } else {
92
+ args.push("--system-custom", options.systemPrompt);
93
+ }
94
+ } else {
95
+ args.push("--system", options.systemPrompt.preset);
96
+ if (options.systemPrompt.append) {
97
+ args.push("--system-append", options.systemPrompt.append);
98
+ }
99
+ }
100
+ }
101
+ if (options.memory !== undefined && !options.agentId) {
102
+ if (options.memory.length === 0) {
103
+ args.push("--init-blocks", "");
104
+ } else {
105
+ const presetNames = [];
106
+ const memoryBlocksJson = [];
107
+ for (const item of options.memory) {
108
+ if (typeof item === "string") {
109
+ presetNames.push(item);
110
+ } else if ("blockId" in item) {
111
+ memoryBlocksJson.push(item);
112
+ } else {
113
+ memoryBlocksJson.push(item);
114
+ }
115
+ }
116
+ if (memoryBlocksJson.length > 0) {
117
+ args.push("--memory-blocks", JSON.stringify(memoryBlocksJson));
118
+ if (presetNames.length > 0) {
119
+ console.warn("[letta-agent-sdk] Using custom memory blocks. " + `Preset blocks are ignored when custom blocks are provided: ${presetNames.join(", ")}`);
120
+ }
121
+ } else if (presetNames.length > 0) {
122
+ args.push("--init-blocks", presetNames.join(","));
123
+ }
124
+ }
125
+ }
126
+ if (!options.agentId) {
127
+ if (options.persona !== undefined) {
128
+ args.push("--block-value", `persona=${options.persona}`);
129
+ }
130
+ if (options.human !== undefined) {
131
+ args.push("--block-value", `human=${options.human}`);
132
+ }
133
+ }
134
+ if (options.permissionMode === "bypassPermissions") {
135
+ args.push("--yolo");
136
+ } else if (options.permissionMode && options.permissionMode !== "default") {
137
+ args.push("--permission-mode", options.permissionMode);
138
+ }
139
+ if (options.allowedTools) {
140
+ args.push("--allowedTools", options.allowedTools.join(","));
141
+ }
142
+ if (options.disallowedTools) {
143
+ args.push("--disallowedTools", options.disallowedTools.join(","));
144
+ }
145
+ const tags = applyNewAgentDefaults ? includeSdkAgentOriginTag(options.tags) : options.tags;
146
+ if (tags && tags.length > 0) {
147
+ args.push("--tags", tags.join(","));
148
+ }
149
+ if (applyNewAgentDefaults) {
150
+ args.push("--memfs");
151
+ }
152
+ if (options.skillSources !== undefined) {
153
+ const sources = [...new Set(options.skillSources)];
154
+ if (sources.length === 0) {
155
+ args.push("--no-skills");
156
+ } else {
157
+ args.push("--skill-sources", sources.join(","));
158
+ }
159
+ }
160
+ if (options.systemInfoReminder === false) {
161
+ args.push("--no-system-info-reminder");
162
+ }
163
+ if (options.dreaming?.trigger !== undefined) {
164
+ args.push("--reflection-trigger", options.dreaming.trigger);
165
+ }
166
+ if (options.dreaming?.behavior !== undefined) {
167
+ args.push("--reflection-behavior", options.dreaming.behavior);
168
+ }
169
+ if (options.dreaming?.stepCount !== undefined) {
170
+ args.push("--reflection-step-count", String(options.dreaming.stepCount));
171
+ }
172
+ return args;
173
+ }
174
+
175
+ class SubprocessTransport {
176
+ options;
177
+ process = null;
178
+ stdout = null;
179
+ messageQueue = [];
180
+ messageResolvers = [];
181
+ closed = false;
182
+ agentId;
183
+ wireMessageCount = 0;
184
+ lastMessageAt = 0;
185
+ stderrLines = [];
186
+ constructor(options = {}) {
187
+ this.options = options;
188
+ }
189
+ async connect() {
190
+ const args = this.buildArgs();
191
+ const cliPath = await this.findCli();
192
+ sdkLog("connect", `CLI: ${cliPath}`);
193
+ sdkLog("connect", `args: ${args.join(" ")}`);
194
+ sdkLog("connect", `cwd: ${this.options.cwd || process.cwd()}`);
195
+ sdkLog("connect", `permissionMode: ${this.options.permissionMode || "default"}`);
196
+ this.process = spawn("node", [cliPath, ...args], {
197
+ cwd: this.options.cwd || process.cwd(),
198
+ stdio: ["pipe", "pipe", "pipe"],
199
+ env: { ...process.env }
200
+ });
201
+ const pid = this.process.pid;
202
+ sdkLog("connect", `CLI process spawned, pid=${pid}`);
203
+ if (!this.process.stdout || !this.process.stdin) {
204
+ throw new Error("Failed to create subprocess pipes");
205
+ }
206
+ this.stdout = createInterface({
207
+ input: this.process.stdout,
208
+ crlfDelay: Infinity
209
+ });
210
+ this.stdout.on("line", (line) => {
211
+ if (!line.trim())
212
+ return;
213
+ try {
214
+ const msg = JSON.parse(line);
215
+ this.handleMessage(msg);
216
+ } catch {
217
+ sdkLog("stdout", `[non-JSON] ${line.slice(0, 500)}`);
218
+ }
219
+ });
220
+ if (this.process.stderr) {
221
+ this.process.stderr.on("data", (data) => {
222
+ const msg = data.toString().trim();
223
+ if (msg) {
224
+ console.error("[letta-agent-sdk] CLI stderr:", msg);
225
+ this.stderrLines.push(msg);
226
+ }
227
+ });
228
+ }
229
+ this.process.on("close", (code, signal) => {
230
+ if (code !== 0 && code !== null) {
231
+ console.error(`[letta-agent-sdk] CLI process exited with code ${code}`);
232
+ }
233
+ sdkLog("close", `CLI process exited: pid=${pid} code=${code} signal=${signal} wireMessages=${this.wireMessageCount} msSinceLastMsg=${this.lastMessageAt ? Date.now() - this.lastMessageAt : 0} pendingResolvers=${this.messageResolvers.length} queueLen=${this.messageQueue.length}`);
234
+ this.closed = true;
235
+ for (const resolve of this.messageResolvers) {
236
+ resolve(null);
237
+ }
238
+ this.messageResolvers = [];
239
+ });
240
+ this.process.on("error", (err) => {
241
+ console.error("[letta-agent-sdk] CLI process error:", err);
242
+ this.closed = true;
243
+ });
244
+ }
245
+ async write(data) {
246
+ if (!this.process?.stdin || this.closed) {
247
+ const err = new Error(`Transport not connected (closed=${this.closed}, pid=${this.process?.pid}, stdin=${!!this.process?.stdin})`);
248
+ sdkLog("write", err.message);
249
+ throw err;
250
+ }
251
+ const payload = data;
252
+ sdkLog("write", `type=${payload.type} subtype=${payload.request?.subtype || payload.response?.subtype || "N/A"}`);
253
+ this.process.stdin.write(JSON.stringify(data) + `
254
+ `);
255
+ }
256
+ async read() {
257
+ if (this.messageQueue.length > 0) {
258
+ return this.messageQueue.shift();
259
+ }
260
+ if (this.closed) {
261
+ sdkLog("read", `returning null (closed), total wireMessages=${this.wireMessageCount}`);
262
+ return null;
263
+ }
264
+ sdkLog("read", `waiting for next message (resolvers=${this.messageResolvers.length + 1}, queue=${this.messageQueue.length})`);
265
+ return new Promise((resolve) => {
266
+ this.messageResolvers.push(resolve);
267
+ });
268
+ }
269
+ async* messages() {
270
+ while (true) {
271
+ const msg = await this.read();
272
+ if (msg === null) {
273
+ sdkLog("messages", `iterator ending (closed=${this.closed}, wireMessages=${this.wireMessageCount})`);
274
+ break;
275
+ }
276
+ yield msg;
277
+ }
278
+ }
279
+ close() {
280
+ sdkLog("close", `explicit close called (wireMessages=${this.wireMessageCount}, pendingResolvers=${this.messageResolvers.length}, pid=${this.process?.pid})`);
281
+ if (this.process) {
282
+ this.process.stdin?.end();
283
+ this.process.kill();
284
+ this.process = null;
285
+ }
286
+ this.closed = true;
287
+ for (const resolve of this.messageResolvers) {
288
+ resolve(null);
289
+ }
290
+ this.messageResolvers = [];
291
+ }
292
+ get isClosed() {
293
+ return this.closed;
294
+ }
295
+ getStderr() {
296
+ return this.stderrLines.join(`
297
+ `);
298
+ }
299
+ handleMessage(msg) {
300
+ this.wireMessageCount++;
301
+ this.lastMessageAt = Date.now();
302
+ const wirePayload = msg;
303
+ const msgType = wirePayload.message_type || wirePayload.subtype || "";
304
+ sdkLog("wire", `#${this.wireMessageCount} type=${msg.type} ${msgType ? `msg_type=${msgType}` : ""} resolvers=${this.messageResolvers.length} queue=${this.messageQueue.length}`);
305
+ if (msg.type === "result") {
306
+ const result = wirePayload;
307
+ sdkLog("wire", `RESULT: subtype=${result.subtype} stop_reason=${result.stop_reason || "N/A"} duration=${result.duration_ms}ms resultLen=${result.result?.length || 0}`);
308
+ }
309
+ if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
310
+ this.agentId = msg.agent_id;
311
+ sdkLog("wire", `INIT: agent_id=${this.agentId}`);
312
+ }
313
+ if (msg.type === "control_request") {
314
+ const req = wirePayload;
315
+ sdkLog("wire", `CONTROL_REQUEST: id=${req.request_id} subtype=${req.request?.subtype} tool=${req.request?.tool_name || "N/A"}`);
316
+ }
317
+ if (this.messageResolvers.length > 0) {
318
+ const resolve = this.messageResolvers.shift();
319
+ resolve(msg);
320
+ } else {
321
+ this.messageQueue.push(msg);
322
+ }
323
+ }
324
+ buildArgs() {
325
+ return buildCliArgs(this.options);
326
+ }
327
+ async findCli() {
328
+ const { existsSync } = await import("node:fs");
329
+ const { dirname, join } = await import("node:path");
330
+ const { fileURLToPath } = await import("node:url");
331
+ if (process.env.LETTA_CLI_PATH && existsSync(process.env.LETTA_CLI_PATH)) {
332
+ return process.env.LETTA_CLI_PATH;
333
+ }
334
+ try {
335
+ const { createRequire: createRequire2 } = await import("node:module");
336
+ const require2 = createRequire2(import.meta.url);
337
+ const resolved = require2.resolve("@letta-ai/letta-code");
338
+ if (existsSync(resolved)) {
339
+ return resolved;
340
+ }
341
+ } catch {}
342
+ const __filename2 = fileURLToPath(import.meta.url);
343
+ const __dirname2 = dirname(__filename2);
344
+ const localPaths = [
345
+ join(__dirname2, "../../@letta-ai/letta-code/letta.js"),
346
+ join(__dirname2, "../../../letta-code-prod/letta.js"),
347
+ join(__dirname2, "../../../letta-code/letta.js")
348
+ ];
349
+ for (const p of localPaths) {
350
+ if (existsSync(p)) {
351
+ return p;
352
+ }
353
+ }
354
+ throw new Error("Letta Code CLI not found. Set LETTA_CLI_PATH or install @letta-ai/letta-code.");
355
+ }
356
+ }
357
+
358
+ // src/interactiveToolPolicy.ts
359
+ var INTERACTIVE_APPROVAL_TOOLS = new Set([
360
+ "AskUserQuestion",
361
+ "EnterPlanMode",
362
+ "ExitPlanMode"
363
+ ]);
364
+ var RUNTIME_USER_INPUT_TOOLS = new Set(["AskUserQuestion", "ExitPlanMode"]);
365
+ var HEADLESS_AUTO_ALLOW_TOOLS = new Set(["EnterPlanMode"]);
366
+ function requiresRuntimeUserInput(toolName) {
367
+ return RUNTIME_USER_INPUT_TOOLS.has(toolName);
368
+ }
369
+ function isHeadlessAutoAllowTool(toolName) {
370
+ return HEADLESS_AUTO_ALLOW_TOOLS.has(toolName);
371
+ }
372
+
373
+ // src/session.ts
374
+ function sessionLog(tag, ...args) {
375
+ if (process.env.DEBUG_SDK)
376
+ console.error(`[SDK-Session] [${tag}]`, ...args);
377
+ }
378
+ var MAX_BUFFERED_STREAM_MESSAGES = 100;
379
+ var DEFAULT_MAX_APPROVAL_RECOVERY_ATTEMPTS = 1;
380
+ var DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS = 5000;
381
+ var KNOWN_SDK_ERROR_CODES = new Set([
382
+ "approval_conflict",
383
+ "approval_conflict_terminal",
384
+ "protocol_error",
385
+ "error",
386
+ "llm_api_error",
387
+ "max_steps",
388
+ "interrupted",
389
+ "stream_closed"
390
+ ]);
391
+ function isKnownSdkErrorCode(value) {
392
+ return KNOWN_SDK_ERROR_CODES.has(value);
393
+ }
394
+ function toSdkErrorCode(value) {
395
+ if (!value || value.length === 0)
396
+ return;
397
+ return isKnownSdkErrorCode(value) ? value : undefined;
398
+ }
399
+ function isUnknownControlSubtypeError(errorMessage, subtype) {
400
+ if (!errorMessage)
401
+ return false;
402
+ return errorMessage.toLowerCase().includes(`unknown control request subtype: ${subtype}`.toLowerCase());
403
+ }
404
+ function extractApiErrorDetail(apiError) {
405
+ const detail = apiError?.detail;
406
+ if (typeof detail !== "string")
407
+ return;
408
+ const trimmed = detail.trim();
409
+ return trimmed.length > 0 ? trimmed : undefined;
410
+ }
411
+ function isApprovalConflictSignal(params) {
412
+ if (params.stopReason === "requires_approval")
413
+ return true;
414
+ const haystack = [params.detail, params.message].filter((value) => typeof value === "string" && value.length > 0).join(`
415
+ `).toLowerCase();
416
+ if (!haystack)
417
+ return false;
418
+ return haystack.includes("waiting for approval on a tool call") || haystack.includes("cannot send a new message") || haystack.includes("requires_approval");
419
+ }
420
+
421
+ class Session {
422
+ options;
423
+ transport;
424
+ _agentId = null;
425
+ _sessionId = null;
426
+ _conversationId = null;
427
+ initialized = false;
428
+ externalTools = new Map;
429
+ streamQueue = [];
430
+ streamResolvers = [];
431
+ pumpPromise = null;
432
+ pumpClosed = false;
433
+ droppedStreamMessages = 0;
434
+ sendGeneration = 0;
435
+ lastCompletedRunIds = new Set;
436
+ controlResponseWaiters = new Map;
437
+ constructor(options = {}) {
438
+ this.options = options;
439
+ if (options.reasoningEffort !== undefined) {
440
+ throw new Error("reasoningEffort is not supported by this session. Create a Cloud, Remote, or local agent session before setting reasoning effort.");
441
+ }
442
+ this.transport = new SubprocessTransport(options);
443
+ if (options.tools) {
444
+ for (const tool of options.tools) {
445
+ this.externalTools.set(tool.name, tool);
446
+ }
447
+ }
448
+ }
449
+ async initialize() {
450
+ if (this.initialized) {
451
+ throw new Error("Session already initialized");
452
+ }
453
+ sessionLog("init", "connecting transport...");
454
+ await this.transport.connect();
455
+ sessionLog("init", "transport connected, sending initialize request");
456
+ await this.transport.write({
457
+ type: "control_request",
458
+ request_id: "init_1",
459
+ request: { subtype: "initialize" }
460
+ });
461
+ sessionLog("init", "waiting for init message from CLI...");
462
+ for await (const msg of this.transport.messages()) {
463
+ sessionLog("init", `received wire message: type=${msg.type}`);
464
+ if (msg.type === "control_request") {
465
+ const handled = await this.handleControlRequest(msg);
466
+ if (!handled) {
467
+ const wireMsgAny = msg;
468
+ sessionLog("init", `DROPPED unsupported control_request: subtype=${wireMsgAny.request?.subtype || "N/A"}`);
469
+ }
470
+ continue;
471
+ }
472
+ if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
473
+ const initMsg = msg;
474
+ this._agentId = initMsg.agent_id;
475
+ this._sessionId = initMsg.session_id;
476
+ this._conversationId = initMsg.conversation_id;
477
+ this.initialized = true;
478
+ this.startBackgroundPump();
479
+ if (this.externalTools.size > 0) {
480
+ await this.registerExternalTools();
481
+ }
482
+ const allTools = [
483
+ ...initMsg.tools,
484
+ ...Array.from(this.externalTools.keys())
485
+ ];
486
+ sessionLog("init", `initialized: agent=${initMsg.agent_id} conversation=${initMsg.conversation_id} model=${initMsg.model} tools=${allTools.length} (${this.externalTools.size} external)`);
487
+ return {
488
+ type: "init",
489
+ agentId: initMsg.agent_id,
490
+ sessionId: initMsg.session_id,
491
+ conversationId: initMsg.conversation_id,
492
+ model: initMsg.model,
493
+ tools: allTools,
494
+ memfsEnabled: initMsg.memfs_enabled,
495
+ skillSources: initMsg.skill_sources,
496
+ systemInfoReminderEnabled: initMsg.system_info_reminder_enabled,
497
+ dreaming: initMsg.reflection_trigger && initMsg.reflection_behavior && typeof initMsg.reflection_step_count === "number" ? {
498
+ trigger: initMsg.reflection_trigger,
499
+ behavior: initMsg.reflection_behavior,
500
+ stepCount: initMsg.reflection_step_count
501
+ } : undefined
502
+ };
503
+ }
504
+ }
505
+ const stderr = this.transport.getStderr();
506
+ const detail = stderr ? `
507
+ CLI stderr:
508
+ ${stderr}` : "";
509
+ sessionLog("init", `ERROR: transport closed before init message received${detail}`);
510
+ throw new Error(`Failed to initialize session - no init message received${detail}`);
511
+ }
512
+ async send(message) {
513
+ if (!this.initialized) {
514
+ sessionLog("send", "auto-initializing (not yet initialized)");
515
+ await this.initialize();
516
+ }
517
+ const preview = typeof message === "string" ? message.slice(0, 100) : Array.isArray(message) ? `[multimodal: ${message.length} parts]` : String(message).slice(0, 100);
518
+ sessionLog("send", `sending message: ${preview}${typeof message === "string" && message.length > 100 ? "..." : ""}`);
519
+ if (this.streamQueue.length > 0) {
520
+ sessionLog("send", `clearing ${this.streamQueue.length} stale messages from previous turn`);
521
+ this.streamQueue.length = 0;
522
+ }
523
+ await this.transport.write({
524
+ type: "user",
525
+ message: { role: "user", content: message }
526
+ });
527
+ this.sendGeneration++;
528
+ sessionLog("send", `message written to transport (generation=${this.sendGeneration})`);
529
+ }
530
+ async runTurn(message, options = {}) {
531
+ const maxApprovalRecoveryAttempts = this.resolveMaxApprovalRecoveryAttempts(options);
532
+ const recoveryTimeoutMs = this.resolveApprovalRecoveryTimeoutMs(options);
533
+ let recoveryAttempts = 0;
534
+ let result = await this.runSingleTurn(message);
535
+ while (!result.success && result.approvalConflict === true && recoveryAttempts < maxApprovalRecoveryAttempts) {
536
+ recoveryAttempts += 1;
537
+ const recovery = await this.recoverPendingApprovals({
538
+ timeoutMs: recoveryTimeoutMs
539
+ });
540
+ if (!recovery.recovered) {
541
+ return this.toTerminalApprovalConflictResult(result, {
542
+ recoveryAttempts,
543
+ detail: recovery.detail
544
+ });
545
+ }
546
+ result = await this.runSingleTurn(message);
547
+ }
548
+ if (!result.success && result.approvalConflict === true) {
549
+ return this.toTerminalApprovalConflictResult(result, { recoveryAttempts });
550
+ }
551
+ if (recoveryAttempts > 0) {
552
+ result.recoveryAttempts = recoveryAttempts;
553
+ }
554
+ return result;
555
+ }
556
+ async recoverPendingApprovals(options = {}) {
557
+ if (!this.initialized) {
558
+ await this.initialize();
559
+ }
560
+ const timeoutMs = options.timeoutMs ?? this.options.approvalRecoveryTimeoutMs ?? DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS;
561
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
562
+ throw new Error("Invalid approval recovery timeout. Expected a positive integer.");
563
+ }
564
+ const requestId = `recover-approvals-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
565
+ let resp;
566
+ try {
567
+ resp = await this.requestControlResponse(requestId, {
568
+ subtype: "recover_pending_approvals",
569
+ ...this._agentId ? { agent_id: this._agentId } : {},
570
+ ...this._conversationId ? { conversation_id: this._conversationId } : {}
571
+ }, { timeoutMs });
572
+ } catch (error) {
573
+ const detail = error instanceof Error ? error.message : String(error);
574
+ return {
575
+ recovered: false,
576
+ unsupported: false,
577
+ detail
578
+ };
579
+ }
580
+ if (resp.subtype === "error") {
581
+ const detail = resp.error ?? "recover_pending_approvals failed";
582
+ return {
583
+ recovered: false,
584
+ pendingApproval: true,
585
+ unsupported: isUnknownControlSubtypeError(detail, "recover_pending_approvals"),
586
+ detail
587
+ };
588
+ }
589
+ return {
590
+ recovered: true,
591
+ pendingApproval: false,
592
+ unsupported: false
593
+ };
594
+ }
595
+ resolveMaxApprovalRecoveryAttempts(options) {
596
+ const value = options.maxApprovalRecoveryAttempts ?? this.options.maxApprovalRecoveryAttempts ?? DEFAULT_MAX_APPROVAL_RECOVERY_ATTEMPTS;
597
+ if (!Number.isInteger(value) || value < 0) {
598
+ throw new Error("Invalid maxApprovalRecoveryAttempts. Expected a non-negative integer.");
599
+ }
600
+ return value;
601
+ }
602
+ resolveApprovalRecoveryTimeoutMs(options) {
603
+ const value = options.recoveryTimeoutMs ?? this.options.approvalRecoveryTimeoutMs ?? DEFAULT_APPROVAL_RECOVERY_TIMEOUT_MS;
604
+ if (!Number.isInteger(value) || value <= 0) {
605
+ throw new Error("Invalid approval recovery timeout. Expected a positive integer.");
606
+ }
607
+ return value;
608
+ }
609
+ toTerminalApprovalConflictResult(result, options) {
610
+ return {
611
+ ...result,
612
+ success: false,
613
+ error: "approval_conflict_terminal",
614
+ errorCode: "approval_conflict_terminal",
615
+ approvalConflict: true,
616
+ recoverable: false,
617
+ recoveryAttempts: options.recoveryAttempts,
618
+ errorDetail: options.detail || result.errorDetail || "Approval conflict remained unresolved after bounded SDK recovery."
619
+ };
620
+ }
621
+ async runSingleTurn(message) {
622
+ await this.send(message);
623
+ let latestApprovalConflictDetail;
624
+ let latestNonConflictErrorDetail;
625
+ for await (const msg of this.stream()) {
626
+ if (msg.type === "error") {
627
+ const detail = msg.errorDetail || msg.message;
628
+ if (msg.approvalConflict) {
629
+ latestApprovalConflictDetail = detail;
630
+ } else {
631
+ latestNonConflictErrorDetail = detail;
632
+ }
633
+ continue;
634
+ }
635
+ if (msg.type === "result") {
636
+ if (!msg.success && !msg.errorDetail) {
637
+ if (msg.approvalConflict && latestApprovalConflictDetail) {
638
+ return {
639
+ ...msg,
640
+ errorDetail: latestApprovalConflictDetail
641
+ };
642
+ }
643
+ if (!msg.approvalConflict && latestNonConflictErrorDetail) {
644
+ return {
645
+ ...msg,
646
+ errorDetail: latestNonConflictErrorDetail
647
+ };
648
+ }
649
+ }
650
+ return msg;
651
+ }
652
+ }
653
+ return {
654
+ type: "result",
655
+ success: false,
656
+ error: "stream_closed",
657
+ errorCode: "stream_closed",
658
+ recoverable: false,
659
+ errorDetail: "Stream ended before terminal result",
660
+ durationMs: 0,
661
+ conversationId: this._conversationId
662
+ };
663
+ }
664
+ async* stream() {
665
+ const streamStart = Date.now();
666
+ const minGeneration = this.sendGeneration;
667
+ let yieldCount = 0;
668
+ let staleCount = 0;
669
+ let staleRunIdCount = 0;
670
+ let gotResult = false;
671
+ const currentStreamRunIds = new Set;
672
+ const staleRunIds = new Set(this.lastCompletedRunIds);
673
+ const approvalConflictDetailsByRunId = new Map;
674
+ let latestApprovalConflictDetail;
675
+ this.startBackgroundPump();
676
+ sessionLog("stream", `starting stream (agent=${this._agentId}, conversation=${this._conversationId}, generation=${minGeneration})`);
677
+ while (true) {
678
+ const bufferedMsg = await this.nextBufferedMessage();
679
+ if (!bufferedMsg) {
680
+ break;
681
+ }
682
+ if (bufferedMsg.generation < minGeneration) {
683
+ staleCount++;
684
+ sessionLog("stream", `discarding stale message: type=${bufferedMsg.message.type} generation=${bufferedMsg.generation} (current=${minGeneration})`);
685
+ continue;
686
+ }
687
+ if (bufferedMsg.runId && staleRunIds.has(bufferedMsg.runId)) {
688
+ staleRunIdCount++;
689
+ sessionLog("stream", `discarding stale message: type=${bufferedMsg.message.type} runId=${bufferedMsg.runId}`);
690
+ continue;
691
+ }
692
+ if (bufferedMsg.runId) {
693
+ currentStreamRunIds.add(bufferedMsg.runId);
694
+ }
695
+ const sdkMsg = bufferedMsg.message;
696
+ if (sdkMsg.type === "error" && sdkMsg.approvalConflict) {
697
+ const detail = sdkMsg.errorDetail || sdkMsg.message;
698
+ if (detail) {
699
+ latestApprovalConflictDetail = detail;
700
+ if (sdkMsg.runId) {
701
+ approvalConflictDetailsByRunId.set(sdkMsg.runId, detail);
702
+ }
703
+ }
704
+ }
705
+ let normalizedMsg = sdkMsg;
706
+ if (sdkMsg.type === "result" && !sdkMsg.success) {
707
+ let detail = sdkMsg.errorDetail;
708
+ if (!detail && Array.isArray(sdkMsg.runIds)) {
709
+ for (const runId of sdkMsg.runIds) {
710
+ const fromRun = approvalConflictDetailsByRunId.get(runId);
711
+ if (fromRun) {
712
+ detail = fromRun;
713
+ break;
714
+ }
715
+ }
716
+ }
717
+ if (!detail) {
718
+ detail = latestApprovalConflictDetail;
719
+ }
720
+ const approvalConflict = sdkMsg.approvalConflict === true || isApprovalConflictSignal({
721
+ detail,
722
+ message: sdkMsg.errorCode || sdkMsg.error,
723
+ stopReason: sdkMsg.stopReason
724
+ });
725
+ if (approvalConflict) {
726
+ const normalizedErrorCode = !sdkMsg.errorCode || sdkMsg.errorCode === "error" ? "approval_conflict" : sdkMsg.errorCode;
727
+ const normalizedError = !sdkMsg.error || sdkMsg.error === "error" ? normalizedErrorCode : sdkMsg.error;
728
+ normalizedMsg = {
729
+ ...sdkMsg,
730
+ approvalConflict: true,
731
+ recoverable: sdkMsg.recoverable ?? true,
732
+ ...detail ? { errorDetail: detail } : {},
733
+ errorCode: normalizedErrorCode,
734
+ error: normalizedError
735
+ };
736
+ } else if (!sdkMsg.errorCode && sdkMsg.error) {
737
+ const errorCode = toSdkErrorCode(sdkMsg.error);
738
+ if (errorCode) {
739
+ normalizedMsg = {
740
+ ...sdkMsg,
741
+ errorCode
742
+ };
743
+ }
744
+ }
745
+ }
746
+ yieldCount++;
747
+ sessionLog("stream", `yield #${yieldCount}: type=${normalizedMsg.type}${normalizedMsg.type === "result" ? ` success=${normalizedMsg.success} error=${normalizedMsg.error || "none"}` : ""}`);
748
+ yield normalizedMsg;
749
+ if (normalizedMsg.type === "result") {
750
+ gotResult = true;
751
+ this.updateCompletedRunIds(normalizedMsg.runIds, currentStreamRunIds);
752
+ break;
753
+ }
754
+ }
755
+ const elapsed = Date.now() - streamStart;
756
+ sessionLog("stream", `stream ended: duration=${elapsed}ms yielded=${yieldCount} staleFiltered=${staleCount} staleRunIdFiltered=${staleRunIdCount} dropped=${this.droppedStreamMessages} gotResult=${gotResult}`);
757
+ if (!gotResult) {
758
+ sessionLog("stream", "WARNING: stream ended WITHOUT a result message -- transport may have closed unexpectedly");
759
+ }
760
+ }
761
+ startBackgroundPump() {
762
+ if (this.pumpPromise) {
763
+ return;
764
+ }
765
+ this.pumpClosed = false;
766
+ this.pumpPromise = this.runBackgroundPump().catch((err) => {
767
+ sessionLog("pump", `ERROR: ${err instanceof Error ? err.message : String(err)}`);
768
+ }).finally(() => {
769
+ this.pumpClosed = true;
770
+ this.resolveAllStreamWaiters(null);
771
+ });
772
+ }
773
+ async runBackgroundPump() {
774
+ sessionLog("pump", "background pump started");
775
+ const indexToToolCallId = new Map;
776
+ for await (const wireMsg of this.transport.messages()) {
777
+ const wireMsgAny = wireMsg;
778
+ if (wireMsg.type === "control_request") {
779
+ const handled = await this.handleControlRequest(wireMsg);
780
+ if (!handled) {
781
+ sessionLog("pump", `DROPPED unsupported control_request: subtype=${wireMsgAny.request?.subtype || "N/A"}`);
782
+ }
783
+ continue;
784
+ }
785
+ if (wireMsg.type === "control_response") {
786
+ const respMsg = wireMsg;
787
+ const requestId = respMsg.response?.request_id;
788
+ if (requestId && this.controlResponseWaiters.has(requestId)) {
789
+ const resolve = this.controlResponseWaiters.get(requestId);
790
+ this.controlResponseWaiters.delete(requestId);
791
+ resolve(respMsg.response);
792
+ } else {
793
+ sessionLog("pump", `DROPPED unmatched control_response: request_id=${requestId ?? "N/A"}`);
794
+ }
795
+ continue;
796
+ }
797
+ const messageType = wireMsgAny.message_type;
798
+ if (wireMsg.type === "message" && (messageType === "tool_call_message" || messageType === "approval_request_message")) {
799
+ const toolCalls = wireMsgAny.tool_calls;
800
+ const toolCall = wireMsgAny.tool_call;
801
+ const tc = toolCalls?.[0] || toolCall;
802
+ if (tc) {
803
+ const fnObj = tc.function;
804
+ const tcId = tc.tool_call_id ?? tc.id;
805
+ const tcIndex = tc.index;
806
+ if (tcId && tcIndex !== undefined) {
807
+ indexToToolCallId.set(tcIndex, tcId);
808
+ } else if (!tcId && tcIndex !== undefined) {
809
+ const resolvedId = indexToToolCallId.get(tcIndex);
810
+ if (resolvedId) {
811
+ if (fnObj) {
812
+ tc.id = resolvedId;
813
+ } else {
814
+ tc.tool_call_id = resolvedId;
815
+ }
816
+ }
817
+ }
818
+ }
819
+ }
820
+ const sdkMsg = this.transformMessage(wireMsg);
821
+ if (sdkMsg) {
822
+ this.enqueueStreamMessage(sdkMsg);
823
+ } else {
824
+ sessionLog("pump", `DROPPED wire message: type=${wireMsg.type} message_type=${wireMsgAny.message_type || "N/A"} subtype=${wireMsgAny.subtype || "N/A"}`);
825
+ }
826
+ }
827
+ sessionLog("pump", "background pump ended");
828
+ }
829
+ async handleControlRequest(controlReq) {
830
+ const subtype = controlReq.request.subtype;
831
+ sessionLog("pump", `control_request: subtype=${subtype} tool=${controlReq.request.tool_name || "N/A"}`);
832
+ if (subtype === "can_use_tool") {
833
+ await this.handleCanUseTool(controlReq.request_id, controlReq.request);
834
+ return true;
835
+ }
836
+ if (subtype === "execute_external_tool") {
837
+ const rawReq = controlReq.request;
838
+ await this.handleExecuteExternalTool(controlReq.request_id, {
839
+ subtype: "execute_external_tool",
840
+ tool_call_id: rawReq.tool_call_id,
841
+ tool_name: rawReq.tool_name,
842
+ input: rawReq.input
843
+ });
844
+ return true;
845
+ }
846
+ return false;
847
+ }
848
+ enqueueStreamMessage(msg) {
849
+ const bufferedMsg = {
850
+ message: msg,
851
+ generation: this.sendGeneration,
852
+ runId: this.getMessageRunId(msg)
853
+ };
854
+ if (this.streamResolvers.length > 0) {
855
+ const resolve = this.streamResolvers.shift();
856
+ resolve(bufferedMsg);
857
+ return;
858
+ }
859
+ if (this.streamQueue.length >= MAX_BUFFERED_STREAM_MESSAGES) {
860
+ this.streamQueue.shift();
861
+ this.droppedStreamMessages++;
862
+ sessionLog("pump", `stream queue overflow: dropped oldest message (total_dropped=${this.droppedStreamMessages}, max=${MAX_BUFFERED_STREAM_MESSAGES})`);
863
+ }
864
+ this.streamQueue.push(bufferedMsg);
865
+ }
866
+ async nextBufferedMessage() {
867
+ if (this.streamQueue.length > 0) {
868
+ return this.streamQueue.shift();
869
+ }
870
+ if (this.pumpClosed) {
871
+ return null;
872
+ }
873
+ return new Promise((resolve) => {
874
+ this.streamResolvers.push(resolve);
875
+ });
876
+ }
877
+ resolveAllStreamWaiters(msg) {
878
+ for (const resolve of this.streamResolvers) {
879
+ resolve(msg);
880
+ }
881
+ this.streamResolvers = [];
882
+ for (const resolve of this.controlResponseWaiters.values()) {
883
+ resolve({ subtype: "error", error: "session closed" });
884
+ }
885
+ this.controlResponseWaiters.clear();
886
+ }
887
+ getMessageRunId(msg) {
888
+ switch (msg.type) {
889
+ case "assistant":
890
+ case "tool_call":
891
+ case "tool_result":
892
+ case "reasoning":
893
+ case "error":
894
+ case "retry":
895
+ return msg.runId;
896
+ default:
897
+ return;
898
+ }
899
+ }
900
+ updateCompletedRunIds(resultRunIds, streamedRunIds) {
901
+ const nextRunIds = new Set;
902
+ if (Array.isArray(resultRunIds)) {
903
+ for (const runId of resultRunIds) {
904
+ if (runId) {
905
+ nextRunIds.add(runId);
906
+ }
907
+ }
908
+ }
909
+ for (const runId of streamedRunIds) {
910
+ if (runId) {
911
+ nextRunIds.add(runId);
912
+ }
913
+ }
914
+ this.lastCompletedRunIds = nextRunIds;
915
+ }
916
+ async registerExternalTools() {
917
+ const toolDefs = Array.from(this.externalTools.values()).map((tool) => ({
918
+ name: tool.name,
919
+ label: tool.label,
920
+ description: tool.description,
921
+ parameters: this.schemaToJsonSchema(tool.parameters)
922
+ }));
923
+ sessionLog("registerTools", `registering ${toolDefs.length} external tools: ${toolDefs.map((t) => t.name).join(", ")}`);
924
+ await this.transport.write({
925
+ type: "control_request",
926
+ request_id: `register_tools_${Date.now()}`,
927
+ request: {
928
+ subtype: "register_external_tools",
929
+ tools: toolDefs
930
+ }
931
+ });
932
+ }
933
+ schemaToJsonSchema(schema) {
934
+ if (schema && typeof schema === "object") {
935
+ const s = schema;
936
+ return {
937
+ type: s.type,
938
+ properties: s.properties,
939
+ required: s.required,
940
+ additionalProperties: s.additionalProperties,
941
+ description: s.description
942
+ };
943
+ }
944
+ return { type: "object" };
945
+ }
946
+ async handleExecuteExternalTool(requestId, req) {
947
+ const tool = this.externalTools.get(req.tool_name);
948
+ if (!tool) {
949
+ sessionLog("executeExternalTool", `ERROR: unknown tool ${req.tool_name}`);
950
+ await this.transport.write({
951
+ type: "control_response",
952
+ response: {
953
+ subtype: "external_tool_result",
954
+ request_id: requestId,
955
+ tool_call_id: req.tool_call_id,
956
+ content: [{ type: "text", text: `Unknown external tool: ${req.tool_name}` }],
957
+ is_error: true
958
+ }
959
+ });
960
+ return;
961
+ }
962
+ try {
963
+ sessionLog("executeExternalTool", `executing ${req.tool_name} (call_id=${req.tool_call_id})`);
964
+ const result = await tool.execute(req.tool_call_id, req.input);
965
+ await this.transport.write({
966
+ type: "control_response",
967
+ response: {
968
+ subtype: "external_tool_result",
969
+ request_id: requestId,
970
+ tool_call_id: req.tool_call_id,
971
+ content: result.content,
972
+ is_error: false
973
+ }
974
+ });
975
+ sessionLog("executeExternalTool", `${req.tool_name} completed successfully`);
976
+ } catch (err) {
977
+ const errorMessage = err instanceof Error ? err.message : String(err);
978
+ sessionLog("executeExternalTool", `${req.tool_name} failed: ${errorMessage}`);
979
+ await this.transport.write({
980
+ type: "control_response",
981
+ response: {
982
+ subtype: "external_tool_result",
983
+ request_id: requestId,
984
+ tool_call_id: req.tool_call_id,
985
+ content: [{ type: "text", text: `Tool execution error: ${errorMessage}` }],
986
+ is_error: true
987
+ }
988
+ });
989
+ }
990
+ }
991
+ async handleCanUseTool(requestId, req) {
992
+ let response;
993
+ const toolName = req.tool_name;
994
+ const hasCallback = typeof this.options.canUseTool === "function";
995
+ const toolNeedsRuntimeUserInput = requiresRuntimeUserInput(toolName);
996
+ const autoAllowWithoutCallback = isHeadlessAutoAllowTool(toolName);
997
+ sessionLog("canUseTool", `tool=${toolName} mode=${this.options.permissionMode || "default"} requestId=${requestId}`);
998
+ if (toolNeedsRuntimeUserInput && !hasCallback) {
999
+ response = {
1000
+ behavior: "deny",
1001
+ message: "No canUseTool callback registered",
1002
+ interrupt: false
1003
+ };
1004
+ } else if (this.options.permissionMode === "bypassPermissions" && !toolNeedsRuntimeUserInput) {
1005
+ sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (bypassPermissions)`);
1006
+ response = {
1007
+ behavior: "allow",
1008
+ updatedInput: null,
1009
+ updatedPermissions: []
1010
+ };
1011
+ } else if (hasCallback) {
1012
+ try {
1013
+ const result = await this.options.canUseTool(toolName, req.input);
1014
+ if (result.behavior === "allow") {
1015
+ response = {
1016
+ behavior: "allow",
1017
+ updatedInput: result.updatedInput ?? null,
1018
+ updatedPermissions: []
1019
+ };
1020
+ } else {
1021
+ response = {
1022
+ behavior: "deny",
1023
+ message: result.message ?? "Denied by canUseTool callback",
1024
+ interrupt: false
1025
+ };
1026
+ }
1027
+ } catch (err) {
1028
+ response = {
1029
+ behavior: "deny",
1030
+ message: err instanceof Error ? err.message : "Callback error",
1031
+ interrupt: false
1032
+ };
1033
+ }
1034
+ } else if (autoAllowWithoutCallback) {
1035
+ sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (default behavior)`);
1036
+ response = {
1037
+ behavior: "allow",
1038
+ updatedInput: null,
1039
+ updatedPermissions: []
1040
+ };
1041
+ } else {
1042
+ response = {
1043
+ behavior: "deny",
1044
+ message: "No canUseTool callback registered",
1045
+ interrupt: false
1046
+ };
1047
+ }
1048
+ const responseBehavior = "behavior" in response ? response.behavior : "unknown";
1049
+ sessionLog("canUseTool", `responding: requestId=${requestId} behavior=${responseBehavior}`);
1050
+ await this.transport.write({
1051
+ type: "control_response",
1052
+ response: {
1053
+ subtype: "success",
1054
+ request_id: requestId,
1055
+ response
1056
+ }
1057
+ });
1058
+ sessionLog("canUseTool", `response sent for ${toolName}`);
1059
+ }
1060
+ async abort() {
1061
+ if (!this.initialized || this.pumpClosed)
1062
+ return;
1063
+ sessionLog("abort", `aborting session (agent=${this._agentId})`);
1064
+ await this.transport.write({
1065
+ type: "control_request",
1066
+ request_id: `interrupt-${Date.now()}`,
1067
+ request: { subtype: "interrupt" }
1068
+ });
1069
+ }
1070
+ async requestControlResponse(requestId, request, options = {}) {
1071
+ const responsePromise = new Promise((resolve) => {
1072
+ this.controlResponseWaiters.set(requestId, resolve);
1073
+ });
1074
+ try {
1075
+ await this.transport.write({
1076
+ type: "control_request",
1077
+ request_id: requestId,
1078
+ request
1079
+ });
1080
+ } catch (error) {
1081
+ this.controlResponseWaiters.delete(requestId);
1082
+ throw error;
1083
+ }
1084
+ let timeoutHandle;
1085
+ try {
1086
+ if (typeof options.timeoutMs === "number") {
1087
+ return await Promise.race([
1088
+ responsePromise,
1089
+ new Promise((_, reject) => {
1090
+ timeoutHandle = setTimeout(() => {
1091
+ this.controlResponseWaiters.delete(requestId);
1092
+ reject(new Error(`Timed out waiting for control_response (${requestId})`));
1093
+ }, options.timeoutMs);
1094
+ })
1095
+ ]);
1096
+ }
1097
+ return await responsePromise;
1098
+ } finally {
1099
+ if (timeoutHandle) {
1100
+ clearTimeout(timeoutHandle);
1101
+ }
1102
+ }
1103
+ }
1104
+ async listMessages(options = {}) {
1105
+ if (!this.initialized) {
1106
+ throw new Error("Session must be initialized before calling listMessages()");
1107
+ }
1108
+ const requestId = `list-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
1109
+ const resp = await this.requestControlResponse(requestId, {
1110
+ subtype: "list_messages",
1111
+ ...options.conversationId ? { conversation_id: options.conversationId } : {},
1112
+ ...options.before ? { before: options.before } : {},
1113
+ ...options.after ? { after: options.after } : {},
1114
+ ...options.order ? { order: options.order } : {},
1115
+ ...options.limit !== undefined ? { limit: options.limit } : {}
1116
+ });
1117
+ if (!resp) {
1118
+ throw new Error("Session closed before listMessages response arrived");
1119
+ }
1120
+ if (resp.subtype === "error") {
1121
+ throw new Error(resp.error ?? "listMessages failed");
1122
+ }
1123
+ const payload = resp.response;
1124
+ return {
1125
+ messages: payload?.messages ?? [],
1126
+ nextBefore: payload?.next_before ?? null,
1127
+ hasMore: payload?.has_more ?? false
1128
+ };
1129
+ }
1130
+ async listModels() {
1131
+ throw new Error("listModels() is not supported by this session. Use a Cloud, Remote, or local agent session.");
1132
+ }
1133
+ async sendCommand(_command, _options) {
1134
+ throw new Error("sendCommand() is not supported by this session. Use a Cloud, Remote, or local agent session.");
1135
+ }
1136
+ async updateModel(_update) {
1137
+ throw new Error("updateModel() is not supported by this session. Use a Cloud, Remote, or local agent session.");
1138
+ }
1139
+ async bootstrapState(options = {}) {
1140
+ if (!this.initialized) {
1141
+ throw new Error("Session must be initialized before calling bootstrapState()");
1142
+ }
1143
+ const requestId = `bootstrap-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
1144
+ const resp = await this.requestControlResponse(requestId, {
1145
+ subtype: "bootstrap_session_state",
1146
+ ...options.limit !== undefined ? { limit: options.limit } : {},
1147
+ ...options.order ? { order: options.order } : {}
1148
+ });
1149
+ if (!resp) {
1150
+ throw new Error("Session closed before bootstrapState response arrived");
1151
+ }
1152
+ if (resp.subtype === "error") {
1153
+ throw new Error(resp.error ?? "bootstrapState failed");
1154
+ }
1155
+ const payload = resp.response;
1156
+ const state = {
1157
+ agentId: payload?.agent_id ?? this._agentId ?? "",
1158
+ conversationId: payload?.conversation_id ?? this._conversationId ?? "",
1159
+ model: payload?.model,
1160
+ memfsEnabled: payload?.memfs_enabled ?? false,
1161
+ messages: payload?.messages ?? [],
1162
+ nextBefore: payload?.next_before ?? null,
1163
+ hasMore: payload?.has_more ?? false,
1164
+ hasPendingApproval: payload?.has_pending_approval ?? false
1165
+ };
1166
+ if (payload?.tools !== undefined)
1167
+ state.tools = payload.tools;
1168
+ if (payload?.timings !== undefined)
1169
+ state.timings = payload.timings;
1170
+ return state;
1171
+ }
1172
+ async updateToolset(_toolsetPreference) {
1173
+ throw new Error("updateToolset() is not supported by this session. Use a Cloud, Remote, or local agent session.");
1174
+ }
1175
+ close() {
1176
+ sessionLog("close", `closing session (agent=${this._agentId}, conversation=${this._conversationId})`);
1177
+ this.transport.close();
1178
+ this.pumpClosed = true;
1179
+ this.resolveAllStreamWaiters(null);
1180
+ }
1181
+ get agentId() {
1182
+ return this._agentId;
1183
+ }
1184
+ get sessionId() {
1185
+ return this._sessionId;
1186
+ }
1187
+ get conversationId() {
1188
+ return this._conversationId;
1189
+ }
1190
+ async[Symbol.asyncDispose]() {
1191
+ this.close();
1192
+ }
1193
+ transformMessage(wireMsg) {
1194
+ if (wireMsg.type === "system" && "subtype" in wireMsg && wireMsg.subtype === "init") {
1195
+ const msg = wireMsg;
1196
+ return {
1197
+ type: "init",
1198
+ agentId: msg.agent_id,
1199
+ sessionId: msg.session_id,
1200
+ conversationId: msg.conversation_id,
1201
+ model: msg.model,
1202
+ tools: msg.tools,
1203
+ memfsEnabled: msg.memfs_enabled,
1204
+ skillSources: msg.skill_sources,
1205
+ systemInfoReminderEnabled: msg.system_info_reminder_enabled,
1206
+ dreaming: msg.reflection_trigger && msg.reflection_behavior && typeof msg.reflection_step_count === "number" ? {
1207
+ trigger: msg.reflection_trigger,
1208
+ behavior: msg.reflection_behavior,
1209
+ stepCount: msg.reflection_step_count
1210
+ } : undefined
1211
+ };
1212
+ }
1213
+ if (wireMsg.type === "message" && "message_type" in wireMsg) {
1214
+ const msg = wireMsg;
1215
+ const runId = msg.run_id || undefined;
1216
+ if (msg.message_type === "assistant_message" && msg.content) {
1217
+ return {
1218
+ type: "assistant",
1219
+ content: msg.content,
1220
+ uuid: msg.uuid,
1221
+ runId
1222
+ };
1223
+ }
1224
+ if (msg.message_type === "tool_call_message" || msg.message_type === "approval_request_message") {
1225
+ const toolCallRaw = msg.tool_calls?.[0] || msg.tool_call;
1226
+ if (toolCallRaw) {
1227
+ const fnObj = toolCallRaw.function;
1228
+ const toolCallId = toolCallRaw.tool_call_id ?? toolCallRaw.id;
1229
+ if (!toolCallId) {
1230
+ const detail = `Missing tool_call_id in ${msg.message_type} (uuid=${msg.uuid || "unknown"})`;
1231
+ sessionLog("transform", detail);
1232
+ return {
1233
+ type: "error",
1234
+ message: detail,
1235
+ errorCode: "protocol_error",
1236
+ stopReason: "protocol_error",
1237
+ runId,
1238
+ apiError: {
1239
+ error_type: "protocol_error",
1240
+ detail,
1241
+ message_type: msg.message_type
1242
+ },
1243
+ recoverable: false,
1244
+ errorDetail: detail
1245
+ };
1246
+ }
1247
+ const toolName = toolCallRaw.name ?? fnObj?.name ?? "?";
1248
+ const toolArgs = toolCallRaw.arguments ?? fnObj?.arguments ?? "";
1249
+ let toolInput = {};
1250
+ try {
1251
+ toolInput = JSON.parse(toolArgs);
1252
+ } catch {
1253
+ toolInput = { raw: toolArgs };
1254
+ }
1255
+ return {
1256
+ type: "tool_call",
1257
+ toolCallId,
1258
+ toolName,
1259
+ toolInput,
1260
+ rawArguments: toolArgs || undefined,
1261
+ uuid: msg.uuid,
1262
+ runId
1263
+ };
1264
+ }
1265
+ }
1266
+ if (msg.message_type === "tool_return_message" && msg.tool_call_id) {
1267
+ return {
1268
+ type: "tool_result",
1269
+ toolCallId: msg.tool_call_id,
1270
+ content: msg.tool_return || "",
1271
+ isError: msg.status === "error",
1272
+ uuid: msg.uuid,
1273
+ runId
1274
+ };
1275
+ }
1276
+ if (msg.message_type === "reasoning_message" && msg.reasoning) {
1277
+ return {
1278
+ type: "reasoning",
1279
+ content: msg.reasoning,
1280
+ uuid: msg.uuid,
1281
+ runId
1282
+ };
1283
+ }
1284
+ }
1285
+ if (wireMsg.type === "stream_event") {
1286
+ const msg = wireMsg;
1287
+ const eventPayload = msg.event ?? {};
1288
+ return {
1289
+ type: "stream_event",
1290
+ event: eventPayload,
1291
+ uuid: msg.uuid
1292
+ };
1293
+ }
1294
+ if (wireMsg.type === "result") {
1295
+ const msg = wireMsg;
1296
+ const runIds = Array.isArray(msg.run_ids) ? msg.run_ids.filter((id) => typeof id === "string") : undefined;
1297
+ const approvalConflict = isApprovalConflictSignal({
1298
+ stopReason: msg.stop_reason
1299
+ });
1300
+ const legacyError = msg.subtype !== "success" ? approvalConflict && msg.subtype === "error" ? "approval_conflict" : msg.subtype : undefined;
1301
+ const errorCode = toSdkErrorCode(legacyError);
1302
+ return {
1303
+ type: "result",
1304
+ success: msg.subtype === "success",
1305
+ result: msg.result,
1306
+ error: legacyError,
1307
+ errorCode,
1308
+ ...approvalConflict ? {
1309
+ approvalConflict: true,
1310
+ recoverable: true
1311
+ } : {},
1312
+ stopReason: msg.stop_reason,
1313
+ durationMs: msg.duration_ms,
1314
+ totalCostUsd: msg.total_cost_usd,
1315
+ conversationId: msg.conversation_id,
1316
+ runIds
1317
+ };
1318
+ }
1319
+ if (wireMsg.type === "error") {
1320
+ const msg = wireMsg;
1321
+ const errorDetail = extractApiErrorDetail(msg.api_error);
1322
+ const approvalConflict = isApprovalConflictSignal({
1323
+ detail: errorDetail,
1324
+ message: msg.message,
1325
+ stopReason: msg.stop_reason
1326
+ });
1327
+ const errorCode = approvalConflict ? "approval_conflict" : toSdkErrorCode(msg.stop_reason);
1328
+ return {
1329
+ type: "error",
1330
+ message: msg.message,
1331
+ ...errorCode ? { errorCode } : {},
1332
+ ...approvalConflict ? {
1333
+ approvalConflict: true,
1334
+ recoverable: true
1335
+ } : {},
1336
+ ...errorDetail ? { errorDetail } : {},
1337
+ stopReason: msg.stop_reason,
1338
+ runId: msg.run_id,
1339
+ apiError: msg.api_error
1340
+ };
1341
+ }
1342
+ if (wireMsg.type === "retry") {
1343
+ const msg = wireMsg;
1344
+ return {
1345
+ type: "retry",
1346
+ reason: msg.reason,
1347
+ attempt: msg.attempt,
1348
+ maxAttempts: msg.max_attempts,
1349
+ delayMs: msg.delay_ms,
1350
+ runId: msg.run_id
1351
+ };
1352
+ }
1353
+ return null;
1354
+ }
1355
+ }
1356
+
1357
+ // node_modules/@letta-ai/letta-code/dist/app-server-client.js
1358
+ var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
1359
+ var WEBSOCKET_OPEN_STATE = 1;
1360
+ function getGlobalWebSocket() {
1361
+ return globalThis.WebSocket;
1362
+ }
1363
+ function normalizeBaseUrl(url) {
1364
+ const parsed = new URL(url);
1365
+ if (parsed.protocol === "http:")
1366
+ parsed.protocol = "ws:";
1367
+ if (parsed.protocol === "https:")
1368
+ parsed.protocol = "wss:";
1369
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
1370
+ throw new Error(`Unsupported app-server URL protocol: ${parsed.protocol}`);
1371
+ }
1372
+ if (!parsed.pathname || parsed.pathname === "/") {
1373
+ parsed.pathname = "/ws";
1374
+ }
1375
+ return parsed;
1376
+ }
1377
+ function resolveAppServerChannelUrl(url, channel) {
1378
+ const parsed = normalizeBaseUrl(url);
1379
+ parsed.searchParams.set("channel", channel);
1380
+ return parsed.toString();
1381
+ }
1382
+ function attachSocketListener(socket, type, listener) {
1383
+ if (socket.addEventListener && socket.removeEventListener) {
1384
+ socket.addEventListener(type, listener);
1385
+ return () => socket.removeEventListener?.(type, listener);
1386
+ }
1387
+ if (socket.on) {
1388
+ socket.on(type, listener);
1389
+ return () => socket.off?.(type, listener);
1390
+ }
1391
+ throw new Error("WebSocket implementation does not support event listeners");
1392
+ }
1393
+ function onceSocketEvent(socket, type, listener) {
1394
+ if (socket.once) {
1395
+ socket.once(type, listener);
1396
+ return () => socket.off?.(type, listener);
1397
+ }
1398
+ let detach = () => {};
1399
+ detach = attachSocketListener(socket, type, (event) => {
1400
+ detach();
1401
+ listener(event);
1402
+ });
1403
+ return detach;
1404
+ }
1405
+ function waitForSocketOpen(socket) {
1406
+ if (socket.readyState === WEBSOCKET_OPEN_STATE) {
1407
+ return Promise.resolve();
1408
+ }
1409
+ return new Promise((resolve, reject) => {
1410
+ let detachOpen = () => {};
1411
+ let detachError = () => {};
1412
+ const cleanup = () => {
1413
+ detachOpen();
1414
+ detachError();
1415
+ };
1416
+ detachOpen = onceSocketEvent(socket, "open", () => {
1417
+ cleanup();
1418
+ resolve();
1419
+ });
1420
+ detachError = onceSocketEvent(socket, "error", (event) => {
1421
+ cleanup();
1422
+ reject(new Error(`App-server WebSocket failed to open: ${String(event)}`));
1423
+ });
1424
+ });
1425
+ }
1426
+ function rawEventData(event) {
1427
+ if (event && typeof event === "object" && "data" in event) {
1428
+ return event.data;
1429
+ }
1430
+ return event;
1431
+ }
1432
+ function messageDataToString(data) {
1433
+ const raw = rawEventData(data);
1434
+ if (typeof raw === "string")
1435
+ return raw;
1436
+ if (raw instanceof ArrayBuffer) {
1437
+ return new TextDecoder().decode(raw);
1438
+ }
1439
+ if (raw instanceof Uint8Array) {
1440
+ return new TextDecoder().decode(raw);
1441
+ }
1442
+ if (ArrayBuffer.isView(raw)) {
1443
+ return new TextDecoder().decode(new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength));
1444
+ }
1445
+ return String(raw);
1446
+ }
1447
+ function parseProtocolMessage(event) {
1448
+ return JSON.parse(messageDataToString(event));
1449
+ }
1450
+ function appServerSocketOptions(authToken) {
1451
+ if (authToken === undefined) {
1452
+ return;
1453
+ }
1454
+ const token = authToken.trim();
1455
+ if (!token) {
1456
+ throw new Error("app-server auth token must not be empty");
1457
+ }
1458
+ return { headers: { Authorization: `Bearer ${token}` } };
1459
+ }
1460
+ function sameRuntime(a, b) {
1461
+ return a?.agent_id === b.agent_id && a?.conversation_id === b.conversation_id;
1462
+ }
1463
+ function isWaitingLoopStatus(message) {
1464
+ return message.loop_status.status === "WAITING_ON_INPUT";
1465
+ }
1466
+ function isWaitingOnApprovalLoopStatus(message) {
1467
+ return message.loop_status.status === "WAITING_ON_APPROVAL";
1468
+ }
1469
+ function streamDeltaRunId(message) {
1470
+ const runId = message.delta.run_id;
1471
+ return typeof runId === "string" ? runId : null;
1472
+ }
1473
+ function streamDeltaMessageType(message) {
1474
+ const messageType = message.delta.message_type;
1475
+ return typeof messageType === "string" ? messageType : null;
1476
+ }
1477
+ function streamDeltaStopReason(message) {
1478
+ const stopReason = message.delta.stop_reason;
1479
+ return typeof stopReason === "string" ? stopReason : null;
1480
+ }
1481
+ function streamDeltaErrorMessage(message) {
1482
+ const delta = message.delta;
1483
+ const apiMessage = delta.api_error?.message ?? delta.api_error?.detail;
1484
+ if (typeof apiMessage === "string" && apiMessage.length > 0)
1485
+ return apiMessage;
1486
+ if (typeof delta.message === "string" && delta.message.length > 0)
1487
+ return delta.message;
1488
+ return "App-server turn failed";
1489
+ }
1490
+
1491
+ class AppServerClient {
1492
+ control;
1493
+ stream;
1494
+ requestTimeoutMs;
1495
+ pending = new Map;
1496
+ messageHandlers = new Set;
1497
+ sendHandlers = new Set;
1498
+ activeTurnRuntimes = new Set;
1499
+ nextRequestNumber = 0;
1500
+ constructor(options) {
1501
+ const WebSocket = options.WebSocket ?? getGlobalWebSocket();
1502
+ if (!WebSocket) {
1503
+ throw new Error("No WebSocket implementation available");
1504
+ }
1505
+ this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
1506
+ const socketOptions = appServerSocketOptions(options.authToken);
1507
+ this.control = new WebSocket(resolveAppServerChannelUrl(options.url, "control"), socketOptions);
1508
+ this.stream = new WebSocket(resolveAppServerChannelUrl(options.url, "stream"), socketOptions);
1509
+ attachSocketListener(this.control, "message", (event) => {
1510
+ this.handleMessage(event, "control");
1511
+ });
1512
+ attachSocketListener(this.stream, "message", (event) => {
1513
+ this.handleMessage(event, "stream");
1514
+ });
1515
+ const rejectPending = () => this.rejectAllPending("App-server socket closed");
1516
+ attachSocketListener(this.control, "close", rejectPending);
1517
+ attachSocketListener(this.stream, "close", rejectPending);
1518
+ }
1519
+ async connect() {
1520
+ await Promise.all([
1521
+ waitForSocketOpen(this.control),
1522
+ waitForSocketOpen(this.stream)
1523
+ ]);
1524
+ return this;
1525
+ }
1526
+ close() {
1527
+ this.rejectAllPending("App-server client closed");
1528
+ this.control.close();
1529
+ this.stream.close();
1530
+ }
1531
+ onMessage(handler) {
1532
+ this.messageHandlers.add(handler);
1533
+ return () => this.messageHandlers.delete(handler);
1534
+ }
1535
+ onSend(handler) {
1536
+ this.sendHandlers.add(handler);
1537
+ return () => this.sendHandlers.delete(handler);
1538
+ }
1539
+ nextRequestId(prefix = "req") {
1540
+ this.nextRequestNumber += 1;
1541
+ return `${prefix}-${this.nextRequestNumber}`;
1542
+ }
1543
+ send(command) {
1544
+ for (const handler of this.sendHandlers) {
1545
+ handler(command);
1546
+ }
1547
+ this.control.send(JSON.stringify(command));
1548
+ }
1549
+ request(commandOrType, bodyOrOptions = {}, maybeOptions = {}) {
1550
+ const isTypeRequest = typeof commandOrType === "string";
1551
+ const command = isTypeRequest ? {
1552
+ type: commandOrType,
1553
+ request_id: bodyOrOptions.request_id ?? this.nextRequestId(commandOrType),
1554
+ ...bodyOrOptions
1555
+ } : commandOrType;
1556
+ const options = isTypeRequest ? maybeOptions : bodyOrOptions;
1557
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
1558
+ return new Promise((resolve, reject) => {
1559
+ const timeout = setTimeout(() => {
1560
+ this.pending.delete(command.request_id);
1561
+ reject(new Error(`Timed out waiting for ${command.request_id}`));
1562
+ }, timeoutMs);
1563
+ this.pending.set(command.request_id, {
1564
+ resolve: (message) => resolve(message),
1565
+ reject,
1566
+ predicate: options.predicate,
1567
+ timeout
1568
+ });
1569
+ try {
1570
+ this.send(command);
1571
+ } catch (error) {
1572
+ clearTimeout(timeout);
1573
+ this.pending.delete(command.request_id);
1574
+ reject(error instanceof Error ? error : new Error(String(error)));
1575
+ }
1576
+ });
1577
+ }
1578
+ runtimeStart(command, options = {}) {
1579
+ return this.request({
1580
+ type: "runtime_start",
1581
+ request_id: command.request_id ?? this.nextRequestId("runtime-start"),
1582
+ ...command
1583
+ }, {
1584
+ ...options,
1585
+ predicate: (message) => message.type === "runtime_start_response"
1586
+ });
1587
+ }
1588
+ sync(command, options = {}) {
1589
+ return this.request({
1590
+ type: "sync",
1591
+ request_id: command.request_id ?? this.nextRequestId("sync"),
1592
+ ...command
1593
+ }, {
1594
+ ...options,
1595
+ predicate: (message) => message.type === "sync_response"
1596
+ });
1597
+ }
1598
+ abort(command, options = {}) {
1599
+ return this.request({
1600
+ type: "abort_message",
1601
+ request_id: command.request_id ?? this.nextRequestId("abort"),
1602
+ ...command
1603
+ }, {
1604
+ ...options,
1605
+ predicate: (message) => message.type === "abort_message_response"
1606
+ });
1607
+ }
1608
+ onExternalToolCall(handler) {
1609
+ return this.onMessage((message, channel) => {
1610
+ if (channel !== "control" || message.type !== "external_tool_call_request") {
1611
+ return;
1612
+ }
1613
+ Promise.resolve(handler(message)).then((result) => {
1614
+ this.send({
1615
+ type: "external_tool_call_response",
1616
+ request_id: message.request_id,
1617
+ result
1618
+ });
1619
+ }).catch((error) => {
1620
+ this.send({
1621
+ type: "external_tool_call_response",
1622
+ request_id: message.request_id,
1623
+ error: error instanceof Error ? error.message : String(error)
1624
+ });
1625
+ });
1626
+ });
1627
+ }
1628
+ input(command) {
1629
+ this.send({ type: "input", ...command });
1630
+ }
1631
+ runTurn(command, options = {}) {
1632
+ const runtimeKey = `${command.runtime.agent_id}/${command.runtime.conversation_id}`;
1633
+ if (this.activeTurnRuntimes.has(runtimeKey)) {
1634
+ return Promise.reject(new Error(`A turn is already in flight for ${runtimeKey}`));
1635
+ }
1636
+ this.activeTurnRuntimes.add(runtimeKey);
1637
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
1638
+ const commandWithIds = this.withClientMessageIds(command);
1639
+ const runIds = new Set;
1640
+ let observedTurnEvidence = false;
1641
+ let observedRequiresApprovalStop = false;
1642
+ return new Promise((resolve, reject) => {
1643
+ const timeout = setTimeout(() => {
1644
+ cleanup();
1645
+ reject(new Error(`Timed out waiting for app-server turn on ${command.runtime.agent_id}/${command.runtime.conversation_id}`));
1646
+ }, timeoutMs);
1647
+ const cleanup = () => {
1648
+ clearTimeout(timeout);
1649
+ this.activeTurnRuntimes.delete(runtimeKey);
1650
+ offMessage();
1651
+ };
1652
+ const finish = (completedBy, terminalMessage, stopReason) => {
1653
+ cleanup();
1654
+ resolve({
1655
+ runtime: command.runtime,
1656
+ stopReason,
1657
+ runIds: [...runIds],
1658
+ clientMessageIds: commandWithIds.clientMessageIds,
1659
+ completedBy,
1660
+ terminalMessage
1661
+ });
1662
+ };
1663
+ const fail = (error) => {
1664
+ cleanup();
1665
+ reject(error);
1666
+ };
1667
+ const offMessage = this.onMessage((message) => {
1668
+ if (!sameRuntime(message.runtime, command.runtime)) {
1669
+ return;
1670
+ }
1671
+ if (message.type === "stream_delta") {
1672
+ observedTurnEvidence = true;
1673
+ const runId = streamDeltaRunId(message);
1674
+ if (runId)
1675
+ runIds.add(runId);
1676
+ const messageType = streamDeltaMessageType(message);
1677
+ if (messageType === "loop_error" || messageType === "error_message") {
1678
+ fail(new Error(streamDeltaErrorMessage(message)));
1679
+ return;
1680
+ }
1681
+ if (messageType === "stop_reason") {
1682
+ const stopReason = streamDeltaStopReason(message);
1683
+ if (stopReason === "requires_approval") {
1684
+ observedRequiresApprovalStop = true;
1685
+ return;
1686
+ }
1687
+ finish("stop_reason", message, stopReason);
1688
+ }
1689
+ return;
1690
+ }
1691
+ if (message.type === "update_loop_status") {
1692
+ const hadTurnEvidenceBeforeLoopStatus = observedTurnEvidence || observedRequiresApprovalStop;
1693
+ if (!hadTurnEvidenceBeforeLoopStatus && (isWaitingOnApprovalLoopStatus(message) || options.allowLoopStatusFallback === true && isWaitingLoopStatus(message))) {
1694
+ return;
1695
+ }
1696
+ for (const runId of message.loop_status.active_run_ids) {
1697
+ observedTurnEvidence = true;
1698
+ runIds.add(runId);
1699
+ }
1700
+ if (hadTurnEvidenceBeforeLoopStatus && isWaitingOnApprovalLoopStatus(message)) {
1701
+ finish("loop_status_waiting_on_approval", message, "requires_approval");
1702
+ return;
1703
+ }
1704
+ if (options.allowLoopStatusFallback === true && hadTurnEvidenceBeforeLoopStatus && isWaitingLoopStatus(message)) {
1705
+ finish("loop_status_waiting_fallback", message, null);
1706
+ }
1707
+ }
1708
+ });
1709
+ try {
1710
+ this.input(commandWithIds.command);
1711
+ } catch (error) {
1712
+ fail(error instanceof Error ? error : new Error(String(error)));
1713
+ }
1714
+ });
1715
+ }
1716
+ withClientMessageIds(command) {
1717
+ if (command.payload.kind !== "create_message") {
1718
+ return { command, clientMessageIds: [] };
1719
+ }
1720
+ const clientMessageIds = [];
1721
+ const messages = command.payload.messages.map((message) => {
1722
+ if (message.role !== "user")
1723
+ return message;
1724
+ const existing = message.client_message_id;
1725
+ const clientMessageId = typeof existing === "string" && existing.length > 0 ? existing : this.nextRequestId("client-message");
1726
+ clientMessageIds.push(clientMessageId);
1727
+ return { ...message, client_message_id: clientMessageId };
1728
+ });
1729
+ return {
1730
+ command: {
1731
+ ...command,
1732
+ payload: { ...command.payload, messages }
1733
+ },
1734
+ clientMessageIds
1735
+ };
1736
+ }
1737
+ handleMessage(event, channel) {
1738
+ const message = parseProtocolMessage(event);
1739
+ for (const handler of this.messageHandlers) {
1740
+ handler(message, channel);
1741
+ }
1742
+ const requestId = message && typeof message === "object" && "request_id" in message ? message.request_id : undefined;
1743
+ if (channel !== "control" || typeof requestId !== "string") {
1744
+ return;
1745
+ }
1746
+ const pending = this.pending.get(requestId);
1747
+ if (!pending || pending.predicate && !pending.predicate(message)) {
1748
+ return;
1749
+ }
1750
+ clearTimeout(pending.timeout);
1751
+ this.pending.delete(requestId);
1752
+ pending.resolve(message);
1753
+ }
1754
+ rejectAllPending(reason) {
1755
+ for (const [requestId, pending] of this.pending) {
1756
+ clearTimeout(pending.timeout);
1757
+ this.pending.delete(requestId);
1758
+ pending.reject(new Error(reason));
1759
+ }
1760
+ }
1761
+ }
1762
+ function createAppServerClient(options) {
1763
+ return new AppServerClient(options);
1764
+ }
1765
+
1766
+ // src/local-app-server.ts
1767
+ import { spawn as spawn2 } from "node:child_process";
1768
+
1769
+ // src/cli-resolver.ts
1770
+ import { existsSync } from "node:fs";
1771
+ import { createRequire as createRequire2 } from "node:module";
1772
+ import { dirname, join } from "node:path";
1773
+ import { fileURLToPath } from "node:url";
1774
+ var require2 = createRequire2(import.meta.url);
1775
+ var __filename2 = fileURLToPath(import.meta.url);
1776
+ var __dirname2 = dirname(__filename2);
1777
+ function findLettaCli() {
1778
+ const envPath = process.env.LETTA_CLI_PATH;
1779
+ if (envPath && existsSync(envPath)) {
1780
+ return envPath;
1781
+ }
1782
+ try {
1783
+ return require2.resolve("@letta-ai/letta-code");
1784
+ } catch {}
1785
+ const localPaths = [
1786
+ join(__dirname2, "..", "node_modules", "@letta-ai", "letta-code", "letta.js"),
1787
+ join(__dirname2, "../../@letta-ai/letta-code/letta.js"),
1788
+ join(__dirname2, "../../../letta-code-prod/letta.js"),
1789
+ join(__dirname2, "../../../letta-code/letta.js"),
1790
+ join(__dirname2, "..", "..", "letta-code", "letta.js")
1791
+ ];
1792
+ for (const path of localPaths) {
1793
+ if (existsSync(path)) {
1794
+ return path;
1795
+ }
1796
+ }
1797
+ throw new Error("Could not find Letta Code CLI. Set LETTA_CLI_PATH environment variable or install @letta-ai/letta-code.");
1798
+ }
1799
+
1800
+ // src/local-app-server.ts
1801
+ var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0";
1802
+ var DEFAULT_STARTUP_TIMEOUT_MS = 30000;
1803
+ var LISTENING_RE = /^Listening on\s+(ws:\/\/\S+)\s*$/m;
1804
+ function appendLine(buffer, chunk) {
1805
+ return buffer + String(chunk);
1806
+ }
1807
+ function tryExtractListeningUrl(output) {
1808
+ const match = output.match(LISTENING_RE);
1809
+ return match?.[1] ?? null;
1810
+ }
1811
+ function buildLocalAppServerArgs(cliPath, options = {}) {
1812
+ return [
1813
+ cliPath,
1814
+ ...options.backend !== undefined ? ["--backend", options.backend] : [],
1815
+ "app-server",
1816
+ "--listen",
1817
+ options.listen ?? DEFAULT_LISTEN_URL
1818
+ ];
1819
+ }
1820
+ function terminateProcess(child) {
1821
+ if (child.exitCode !== null || child.signalCode !== null)
1822
+ return;
1823
+ child.kill("SIGTERM");
1824
+ setTimeout(() => {
1825
+ if (child.exitCode === null && child.signalCode === null) {
1826
+ child.kill("SIGKILL");
1827
+ }
1828
+ }, 1000).unref?.();
1829
+ }
1830
+ function startLocalAppServer(options = {}) {
1831
+ const cliPath = options.cliPath ?? findLettaCli();
1832
+ const args = buildLocalAppServerArgs(cliPath, options);
1833
+ const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
1834
+ return new Promise((resolve, reject) => {
1835
+ const child = spawn2(process.execPath, args, {
1836
+ stdio: ["ignore", "pipe", "pipe"],
1837
+ env: { ...process.env, ...options.env ?? {} }
1838
+ });
1839
+ let settled = false;
1840
+ let output = "";
1841
+ const cleanup = () => {
1842
+ child.stdout?.off("data", onStdout);
1843
+ child.stderr?.off("data", onStderr);
1844
+ child.off("error", onError);
1845
+ child.off("exit", onExit);
1846
+ clearTimeout(timeout);
1847
+ };
1848
+ const fail = (error) => {
1849
+ if (settled)
1850
+ return;
1851
+ settled = true;
1852
+ cleanup();
1853
+ terminateProcess(child);
1854
+ reject(error);
1855
+ };
1856
+ const succeed = (url) => {
1857
+ if (settled)
1858
+ return;
1859
+ settled = true;
1860
+ cleanup();
1861
+ resolve({
1862
+ url,
1863
+ close: () => terminateProcess(child)
1864
+ });
1865
+ };
1866
+ const onOutput = (chunk) => {
1867
+ output = appendLine(output, chunk);
1868
+ const url = tryExtractListeningUrl(output);
1869
+ if (url)
1870
+ succeed(url);
1871
+ };
1872
+ const onStdout = (chunk) => onOutput(chunk);
1873
+ const onStderr = (chunk) => {
1874
+ output = appendLine(output, chunk);
1875
+ };
1876
+ const onError = (error) => fail(error);
1877
+ const onExit = (code, signal) => {
1878
+ if (settled)
1879
+ return;
1880
+ fail(new Error(`Local Letta Code app-server exited before listening (code=${code ?? "null"}, signal=${signal ?? "null"}).${output ? ` Output:
1881
+ ${output.trim()}` : ""}`));
1882
+ };
1883
+ const timeout = setTimeout(() => {
1884
+ fail(new Error(`Timed out waiting for local Letta Code app-server to start.${output ? ` Output:
1885
+ ${output.trim()}` : ""}`));
1886
+ }, startupTimeoutMs);
1887
+ child.stdout?.on("data", onStdout);
1888
+ child.stderr?.on("data", onStderr);
1889
+ child.once("error", onError);
1890
+ child.once("exit", onExit);
1891
+ });
1892
+ }
1893
+
1894
+ // src/remote-client-session-core.ts
1895
+ var FAILURE_STOP_REASONS = new Set([
1896
+ "error",
1897
+ "llm_api_error",
1898
+ "max_steps",
1899
+ "interrupted",
1900
+ "cancelled",
1901
+ "canceled"
1902
+ ]);
1903
+ var REASONING_EFFORTS = new Set([
1904
+ "none",
1905
+ "minimal",
1906
+ "low",
1907
+ "medium",
1908
+ "high",
1909
+ "xhigh"
1910
+ ]);
1911
+ var KNOWN_SDK_ERROR_CODES2 = new Set([
1912
+ "approval_conflict",
1913
+ "approval_conflict_terminal",
1914
+ "protocol_error",
1915
+ "error",
1916
+ "llm_api_error",
1917
+ "max_steps",
1918
+ "interrupted",
1919
+ "stream_closed"
1920
+ ]);
1921
+ function mapPermissionMode(mode) {
1922
+ if (mode === undefined || mode === "default")
1923
+ return;
1924
+ if (mode === "acceptEdits")
1925
+ return "acceptEdits";
1926
+ if (mode === "bypassPermissions")
1927
+ return "unrestricted";
1928
+ if (mode === "plan")
1929
+ return "memory";
1930
+ return;
1931
+ }
1932
+ function ensureSuccess(message, fallback) {
1933
+ if (message.success === false) {
1934
+ throw new Error(typeof message.error === "string" ? message.error : fallback);
1935
+ }
1936
+ }
1937
+ function toSdkErrorCode2(value) {
1938
+ if (!value || value.length === 0)
1939
+ return;
1940
+ return KNOWN_SDK_ERROR_CODES2.has(value) ? value : undefined;
1941
+ }
1942
+ function isReasoningEffort(value) {
1943
+ return typeof value === "string" && REASONING_EFFORTS.has(value);
1944
+ }
1945
+ function nonEmptyString(value, name) {
1946
+ if (value === undefined)
1947
+ return;
1948
+ if (typeof value !== "string" || value.length === 0) {
1949
+ throw new Error(`Invalid ${name}. Expected a non-empty string.`);
1950
+ }
1951
+ return value;
1952
+ }
1953
+ function normalizeUpdateModelInput(update) {
1954
+ if (typeof update === "string") {
1955
+ if (update.length === 0) {
1956
+ throw new Error("Invalid model. Expected a non-empty string.");
1957
+ }
1958
+ return { model: update };
1959
+ }
1960
+ if (!update || typeof update !== "object" || Array.isArray(update)) {
1961
+ throw new Error("Invalid updateModel options. Expected a model string or options object.");
1962
+ }
1963
+ const model = nonEmptyString(update.model, "model");
1964
+ const modelId = nonEmptyString(update.modelId, "modelId");
1965
+ const modelHandle = nonEmptyString(update.modelHandle, "modelHandle");
1966
+ const reasoningEffort = update.reasoningEffort;
1967
+ if (reasoningEffort !== undefined && !isReasoningEffort(reasoningEffort)) {
1968
+ throw new Error(`Invalid reasoningEffort '${String(reasoningEffort)}'. Valid values: ${[...REASONING_EFFORTS].join(", ")}`);
1969
+ }
1970
+ if (model !== undefined && (modelId !== undefined || modelHandle !== undefined)) {
1971
+ throw new Error("Invalid updateModel options. Use either model or explicit modelId/modelHandle, not both.");
1972
+ }
1973
+ if (model === undefined && modelId === undefined && modelHandle === undefined && reasoningEffort === undefined) {
1974
+ throw new Error("Invalid updateModel options. Provide model, modelId, modelHandle, or reasoningEffort.");
1975
+ }
1976
+ return {
1977
+ ...model !== undefined ? { model } : {},
1978
+ ...modelId !== undefined ? { modelId } : {},
1979
+ ...modelHandle !== undefined ? { modelHandle } : {},
1980
+ ...reasoningEffort !== undefined ? { reasoningEffort } : {}
1981
+ };
1982
+ }
1983
+ function modelPayloadWithoutReasoning(input) {
1984
+ const payload = {};
1985
+ if (input.modelId !== undefined)
1986
+ payload.model_id = input.modelId;
1987
+ if (input.modelHandle !== undefined)
1988
+ payload.model_handle = input.modelHandle;
1989
+ if (input.model !== undefined) {
1990
+ if (input.model.includes("/"))
1991
+ payload.model_handle = input.model;
1992
+ else
1993
+ payload.model_id = input.model;
1994
+ }
1995
+ return payload;
1996
+ }
1997
+ function toBaseModelHandle(handle, byokProviderAliases) {
1998
+ if (!handle)
1999
+ return;
2000
+ const slashIndex = handle.indexOf("/");
2001
+ if (slashIndex === -1)
2002
+ return handle;
2003
+ const provider = handle.slice(0, slashIndex);
2004
+ const model = handle.slice(slashIndex + 1);
2005
+ const baseProvider = byokProviderAliases?.[provider];
2006
+ return baseProvider ? `${baseProvider}/${model}` : handle;
2007
+ }
2008
+ function getContextWindow(value) {
2009
+ const contextWindow = value?.context_window;
2010
+ return typeof contextWindow === "number" ? contextWindow : undefined;
2011
+ }
2012
+ function getReasoningEffort(entry) {
2013
+ const effort = entry.updateArgs?.reasoning_effort;
2014
+ return typeof effort === "string" ? effort : undefined;
2015
+ }
2016
+ function sameContextCandidates(candidates, contextWindow) {
2017
+ if (contextWindow === undefined)
2018
+ return candidates;
2019
+ const matches = candidates.filter((entry) => getContextWindow(entry.updateArgs) === contextWindow);
2020
+ return matches.length > 0 ? matches : candidates;
2021
+ }
2022
+ function isApprovalConflictSignal2(params) {
2023
+ if (params.stopReason === "requires_approval")
2024
+ return true;
2025
+ const haystack = [params.detail, params.message].filter((value) => typeof value === "string" && value.length > 0).join(`
2026
+ `).toLowerCase();
2027
+ return haystack.includes("waiting for approval on a tool call") || haystack.includes("cannot send a new message") || haystack.includes("requires_approval");
2028
+ }
2029
+ function resolveDreamingSettings(dreaming) {
2030
+ if (!dreaming)
2031
+ return null;
2032
+ return {
2033
+ trigger: dreaming.trigger ?? "step-count",
2034
+ step_count: dreaming.stepCount ?? 5
2035
+ };
2036
+ }
2037
+ function extractTextFromContent(content) {
2038
+ if (typeof content === "string")
2039
+ return content;
2040
+ if (Array.isArray(content)) {
2041
+ const pieces = [];
2042
+ for (const part of content) {
2043
+ if (typeof part === "string") {
2044
+ pieces.push(part);
2045
+ continue;
2046
+ }
2047
+ if (part && typeof part === "object") {
2048
+ const record = part;
2049
+ if (typeof record.text === "string") {
2050
+ pieces.push(record.text);
2051
+ }
2052
+ }
2053
+ }
2054
+ const joined = pieces.join("");
2055
+ return joined.length > 0 ? joined : null;
2056
+ }
2057
+ if (content && typeof content === "object") {
2058
+ const record = content;
2059
+ if (typeof record.text === "string")
2060
+ return record.text;
2061
+ }
2062
+ return null;
2063
+ }
2064
+ function toolInputFromArguments(args) {
2065
+ if (args && typeof args === "object" && !Array.isArray(args)) {
2066
+ return { input: args };
2067
+ }
2068
+ const raw = typeof args === "string" ? args : "";
2069
+ if (!raw)
2070
+ return { input: {} };
2071
+ try {
2072
+ const parsed = JSON.parse(raw);
2073
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2074
+ return { input: parsed, raw };
2075
+ }
2076
+ } catch {}
2077
+ return { input: { raw }, raw };
2078
+ }
2079
+ function firstToolCall(delta) {
2080
+ const toolCalls = delta.tool_calls;
2081
+ if (Array.isArray(toolCalls)) {
2082
+ const first = toolCalls[0];
2083
+ return first && typeof first === "object" ? first : undefined;
2084
+ }
2085
+ if (toolCalls && typeof toolCalls === "object") {
2086
+ return toolCalls;
2087
+ }
2088
+ const toolCall = delta.tool_call;
2089
+ return toolCall && typeof toolCall === "object" ? toolCall : undefined;
2090
+ }
2091
+ function firstToolReturn(delta) {
2092
+ const toolReturns = delta.tool_returns;
2093
+ if (Array.isArray(toolReturns)) {
2094
+ const first = toolReturns[0];
2095
+ return first && typeof first === "object" ? first : undefined;
2096
+ }
2097
+ return;
2098
+ }
2099
+ function sameRuntime2(message, runtime) {
2100
+ const msgRuntime = message.runtime;
2101
+ if (msgRuntime) {
2102
+ return msgRuntime.agent_id === runtime.agent_id && msgRuntime.conversation_id === runtime.conversation_id;
2103
+ }
2104
+ const messageAgentId = typeof message.agent_id === "string" ? message.agent_id : typeof message.agentId === "string" ? message.agentId : undefined;
2105
+ const messageConversationId = typeof message.conversation_id === "string" ? message.conversation_id : typeof message.conversationId === "string" ? message.conversationId : undefined;
2106
+ if (messageAgentId && messageAgentId !== runtime.agent_id)
2107
+ return false;
2108
+ if (messageConversationId && messageConversationId !== runtime.conversation_id)
2109
+ return false;
2110
+ return true;
2111
+ }
2112
+ function streamDeltaRecord(message) {
2113
+ if (message.type !== "stream_delta")
2114
+ return null;
2115
+ const delta = message.delta;
2116
+ return delta && typeof delta === "object" && !Array.isArray(delta) ? delta : null;
2117
+ }
2118
+ function streamDeltaMessageType2(delta) {
2119
+ return typeof delta.message_type === "string" ? delta.message_type : undefined;
2120
+ }
2121
+ function streamDeltaRunId2(delta) {
2122
+ return typeof delta.run_id === "string" ? delta.run_id : undefined;
2123
+ }
2124
+ function streamDeltaStopReason2(delta) {
2125
+ return typeof delta.stop_reason === "string" ? delta.stop_reason : undefined;
2126
+ }
2127
+ function loopStatusRecord(message) {
2128
+ if (message.type !== "update_loop_status")
2129
+ return null;
2130
+ const loopStatus = message.loop_status;
2131
+ return loopStatus && typeof loopStatus === "object" && !Array.isArray(loopStatus) ? loopStatus : null;
2132
+ }
2133
+ function loopStatusValue(message) {
2134
+ const loopStatus = loopStatusRecord(message);
2135
+ return typeof loopStatus?.status === "string" ? loopStatus.status : undefined;
2136
+ }
2137
+ function loopStatusRunIds(message) {
2138
+ const activeRunIds = loopStatusRecord(message)?.active_run_ids;
2139
+ return Array.isArray(activeRunIds) ? activeRunIds.filter((runId) => typeof runId === "string") : [];
2140
+ }
2141
+ function queueItems(message) {
2142
+ const queue = message.queue;
2143
+ if (!Array.isArray(queue))
2144
+ return [];
2145
+ return queue.flatMap((item) => {
2146
+ if (!item || typeof item !== "object" || Array.isArray(item))
2147
+ return [];
2148
+ const record = item;
2149
+ if (typeof record.id !== "string")
2150
+ return [];
2151
+ return [
2152
+ {
2153
+ id: record.id,
2154
+ clientMessageId: typeof record.client_message_id === "string" ? record.client_message_id : "",
2155
+ kind: typeof record.kind === "string" ? record.kind : "message",
2156
+ source: typeof record.source === "string" ? record.source : "user",
2157
+ content: record.content,
2158
+ enqueuedAt: typeof record.enqueued_at === "string" ? record.enqueued_at : ""
2159
+ }
2160
+ ];
2161
+ });
2162
+ }
2163
+ function normalizeSendMessage(message) {
2164
+ return message;
2165
+ }
2166
+
2167
+ class RemoteClientSessionCore {
2168
+ mode;
2169
+ controller = null;
2170
+ runtime = null;
2171
+ initialized = false;
2172
+ closed = false;
2173
+ activeTurnStartedAt = 0;
2174
+ _agentId = null;
2175
+ _sessionId = null;
2176
+ _conversationId = null;
2177
+ _model = "";
2178
+ _modelSettings = null;
2179
+ label;
2180
+ requestTimeoutMs;
2181
+ streamQueue = [];
2182
+ streamResolvers = [];
2183
+ removeMessageHandler = null;
2184
+ activeTurn = null;
2185
+ pendingTurns = [];
2186
+ nextTurnId = 0;
2187
+ messageCounter = 0;
2188
+ clientMessageCounter = 0;
2189
+ toolNames;
2190
+ constructor(mode, config) {
2191
+ this.mode = mode;
2192
+ this.label = config.label;
2193
+ this.requestTimeoutMs = config.requestTimeoutMs;
2194
+ }
2195
+ async initialize() {
2196
+ if (this.initialized) {
2197
+ throw new Error("Session already initialized");
2198
+ }
2199
+ if (this.closed) {
2200
+ throw new Error("Session is closed");
2201
+ }
2202
+ try {
2203
+ const init = await this.initializeRuntimeController();
2204
+ this.controller = init.controller;
2205
+ this.runtime = init.runtime;
2206
+ this._agentId = init.runtime.agent_id;
2207
+ this._conversationId = init.runtime.conversation_id;
2208
+ this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
2209
+ this._modelSettings = init.modelSettings ?? null;
2210
+ this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
2211
+ this.toolNames = init.tools;
2212
+ this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
2213
+ this.initialized = true;
2214
+ await this.afterRuntimeInitialized();
2215
+ await this.applyPostInitializeOptions();
2216
+ const initMessage = {
2217
+ type: "init",
2218
+ agentId: init.runtime.agent_id,
2219
+ sessionId: this._sessionId,
2220
+ conversationId: init.runtime.conversation_id,
2221
+ model: this._model
2222
+ };
2223
+ if (this.toolNames !== undefined)
2224
+ initMessage.tools = this.toolNames;
2225
+ return initMessage;
2226
+ } catch (error) {
2227
+ this.close();
2228
+ throw error;
2229
+ }
2230
+ }
2231
+ async send(message) {
2232
+ if (!this.initialized) {
2233
+ await this.initialize();
2234
+ }
2235
+ if (!this.controller || !this.runtime) {
2236
+ throw new Error("Session is not initialized");
2237
+ }
2238
+ await this.beforeTurn();
2239
+ const turn = this.trackSentTurn(this.runtime);
2240
+ try {
2241
+ this.controller.sendTurnMessage(this.runtime, message, {
2242
+ clientMessageId: turn.clientMessageId
2243
+ });
2244
+ } catch (error) {
2245
+ this.removeTrackedTurn(turn);
2246
+ throw error;
2247
+ }
2248
+ }
2249
+ async runTurn(message, _options = {}) {
2250
+ if (this.activeTurn || this.pendingTurns.length > 0) {
2251
+ throw new Error(`A turn is already in flight for this ${this.label} session. Use send() and stream() to let the listener queue messages.`);
2252
+ }
2253
+ await this.send(message);
2254
+ for await (const msg of this.stream()) {
2255
+ if (msg.type === "result") {
2256
+ return msg;
2257
+ }
2258
+ }
2259
+ return {
2260
+ type: "result",
2261
+ success: false,
2262
+ error: "stream_closed",
2263
+ errorCode: "stream_closed",
2264
+ recoverable: false,
2265
+ errorDetail: "Stream ended before terminal result",
2266
+ durationMs: Date.now() - this.activeTurnStartedAt,
2267
+ conversationId: this._conversationId
2268
+ };
2269
+ }
2270
+ async* stream() {
2271
+ while (true) {
2272
+ const msg = await this.nextMessage();
2273
+ if (!msg)
2274
+ break;
2275
+ yield msg;
2276
+ if (msg.type === "result")
2277
+ break;
2278
+ }
2279
+ }
2280
+ async abort() {
2281
+ if (!this.initialized)
2282
+ return;
2283
+ if (!this.controller || !this.runtime)
2284
+ return;
2285
+ if (this.activeTurn)
2286
+ this.activeTurn.abortRequested = true;
2287
+ await this.controller.abort(this.runtime);
2288
+ }
2289
+ async sendCommand(command, options) {
2290
+ if (!this.initialized) {
2291
+ await this.initialize();
2292
+ }
2293
+ if (!this.controller) {
2294
+ throw new Error("Session is not initialized");
2295
+ }
2296
+ if (!command || typeof command !== "object" || Array.isArray(command)) {
2297
+ throw new Error("Invalid command. Expected a protocol command object.");
2298
+ }
2299
+ if (typeof command.type !== "string" || command.type.length === 0) {
2300
+ throw new Error("Invalid command. Expected a non-empty type.");
2301
+ }
2302
+ if (!options || !options.responseType && !options.predicate && options.timeoutMs === undefined) {
2303
+ this.controller.send(command);
2304
+ return;
2305
+ }
2306
+ const { type, ...body } = command;
2307
+ const response = await this.controller.request(type, body, {
2308
+ ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {},
2309
+ predicate: options.predicate ? (message) => options.predicate?.(message) === true : options.responseType ? (message) => message.type === options.responseType : undefined
2310
+ });
2311
+ return response;
2312
+ }
2313
+ async listModels() {
2314
+ if (!this.initialized) {
2315
+ await this.initialize();
2316
+ }
2317
+ if (!this.controller) {
2318
+ throw new Error("Session is not initialized");
2319
+ }
2320
+ return this.controller.listModels();
2321
+ }
2322
+ async updateModel(update) {
2323
+ if (!this.initialized) {
2324
+ await this.initialize();
2325
+ }
2326
+ if (!this.controller || !this.runtime) {
2327
+ throw new Error("Session is not initialized");
2328
+ }
2329
+ const normalized = normalizeUpdateModelInput(update);
2330
+ const payload = await this.resolveUpdateModelPayload(normalized);
2331
+ const result = await this.controller.updateModel(this.runtime, payload);
2332
+ if (result.modelHandle !== undefined) {
2333
+ this._model = result.modelHandle;
2334
+ } else if (payload.model_handle !== undefined) {
2335
+ this._model = payload.model_handle;
2336
+ } else if (typeof result.modelSettings?.model === "string") {
2337
+ this._model = result.modelSettings.model;
2338
+ }
2339
+ if ("modelSettings" in result) {
2340
+ this._modelSettings = result.modelSettings ?? null;
2341
+ }
2342
+ return result;
2343
+ }
2344
+ async recoverPendingApprovals(options = {}) {
2345
+ if (!this.initialized) {
2346
+ await this.initialize();
2347
+ }
2348
+ if (!this.controller || !this.runtime) {
2349
+ throw new Error("Session is not initialized");
2350
+ }
2351
+ return this.controller.recoverPendingApprovals(this.runtime, options);
2352
+ }
2353
+ async listMessages(options = {}) {
2354
+ if (!this.initialized) {
2355
+ await this.initialize();
2356
+ }
2357
+ if (!this.controller) {
2358
+ throw new Error("Session is not initialized");
2359
+ }
2360
+ const conversationId = options.conversationId ?? this._conversationId;
2361
+ if (!conversationId) {
2362
+ throw new Error("No conversation id available for listMessages()");
2363
+ }
2364
+ return this.controller.listMessages(conversationId, options);
2365
+ }
2366
+ async bootstrapState(options = {}) {
2367
+ if (!this.initialized) {
2368
+ await this.initialize();
2369
+ }
2370
+ const page = await this.listMessages({
2371
+ limit: options.limit,
2372
+ order: options.order
2373
+ });
2374
+ const state = {
2375
+ agentId: this._agentId ?? "",
2376
+ conversationId: this._conversationId ?? "",
2377
+ model: this._model,
2378
+ messages: page.messages
2379
+ };
2380
+ if (this.toolNames !== undefined) {
2381
+ state.tools = this.toolNames;
2382
+ }
2383
+ if (page.nextBefore !== undefined) {
2384
+ state.nextBefore = page.nextBefore;
2385
+ }
2386
+ if (page.hasMore !== undefined) {
2387
+ state.hasMore = page.hasMore;
2388
+ }
2389
+ return state;
2390
+ }
2391
+ close() {
2392
+ if (this.closed)
2393
+ return;
2394
+ this.closed = true;
2395
+ this.removeMessageHandler?.();
2396
+ this.removeMessageHandler = null;
2397
+ if (this.activeTurn?.timeout)
2398
+ clearTimeout(this.activeTurn.timeout);
2399
+ for (const turn of this.pendingTurns) {
2400
+ if (turn.timeout)
2401
+ clearTimeout(turn.timeout);
2402
+ }
2403
+ this.activeTurn = null;
2404
+ this.pendingTurns.length = 0;
2405
+ this.controller?.close();
2406
+ this.controller = null;
2407
+ this.onCoreClose();
2408
+ this.resolveAll(null);
2409
+ }
2410
+ get agentId() {
2411
+ return this._agentId;
2412
+ }
2413
+ get sessionId() {
2414
+ return this._sessionId;
2415
+ }
2416
+ get conversationId() {
2417
+ return this._conversationId;
2418
+ }
2419
+ async[Symbol.asyncDispose]() {
2420
+ this.close();
2421
+ }
2422
+ async changeDeviceState(updates) {
2423
+ if (!this.initialized) {
2424
+ await this.initialize();
2425
+ }
2426
+ if (!this.controller || !this.runtime) {
2427
+ throw new Error("Session is not initialized");
2428
+ }
2429
+ const payload = {};
2430
+ if (updates.cwd !== undefined)
2431
+ payload.cwd = updates.cwd;
2432
+ const mode = mapPermissionMode(updates.permissionMode);
2433
+ if (mode !== undefined)
2434
+ payload.mode = mode;
2435
+ if (updates.agentId !== undefined)
2436
+ payload.agent_id = updates.agentId;
2437
+ if (updates.conversationId !== undefined)
2438
+ payload.conversation_id = updates.conversationId;
2439
+ this.controller.send({
2440
+ type: "change_device_state",
2441
+ runtime: this.runtime,
2442
+ payload
2443
+ });
2444
+ }
2445
+ async updateToolset(toolsetPreference) {
2446
+ if (!this.initialized) {
2447
+ await this.initialize();
2448
+ }
2449
+ if (!this.controller || !this.runtime) {
2450
+ throw new Error("Session is not initialized");
2451
+ }
2452
+ const response = await this.controller.request("update_toolset", {
2453
+ runtime: this.runtime,
2454
+ toolset_preference: toolsetPreference
2455
+ }, { predicate: (message) => message.type === "update_toolset_response" });
2456
+ ensureSuccess(response, "Failed to update toolset");
2457
+ }
2458
+ async afterRuntimeInitialized() {}
2459
+ async beforeTurn() {}
2460
+ onCoreClose() {}
2461
+ currentOptions() {
2462
+ return this.mode.options;
2463
+ }
2464
+ shouldEnableMemfs(options) {
2465
+ return false;
2466
+ }
2467
+ enableMemfsBody() {
2468
+ if (!this.runtime)
2469
+ return {};
2470
+ return { agent_id: this.runtime.agent_id };
2471
+ }
2472
+ setModel(model) {
2473
+ this._model = model;
2474
+ }
2475
+ trackSentTurn(runtime) {
2476
+ const turn = {
2477
+ id: ++this.nextTurnId,
2478
+ runtime,
2479
+ clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
2480
+ queuedAt: Date.now(),
2481
+ startedAt: 0,
2482
+ assistantText: "",
2483
+ runIds: new Set,
2484
+ observedTurnEvidence: false,
2485
+ observedRequiresApprovalStop: false,
2486
+ abortRequested: false,
2487
+ timeout: null
2488
+ };
2489
+ if (this.activeTurn) {
2490
+ this.pendingTurns.push(turn);
2491
+ } else {
2492
+ this.activateTurn(turn);
2493
+ }
2494
+ return turn;
2495
+ }
2496
+ activateTurn(turn) {
2497
+ this.activeTurn = turn;
2498
+ turn.startedAt = Date.now();
2499
+ this.activeTurnStartedAt = turn.startedAt;
2500
+ if (this.requestTimeoutMs !== undefined) {
2501
+ turn.timeout = setTimeout(() => {
2502
+ this.failTurn(turn, `Timed out waiting for ${this.label} turn`);
2503
+ }, this.requestTimeoutMs);
2504
+ turn.timeout.unref?.();
2505
+ }
2506
+ }
2507
+ activateNextTurnFromProtocol() {
2508
+ if (this.activeTurn)
2509
+ return this.activeTurn;
2510
+ const next = this.pendingTurns.shift();
2511
+ if (!next)
2512
+ return null;
2513
+ this.activateTurn(next);
2514
+ return next;
2515
+ }
2516
+ removeTrackedTurn(turn) {
2517
+ if (turn.timeout)
2518
+ clearTimeout(turn.timeout);
2519
+ if (this.activeTurn === turn) {
2520
+ this.activeTurn = null;
2521
+ return;
2522
+ }
2523
+ const index = this.pendingTurns.indexOf(turn);
2524
+ if (index !== -1)
2525
+ this.pendingTurns.splice(index, 1);
2526
+ }
2527
+ failTurn(turn, detail) {
2528
+ if (this.activeTurn !== turn)
2529
+ return;
2530
+ this.enqueue({
2531
+ type: "error",
2532
+ message: detail,
2533
+ errorCode: "error",
2534
+ stopReason: "error",
2535
+ errorDetail: detail,
2536
+ recoverable: false
2537
+ });
2538
+ this.completeActiveTurn({
2539
+ runtime: turn.runtime,
2540
+ stopReason: "error",
2541
+ runIds: [...turn.runIds],
2542
+ success: false,
2543
+ detail,
2544
+ errorCode: "error"
2545
+ });
2546
+ }
2547
+ completeActiveTurn(turn) {
2548
+ const active = this.activeTurn;
2549
+ if (!active)
2550
+ return;
2551
+ if (active.timeout) {
2552
+ clearTimeout(active.timeout);
2553
+ active.timeout = null;
2554
+ }
2555
+ this.enqueue(this.resultFromTurn(turn, active));
2556
+ this.activeTurn = null;
2557
+ }
2558
+ async resolveUpdateModelPayload(input) {
2559
+ if (input.reasoningEffort === undefined) {
2560
+ return modelPayloadWithoutReasoning(input);
2561
+ }
2562
+ if (!this.controller) {
2563
+ throw new Error("Session is not initialized");
2564
+ }
2565
+ const catalog = await this.controller.listModels();
2566
+ const byId = new Map(catalog.entries.map((entry) => [entry.id, entry]));
2567
+ const aliases = catalog.byokProviderAliases;
2568
+ let baseEntry;
2569
+ let explicitHandle;
2570
+ let targetHandle;
2571
+ if (input.modelId !== undefined) {
2572
+ baseEntry = byId.get(input.modelId);
2573
+ explicitHandle = input.modelHandle;
2574
+ targetHandle = baseEntry?.handle ?? toBaseModelHandle(input.modelHandle, aliases);
2575
+ } else if (input.modelHandle !== undefined) {
2576
+ explicitHandle = input.modelHandle;
2577
+ targetHandle = toBaseModelHandle(input.modelHandle, aliases);
2578
+ } else if (input.model !== undefined) {
2579
+ baseEntry = byId.get(input.model);
2580
+ if (baseEntry) {
2581
+ targetHandle = baseEntry.handle;
2582
+ } else {
2583
+ explicitHandle = input.model;
2584
+ targetHandle = toBaseModelHandle(input.model, aliases);
2585
+ }
2586
+ } else {
2587
+ explicitHandle = this._model || undefined;
2588
+ targetHandle = toBaseModelHandle(this._model || undefined, aliases);
2589
+ }
2590
+ if (!targetHandle) {
2591
+ throw new Error("reasoningEffort requires a current model or explicit model/modelId/modelHandle.");
2592
+ }
2593
+ const candidates = catalog.entries.filter((entry) => entry.handle === targetHandle || entry.handle === explicitHandle);
2594
+ if (candidates.length === 0) {
2595
+ throw new Error(`reasoningEffort requires a model from listModels(); no catalog entry found for ${targetHandle}.`);
2596
+ }
2597
+ const contextWindow = getContextWindow(baseEntry?.updateArgs) ?? getContextWindow(this._modelSettings);
2598
+ const scopedCandidates = sameContextCandidates(candidates, contextWindow);
2599
+ const matchingEntry = scopedCandidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort) ?? candidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort);
2600
+ if (!matchingEntry) {
2601
+ throw new Error(`No ${input.reasoningEffort} reasoning tier found for model ${targetHandle}.`);
2602
+ }
2603
+ const payload = { model_id: matchingEntry.id };
2604
+ if (explicitHandle !== undefined) {
2605
+ payload.model_handle = explicitHandle;
2606
+ }
2607
+ return payload;
2608
+ }
2609
+ async applyPostInitializeOptions() {
2610
+ if (!this.controller || !this.runtime)
2611
+ return;
2612
+ const options = this.currentOptions();
2613
+ if (this.shouldEnableMemfs(options)) {
2614
+ const response = await this.controller.request("enable_memfs", this.enableMemfsBody(), { predicate: (message) => message.type === "enable_memfs_response" });
2615
+ ensureSuccess(response, "Failed to enable memfs");
2616
+ }
2617
+ const dreamingSettings = resolveDreamingSettings(options.dreaming);
2618
+ if (dreamingSettings) {
2619
+ const response = await this.controller.request("set_reflection_settings", {
2620
+ runtime: this.runtime,
2621
+ settings: dreamingSettings,
2622
+ scope: "both"
2623
+ }, { predicate: (message) => message.type === "set_reflection_settings_response" });
2624
+ ensureSuccess(response, "Failed to update dreaming settings");
2625
+ }
2626
+ if (this.mode.kind !== "session")
2627
+ return;
2628
+ if (this.mode.options.model !== undefined || this.mode.options.reasoningEffort !== undefined) {
2629
+ await this.updateModel({
2630
+ ...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
2631
+ ...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
2632
+ });
2633
+ }
2634
+ }
2635
+ handleProtocolMessage = (message) => {
2636
+ if (!this.runtime || !sameRuntime2(message, this.runtime))
2637
+ return;
2638
+ if (message.type === "update_queue") {
2639
+ const sdkMessage2 = {
2640
+ type: "queue_update",
2641
+ queue: queueItems(message)
2642
+ };
2643
+ this.enqueue(sdkMessage2);
2644
+ return;
2645
+ }
2646
+ if (message.type === "update_loop_status") {
2647
+ this.handleLoopStatusMessage(message);
2648
+ return;
2649
+ }
2650
+ const delta = streamDeltaRecord(message);
2651
+ if (!delta)
2652
+ return;
2653
+ const active = this.activateNextTurnFromProtocol();
2654
+ if (active) {
2655
+ active.observedTurnEvidence = true;
2656
+ const runId = streamDeltaRunId2(delta);
2657
+ if (runId)
2658
+ active.runIds.add(runId);
2659
+ }
2660
+ const sdkMessage = this.transformStreamDelta(delta);
2661
+ if (sdkMessage) {
2662
+ this.enqueue(sdkMessage);
2663
+ }
2664
+ this.handleTurnTerminalDelta(delta, sdkMessage);
2665
+ };
2666
+ handleLoopStatusMessage(message) {
2667
+ const status = loopStatusValue(message);
2668
+ if (!status)
2669
+ return;
2670
+ const activeRunIds = loopStatusRunIds(message);
2671
+ const sdkMessage = {
2672
+ type: "loop_status",
2673
+ status,
2674
+ activeRunIds
2675
+ };
2676
+ this.enqueue(sdkMessage);
2677
+ const active = this.activeTurn;
2678
+ if (!active)
2679
+ return;
2680
+ for (const runId of activeRunIds)
2681
+ active.runIds.add(runId);
2682
+ const hadTurnEvidence = active.observedTurnEvidence || active.observedRequiresApprovalStop;
2683
+ if (!hadTurnEvidence)
2684
+ return;
2685
+ if (status === "WAITING_ON_APPROVAL") {
2686
+ this.completeActiveTurn({
2687
+ runtime: active.runtime,
2688
+ stopReason: "requires_approval",
2689
+ runIds: [...active.runIds]
2690
+ });
2691
+ return;
2692
+ }
2693
+ if (status === "WAITING_ON_INPUT" && active.abortRequested) {
2694
+ this.completeActiveTurn({
2695
+ runtime: active.runtime,
2696
+ stopReason: "interrupted",
2697
+ runIds: [...active.runIds],
2698
+ success: false,
2699
+ detail: "Interrupted",
2700
+ errorCode: "interrupted"
2701
+ });
2702
+ return;
2703
+ }
2704
+ if (status === "WAITING_ON_INPUT" && active.observedTurnEvidence) {
2705
+ this.completeActiveTurn({
2706
+ runtime: active.runtime,
2707
+ stopReason: null,
2708
+ runIds: [...active.runIds]
2709
+ });
2710
+ }
2711
+ }
2712
+ handleTurnTerminalDelta(delta, sdkMessage) {
2713
+ const active = this.activeTurn;
2714
+ if (!active)
2715
+ return;
2716
+ const messageType = streamDeltaMessageType2(delta);
2717
+ if (messageType === "stop_reason") {
2718
+ const stopReason = streamDeltaStopReason2(delta) ?? null;
2719
+ if (stopReason === "requires_approval") {
2720
+ active.observedRequiresApprovalStop = true;
2721
+ return;
2722
+ }
2723
+ this.completeActiveTurn({
2724
+ runtime: active.runtime,
2725
+ stopReason,
2726
+ runIds: [...active.runIds]
2727
+ });
2728
+ return;
2729
+ }
2730
+ if (sdkMessage?.type === "error") {
2731
+ this.completeActiveTurn({
2732
+ runtime: active.runtime,
2733
+ stopReason: sdkMessage.stopReason,
2734
+ runIds: [...active.runIds],
2735
+ success: false,
2736
+ detail: sdkMessage.errorDetail ?? sdkMessage.message,
2737
+ errorCode: sdkMessage.errorCode
2738
+ });
2739
+ }
2740
+ }
2741
+ transformStreamDelta(delta) {
2742
+ const messageType = typeof delta.message_type === "string" ? delta.message_type : undefined;
2743
+ const runId = typeof delta.run_id === "string" ? delta.run_id : undefined;
2744
+ const uuid = typeof delta.id === "string" ? delta.id : `${this.label}-${++this.messageCounter}`;
2745
+ if (messageType === "assistant_message") {
2746
+ const content = extractTextFromContent(delta.content);
2747
+ if (!content)
2748
+ return null;
2749
+ if (this.activeTurn)
2750
+ this.activeTurn.assistantText += content;
2751
+ return { type: "assistant", content, uuid, runId };
2752
+ }
2753
+ if (messageType === "reasoning_message") {
2754
+ const content = typeof delta.reasoning === "string" ? delta.reasoning : extractTextFromContent(delta.content);
2755
+ if (!content)
2756
+ return null;
2757
+ return { type: "reasoning", content, uuid, runId };
2758
+ }
2759
+ if (messageType === "tool_call_message" || messageType === "approval_request_message") {
2760
+ const toolCall = firstToolCall(delta);
2761
+ if (!toolCall)
2762
+ return null;
2763
+ const fn = toolCall.function && typeof toolCall.function === "object" ? toolCall.function : undefined;
2764
+ const toolCallId = (typeof toolCall.tool_call_id === "string" ? toolCall.tool_call_id : undefined) ?? (typeof toolCall.id === "string" ? toolCall.id : undefined);
2765
+ if (!toolCallId) {
2766
+ const detail = `Missing tool_call_id in ${messageType} (uuid=${uuid})`;
2767
+ return {
2768
+ type: "error",
2769
+ message: detail,
2770
+ errorCode: "protocol_error",
2771
+ stopReason: "protocol_error",
2772
+ runId,
2773
+ recoverable: false,
2774
+ errorDetail: detail
2775
+ };
2776
+ }
2777
+ const toolName = (typeof toolCall.name === "string" ? toolCall.name : undefined) ?? (typeof fn?.name === "string" ? fn.name : undefined) ?? "?";
2778
+ const { input, raw } = toolInputFromArguments(toolCall.arguments ?? fn?.arguments);
2779
+ return {
2780
+ type: "tool_call",
2781
+ toolCallId,
2782
+ toolName,
2783
+ toolInput: input,
2784
+ rawArguments: raw,
2785
+ uuid,
2786
+ runId
2787
+ };
2788
+ }
2789
+ if (messageType === "tool_return_message") {
2790
+ const toolReturn = firstToolReturn(delta) ?? delta;
2791
+ const toolCallId = (typeof delta.tool_call_id === "string" ? delta.tool_call_id : undefined) ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
2792
+ if (!toolCallId)
2793
+ return null;
2794
+ const content = extractTextFromContent(delta.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
2795
+ const status = typeof delta.status === "string" ? delta.status : toolReturn.status;
2796
+ return {
2797
+ type: "tool_result",
2798
+ toolCallId,
2799
+ content,
2800
+ isError: status === "error",
2801
+ uuid,
2802
+ runId
2803
+ };
2804
+ }
2805
+ if (messageType === "error_message" || messageType === "loop_error") {
2806
+ const detail = (typeof delta.detail === "string" ? delta.detail : undefined) ?? (typeof delta.message === "string" ? delta.message : undefined) ?? `${this.label} turn failed`;
2807
+ const stopReason = (typeof delta.stop_reason === "string" ? delta.stop_reason : undefined) ?? (typeof delta.error_type === "string" ? delta.error_type : undefined) ?? "error";
2808
+ const approvalConflict = isApprovalConflictSignal2({
2809
+ detail,
2810
+ message: typeof delta.message === "string" ? delta.message : undefined,
2811
+ stopReason
2812
+ });
2813
+ return {
2814
+ type: "error",
2815
+ message: detail,
2816
+ errorCode: approvalConflict ? "approval_conflict" : toSdkErrorCode2(stopReason),
2817
+ approvalConflict: approvalConflict || undefined,
2818
+ recoverable: approvalConflict ? true : false,
2819
+ errorDetail: detail,
2820
+ stopReason,
2821
+ runId
2822
+ };
2823
+ }
2824
+ if (messageType === "retry") {
2825
+ return {
2826
+ type: "retry",
2827
+ reason: typeof delta.reason === "string" ? delta.reason : "error",
2828
+ attempt: typeof delta.attempt === "number" ? delta.attempt : 0,
2829
+ maxAttempts: typeof delta.max_attempts === "number" ? delta.max_attempts : 0,
2830
+ delayMs: typeof delta.delay_ms === "number" ? delta.delay_ms : 0,
2831
+ runId
2832
+ };
2833
+ }
2834
+ if (messageType === "stop_reason" || messageType === "ping") {
2835
+ return null;
2836
+ }
2837
+ return {
2838
+ type: "stream_event",
2839
+ event: delta,
2840
+ uuid
2841
+ };
2842
+ }
2843
+ resultFromTurn(turn, tracker) {
2844
+ const stopReason = turn.stopReason ?? (turn.success === false ? "error" : undefined);
2845
+ const approvalConflict = isApprovalConflictSignal2({
2846
+ detail: turn.detail,
2847
+ stopReason
2848
+ });
2849
+ const success = turn.success !== undefined ? turn.success && !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "") : !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "");
2850
+ const errorCode = approvalConflict ? "approval_conflict" : turn.errorCode ?? toSdkErrorCode2(stopReason);
2851
+ return {
2852
+ type: "result",
2853
+ success,
2854
+ result: success ? tracker?.assistantText || undefined : undefined,
2855
+ error: success ? undefined : errorCode ?? stopReason ?? "error",
2856
+ errorCode: success ? undefined : errorCode ?? "error",
2857
+ approvalConflict: approvalConflict || undefined,
2858
+ recoverable: approvalConflict ? true : success ? undefined : false,
2859
+ errorDetail: success ? undefined : turn.detail,
2860
+ stopReason,
2861
+ durationMs: Date.now() - (tracker?.startedAt || this.activeTurnStartedAt),
2862
+ conversationId: turn.runtime.conversation_id,
2863
+ runIds: turn.runIds.length > 0 ? turn.runIds : undefined
2864
+ };
2865
+ }
2866
+ enqueue(message) {
2867
+ const resolver = this.streamResolvers.shift();
2868
+ if (resolver) {
2869
+ resolver(message);
2870
+ return;
2871
+ }
2872
+ this.streamQueue.push(message);
2873
+ }
2874
+ nextMessage() {
2875
+ const next = this.streamQueue.shift();
2876
+ if (next)
2877
+ return Promise.resolve(next);
2878
+ if (this.closed)
2879
+ return Promise.resolve(null);
2880
+ return new Promise((resolve) => {
2881
+ this.streamResolvers.push(resolve);
2882
+ });
2883
+ }
2884
+ resolveAll(value) {
2885
+ for (const resolve of this.streamResolvers.splice(0)) {
2886
+ resolve(value);
2887
+ }
2888
+ }
2889
+ }
2890
+
2891
+ // src/app-server-session.ts
2892
+ function agentToolNames(agent) {
2893
+ const tools = agent?.tools;
2894
+ if (!Array.isArray(tools))
2895
+ return;
2896
+ return tools.flatMap((tool) => {
2897
+ if (typeof tool === "string" && tool.length > 0)
2898
+ return [tool];
2899
+ if (!tool || typeof tool !== "object")
2900
+ return [];
2901
+ const name = tool.name;
2902
+ return typeof name === "string" && name.length > 0 ? [name] : [];
2903
+ });
2904
+ }
2905
+ var SDK_AGENT_ORIGIN_TAG2 = "origin:letta-code";
2906
+ function isPresetSystemPrompt(value) {
2907
+ return [
2908
+ "default",
2909
+ "letta-claude",
2910
+ "letta-codex",
2911
+ "letta-gemini",
2912
+ "claude",
2913
+ "codex",
2914
+ "gemini"
2915
+ ].includes(value);
2916
+ }
2917
+ function includeSdkAgentOriginTag2(tags) {
2918
+ const normalizedTags = [];
2919
+ let hasOriginTag = false;
2920
+ for (const tag of tags ?? []) {
2921
+ if (tag === SDK_AGENT_ORIGIN_TAG2) {
2922
+ if (hasOriginTag)
2923
+ continue;
2924
+ hasOriginTag = true;
2925
+ }
2926
+ normalizedTags.push(tag);
2927
+ }
2928
+ if (!hasOriginTag) {
2929
+ normalizedTags.push(SDK_AGENT_ORIGIN_TAG2);
2930
+ }
2931
+ return normalizedTags;
2932
+ }
2933
+ function assertRemoteCreateAgentOptionsSupported(options) {
2934
+ if (options.allowedTools !== undefined || options.disallowedTools !== undefined) {
2935
+ throw new Error("App-server createAgent() does not yet support allowedTools/disallowedTools.");
2936
+ }
2937
+ if (options.canUseTool !== undefined) {
2938
+ throw new Error("App-server createAgent() does not yet support canUseTool callbacks.");
2939
+ }
2940
+ if (options.skillSources !== undefined) {
2941
+ throw new Error("App-server createAgent() does not yet support skillSources overrides.");
2942
+ }
2943
+ if (options.systemInfoReminder !== undefined) {
2944
+ throw new Error("App-server createAgent() does not yet support systemInfoReminder overrides.");
2945
+ }
2946
+ if (options.dreaming?.behavior !== undefined) {
2947
+ throw new Error("App-server createAgent() does not yet support dreaming.behavior overrides.");
2948
+ }
2949
+ }
2950
+ function assertRemoteSessionOptionsSupported(action, options) {
2951
+ if (options.systemPrompt !== undefined) {
2952
+ throw new Error(`App-server ${action}() does not yet support systemPrompt overrides for existing agents.`);
2953
+ }
2954
+ if (options.allowedTools !== undefined || options.disallowedTools !== undefined) {
2955
+ throw new Error(`App-server ${action}() does not yet support allowedTools/disallowedTools.`);
2956
+ }
2957
+ if (options.skillSources !== undefined) {
2958
+ throw new Error(`App-server ${action}() does not yet support skillSources overrides.`);
2959
+ }
2960
+ if (options.systemInfoReminder !== undefined) {
2961
+ throw new Error(`App-server ${action}() does not yet support systemInfoReminder overrides.`);
2962
+ }
2963
+ if (options.dreaming?.behavior !== undefined) {
2964
+ throw new Error(`App-server ${action}() does not yet support dreaming.behavior overrides.`);
2965
+ }
2966
+ if (options.memfsStartup !== undefined) {
2967
+ throw new Error(`App-server ${action}() does not support memfsStartup.`);
2968
+ }
2969
+ if (options.includePartialMessages !== undefined) {
2970
+ throw new Error(`App-server ${action}() streams app-server deltas directly and does not support includePartialMessages.`);
2971
+ }
2972
+ }
2973
+ function normalizeMemoryBlock(block) {
2974
+ const normalized = { ...block };
2975
+ if (normalized.value === undefined && typeof normalized.content === "string") {
2976
+ normalized.value = normalized.content;
2977
+ }
2978
+ return normalized;
2979
+ }
2980
+ function createAgentBody(options, settings = {}) {
2981
+ assertRemoteCreateAgentOptionsSupported(options);
2982
+ const includeOriginTag = settings.includeSdkOriginTag ?? true;
2983
+ const body = {};
2984
+ if (includeOriginTag || options.tags !== undefined) {
2985
+ body.tags = includeOriginTag ? includeSdkAgentOriginTag2(options.tags) : options.tags;
2986
+ }
2987
+ if (options.model !== undefined)
2988
+ body.model = options.model;
2989
+ if (options.embedding !== undefined)
2990
+ body.embedding = options.embedding;
2991
+ if (options.systemPrompt !== undefined) {
2992
+ if (typeof options.systemPrompt === "string") {
2993
+ if (isPresetSystemPrompt(options.systemPrompt)) {
2994
+ throw new Error("App-server createAgent() does not yet support system prompt presets.");
2995
+ }
2996
+ body.system = options.systemPrompt;
2997
+ } else {
2998
+ throw new Error("App-server createAgent() does not yet support system prompt preset objects.");
2999
+ }
3000
+ }
3001
+ const memoryBlocks = [];
3002
+ const blockIds = [];
3003
+ for (const item of options.memory ?? []) {
3004
+ if (typeof item === "string") {
3005
+ throw new Error("App-server createAgent() does not yet support memory preset names.");
3006
+ }
3007
+ if ("blockId" in item) {
3008
+ blockIds.push(item.blockId);
3009
+ } else {
3010
+ memoryBlocks.push(normalizeMemoryBlock(item));
3011
+ }
3012
+ }
3013
+ if (options.persona !== undefined) {
3014
+ memoryBlocks.push({ label: "persona", value: options.persona });
3015
+ }
3016
+ if (options.human !== undefined) {
3017
+ memoryBlocks.push({ label: "human", value: options.human });
3018
+ }
3019
+ if (memoryBlocks.length > 0)
3020
+ body.memory_blocks = memoryBlocks;
3021
+ if (blockIds.length > 0)
3022
+ body.block_ids = blockIds;
3023
+ return body;
3024
+ }
3025
+ function externalToolGroups(tools) {
3026
+ if (!tools || tools.length === 0)
3027
+ return;
3028
+ return [
3029
+ {
3030
+ tools: tools.map((tool) => ({
3031
+ name: tool.name,
3032
+ label: tool.label,
3033
+ description: tool.description,
3034
+ parameters: tool.parameters
3035
+ }))
3036
+ }
3037
+ ];
3038
+ }
3039
+ function createExternalToolCallHandler(externalTools) {
3040
+ return async (request) => {
3041
+ const tool = externalTools.get(request.tool_name);
3042
+ if (!tool) {
3043
+ throw new Error(`Unknown external tool: ${request.tool_name}`);
3044
+ }
3045
+ const result = await tool.execute(request.tool_call_id, request.input);
3046
+ return {
3047
+ content: result.content.map((part) => ({
3048
+ type: part.type,
3049
+ ...part.text !== undefined ? { text: part.text } : {},
3050
+ ...part.data !== undefined ? { data: part.data } : {},
3051
+ ...part.mimeType !== undefined ? { mimeType: part.mimeType } : {}
3052
+ }))
3053
+ };
3054
+ };
3055
+ }
3056
+ async function resolveAppServerToolApproval(options, toolName, toolInput) {
3057
+ const hasCallback = typeof options.canUseTool === "function";
3058
+ const toolNeedsRuntimeUserInput = requiresRuntimeUserInput(toolName);
3059
+ if (toolNeedsRuntimeUserInput && !hasCallback) {
3060
+ return {
3061
+ behavior: "deny",
3062
+ message: "No canUseTool callback registered",
3063
+ interrupt: false
3064
+ };
3065
+ }
3066
+ if (options.permissionMode === "bypassPermissions" && !toolNeedsRuntimeUserInput) {
3067
+ return { behavior: "allow", updatedInput: null, updatedPermissions: [] };
3068
+ }
3069
+ if (hasCallback) {
3070
+ try {
3071
+ const result = await options.canUseTool(toolName, toolInput);
3072
+ if (result.behavior === "allow") {
3073
+ return {
3074
+ behavior: "allow",
3075
+ message: result.message,
3076
+ updatedInput: result.updatedInput ?? null,
3077
+ updatedPermissions: result.updatedPermissions ?? []
3078
+ };
3079
+ }
3080
+ return {
3081
+ behavior: "deny",
3082
+ message: result.message ?? "Denied by canUseTool callback",
3083
+ interrupt: result.interrupt ?? false
3084
+ };
3085
+ } catch (error) {
3086
+ return {
3087
+ behavior: "deny",
3088
+ message: error instanceof Error ? error.message : "Callback error",
3089
+ interrupt: false
3090
+ };
3091
+ }
3092
+ }
3093
+ if (isHeadlessAutoAllowTool(toolName)) {
3094
+ return { behavior: "allow", updatedInput: null, updatedPermissions: [] };
3095
+ }
3096
+ return {
3097
+ behavior: "deny",
3098
+ message: "No canUseTool callback registered",
3099
+ interrupt: false
3100
+ };
3101
+ }
3102
+ function permissionSuggestionId(value) {
3103
+ if (typeof value === "string")
3104
+ return value;
3105
+ if (!value || typeof value !== "object")
3106
+ return null;
3107
+ const record = value;
3108
+ if (typeof record.id === "string")
3109
+ return record.id;
3110
+ if (typeof record.suggestion_id === "string")
3111
+ return record.suggestion_id;
3112
+ if (typeof record.permission_suggestion_id === "string")
3113
+ return record.permission_suggestion_id;
3114
+ return null;
3115
+ }
3116
+ function toAppServerApprovalDecision(decision) {
3117
+ if (decision.behavior === "deny") {
3118
+ return {
3119
+ behavior: "deny",
3120
+ message: decision.message
3121
+ };
3122
+ }
3123
+ const selectedPermissionSuggestionIds = (decision.updatedPermissions ?? []).map(permissionSuggestionId).filter((id) => id !== null);
3124
+ return {
3125
+ behavior: "allow",
3126
+ ...decision.message !== undefined ? { message: decision.message } : {},
3127
+ updated_input: decision.updatedInput ?? null,
3128
+ selected_permission_suggestion_ids: selectedPermissionSuggestionIds
3129
+ };
3130
+ }
3131
+ function stringRecord(value) {
3132
+ if (!value || typeof value !== "object" || Array.isArray(value))
3133
+ return;
3134
+ const record = {};
3135
+ for (const [key, entry] of Object.entries(value)) {
3136
+ if (typeof entry === "string")
3137
+ record[key] = entry;
3138
+ }
3139
+ return record;
3140
+ }
3141
+ function objectRecord(value) {
3142
+ if (!value || typeof value !== "object" || Array.isArray(value))
3143
+ return;
3144
+ return { ...value };
3145
+ }
3146
+ function normalizeListModelsResponse(response) {
3147
+ if (!response.success) {
3148
+ throw new Error(response.error ?? "listModels failed");
3149
+ }
3150
+ const entries = Array.isArray(response.entries) ? response.entries.flatMap((entry) => {
3151
+ if (!entry || typeof entry !== "object")
3152
+ return [];
3153
+ const record = entry;
3154
+ if (typeof record.id !== "string" || typeof record.handle !== "string" || typeof record.label !== "string" || typeof record.description !== "string") {
3155
+ return [];
3156
+ }
3157
+ const updateArgs = objectRecord(record.updateArgs);
3158
+ return [
3159
+ {
3160
+ id: record.id,
3161
+ handle: record.handle,
3162
+ label: record.label,
3163
+ description: record.description,
3164
+ ...typeof record.isDefault === "boolean" ? { isDefault: record.isDefault } : {},
3165
+ ...typeof record.isFeatured === "boolean" ? { isFeatured: record.isFeatured } : {},
3166
+ ...typeof record.free === "boolean" ? { free: record.free } : {},
3167
+ ...updateArgs ? { updateArgs } : {}
3168
+ }
3169
+ ];
3170
+ }) : [];
3171
+ const result = { entries };
3172
+ if (response.available_handles === null) {
3173
+ result.availableHandles = null;
3174
+ } else if (Array.isArray(response.available_handles)) {
3175
+ result.availableHandles = response.available_handles.filter((handle) => typeof handle === "string");
3176
+ }
3177
+ const aliases = stringRecord(response.byok_provider_aliases);
3178
+ if (aliases)
3179
+ result.byokProviderAliases = aliases;
3180
+ return result;
3181
+ }
3182
+ function normalizeUpdateModelResponse(response) {
3183
+ if (!response.success) {
3184
+ throw new Error(response.error ?? "Failed to update model");
3185
+ }
3186
+ const result = {};
3187
+ if (response.applied_to === "agent" || response.applied_to === "conversation") {
3188
+ result.appliedTo = response.applied_to;
3189
+ }
3190
+ if (typeof response.model_id === "string")
3191
+ result.modelId = response.model_id;
3192
+ if (typeof response.model_handle === "string")
3193
+ result.modelHandle = response.model_handle;
3194
+ if (response.model_settings === null) {
3195
+ result.modelSettings = null;
3196
+ } else if (response.model_settings && typeof response.model_settings === "object") {
3197
+ result.modelSettings = { ...response.model_settings };
3198
+ }
3199
+ return result;
3200
+ }
3201
+ function runtimeScopeFromMessage(message) {
3202
+ if (message.runtime)
3203
+ return message.runtime;
3204
+ const agentId = typeof message.agent_id === "string" ? message.agent_id : null;
3205
+ const conversationId = typeof message.conversation_id === "string" ? message.conversation_id : null;
3206
+ if (agentId && conversationId) {
3207
+ return { agent_id: agentId, conversation_id: conversationId };
3208
+ }
3209
+ return null;
3210
+ }
3211
+ function registerAppServerControlRequestHandler(config) {
3212
+ return config.client.onMessage((rawMessage, channel) => {
3213
+ const message = rawMessage;
3214
+ if (channel !== "control" || message.type !== "control_request")
3215
+ return;
3216
+ respondToAppServerControlRequest(config, message).catch(() => {});
3217
+ });
3218
+ }
3219
+ async function respondToAppServerControlRequest(config, message) {
3220
+ const runtime = runtimeScopeFromMessage(message) ?? config.getRuntime();
3221
+ if (!runtime)
3222
+ return;
3223
+ const requestId = typeof message.request_id === "string" ? message.request_id : undefined;
3224
+ const request = message.request;
3225
+ if (!requestId || !request || typeof request !== "object")
3226
+ return;
3227
+ const requestRecord = request;
3228
+ if (requestRecord.subtype !== "can_use_tool")
3229
+ return;
3230
+ const toolName = typeof requestRecord.tool_name === "string" ? requestRecord.tool_name : "unknown";
3231
+ const toolInput = requestRecord.input && typeof requestRecord.input === "object" && !Array.isArray(requestRecord.input) ? requestRecord.input : {};
3232
+ const decision = await resolveAppServerToolApproval(config.getOptions(), toolName, toolInput);
3233
+ config.client.input({
3234
+ runtime,
3235
+ payload: {
3236
+ kind: "approval_response",
3237
+ request_id: requestId,
3238
+ decision: toAppServerApprovalDecision(decision)
3239
+ }
3240
+ });
3241
+ }
3242
+
3243
+ class AppServerRuntimeController {
3244
+ client;
3245
+ options;
3246
+ constructor(client, options) {
3247
+ this.client = client;
3248
+ this.options = options;
3249
+ }
3250
+ onMessage(handler) {
3251
+ return this.client.onMessage((message, channel) => {
3252
+ handler(message, channel);
3253
+ });
3254
+ }
3255
+ send(command) {
3256
+ this.client.send(command);
3257
+ }
3258
+ sendTurnMessage(runtime, message, options) {
3259
+ const payload = {
3260
+ kind: "create_message",
3261
+ messages: [
3262
+ {
3263
+ role: "user",
3264
+ content: normalizeSendMessage(message),
3265
+ client_message_id: options.clientMessageId
3266
+ }
3267
+ ]
3268
+ };
3269
+ this.client.input({
3270
+ runtime,
3271
+ payload
3272
+ });
3273
+ }
3274
+ async abort(runtime) {
3275
+ await this.client.abort({ runtime });
3276
+ }
3277
+ request(type, body, options = {}) {
3278
+ const request = this.client.request.bind(this.client);
3279
+ return request(type, body, options);
3280
+ }
3281
+ async listModels() {
3282
+ const response = await this.request("list_models", {}, { predicate: (message) => message.type === "list_models_response" });
3283
+ return normalizeListModelsResponse(response);
3284
+ }
3285
+ async updateModel(runtime, payload) {
3286
+ const response = await this.request("update_model", {
3287
+ runtime,
3288
+ payload
3289
+ }, { predicate: (message) => message.type === "update_model_response" });
3290
+ return normalizeUpdateModelResponse(response);
3291
+ }
3292
+ async recoverPendingApprovals(runtime, options = {}) {
3293
+ const response = await this.client.sync({
3294
+ runtime,
3295
+ recover_approvals: true,
3296
+ force_device_status: true
3297
+ }, options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {});
3298
+ if (!response.success) {
3299
+ return {
3300
+ recovered: false,
3301
+ unsupported: false,
3302
+ detail: response.error ?? "Failed to recover pending approvals"
3303
+ };
3304
+ }
3305
+ return { recovered: true, unsupported: false };
3306
+ }
3307
+ async listMessages(conversationId, options = {}) {
3308
+ const query = {};
3309
+ if (options.before !== undefined)
3310
+ query.before = options.before;
3311
+ if (options.after !== undefined)
3312
+ query.after = options.after;
3313
+ if (options.order !== undefined)
3314
+ query.order = options.order;
3315
+ if (options.limit !== undefined)
3316
+ query.limit = options.limit;
3317
+ const response = await this.request("conversation_messages_list", {
3318
+ conversation_id: conversationId,
3319
+ ...Object.keys(query).length > 0 ? { query } : {}
3320
+ }, { predicate: (message) => message.type === "conversation_messages_list_response" });
3321
+ if (!response.success) {
3322
+ throw new Error(response.error ?? "listMessages failed");
3323
+ }
3324
+ const result = {
3325
+ messages: response.messages ?? []
3326
+ };
3327
+ const nextBefore = typeof response.nextBefore === "string" || response.nextBefore === null ? response.nextBefore : typeof response.next_before === "string" || response.next_before === null ? response.next_before : undefined;
3328
+ if (nextBefore !== undefined)
3329
+ result.nextBefore = nextBefore;
3330
+ const hasMore = typeof response.hasMore === "boolean" ? response.hasMore : typeof response.has_more === "boolean" ? response.has_more : undefined;
3331
+ if (hasMore !== undefined)
3332
+ result.hasMore = hasMore;
3333
+ return result;
3334
+ }
3335
+ close() {
3336
+ this.client.close();
3337
+ }
3338
+ }
3339
+
3340
+ class AppServerSession extends RemoteClientSessionCore {
3341
+ remoteOptions;
3342
+ ownedAppServer = null;
3343
+ externalTools = new Map;
3344
+ removeExternalToolHandler = null;
3345
+ removeControlRequestHandler = null;
3346
+ constructor(remoteOptions, mode) {
3347
+ super(mode, {
3348
+ label: "app-server",
3349
+ requestTimeoutMs: remoteOptions.requestTimeoutMs
3350
+ });
3351
+ this.remoteOptions = remoteOptions;
3352
+ const tools = mode.options.tools;
3353
+ for (const tool of tools ?? []) {
3354
+ this.externalTools.set(tool.name, tool);
3355
+ }
3356
+ }
3357
+ shouldEnableMemfs(options) {
3358
+ return this.mode.kind === "create-agent";
3359
+ }
3360
+ async initializeRuntimeController() {
3361
+ const url = await this.resolveAppServerUrl();
3362
+ const client = createAppServerClient({
3363
+ url,
3364
+ ...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
3365
+ ...this.remoteOptions.WebSocket ? { WebSocket: this.remoteOptions.WebSocket } : {},
3366
+ ...this.remoteOptions.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.remoteOptions.requestTimeoutMs } : {}
3367
+ });
3368
+ this.removeControlRequestHandler = registerAppServerControlRequestHandler({
3369
+ client,
3370
+ getRuntime: () => this.runtime,
3371
+ getOptions: () => this.currentOptions()
3372
+ });
3373
+ if (this.externalTools.size > 0) {
3374
+ this.removeExternalToolHandler = client.onExternalToolCall(createExternalToolCallHandler(this.externalTools));
3375
+ }
3376
+ try {
3377
+ await client.connect();
3378
+ const response = await this.startRuntime(client);
3379
+ if (!response.success || !response.runtime) {
3380
+ throw new Error(response.error ?? "Failed to start app-server runtime");
3381
+ }
3382
+ const tools = agentToolNames(response.agent);
3383
+ return {
3384
+ controller: new AppServerRuntimeController(client, this.remoteOptions),
3385
+ runtime: response.runtime,
3386
+ model: typeof response.agent?.model === "string" ? response.agent.model : "",
3387
+ modelSettings: response.agent?.model_settings ?? null,
3388
+ ...tools !== undefined ? { tools } : {}
3389
+ };
3390
+ } catch (error) {
3391
+ this.removeExternalToolHandler?.();
3392
+ this.removeExternalToolHandler = null;
3393
+ this.removeControlRequestHandler?.();
3394
+ this.removeControlRequestHandler = null;
3395
+ client.close();
3396
+ throw error;
3397
+ }
3398
+ }
3399
+ onCoreClose() {
3400
+ this.removeExternalToolHandler?.();
3401
+ this.removeExternalToolHandler = null;
3402
+ this.removeControlRequestHandler?.();
3403
+ this.removeControlRequestHandler = null;
3404
+ this.ownedAppServer?.close();
3405
+ this.ownedAppServer = null;
3406
+ }
3407
+ async resolveAppServerUrl() {
3408
+ if (this.remoteOptions.url) {
3409
+ return this.remoteOptions.url;
3410
+ }
3411
+ if (this.remoteOptions.local !== true) {
3412
+ throw new Error("App-server session requires a url unless local app-server spawning is enabled.");
3413
+ }
3414
+ this.ownedAppServer = await startLocalAppServer({
3415
+ listen: this.remoteOptions.localListen,
3416
+ backend: this.remoteOptions.localBackend,
3417
+ startupTimeoutMs: this.remoteOptions.localStartupTimeoutMs,
3418
+ env: this.remoteOptions.localEnv
3419
+ });
3420
+ return this.ownedAppServer.url;
3421
+ }
3422
+ async startRuntime(client) {
3423
+ const command = await this.buildRuntimeStartCommand(client);
3424
+ const response = await client.runtimeStart(command);
3425
+ return response;
3426
+ }
3427
+ async buildRuntimeStartCommand(client) {
3428
+ const options = this.mode.options;
3429
+ const command = {
3430
+ client_info: {
3431
+ name: "@letta-ai/letta-agent-sdk",
3432
+ title: "Letta Agent SDK"
3433
+ },
3434
+ recover_approvals: false,
3435
+ force_device_status: true
3436
+ };
3437
+ const mode = mapPermissionMode(options.permissionMode);
3438
+ if (mode)
3439
+ command.mode = mode;
3440
+ if (options.cwd !== undefined)
3441
+ command.cwd = options.cwd;
3442
+ const groups = externalToolGroups(options.tools);
3443
+ if (groups)
3444
+ command.external_tools = groups;
3445
+ if (this.mode.kind === "create-agent") {
3446
+ command.create_agent = {
3447
+ body: createAgentBody(this.mode.options, {
3448
+ includeSdkOriginTag: this.remoteOptions.includeSdkOriginTag
3449
+ }),
3450
+ pin_global: this.remoteOptions.pinGlobalAgent ?? true
3451
+ };
3452
+ return command;
3453
+ }
3454
+ if (this.mode.agentId) {
3455
+ command.agent_id = this.mode.agentId;
3456
+ if (this.mode.newConversation) {
3457
+ command.create_conversation = { body: {} };
3458
+ } else if (this.mode.defaultConversation) {
3459
+ command.conversation_id = "default";
3460
+ }
3461
+ return command;
3462
+ }
3463
+ if (this.mode.conversationId) {
3464
+ const agentId = await this.resolveConversationAgentId(client, this.mode.conversationId);
3465
+ command.agent_id = agentId;
3466
+ command.conversation_id = this.mode.conversationId;
3467
+ return command;
3468
+ }
3469
+ throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
3470
+ }
3471
+ async resolveConversationAgentId(client, conversationId) {
3472
+ const request = client.request.bind(client);
3473
+ const response = await request("conversation_retrieve", { conversation_id: conversationId }, { predicate: (message) => message.type === "conversation_retrieve_response" });
3474
+ if (!response.success || !response.conversation?.agent_id) {
3475
+ throw new Error(response.error ?? `Failed to retrieve conversation ${conversationId}`);
3476
+ }
3477
+ return response.conversation.agent_id;
3478
+ }
3479
+ }
3480
+
3481
+ // src/remote.ts
3482
+ function getDefaultApiKey() {
3483
+ if (typeof process === "undefined") {
3484
+ return;
3485
+ }
3486
+ return process.env.LETTA_API_KEY;
3487
+ }
3488
+ function createHeaders(options) {
3489
+ const apiKey = options.apiKey ?? getDefaultApiKey();
3490
+ return {
3491
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
3492
+ ...options.headers ?? {}
3493
+ };
3494
+ }
3495
+ function getFetch(options) {
3496
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3497
+ if (!fetchImpl) {
3498
+ throw new Error("Remote environments require a fetch implementation");
3499
+ }
3500
+ return fetchImpl.bind(globalThis);
3501
+ }
3502
+ function normalizeBaseUrl2(baseUrl) {
3503
+ return (baseUrl ?? "https://api.letta.com").replace(/\/$/, "");
3504
+ }
3505
+ function ensureOnline(environment, target) {
3506
+ if (!environment.connectionId) {
3507
+ const label = "deviceId" in target ? target.deviceId : ("environmentId" in target) ? target.environmentId : ("connectionName" in target) ? target.connectionName : environment.deviceId;
3508
+ throw new Error(`Remote environment is offline: ${label}`);
3509
+ }
3510
+ return {
3511
+ connectionId: environment.connectionId,
3512
+ environment,
3513
+ target
3514
+ };
3515
+ }
3516
+ async function parseJsonResponse(response) {
3517
+ const text = await response.text();
3518
+ const body = text ? JSON.parse(text) : null;
3519
+ if (!response.ok) {
3520
+ const message = body && typeof body === "object" && "message" in body ? String(body.message) : response.statusText;
3521
+ throw new Error(`Letta API request failed (${response.status}): ${message}`);
3522
+ }
3523
+ return body;
3524
+ }
3525
+
3526
+ class RemoteEnvironmentClient {
3527
+ options;
3528
+ baseUrl;
3529
+ fetchImpl;
3530
+ constructor(options = {}) {
3531
+ this.options = options;
3532
+ this.baseUrl = normalizeBaseUrl2(options.baseUrl);
3533
+ this.fetchImpl = getFetch(options);
3534
+ }
3535
+ async listEnvironments() {
3536
+ const url = new URL(`${this.baseUrl}/v1/environments`);
3537
+ const response = await this.fetchImpl(url, {
3538
+ headers: createHeaders(this.options)
3539
+ });
3540
+ return parseJsonResponse(response);
3541
+ }
3542
+ async getEnvironmentByDeviceId(deviceId) {
3543
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/environments/${encodeURIComponent(deviceId)}`, { headers: createHeaders(this.options) });
3544
+ return parseJsonResponse(response);
3545
+ }
3546
+ async resolveEnvironment(target) {
3547
+ if ("connectionId" in target) {
3548
+ return { connectionId: target.connectionId, target };
3549
+ }
3550
+ if ("deviceId" in target) {
3551
+ return ensureOnline(await this.getEnvironmentByDeviceId(target.deviceId), target);
3552
+ }
3553
+ const { connections } = await this.listEnvironments();
3554
+ if ("environmentId" in target) {
3555
+ const match2 = connections.find((env) => env.id === target.environmentId);
3556
+ if (!match2) {
3557
+ throw new Error(`Remote environment not found: ${target.environmentId}`);
3558
+ }
3559
+ return ensureOnline(match2, target);
3560
+ }
3561
+ const matches = connections.filter((env) => env.connectionName === target.connectionName);
3562
+ if (matches.length === 0) {
3563
+ throw new Error(`Remote environment not found: ${target.connectionName}`);
3564
+ }
3565
+ if (matches.length > 1) {
3566
+ throw new Error(`Remote environment name is ambiguous: ${target.connectionName}`);
3567
+ }
3568
+ const match = matches[0];
3569
+ if (!match) {
3570
+ throw new Error(`Remote environment not found: ${target.connectionName}`);
3571
+ }
3572
+ return ensureOnline(match, target);
3573
+ }
3574
+ }
3575
+
3576
+ // src/cloud-session.ts
3577
+ var DEFAULT_CLOUD_API_BASE_URL = "https://api.letta.com";
3578
+ var DEFAULT_TURN_TIMEOUT_MS = 120000;
3579
+ var DEFAULT_PING_INTERVAL_MS = 30000;
3580
+ var DEFAULT_SANDBOX_TTL_MINUTES = 5;
3581
+ var MIN_SANDBOX_TTL_MINUTES = 1;
3582
+ var MAX_SANDBOX_TTL_MINUTES = 60;
3583
+ var DEFAULT_SANDBOX_READY_TIMEOUT_MS = 120000;
3584
+ var DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS = 1000;
3585
+ var SDK_AGENT_ORIGIN = "@letta-ai/letta-agent-sdk";
3586
+
3587
+ class CloudManagedSandboxOwnershipError extends Error {
3588
+ }
3589
+ function getDefaultApiKey2() {
3590
+ const env = globalThis.process?.env;
3591
+ return env?.LETTA_API_KEY ?? env?.LETTA_CLOUD_API_KEY;
3592
+ }
3593
+ function bearerTokenFromHeaders(headers) {
3594
+ const authorization = headers?.Authorization ?? headers?.authorization;
3595
+ if (!authorization)
3596
+ return;
3597
+ const match = /^Bearer\s+(.+)$/i.exec(authorization);
3598
+ return match?.[1];
3599
+ }
3600
+ function getCloudApiKey(options) {
3601
+ return options.apiKey ?? bearerTokenFromHeaders(options.headers) ?? getDefaultApiKey2();
3602
+ }
3603
+ function getFetch2(fetchOverride) {
3604
+ const resolved = fetchOverride ?? globalThis.fetch;
3605
+ if (!resolved) {
3606
+ throw new Error("No fetch implementation available for cloud backend.");
3607
+ }
3608
+ return resolved.bind(globalThis);
3609
+ }
3610
+ function getWebSocketConstructor(websocketOverride) {
3611
+ const resolved = websocketOverride ?? globalThis.WebSocket;
3612
+ if (!resolved) {
3613
+ throw new Error("No WebSocket implementation available for cloud backend.");
3614
+ }
3615
+ return resolved;
3616
+ }
3617
+ function normalizeCloudApiBaseUrl(url) {
3618
+ const parsed = new URL(url ?? DEFAULT_CLOUD_API_BASE_URL);
3619
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "");
3620
+ parsed.search = "";
3621
+ parsed.hash = "";
3622
+ return parsed.toString().replace(/\/$/, "");
3623
+ }
3624
+ function cloudHeaders(options) {
3625
+ const headers = {
3626
+ "Content-Type": "application/json",
3627
+ ...options.headers ?? {}
3628
+ };
3629
+ const apiKey = getCloudApiKey(options);
3630
+ if (apiKey && !headers.Authorization && !headers.authorization) {
3631
+ headers.Authorization = `Bearer ${apiKey}`;
3632
+ }
3633
+ return headers;
3634
+ }
3635
+ function cloudWebSocketHeaders(options) {
3636
+ const headers = { ...options.headers ?? {} };
3637
+ delete headers.authorization;
3638
+ delete headers.Authorization;
3639
+ const apiKey = getCloudApiKey(options);
3640
+ if (apiKey)
3641
+ headers.Authorization = `Bearer ${apiKey}`;
3642
+ return Object.keys(headers).length > 0 ? headers : undefined;
3643
+ }
3644
+ async function parseJsonResponse2(response) {
3645
+ const text = await response.text();
3646
+ if (!text)
3647
+ return null;
3648
+ try {
3649
+ return JSON.parse(text);
3650
+ } catch {
3651
+ return text;
3652
+ }
3653
+ }
3654
+ function responseErrorMessage(body, fallback) {
3655
+ if (body && typeof body === "object") {
3656
+ const record = body;
3657
+ const message = record.message ?? record.error ?? record.detail;
3658
+ const reasonText = record.reason_text;
3659
+ const pieces = [message, reasonText].filter((value) => typeof value === "string" && value.length > 0);
3660
+ if (pieces.length > 0)
3661
+ return pieces.join(": ");
3662
+ }
3663
+ return fallback;
3664
+ }
3665
+ function assertOkResponse(response, body, action) {
3666
+ if (!response.ok) {
3667
+ throw new Error(responseErrorMessage(body, `${action} failed with HTTP ${response.status}`));
3668
+ }
3669
+ }
3670
+ function validatePositiveInteger(value, name) {
3671
+ if (value !== undefined && (!Number.isInteger(value) || value <= 0)) {
3672
+ throw new Error(`Invalid ${name}. Expected a positive integer.`);
3673
+ }
3674
+ }
3675
+ function validateIntegerRange(value, name, min, max) {
3676
+ if (value === undefined)
3677
+ return;
3678
+ if (!Number.isInteger(value) || value < min || value > max) {
3679
+ throw new Error(`Invalid ${name}. Expected an integer between ${min} and ${max}.`);
3680
+ }
3681
+ }
3682
+ function validateCloudSandboxOptions(options, name) {
3683
+ if (options === undefined)
3684
+ return;
3685
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
3686
+ throw new Error(`Invalid ${name}. Expected an object.`);
3687
+ }
3688
+ validateIntegerRange(options.ttlMinutes, `${name}.ttlMinutes`, MIN_SANDBOX_TTL_MINUTES, MAX_SANDBOX_TTL_MINUTES);
3689
+ validatePositiveInteger(options.readyTimeoutMs, `${name}.readyTimeoutMs`);
3690
+ validatePositiveInteger(options.readyPollIntervalMs, `${name}.readyPollIntervalMs`);
3691
+ validatePositiveInteger(options.refreshIntervalMs, `${name}.refreshIntervalMs`);
3692
+ if (options.terminateOnClose !== undefined && typeof options.terminateOnClose !== "boolean") {
3693
+ throw new Error(`Invalid ${name}.terminateOnClose. Expected a boolean.`);
3694
+ }
3695
+ }
3696
+ function validateCloudClientOptions(options) {
3697
+ validatePositiveInteger(options.requestTimeoutMs, "requestTimeoutMs");
3698
+ validatePositiveInteger(options.appServer?.requestTimeoutMs, "appServer.requestTimeoutMs");
3699
+ validatePositiveInteger(options.appServer?.startupTimeoutMs, "appServer.startupTimeoutMs");
3700
+ validateCloudSandboxOptions(options.sandbox, "sandbox");
3701
+ if (options.environment !== undefined && options.sandbox !== undefined) {
3702
+ throw new Error("Constellation sessions cannot specify both environment and sandbox options.");
3703
+ }
3704
+ if (options.webSocketAuth !== undefined && options.webSocketAuth !== "header" && options.webSocketAuth !== "query") {
3705
+ throw new Error("Invalid webSocketAuth. Valid values: header, query.");
3706
+ }
3707
+ }
3708
+ function environmentToRemoteTarget(environment) {
3709
+ if (typeof environment === "string") {
3710
+ return { connectionName: environment };
3711
+ }
3712
+ if ("name" in environment) {
3713
+ return { connectionName: environment.name };
3714
+ }
3715
+ if ("id" in environment) {
3716
+ return { environmentId: environment.id };
3717
+ }
3718
+ if ("connectionId" in environment) {
3719
+ return { connectionId: environment.connectionId };
3720
+ }
3721
+ if ("deviceId" in environment) {
3722
+ return { deviceId: environment.deviceId };
3723
+ }
3724
+ throw new Error("Unknown cloud environment selector.");
3725
+ }
3726
+ function buildCloudStatusWebSocketUrl(params) {
3727
+ const base = new URL(normalizeCloudApiBaseUrl(params.apiBaseUrl));
3728
+ if (base.protocol === "http:") {
3729
+ base.protocol = "ws:";
3730
+ } else if (base.protocol === "https:") {
3731
+ base.protocol = "wss:";
3732
+ } else if (base.protocol !== "ws:" && base.protocol !== "wss:") {
3733
+ throw new Error(`Unsupported cloud apiBaseUrl protocol: ${base.protocol}`);
3734
+ }
3735
+ base.pathname = `/v1/environments/${encodeURIComponent(params.connectionId)}/status/ws`;
3736
+ base.searchParams.set("agentId", params.agentId);
3737
+ base.searchParams.set("conversationId", params.conversationId);
3738
+ base.searchParams.set("channel", "stream");
3739
+ if (params.authMode === "query" && params.apiKey) {
3740
+ base.searchParams.set("token", params.apiKey);
3741
+ }
3742
+ return base.toString();
3743
+ }
3744
+ function addSocketListener(socket, type, listener) {
3745
+ if (socket.addEventListener) {
3746
+ socket.addEventListener(type, listener);
3747
+ return () => socket.removeEventListener?.(type, listener);
3748
+ }
3749
+ if (socket.on) {
3750
+ socket.on(type, listener);
3751
+ return () => socket.off?.(type, listener);
3752
+ }
3753
+ throw new Error("WebSocket implementation does not support event listeners.");
3754
+ }
3755
+ function messageEventData(event) {
3756
+ if (typeof event === "string")
3757
+ return event;
3758
+ if (event && typeof event === "object") {
3759
+ const data = event.data;
3760
+ if (typeof data === "string")
3761
+ return data;
3762
+ if (data instanceof ArrayBuffer) {
3763
+ return new TextDecoder().decode(data);
3764
+ }
3765
+ if (data instanceof Uint8Array) {
3766
+ return new TextDecoder().decode(data);
3767
+ }
3768
+ }
3769
+ return null;
3770
+ }
3771
+ function createCloudStatusWebSocketConstructor(params) {
3772
+ const WebSocketCtor = getWebSocketConstructor(params.cloudOptions.WebSocket);
3773
+ const authMode = params.cloudOptions.webSocketAuth ?? "header";
3774
+ const cloudHeaders2 = authMode === "header" ? cloudWebSocketHeaders(params.cloudOptions) : undefined;
3775
+ const pingIntervalMs = params.cloudOptions.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
3776
+ const state = {
3777
+ runtime: params.runtime,
3778
+ seenIdempotencyKeys: new Set,
3779
+ seenIdempotencyOrder: [],
3780
+ lastEventSeq: null,
3781
+ controlSocket: null
3782
+ };
3783
+
3784
+ class CloudStatusSocketAdapter {
3785
+ socket;
3786
+ channel;
3787
+ listenerRemovers = new Map;
3788
+ pingTimer = null;
3789
+ removeCloseListener = null;
3790
+ constructor(url, options) {
3791
+ this.channel = new URL(url).searchParams.get("channel");
3792
+ const mergedHeaders = authMode === "header" ? {
3793
+ ...options?.headers ?? {},
3794
+ ...cloudHeaders2 ?? {}
3795
+ } : undefined;
3796
+ const socketOptions = mergedHeaders && Object.keys(mergedHeaders).length > 0 ? { headers: mergedHeaders } : undefined;
3797
+ this.socket = new WebSocketCtor(url, socketOptions);
3798
+ if (this.channel === "control") {
3799
+ state.controlSocket = this;
3800
+ }
3801
+ this.removeCloseListener = addSocketListener(this.socket, "close", () => {
3802
+ this.stopPing();
3803
+ if (state.controlSocket === this)
3804
+ state.controlSocket = null;
3805
+ });
3806
+ this.startPing(pingIntervalMs);
3807
+ }
3808
+ get readyState() {
3809
+ return this.socket.readyState;
3810
+ }
3811
+ send(data) {
3812
+ this.socket.send(data);
3813
+ }
3814
+ close() {
3815
+ this.stopPing();
3816
+ this.removeCloseListener?.();
3817
+ this.removeCloseListener = null;
3818
+ if (state.controlSocket === this)
3819
+ state.controlSocket = null;
3820
+ this.socket.close();
3821
+ }
3822
+ addEventListener(type, listener) {
3823
+ const wrapped = type === "message" ? (event) => {
3824
+ if (this.handleIncomingMessage(event)) {
3825
+ listener(event);
3826
+ }
3827
+ } : listener;
3828
+ const remove = addSocketListener(this.socket, type, wrapped);
3829
+ let typeRemovers = this.listenerRemovers.get(type);
3830
+ if (!typeRemovers) {
3831
+ typeRemovers = new Map;
3832
+ this.listenerRemovers.set(type, typeRemovers);
3833
+ }
3834
+ typeRemovers.set(listener, remove);
3835
+ }
3836
+ removeEventListener(type, listener) {
3837
+ const typeRemovers = this.listenerRemovers.get(type);
3838
+ const remove = typeRemovers?.get(listener);
3839
+ if (!remove)
3840
+ return;
3841
+ remove();
3842
+ typeRemovers?.delete(listener);
3843
+ if (typeRemovers?.size === 0) {
3844
+ this.listenerRemovers.delete(type);
3845
+ }
3846
+ }
3847
+ handleIncomingMessage(event) {
3848
+ const data = messageEventData(event);
3849
+ if (!data)
3850
+ return true;
3851
+ let message;
3852
+ try {
3853
+ message = JSON.parse(data);
3854
+ } catch {
3855
+ return true;
3856
+ }
3857
+ this.ackIfSequenced(message);
3858
+ if (this.channel === "control" && message.type === "stream_delta") {
3859
+ return false;
3860
+ }
3861
+ if (this.isDuplicate(message))
3862
+ return false;
3863
+ this.trackEventSeq(message);
3864
+ return true;
3865
+ }
3866
+ ackIfSequenced(message) {
3867
+ if (typeof message.seq !== "number")
3868
+ return;
3869
+ this.sendCloudCommand({ type: "ack", seq: message.seq });
3870
+ }
3871
+ isDuplicate(message) {
3872
+ const idempotencyKey = typeof message.idempotency_key === "string" ? message.idempotency_key : undefined;
3873
+ if (!idempotencyKey)
3874
+ return false;
3875
+ if (state.seenIdempotencyKeys.has(idempotencyKey))
3876
+ return true;
3877
+ state.seenIdempotencyKeys.add(idempotencyKey);
3878
+ state.seenIdempotencyOrder.push(idempotencyKey);
3879
+ while (state.seenIdempotencyOrder.length > 1000) {
3880
+ const oldest = state.seenIdempotencyOrder.shift();
3881
+ if (oldest)
3882
+ state.seenIdempotencyKeys.delete(oldest);
3883
+ }
3884
+ return false;
3885
+ }
3886
+ trackEventSeq(message) {
3887
+ if (typeof message.event_seq !== "number")
3888
+ return;
3889
+ if (state.lastEventSeq !== null && message.event_seq > state.lastEventSeq + 1) {
3890
+ this.sendSync(true);
3891
+ }
3892
+ if (state.lastEventSeq === null || message.event_seq > state.lastEventSeq) {
3893
+ state.lastEventSeq = message.event_seq;
3894
+ }
3895
+ }
3896
+ sendSync(recoverApprovals) {
3897
+ const target = state.controlSocket?.readyState === 1 ? state.controlSocket : this;
3898
+ target.sendCloudCommand({
3899
+ type: "sync",
3900
+ runtime: state.runtime,
3901
+ recover_approvals: recoverApprovals,
3902
+ force_device_status: true
3903
+ });
3904
+ }
3905
+ sendCloudCommand(command) {
3906
+ if (this.readyState !== 1)
3907
+ return;
3908
+ try {
3909
+ this.socket.send(JSON.stringify(command));
3910
+ } catch {}
3911
+ }
3912
+ startPing(intervalMs) {
3913
+ if (this.pingTimer)
3914
+ return;
3915
+ this.pingTimer = setInterval(() => {
3916
+ this.sendCloudCommand({ type: "ping" });
3917
+ }, intervalMs);
3918
+ this.pingTimer.unref?.();
3919
+ }
3920
+ stopPing() {
3921
+ if (!this.pingTimer)
3922
+ return;
3923
+ clearInterval(this.pingTimer);
3924
+ this.pingTimer = null;
3925
+ }
3926
+ }
3927
+ return CloudStatusSocketAdapter;
3928
+ }
3929
+ function isCloudConversation(value) {
3930
+ return Boolean(value && typeof value === "object" && typeof value.id === "string");
3931
+ }
3932
+ function isCloudAgentSandbox(value) {
3933
+ return Boolean(value && typeof value === "object" && typeof value.sandboxId === "string" && typeof value.deviceId === "string" && typeof value.connectionName === "string");
3934
+ }
3935
+ function isCloudAgentSandboxRefresh(value) {
3936
+ return Boolean(value && typeof value === "object" && typeof value.success === "boolean" && typeof value.sandboxId === "string" && typeof value.ttlMinutes === "number");
3937
+ }
3938
+ function isRetryableManagedSandboxResolveError(error) {
3939
+ const message = error instanceof Error ? error.message : String(error);
3940
+ return message.includes("Remote environment is offline") || message.toLowerCase().includes("not found") || message.includes("(404)");
3941
+ }
3942
+ function sleep(ms) {
3943
+ return new Promise((resolve) => setTimeout(resolve, ms));
3944
+ }
3945
+ function externalToolsByName(tools) {
3946
+ const result = new Map;
3947
+ for (const tool of tools ?? []) {
3948
+ result.set(tool.name, tool);
3949
+ }
3950
+ return result;
3951
+ }
3952
+ function cloudHarnessAppServerOptions(clientOptions) {
3953
+ const appServer = clientOptions.appServer;
3954
+ const apiKey = getCloudApiKey(clientOptions);
3955
+ const localEnv = {
3956
+ ...apiKey ? { LETTA_API_KEY: apiKey } : {},
3957
+ ...clientOptions.apiBaseUrl ? { LETTA_BASE_URL: normalizeCloudApiBaseUrl(clientOptions.apiBaseUrl) } : {}
3958
+ };
3959
+ return {
3960
+ local: appServer?.url === undefined,
3961
+ localBackend: "api",
3962
+ ...appServer?.url !== undefined ? { url: appServer.url } : {},
3963
+ ...appServer?.WebSocket !== undefined ? { WebSocket: appServer.WebSocket } : {},
3964
+ ...clientOptions.requestTimeoutMs !== undefined || appServer?.requestTimeoutMs !== undefined ? { requestTimeoutMs: appServer?.requestTimeoutMs ?? clientOptions.requestTimeoutMs } : {},
3965
+ ...appServer?.listen !== undefined ? { localListen: appServer.listen } : {},
3966
+ ...appServer?.startupTimeoutMs !== undefined ? { localStartupTimeoutMs: appServer.startupTimeoutMs } : {},
3967
+ ...Object.keys(localEnv).length > 0 ? { localEnv } : {}
3968
+ };
3969
+ }
3970
+ async function createCloudAgent(clientOptions, agentOptions) {
3971
+ const session = new AppServerSession(cloudHarnessAppServerOptions(clientOptions), {
3972
+ kind: "create-agent",
3973
+ options: agentOptions
3974
+ });
3975
+ const initMsg = await session.initialize();
3976
+ session.close();
3977
+ return initMsg.agentId;
3978
+ }
3979
+ function assertCloudSessionOptionsSupported(action, options) {
3980
+ validateCloudSandboxOptions(options.sandbox, "sandbox");
3981
+ if (options.environment !== undefined && options.sandbox !== undefined) {
3982
+ throw new Error(`Constellation ${action}() cannot specify both environment and sandbox options.`);
3983
+ }
3984
+ if (options.systemPrompt !== undefined) {
3985
+ throw new Error(`Constellation ${action}() cannot rewrite an existing agent's systemPrompt from the SDK adapter yet.`);
3986
+ }
3987
+ if (options.allowedTools !== undefined || options.disallowedTools !== undefined) {
3988
+ throw new Error(`Constellation ${action}() has not wired allowedTools/disallowedTools to the remote device protocol yet.`);
3989
+ }
3990
+ if (options.skillSources !== undefined) {
3991
+ throw new Error(`Constellation ${action}() has not wired skillSources to the remote device protocol yet.`);
3992
+ }
3993
+ if (options.systemInfoReminder !== undefined) {
3994
+ throw new Error(`Constellation ${action}() has not wired systemInfoReminder to the remote device protocol yet.`);
3995
+ }
3996
+ if (options.dreaming?.behavior !== undefined) {
3997
+ throw new Error(`Constellation ${action}() does not yet support dreaming.behavior overrides.`);
3998
+ }
3999
+ if (options.memfsStartup !== undefined) {
4000
+ throw new Error(`Constellation ${action}() does not support memfsStartup.`);
4001
+ }
4002
+ if (options.includePartialMessages !== undefined) {
4003
+ throw new Error(`Constellation ${action}() streams Remote Client deltas directly; includePartialMessages is not a separate toggle.`);
4004
+ }
4005
+ }
4006
+
4007
+ class CloudEnvironmentSession extends RemoteClientSessionCore {
4008
+ cloudOptions;
4009
+ connectionId = null;
4010
+ removeExternalToolHandler = null;
4011
+ removeControlRequestHandler = null;
4012
+ externalTools = new Map;
4013
+ managedSandbox = null;
4014
+ sandboxRefreshTimer = null;
4015
+ sandboxRefreshInFlight = null;
4016
+ cloudMode;
4017
+ constructor(cloudOptions, mode) {
4018
+ super(mode, {
4019
+ label: "cloud",
4020
+ requestTimeoutMs: cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
4021
+ });
4022
+ this.cloudOptions = cloudOptions;
4023
+ this.cloudMode = mode;
4024
+ const tools = mode.options.tools;
4025
+ this.externalTools = externalToolsByName(tools);
4026
+ }
4027
+ async initializeRuntimeController() {
4028
+ const resolved = await this.resolveRuntime();
4029
+ const connection = await this.resolveConnectionForRuntime(resolved.runtime);
4030
+ this.connectionId = connection.connectionId;
4031
+ const apiKey = getCloudApiKey(this.cloudOptions);
4032
+ const url = buildCloudStatusWebSocketUrl({
4033
+ apiBaseUrl: this.cloudOptions.apiBaseUrl,
4034
+ connectionId: connection.connectionId,
4035
+ agentId: resolved.runtime.agent_id,
4036
+ conversationId: resolved.runtime.conversation_id,
4037
+ apiKey,
4038
+ authMode: this.cloudOptions.webSocketAuth ?? "header"
4039
+ });
4040
+ const client = createAppServerClient({
4041
+ url,
4042
+ WebSocket: createCloudStatusWebSocketConstructor({
4043
+ cloudOptions: this.cloudOptions,
4044
+ runtime: resolved.runtime
4045
+ }),
4046
+ requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
4047
+ });
4048
+ this.removeControlRequestHandler = registerAppServerControlRequestHandler({
4049
+ client,
4050
+ getRuntime: () => this.runtime,
4051
+ getOptions: () => this.currentOptions()
4052
+ });
4053
+ if (this.externalTools.size > 0) {
4054
+ this.removeExternalToolHandler = client.onExternalToolCall(createExternalToolCallHandler(this.externalTools));
4055
+ }
4056
+ try {
4057
+ await client.connect();
4058
+ const response = await this.startCloudRuntime(client, resolved.runtime);
4059
+ if (!response.success || !response.runtime) {
4060
+ throw new Error(response.error ?? "Failed to start Cloud status runtime");
4061
+ }
4062
+ const tools = agentToolNames(response.agent);
4063
+ return {
4064
+ controller: new AppServerRuntimeController(client, {
4065
+ requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
4066
+ }),
4067
+ runtime: response.runtime,
4068
+ model: typeof response.agent?.model === "string" ? response.agent.model : "",
4069
+ modelSettings: response.agent?.model_settings ?? null,
4070
+ ...tools !== undefined ? { tools } : {}
4071
+ };
4072
+ } catch (error) {
4073
+ this.removeExternalToolHandler?.();
4074
+ this.removeExternalToolHandler = null;
4075
+ this.removeControlRequestHandler?.();
4076
+ this.removeControlRequestHandler = null;
4077
+ client.close();
4078
+ await this.cleanupManagedSandbox();
4079
+ throw error;
4080
+ }
4081
+ }
4082
+ async startCloudRuntime(client, runtime) {
4083
+ const options = this.currentOptions();
4084
+ const command = {
4085
+ client_info: {
4086
+ name: SDK_AGENT_ORIGIN,
4087
+ title: "Letta Agent SDK"
4088
+ },
4089
+ agent_id: runtime.agent_id,
4090
+ conversation_id: runtime.conversation_id,
4091
+ recover_approvals: false,
4092
+ force_device_status: true
4093
+ };
4094
+ const mode = mapPermissionMode(options.permissionMode);
4095
+ if (mode)
4096
+ command.mode = mode;
4097
+ if (options.cwd !== undefined)
4098
+ command.cwd = options.cwd;
4099
+ const groups = externalToolGroups(options.tools);
4100
+ if (groups)
4101
+ command.external_tools = groups;
4102
+ return await client.runtimeStart(command);
4103
+ }
4104
+ async afterRuntimeInitialized() {
4105
+ if (!this.controller || !this.runtime)
4106
+ return;
4107
+ this.controller.send({
4108
+ type: "sync",
4109
+ runtime: this.runtime,
4110
+ recover_approvals: true,
4111
+ force_device_status: true
4112
+ });
4113
+ }
4114
+ async beforeTurn() {
4115
+ const sandbox = this.managedSandbox;
4116
+ if (!sandbox)
4117
+ return;
4118
+ await this.refreshManagedSandbox(sandbox);
4119
+ }
4120
+ onCoreClose() {
4121
+ this.removeExternalToolHandler?.();
4122
+ this.removeExternalToolHandler = null;
4123
+ this.removeControlRequestHandler?.();
4124
+ this.removeControlRequestHandler = null;
4125
+ this.cleanupManagedSandbox();
4126
+ }
4127
+ async resolveRuntime() {
4128
+ let agentId = this.cloudMode.agentId;
4129
+ let conversationId = this.cloudMode.conversationId;
4130
+ if (agentId && this.cloudMode.newConversation) {
4131
+ const conversation = await this.createConversation(agentId);
4132
+ conversationId = conversation.id;
4133
+ } else if (agentId && this.cloudMode.defaultConversation) {
4134
+ conversationId = "default";
4135
+ }
4136
+ if (!agentId && conversationId) {
4137
+ const conversation = await this.retrieveConversation(conversationId);
4138
+ if (!conversation.agent_id) {
4139
+ throw new Error(`Cloud conversation ${conversationId} did not include an agent id.`);
4140
+ }
4141
+ agentId = conversation.agent_id;
4142
+ }
4143
+ if (!agentId || !conversationId) {
4144
+ throw new Error("Constellation createSession()/resumeSession() requires an agent id or conversation id.");
4145
+ }
4146
+ return { runtime: { agent_id: agentId, conversation_id: conversationId } };
4147
+ }
4148
+ async createConversation(agentId) {
4149
+ const fetchImpl = getFetch2(this.cloudOptions.fetch);
4150
+ const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
4151
+ const url = new URL(`${baseUrl}/v1/conversations/`);
4152
+ url.searchParams.set("agent_id", agentId);
4153
+ const response = await fetchImpl(url, {
4154
+ method: "POST",
4155
+ headers: cloudHeaders(this.cloudOptions),
4156
+ body: JSON.stringify({})
4157
+ });
4158
+ const body = await parseJsonResponse2(response);
4159
+ assertOkResponse(response, body, "Cloud createSession()");
4160
+ if (!isCloudConversation(body)) {
4161
+ throw new Error("Cloud createSession() response did not include a conversation id.");
4162
+ }
4163
+ return { id: body.id, agent_id: body.agent_id };
4164
+ }
4165
+ async retrieveConversation(conversationId) {
4166
+ const fetchImpl = getFetch2(this.cloudOptions.fetch);
4167
+ const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
4168
+ const response = await fetchImpl(`${baseUrl}/v1/conversations/${encodeURIComponent(conversationId)}`, { headers: cloudHeaders(this.cloudOptions) });
4169
+ const body = await parseJsonResponse2(response);
4170
+ assertOkResponse(response, body, "Cloud resumeSession()");
4171
+ if (!isCloudConversation(body)) {
4172
+ throw new Error(`Cloud resumeSession() could not retrieve conversation ${conversationId}.`);
4173
+ }
4174
+ return { id: body.id, agent_id: body.agent_id };
4175
+ }
4176
+ async resolveConnectionForRuntime(runtime) {
4177
+ const environment = this.effectiveEnvironment();
4178
+ const sandboxOptions = this.effectiveSandboxOptions();
4179
+ if (environment !== undefined) {
4180
+ if (sandboxOptions !== undefined) {
4181
+ throw new Error("Constellation sessions cannot specify both environment and sandbox options.");
4182
+ }
4183
+ return this.resolveExplicitConnection(environment);
4184
+ }
4185
+ return this.createManagedSandboxConnection(runtime);
4186
+ }
4187
+ async resolveExplicitConnection(environment) {
4188
+ const target = environmentToRemoteTarget(environment);
4189
+ const resolved = await this.remoteEnvironmentClient().resolveEnvironment(target);
4190
+ return { connectionId: resolved.connectionId };
4191
+ }
4192
+ async createManagedSandboxConnection(runtime) {
4193
+ const sandbox = await this.createManagedSandbox(runtime.agent_id);
4194
+ this.managedSandbox = sandbox;
4195
+ try {
4196
+ await this.refreshManagedSandbox(sandbox);
4197
+ const connection = await this.waitForManagedSandboxConnection(sandbox);
4198
+ this.startManagedSandboxRefresh(sandbox);
4199
+ return { connectionId: connection.connectionId };
4200
+ } catch (error) {
4201
+ await this.cleanupManagedSandbox();
4202
+ throw error;
4203
+ }
4204
+ }
4205
+ async createManagedSandbox(agentId) {
4206
+ const fetchImpl = getFetch2(this.cloudOptions.fetch);
4207
+ const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
4208
+ const response = await fetchImpl(`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {
4209
+ method: "POST",
4210
+ headers: cloudHeaders(this.cloudOptions),
4211
+ body: JSON.stringify({})
4212
+ });
4213
+ const body = await parseJsonResponse2(response);
4214
+ assertOkResponse(response, body, "Cloud create managed sandbox");
4215
+ if (!isCloudAgentSandbox(body)) {
4216
+ throw new Error("Cloud create managed sandbox response did not include sandbox connection details.");
4217
+ }
4218
+ const sandboxOptions = this.resolvedSandboxOptions();
4219
+ const ttlMinutes = sandboxOptions.ttlMinutes ?? DEFAULT_SANDBOX_TTL_MINUTES;
4220
+ const readyTimeoutMs = sandboxOptions.readyTimeoutMs ?? DEFAULT_SANDBOX_READY_TIMEOUT_MS;
4221
+ const readyPollIntervalMs = sandboxOptions.readyPollIntervalMs ?? DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS;
4222
+ const defaultRefreshIntervalMs = Math.max(1000, Math.floor(ttlMinutes * 60000 * 0.8));
4223
+ return {
4224
+ agentId,
4225
+ sandboxId: body.sandboxId,
4226
+ deviceId: body.deviceId,
4227
+ connectionName: body.connectionName,
4228
+ ttlMinutes,
4229
+ readyTimeoutMs,
4230
+ readyPollIntervalMs,
4231
+ refreshIntervalMs: sandboxOptions.refreshIntervalMs ?? defaultRefreshIntervalMs,
4232
+ terminateOnClose: sandboxOptions.terminateOnClose ?? true
4233
+ };
4234
+ }
4235
+ async refreshManagedSandbox(sandbox) {
4236
+ if (this.sandboxRefreshInFlight) {
4237
+ await this.sandboxRefreshInFlight;
4238
+ return;
4239
+ }
4240
+ this.sandboxRefreshInFlight = this.refreshManagedSandboxOnce(sandbox);
4241
+ try {
4242
+ await this.sandboxRefreshInFlight;
4243
+ } finally {
4244
+ this.sandboxRefreshInFlight = null;
4245
+ }
4246
+ }
4247
+ async refreshManagedSandboxOnce(sandbox) {
4248
+ const fetchImpl = getFetch2(this.cloudOptions.fetch);
4249
+ const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
4250
+ const response = await fetchImpl(`${baseUrl}/v1/agents/${encodeURIComponent(sandbox.agentId)}/sandboxes/refresh`, {
4251
+ method: "POST",
4252
+ headers: cloudHeaders(this.cloudOptions),
4253
+ body: JSON.stringify({ ttlMinutes: sandbox.ttlMinutes })
4254
+ });
4255
+ const body = await parseJsonResponse2(response);
4256
+ assertOkResponse(response, body, "Cloud refresh managed sandbox");
4257
+ if (!isCloudAgentSandboxRefresh(body) || !body.success) {
4258
+ throw new Error("Cloud refresh managed sandbox response did not confirm refresh.");
4259
+ }
4260
+ if (body.sandboxId !== sandbox.sandboxId) {
4261
+ throw new CloudManagedSandboxOwnershipError(`Cloud managed sandbox ownership changed for agent ${sandbox.agentId}: expected ${sandbox.sandboxId}, got ${body.sandboxId}.`);
4262
+ }
4263
+ }
4264
+ async terminateManagedSandbox(sandbox) {
4265
+ await this.refreshManagedSandbox(sandbox);
4266
+ const fetchImpl = getFetch2(this.cloudOptions.fetch);
4267
+ const baseUrl = normalizeCloudApiBaseUrl(this.cloudOptions.apiBaseUrl);
4268
+ const response = await fetchImpl(`${baseUrl}/v1/agents/${encodeURIComponent(sandbox.agentId)}/sandboxes`, {
4269
+ method: "DELETE",
4270
+ headers: cloudHeaders(this.cloudOptions)
4271
+ });
4272
+ const body = await parseJsonResponse2(response);
4273
+ if (response.status === 404)
4274
+ return;
4275
+ assertOkResponse(response, body, "Cloud terminate managed sandbox");
4276
+ }
4277
+ async waitForManagedSandboxConnection(sandbox) {
4278
+ const deadline = Date.now() + sandbox.readyTimeoutMs;
4279
+ let lastError;
4280
+ while (true) {
4281
+ try {
4282
+ const resolved = await this.remoteEnvironmentClient().resolveEnvironment({
4283
+ deviceId: sandbox.deviceId
4284
+ });
4285
+ return { connectionId: resolved.connectionId };
4286
+ } catch (error) {
4287
+ lastError = error;
4288
+ if (!isRetryableManagedSandboxResolveError(error) || Date.now() >= deadline) {
4289
+ break;
4290
+ }
4291
+ const remainingMs = Math.max(0, deadline - Date.now());
4292
+ await sleep(Math.min(sandbox.readyPollIntervalMs, remainingMs));
4293
+ }
4294
+ }
4295
+ const detail = lastError instanceof Error ? lastError.message : String(lastError);
4296
+ throw new Error(`Cloud managed sandbox ${sandbox.sandboxId} did not come online within ${sandbox.readyTimeoutMs}ms: ${detail}`);
4297
+ }
4298
+ startManagedSandboxRefresh(sandbox) {
4299
+ this.stopManagedSandboxRefresh();
4300
+ this.sandboxRefreshTimer = setInterval(() => {
4301
+ this.refreshManagedSandbox(sandbox).catch((error) => {
4302
+ if (error instanceof CloudManagedSandboxOwnershipError) {
4303
+ this.stopManagedSandboxRefresh();
4304
+ }
4305
+ });
4306
+ }, sandbox.refreshIntervalMs);
4307
+ this.sandboxRefreshTimer.unref?.();
4308
+ }
4309
+ stopManagedSandboxRefresh() {
4310
+ if (!this.sandboxRefreshTimer)
4311
+ return;
4312
+ clearInterval(this.sandboxRefreshTimer);
4313
+ this.sandboxRefreshTimer = null;
4314
+ }
4315
+ async cleanupManagedSandbox() {
4316
+ this.stopManagedSandboxRefresh();
4317
+ const sandbox = this.managedSandbox;
4318
+ this.managedSandbox = null;
4319
+ if (!sandbox || !sandbox.terminateOnClose)
4320
+ return;
4321
+ try {
4322
+ await this.terminateManagedSandbox(sandbox);
4323
+ } catch {}
4324
+ }
4325
+ remoteEnvironmentClient() {
4326
+ return new RemoteEnvironmentClient({
4327
+ baseUrl: this.cloudOptions.apiBaseUrl,
4328
+ apiKey: getCloudApiKey(this.cloudOptions),
4329
+ headers: this.cloudOptions.headers,
4330
+ fetch: this.cloudOptions.fetch
4331
+ });
4332
+ }
4333
+ effectiveEnvironment() {
4334
+ const modeEnvironment = this.mode.kind === "session" ? this.mode.options.environment : undefined;
4335
+ return modeEnvironment ?? this.cloudOptions.environment;
4336
+ }
4337
+ effectiveSandboxOptions() {
4338
+ const modeSandbox = this.mode.kind === "session" ? this.mode.options.sandbox : undefined;
4339
+ return modeSandbox ?? this.cloudOptions.sandbox;
4340
+ }
4341
+ resolvedSandboxOptions() {
4342
+ return this.effectiveSandboxOptions() ?? {};
4343
+ }
4344
+ }
4345
+
4346
+ // src/validation.ts
4347
+ var VALID_SKILL_SOURCES = [
4348
+ "bundled",
4349
+ "global",
4350
+ "agent",
4351
+ "project"
4352
+ ];
4353
+ var VALID_REASONING_EFFORTS = [
4354
+ "none",
4355
+ "minimal",
4356
+ "low",
4357
+ "medium",
4358
+ "high",
4359
+ "xhigh"
4360
+ ];
4361
+ function getBlockLabels(memory) {
4362
+ return memory.map((item) => {
4363
+ if (typeof item === "string")
4364
+ return item;
4365
+ if ("label" in item)
4366
+ return item.label;
4367
+ return null;
4368
+ }).filter((label) => label !== null);
4369
+ }
4370
+ function validateApprovalRecoveryOptions(options) {
4371
+ if (options.maxApprovalRecoveryAttempts !== undefined && (!Number.isInteger(options.maxApprovalRecoveryAttempts) || options.maxApprovalRecoveryAttempts < 0)) {
4372
+ throw new Error("Invalid maxApprovalRecoveryAttempts. Expected a non-negative integer.");
4373
+ }
4374
+ if (options.approvalRecoveryTimeoutMs !== undefined && (!Number.isInteger(options.approvalRecoveryTimeoutMs) || options.approvalRecoveryTimeoutMs <= 0)) {
4375
+ throw new Error("Invalid approvalRecoveryTimeoutMs. Expected a positive integer.");
4376
+ }
4377
+ }
4378
+ function validateRemovedSessionOptions(options) {
4379
+ if (options.memfsStartup !== undefined) {
4380
+ throw new Error("memfsStartup is not supported by the SDK.");
4381
+ }
4382
+ }
4383
+ function validateSystemPromptPreset(preset) {
4384
+ const validPresets = [
4385
+ "default",
4386
+ "letta-claude",
4387
+ "letta-codex",
4388
+ "letta-gemini",
4389
+ "claude",
4390
+ "codex",
4391
+ "gemini"
4392
+ ];
4393
+ if (!validPresets.includes(preset)) {
4394
+ throw new Error(`Invalid system prompt preset '${preset}'. ` + `Valid presets: ${validPresets.join(", ")}`);
4395
+ }
4396
+ }
4397
+ function validateSkillSources(sources) {
4398
+ if (sources === undefined) {
4399
+ return;
4400
+ }
4401
+ for (const source of sources) {
4402
+ if (!VALID_SKILL_SOURCES.includes(source)) {
4403
+ throw new Error(`Invalid skill source '${source}'. Valid values: ${VALID_SKILL_SOURCES.join(", ")}`);
4404
+ }
4405
+ }
4406
+ }
4407
+ function validateDreamingOptions(dreaming) {
4408
+ if (dreaming === undefined) {
4409
+ return;
4410
+ }
4411
+ if (dreaming.trigger !== undefined && !["off", "step-count", "compaction-event"].includes(dreaming.trigger)) {
4412
+ throw new Error(`Invalid dreaming.trigger '${String(dreaming.trigger)}'. Valid values: off, step-count, compaction-event`);
4413
+ }
4414
+ if (dreaming.behavior !== undefined && !["reminder", "auto-launch"].includes(dreaming.behavior)) {
4415
+ throw new Error(`Invalid dreaming.behavior '${String(dreaming.behavior)}'. Valid values: reminder, auto-launch`);
4416
+ }
4417
+ if (dreaming.stepCount !== undefined && (!Number.isInteger(dreaming.stepCount) || dreaming.stepCount <= 0)) {
4418
+ throw new Error("Invalid dreaming.stepCount. Expected a positive integer.");
4419
+ }
4420
+ }
4421
+ function validateReasoningEffort(value) {
4422
+ if (value === undefined)
4423
+ return;
4424
+ if (typeof value !== "string" || !VALID_REASONING_EFFORTS.includes(value)) {
4425
+ throw new Error(`Invalid reasoningEffort '${String(value)}'. Valid values: ${VALID_REASONING_EFFORTS.join(", ")}`);
4426
+ }
4427
+ }
4428
+ function validateCreateSessionOptions(options) {
4429
+ if (options.systemPrompt !== undefined) {
4430
+ validateSystemPromptPreset(options.systemPrompt);
4431
+ }
4432
+ validateSkillSources(options.skillSources);
4433
+ validateReasoningEffort(options.reasoningEffort);
4434
+ validateDreamingOptions(options.dreaming);
4435
+ validateApprovalRecoveryOptions(options);
4436
+ validateRemovedSessionOptions(options);
4437
+ }
4438
+ function validateCreateAgentOptions(options) {
4439
+ if (options.memory !== undefined) {
4440
+ const blockLabels = getBlockLabels(options.memory);
4441
+ if (options.persona !== undefined && !blockLabels.includes("persona")) {
4442
+ throw new Error("Cannot set 'persona' value - block not included in 'memory'. " + "Either add 'persona' to memory array or remove the persona option.");
4443
+ }
4444
+ if (options.human !== undefined && !blockLabels.includes("human")) {
4445
+ throw new Error("Cannot set 'human' value - block not included in 'memory'. " + "Either add 'human' to memory array or remove the human option.");
4446
+ }
4447
+ }
4448
+ if (options.systemPrompt !== undefined && typeof options.systemPrompt === "object") {
4449
+ validateSystemPromptPreset(options.systemPrompt.preset);
4450
+ } else if (options.systemPrompt !== undefined && typeof options.systemPrompt === "string") {
4451
+ const validPresets = [
4452
+ "default",
4453
+ "letta-claude",
4454
+ "letta-codex",
4455
+ "letta-gemini",
4456
+ "claude",
4457
+ "codex",
4458
+ "gemini"
4459
+ ];
4460
+ if (validPresets.includes(options.systemPrompt)) {
4461
+ validateSystemPromptPreset(options.systemPrompt);
4462
+ }
4463
+ }
4464
+ validateSkillSources(options.skillSources);
4465
+ validateDreamingOptions(options.dreaming);
4466
+ }
4467
+
4468
+ // src/client.ts
4469
+ var VALID_BACKENDS = new Set([
4470
+ "local",
4471
+ "remote",
4472
+ "cloud"
4473
+ ]);
4474
+ function isLettaCodeBackend(value) {
4475
+ return VALID_BACKENDS.has(value);
4476
+ }
4477
+ function getOptionsEnvironment(options) {
4478
+ if ("environment" in options) {
4479
+ return options.environment;
4480
+ }
4481
+ return;
4482
+ }
4483
+ function stripCloudExecutionOptions(options) {
4484
+ const sessionOptions = { ...options };
4485
+ delete sessionOptions.environment;
4486
+ delete sessionOptions.sandbox;
4487
+ return sessionOptions;
4488
+ }
4489
+ function hasCreateAgentEnvironment(options) {
4490
+ return "environment" in options;
4491
+ }
4492
+ function looksLikeConversationId(id) {
4493
+ return id.startsWith("conv-") || id.startsWith("local-conv-");
4494
+ }
4495
+
4496
+ class LettaCodeClient {
4497
+ backend;
4498
+ environment;
4499
+ options;
4500
+ constructor(options = {}) {
4501
+ const backend = options.backend ?? "local";
4502
+ if (!isLettaCodeBackend(backend)) {
4503
+ throw new Error(`Invalid Letta Code backend '${String(backend)}'. Valid values: local, remote, cloud.`);
4504
+ }
4505
+ this.backend = backend;
4506
+ this.environment = getOptionsEnvironment(options);
4507
+ this.options = options;
4508
+ if (this.backend === "local" && this.environment !== undefined) {
4509
+ throw new Error('LettaCodeClient environment is only valid with backend: "cloud".');
4510
+ }
4511
+ if (this.backend === "remote" && this.environment !== undefined) {
4512
+ throw new Error('LettaCodeClient environment is only valid with backend: "cloud"; remote url selects the app-server runtime.');
4513
+ }
4514
+ if (this.backend !== "cloud" && options.sandbox !== undefined) {
4515
+ throw new Error('LettaCodeClient sandbox options are only valid with backend: "cloud".');
4516
+ }
4517
+ if (this.backend === "local") {
4518
+ const localOptions = options;
4519
+ if (localOptions.transport !== undefined && localOptions.transport !== "app-server" && localOptions.transport !== "stdio") {
4520
+ throw new Error("Invalid local transport. Valid values: app-server, stdio.");
4521
+ }
4522
+ const requestTimeoutMs = localOptions.appServer?.requestTimeoutMs;
4523
+ if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
4524
+ throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
4525
+ }
4526
+ const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
4527
+ if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
4528
+ throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
4529
+ }
4530
+ }
4531
+ if (this.backend === "remote") {
4532
+ if (!("url" in options) || typeof options.url !== "string" || options.url.length === 0) {
4533
+ throw new Error("LettaCodeClient remote backend requires a non-empty url.");
4534
+ }
4535
+ if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
4536
+ throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
4537
+ }
4538
+ }
4539
+ if (this.backend === "cloud") {
4540
+ validateCloudClientOptions(options);
4541
+ }
4542
+ }
4543
+ async createAgent(options = {}) {
4544
+ if (hasCreateAgentEnvironment(options)) {
4545
+ throw new Error("createAgent() does not accept environment. Set a client default or pass environment to resumeSession()/createSession().");
4546
+ }
4547
+ validateCreateAgentOptions(options);
4548
+ if (this.backend === "remote") {
4549
+ const session2 = new AppServerSession(this.remoteOptions(), {
4550
+ kind: "create-agent",
4551
+ options
4552
+ });
4553
+ const initMsg2 = await session2.initialize();
4554
+ session2.close();
4555
+ return initMsg2.agentId;
4556
+ }
4557
+ if (this.backend === "cloud") {
4558
+ return createCloudAgent(this.cloudOptions(), options);
4559
+ }
4560
+ this.assertLocalBackend("createAgent");
4561
+ if (!this.useLegacyLocalStdio()) {
4562
+ const session2 = new AppServerSession(this.localAppServerOptions(), {
4563
+ kind: "create-agent",
4564
+ options
4565
+ });
4566
+ const initMsg2 = await session2.initialize();
4567
+ session2.close();
4568
+ return initMsg2.agentId;
4569
+ }
4570
+ const session = new Session({ ...options, createOnly: true });
4571
+ const initMsg = await session.initialize();
4572
+ session.close();
4573
+ return initMsg.agentId;
4574
+ }
4575
+ createSession(agentIdOrOptions, options = {}) {
4576
+ const agentId = typeof agentIdOrOptions === "string" ? agentIdOrOptions : undefined;
4577
+ const resolvedOptions = typeof agentIdOrOptions === "string" ? options : agentIdOrOptions ?? {};
4578
+ this.assertSessionBackend("createSession", resolvedOptions);
4579
+ const sessionOptions = stripCloudExecutionOptions(resolvedOptions);
4580
+ validateCreateSessionOptions(sessionOptions);
4581
+ if (this.backend === "remote") {
4582
+ if (!agentId) {
4583
+ throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
4584
+ }
4585
+ return new AppServerSession(this.remoteOptions(), {
4586
+ kind: "session",
4587
+ agentId,
4588
+ newConversation: true,
4589
+ options: resolvedOptions
4590
+ });
4591
+ }
4592
+ if (this.backend === "cloud") {
4593
+ if (!agentId) {
4594
+ throw new Error("Constellation createSession() requires an agent id. Call createAgent() first or pass an agent id.");
4595
+ }
4596
+ return new CloudEnvironmentSession(this.cloudOptions(), {
4597
+ kind: "session",
4598
+ agentId,
4599
+ newConversation: true,
4600
+ options: resolvedOptions
4601
+ });
4602
+ }
4603
+ if (!this.useLegacyLocalStdio() && agentId) {
4604
+ return new AppServerSession(this.localAppServerOptions(), {
4605
+ kind: "session",
4606
+ agentId,
4607
+ newConversation: true,
4608
+ options: resolvedOptions
4609
+ });
4610
+ }
4611
+ if (agentId) {
4612
+ return new Session({ ...sessionOptions, agentId, newConversation: true });
4613
+ }
4614
+ return new Session({ ...sessionOptions, newConversation: true });
4615
+ }
4616
+ resumeSession(id, options = {}) {
4617
+ this.assertSessionBackend("resumeSession", options);
4618
+ const sessionOptions = stripCloudExecutionOptions(options);
4619
+ validateCreateSessionOptions(sessionOptions);
4620
+ if (this.backend === "remote") {
4621
+ if (looksLikeConversationId(id)) {
4622
+ return new AppServerSession(this.remoteOptions(), {
4623
+ kind: "session",
4624
+ conversationId: id,
4625
+ options
4626
+ });
4627
+ }
4628
+ return new AppServerSession(this.remoteOptions(), {
4629
+ kind: "session",
4630
+ agentId: id,
4631
+ defaultConversation: true,
4632
+ options
4633
+ });
4634
+ }
4635
+ if (this.backend === "cloud") {
4636
+ if (looksLikeConversationId(id)) {
4637
+ return new CloudEnvironmentSession(this.cloudOptions(), {
4638
+ kind: "session",
4639
+ conversationId: id,
4640
+ options
4641
+ });
4642
+ }
4643
+ return new CloudEnvironmentSession(this.cloudOptions(), {
4644
+ kind: "session",
4645
+ agentId: id,
4646
+ defaultConversation: true,
4647
+ options
4648
+ });
4649
+ }
4650
+ if (!this.useLegacyLocalStdio()) {
4651
+ if (looksLikeConversationId(id)) {
4652
+ return new AppServerSession(this.localAppServerOptions(), {
4653
+ kind: "session",
4654
+ conversationId: id,
4655
+ options
4656
+ });
4657
+ }
4658
+ return new AppServerSession(this.localAppServerOptions(), {
4659
+ kind: "session",
4660
+ agentId: id,
4661
+ defaultConversation: true,
4662
+ options
4663
+ });
4664
+ }
4665
+ if (looksLikeConversationId(id)) {
4666
+ return new Session({ ...sessionOptions, conversationId: id });
4667
+ }
4668
+ return new Session({
4669
+ ...sessionOptions,
4670
+ agentId: id,
4671
+ defaultConversation: true
4672
+ });
4673
+ }
4674
+ async prompt(message, agentId, options = {}) {
4675
+ const session = agentId ? this.createSession(agentId, options) : this.createSession(options);
4676
+ try {
4677
+ return await session.runTurn(message);
4678
+ } finally {
4679
+ session.close();
4680
+ }
4681
+ }
4682
+ assertLocalBackend(action) {
4683
+ if (this.backend === "local") {
4684
+ return;
4685
+ }
4686
+ throw new Error(`LettaCodeClient backend '${this.backend}' is not implemented yet. ${action} currently supports backend 'local' only.`);
4687
+ }
4688
+ assertSessionBackend(action, options) {
4689
+ const effectiveEnvironment = options.environment ?? this.environment;
4690
+ if (this.backend === "local") {
4691
+ if (effectiveEnvironment !== undefined) {
4692
+ throw new Error(`${action}() environment overrides are only valid with backend: "cloud".`);
4693
+ }
4694
+ if (options.sandbox !== undefined) {
4695
+ throw new Error(`${action}() sandbox options are only valid with backend: "cloud".`);
4696
+ }
4697
+ if (!this.useLegacyLocalStdio()) {
4698
+ assertRemoteSessionOptionsSupported(action, options);
4699
+ }
4700
+ return;
4701
+ }
4702
+ if (this.backend === "remote") {
4703
+ if (options.environment !== undefined) {
4704
+ throw new Error(`${action}() environment overrides are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
4705
+ }
4706
+ if (options.sandbox !== undefined) {
4707
+ throw new Error(`${action}() sandbox options are only valid with backend: "cloud"; remote url selects the app-server runtime.`);
4708
+ }
4709
+ assertRemoteSessionOptionsSupported(action, options);
4710
+ return;
4711
+ }
4712
+ if (this.backend === "cloud") {
4713
+ const cloudOptions = this.cloudOptions();
4714
+ if (cloudOptions.environment !== undefined && options.sandbox !== undefined) {
4715
+ throw new Error(`Constellation ${action}() cannot specify sandbox options when the client has a default environment.`);
4716
+ }
4717
+ if (cloudOptions.sandbox !== undefined && options.environment !== undefined) {
4718
+ throw new Error(`Constellation ${action}() cannot specify an environment when the client has default sandbox options.`);
4719
+ }
4720
+ assertCloudSessionOptionsSupported(action, options);
4721
+ return;
4722
+ }
4723
+ throw new Error(`LettaCodeClient backend '${this.backend}' is not implemented yet. ${action} currently supports backend 'local' only.`);
4724
+ }
4725
+ useLegacyLocalStdio() {
4726
+ return this.options.transport === "stdio";
4727
+ }
4728
+ localAppServerOptions() {
4729
+ const localOptions = this.options;
4730
+ const appServer = localOptions.appServer;
4731
+ return {
4732
+ local: appServer?.url === undefined,
4733
+ ...appServer?.url !== undefined ? { url: appServer.url } : {},
4734
+ ...appServer?.WebSocket !== undefined ? { WebSocket: appServer.WebSocket } : {},
4735
+ ...appServer?.requestTimeoutMs !== undefined ? { requestTimeoutMs: appServer.requestTimeoutMs } : {},
4736
+ ...appServer?.listen !== undefined ? { localListen: appServer.listen } : {},
4737
+ ...appServer?.startupTimeoutMs !== undefined ? { localStartupTimeoutMs: appServer.startupTimeoutMs } : {}
4738
+ };
4739
+ }
4740
+ remoteOptions() {
4741
+ if (this.backend !== "remote") {
4742
+ throw new Error("Remote options requested for non-remote backend.");
4743
+ }
4744
+ return this.options;
4745
+ }
4746
+ cloudOptions() {
4747
+ if (this.backend !== "cloud") {
4748
+ throw new Error('Constellation options requested for non-"cloud" backend.');
4749
+ }
4750
+ return this.options;
4751
+ }
4752
+ }
4753
+ // src/stream-events.ts
4754
+ function extractTextFromContent2(content) {
4755
+ if (typeof content === "string") {
4756
+ return content;
4757
+ }
4758
+ if (Array.isArray(content)) {
4759
+ const pieces = [];
4760
+ for (const part of content) {
4761
+ if (typeof part === "string") {
4762
+ pieces.push(part);
4763
+ continue;
4764
+ }
4765
+ if (!part || typeof part !== "object") {
4766
+ continue;
4767
+ }
4768
+ const rec = part;
4769
+ if (typeof rec.text === "string") {
4770
+ pieces.push(rec.text);
4771
+ }
4772
+ }
4773
+ const joined = pieces.join("");
4774
+ return joined.length > 0 ? joined : null;
4775
+ }
4776
+ if (content && typeof content === "object") {
4777
+ const rec = content;
4778
+ if (typeof rec.text === "string") {
4779
+ return rec.text;
4780
+ }
4781
+ }
4782
+ return null;
4783
+ }
4784
+ function extractStreamTextDelta(event) {
4785
+ if (!event || typeof event !== "object") {
4786
+ return null;
4787
+ }
4788
+ const rec = event;
4789
+ const maybeDelta = rec.delta;
4790
+ if (maybeDelta && typeof maybeDelta === "object") {
4791
+ const delta = maybeDelta;
4792
+ if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
4793
+ return { kind: "reasoning", text: delta.reasoning };
4794
+ }
4795
+ if (typeof delta.text === "string" && delta.text.length > 0) {
4796
+ return { kind: "assistant", text: delta.text };
4797
+ }
4798
+ }
4799
+ const messageType = rec.message_type;
4800
+ if (messageType === "reasoning_message") {
4801
+ const reasoningText = typeof rec.reasoning === "string" ? rec.reasoning : extractTextFromContent2(rec.content);
4802
+ if (reasoningText && reasoningText.length > 0) {
4803
+ return { kind: "reasoning", text: reasoningText };
4804
+ }
4805
+ }
4806
+ if (messageType === "assistant_message") {
4807
+ const assistantText = extractTextFromContent2(rec.content);
4808
+ if (assistantText && assistantText.length > 0) {
4809
+ return { kind: "assistant", text: assistantText };
4810
+ }
4811
+ }
4812
+ return null;
4813
+ }
4814
+ // src/tool-helpers.ts
4815
+ function jsonResult(payload) {
4816
+ return {
4817
+ content: [
4818
+ {
4819
+ type: "text",
4820
+ text: JSON.stringify(payload, null, 2)
4821
+ }
4822
+ ],
4823
+ details: payload
4824
+ };
4825
+ }
4826
+ function readStringParam(params, key, options = {}) {
4827
+ const { required = false, trim = true, label = key, allowEmpty = false } = options;
4828
+ const raw = params[key];
4829
+ if (typeof raw !== "string") {
4830
+ if (required)
4831
+ throw new Error(`${label} required`);
4832
+ return;
4833
+ }
4834
+ const value = trim ? raw.trim() : raw;
4835
+ if (!value && !allowEmpty) {
4836
+ if (required)
4837
+ throw new Error(`${label} required`);
4838
+ return;
4839
+ }
4840
+ return value;
4841
+ }
4842
+ function readNumberParam(params, key, options = {}) {
4843
+ const { required = false, label = key, integer = false } = options;
4844
+ const raw = params[key];
4845
+ let value;
4846
+ if (typeof raw === "number" && Number.isFinite(raw)) {
4847
+ value = raw;
4848
+ } else if (typeof raw === "string") {
4849
+ const trimmed = raw.trim();
4850
+ if (trimmed) {
4851
+ const parsed = Number.parseFloat(trimmed);
4852
+ if (Number.isFinite(parsed))
4853
+ value = parsed;
4854
+ }
4855
+ }
4856
+ if (value === undefined) {
4857
+ if (required)
4858
+ throw new Error(`${label} required`);
4859
+ return;
4860
+ }
4861
+ return integer ? Math.trunc(value) : value;
4862
+ }
4863
+ function readBooleanParam(params, key, options = {}) {
4864
+ const { required = false, label = key } = options;
4865
+ const raw = params[key];
4866
+ if (typeof raw === "boolean") {
4867
+ return raw;
4868
+ }
4869
+ if (typeof raw === "string") {
4870
+ const lower = raw.toLowerCase().trim();
4871
+ if (lower === "true" || lower === "1" || lower === "yes")
4872
+ return true;
4873
+ if (lower === "false" || lower === "0" || lower === "no")
4874
+ return false;
4875
+ }
4876
+ if (required)
4877
+ throw new Error(`${label} required`);
4878
+ return;
4879
+ }
4880
+ function readStringArrayParam(params, key, options = {}) {
4881
+ const { required = false, label = key } = options;
4882
+ const raw = params[key];
4883
+ if (Array.isArray(raw)) {
4884
+ const values = raw.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
4885
+ if (values.length === 0) {
4886
+ if (required)
4887
+ throw new Error(`${label} required`);
4888
+ return;
4889
+ }
4890
+ return values;
4891
+ }
4892
+ if (typeof raw === "string") {
4893
+ const value = raw.trim();
4894
+ if (!value) {
4895
+ if (required)
4896
+ throw new Error(`${label} required`);
4897
+ return;
4898
+ }
4899
+ return [value];
4900
+ }
4901
+ if (required)
4902
+ throw new Error(`${label} required`);
4903
+ return;
4904
+ }
4905
+
4906
+ // src/index.ts
4907
+ import { readFileSync } from "node:fs";
4908
+ async function createAgent(options = {}) {
4909
+ validateCreateAgentOptions(options);
4910
+ return new LettaCodeClient().createAgent(options);
4911
+ }
4912
+ function createSession(agentId, options = {}) {
4913
+ validateCreateSessionOptions(options);
4914
+ if (agentId) {
4915
+ return new LettaCodeClient().createSession(agentId, options);
4916
+ }
4917
+ return new LettaCodeClient({ backend: "local", transport: "stdio" }).createSession(options);
4918
+ }
4919
+ function resumeSession(id, options = {}) {
4920
+ validateCreateSessionOptions(options);
4921
+ return new LettaCodeClient().resumeSession(id, options);
4922
+ }
4923
+ async function prompt(message, agentId) {
4924
+ const session = agentId ? createSession(agentId) : createSession();
4925
+ try {
4926
+ return await session.runTurn(message);
4927
+ } finally {
4928
+ session.close();
4929
+ }
4930
+ }
4931
+ async function listMessagesDirect(agentId, options = {}) {
4932
+ const session = new LettaCodeClient().resumeSession(agentId, {
4933
+ permissionMode: "bypassPermissions"
4934
+ });
4935
+ await session.initialize();
4936
+ try {
4937
+ return await session.listMessages(options);
4938
+ } finally {
4939
+ session.close();
4940
+ }
4941
+ }
4942
+ function imageFromFile(filePath) {
4943
+ const data = readFileSync(filePath).toString("base64");
4944
+ const ext = filePath.toLowerCase();
4945
+ const media_type = ext.endsWith(".png") ? "image/png" : ext.endsWith(".gif") ? "image/gif" : ext.endsWith(".webp") ? "image/webp" : "image/jpeg";
4946
+ return {
4947
+ type: "image",
4948
+ source: { type: "base64", media_type, data }
4949
+ };
4950
+ }
4951
+ function imageFromBase64(data, media_type = "image/png") {
4952
+ return {
4953
+ type: "image",
4954
+ source: { type: "base64", media_type, data }
4955
+ };
4956
+ }
4957
+ async function imageFromURL(url) {
4958
+ const response = await fetch(url);
4959
+ const buffer = await response.arrayBuffer();
4960
+ const data = Buffer.from(buffer).toString("base64");
4961
+ const contentType = response.headers.get("content-type");
4962
+ let media_type = "image/png";
4963
+ if (contentType?.includes("jpeg") || contentType?.includes("jpg") || url.match(/\.jpe?g$/i)) {
4964
+ media_type = "image/jpeg";
4965
+ } else if (contentType?.includes("gif") || url.endsWith(".gif")) {
4966
+ media_type = "image/gif";
4967
+ } else if (contentType?.includes("webp") || url.endsWith(".webp")) {
4968
+ media_type = "image/webp";
4969
+ }
4970
+ return {
4971
+ type: "image",
4972
+ source: { type: "base64", media_type, data }
4973
+ };
4974
+ }
4975
+ export {
4976
+ resumeSession,
4977
+ readStringParam,
4978
+ readStringArrayParam,
4979
+ readNumberParam,
4980
+ readBooleanParam,
4981
+ prompt,
4982
+ listMessagesDirect,
4983
+ jsonResult,
4984
+ imageFromURL,
4985
+ imageFromFile,
4986
+ imageFromBase64,
4987
+ extractStreamTextDelta,
4988
+ createSession,
4989
+ createAgent,
4990
+ Session,
4991
+ LettaCodeClient
4992
+ };
4993
+
4994
+ //# debugId=386FEEB02928954D64756E2164756E21