@turingfocus/chat-gateway-tfrobot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mapper.js ADDED
@@ -0,0 +1,705 @@
1
+ import { ASK_USER_MAX_ANSWER_VALUES, ASK_USER_MAX_OPTIONS, ASK_USER_MAX_QUESTIONS, ASK_USER_MAX_TEXT_CHARACTERS, agentEventSchema, askUserInteractionResultSchema, chatErrorSchema, chatSnapshotSchema, chatUpdateSchema, compareAgentEventTransitions, compareTimelineItems, conversationSchema, messageSchema, sanitizeDiagnosticText, sanitizeRaw, unknownEventSchema, } from "@turingfocus/chat-protocol";
2
+ export const TFROBOT_CAPABILITIES = Object.freeze({
3
+ interrupt: true,
4
+ listConversations: true,
5
+ liveUpdates: true,
6
+ loadHistory: true,
7
+ sendText: true,
8
+ });
9
+ const asId = (value) => String(value);
10
+ export const syntheticRunId = (conversationId) => `run:${conversationId}:active`;
11
+ const safeRaw = (value) => {
12
+ try {
13
+ return sanitizeRaw(value);
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ };
19
+ const optionalRaw = (value) => {
20
+ const raw = safeRaw(value);
21
+ return raw === undefined ? {} : { raw };
22
+ };
23
+ const extraRaw = (value, knownKeys) => {
24
+ const extras = {};
25
+ for (const [key, item] of Object.entries(value)) {
26
+ if (!knownKeys.has(key))
27
+ extras[key] = item;
28
+ }
29
+ return Object.keys(extras).length === 0 ? {} : optionalRaw(extras);
30
+ };
31
+ const CONVERSATION_KEYS = new Set([
32
+ "conversationId",
33
+ "description",
34
+ "title",
35
+ "updateTimestamp",
36
+ ]);
37
+ const MESSAGE_KEYS = new Set([
38
+ "additionalKwargs",
39
+ "attachments",
40
+ "content",
41
+ "conversationId",
42
+ "createTimestamp",
43
+ "creator",
44
+ "msgId",
45
+ "msgType",
46
+ "reasoningContent",
47
+ "role",
48
+ "sequence",
49
+ ]);
50
+ const EVENT_KEYS = new Set([
51
+ "content",
52
+ "conversationId",
53
+ "createTimestamp",
54
+ "eventCreateTimestamp",
55
+ "eventId",
56
+ "eventScene",
57
+ "exception",
58
+ "sequence",
59
+ "status",
60
+ "transitionId",
61
+ "transitionSequence",
62
+ ]);
63
+ const messageRaw = (dto) => {
64
+ const raw = {};
65
+ for (const [key, value] of Object.entries(dto)) {
66
+ if (!MESSAGE_KEYS.has(key))
67
+ raw[key] = value;
68
+ }
69
+ if (!isEmptyRecord(dto.additionalKwargs)) {
70
+ raw["additionalKwargs"] = dto.additionalKwargs;
71
+ }
72
+ if (dto.attachments != null)
73
+ raw["attachments"] = dto.attachments;
74
+ if (Array.isArray(dto.reasoningContent)) {
75
+ raw["reasoningContent"] = dto.reasoningContent;
76
+ }
77
+ return Object.keys(raw).length === 0 ? {} : optionalRaw(raw);
78
+ };
79
+ const reasoningText = (dto) => {
80
+ if (typeof dto.reasoningContent === "string")
81
+ return dto.reasoningContent;
82
+ if (!Array.isArray(dto.reasoningContent))
83
+ return undefined;
84
+ const thinking = dto.reasoningContent.flatMap((entry) => {
85
+ const frozenText = entry["reasoningContent"];
86
+ if (typeof frozenText === "string" && frozenText.length > 0) {
87
+ return [frozenText];
88
+ }
89
+ const currentText = entry["text"];
90
+ return entry["kind"] === "thinking" &&
91
+ typeof currentText === "string" &&
92
+ currentText.length > 0
93
+ ? [currentText]
94
+ : [];
95
+ });
96
+ return thinking.length === 0 ? undefined : thinking.join("\n\n");
97
+ };
98
+ const summaryOf = (value, fallback) => {
99
+ if (typeof value === "string" && value.trim().length > 0) {
100
+ return sanitizeDiagnosticText(value);
101
+ }
102
+ return fallback;
103
+ };
104
+ export const mapConversation = (dto) => conversationSchema.parse({
105
+ id: asId(dto.conversationId),
106
+ title: dto.title,
107
+ ...(dto.description == null ? {} : { description: dto.description }),
108
+ ...(dto.updateTimestamp === undefined
109
+ ? {}
110
+ : { updatedAt: dto.updateTimestamp }),
111
+ ...extraRaw(dto, CONVERSATION_KEYS),
112
+ });
113
+ const mapRole = (role) => {
114
+ switch (role.toLocaleLowerCase("en-US")) {
115
+ case "assistant":
116
+ case "system":
117
+ case "tool":
118
+ case "user":
119
+ return role.toLocaleLowerCase("en-US");
120
+ default:
121
+ return "unknown";
122
+ }
123
+ };
124
+ const mapMessageContent = (dto) => {
125
+ const type = dto.msgType.toLocaleLowerCase("en-US");
126
+ if (type === "text" && typeof dto.content === "string") {
127
+ return { kind: "text", text: dto.content };
128
+ }
129
+ if (type === "audio" || type === "image" || type === "video") {
130
+ return {
131
+ kind: "media",
132
+ mediaType: type,
133
+ summary: summaryOf(dto.content, `${type} message`),
134
+ ...optionalRaw({
135
+ content: dto.content,
136
+ attachments: dto.attachments,
137
+ additionalKwargs: dto.additionalKwargs,
138
+ }),
139
+ };
140
+ }
141
+ if (type === "file") {
142
+ return {
143
+ kind: "file",
144
+ summary: summaryOf(dto.content, "File message"),
145
+ ...optionalRaw({
146
+ content: dto.content,
147
+ attachments: dto.attachments,
148
+ additionalKwargs: dto.additionalKwargs,
149
+ }),
150
+ };
151
+ }
152
+ if (type === "contact") {
153
+ return {
154
+ kind: "contact",
155
+ summary: summaryOf(dto.content, "Contact message"),
156
+ ...optionalRaw({ content: dto.content }),
157
+ };
158
+ }
159
+ if (type === "url") {
160
+ return {
161
+ kind: "url",
162
+ summary: summaryOf(dto.content, "URL message"),
163
+ ...optionalRaw({ content: dto.content }),
164
+ };
165
+ }
166
+ return {
167
+ kind: "unknown",
168
+ summary: summaryOf(dto.content, `Unsupported message type: ${dto.msgType}`),
169
+ ...optionalRaw({
170
+ msgType: dto.msgType,
171
+ content: dto.content,
172
+ attachments: dto.attachments,
173
+ additionalKwargs: dto.additionalKwargs,
174
+ }),
175
+ };
176
+ };
177
+ export const mapMessage = (dto) => {
178
+ const reasoning = reasoningText(dto);
179
+ return messageSchema.parse({
180
+ kind: "message",
181
+ id: dto.msgId == null
182
+ ? `message:${asId(dto.conversationId)}:${dto.createTimestamp}`
183
+ : asId(dto.msgId),
184
+ conversationId: asId(dto.conversationId),
185
+ role: mapRole(dto.role),
186
+ content: mapMessageContent(dto),
187
+ createdAt: dto.createTimestamp,
188
+ ...(dto.sequence === undefined ? {} : { sequence: dto.sequence }),
189
+ ...(dto.creator == null
190
+ ? {}
191
+ : {
192
+ author: {
193
+ ...(dto.creator.uid == null ? {} : { id: asId(dto.creator.uid) }),
194
+ ...(dto.creator.name == null
195
+ ? {}
196
+ : { displayName: dto.creator.name }),
197
+ ...(dto.creator.avatar == null
198
+ ? {}
199
+ : { avatarUrl: dto.creator.avatar }),
200
+ },
201
+ }),
202
+ ...(reasoning === undefined ? {} : { reasoning }),
203
+ ...messageRaw(dto),
204
+ });
205
+ };
206
+ function isEmptyRecord(value) {
207
+ return (value !== null &&
208
+ typeof value === "object" &&
209
+ !Array.isArray(value) &&
210
+ Object.keys(value).length === 0);
211
+ }
212
+ const mapEventStatus = (status) => {
213
+ switch (status.toLocaleLowerCase("en-US")) {
214
+ case "aborted":
215
+ case "failed":
216
+ case "running":
217
+ case "success":
218
+ case "timeout":
219
+ return status.toLocaleLowerCase("en-US");
220
+ default:
221
+ return "unknown";
222
+ }
223
+ };
224
+ const parseJsonIfPossible = (value) => {
225
+ if (typeof value !== "string")
226
+ return value;
227
+ try {
228
+ return JSON.parse(value);
229
+ }
230
+ catch {
231
+ return value;
232
+ }
233
+ };
234
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
235
+ const askUserToolNames = new Set([
236
+ "askuser",
237
+ "askuserquestions",
238
+ "askusertool",
239
+ ]);
240
+ const isAskUserToolName = (value) => typeof value === "string" &&
241
+ askUserToolNames.has(value
242
+ .trim()
243
+ .toLocaleLowerCase("en-US")
244
+ .replace(/[^a-z0-9]/g, ""));
245
+ const stringValue = (value) => typeof value === "string" && value.trim().length > 0
246
+ ? sanitizeDiagnosticText(value)
247
+ : typeof value === "number" || typeof value === "boolean"
248
+ ? String(value)
249
+ : undefined;
250
+ const displayStringValue = (value) => {
251
+ const normalized = stringValue(value);
252
+ if (normalized === undefined)
253
+ return undefined;
254
+ return normalized.slice(0, ASK_USER_MAX_TEXT_CHARACTERS);
255
+ };
256
+ const mapAskUserInteractionValue = (value) => {
257
+ if (!Array.isArray(value))
258
+ return displayStringValue(value);
259
+ const normalized = value
260
+ .slice(0, ASK_USER_MAX_ANSWER_VALUES)
261
+ .flatMap((item) => {
262
+ const entry = displayStringValue(item);
263
+ return entry === undefined ? [] : [entry];
264
+ });
265
+ return normalized.length > 0 || value.length === 0 ? normalized : undefined;
266
+ };
267
+ const mapAskUserOptions = (value) => {
268
+ if (!Array.isArray(value))
269
+ return [];
270
+ const options = value.slice(0, ASK_USER_MAX_OPTIONS).flatMap((option) => {
271
+ if (typeof option === "string") {
272
+ const label = displayStringValue(option);
273
+ return label === undefined ? [] : [{ label, value: label }];
274
+ }
275
+ if (!isRecord(option))
276
+ return [];
277
+ const label = displayStringValue(option["label"]);
278
+ if (label === undefined)
279
+ return [];
280
+ const normalizedValue = displayStringValue(option["value"]) ?? label;
281
+ const description = displayStringValue(option["description"]);
282
+ return [
283
+ {
284
+ label,
285
+ value: normalizedValue,
286
+ ...(description === undefined ? {} : { description }),
287
+ },
288
+ ];
289
+ });
290
+ const seenValues = new Set();
291
+ return options.filter(({ value: optionValue }) => {
292
+ if (seenValues.has(optionValue))
293
+ return false;
294
+ seenValues.add(optionValue);
295
+ return true;
296
+ });
297
+ };
298
+ const mapAskUserQuestions = (value) => {
299
+ const record = isRecord(value) ? value : undefined;
300
+ const candidates = Array.isArray(value)
301
+ ? value
302
+ : Array.isArray(record?.["questions"])
303
+ ? record["questions"]
304
+ : record === undefined
305
+ ? []
306
+ : [record];
307
+ return candidates
308
+ .slice(0, ASK_USER_MAX_QUESTIONS)
309
+ .flatMap((candidate, index) => {
310
+ if (!isRecord(candidate))
311
+ return [];
312
+ const prompt = displayStringValue(candidate["question"]) ??
313
+ displayStringValue(candidate["prompt"]) ??
314
+ displayStringValue(candidate["message"]);
315
+ if (prompt === undefined)
316
+ return [];
317
+ const title = displayStringValue(candidate["title"]) ??
318
+ displayStringValue(candidate["header"]);
319
+ const description = displayStringValue(candidate["description"]);
320
+ const placeholder = displayStringValue(candidate["placeholder"]);
321
+ const defaultValue = mapAskUserInteractionValue(candidate["default"]);
322
+ const options = mapAskUserOptions(candidate["options"] ?? candidate["choices"]);
323
+ return [
324
+ {
325
+ id: String(index),
326
+ prompt,
327
+ ...(title === undefined ? {} : { title }),
328
+ ...(description === undefined ? {} : { description }),
329
+ ...(placeholder === undefined ? {} : { placeholder }),
330
+ required: typeof candidate["required"] === "boolean"
331
+ ? candidate["required"]
332
+ : true,
333
+ multiple: candidate["multiSelect"] === true ||
334
+ candidate["multi_select"] === true,
335
+ ...(defaultValue === undefined ? {} : { defaultValue }),
336
+ options,
337
+ },
338
+ ];
339
+ });
340
+ };
341
+ const mapAnswerRecord = (value, questionSource, questions) => {
342
+ if (!isRecord(value))
343
+ return undefined;
344
+ const questionRecord = isRecord(questionSource) ? questionSource : undefined;
345
+ const candidates = Array.isArray(questionSource)
346
+ ? questionSource
347
+ : Array.isArray(questionRecord?.["questions"])
348
+ ? questionRecord["questions"]
349
+ : questionRecord === undefined
350
+ ? []
351
+ : [questionRecord];
352
+ const answers = {};
353
+ for (const question of questions) {
354
+ const index = Number(question.id);
355
+ const candidate = candidates[index];
356
+ const sourceId = isRecord(candidate)
357
+ ? (stringValue(candidate["id"]) ?? question.id)
358
+ : question.id;
359
+ const answer = Object.hasOwn(value, sourceId)
360
+ ? value[sourceId]
361
+ : value[question.id];
362
+ const normalized = mapAskUserInteractionValue(answer);
363
+ if (normalized !== undefined)
364
+ answers[question.id] = normalized;
365
+ }
366
+ return Object.keys(answers).length === 0 ? undefined : answers;
367
+ };
368
+ const mapAskUserResult = (toolName, toolCall, toolReturn) => {
369
+ const originValue = parseJsonIfPossible(toolReturn?.["origin"]);
370
+ const origin = isRecord(originValue) ? originValue : undefined;
371
+ if (origin === undefined ||
372
+ (!isAskUserToolName(toolName) && !isAskUserToolName(origin["type"]))) {
373
+ return undefined;
374
+ }
375
+ const response = isRecord(origin["response"])
376
+ ? origin["response"]
377
+ : undefined;
378
+ const requestId = stringValue(origin["requestId"]) ??
379
+ stringValue(response?.["requestId"]) ??
380
+ stringValue(toolCall?.["toolId"]);
381
+ const functionCall = isRecord(toolCall?.["functionCall"])
382
+ ? toolCall["functionCall"]
383
+ : undefined;
384
+ const parameters = parseJsonIfPossible(functionCall?.["parameters"]);
385
+ const questionSource = origin["questions"] ?? parameters;
386
+ const questions = mapAskUserQuestions(questionSource);
387
+ if (requestId === undefined || questions.length === 0)
388
+ return undefined;
389
+ const rawStatus = stringValue(origin["status"])?.toLocaleLowerCase("en-US");
390
+ const originError = displayStringValue(origin["error"]);
391
+ const responseError = displayStringValue(response?.["error"]);
392
+ const meta = isRecord(toolReturn?.["meta"]) ? toolReturn["meta"] : undefined;
393
+ const status = rawStatus === "failed"
394
+ ? "failed"
395
+ : rawStatus === "timeout" || response?.["timedOut"] === true
396
+ ? "timeout"
397
+ : rawStatus === "cancelled" || response?.["cancelled"] === true
398
+ ? "cancelled"
399
+ : rawStatus === "chat-about-this" ||
400
+ response?.["chatAboutThis"] === true
401
+ ? "chat-about-this"
402
+ : originError !== undefined ||
403
+ responseError !== undefined ||
404
+ origin["success"] === false ||
405
+ response?.["success"] === false ||
406
+ meta?.["success"] === false
407
+ ? "failed"
408
+ : "answered";
409
+ const answers = mapAnswerRecord(response?.["answers"] ?? origin["answers"], questionSource, questions);
410
+ const error = originError ?? responseError;
411
+ const parsed = askUserInteractionResultSchema.safeParse({
412
+ kind: "ask-user",
413
+ requestId,
414
+ status,
415
+ questions,
416
+ ...(answers === undefined ? {} : { answers }),
417
+ ...(error === undefined ? {} : { error }),
418
+ });
419
+ return parsed.success ? parsed.data : undefined;
420
+ };
421
+ const eventIdentity = (dto) => dto.eventId == null
422
+ ? `event:${asId(dto.conversationId)}:${dto.createTimestamp}:${dto.eventScene}`
423
+ : asId(dto.eventId);
424
+ const mapEventError = (dto) => {
425
+ if (dto.exception == null)
426
+ return undefined;
427
+ return chatErrorSchema.parse({
428
+ code: "server",
429
+ message: summaryOf(dto.exception, "TFRobot event failed"),
430
+ retryable: false,
431
+ conversationId: asId(dto.conversationId),
432
+ ...optionalRaw({ exception: dto.exception }),
433
+ });
434
+ };
435
+ const mapToolTransition = (dto, transitionId) => {
436
+ if (!isRecord(dto.content))
437
+ return undefined;
438
+ const toolCallValue = dto.content["toolCall"];
439
+ const toolReturnValue = dto.content["toolReturn"];
440
+ const toolCall = isRecord(toolCallValue) ? toolCallValue : undefined;
441
+ const functionCall = isRecord(toolCall?.["functionCall"])
442
+ ? toolCall["functionCall"]
443
+ : undefined;
444
+ const name = functionCall?.["name"];
445
+ const toolReturn = isRecord(toolReturnValue) ? toolReturnValue : undefined;
446
+ const meta = isRecord(toolReturn?.["meta"]) ? toolReturn["meta"] : undefined;
447
+ const normalizedToolCall = typeof name === "string" && name.length > 0
448
+ ? {
449
+ ...(toolCall?.["toolId"] == null
450
+ ? {}
451
+ : { id: asId(toolCall["toolId"]) }),
452
+ name,
453
+ ...(typeof functionCall?.["description"] === "string"
454
+ ? { description: functionCall["description"] }
455
+ : {}),
456
+ ...(functionCall?.["parameters"] === undefined
457
+ ? {}
458
+ : {
459
+ arguments: safeRaw(parseJsonIfPossible(functionCall["parameters"])),
460
+ }),
461
+ ...(typeof toolCall?.["index"] === "number"
462
+ ? { index: toolCall["index"] }
463
+ : {}),
464
+ }
465
+ : undefined;
466
+ const result = toolReturn?.["origin"] === undefined
467
+ ? undefined
468
+ : safeRaw(toolReturn["origin"]);
469
+ const success = typeof meta?.["success"] === "boolean" ? meta["success"] : undefined;
470
+ const done = typeof meta?.["done"] === "boolean" ? meta["done"] : undefined;
471
+ let normalizedToolReturn;
472
+ if (result !== undefined) {
473
+ normalizedToolReturn = {
474
+ result,
475
+ ...(success === undefined ? {} : { success }),
476
+ ...(done === undefined ? {} : { done }),
477
+ ...optionalRaw(toolReturn),
478
+ };
479
+ }
480
+ else if (success !== undefined) {
481
+ normalizedToolReturn = {
482
+ success,
483
+ ...(done === undefined ? {} : { done }),
484
+ ...optionalRaw(toolReturn),
485
+ };
486
+ }
487
+ else if (done !== undefined) {
488
+ normalizedToolReturn = {
489
+ done,
490
+ ...optionalRaw(toolReturn),
491
+ };
492
+ }
493
+ if (normalizedToolCall === undefined && normalizedToolReturn === undefined) {
494
+ return undefined;
495
+ }
496
+ const interaction = mapAskUserResult(name, toolCall, toolReturn);
497
+ return {
498
+ id: transitionId,
499
+ status: mapEventStatus(dto.status),
500
+ occurredAt: dto.createTimestamp,
501
+ ...(dto.transitionSequence === undefined
502
+ ? {}
503
+ : { sequence: dto.transitionSequence }),
504
+ ...(mapEventError(dto) === undefined ? {} : { error: mapEventError(dto) }),
505
+ ...(normalizedToolCall === undefined
506
+ ? {}
507
+ : { toolCall: normalizedToolCall }),
508
+ ...(normalizedToolReturn === undefined
509
+ ? {}
510
+ : { toolReturn: normalizedToolReturn }),
511
+ ...(interaction === undefined ? {} : { interaction }),
512
+ ...extraRaw(dto, EVENT_KEYS),
513
+ };
514
+ };
515
+ export const mapEvent = (dto) => {
516
+ const id = eventIdentity(dto);
517
+ const transitionId = dto.transitionId === undefined
518
+ ? `${id}:${dto.status}:${dto.createTimestamp}`
519
+ : asId(dto.transitionId);
520
+ const isTool = dto.eventScene.toLocaleLowerCase("en-US") === "tool";
521
+ const toolTransition = isTool
522
+ ? mapToolTransition(dto, transitionId)
523
+ : undefined;
524
+ if (isTool && toolTransition === undefined) {
525
+ return agentEventSchema.parse({
526
+ kind: "agent-event",
527
+ eventCategory: "generic",
528
+ id,
529
+ conversationId: asId(dto.conversationId),
530
+ eventType: dto.eventScene,
531
+ status: mapEventStatus(dto.status),
532
+ createdAt: dto.eventCreateTimestamp ?? dto.createTimestamp,
533
+ ...(dto.sequence === undefined ? {} : { sequence: dto.sequence }),
534
+ transitions: [
535
+ {
536
+ id: transitionId,
537
+ status: mapEventStatus(dto.status),
538
+ occurredAt: dto.createTimestamp,
539
+ ...(dto.transitionSequence === undefined
540
+ ? {}
541
+ : { sequence: dto.transitionSequence }),
542
+ summary: "Tool event payload could not be normalized",
543
+ ...(mapEventError(dto) === undefined
544
+ ? {}
545
+ : { error: mapEventError(dto) }),
546
+ ...extraRaw(dto, EVENT_KEYS),
547
+ },
548
+ ],
549
+ summary: "Tool event payload could not be normalized",
550
+ ...extraRaw(dto, EVENT_KEYS),
551
+ });
552
+ }
553
+ const transition = toolTransition ?? {
554
+ id: transitionId,
555
+ status: mapEventStatus(dto.status),
556
+ occurredAt: dto.createTimestamp,
557
+ ...(dto.transitionSequence === undefined
558
+ ? {}
559
+ : { sequence: dto.transitionSequence }),
560
+ ...(typeof dto.content === "string"
561
+ ? { summary: sanitizeDiagnosticText(dto.content) }
562
+ : {}),
563
+ ...(mapEventError(dto) === undefined
564
+ ? {}
565
+ : { error: mapEventError(dto) }),
566
+ ...extraRaw(dto, EVENT_KEYS),
567
+ };
568
+ return agentEventSchema.parse({
569
+ kind: "agent-event",
570
+ eventCategory: toolTransition === undefined ? "generic" : "tool",
571
+ id,
572
+ conversationId: asId(dto.conversationId),
573
+ eventType: dto.eventScene,
574
+ status: transition.status,
575
+ createdAt: dto.eventCreateTimestamp ?? dto.createTimestamp,
576
+ ...(dto.sequence === undefined ? {} : { sequence: dto.sequence }),
577
+ transitions: [transition],
578
+ ...(transition.summary === undefined
579
+ ? {}
580
+ : { summary: transition.summary }),
581
+ ...extraRaw(dto, EVENT_KEYS),
582
+ });
583
+ };
584
+ const mergeEvents = (events) => {
585
+ const merged = new Map();
586
+ for (const event of events) {
587
+ const current = merged.get(event.id);
588
+ if (current === undefined ||
589
+ current.eventCategory !== event.eventCategory ||
590
+ current.eventType !== event.eventType) {
591
+ merged.set(event.id, event);
592
+ continue;
593
+ }
594
+ const transitions = new Map(current.transitions.map((transition) => [transition.id, transition]));
595
+ for (const transition of event.transitions) {
596
+ transitions.set(transition.id, transition);
597
+ }
598
+ const ordered = [...transitions.values()].sort(compareAgentEventTransitions);
599
+ const latest = ordered.at(-1);
600
+ merged.set(event.id, agentEventSchema.parse({
601
+ ...current,
602
+ status: latest.status,
603
+ transitions: ordered,
604
+ ...(latest.summary === undefined ? {} : { summary: latest.summary }),
605
+ }));
606
+ }
607
+ return [...merged.values()];
608
+ };
609
+ export const mapRun = (conversationId, dto) => {
610
+ if (!dto.working)
611
+ return null;
612
+ const hasTransportTaskId = dto.taskId != null;
613
+ return {
614
+ id: dto.taskId == null ? syntheticRunId(conversationId) : asId(dto.taskId),
615
+ conversationId,
616
+ status: "running",
617
+ canInterrupt: hasTransportTaskId,
618
+ ...(dto.startedAt === undefined ? {} : { startedAt: dto.startedAt }),
619
+ };
620
+ };
621
+ export const mapSnapshot = (conversation, history, status) => {
622
+ const timeline = [
623
+ ...(history.messages ?? []).map(mapMessage),
624
+ ...mergeEvents((history.events ?? []).map(mapEvent)),
625
+ ].sort(compareTimelineItems);
626
+ return chatSnapshotSchema.parse({
627
+ conversation,
628
+ timeline,
629
+ run: mapRun(conversation.id, status),
630
+ capabilities: TFROBOT_CAPABILITIES,
631
+ pageInfo: {
632
+ hasPreviousPage: Boolean(history.cursor),
633
+ ...(history.cursor == null || history.cursor.length === 0
634
+ ? {}
635
+ : { previousCursor: history.cursor }),
636
+ },
637
+ });
638
+ };
639
+ export const mapMessageUpdate = (dto) => chatUpdateSchema.parse({
640
+ kind: "timeline.upsert",
641
+ conversationId: asId(dto.conversationId),
642
+ item: mapMessage(dto),
643
+ });
644
+ export const mapEventUpdate = (dto) => {
645
+ const event = mapEvent(dto);
646
+ return chatUpdateSchema.parse({
647
+ kind: "event.transition.upsert",
648
+ conversationId: event.conversationId,
649
+ event: {
650
+ id: event.id,
651
+ eventCategory: event.eventCategory,
652
+ eventType: event.eventType,
653
+ createdAt: event.createdAt,
654
+ ...(event.sequence === undefined ? {} : { sequence: event.sequence }),
655
+ transition: event.transitions[0],
656
+ },
657
+ });
658
+ };
659
+ export const mapUnknownSocketEvent = (eventName, payload, conversationId, now) => {
660
+ const safeEventName = sanitizeDiagnosticText(eventName);
661
+ const record = isRecord(payload) ? payload : undefined;
662
+ const idValue = record?.["id"] ?? record?.["eventId"];
663
+ const createdAtValue = record?.["createTimestamp"];
664
+ const summaryValue = record?.["summary"];
665
+ const sequenceValue = record?.["sequence"];
666
+ const payloadConversationId = record?.["conversationId"] ?? record?.["conversation_id"];
667
+ const targetConversationId = typeof payloadConversationId === "string" ||
668
+ typeof payloadConversationId === "number"
669
+ ? String(payloadConversationId)
670
+ : conversationId;
671
+ const rawPayload = record === undefined
672
+ ? payload
673
+ : Object.fromEntries(Object.entries(record).filter(([key]) => key !== "id" &&
674
+ key !== "eventId" &&
675
+ key !== "conversationId" &&
676
+ key !== "conversation_id" &&
677
+ key !== "createTimestamp" &&
678
+ key !== "summary" &&
679
+ key !== "sequence"));
680
+ return chatUpdateSchema.parse({
681
+ kind: "timeline.upsert",
682
+ conversationId: targetConversationId,
683
+ item: unknownEventSchema.parse({
684
+ kind: "unknown-event",
685
+ id: typeof idValue === "string" || typeof idValue === "number"
686
+ ? String(idValue)
687
+ : `socket:${targetConversationId}:${safeEventName}:${now}`,
688
+ conversationId: targetConversationId,
689
+ originalType: safeEventName,
690
+ createdAt: typeof createdAtValue === "number" && Number.isFinite(createdAtValue)
691
+ ? createdAtValue
692
+ : now,
693
+ summary: typeof summaryValue === "string"
694
+ ? sanitizeDiagnosticText(summaryValue)
695
+ : `Unsupported realtime event: ${safeEventName}`,
696
+ ...(typeof sequenceValue === "number" &&
697
+ Number.isInteger(sequenceValue) &&
698
+ sequenceValue >= 0
699
+ ? { sequence: sequenceValue }
700
+ : {}),
701
+ ...optionalRaw(rawPayload),
702
+ }),
703
+ });
704
+ };
705
+ //# sourceMappingURL=mapper.js.map