@hashgraphonline/conversational-agent 0.2.101 → 0.2.103

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.
@@ -621,46 +621,56 @@ export class LangChainAgent extends BaseAgent {
621
621
  );
622
622
  }
623
623
 
624
- const parsedSteps = result?.intermediateSteps?.[0]?.observation;
625
- if (
626
- parsedSteps &&
627
- typeof parsedSteps === 'string' &&
628
- this.isJSON(parsedSteps)
629
- ) {
624
+ const steps = (result?.intermediateSteps as IntermediateStep[]) || [];
625
+ const lastJsonObservation = [...steps]
626
+ .reverse()
627
+ .find(
628
+ (s) => typeof s?.observation === 'string' && this.isJSON(s.observation as string)
629
+ )?.observation as string | undefined;
630
+
631
+ if (lastJsonObservation) {
630
632
  try {
631
- const parsed = JSON.parse(parsedSteps);
633
+ const parsed = JSON.parse(lastJsonObservation);
632
634
 
633
635
  if (ResponseFormatter.isInscriptionResponse(parsed)) {
634
- const formattedMessage =
635
- ResponseFormatter.formatInscriptionResponse(parsed);
636
+ const formattedMessage = ResponseFormatter.formatInscriptionResponse(parsed);
636
637
  response.output = formattedMessage;
637
638
  response.message = formattedMessage;
638
-
639
639
  if (parsed.inscription) {
640
640
  response.inscription = parsed.inscription;
641
641
  }
642
642
  if (parsed.metadata) {
643
+ response.metadata = { ...response.metadata, ...parsed.metadata };
644
+ }
645
+ } else {
646
+ if (typeof parsed.message === 'string' && parsed.message.trim().length > 0) {
647
+ response.message = parsed.message;
648
+ response.output = parsed.message;
649
+ }
650
+ if (parsed.success === true) {
651
+ delete (response as { error?: string }).error;
652
+ }
653
+ if (typeof parsed.transactionBytes === 'string') {
643
654
  response.metadata = {
644
655
  ...response.metadata,
645
- ...parsed.metadata,
656
+ transactionBytes: parsed.transactionBytes as string,
646
657
  };
647
658
  }
648
- } else {
649
- response = { ...response, ...parsed };
659
+ if (typeof parsed.scheduleId === 'string') {
660
+ (response as { scheduleId?: string }).scheduleId = parsed.scheduleId as string;
661
+ }
650
662
  }
651
663
 
652
664
  const blockMetadata = this.processHashLinkBlocks(parsed);
653
665
  if (blockMetadata.hashLinkBlock) {
654
- response.metadata = {
655
- ...response.metadata,
656
- ...blockMetadata,
657
- };
666
+ response.metadata = { ...response.metadata, ...blockMetadata };
658
667
  }
659
668
  } catch (error) {
660
669
  this.logger.error('Error parsing intermediate steps:', error);
661
670
  }
662
671
  }
663
672
 
673
+
664
674
  if (!response.output || response.output.trim() === '') {
665
675
  response.output = 'Agent action complete.';
666
676
  }
@@ -19,6 +19,8 @@ export interface EntityAssociation {
19
19
  createdAt: Date;
20
20
  /** Transaction ID that created this entity */
21
21
  transactionId?: string;
22
+ /** Optional session identifier to scope associations */
23
+ sessionId?: string;
22
24
  }
23
25
 
24
26
  /**
@@ -381,7 +383,8 @@ export class SmartMemoryManager {
381
383
  entityId: string,
382
384
  entityName: string,
383
385
  entityType: string,
384
- transactionId?: string
386
+ transactionId?: string,
387
+ sessionId?: string
385
388
  ): void {
386
389
  try {
387
390
  if (
@@ -410,7 +413,7 @@ export class SmartMemoryManager {
410
413
 
411
414
  const sanitizedEntityId = entityId.trim();
412
415
  const sanitizedEntityName = entityName.trim().substring(0, 100);
413
- const sanitizedEntityType = entityType.trim().toLowerCase();
416
+ const sanitizedEntityType = this.normalizeEntityType(entityType);
414
417
 
415
418
  let usageHint = '';
416
419
  if (sanitizedEntityType === 'tokenid') {
@@ -433,7 +436,7 @@ export class SmartMemoryManager {
433
436
  createdAt: new Date(),
434
437
  isEntityAssociation: true,
435
438
  ...(usageHint ? { usage: usageHint } : {}),
436
- ...(sanitizedEntityType === 'topicid'
439
+ ...(sanitizedEntityType === 'topicId'
437
440
  ? { hrl: `hcs://1/${sanitizedEntityId}` }
438
441
  : {}),
439
442
  ...(transactionId !== undefined &&
@@ -441,6 +444,7 @@ export class SmartMemoryManager {
441
444
  transactionId.trim() !== ''
442
445
  ? { transactionId: transactionId.trim() }
443
446
  : {}),
447
+ ...(sessionId && sessionId.trim() !== '' ? { sessionId: sessionId.trim() } : {}),
444
448
  };
445
449
 
446
450
  const content = JSON.stringify(association);
@@ -462,6 +466,7 @@ export class SmartMemoryManager {
462
466
  entityName: sanitizedEntityName,
463
467
  entityType: sanitizedEntityType,
464
468
  isEntityAssociation: true,
469
+ ...(sessionId && sessionId.trim() !== '' ? { sessionId: sessionId.trim() } : {}),
465
470
  },
466
471
  };
467
472
 
@@ -480,6 +485,43 @@ export class SmartMemoryManager {
480
485
  }
481
486
  }
482
487
 
488
+ /**
489
+ * Normalize various type aliases to canonical EntityFormat strings using a registry.
490
+ */
491
+ private normalizeEntityType(input: string): string {
492
+ const raw = (input || '').trim();
493
+ if (raw.length === 0) {
494
+ return '';
495
+ }
496
+
497
+ const key = raw.replace(/[^a-z]/gi, '').toLowerCase();
498
+
499
+ const REGISTRY: Record<string, string> = {
500
+ topic: 'topicId',
501
+ topicid: 'topicId',
502
+ token: 'tokenId',
503
+ tokenid: 'tokenId',
504
+ account: 'accountId',
505
+ accountid: 'accountId',
506
+ contract: 'contractId',
507
+ contractid: 'contractId',
508
+ file: 'fileId',
509
+ fileid: 'fileId',
510
+ schedule: 'scheduleId',
511
+ scheduleid: 'scheduleId',
512
+ };
513
+
514
+ if (Object.prototype.hasOwnProperty.call(REGISTRY, key)) {
515
+ return REGISTRY[key];
516
+ }
517
+
518
+ if (/^[a-z]+Id$/.test(raw)) {
519
+ return raw;
520
+ }
521
+
522
+ return raw;
523
+ }
524
+
483
525
  /**
484
526
  * Resolve entity references from natural language queries
485
527
  * @param query - Search query (entity name or natural language reference)
@@ -611,19 +653,15 @@ export class SmartMemoryManager {
611
653
  */
612
654
  getEntityAssociations(entityType?: string): EntityAssociation[] {
613
655
  try {
614
- const sanitizedEntityType = entityType
615
- ? entityType.trim().toLowerCase()
616
- : undefined;
656
+ const rawFilter = entityType ? entityType.trim() : undefined;
657
+ const filterCanonical = rawFilter ? this.normalizeEntityType(rawFilter) : undefined;
617
658
 
618
- if (
619
- entityType &&
620
- (!sanitizedEntityType || sanitizedEntityType.length === 0)
621
- ) {
659
+ if (entityType && (!rawFilter || rawFilter.length === 0)) {
622
660
  return [];
623
661
  }
624
662
 
625
663
  const SEARCH_ANY_ENTITY = 'entityId';
626
- const searchQuery = sanitizedEntityType || SEARCH_ANY_ENTITY;
664
+ const searchQuery = filterCanonical || SEARCH_ANY_ENTITY;
627
665
  const searchResults = this._contentStorage.searchMessages(searchQuery, {
628
666
  caseSensitive: false,
629
667
  limit: 100,
@@ -638,10 +676,7 @@ export class SmartMemoryManager {
638
676
  const parsed = JSON.parse(content);
639
677
 
640
678
  if (parsed.entityId && parsed.entityName && parsed.entityType) {
641
- if (
642
- sanitizedEntityType &&
643
- parsed.entityType !== sanitizedEntityType
644
- ) {
679
+ if (filterCanonical && parsed.entityType !== filterCanonical) {
645
680
  continue;
646
681
  }
647
682
 
@@ -667,18 +702,36 @@ export class SmartMemoryManager {
667
702
  }
668
703
  }
669
704
 
670
- const results = associations
671
- .filter(
672
- (assoc, index, arr) =>
673
- arr.findIndex((a) => a.entityId === assoc.entityId) === index
674
- )
675
- .sort((a, b): number => {
676
- const getTime = (d: Date | string): number =>
677
- d instanceof Date ? d.getTime() : new Date(d).getTime();
678
- const aTime = getTime(a.createdAt);
679
- const bTime = getTime(b.createdAt);
680
- return bTime - aTime;
681
- });
705
+ // Merge duplicates by entityId, preferring the newest and one that carries transactionId
706
+ const mergedById = new Map<string, EntityAssociation>();
707
+ const getTime = (d: Date | string): number =>
708
+ d instanceof Date ? d.getTime() : new Date(d).getTime();
709
+
710
+ for (const assoc of associations) {
711
+ const existing = mergedById.get(assoc.entityId);
712
+ if (!existing) {
713
+ mergedById.set(assoc.entityId, assoc);
714
+ continue;
715
+ }
716
+
717
+ const existingTime = getTime(existing.createdAt);
718
+ const currentTime = getTime(assoc.createdAt);
719
+
720
+ const preferCurrent =
721
+ currentTime > existingTime ||
722
+ (!!assoc.transactionId && !existing.transactionId);
723
+
724
+ if (preferCurrent) {
725
+ mergedById.set(assoc.entityId, {
726
+ ...existing,
727
+ ...assoc,
728
+ });
729
+ }
730
+ }
731
+
732
+ const results = Array.from(mergedById.values()).sort((a, b) =>
733
+ getTime(b.createdAt) - getTime(a.createdAt)
734
+ );
682
735
 
683
736
  return results;
684
737
  } catch (error) {
@@ -69,9 +69,9 @@ async function createTestAgent(): Promise<ConversationalAgent> {
69
69
  network: (process.env.HEDERA_NETWORK as 'testnet' | 'mainnet') || 'testnet',
70
70
  openAIApiKey: process.env.OPENAI_API_KEY!,
71
71
  openAIModelName: 'gpt-4o-mini',
72
- verbose: true, // Enable verbose for debugging
73
- disableLogging: false, // Enable logging for debugging
74
- entityMemoryEnabled: false, // Disable for testing
72
+ verbose: true,
73
+ disableLogging: false,
74
+ entityMemoryEnabled: false,
75
75
  };
76
76
 
77
77
  const agent = new ConversationalAgent(options);
@@ -64,9 +64,9 @@ async function createTestAgent(): Promise<ConversationalAgent> {
64
64
  network: (process.env.HEDERA_NETWORK as 'testnet' | 'mainnet') || 'testnet',
65
65
  openAIApiKey: process.env.OPENAI_API_KEY!,
66
66
  openAIModelName: 'gpt-4o-mini',
67
- verbose: false, // Reduce verbosity
68
- disableLogging: true, // Disable logs
69
- entityMemoryEnabled: false, // Disable for testing
67
+ verbose: false,
68
+ disableLogging: true,
69
+ entityMemoryEnabled: false,
70
70
  };
71
71
 
72
72
  const agent = new ConversationalAgent(options);