UNPKG

@hashgraphonline/standards-agent-kit

Version:

A modular SDK for building on-chain autonomous agents using Hashgraph Online Standards, including HCS-10 for agent discovery and communication.

195 lines (173 loc) 7.54 kB
import { StructuredTool, ToolParams } from '@langchain/core/tools'; import { z } from 'zod'; import { HCS10Client, HCSMessageWithTimestamp } from '../hcs10/HCS10Client'; import { IStateManager } from '../state/state-types'; import { Logger } from '@hashgraphonline/standards-sdk'; // Assuming logger utility export interface CheckMessagesToolParams extends ToolParams { hcsClient: HCS10Client; stateManager: IStateManager; } /** * A tool to check for new messages on an active HCS-10 connection topic, * or optionally fetch the latest messages regardless of timestamp. */ export class CheckMessagesTool extends StructuredTool { name = 'check_messages'; description = `Checks for and retrieves messages from an active connection. Identify the target agent using their account ID (e.g., 0.0.12345) or the connection number shown in 'list_connections'. By default, it only retrieves messages newer than the last check. Use 'fetchLatest: true' to get the most recent messages regardless of when they arrived. Use 'lastMessagesCount' to specify how many latest messages to retrieve (default 1 when fetchLatest is true).`; schema = z.object({ targetIdentifier: z .string() .describe( "The account ID (e.g., 0.0.12345) of the target agent OR the connection number (e.g., '1', '2') from the 'list_connections' tool to check messages for." ), fetchLatest: z .boolean() .optional() .default(false) .describe( 'Set to true to fetch the latest messages even if they have been seen before, ignoring the last checked timestamp. Defaults to false (fetching only new messages).' ), lastMessagesCount: z .number() .int() .positive() .optional() .describe( 'When fetchLatest is true, specifies how many of the most recent messages to retrieve. Defaults to 1.' ), }); public hcsClient: HCS10Client; private stateManager: IStateManager; private logger: Logger; constructor({ hcsClient, stateManager, ...rest }: CheckMessagesToolParams) { super(rest); this.hcsClient = hcsClient; this.stateManager = stateManager; this.logger = Logger.getInstance({ module: 'CheckMessagesTool' }); } protected async _call({ targetIdentifier, fetchLatest, lastMessagesCount, }: z.infer<this['schema']>): Promise<string> { const connection = this.stateManager.getConnectionByIdentifier(targetIdentifier); if (!connection) { return `Error: Could not find an active connection matching identifier "${targetIdentifier}". Use 'list_connections' to see active connections.`; } const connectionTopicId = connection.connectionTopicId; const targetAgentName = connection.targetAgentName; const lastProcessedTimestamp = this.stateManager.getLastTimestamp(connectionTopicId); this.logger.info( `Checking messages for connection with ${targetAgentName} (${connection.targetAccountId}) on topic ${connectionTopicId} (fetchLatest: ${fetchLatest}, lastCount: ${lastMessagesCount}, since: ${lastProcessedTimestamp})` ); try { // 1. Get messages from the topic const result = await this.hcsClient.getMessages(connectionTopicId); const allMessages = result.messages; if (!allMessages || allMessages.length === 0) { return `No messages found on connection topic ${connectionTopicId}.`; } let messagesToProcess: HCSMessageWithTimestamp[] = []; let latestTimestampNanos = lastProcessedTimestamp; const isFetchingLatest = fetchLatest === true; if (isFetchingLatest) { this.logger.info('Fetching latest messages regardless of timestamp.'); const count = lastMessagesCount ?? 1; messagesToProcess = allMessages.slice(-count); } else { this.logger.info( `Filtering for messages newer than ${lastProcessedTimestamp}` ); messagesToProcess = allMessages.filter((msg) => { const msgTimestampNanos = msg.timestamp * 1_000_000; return msgTimestampNanos > lastProcessedTimestamp; }); if (messagesToProcess.length > 0) { latestTimestampNanos = messagesToProcess.reduce( (maxTs, msg) => Math.max(maxTs, msg.timestamp * 1_000_000), lastProcessedTimestamp ); } } if (messagesToProcess.length === 0) { return isFetchingLatest ? `Could not retrieve the latest message(s). No messages found on topic ${connectionTopicId}.` : `No new messages found for connection with ${targetAgentName} since last check.`; } this.logger.info(`Processing ${messagesToProcess.length} message(s).`); // 3. Process messages (resolve inscriptions, format) let outputString = isFetchingLatest ? `Latest message(s) from ${targetAgentName}: ` : `New messages from ${targetAgentName}: `; for (const msg of messagesToProcess) { let content = msg.data; try { // Check for inscription HRL if (typeof content === 'string' && content.startsWith('hcs://')) { this.logger.debug(`Resolving inscribed message: ${content}`); content = await this.hcsClient.getMessageContent(content); this.logger.debug(`Resolved content length: ${content?.length}`); } // Attempt to parse the content as the HCS-10 structure let displayContent = content; // Default to raw content try { const parsed = JSON.parse(content || '{}'); if ( parsed.p === 'hcs-10' && parsed.op === 'message' && parsed.data ) { // Extract sender and actual data from standard message format const senderOpId = parsed.operator_id || 'unknown_sender'; displayContent = `[${senderOpId}]: ${parsed.data}`; } else { // If not standard format, maybe just show raw stringified version displayContent = content; // Keep raw if parsing worked but not expected format } } catch (parseError) { // Content wasn't JSON, keep raw content displayContent = content; } const messageDate = new Date(msg.timestamp); outputString += `\n[${messageDate.toLocaleString()}] (Seq: ${ msg.sequence_number }) ${displayContent} `; } catch (error) { const errorMsg = `Error processing message (Seq: ${ msg.sequence_number }): ${error instanceof Error ? error.message : String(error)}`; this.logger.error(errorMsg); outputString += `\n[Error processing message Seq: ${msg.sequence_number}]\n`; } } // 4. Update the timestamp in demo state ONLY if fetching NEW messages if (!isFetchingLatest && latestTimestampNanos > lastProcessedTimestamp) { this.logger.debug( `Updating timestamp for topic ${connectionTopicId} to ${latestTimestampNanos}` ); this.stateManager.updateTimestamp( connectionTopicId, latestTimestampNanos ); } return outputString.trim(); } catch (error) { this.logger.error( `Failed to check messages for topic ${connectionTopicId}: ${error}` ); return `Error checking messages for ${targetAgentName}: ${ error instanceof Error ? error.message : String(error) }`; } } }