n8n-nodes-tembory 1.1.4 → 1.1.5
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.
|
@@ -399,6 +399,275 @@ const safeParseToolPayload = (value) => {
|
|
|
399
399
|
return text;
|
|
400
400
|
}
|
|
401
401
|
};
|
|
402
|
+
const parseNestedToolPayload = (value, depth = 0) => {
|
|
403
|
+
if (depth > 8)
|
|
404
|
+
return value;
|
|
405
|
+
if (typeof value === 'string') {
|
|
406
|
+
const text = value.trim();
|
|
407
|
+
if (!text)
|
|
408
|
+
return '';
|
|
409
|
+
if (!/^[\[{]/.test(text))
|
|
410
|
+
return value;
|
|
411
|
+
try {
|
|
412
|
+
return parseNestedToolPayload(JSON.parse(text), depth + 1);
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return value;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (Array.isArray(value))
|
|
419
|
+
return value.map((item) => parseNestedToolPayload(item, depth + 1));
|
|
420
|
+
if (!value || typeof value !== 'object')
|
|
421
|
+
return value;
|
|
422
|
+
const out = {};
|
|
423
|
+
for (const [key, item] of Object.entries(value))
|
|
424
|
+
out[key] = parseNestedToolPayload(item, depth + 1);
|
|
425
|
+
return out;
|
|
426
|
+
};
|
|
427
|
+
const collectObjectsDeep = (value, out = [], seen = new Set()) => {
|
|
428
|
+
if (!value || typeof value !== 'object' || seen.has(value))
|
|
429
|
+
return out;
|
|
430
|
+
seen.add(value);
|
|
431
|
+
if (!Array.isArray(value))
|
|
432
|
+
out.push(value);
|
|
433
|
+
for (const item of Array.isArray(value) ? value : Object.values(value))
|
|
434
|
+
collectObjectsDeep(item, out, seen);
|
|
435
|
+
return out;
|
|
436
|
+
};
|
|
437
|
+
const compactObject = (value) => {
|
|
438
|
+
if (Array.isArray(value)) {
|
|
439
|
+
const arr = value.map(compactObject).filter((item) => item !== undefined);
|
|
440
|
+
return arr.length ? arr : undefined;
|
|
441
|
+
}
|
|
442
|
+
if (!value || typeof value !== 'object')
|
|
443
|
+
return value === '' || value === null || value === undefined ? undefined : value;
|
|
444
|
+
const out = {};
|
|
445
|
+
for (const [key, item] of Object.entries(value)) {
|
|
446
|
+
const cleaned = compactObject(item);
|
|
447
|
+
if (cleaned !== undefined)
|
|
448
|
+
out[key] = cleaned;
|
|
449
|
+
}
|
|
450
|
+
return Object.keys(out).length ? out : undefined;
|
|
451
|
+
};
|
|
452
|
+
const normalizeFieldKey = (value = '') => String(value || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
453
|
+
const hasFieldKey = (key, candidates) => candidates.map(normalizeFieldKey).includes(normalizeFieldKey(key));
|
|
454
|
+
const findFieldDeep = (value, keys) => {
|
|
455
|
+
const objects = collectObjectsDeep(value);
|
|
456
|
+
for (const object of objects) {
|
|
457
|
+
for (const [key, item] of Object.entries(object)) {
|
|
458
|
+
if (hasFieldKey(key, keys) && item !== undefined && item !== null && item !== '')
|
|
459
|
+
return item;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return undefined;
|
|
463
|
+
};
|
|
464
|
+
const readPath = (value, path) => {
|
|
465
|
+
let current = value;
|
|
466
|
+
for (const segment of path) {
|
|
467
|
+
if (!current || typeof current !== 'object')
|
|
468
|
+
return undefined;
|
|
469
|
+
current = current[segment];
|
|
470
|
+
}
|
|
471
|
+
return current;
|
|
472
|
+
};
|
|
473
|
+
const firstPath = (roots, paths) => {
|
|
474
|
+
for (const root of roots) {
|
|
475
|
+
for (const path of paths) {
|
|
476
|
+
const value = readPath(root, path);
|
|
477
|
+
if (value !== undefined && value !== null && value !== '')
|
|
478
|
+
return value;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return undefined;
|
|
482
|
+
};
|
|
483
|
+
const numberFrom = (value) => {
|
|
484
|
+
if (value === undefined || value === null || value === '')
|
|
485
|
+
return undefined;
|
|
486
|
+
const match = String(value).match(/-?\d+(?:[.,]\d+)?/);
|
|
487
|
+
if (!match)
|
|
488
|
+
return undefined;
|
|
489
|
+
const parsed = Number(match[0].replace(',', '.'));
|
|
490
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
491
|
+
};
|
|
492
|
+
const integerFrom = (value) => {
|
|
493
|
+
const parsed = numberFrom(value);
|
|
494
|
+
return parsed === undefined ? undefined : Math.trunc(parsed);
|
|
495
|
+
};
|
|
496
|
+
const normalizeTimeValue = (value) => {
|
|
497
|
+
const text = String(value || '').trim().toLowerCase();
|
|
498
|
+
const match = /^(\d{1,2})(?:(?::|h)(\d{0,2}))?$/.exec(text);
|
|
499
|
+
if (!match)
|
|
500
|
+
return '';
|
|
501
|
+
const hour = Number(match[1]);
|
|
502
|
+
const minute = match[2] === '' || match[2] === undefined ? 0 : Number(match[2]);
|
|
503
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
|
504
|
+
return '';
|
|
505
|
+
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
|
|
506
|
+
};
|
|
507
|
+
const isoDateFromParts = (day, month, year, referenceDates = []) => {
|
|
508
|
+
const dd = String(day || '').padStart(2, '0');
|
|
509
|
+
const mm = String(month || '').padStart(2, '0');
|
|
510
|
+
let yyyy = String(year || '');
|
|
511
|
+
if (!yyyy) {
|
|
512
|
+
const matched = (referenceDates || []).find((date) => String(date || '').slice(5, 10) === `${mm}-${dd}`);
|
|
513
|
+
yyyy = matched ? String(matched).slice(0, 4) : String(new Date().getFullYear());
|
|
514
|
+
}
|
|
515
|
+
if (yyyy.length === 2)
|
|
516
|
+
yyyy = `20${yyyy}`;
|
|
517
|
+
return /^\d{4}$/.test(yyyy) && /^\d{2}$/.test(mm) && /^\d{2}$/.test(dd) ? `${yyyy}-${mm}-${dd}` : '';
|
|
518
|
+
};
|
|
519
|
+
const bookingToolName = (name = '') => String(name || '').toLowerCase();
|
|
520
|
+
const serviceNameFromPageContent = (value) => {
|
|
521
|
+
const content = String(value || '').trim();
|
|
522
|
+
if (!content)
|
|
523
|
+
return '';
|
|
524
|
+
return content.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
|
|
525
|
+
};
|
|
526
|
+
const extractAvailabilitySlots = (parsedResult) => {
|
|
527
|
+
const slots = [];
|
|
528
|
+
const roots = Array.isArray(parsedResult) ? parsedResult : [parsedResult];
|
|
529
|
+
for (const root of roots) {
|
|
530
|
+
if (!root || typeof root !== 'object')
|
|
531
|
+
continue;
|
|
532
|
+
const availability = Array.isArray(root.disponibilidade) ? root.disponibilidade : [];
|
|
533
|
+
for (const item of availability) {
|
|
534
|
+
const date = item.dataDisponivel || item.date || item.data || '';
|
|
535
|
+
const providers = Array.isArray(item.providers) ? item.providers : [];
|
|
536
|
+
for (const provider of providers) {
|
|
537
|
+
const providerId = integerFrom(provider.provider_id ?? provider.providerId ?? provider.id);
|
|
538
|
+
const providerName = provider.provider_name || provider.providerName || provider.name || '';
|
|
539
|
+
const horarios = Array.isArray(provider.horarios) ? provider.horarios : [];
|
|
540
|
+
for (const horario of horarios) {
|
|
541
|
+
const time = normalizeTimeValue(horario);
|
|
542
|
+
if (date && time)
|
|
543
|
+
slots.push(compactObject({ date, time, providerId, providerName }));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return slots.filter(Boolean);
|
|
549
|
+
};
|
|
550
|
+
const extractToolBookingFacts = (toolName = '', input = '', rawResult = '') => {
|
|
551
|
+
const name = bookingToolName(toolName);
|
|
552
|
+
const inputObj = parseNestedToolPayload(safeParseToolPayload(input));
|
|
553
|
+
const parsedResult = parseNestedToolPayload(safeParseToolPayload(rawResult));
|
|
554
|
+
const roots = Array.isArray(parsedResult) ? parsedResult : [parsedResult];
|
|
555
|
+
const objects = collectObjectsDeep(parsedResult);
|
|
556
|
+
const facts = { source_tool: toolName };
|
|
557
|
+
if (/check[_-]?availabilities|disponibilidade/i.test(name)) {
|
|
558
|
+
facts.kind = 'availability';
|
|
559
|
+
facts.locationId = integerFrom(firstPath(roots, [['locationId'], ['location_id']]) ?? findFieldDeep(parsedResult, ['locationId', 'location_id']));
|
|
560
|
+
facts.locationName = inputObj && typeof inputObj === 'object' ? inputObj.location_name || inputObj.locationName : undefined;
|
|
561
|
+
facts.serviceName = firstPath(roots, [['especialidade'], ['serviceName'], ['service_name']]) || (inputObj && typeof inputObj === 'object' ? inputObj.service_name || inputObj.serviceName : undefined);
|
|
562
|
+
facts.serviceId = integerFrom(firstPath(roots, [['services', 'id'], ['service', 'id'], ['serviceId'], ['service_id']]) ?? findFieldDeep(parsedResult, ['serviceId', 'service_id']));
|
|
563
|
+
facts.durationMinutes = integerFrom(firstPath(roots, [['services', 'duration'], ['duration'], ['durationMinutes']]) ?? findFieldDeep(parsedResult, ['duration', 'durationMinutes']));
|
|
564
|
+
facts.price = firstPath(roots, [['services', 'price'], ['price']]) ?? findFieldDeep(parsedResult, ['price', 'price_num']);
|
|
565
|
+
facts.providerId = integerFrom(firstPath(roots, [['provider', 'id'], ['providerId'], ['provider_id']]) ?? findFieldDeep(parsedResult, ['providerId', 'provider_id']));
|
|
566
|
+
facts.availability_slots = extractAvailabilitySlots(parsedResult).slice(0, 80);
|
|
567
|
+
}
|
|
568
|
+
else if (/services?|veterinarios?/i.test(name)) {
|
|
569
|
+
facts.kind = /veterinarios?/i.test(name) ? 'provider_service' : 'service';
|
|
570
|
+
facts.serviceId = integerFrom(firstPath(roots, [['metadata', 'metadata', 'service_id'], ['metadata', 'service_id'], ['service_id'], ['serviceId']]) ?? findFieldDeep(parsedResult, ['service_id', 'serviceId']));
|
|
571
|
+
facts.serviceName = firstPath(roots, [['pageContent']]) || (objects.map((object) => serviceNameFromPageContent(object.pageContent)).find(Boolean));
|
|
572
|
+
facts.serviceName = serviceNameFromPageContent(facts.serviceName || '');
|
|
573
|
+
facts.locationId = integerFrom(firstPath(roots, [['metadata', 'metadata', 'location_id'], ['metadata', 'location_id'], ['locationId'], ['local']]) ?? findFieldDeep(parsedResult, ['location_id', 'locationId', 'local']));
|
|
574
|
+
facts.providerId = integerFrom(firstPath(roots, [['metadata', 'metadata', 'provider_id'], ['metadata', 'provider_id'], ['provider_id'], ['providerId']]) ?? findFieldDeep(parsedResult, ['provider_id', 'providerId']));
|
|
575
|
+
facts.price = firstPath(roots, [['metadata', 'metadata', 'price_num'], ['metadata', 'price_num'], ['price']]) ?? findFieldDeep(parsedResult, ['price_num', 'price']);
|
|
576
|
+
facts.durationMinutes = integerFrom(firstPath(roots, [['metadata', 'metadata', 'duration'], ['metadata', 'duration'], ['duration']]) ?? findFieldDeep(parsedResult, ['duration']));
|
|
577
|
+
}
|
|
578
|
+
else if (/anclivepa.*customer.*upsert|customer.*upsert/i.test(name)) {
|
|
579
|
+
facts.kind = 'booking_customer';
|
|
580
|
+
facts.customerId = integerFrom(firstPath(roots, [['customerId'], ['customer_id']]) ?? findFieldDeep(parsedResult, ['customerId', 'customer_id']));
|
|
581
|
+
facts.petName = firstPath(roots, [['petChosen'], ['petName'], ['pet_name']]) || (inputObj && typeof inputObj === 'object' ? inputObj.petName || inputObj.pet_name : undefined);
|
|
582
|
+
facts.readyForBooking = Boolean(firstPath(roots, [['readyForBooking']]) ?? findFieldDeep(parsedResult, ['readyForBooking']));
|
|
583
|
+
}
|
|
584
|
+
const compacted = compactObject(facts);
|
|
585
|
+
return compacted && Object.keys(compacted).length > 2 ? compacted : undefined;
|
|
586
|
+
};
|
|
587
|
+
const extractBookingSelectionFromMessages = (recentMessages = [], referenceDates = []) => {
|
|
588
|
+
const ordered = [...(recentMessages || [])].reverse();
|
|
589
|
+
for (const msg of ordered) {
|
|
590
|
+
const text = String(msg.content || '');
|
|
591
|
+
const dateTime = /(\d{1,2})[/-](\d{1,2})(?:[/-](\d{2,4}))?[\s\S]{0,160}?(?:às|as|hor[aá]rio)\s*(\d{1,2})(?:(?::|h)(\d{2}))?/i.exec(text);
|
|
592
|
+
if (dateTime) {
|
|
593
|
+
return compactObject({
|
|
594
|
+
date: isoDateFromParts(dateTime[1], dateTime[2], dateTime[3], referenceDates),
|
|
595
|
+
time: normalizeTimeValue(`${dateTime[4]}:${dateTime[5] || '00'}`),
|
|
596
|
+
source: 'recent_message',
|
|
597
|
+
}) || {};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const dateMsg = ordered.find((msg) => /(\d{1,2})[/-](\d{1,2})(?:[/-](\d{2,4}))?/.test(String(msg.content || '')));
|
|
601
|
+
const timeMsg = ordered.find((msg) => /^(?:\s*(?:às|as)\s*)?\d{1,2}(?:(?::|h)\d{0,2})?\s*$/i.test(String(msg.content || '')));
|
|
602
|
+
const dateMatch = dateMsg ? /(\d{1,2})[/-](\d{1,2})(?:[/-](\d{2,4}))?/.exec(String(dateMsg.content || '')) : null;
|
|
603
|
+
return compactObject({
|
|
604
|
+
date: dateMatch ? isoDateFromParts(dateMatch[1], dateMatch[2], dateMatch[3], referenceDates) : undefined,
|
|
605
|
+
time: timeMsg ? normalizeTimeValue(timeMsg.content) : undefined,
|
|
606
|
+
source: dateMatch || timeMsg ? 'recent_message_partial' : undefined,
|
|
607
|
+
}) || {};
|
|
608
|
+
};
|
|
609
|
+
const addMinutesToSlot = (date, time, minutes = 60) => {
|
|
610
|
+
if (!date || !time)
|
|
611
|
+
return '';
|
|
612
|
+
const start = new Date(`${date}T${time}:00Z`);
|
|
613
|
+
if (!Number.isFinite(start.getTime()))
|
|
614
|
+
return '';
|
|
615
|
+
const end = new Date(start.getTime() + (Number(minutes) || 60) * 60000);
|
|
616
|
+
return `${end.toISOString().slice(0, 10)} ${end.toISOString().slice(11, 19)}`;
|
|
617
|
+
};
|
|
618
|
+
const deriveBookingState = (toolHistory = [], recentMessages = []) => {
|
|
619
|
+
const facts = (toolHistory || []).map((tool) => tool.booking_facts || extractToolBookingFacts(tool.name, tool.input, tool.result)).filter(Boolean);
|
|
620
|
+
if (!facts.length)
|
|
621
|
+
return undefined;
|
|
622
|
+
const latest = (predicate) => [...facts].reverse().find(predicate) || {};
|
|
623
|
+
const availability = latest((fact) => Array.isArray(fact.availability_slots) && fact.availability_slots.length);
|
|
624
|
+
const service = latest((fact) => fact.serviceId);
|
|
625
|
+
const customer = latest((fact) => fact.kind === 'booking_customer' && fact.customerId);
|
|
626
|
+
const slots = availability.availability_slots || [];
|
|
627
|
+
const referenceDates = Array.from(new Set(slots.map((slot) => slot.date).filter(Boolean)));
|
|
628
|
+
const selection = extractBookingSelectionFromMessages(recentMessages, referenceDates);
|
|
629
|
+
const selectedSlot = slots.find((slot) => slot.date === selection.date && slot.time === selection.time) || {};
|
|
630
|
+
const uniqueProviderIds = Array.from(new Set(slots.map((slot) => slot.providerId).filter(Boolean)));
|
|
631
|
+
const durationMinutes = availability.durationMinutes || service.durationMinutes || 60;
|
|
632
|
+
const providerId = selectedSlot.providerId || (uniqueProviderIds.length === 1 ? uniqueProviderIds[0] : availability.providerId || service.providerId);
|
|
633
|
+
const serviceId = availability.serviceId || service.serviceId;
|
|
634
|
+
const locationId = availability.locationId || service.locationId;
|
|
635
|
+
const customerId = customer.customerId;
|
|
636
|
+
const start = selection.date && selection.time ? `${selection.date} ${selection.time}:00` : undefined;
|
|
637
|
+
const end = start ? addMinutesToSlot(selection.date, selection.time, durationMinutes) : undefined;
|
|
638
|
+
const bookingCandidate = compactObject({
|
|
639
|
+
start,
|
|
640
|
+
end,
|
|
641
|
+
date: selection.date,
|
|
642
|
+
time: selection.time,
|
|
643
|
+
providerId,
|
|
644
|
+
providerName: selectedSlot.providerName,
|
|
645
|
+
serviceId,
|
|
646
|
+
serviceName: availability.serviceName || service.serviceName,
|
|
647
|
+
locationId,
|
|
648
|
+
locationName: availability.locationName,
|
|
649
|
+
customerId,
|
|
650
|
+
petName: customer.petName,
|
|
651
|
+
durationMinutes,
|
|
652
|
+
price: availability.price || service.price,
|
|
653
|
+
source: 'derived_from_tool_history',
|
|
654
|
+
}) || {};
|
|
655
|
+
const required = ['providerId', 'serviceId', 'locationId', 'customerId', 'date', 'time'];
|
|
656
|
+
const missing = required.filter((key) => bookingCandidate[key] === undefined || bookingCandidate[key] === null || bookingCandidate[key] === '');
|
|
657
|
+
const warnings = [];
|
|
658
|
+
if (bookingCandidate.providerId && bookingCandidate.locationId && String(bookingCandidate.providerId) === String(bookingCandidate.locationId))
|
|
659
|
+
warnings.push('providerId_equals_locationId');
|
|
660
|
+
if (bookingCandidate.serviceId && bookingCandidate.locationId && String(bookingCandidate.serviceId) === String(bookingCandidate.locationId))
|
|
661
|
+
warnings.push('serviceId_equals_locationId');
|
|
662
|
+
return compactObject({
|
|
663
|
+
booking_candidate: bookingCandidate,
|
|
664
|
+
selected_slot: selectedSlot,
|
|
665
|
+
missing,
|
|
666
|
+
warnings,
|
|
667
|
+
available_slot_count: slots.length || undefined,
|
|
668
|
+
source_tools: Array.from(new Set(facts.map((fact) => fact.source_tool).filter(Boolean))),
|
|
669
|
+
});
|
|
670
|
+
};
|
|
402
671
|
const compactToolPayload = (value) => truncate(typeof value === 'string' ? value : safeStringify(value), 900);
|
|
403
672
|
const maybeToolResult = (tool, includeResults = true) => includeResults === false ? undefined : compactToolPayload(safeParseToolPayload(tool === null || tool === void 0 ? void 0 : tool.result));
|
|
404
673
|
const isToolName = (value = '') => /^[A-Za-z_][A-Za-z0-9_.:-]{1,127}$/.test(String(value || ''));
|
|
@@ -438,6 +707,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
438
707
|
at: tool.at || null,
|
|
439
708
|
input: compactInput,
|
|
440
709
|
result: compactResult,
|
|
710
|
+
booking_facts: tool.booking_facts,
|
|
441
711
|
reason,
|
|
442
712
|
source: tool.source || 'unknown',
|
|
443
713
|
};
|
|
@@ -449,6 +719,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
449
719
|
reason,
|
|
450
720
|
input: compactInput,
|
|
451
721
|
output: compactResult,
|
|
722
|
+
booking_facts: tool.booking_facts,
|
|
452
723
|
}));
|
|
453
724
|
if (tool.ok === false)
|
|
454
725
|
failedByName[name] = (failedByName[name] || 0) + 1;
|
|
@@ -458,9 +729,11 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
458
729
|
const guidance = [
|
|
459
730
|
'Use tool_state and action_ledger as the source of truth for prior tool calls, inputs, outputs, failures and decisions. Domain-specific tool policy must come from the agent prompt, not from memory.',
|
|
460
731
|
];
|
|
732
|
+
const bookingState = deriveBookingState(tools, recentMessages);
|
|
461
733
|
return {
|
|
462
734
|
profile_complete: Boolean(profileFacts && profileFacts.name && profileFacts.company && profileFacts.email && profileFacts.phone),
|
|
463
735
|
last_tool: lastTool ? { name: lastTool.name, ok: lastTool.ok, at: lastTool.at } : null,
|
|
736
|
+
booking_state: bookingState,
|
|
464
737
|
tool_counts: {
|
|
465
738
|
total: tools.length,
|
|
466
739
|
ok: successfulTools.length,
|
|
@@ -480,6 +753,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
480
753
|
name: successfulTools[successfulTools.length - 1].name,
|
|
481
754
|
at: successfulTools[successfulTools.length - 1].at || null,
|
|
482
755
|
result: maybeToolResult(successfulTools[successfulTools.length - 1], includeResults),
|
|
756
|
+
booking_facts: successfulTools[successfulTools.length - 1].booking_facts,
|
|
483
757
|
} : null,
|
|
484
758
|
},
|
|
485
759
|
blocked_without_context: Array.from(new Set(blockedWithoutContext)),
|
|
@@ -498,6 +772,7 @@ const deriveActionLedger = (toolHistory = [], maxItems = 20, includeResults = tr
|
|
|
498
772
|
reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260),
|
|
499
773
|
input: compactToolPayload(safeParseToolPayload(tool.input)),
|
|
500
774
|
result: maybeToolResult(tool, includeResults),
|
|
775
|
+
booking_facts: tool.booking_facts,
|
|
501
776
|
at: tool.at || null,
|
|
502
777
|
source: tool.source || 'unknown',
|
|
503
778
|
})), maxItems || 20);
|
|
@@ -602,6 +877,7 @@ const encodeToolCall = (tool, threadId) => `${TOOL_HISTORY_MARKER}${safeStringif
|
|
|
602
877
|
input: tool.input || '',
|
|
603
878
|
ok: tool.ok !== false,
|
|
604
879
|
result: tool.result || '',
|
|
880
|
+
booking_facts: tool.booking_facts,
|
|
605
881
|
at: tool.at || nowIso(),
|
|
606
882
|
source: tool.source || 'n8n',
|
|
607
883
|
thread_id: threadId,
|
|
@@ -621,6 +897,7 @@ const encodeToolLedger = (tools = [], threadId) => {
|
|
|
621
897
|
input: tool.input || '',
|
|
622
898
|
ok: tool.ok !== false,
|
|
623
899
|
result: tool.result || '',
|
|
900
|
+
booking_facts: tool.booking_facts,
|
|
624
901
|
at: tool.at || nowIso(),
|
|
625
902
|
source: tool.source || 'n8n',
|
|
626
903
|
})),
|
|
@@ -647,6 +924,7 @@ const encodeTurnArchive = ({ threadId, messages = [], tools = [], workingMemory
|
|
|
647
924
|
input: tool.input || '',
|
|
648
925
|
ok: tool.ok !== false,
|
|
649
926
|
output: tool.result || '',
|
|
927
|
+
booking_facts: tool.booking_facts,
|
|
650
928
|
at: tool.at || nowIso(),
|
|
651
929
|
source: tool.source || 'n8n',
|
|
652
930
|
})),
|
|
@@ -688,6 +966,7 @@ const parseToolHistoryMarker = (text) => {
|
|
|
688
966
|
input: parsed.input === undefined ? '' : String(parsed.input),
|
|
689
967
|
ok: parsed.ok !== false,
|
|
690
968
|
result: truncate(parsed.result || '', 1000),
|
|
969
|
+
booking_facts: parsed.booking_facts || parsed.bookingFacts,
|
|
691
970
|
at: parsed.at || nowIso(),
|
|
692
971
|
source: parsed.source || 'tembory_marker',
|
|
693
972
|
};
|
|
@@ -715,6 +994,7 @@ const parseToolLedgerMarker = (text) => {
|
|
|
715
994
|
input: tool.input === undefined ? '' : String(tool.input),
|
|
716
995
|
ok: tool.ok !== false,
|
|
717
996
|
result: truncate(tool.result || '', 1000),
|
|
997
|
+
booking_facts: tool.booking_facts || tool.bookingFacts,
|
|
718
998
|
at: tool.at || parsed.generated_at || nowIso(),
|
|
719
999
|
source: tool.source || 'tool_ledger_marker',
|
|
720
1000
|
}));
|
|
@@ -818,6 +1098,7 @@ const toolHistoryFromMemory = (item) => {
|
|
|
818
1098
|
input: truncate((factMatch[2] || '').trim(), 1000),
|
|
819
1099
|
ok: true,
|
|
820
1100
|
result: truncate((factMatch[3] || '').trim(), 1000),
|
|
1101
|
+
booking_facts: meta.booking_facts || meta.bookingFacts,
|
|
821
1102
|
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
822
1103
|
source: 'semantic_fact',
|
|
823
1104
|
};
|
|
@@ -847,6 +1128,7 @@ const toolHistoryFromMemory = (item) => {
|
|
|
847
1128
|
input: meta.input === undefined ? '' : String(meta.input),
|
|
848
1129
|
ok: meta.ok !== false,
|
|
849
1130
|
result: truncate(meta.result || content || '', 1000),
|
|
1131
|
+
booking_facts: meta.booking_facts || meta.bookingFacts,
|
|
850
1132
|
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
851
1133
|
source: meta.source || 'metadata',
|
|
852
1134
|
};
|
|
@@ -866,6 +1148,7 @@ const explicitToolHistoryItemsFromMemory = (item) => {
|
|
|
866
1148
|
input: tool.input === undefined ? '' : String(tool.input),
|
|
867
1149
|
ok: tool.ok !== false,
|
|
868
1150
|
result: truncate(tool.output || tool.result || '', 1000),
|
|
1151
|
+
booking_facts: tool.booking_facts || tool.bookingFacts,
|
|
869
1152
|
at: tool.at || archive.generated_at || nowIso(),
|
|
870
1153
|
source: tool.source || 'turn_archive',
|
|
871
1154
|
}));
|
|
@@ -930,7 +1213,9 @@ const canonicalToolInput = (value) => {
|
|
|
930
1213
|
const normalizeToolCall = (tool, sequence = 0, defaults = {}) => {
|
|
931
1214
|
const at = tool.at || defaults.at || nowIso();
|
|
932
1215
|
const input = canonicalToolInput(tool.input);
|
|
933
|
-
const
|
|
1216
|
+
const rawResult = tool.result || tool.output || tool.observation || '';
|
|
1217
|
+
const result = summarizeToolResult(rawResult);
|
|
1218
|
+
const bookingFacts = tool.booking_facts || tool.bookingFacts || extractToolBookingFacts(tool.name || tool.tool || tool.toolName || '', input, rawResult);
|
|
934
1219
|
const normalized = {
|
|
935
1220
|
id: tool.id || tool.callId || tool.call_id || '',
|
|
936
1221
|
call_id: tool.id || tool.callId || tool.call_id || '',
|
|
@@ -945,11 +1230,14 @@ const normalizeToolCall = (tool, sequence = 0, defaults = {}) => {
|
|
|
945
1230
|
ok: tool.ok !== false,
|
|
946
1231
|
result,
|
|
947
1232
|
result_summary: result,
|
|
1233
|
+
booking_facts: bookingFacts,
|
|
948
1234
|
at,
|
|
949
1235
|
timestamp: at,
|
|
950
1236
|
source: tool.source || defaults.source || 'n8n',
|
|
951
1237
|
status: tool.ok === false ? 'failed' : 'ok',
|
|
952
1238
|
};
|
|
1239
|
+
if (!normalized.booking_facts)
|
|
1240
|
+
delete normalized.booking_facts;
|
|
953
1241
|
if (!normalized.id)
|
|
954
1242
|
normalized.id = makeToolEventId(normalized, normalized.sequence);
|
|
955
1243
|
if (!normalized.call_id)
|
|
@@ -1026,8 +1314,12 @@ const extractToolCallsFromMessages = (messages = []) => {
|
|
|
1026
1314
|
const name = message.name || message.toolName || message.tool || additional.name || additional.tool_name || additional.toolName || kwargs.name || 'tool';
|
|
1027
1315
|
const existing = id ? toolCallById.get(String(id)) : null;
|
|
1028
1316
|
if (existing) {
|
|
1029
|
-
|
|
1317
|
+
const rawContent = messageContentOf(message);
|
|
1318
|
+
existing.result = summarizeToolResult(rawContent);
|
|
1030
1319
|
existing.result_summary = existing.result;
|
|
1320
|
+
existing.booking_facts = extractToolBookingFacts(existing.name, existing.input, rawContent) || existing.booking_facts;
|
|
1321
|
+
if (!existing.booking_facts)
|
|
1322
|
+
delete existing.booking_facts;
|
|
1031
1323
|
existing.ok = true;
|
|
1032
1324
|
existing.status = 'ok';
|
|
1033
1325
|
existing.result_hash = stableHash(existing.result || '');
|
|
@@ -1956,7 +2248,7 @@ const contextMemoryText = (memory, max = 700) => {
|
|
|
1956
2248
|
};
|
|
1957
2249
|
const approxTokenCount = (text) => Math.ceil(String(text || '').length / 4);
|
|
1958
2250
|
const noisyToolFields = new Set(['requested_from', 'thread', 'thread_id', 'run_id', 'dedupe_key', 'args_hash', 'result_hash', 'source']);
|
|
1959
|
-
const importantJsonFields = ['id', 'status', 'state', 'next_step', 'action', 'intent', 'tool', 'input', 'output', 'result', 'error', 'message', 'reason', 'customer_id', 'user_id', 'lead_id', 'ticket_id', 'charge_id', 'reservation_id', 'confirmation_id', 'selected_from_message', 'available_slots', 'timezone', 'name', 'phone', 'segmento', 'platform', 'plataforma', 'lifecycle_stage', 'provider', 'sku', 'product', 'stock', 'queue', 'priority'];
|
|
2251
|
+
const importantJsonFields = ['id', 'status', 'state', 'next_step', 'action', 'intent', 'tool', 'input', 'output', 'result', 'error', 'message', 'reason', 'customer_id', 'customerId', 'user_id', 'lead_id', 'ticket_id', 'charge_id', 'reservation_id', 'confirmation_id', 'selected_from_message', 'available_slots', 'availability_slots', 'disponibilidade', 'dataDisponivel', 'horarios', 'timezone', 'name', 'phone', 'segmento', 'platform', 'plataforma', 'lifecycle_stage', 'provider', 'provider_id', 'providerId', 'provider_name', 'services', 'service_id', 'serviceId', 'locationId', 'location_id', 'local', 'especialidade', 'duration', 'price', 'price_num', 'sku', 'product', 'stock', 'queue', 'priority'];
|
|
1960
2252
|
const stripNoisyToolFields = (value, depth = 0) => {
|
|
1961
2253
|
if (depth > 8)
|
|
1962
2254
|
return undefined;
|
|
@@ -2028,6 +2320,7 @@ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResul
|
|
|
2028
2320
|
reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 140),
|
|
2029
2321
|
input: truncate(String(tool.input || ''), 140) || undefined,
|
|
2030
2322
|
result: includeResults ? compactToolResult(tool.result, 240) : undefined,
|
|
2323
|
+
booking_facts: tool.booking_facts,
|
|
2031
2324
|
}));
|
|
2032
2325
|
const cleanContextValue = (value) => {
|
|
2033
2326
|
if (Array.isArray(value)) {
|
|
@@ -2084,6 +2377,7 @@ const compactOperationalStateForAgent = (state = {}) => {
|
|
|
2084
2377
|
const toolState = state.tool_state || {};
|
|
2085
2378
|
return {
|
|
2086
2379
|
last_tool: state.last_tool || null,
|
|
2380
|
+
booking_state: state.booking_state,
|
|
2087
2381
|
tool_counts: {
|
|
2088
2382
|
total: counts.total || 0,
|
|
2089
2383
|
ok: counts.ok || 0,
|
|
@@ -2112,6 +2406,7 @@ const compactActionLedgerForAgent = (ledger = [], maxItems = 6, includeResults =
|
|
|
2112
2406
|
reason: item.reason,
|
|
2113
2407
|
input: item.input,
|
|
2114
2408
|
result: includeResults ? compactToolResult(item.result, 180) : undefined,
|
|
2409
|
+
booking_facts: item.booking_facts,
|
|
2115
2410
|
}));
|
|
2116
2411
|
const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLimit(timeline || [], maxItems).map((item) => cleanContextValue({
|
|
2117
2412
|
entity: item.entity || item.source || item.name,
|
|
@@ -2420,6 +2715,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
2420
2715
|
const vectorFacts = sectionValue('summary');
|
|
2421
2716
|
const slmSummary = sectionValue('connected_model_summary') || sectionValue('active_summary');
|
|
2422
2717
|
const directive = sectionValue('action_directive');
|
|
2718
|
+
const bookingState = (operationalState || {}).booking_state;
|
|
2423
2719
|
const inferredIntent = deriveUserIntentObservation({ query, workingMemory, decisionState, recentMessages });
|
|
2424
2720
|
const minimalState = cleanContextValue({
|
|
2425
2721
|
next_expected_action: directive ? undefined : workingMemory.next_expected_action,
|
|
@@ -2445,6 +2741,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
2445
2741
|
slm: hasToolLedger ? undefined : slmSummary,
|
|
2446
2742
|
},
|
|
2447
2743
|
state: minimalState,
|
|
2744
|
+
booking: bookingState,
|
|
2448
2745
|
profile: sectionValue('profile_facts'),
|
|
2449
2746
|
tools: compactToolLedger,
|
|
2450
2747
|
});
|
|
@@ -3170,6 +3467,7 @@ class TemboryMemory {
|
|
|
3170
3467
|
input: tool.input,
|
|
3171
3468
|
ok: tool.ok,
|
|
3172
3469
|
result: tool.result,
|
|
3470
|
+
booking_facts: tool.booking_facts,
|
|
3173
3471
|
at: tool.at,
|
|
3174
3472
|
source: tool.source || 'tembory_transcript',
|
|
3175
3473
|
thread_id: threadId,
|
|
@@ -3897,6 +4195,7 @@ class TemboryMemory {
|
|
|
3897
4195
|
dedupe_key: tool.dedupe_key || toolEventKey(tool),
|
|
3898
4196
|
result: adv.includeToolResults === false ? undefined : tool.result,
|
|
3899
4197
|
result_summary: adv.includeToolResults === false ? undefined : (tool.result_summary || tool.result),
|
|
4198
|
+
booking_facts: tool.booking_facts,
|
|
3900
4199
|
})),
|
|
3901
4200
|
},
|
|
3902
4201
|
diagnostics,
|
|
@@ -4060,6 +4359,8 @@ exports.__private = {
|
|
|
4060
4359
|
embedQueryCached,
|
|
4061
4360
|
compactToolResult,
|
|
4062
4361
|
compactToolHistoryForAgent,
|
|
4362
|
+
extractToolBookingFacts,
|
|
4363
|
+
deriveBookingState,
|
|
4063
4364
|
compactVectorMemoriesForAgent,
|
|
4064
4365
|
isConversationEchoMemory,
|
|
4065
4366
|
compactOperationalStateForAgent,
|
package/package.json
CHANGED