@letta-ai/letta-code 0.28.8 → 0.28.9

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/dist/channels-public.js +1053 -0
  2. package/dist/channels-public.js.map +13 -0
  3. package/dist/channels-slack.js +534 -0
  4. package/dist/channels-slack.js.map +15 -0
  5. package/dist/types/channels/core-stream.d.ts +35 -0
  6. package/dist/types/channels/core-stream.d.ts.map +1 -0
  7. package/dist/types/channels/plugin-types.d.ts +226 -0
  8. package/dist/types/channels/plugin-types.d.ts.map +1 -0
  9. package/dist/types/channels/processor.d.ts +27 -0
  10. package/dist/types/channels/processor.d.ts.map +1 -0
  11. package/dist/types/channels/progress-builder.d.ts +13 -0
  12. package/dist/types/channels/progress-builder.d.ts.map +1 -0
  13. package/dist/types/channels/progress-formatting.d.ts +34 -0
  14. package/dist/types/channels/progress-formatting.d.ts.map +1 -0
  15. package/dist/types/channels/slack/ingress-policy.d.ts +72 -0
  16. package/dist/types/channels/slack/ingress-policy.d.ts.map +1 -0
  17. package/dist/types/channels/slack/progress.d.ts +7 -0
  18. package/dist/types/channels/slack/progress.d.ts.map +1 -0
  19. package/dist/types/channels/slack/public-utils.d.ts +11 -0
  20. package/dist/types/channels/slack/public-utils.d.ts.map +1 -0
  21. package/dist/types/channels/slack/sender.d.ts +39 -0
  22. package/dist/types/channels/slack/sender.d.ts.map +1 -0
  23. package/dist/types/channels/slack/status-controller.d.ts +35 -0
  24. package/dist/types/channels/slack/status-controller.d.ts.map +1 -0
  25. package/dist/types/channels/types.d.ts +766 -0
  26. package/dist/types/channels/types.d.ts.map +1 -0
  27. package/dist/types/channels-public.d.ts +8 -0
  28. package/dist/types/channels-public.d.ts.map +1 -0
  29. package/dist/types/channels-slack.d.ts +9 -0
  30. package/dist/types/channels-slack.d.ts.map +1 -0
  31. package/dist/types/permissions/mode.d.ts +52 -0
  32. package/dist/types/permissions/mode.d.ts.map +1 -0
  33. package/dist/types/types/protocol_v2.d.ts +4 -0
  34. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  35. package/letta.js +2423 -1788
  36. package/package.json +33 -2
  37. package/scripts/isolated-unit-tests.json +12 -0
  38. package/scripts/run-unit-tests.cjs +5 -2
  39. package/scripts/source-file-size-baseline.json +4 -4
@@ -0,0 +1,1053 @@
1
+ // src/channels/core-stream.ts
2
+ var LETTA_STREAM_NO_ASSISTANT_MESSAGE_ERROR = "No assistant message received in stream";
3
+
4
+ class LettaStreamCoreError extends Error {
5
+ errorType;
6
+ detail;
7
+ runId;
8
+ constructor(params) {
9
+ super(params.message);
10
+ this.name = "LettaStreamCoreError";
11
+ this.errorType = params.errorType;
12
+ this.detail = params.detail;
13
+ this.runId = params.runId;
14
+ }
15
+ }
16
+
17
+ class LettaStreamNoAssistantMessageError extends Error {
18
+ constructor() {
19
+ super(LETTA_STREAM_NO_ASSISTANT_MESSAGE_ERROR);
20
+ this.name = "LettaStreamNoAssistantMessageError";
21
+ }
22
+ }
23
+ function normalizeErrorLine(value) {
24
+ const trimmed = value?.trim();
25
+ return trimmed ? trimmed : undefined;
26
+ }
27
+ function formatLettaStreamCoreErrorForChannel(error, options = {}) {
28
+ const message = normalizeErrorLine(error.message) ?? "Core failed to generate a response.";
29
+ const detail = normalizeErrorLine(error.detail);
30
+ if (options.includeDetail === false || !detail || detail === message) {
31
+ return message;
32
+ }
33
+ return `${message}
34
+ ${detail}`;
35
+ }
36
+ function isRecord(value) {
37
+ return value !== null && typeof value === "object";
38
+ }
39
+ function isOptionalString(value) {
40
+ return typeof value === "undefined" || typeof value === "string";
41
+ }
42
+ function isLettaSseTextContentPart(value) {
43
+ if (!isRecord(value)) {
44
+ return false;
45
+ }
46
+ return value.type === "text" && typeof value.text === "string" && isOptionalString(value.signature);
47
+ }
48
+ function isLettaSseAssistantMessage(value) {
49
+ if (!isRecord(value) || value.message_type !== "assistant_message") {
50
+ return false;
51
+ }
52
+ const content = value.content;
53
+ return typeof content === "undefined" || typeof content === "string" || Array.isArray(content) && content.every(isLettaSseTextContentPart);
54
+ }
55
+ function isLettaSseErrorMessage(value) {
56
+ if (!isRecord(value) || value.message_type !== "error_message") {
57
+ return false;
58
+ }
59
+ return isOptionalString(value.error_type) && isOptionalString(value.message) && isOptionalString(value.detail) && isOptionalString(value.run_id);
60
+ }
61
+ function isLettaSseStopReasonMessage(value) {
62
+ if (!isRecord(value) || value.message_type !== "stop_reason") {
63
+ return false;
64
+ }
65
+ return isOptionalString(value.stop_reason);
66
+ }
67
+ function coreErrorFromMessage(message) {
68
+ return new LettaStreamCoreError({
69
+ errorType: message.error_type,
70
+ message: message.message ?? "Core failed to generate a response.",
71
+ detail: message.detail,
72
+ runId: message.run_id
73
+ });
74
+ }
75
+ function collectAssistantContent(message) {
76
+ if (typeof message.content === "string") {
77
+ return [message.content];
78
+ }
79
+ if (Array.isArray(message.content)) {
80
+ return message.content.map((part) => part.text);
81
+ }
82
+ return [];
83
+ }
84
+ async function collectLettaSseAssistantText(body, options = {}) {
85
+ const reader = body.getReader();
86
+ const decoder = new TextDecoder;
87
+ let buffer = "";
88
+ const assistantMessages = [];
89
+ let stopReason;
90
+ async function processData(data) {
91
+ if (data === "[DONE]") {
92
+ return;
93
+ }
94
+ const parsed = JSON.parse(data);
95
+ await options.onDelta?.(parsed);
96
+ const progressUpdates = options.progressBuilder?.buildUpdates(parsed) ?? [];
97
+ for (const update of progressUpdates) {
98
+ await options.onProgressUpdate?.(update);
99
+ }
100
+ if (isLettaSseErrorMessage(parsed)) {
101
+ throw coreErrorFromMessage(parsed);
102
+ }
103
+ if (isLettaSseAssistantMessage(parsed)) {
104
+ assistantMessages.push(...collectAssistantContent(parsed));
105
+ return;
106
+ }
107
+ if (isLettaSseStopReasonMessage(parsed)) {
108
+ stopReason = parsed.stop_reason;
109
+ }
110
+ }
111
+ async function processLine(line) {
112
+ if (!line.trim() || !line.startsWith("data: ")) {
113
+ return;
114
+ }
115
+ await processData(line.slice(6));
116
+ }
117
+ try {
118
+ while (true) {
119
+ const { done, value } = await reader.read();
120
+ if (done) {
121
+ break;
122
+ }
123
+ buffer += decoder.decode(value, { stream: true });
124
+ const lines = buffer.split(`
125
+ `);
126
+ buffer = lines.pop() ?? "";
127
+ for (const line of lines) {
128
+ await processLine(line);
129
+ }
130
+ }
131
+ const flushed = decoder.decode();
132
+ if (flushed) {
133
+ buffer += flushed;
134
+ }
135
+ if (buffer) {
136
+ await processLine(buffer);
137
+ }
138
+ } finally {
139
+ reader.releaseLock();
140
+ }
141
+ const text = assistantMessages.join(" ").trim();
142
+ if (!text) {
143
+ throw new LettaStreamNoAssistantMessageError;
144
+ }
145
+ return {
146
+ text,
147
+ chunkCount: assistantMessages.length,
148
+ stopReason
149
+ };
150
+ }
151
+ // src/channels/processor.ts
152
+ function formatSenderLabel(entry) {
153
+ const senderName = entry.senderName?.trim();
154
+ const senderId = entry.senderId?.trim();
155
+ if (senderName && senderId && senderName !== senderId) {
156
+ return `${senderName}:${senderId}`;
157
+ }
158
+ if (senderName) {
159
+ return senderName;
160
+ }
161
+ if (senderId) {
162
+ return senderId;
163
+ }
164
+ return "unknown";
165
+ }
166
+ function formatThreadContextEntry(entry) {
167
+ const messageId = entry.messageId ? `<${entry.messageId}>` : "";
168
+ return `[${formatSenderLabel(entry)}]${messageId}:${entry.text}`;
169
+ }
170
+ function formatThreadContext(context) {
171
+ const lines = [];
172
+ const label = context.label?.trim();
173
+ lines.push(label ? `--- ${label} ---` : "--- Thread Context ---");
174
+ if (context.starter) {
175
+ lines.push(`starter: ${formatThreadContextEntry(context.starter)}`);
176
+ }
177
+ for (const entry of context.history ?? []) {
178
+ lines.push(formatThreadContextEntry(entry));
179
+ }
180
+ lines.push("--- End Thread Context ---");
181
+ return `${lines.join(`
182
+ `)}
183
+
184
+ `;
185
+ }
186
+ function formatInboundSender(message) {
187
+ const senderName = message.senderName?.trim();
188
+ if (senderName && senderName !== message.senderId) {
189
+ return `${senderName}:${message.senderId}`;
190
+ }
191
+ return message.senderId;
192
+ }
193
+ function buildChannelTurnSource(params) {
194
+ const { message, route } = params;
195
+ return {
196
+ channel: message.channel,
197
+ accountId: message.accountId ?? route.accountId,
198
+ chatId: message.chatId,
199
+ chatType: message.chatType ?? route.chatType,
200
+ senderId: message.senderId,
201
+ senderTeamId: message.senderTeamId,
202
+ messageId: message.messageId,
203
+ threadId: message.threadId ?? route.threadId ?? null,
204
+ agentId: route.agentId,
205
+ conversationId: route.conversationId
206
+ };
207
+ }
208
+ function formatInboundChannelMessageForAgent(params) {
209
+ const { message } = params;
210
+ const context = message.threadContext ? formatThreadContext(message.threadContext) : "";
211
+ const chatLabel = message.chatLabel?.trim() || message.chatId;
212
+ const timestamp = new Date(message.timestamp).toISOString();
213
+ const prefix = `[${message.channel}:${chatLabel}][${formatInboundSender(message)}]<${timestamp}>:`;
214
+ return `${context}${prefix}${message.text}`;
215
+ }
216
+ function formatLegacyBatchedSender(message) {
217
+ const senderName = message.senderName?.trim();
218
+ const senderId = message.senderId?.trim() ?? "unknown";
219
+ if (senderName && senderName !== senderId) {
220
+ return `${senderName}:${senderId}`;
221
+ }
222
+ return senderId;
223
+ }
224
+ function formatBatchedChannelMessagesForAgent(params) {
225
+ const { messages } = params;
226
+ if (messages.length === 0) {
227
+ return "";
228
+ }
229
+ const firstMessage = messages[0];
230
+ if (messages.length === 1 && firstMessage) {
231
+ return firstMessage.text;
232
+ }
233
+ if (messages.every((message) => message.channelTurnSource)) {
234
+ const formatted2 = messages.map((message) => message.text).join(`
235
+ `);
236
+ return `--- Batched Channel Messages (${messages.length}) ---
237
+ ${formatted2}
238
+ --- End Batched Channel Messages ---`;
239
+ }
240
+ const formatted = messages.map((message) => {
241
+ const timestamp = message.timestamp ?? "unknown";
242
+ return `[user@${formatLegacyBatchedSender(message)}]<${timestamp}>:${message.text}`;
243
+ }).join(`
244
+ `);
245
+ return `--- Batched Messages (${messages.length}) ---
246
+ ${formatted}
247
+ --- End Batched Messages ---`;
248
+ }
249
+ function buildOutboundChannelMessageFromTurnSource(params) {
250
+ const { turnSource } = params;
251
+ return {
252
+ channel: turnSource.channel,
253
+ accountId: turnSource.accountId,
254
+ chatId: turnSource.chatId,
255
+ threadId: turnSource.threadId,
256
+ text: params.text,
257
+ agentId: turnSource.agentId,
258
+ conversationId: turnSource.conversationId
259
+ };
260
+ }
261
+ // src/channels/progress-formatting.ts
262
+ var MAX_PROGRESS_TEXT_LENGTH = 140;
263
+ var MAX_PROGRESS_DETAILS_LENGTH = 180;
264
+ var MAX_SHELL_PROGRESS_DETAILS_LENGTH = 64;
265
+ var MAX_SUBAGENT_PROGRESS_DETAILS_LENGTH = 180;
266
+ var ESCAPE_CODE = String.fromCharCode(27);
267
+ var ANSI_ESCAPE_RE = new RegExp(`${ESCAPE_CODE}\\[[0-9;?]*[ -/]*[@-~]`, "g");
268
+ var SECRET_ASSIGNMENT_RE = /\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASS|API[_-]?KEY|ACCESS[_-]?KEY)[A-Z0-9_]*)\s*=\s*("[^"]*"|'[^']*'|\S+)/gi;
269
+ var SECRET_JSON_RE = /(["']?(?:token|secret|password|api[_-]?key|access[_-]?key)["']?\s*[:=]\s*)("[^"]*"|'[^']*'|\S+)/gi;
270
+ function isTaskTool(name) {
271
+ return name === "Task" || name === "task" || name === "Agent" || name === "agent";
272
+ }
273
+ function isShellTool(name) {
274
+ const normalized = name.toLowerCase();
275
+ return normalized === "bash" || normalized === "exec_command" || normalized === "shell_command" || normalized === "shell" || normalized === "runshellcommand";
276
+ }
277
+ function isWebSearchTool(name) {
278
+ const normalized = name.toLowerCase();
279
+ return normalized === "web_search" || normalized === "websearch";
280
+ }
281
+ function isSearchTool(name) {
282
+ const normalized = name.toLowerCase();
283
+ return normalized === "grep" || normalized === "grep_files" || normalized === "grepfiles" || normalized === "search_file_content" || normalized === "searchfilecontent";
284
+ }
285
+ function isGlobTool(name) {
286
+ const normalized = name.toLowerCase();
287
+ return normalized === "glob" || normalized === "glob_gemini";
288
+ }
289
+ function isFileReadTool(name) {
290
+ const normalized = name.toLowerCase();
291
+ return normalized === "read" || normalized === "read_file" || normalized === "readfile" || normalized === "read_file_gemini" || normalized === "readfilegemini";
292
+ }
293
+ function isFileWriteTool(name) {
294
+ const normalized = name.toLowerCase();
295
+ return normalized === "write" || normalized === "write_file" || normalized === "writefile" || normalized === "write_file_gemini" || normalized === "writefilegemini";
296
+ }
297
+ function isFileEditTool(name) {
298
+ const normalized = name.toLowerCase();
299
+ return normalized === "edit" || normalized === "multi_edit" || normalized === "multiedit" || normalized === "replace" || normalized === "apply_patch" || normalized === "applypatch";
300
+ }
301
+ function getPathBaseName(filePath) {
302
+ return filePath.split(/[\\/]/).filter(Boolean).pop() ?? filePath;
303
+ }
304
+ function asRecord(value) {
305
+ return value && typeof value === "object" ? value : null;
306
+ }
307
+ function firstNonEmptyString(...values) {
308
+ for (const value of values) {
309
+ if (typeof value === "string" && value.trim().length > 0) {
310
+ return value;
311
+ }
312
+ }
313
+ return;
314
+ }
315
+ function truncateChannelProgressText(value, maxLength, marker = "...") {
316
+ if (value.length <= maxLength) {
317
+ return value;
318
+ }
319
+ if (maxLength <= marker.length) {
320
+ return marker.slice(0, Math.max(0, maxLength));
321
+ }
322
+ return `${value.slice(0, maxLength - marker.length).trimEnd()}${marker}`;
323
+ }
324
+ function replaceControlCharacters(value) {
325
+ let result = "";
326
+ for (let index = 0;index < value.length; index += 1) {
327
+ const character = value[index] ?? "";
328
+ const code = character.charCodeAt(0);
329
+ result += code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31 || code === 127 ? " " : character;
330
+ }
331
+ return result;
332
+ }
333
+ function sanitizeChannelProgressCore(value) {
334
+ const raw = typeof value === "string" ? value : String(value ?? "");
335
+ const redacted = raw.replace(ANSI_ESCAPE_RE, "").replace(SECRET_ASSIGNMENT_RE, "$1=[redacted]").replace(SECRET_JSON_RE, "$1[redacted]");
336
+ return replaceControlCharacters(redacted).replace(/[\r\n\t]+/g, " ").replace(/@(?=channel|here|everyone|[A-Za-z0-9._-]+)/gi, "@​").replace(/\s+/g, " ").trim();
337
+ }
338
+ function sanitizeChannelProgressText(value, maxLength = MAX_PROGRESS_TEXT_LENGTH) {
339
+ return truncateChannelProgressText(sanitizeChannelProgressCore(value), maxLength);
340
+ }
341
+ function sanitizeChannelProgressIdentifier(value, fallback) {
342
+ const text = sanitizeChannelProgressText(value, 64);
343
+ if (!text) {
344
+ return fallback;
345
+ }
346
+ const cleaned = text.replace(/[^A-Za-z0-9_.:/ -]/g, "").trim();
347
+ return cleaned || fallback;
348
+ }
349
+ function summarizeShellCommand(command) {
350
+ const normalized = sanitizeChannelProgressText(command, 1e4);
351
+ if (!normalized) {
352
+ return "";
353
+ }
354
+ const segments = normalized.split(/\s*;\s*/).map((segment) => segment.trim()).filter(Boolean);
355
+ const firstTwoSegments = segments.slice(0, 2).join("; ");
356
+ const previewSource = firstTwoSegments.length > 0 && firstTwoSegments.length <= 70 ? firstTwoSegments : segments[0] ?? normalized;
357
+ const withoutPipeline = previewSource.split(/\s*\|\s*/)[0] ?? previewSource;
358
+ return sanitizeChannelProgressText(withoutPipeline.trim() || normalized, MAX_SHELL_PROGRESS_DETAILS_LENGTH);
359
+ }
360
+ function getSkillNameFromArguments(parsedArguments) {
361
+ return firstNonEmptyString(parsedArguments.skill, parsedArguments.skillName);
362
+ }
363
+ function getFragmentedSkillName(summary) {
364
+ const skillMatch = summary.argumentsText?.match(/"(?:skill|skillName)"\s*:\s*"([^"]+)"/);
365
+ return skillMatch?.[1];
366
+ }
367
+ function resolveSkillDescription(skillName, options) {
368
+ const lookup = options?.skillDescriptionsByName;
369
+ if (!skillName || !lookup) {
370
+ return;
371
+ }
372
+ if ("get" in lookup && typeof lookup.get === "function") {
373
+ return firstNonEmptyString(lookup.get(skillName));
374
+ }
375
+ return firstNonEmptyString(lookup[skillName]);
376
+ }
377
+ function formatSkillProgressTitleFromName(skillName) {
378
+ const sanitized = sanitizeChannelProgressText(skillName, MAX_PROGRESS_DETAILS_LENGTH);
379
+ return sanitized ? `Skill: ${sanitized}` : undefined;
380
+ }
381
+ function formatShellProgressDetailsFromArguments(parsedArguments) {
382
+ const description = firstNonEmptyString(parsedArguments.description);
383
+ if (description) {
384
+ return sanitizeChannelProgressText(description, MAX_PROGRESS_DETAILS_LENGTH) || undefined;
385
+ }
386
+ const commandPreview = firstNonEmptyString(parsedArguments.command, parsedArguments.cmd);
387
+ return summarizeShellCommand(commandPreview ?? "") || undefined;
388
+ }
389
+ function formatFragmentedShellProgressDetails(summary) {
390
+ const descriptionMatch = summary.argumentsText?.match(/"description"\s*:\s*"([^"]+)"/);
391
+ if (descriptionMatch?.[1]) {
392
+ return sanitizeChannelProgressText(descriptionMatch[1], MAX_PROGRESS_DETAILS_LENGTH) || undefined;
393
+ }
394
+ return;
395
+ }
396
+ function formatSubagentProgressDetailsFromArguments(parsedArguments) {
397
+ const preview = firstNonEmptyString(parsedArguments.prompt, parsedArguments.description, parsedArguments.subject);
398
+ const sanitized = sanitizeChannelProgressText(preview, MAX_SUBAGENT_PROGRESS_DETAILS_LENGTH);
399
+ return sanitized || undefined;
400
+ }
401
+ function formatFragmentedSubagentProgressDetails(summary) {
402
+ const previewMatch = summary.argumentsText?.match(/"(?:prompt|description|subject)"\s*:\s*"([^"]+)"/);
403
+ if (!previewMatch?.[1]) {
404
+ return;
405
+ }
406
+ const sanitized = sanitizeChannelProgressText(previewMatch[1], MAX_SUBAGENT_PROGRESS_DETAILS_LENGTH);
407
+ return sanitized || undefined;
408
+ }
409
+ function parseToolArguments(value) {
410
+ if (!value) {
411
+ return null;
412
+ }
413
+ try {
414
+ return asRecord(JSON.parse(value));
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ function isFetchWebpageToolName(name) {
420
+ return name === "fetch_webpage" || name === "FetchWebpage" || name === "fetchWebpage";
421
+ }
422
+ function isSkillToolName(name) {
423
+ if (!name) {
424
+ return false;
425
+ }
426
+ const normalized = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : name;
427
+ return normalized === "Skill" || normalized === "skill";
428
+ }
429
+ function isFilePathToolName(name) {
430
+ return isFileReadTool(name) || isFileWriteTool(name) || isFileEditTool(name);
431
+ }
432
+ function getFileToolKind(name) {
433
+ if (isFileReadTool(name)) {
434
+ return "read";
435
+ }
436
+ if (isFileWriteTool(name)) {
437
+ return "write";
438
+ }
439
+ if (isFileEditTool(name)) {
440
+ return "update";
441
+ }
442
+ return null;
443
+ }
444
+ function countProgressLines(value) {
445
+ return value ? value.split(`
446
+ `).length : 0;
447
+ }
448
+ function formatLineChangeSummary(summary) {
449
+ if (!summary) {
450
+ return "";
451
+ }
452
+ const parts = [];
453
+ if (summary.additions !== undefined) {
454
+ parts.push(`+${summary.additions}`);
455
+ }
456
+ if (summary.deletions !== undefined) {
457
+ parts.push(`-${summary.deletions}`);
458
+ }
459
+ return parts.length > 0 ? ` ${parts.join(" ")}` : "";
460
+ }
461
+ function extractFilePathFromArguments(parsedArguments) {
462
+ return firstNonEmptyString(parsedArguments.file_path, parsedArguments.filePath, parsedArguments.path);
463
+ }
464
+ function formatProgressFileName(filePath) {
465
+ if (!filePath) {
466
+ return;
467
+ }
468
+ const fileName = getPathBaseName(filePath) || filePath;
469
+ return sanitizeChannelProgressText(fileName, MAX_PROGRESS_DETAILS_LENGTH) || undefined;
470
+ }
471
+ function getEditLineChangeSummary(parsedArguments) {
472
+ const oldString = firstNonEmptyString(parsedArguments.old_string);
473
+ const newString = firstNonEmptyString(parsedArguments.new_string);
474
+ if (oldString === undefined || newString === undefined) {
475
+ return null;
476
+ }
477
+ return {
478
+ additions: countProgressLines(newString),
479
+ deletions: countProgressLines(oldString)
480
+ };
481
+ }
482
+ function getMultiEditLineChangeSummary(parsedArguments) {
483
+ if (!Array.isArray(parsedArguments.edits)) {
484
+ return null;
485
+ }
486
+ let additions = 0;
487
+ let deletions = 0;
488
+ let counted = false;
489
+ for (const edit of parsedArguments.edits) {
490
+ const record = asRecord(edit);
491
+ if (!record) {
492
+ continue;
493
+ }
494
+ const oldString = firstNonEmptyString(record.old_string);
495
+ const newString = firstNonEmptyString(record.new_string);
496
+ if (oldString === undefined || newString === undefined) {
497
+ continue;
498
+ }
499
+ additions += countProgressLines(newString);
500
+ deletions += countProgressLines(oldString);
501
+ counted = true;
502
+ }
503
+ return counted ? { additions, deletions } : null;
504
+ }
505
+ function getWriteLineChangeSummary(parsedArguments) {
506
+ const content = firstNonEmptyString(parsedArguments.content);
507
+ if (content === undefined) {
508
+ return null;
509
+ }
510
+ return {
511
+ additions: countProgressLines(content)
512
+ };
513
+ }
514
+ function getFileLineChangeSummary(name, parsedArguments) {
515
+ if (isFileWriteTool(name)) {
516
+ return getWriteLineChangeSummary(parsedArguments);
517
+ }
518
+ if (name === "MultiEdit" || name === "multi_edit") {
519
+ return getMultiEditLineChangeSummary(parsedArguments);
520
+ }
521
+ if (isFileEditTool(name)) {
522
+ return getEditLineChangeSummary(parsedArguments);
523
+ }
524
+ return null;
525
+ }
526
+ function getFileToolVerb(kind, status) {
527
+ if (status === "error") {
528
+ if (kind === "read") {
529
+ return "Tried to read";
530
+ }
531
+ if (kind === "write") {
532
+ return "Tried to write";
533
+ }
534
+ return "Tried to update";
535
+ }
536
+ if (status === "started") {
537
+ if (kind === "read") {
538
+ return "Reading";
539
+ }
540
+ if (kind === "write") {
541
+ return "Writing";
542
+ }
543
+ return "Updating";
544
+ }
545
+ if (kind === "read") {
546
+ return "Read";
547
+ }
548
+ if (kind === "write") {
549
+ return "Wrote";
550
+ }
551
+ return "Updated";
552
+ }
553
+ function formatFileToolProgressTitle(name, parsedArguments, status) {
554
+ const kind = getFileToolKind(name);
555
+ if (!kind) {
556
+ return;
557
+ }
558
+ const fileName = formatProgressFileName(extractFilePathFromArguments(parsedArguments));
559
+ if (!fileName) {
560
+ return;
561
+ }
562
+ const stats = status === "completed" ? formatLineChangeSummary(getFileLineChangeSummary(name, parsedArguments)) : "";
563
+ return `${getFileToolVerb(kind, status)} ${fileName}${stats}`;
564
+ }
565
+ function formatFragmentedFileToolProgressTitle(summary, status) {
566
+ if (!summary.name || !summary.argumentsText) {
567
+ return;
568
+ }
569
+ const kind = getFileToolKind(summary.name);
570
+ if (!kind) {
571
+ return;
572
+ }
573
+ const filePathMatch = summary.argumentsText.match(/"(?:file_path|filePath|path)"\s*:\s*"([^"]+)"/);
574
+ const fileName = formatProgressFileName(filePathMatch?.[1]);
575
+ if (!fileName) {
576
+ return;
577
+ }
578
+ return `${getFileToolVerb(kind, status)} ${fileName}`;
579
+ }
580
+ function formatToolProgressTitle(summary, status) {
581
+ if (!summary.name || !summary.argumentsText) {
582
+ return;
583
+ }
584
+ const parsedArguments = parseToolArguments(summary.argumentsText);
585
+ if (parsedArguments) {
586
+ if (isSkillToolName(summary.name)) {
587
+ return formatSkillProgressTitleFromName(getSkillNameFromArguments(parsedArguments));
588
+ }
589
+ if (isFilePathToolName(summary.name)) {
590
+ return formatFileToolProgressTitle(summary.name, parsedArguments, status);
591
+ }
592
+ return;
593
+ }
594
+ if (isSkillToolName(summary.name)) {
595
+ return formatSkillProgressTitleFromName(getFragmentedSkillName(summary));
596
+ }
597
+ if (isFilePathToolName(summary.name)) {
598
+ return formatFragmentedFileToolProgressTitle(summary, status);
599
+ }
600
+ return;
601
+ }
602
+ function formatSkillProgressDetailsFromArguments(parsedArguments, options) {
603
+ const skillName = getSkillNameFromArguments(parsedArguments);
604
+ const detail = resolveSkillDescription(skillName, options) ?? skillName;
605
+ const sanitized = sanitizeChannelProgressText(detail, MAX_PROGRESS_DETAILS_LENGTH);
606
+ return sanitized || undefined;
607
+ }
608
+ function formatFragmentedSkillProgressDetails(summary, options) {
609
+ const skillName = getFragmentedSkillName(summary);
610
+ const detail = resolveSkillDescription(skillName, options) ?? skillName;
611
+ const sanitized = sanitizeChannelProgressText(detail, MAX_PROGRESS_DETAILS_LENGTH);
612
+ return sanitized || undefined;
613
+ }
614
+ function formatToolProgressDetails(summary, options) {
615
+ if (!summary.name || !summary.argumentsText) {
616
+ return;
617
+ }
618
+ const parsedArguments = parseToolArguments(summary.argumentsText);
619
+ if (parsedArguments) {
620
+ if (isWebSearchTool(summary.name)) {
621
+ const query = firstNonEmptyString(parsedArguments.query);
622
+ const sanitized = sanitizeChannelProgressText(query, MAX_PROGRESS_DETAILS_LENGTH);
623
+ return sanitized || undefined;
624
+ }
625
+ if (isFetchWebpageToolName(summary.name)) {
626
+ const url = firstNonEmptyString(parsedArguments.url);
627
+ const sanitized = sanitizeChannelProgressText(url, MAX_PROGRESS_DETAILS_LENGTH);
628
+ return sanitized || undefined;
629
+ }
630
+ if (isSkillToolName(summary.name)) {
631
+ return formatSkillProgressDetailsFromArguments(parsedArguments, options);
632
+ }
633
+ if (isTaskTool(summary.name)) {
634
+ return formatSubagentProgressDetailsFromArguments(parsedArguments);
635
+ }
636
+ if (isShellTool(summary.name)) {
637
+ return formatShellProgressDetailsFromArguments(parsedArguments);
638
+ }
639
+ if (isFilePathToolName(summary.name)) {
640
+ const filePath = firstNonEmptyString(parsedArguments.file_path, parsedArguments.filePath, parsedArguments.path);
641
+ const sanitized = sanitizeChannelProgressText(filePath, MAX_PROGRESS_DETAILS_LENGTH);
642
+ return sanitized || undefined;
643
+ }
644
+ if (isGlobTool(summary.name) || isSearchTool(summary.name)) {
645
+ const pattern = firstNonEmptyString(parsedArguments.pattern);
646
+ const sanitized = sanitizeChannelProgressText(pattern, MAX_PROGRESS_DETAILS_LENGTH);
647
+ return sanitized || undefined;
648
+ }
649
+ return;
650
+ }
651
+ if (isShellTool(summary.name)) {
652
+ return formatFragmentedShellProgressDetails(summary);
653
+ }
654
+ if (isSkillToolName(summary.name)) {
655
+ return formatFragmentedSkillProgressDetails(summary, options);
656
+ }
657
+ if (isTaskTool(summary.name)) {
658
+ return formatFragmentedSubagentProgressDetails(summary);
659
+ }
660
+ if (isFilePathToolName(summary.name)) {
661
+ const filePathMatch = summary.argumentsText.match(/"file_path"\s*:\s*"([^"]+)"/);
662
+ if (filePathMatch?.[1]) {
663
+ const sanitized = sanitizeChannelProgressText(filePathMatch[1], MAX_PROGRESS_DETAILS_LENGTH);
664
+ return sanitized || undefined;
665
+ }
666
+ }
667
+ return;
668
+ }
669
+
670
+ // src/channels/progress-builder.ts
671
+ function getMessageType(delta) {
672
+ return firstNonEmptyString(delta.message_type, delta.messageType) ?? null;
673
+ }
674
+ function getRunId(delta) {
675
+ return firstNonEmptyString(delta.run_id, delta.runId);
676
+ }
677
+ function withRunId(update, runId) {
678
+ return {
679
+ ...update,
680
+ ...runId ? { runId } : {}
681
+ };
682
+ }
683
+ function getCommandId(delta) {
684
+ const command = firstNonEmptyString(delta.command_id, delta.commandId);
685
+ return sanitizeChannelProgressIdentifier(command, "command");
686
+ }
687
+ function getSlashCommand(delta) {
688
+ const commandId = firstNonEmptyString(delta.command_id, delta.commandId);
689
+ const command = commandId ? `/${commandId.replace(/^\/+/, "")}` : "command";
690
+ return sanitizeChannelProgressIdentifier(command, "command");
691
+ }
692
+ function getToolStatus(delta) {
693
+ const status = firstNonEmptyString(delta.status)?.toLowerCase();
694
+ return status === "error" || status === "failed" ? "error" : "completed";
695
+ }
696
+ function stringifyProgressValue(value) {
697
+ if (typeof value === "string") {
698
+ return value;
699
+ }
700
+ if (value === undefined || value === null) {
701
+ return;
702
+ }
703
+ try {
704
+ return JSON.stringify(value);
705
+ } catch {
706
+ return String(value);
707
+ }
708
+ }
709
+ function formatToolErrorDetails(record) {
710
+ const toolReturn = record.tool_return ?? record.toolReturn;
711
+ const toolReturnRecord = asRecord(toolReturn);
712
+ const preview = firstNonEmptyString(record.stderr, record.error, record.message, toolReturnRecord?.stderr, toolReturnRecord?.error, toolReturnRecord?.message, toolReturnRecord?.output, stringifyProgressValue(toolReturn));
713
+ const sanitized = sanitizeChannelProgressText(preview, MAX_PROGRESS_DETAILS_LENGTH);
714
+ return sanitized || undefined;
715
+ }
716
+ function toolNameForMessage(summary) {
717
+ return summary?.name ? `: ${summary.name}` : "";
718
+ }
719
+ function createChannelTurnProgressBuilder(options = {}) {
720
+ const argumentsByToolCallId = new Map;
721
+ const namesByToolCallId = new Map;
722
+ function extractToolCallSummary(value) {
723
+ const record = asRecord(value);
724
+ if (!record) {
725
+ return null;
726
+ }
727
+ const id = firstNonEmptyString(record.tool_call_id);
728
+ const cacheId = id ? sanitizeChannelProgressIdentifier(id, "tool-call") : undefined;
729
+ const extractedName = firstNonEmptyString(record.name);
730
+ if (cacheId && extractedName) {
731
+ namesByToolCallId.set(cacheId, extractedName);
732
+ }
733
+ const resolvedName = extractedName ?? (cacheId ? namesByToolCallId.get(cacheId) : undefined);
734
+ const rawArguments = typeof record.arguments === "string" && record.arguments.length > 0 ? record.arguments : asRecord(record.arguments) ? JSON.stringify(record.arguments) : undefined;
735
+ let argumentsText;
736
+ if (cacheId && rawArguments !== undefined) {
737
+ const existing = argumentsByToolCallId.get(cacheId);
738
+ if (parseToolArguments(rawArguments)) {
739
+ argumentsByToolCallId.set(cacheId, rawArguments);
740
+ argumentsText = rawArguments;
741
+ } else if (existing) {
742
+ if (parseToolArguments(existing)) {
743
+ argumentsText = existing;
744
+ } else {
745
+ const accumulated = existing + rawArguments;
746
+ argumentsByToolCallId.set(cacheId, accumulated);
747
+ argumentsText = accumulated;
748
+ }
749
+ } else {
750
+ argumentsByToolCallId.set(cacheId, rawArguments);
751
+ argumentsText = rawArguments;
752
+ }
753
+ } else if (!id && rawArguments !== undefined) {
754
+ argumentsText = rawArguments;
755
+ }
756
+ if (!id && !resolvedName) {
757
+ return null;
758
+ }
759
+ return {
760
+ ...cacheId ? { id: cacheId } : {},
761
+ ...resolvedName ? { name: sanitizeChannelProgressIdentifier(resolvedName, "tool") } : {},
762
+ ...argumentsText ? { argumentsText } : {}
763
+ };
764
+ }
765
+ function extractClientToolSummary(record) {
766
+ const id = firstNonEmptyString(record.tool_call_id, record.toolCallId);
767
+ const cacheId = id ? sanitizeChannelProgressIdentifier(id, "tool-call") : undefined;
768
+ const extractedName = firstNonEmptyString(record.tool_name, record.toolName, record.name);
769
+ if (cacheId && extractedName) {
770
+ namesByToolCallId.set(cacheId, extractedName);
771
+ }
772
+ const resolvedName = extractedName ?? (cacheId ? namesByToolCallId.get(cacheId) : undefined);
773
+ const rawArguments = firstNonEmptyString(record.tool_args, record.toolArgs, record.arguments);
774
+ if (cacheId && rawArguments) {
775
+ argumentsByToolCallId.set(cacheId, rawArguments);
776
+ }
777
+ const argumentsText = rawArguments ?? (cacheId ? argumentsByToolCallId.get(cacheId) : undefined);
778
+ if (!cacheId && !resolvedName) {
779
+ return null;
780
+ }
781
+ return {
782
+ ...cacheId ? { id: cacheId } : {},
783
+ ...resolvedName ? { name: sanitizeChannelProgressIdentifier(resolvedName, "tool") } : {},
784
+ ...argumentsText ? { argumentsText } : {}
785
+ };
786
+ }
787
+ function extractToolCalls(delta) {
788
+ const candidates = Array.isArray(delta.tool_calls) ? delta.tool_calls : delta.tool_calls ? [delta.tool_calls] : delta.tool_call ? [delta.tool_call] : [];
789
+ const summaries = [];
790
+ const seen = new Set;
791
+ for (const candidate of candidates) {
792
+ const summary = extractToolCallSummary(candidate);
793
+ if (!summary) {
794
+ continue;
795
+ }
796
+ const key = `${summary.id ?? ""}:${summary.name ?? ""}`;
797
+ if (seen.has(key)) {
798
+ continue;
799
+ }
800
+ seen.add(key);
801
+ summaries.push(summary);
802
+ }
803
+ return summaries;
804
+ }
805
+ function extractToolReturns(delta) {
806
+ const candidates = Array.isArray(delta.tool_returns) ? delta.tool_returns : [delta];
807
+ const summaries = [];
808
+ const seen = new Set;
809
+ for (const candidate of candidates) {
810
+ const record = asRecord(candidate);
811
+ if (!record) {
812
+ continue;
813
+ }
814
+ const summary = extractToolCallSummary(record);
815
+ if (!summary) {
816
+ continue;
817
+ }
818
+ const key = `${summary.id ?? ""}:${summary.name ?? ""}`;
819
+ if (seen.has(key)) {
820
+ continue;
821
+ }
822
+ seen.add(key);
823
+ const status = getToolStatus(record);
824
+ summaries.push({
825
+ summary,
826
+ status,
827
+ ...status === "error" ? { errorDetails: formatToolErrorDetails(record) } : {}
828
+ });
829
+ }
830
+ return summaries;
831
+ }
832
+ function buildToolCallUpdates(record, runId) {
833
+ const tools = extractToolCalls(record);
834
+ const updates = [];
835
+ for (const tool of tools) {
836
+ const toolDetails = formatToolProgressDetails(tool, options);
837
+ const toolTitle = formatToolProgressTitle(tool, "started");
838
+ updates.push(withRunId({
839
+ kind: "tool",
840
+ state: "started",
841
+ message: `Preparing tool${toolNameForMessage(tool)}`,
842
+ ...tool.id ? { toolCallId: tool.id } : {},
843
+ ...tool.name ? { toolName: tool.name } : {},
844
+ ...toolDetails ? { toolDetails } : {},
845
+ ...toolTitle ? { toolTitle } : {}
846
+ }, runId));
847
+ }
848
+ return updates;
849
+ }
850
+ function buildUpdates(delta) {
851
+ const record = asRecord(delta);
852
+ if (!record) {
853
+ return [];
854
+ }
855
+ const messageType = getMessageType(record);
856
+ const runId = getRunId(record);
857
+ switch (messageType) {
858
+ case "reasoning_message":
859
+ return [
860
+ withRunId({
861
+ kind: "thinking",
862
+ state: "updated",
863
+ message: "Thinking"
864
+ }, runId)
865
+ ];
866
+ case "assistant_message":
867
+ return [
868
+ withRunId({
869
+ kind: "responding",
870
+ state: "updated",
871
+ message: "Writing reply"
872
+ }, runId)
873
+ ];
874
+ case "approval_request_message":
875
+ return buildToolCallUpdates(record, runId);
876
+ case "tool_call_message": {
877
+ const updates = buildToolCallUpdates(record, runId);
878
+ if (updates.length === 0) {
879
+ return [
880
+ withRunId({
881
+ kind: "tool",
882
+ state: "started",
883
+ message: "Preparing tool call"
884
+ }, runId)
885
+ ];
886
+ }
887
+ return updates;
888
+ }
889
+ case "tool_return_message": {
890
+ const toolReturns = extractToolReturns(record);
891
+ const updates = [];
892
+ for (const { summary, status, errorDetails } of toolReturns) {
893
+ const accumulatedArgs = summary.id ? argumentsByToolCallId.get(summary.id) : undefined;
894
+ if (summary.id) {
895
+ argumentsByToolCallId.delete(summary.id);
896
+ namesByToolCallId.delete(summary.id);
897
+ }
898
+ const toolWithAccumulatedArgs = accumulatedArgs ? { ...summary, argumentsText: accumulatedArgs } : summary;
899
+ const toolDetails = formatToolProgressDetails(toolWithAccumulatedArgs, options);
900
+ const toolTitle = formatToolProgressTitle(toolWithAccumulatedArgs, status);
901
+ updates.push(withRunId({
902
+ kind: "tool",
903
+ state: status,
904
+ message: status === "error" ? "Tool failed" : "Tool finished",
905
+ ...summary.id ? { toolCallId: summary.id } : {},
906
+ ...summary.name ? { toolName: summary.name } : {},
907
+ ...toolDetails ? { toolDetails } : {},
908
+ ...status === "error" && errorDetails ? { errorDetails } : {},
909
+ ...toolTitle ? { toolTitle } : {}
910
+ }, runId));
911
+ }
912
+ return updates;
913
+ }
914
+ case "client_tool_start": {
915
+ const tool = extractClientToolSummary(record);
916
+ const toolDetails = tool ? formatToolProgressDetails(tool, options) : undefined;
917
+ const toolTitle = tool ? formatToolProgressTitle(tool, "started") : undefined;
918
+ return [
919
+ withRunId({
920
+ kind: "tool",
921
+ state: "started",
922
+ message: "Running tool",
923
+ ...tool?.id ? { toolCallId: tool.id } : {},
924
+ ...tool?.name ? { toolName: tool.name } : {},
925
+ ...toolDetails ? { toolDetails } : {},
926
+ ...toolTitle ? { toolTitle } : {}
927
+ }, runId)
928
+ ];
929
+ }
930
+ case "client_tool_end": {
931
+ const state = getToolStatus(record);
932
+ const tool = extractClientToolSummary(record);
933
+ const accumulatedArgs = tool?.id ? argumentsByToolCallId.get(tool.id) : undefined;
934
+ if (tool?.id) {
935
+ argumentsByToolCallId.delete(tool.id);
936
+ namesByToolCallId.delete(tool.id);
937
+ }
938
+ const toolWithAccumulatedArgs = tool && accumulatedArgs ? { ...tool, argumentsText: accumulatedArgs } : tool;
939
+ const toolDetails = toolWithAccumulatedArgs ? formatToolProgressDetails(toolWithAccumulatedArgs, options) : undefined;
940
+ const errorDetails = state === "error" ? formatToolErrorDetails(record) : undefined;
941
+ const toolTitle = toolWithAccumulatedArgs ? formatToolProgressTitle(toolWithAccumulatedArgs, state) : undefined;
942
+ return [
943
+ withRunId({
944
+ kind: "tool",
945
+ state,
946
+ message: state === "error" ? "Tool failed" : "Tool finished",
947
+ ...tool?.id ? { toolCallId: tool.id } : {},
948
+ ...tool?.name ? { toolName: tool.name } : {},
949
+ ...toolDetails ? { toolDetails } : {},
950
+ ...errorDetails ? { errorDetails } : {},
951
+ ...toolTitle ? { toolTitle } : {}
952
+ }, runId)
953
+ ];
954
+ }
955
+ case "slash_command_start": {
956
+ const command = getSlashCommand(record);
957
+ return [
958
+ withRunId({
959
+ kind: "command",
960
+ state: "started",
961
+ message: `Running ${command}`,
962
+ command
963
+ }, runId)
964
+ ];
965
+ }
966
+ case "slash_command_end": {
967
+ const command = getSlashCommand(record);
968
+ const success = record.success !== false;
969
+ return [
970
+ withRunId({
971
+ kind: "command",
972
+ state: success ? "completed" : "error",
973
+ message: success ? `${command} finished` : `${command} failed`,
974
+ command
975
+ }, runId)
976
+ ];
977
+ }
978
+ case "command_start": {
979
+ const command = getCommandId(record);
980
+ return [
981
+ withRunId({
982
+ kind: "command",
983
+ state: "started",
984
+ message: "Running command",
985
+ command
986
+ }, runId)
987
+ ];
988
+ }
989
+ case "command_end": {
990
+ const command = getCommandId(record);
991
+ const success = record.success !== false;
992
+ return [
993
+ withRunId({
994
+ kind: "command",
995
+ state: success ? "completed" : "error",
996
+ message: success ? "Command finished" : "Command failed",
997
+ command
998
+ }, runId)
999
+ ];
1000
+ }
1001
+ case "status": {
1002
+ const message = sanitizeChannelProgressText(record.message);
1003
+ if (!message) {
1004
+ return [];
1005
+ }
1006
+ return [
1007
+ withRunId({
1008
+ kind: "status",
1009
+ state: "updated",
1010
+ message
1011
+ }, runId)
1012
+ ];
1013
+ }
1014
+ case "retry": {
1015
+ const attempt = Number(record.attempt);
1016
+ const maxAttempts = Number(record.max_attempts ?? record.maxAttempts);
1017
+ const suffix = Number.isFinite(attempt) && Number.isFinite(maxAttempts) ? ` (${attempt}/${maxAttempts})` : "";
1018
+ return [
1019
+ withRunId({
1020
+ kind: "retry",
1021
+ state: "updated",
1022
+ message: `Retrying request${suffix}`
1023
+ }, runId)
1024
+ ];
1025
+ }
1026
+ case "loop_error":
1027
+ return [
1028
+ withRunId({
1029
+ kind: "error",
1030
+ state: "error",
1031
+ message: "Encountered an error"
1032
+ }, runId)
1033
+ ];
1034
+ default:
1035
+ return [];
1036
+ }
1037
+ }
1038
+ return { buildUpdates };
1039
+ }
1040
+ export {
1041
+ formatLettaStreamCoreErrorForChannel,
1042
+ formatInboundChannelMessageForAgent,
1043
+ formatBatchedChannelMessagesForAgent,
1044
+ createChannelTurnProgressBuilder,
1045
+ collectLettaSseAssistantText,
1046
+ buildOutboundChannelMessageFromTurnSource,
1047
+ buildChannelTurnSource,
1048
+ LettaStreamNoAssistantMessageError,
1049
+ LettaStreamCoreError,
1050
+ LETTA_STREAM_NO_ASSISTANT_MESSAGE_ERROR
1051
+ };
1052
+
1053
+ //# debugId=54152C3E895D639864756E2164756E21