UNPKG

@hashgraphonline/conversational-agent

Version:

Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera

1 lines 266 kB
{"version":3,"file":"index.cjs","sources":["../../src/plugins/hcs-10/HCS10Plugin.ts","../../src/plugins/hcs-2/HCS2Plugin.ts","../../src/plugins/inscribe/InscribePlugin.ts","../../src/plugins/hbar/AccountBuilder.ts","../../src/plugins/hbar/TransferHbarTool.ts","../../src/plugins/hbar/AirdropToolWrapper.ts","../../src/plugins/hbar/HbarPlugin.ts","../../src/langchain/ContentAwareAgentExecutor.ts","../../src/base-agent.ts","../../src/mcp/ContentProcessor.ts","../../src/mcp/MCPClientManager.ts","../../src/mcp/adapters/langchain.ts","../../src/memory/TokenCounter.ts","../../src/memory/MemoryWindow.ts","../../src/memory/ReferenceIdGenerator.ts","../../src/types/content-reference.ts","../../src/memory/ContentStorage.ts","../../src/memory/SmartMemoryManager.ts","../../src/langchain-agent.ts","../../src/agent-factory.ts","../../src/providers.ts","../../src/config/system-message.ts","../../src/services/ContentStoreManager.ts","../../src/tools/EntityResolverTool.ts","../../src/conversational-agent.ts","../../src/mcp/helpers.ts"],"sourcesContent":["import {\n GenericPluginContext,\n HederaTool,\n BasePlugin,\n HederaAgentKit,\n} from 'hedera-agent-kit';\nimport {\n IStateManager,\n OpenConvaiState,\n HCS10Builder,\n RegisterAgentTool,\n FindRegistrationsTool,\n InitiateConnectionTool,\n ListConnectionsTool,\n SendMessageToConnectionTool,\n CheckMessagesTool,\n ConnectionMonitorTool,\n ManageConnectionRequestsTool,\n AcceptConnectionRequestTool,\n RetrieveProfileTool,\n ListUnapprovedConnectionRequestsTool,\n} from '@hashgraphonline/standards-agent-kit';\nimport { HCS10Client } from '@hashgraphonline/standards-sdk';\n\nexport class HCS10Plugin extends BasePlugin {\n id = 'hcs-10';\n name = 'HCS-10 Plugin';\n description =\n 'HCS-10 agent tools for decentralized agent registration, connections, and messaging on Hedera';\n version = '1.0.0';\n author = 'Hashgraph Online';\n namespace = 'hcs10';\n\n private stateManager?: IStateManager;\n private tools: HederaTool[] = [];\n appConfig?: Record<string, unknown>;\n\n override async initialize(context: GenericPluginContext): Promise<void> {\n await super.initialize(context);\n\n const hederaKit = context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n this.context.logger.warn(\n 'HederaKit not found in context. HCS-10 tools will not be available.'\n );\n return;\n }\n\n try {\n this.stateManager =\n (context.stateManager as IStateManager) ||\n (context.config.stateManager as IStateManager) ||\n (this.appConfig?.stateManager as IStateManager) ||\n new OpenConvaiState();\n\n const accountId = hederaKit.signer.getAccountId().toString();\n let inboundTopicId = '';\n let outboundTopicId = '';\n\n try {\n const opKey = hederaKit.signer.getOperatorPrivateKey() as {\n toString?: () => string;\n toStringRaw?: () => string;\n };\n const privateKey = typeof opKey?.toStringRaw === 'function'\n ? opKey.toStringRaw()\n : typeof opKey?.toString === 'function'\n ? opKey.toString()\n : String(opKey);\n\n const hcs10Client = new HCS10Client({\n network: hederaKit.network as 'mainnet' | 'testnet',\n operatorId: accountId,\n operatorPrivateKey: privateKey,\n logLevel: 'error',\n });\n\n const profileResponse = await hcs10Client.retrieveProfile(accountId);\n if (profileResponse.success && profileResponse.topicInfo) {\n inboundTopicId = profileResponse.topicInfo.inboundTopic;\n outboundTopicId = profileResponse.topicInfo.outboundTopic;\n }\n } catch (profileError) {\n this.context.logger.warn(\n 'Could not retrieve profile topics:',\n profileError\n );\n }\n\n this.stateManager.setCurrentAgent({\n name: `Agent ${accountId}`,\n accountId: accountId,\n inboundTopicId,\n outboundTopicId,\n privateKey: ((): string => {\n const opKey = hederaKit.signer.getOperatorPrivateKey() as {\n toString?: () => string;\n toStringRaw?: () => string;\n };\n return typeof opKey?.toStringRaw === 'function'\n ? opKey.toStringRaw()\n : typeof opKey?.toString === 'function'\n ? opKey.toString()\n : String(opKey);\n })(),\n });\n\n this.context.logger.info(\n `Set current agent: ${accountId} with topics ${inboundTopicId}/${outboundTopicId}`\n );\n\n if (this.stateManager && !this.stateManager.getConnectionsManager()) {\n try {\n const opKey = hederaKit.signer.getOperatorPrivateKey() as {\n toString?: () => string;\n toStringRaw?: () => string;\n };\n const privateKey = typeof opKey?.toStringRaw === 'function'\n ? opKey.toStringRaw()\n : typeof opKey?.toString === 'function'\n ? opKey.toString()\n : String(opKey);\n const hcs10Client = new HCS10Client({\n network: hederaKit.network as 'mainnet' | 'testnet',\n operatorId: accountId,\n operatorPrivateKey: privateKey,\n logLevel: 'error',\n });\n\n this.stateManager.initializeConnectionsManager(hcs10Client as any);\n this.context.logger.info(\n 'ConnectionsManager initialized in HCS10Plugin'\n );\n } catch (cmError) {\n this.context.logger.warn('Could not initialize ConnectionsManager:', cmError);\n }\n }\n\n this.initializeTools();\n\n this.context.logger.info('HCS-10 Plugin initialized successfully');\n } catch (error) {\n this.context.logger.error('Failed to initialize HCS-10 plugin:', error);\n }\n }\n\n private initializeTools(): void {\n if (!this.stateManager) {\n throw new Error('StateManager must be initialized before creating tools');\n }\n\n const hederaKit = this.context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n throw new Error('HederaKit not found in context config');\n }\n\n const hcs10Builder = new HCS10Builder(hederaKit, this.stateManager);\n\n this.tools = [\n new RegisterAgentTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new FindRegistrationsTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new RetrieveProfileTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new InitiateConnectionTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new ListConnectionsTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new SendMessageToConnectionTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new CheckMessagesTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new ConnectionMonitorTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new ManageConnectionRequestsTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new AcceptConnectionRequestTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n new ListUnapprovedConnectionRequestsTool({\n hederaKit: hederaKit,\n hcs10Builder: hcs10Builder,\n logger: this.context.logger,\n }),\n ];\n }\n\n getTools(): HederaTool[] {\n return this.tools;\n }\n\n getStateManager(): IStateManager | undefined {\n return this.stateManager;\n }\n\n override async cleanup(): Promise<void> {\n this.tools = [];\n delete this.stateManager;\n if (this.context?.logger) {\n this.context.logger.info('HCS-10 Plugin cleaned up');\n }\n }\n}\n","import {\n GenericPluginContext,\n HederaTool,\n BasePlugin,\n HederaAgentKit,\n} from 'hedera-agent-kit';\nimport {\n HCS2Builder,\n CreateRegistryTool,\n RegisterEntryTool,\n UpdateEntryTool,\n DeleteEntryTool,\n MigrateRegistryTool,\n QueryRegistryTool,\n} from '@hashgraphonline/standards-agent-kit';\n\n/**\n * Plugin providing HCS-2 registry management tools\n */\nexport class HCS2Plugin extends BasePlugin {\n id = 'hcs-2';\n name = 'HCS-2 Plugin';\n description =\n 'HCS-2 registry management tools for decentralized registries on Hedera';\n version = '1.0.0';\n author = 'Hashgraph Online';\n namespace = 'hcs2';\n\n private tools: HederaTool[] = [];\n\n override async initialize(context: GenericPluginContext): Promise<void> {\n await super.initialize(context);\n\n const hederaKit = context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n this.context.logger.warn(\n 'HederaKit not found in context. HCS-2 tools will not be available.'\n );\n return;\n }\n\n try {\n this.initializeTools();\n\n this.context.logger.info(\n 'HCS-2 Plugin initialized successfully'\n );\n } catch (error) {\n this.context.logger.error(\n 'Failed to initialize HCS-2 plugin:',\n error\n );\n }\n }\n\n private initializeTools(): void {\n const hederaKit = this.context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n throw new Error('HederaKit not found in context config');\n }\n\n const hcs2Builder = new HCS2Builder(hederaKit);\n\n this.tools = [\n new CreateRegistryTool({\n hederaKit: hederaKit,\n hcs2Builder: hcs2Builder,\n logger: this.context.logger,\n }),\n new RegisterEntryTool({\n hederaKit: hederaKit,\n hcs2Builder: hcs2Builder,\n logger: this.context.logger,\n }),\n new UpdateEntryTool({\n hederaKit: hederaKit,\n hcs2Builder: hcs2Builder,\n logger: this.context.logger,\n }),\n new DeleteEntryTool({\n hederaKit: hederaKit,\n hcs2Builder: hcs2Builder,\n logger: this.context.logger,\n }),\n new MigrateRegistryTool({\n hederaKit: hederaKit,\n hcs2Builder: hcs2Builder,\n logger: this.context.logger,\n }),\n new QueryRegistryTool({\n hederaKit: hederaKit,\n hcs2Builder: hcs2Builder,\n logger: this.context.logger,\n }),\n ];\n }\n\n getTools(): HederaTool[] {\n return this.tools;\n }\n\n override async cleanup(): Promise<void> {\n this.tools = [];\n if (this.context?.logger) {\n this.context.logger.info('HCS-2 Plugin cleaned up');\n }\n }\n}","import {\n GenericPluginContext,\n HederaTool,\n BasePlugin,\n HederaAgentKit,\n} from 'hedera-agent-kit';\nimport {\n InscriberBuilder,\n InscribeFromUrlTool,\n InscribeFromFileTool,\n InscribeFromBufferTool,\n InscribeHashinalTool,\n RetrieveInscriptionTool,\n} from '@hashgraphonline/standards-agent-kit';\n\n/**\n * Plugin providing content inscription tools for Hedera\n */\nexport class InscribePlugin extends BasePlugin {\n id = 'inscribe';\n name = 'Inscribe Plugin';\n description =\n 'Content inscription tools for storing data on Hedera Consensus Service';\n version = '1.0.0';\n author = 'Hashgraph Online';\n namespace = 'inscribe';\n\n private tools: HederaTool[] = [];\n\n override async initialize(context: GenericPluginContext): Promise<void> {\n await super.initialize(context);\n\n const hederaKit = context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n this.context.logger.warn(\n 'HederaKit not found in context. Inscription tools will not be available.'\n );\n return;\n }\n\n try {\n this.initializeTools();\n\n this.context.logger.info(\n 'Inscribe Plugin initialized successfully'\n );\n } catch (error) {\n this.context.logger.error(\n 'Failed to initialize Inscribe plugin:',\n error\n );\n }\n }\n\n private initializeTools(): void {\n const hederaKit = this.context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n throw new Error('HederaKit not found in context config');\n }\n\n const inscriberBuilder = new InscriberBuilder(hederaKit);\n\n this.tools = [\n new InscribeFromUrlTool({\n hederaKit: hederaKit,\n inscriberBuilder: inscriberBuilder,\n logger: this.context.logger,\n }),\n new InscribeFromFileTool({\n hederaKit: hederaKit,\n inscriberBuilder: inscriberBuilder,\n logger: this.context.logger,\n }),\n new InscribeFromBufferTool({\n hederaKit: hederaKit,\n inscriberBuilder: inscriberBuilder,\n logger: this.context.logger,\n }),\n new InscribeHashinalTool({\n hederaKit: hederaKit,\n inscriberBuilder: inscriberBuilder,\n logger: this.context.logger,\n }),\n new RetrieveInscriptionTool({\n hederaKit: hederaKit,\n inscriberBuilder: inscriberBuilder,\n logger: this.context.logger,\n }),\n ];\n }\n\n getTools(): HederaTool[] {\n return this.tools;\n }\n\n override async cleanup(): Promise<void> {\n this.tools = [];\n if (this.context?.logger) {\n this.context.logger.info('Inscribe Plugin cleaned up');\n }\n }\n}","import {\n AccountId,\n Hbar,\n TransferTransaction,\n} from '@hashgraph/sdk';\nimport BigNumber from 'bignumber.js';\nimport { HederaAgentKit, BaseServiceBuilder } from 'hedera-agent-kit';\nimport { HbarTransferParams } from './types';\n\n/**\n * Custom AccountBuilder that properly handles HBAR decimal conversion\n */\nexport class AccountBuilder extends BaseServiceBuilder {\n constructor(hederaKit: HederaAgentKit) {\n super(hederaKit);\n }\n\n /**\n * Transfers HBAR between accounts with proper decimal handling\n */\n public transferHbar(\n params: HbarTransferParams,\n isUserInitiated: boolean = true\n ): this {\n this.clearNotes();\n const transaction = new TransferTransaction();\n \n if (!params.transfers || params.transfers.length === 0) {\n throw new Error('HbarTransferParams must include at least one transfer.');\n }\n\n let netZeroInTinybars = new BigNumber(0);\n let userTransferProcessedForScheduling = false;\n\n if (\n isUserInitiated &&\n this.kit.userAccountId &&\n (this.kit.operationalMode as string) === 'provideBytes' &&\n params.transfers.length === 1\n ) {\n const receiverTransfer = params.transfers[0];\n const amountValue =\n typeof receiverTransfer.amount === 'string' ||\n typeof receiverTransfer.amount === 'number'\n ? receiverTransfer.amount\n : receiverTransfer.amount.toString();\n\n const amountBigNum = new BigNumber(amountValue);\n\n if (amountBigNum.isPositive()) {\n const recipientAccountId =\n typeof receiverTransfer.accountId === 'string'\n ? AccountId.fromString(receiverTransfer.accountId)\n : receiverTransfer.accountId;\n\n const roundedAmount = amountBigNum.toFixed(8, BigNumber.ROUND_DOWN);\n const sdkHbarAmount = Hbar.fromString(roundedAmount);\n\n this.logger.info(\n `[AccountBuilder.transferHbar] Configuring user-initiated scheduled transfer: ${sdkHbarAmount.toString()} from ${\n this.kit.userAccountId\n } to ${recipientAccountId.toString()}`\n );\n \n this.addNote(\n `Configured HBAR transfer from your account (${\n this.kit.userAccountId\n }) to ${recipientAccountId.toString()} for ${sdkHbarAmount.toString()}.`\n );\n\n transaction.addHbarTransfer(recipientAccountId, sdkHbarAmount);\n transaction.addHbarTransfer(\n AccountId.fromString(this.kit.userAccountId),\n sdkHbarAmount.negated()\n );\n\n userTransferProcessedForScheduling = true;\n }\n }\n\n if (!userTransferProcessedForScheduling) {\n const processedTransfers: Array<{\n accountId: AccountId;\n amount: BigNumber;\n hbar: Hbar;\n }> = [];\n \n for (const transferInput of params.transfers) {\n const accountId =\n typeof transferInput.accountId === 'string'\n ? AccountId.fromString(transferInput.accountId)\n : transferInput.accountId;\n\n const amountValue =\n typeof transferInput.amount === 'string' ||\n typeof transferInput.amount === 'number'\n ? transferInput.amount\n : transferInput.amount.toString();\n\n const amountBigNum = new BigNumber(amountValue);\n const roundedAmount = amountBigNum.toFixed(8, BigNumber.ROUND_DOWN);\n \n this.logger.info(\n `Processing transfer: ${amountValue} HBAR (rounded to ${roundedAmount}) for account ${accountId.toString()}`\n );\n\n const sdkHbarAmount = Hbar.fromString(roundedAmount);\n processedTransfers.push({\n accountId,\n amount: amountBigNum,\n hbar: sdkHbarAmount\n });\n\n const tinybarsContribution = sdkHbarAmount.toTinybars();\n netZeroInTinybars = netZeroInTinybars.plus(\n tinybarsContribution.toString()\n );\n }\n\n if (!netZeroInTinybars.isZero()) {\n this.logger.warn(\n `Transfer sum not zero: ${netZeroInTinybars.toString()} tinybars off. Adjusting last transfer.`\n );\n \n if (processedTransfers.length > 0) {\n const lastTransfer = processedTransfers[processedTransfers.length - 1];\n const adjustment = netZeroInTinybars.dividedBy(-100000000);\n const adjustedAmount = lastTransfer.amount.plus(adjustment);\n const adjustedRounded = adjustedAmount.toFixed(8, BigNumber.ROUND_DOWN);\n lastTransfer.hbar = Hbar.fromString(adjustedRounded);\n \n this.logger.info(\n `Adjusted last transfer for ${lastTransfer.accountId.toString()} to ${adjustedRounded} HBAR`\n );\n }\n }\n \n for (const transfer of processedTransfers) {\n transaction.addHbarTransfer(transfer.accountId, transfer.hbar);\n }\n }\n\n if (typeof params.memo !== 'undefined') {\n if (params.memo === null) {\n this.logger.warn('Received null for memo in transferHbar.');\n } else {\n transaction.setTransactionMemo(params.memo);\n }\n }\n\n this.setCurrentTransaction(transaction);\n return this;\n }\n}","import { z } from 'zod';\nimport { HbarTransferParams } from './types';\nimport { AccountBuilder } from './AccountBuilder';\nimport { BaseHederaTransactionTool, BaseServiceBuilder } from 'hedera-agent-kit';\n\nconst HbarTransferInputSchema = z.object({\n accountId: z\n .string()\n .describe('Account ID for the transfer (e.g., \"0.0.xxxx\").'),\n amount: z\n .union([z.number(), z.string()])\n .describe(\n 'HBAR amount in decimal format (e.g., 1 for 1 HBAR, 0.5 for 0.5 HBAR). Positive for credit, negative for debit. DO NOT multiply by 10^8 for tinybars - just use the HBAR amount directly.'\n ),\n});\n\nconst TransferHbarZodSchemaCore = z.object({\n transfers: z\n .array(HbarTransferInputSchema)\n .min(1)\n .describe(\n 'Array of transfers. For simple transfers from your operator account, just include the recipient with positive amount: [{accountId: \"0.0.800\", amount: 1}]. For complex multi-party transfers, include all parties with negative amounts for senders and positive for receivers.'\n ),\n memo: z.string().optional().describe('Optional. Memo for the transaction.'),\n});\n\n/**\n * A Hedera transaction tool for transferring HBAR between accounts.\n * Supports single and multi-party transfers with automatic balance validation.\n * Extends BaseHederaTransactionTool to handle HBAR transfer transactions on the Hedera Hashgraph.\n */\nexport class TransferHbarTool extends BaseHederaTransactionTool<\n typeof TransferHbarZodSchemaCore\n> {\n name = 'hedera-account-transfer-hbar-v2';\n description =\n 'PRIMARY TOOL FOR HBAR TRANSFERS: Transfers HBAR between accounts. For simple transfers from the operator account, just specify the recipient with a positive amount (e.g., [{accountId: \"0.0.800\", amount: 1}] to send 1 HBAR to 0.0.800). The sender will be automatically added. For multi-party transfers (e.g., \"A sends 5 HBAR to C and B sends 3 HBAR to C\"), include ALL transfers with their amounts (negative for senders, positive for receivers).';\n specificInputSchema = TransferHbarZodSchemaCore;\n namespace = 'account';\n\n\n /**\n * Creates and returns the service builder for account operations.\n * \n * @returns BaseServiceBuilder instance configured for account operations\n */\n protected getServiceBuilder(): BaseServiceBuilder {\n return new AccountBuilder(this.hederaKit) as BaseServiceBuilder;\n }\n\n /**\n * Executes the HBAR transfer using the provided builder and arguments.\n * Validates that all transfers sum to zero before execution.\n * \n * @param builder - The service builder instance for executing transactions\n * @param specificArgs - The validated transfer parameters including transfers array and optional memo\n * @returns Promise that resolves when the transfer is complete\n */\n protected async callBuilderMethod(\n builder: BaseServiceBuilder,\n specificArgs: z.infer<typeof TransferHbarZodSchemaCore>\n ): Promise<void> {\n await (builder as AccountBuilder).transferHbar(\n specificArgs as unknown as HbarTransferParams\n );\n }\n}","import { StructuredTool } from '@langchain/core/tools';\nimport { z } from 'zod';\nimport { HederaAgentKit } from 'hedera-agent-kit';\nimport { Logger } from '@hashgraphonline/standards-sdk';\n\ninterface TokenInfo {\n decimals: number;\n [key: string]: unknown;\n}\n\ninterface ToolWithCall {\n _call(input: unknown): Promise<string>;\n}\n\ninterface AgentKitWithMirrorNode {\n mirrorNode?: {\n getTokenInfo(tokenId: string): Promise<TokenInfo>;\n };\n network: string;\n}\n\nexport class AirdropToolWrapper extends StructuredTool {\n name = 'hedera-hts-airdrop-token';\n description =\n 'Airdrops fungible tokens to multiple recipients. Automatically converts human-readable amounts to smallest units based on token decimals.';\n\n schema = z.object({\n tokenId: z\n .string()\n .describe('The ID of the fungible token to airdrop (e.g., \"0.0.yyyy\").'),\n recipients: z\n .array(\n z.object({\n accountId: z\n .string()\n .describe('Recipient account ID (e.g., \"0.0.xxxx\").'),\n amount: z\n .union([z.number(), z.string()])\n .describe(\n 'Amount in human-readable format (e.g., \"10\" for 10 tokens).'\n ),\n })\n )\n .min(1)\n .describe('Array of recipient objects, each with accountId and amount.'),\n memo: z.string().optional().describe('Optional. Memo for the transaction.'),\n });\n\n private originalTool: StructuredTool & ToolWithCall;\n private agentKit: HederaAgentKit & AgentKitWithMirrorNode;\n private logger: Logger;\n\n constructor(originalTool: StructuredTool, agentKit: unknown) {\n super();\n this.originalTool = originalTool as StructuredTool & ToolWithCall;\n this.agentKit = agentKit as HederaAgentKit & AgentKitWithMirrorNode;\n this.logger = new Logger({ module: 'AirdropToolWrapper' });\n }\n\n async _call(input: z.infer<typeof this.schema>): Promise<string> {\n try {\n this.logger.info(\n `Processing airdrop request for token ${input.tokenId} with ${input.recipients.length} recipients`\n );\n\n const tokenInfo = await this.getTokenInfo(input.tokenId);\n const decimals = tokenInfo.decimals || 0;\n\n this.logger.info(`Token ${input.tokenId} has ${decimals} decimal places`);\n\n const convertedRecipients = input.recipients.map((recipient) => {\n const humanAmount =\n typeof recipient.amount === 'string'\n ? parseFloat(recipient.amount)\n : recipient.amount;\n const smallestUnitAmount = this.convertToSmallestUnits(\n humanAmount,\n decimals\n );\n\n this.logger.info(\n `Converting amount for ${recipient.accountId}: ${humanAmount} tokens → ${smallestUnitAmount} smallest units`\n );\n\n return {\n ...recipient,\n amount: smallestUnitAmount.toString(),\n };\n });\n\n const convertedInput = {\n ...input,\n recipients: convertedRecipients,\n };\n\n this.logger.info(`Calling original airdrop tool with converted amounts`);\n return await this.originalTool._call(convertedInput);\n } catch (error) {\n this.logger.error('Error in airdrop tool wrapper:', error);\n throw error;\n }\n }\n\n private convertToSmallestUnits(amount: number, decimals: number): number {\n return Math.floor(amount * Math.pow(10, decimals));\n }\n\n private async getTokenInfo(tokenId: string): Promise<TokenInfo> {\n return await this.queryTokenInfo(tokenId);\n }\n\n private async queryTokenInfo(tokenId: string): Promise<TokenInfo> {\n try {\n this.logger.info('Querying token info using mirror node');\n const mirrorNode = this.agentKit.mirrorNode;\n if (!mirrorNode) {\n this.logger.info(\n 'MirrorNode not found in agentKit, attempting to access via fetch'\n );\n const network = this.agentKit.network || 'testnet';\n const mirrorNodeUrl =\n network === 'mainnet'\n ? 'https://mainnet.mirrornode.hedera.com'\n : 'https://testnet.mirrornode.hedera.com';\n\n const response = await fetch(\n `${mirrorNodeUrl}/api/v1/tokens/${tokenId}`\n );\n if (response.ok) {\n const tokenData = (await response.json()) as Record<string, unknown>;\n const decimals = parseInt(String(tokenData.decimals || '0'));\n this.logger.info(\n `Token ${tokenId} found with ${decimals} decimals via API`\n );\n return { ...tokenData, decimals };\n }\n } else {\n const tokenData = await mirrorNode.getTokenInfo(tokenId);\n\n if (tokenData && typeof tokenData.decimals !== 'undefined') {\n const decimals = parseInt(tokenData.decimals.toString()) || 0;\n this.logger.info(`Token ${tokenId} found with ${decimals} decimals`);\n return { ...tokenData, decimals };\n }\n }\n\n throw new Error(`Token data not found or missing decimals field`);\n } catch (error) {\n this.logger.warn(`Failed to query token info for ${tokenId}:`, error);\n\n this.logger.info(\n 'Falling back to assumed 0 decimal places (smallest units)'\n );\n return { decimals: 0 };\n }\n }\n}\n","import {\n GenericPluginContext,\n HederaTool,\n BasePlugin,\n HederaAgentKit,\n HederaAirdropTokenTool,\n} from 'hedera-agent-kit';\nimport { TransferHbarTool } from './TransferHbarTool';\nimport { AirdropToolWrapper } from './AirdropToolWrapper';\nimport { StructuredTool } from '@langchain/core/tools';\n\nexport class HbarPlugin extends BasePlugin {\n id = 'hbar';\n name = 'HBAR Plugin';\n description =\n 'HBAR operations: transfer tool with robust decimal handling and compatibility with airdrop improvements';\n version = '1.0.0';\n author = 'Hashgraph Online';\n namespace = 'account';\n\n private tools: (HederaTool | AirdropToolWrapper)[] = [];\n private originalAirdropTool: StructuredTool | null = null;\n\n override async initialize(context: GenericPluginContext): Promise<void> {\n await super.initialize(context);\n\n const hederaKit = context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n this.context.logger.warn(\n 'HederaKit not found in context. HBAR tools will not be available.'\n );\n return;\n }\n\n try {\n this.initializeTools();\n\n this.context.logger.info('HBAR Plugin initialized successfully');\n } catch (error) {\n this.context.logger.error('Failed to initialize HBAR plugin:', error);\n }\n }\n\n private initializeTools(): void {\n const hederaKit = this.context.config.hederaKit as HederaAgentKit;\n if (!hederaKit) {\n throw new Error('HederaKit not found in context config');\n }\n\n const transfer = new TransferHbarTool({\n hederaKit: hederaKit,\n logger: this.context.logger,\n });\n\n this.tools = [transfer];\n\n try {\n this.context.logger.info(\n 'Creating wrapper for passed original airdrop tool'\n );\n\n const airdropTool = new HederaAirdropTokenTool({\n hederaKit: hederaKit,\n logger: this.context.logger,\n });\n const wrappedAirdropTool = new AirdropToolWrapper(airdropTool, hederaKit);\n this.tools.push(wrappedAirdropTool);\n this.context.logger.info('Added wrapped airdrop tool to HBAR Plugin');\n } catch (error) {\n this.context.logger.error('Error creating airdrop tool wrapper:', error);\n }\n\n this.context.logger.info(\n `HBAR Plugin tools initialized with ${this.tools.length} tools`\n );\n }\n\n override getTools(): HederaTool[] {\n // @ts-ignore\n return this.tools;\n }\n\n async shutdown(): Promise<void> {\n this.tools = [];\n }\n}\n","import { AgentExecutor } from 'langchain/agents';\nimport { Logger } from '@hashgraphonline/standards-sdk';\n\n/**\n * Custom AgentExecutor that intercepts large tool outputs and converts them to content references\n * before they are sent to the LLM to avoid token limit issues.\n * \n * Note: The content reference conversion is already handled in the MCP adapter,\n * so this class currently just extends AgentExecutor without modifications.\n * We keep it as a placeholder for future enhancements.\n */\nexport class ContentAwareAgentExecutor extends AgentExecutor {\n private logger: Logger;\n\n constructor(config: any) {\n super(config);\n this.logger = new Logger({ module: 'ContentAwareAgentExecutor' });\n }\n}","import type { BaseMessage } from '@langchain/core/messages';\nimport type { StructuredTool } from '@langchain/core/tools';\nimport type { TransactionReceipt } from '@hashgraph/sdk';\nimport {\n HederaAgentKit,\n ServerSigner,\n TokenUsageCallbackHandler,\n TokenUsage,\n BasePlugin,\n} from 'hedera-agent-kit';\nimport type { CostCalculation } from 'hedera-agent-kit';\nimport type { AIProvider, VercelAIProvider, BAMLProvider } from './providers';\nimport { Logger } from '@hashgraphonline/standards-sdk';\nimport type { MCPServerConfig, MCPConnectionStatus } from './mcp/types';\n\nexport interface ToolFilterConfig {\n namespaceWhitelist?: string[];\n toolBlacklist?: string[];\n toolPredicate?: (tool: StructuredTool) => boolean;\n}\n\nexport type ExecutionMode = 'direct' | 'bytes';\nexport type OperationalMode = 'autonomous' | 'returnBytes';\n\nexport interface HederaAgentConfiguration {\n signer: ServerSigner;\n execution?: {\n mode?: ExecutionMode;\n operationalMode?: OperationalMode;\n userAccountId?: string;\n scheduleUserTransactions?: boolean;\n scheduleUserTransactionsInBytesMode?: boolean;\n };\n ai?: {\n provider?: AIProvider;\n llm?: unknown;\n apiKey?: string;\n modelName?: string;\n temperature?: number;\n };\n filtering?: ToolFilterConfig;\n messaging?: {\n systemPreamble?: string;\n systemPostamble?: string;\n conciseMode?: boolean;\n };\n extensions?: {\n plugins?: BasePlugin[];\n mirrorConfig?: Record<string, unknown>;\n modelCapability?: string;\n };\n mcp?: {\n servers?: MCPServerConfig[];\n autoConnect?: boolean;\n };\n debug?: {\n verbose?: boolean;\n silent?: boolean;\n };\n}\n\nexport interface ConversationContext {\n messages: BaseMessage[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface ChatResponse {\n output: string;\n message?: string;\n transactionBytes?: string;\n receipt?: TransactionReceipt | object;\n scheduleId?: string;\n transactionId?: string;\n notes?: string[];\n error?: string;\n intermediateSteps?: unknown;\n rawToolOutput?: unknown;\n tokenUsage?: TokenUsage;\n cost?: CostCalculation;\n metadata?: Record<string, unknown>;\n tool_calls?: Array<{\n id: string;\n name: string;\n args: Record<string, unknown>;\n output?: string;\n }>;\n [key: string]: unknown;\n}\n\nexport interface UsageStats extends TokenUsage {\n cost: CostCalculation;\n}\n\nexport abstract class BaseAgent {\n protected logger: Logger;\n protected agentKit: HederaAgentKit | undefined;\n protected tools: StructuredTool[] = [];\n protected initialized = false;\n protected tokenTracker: TokenUsageCallbackHandler | undefined;\n\n constructor(protected config: HederaAgentConfiguration) {\n this.logger = new Logger({\n module: 'BaseAgent',\n silent: config.debug?.silent || false,\n });\n }\n\n abstract boot(): Promise<void>;\n abstract chat(\n message: string,\n context?: ConversationContext\n ): Promise<ChatResponse>;\n abstract shutdown(): Promise<void>;\n abstract switchMode(mode: OperationalMode): void;\n abstract getUsageStats(): UsageStats;\n abstract getUsageLog(): UsageStats[];\n abstract clearUsageStats(): void;\n abstract connectMCPServers(): Promise<void>;\n abstract getMCPConnectionStatus(): Map<string, MCPConnectionStatus>;\n\n public getCore(): HederaAgentKit | undefined {\n return this.agentKit;\n }\n\n protected filterTools(tools: StructuredTool[]): StructuredTool[] {\n let filtered = [...tools];\n const filter = this.config.filtering;\n\n if (!filter) return filtered;\n\n if (filter.namespaceWhitelist?.length) {\n filtered = filtered.filter((tool) => {\n const namespace = (tool as StructuredTool & { namespace?: string })\n .namespace;\n return !namespace || filter.namespaceWhitelist!.includes(namespace);\n });\n }\n\n if (filter.toolBlacklist?.length) {\n filtered = filtered.filter(\n (tool) => !filter.toolBlacklist!.includes(tool.name)\n );\n }\n\n if (filter.toolPredicate) {\n filtered = filtered.filter(filter.toolPredicate);\n }\n\n this.logger.debug(`Filtered tools: ${tools.length} → ${filtered.length}`);\n return filtered;\n }\n\n protected buildSystemPrompt(): string {\n const parts: string[] = [];\n const operatorId = this.config.signer.getAccountId().toString();\n const userAccId = this.config.execution?.userAccountId;\n\n if (this.config.messaging?.systemPreamble) {\n parts.push(this.config.messaging.systemPreamble);\n }\n\n parts.push(\n `You are a helpful Hedera assistant. Your primary operator account is ${operatorId}. ` +\n `You have tools to interact with the Hedera Hashgraph. ` +\n `When using any tool, provide all necessary parameters as defined by that tool's schema and description.`\n );\n\n if (userAccId) {\n parts.push(\n `The user you are assisting has a personal Hedera account ID: ${userAccId}. ` +\n `IMPORTANT: When the user says things like \"I want to send HBAR\" or \"transfer my tokens\", you MUST use ${userAccId} as the sender/from account. ` +\n `For example, if user says \"I want to send 2 HBAR to 0.0.800\", you must set up a transfer where ${userAccId} sends the HBAR, not your operator account.`\n );\n }\n\n const operationalMode =\n this.config.execution?.operationalMode || 'returnBytes';\n if (operationalMode === 'autonomous') {\n parts.push(\n `\\nOPERATIONAL MODE: 'autonomous'. Your goal is to execute transactions directly using your tools. ` +\n `Your account ${operatorId} will be the payer for these transactions. ` +\n `Even if the user's account (${\n userAccId || 'a specified account'\n }) is the actor in the transaction body (e.g., sender of HBAR), ` +\n `you (the agent with operator ${operatorId}) are still executing and paying. For HBAR transfers, ensure the amounts in the 'transfers' array sum to zero (as per tool schema), balancing with your operator account if necessary.`\n );\n } else {\n if (\n this.config.execution?.scheduleUserTransactionsInBytesMode &&\n userAccId\n ) {\n parts.push(\n `\\nOPERATIONAL MODE: 'returnBytes' with scheduled transactions for user actions. ` +\n `When a user asks for a transaction to be prepared (e.g., creating a token, topic, transferring assets for them to sign, etc), ` +\n `you MUST default to creating a Scheduled Transaction using the appropriate tool with the metaOption 'schedule: true'. ` +\n `The user (with account ID ${userAccId}) will be the one to ultimately pay for and (if needed) sign the inner transaction. ` +\n `Your operator account (${operatorId}) will pay for creating the schedule entity itself. ` +\n `You MUST return the ScheduleId and details of the scheduled operation in a structured JSON format with these fields: success, op, schedule_id, description, payer_account_id_scheduled_tx, and scheduled_transaction_details.`\n );\n } else {\n parts.push(\n `\\nOPERATIONAL MODE: 'returnBytes'. Your goal is to provide transaction bytes when possible. ` +\n `When a user asks for a transaction to be prepared (e.g., for them to sign, or for scheduling without the default scheduling flow), ` +\n `you MUST call the appropriate tool. ` +\n `IMPORTANT: Only use metaOption 'returnBytes: true' for tools that explicitly support it (like HBAR transfers, token operations). ` +\n `Many tools (inscriptions, HCS-2, HCS-20, etc.) do NOT support returnBytes and will execute directly - this is expected behavior. ` +\n `For tools without returnBytes support, simply call them with their standard parameters. ` +\n `If you need raw bytes for the user to sign for their own account ${\n userAccId || 'if specified'\n }, ensure the tool constructs the transaction body accordingly when returnBytes IS supported.`\n );\n }\n }\n\n if (this.config.messaging?.conciseMode !== false) {\n parts.push(\n '\\nAlways be concise. If the tool provides a JSON string as its primary output (especially in returnBytes mode), make your accompanying text brief. If the tool does not provide JSON output or an error occurs, your narrative becomes primary; if notes were generated by the tool in such cases, append them to your textual response.'\n );\n }\n\n if (this.config.messaging?.systemPostamble) {\n parts.push(this.config.messaging.systemPostamble);\n }\n\n return parts.join('\\n');\n }\n\n isReady(): boolean {\n return this.initialized;\n }\n}\n\nexport type { AIProvider, VercelAIProvider, BAMLProvider };\n","import type { ContentType, ContentSource } from '../types/content-reference';\nimport type { ContentStorage } from '../memory/ContentStorage';\nimport { Logger } from '@hashgraphonline/standards-sdk';\n\nexport interface MCPResponseContent {\n content: unknown;\n type: 'text' | 'image' | 'resource' | 'text[]' | 'image[]';\n sizeBytes: number;\n mimeType?: string;\n}\n\nexport interface ProcessedResponse {\n content: unknown;\n wasProcessed: boolean;\n referenceCreated?: boolean;\n referenceId?: string;\n originalSize?: number;\n errors?: string[];\n}\n\nexport interface ContentAnalysis {\n shouldProcess: boolean;\n contents: MCPResponseContent[];\n totalSize: number;\n largestContentSize: number;\n}\n\nexport class MCPContentProcessor {\n private contentStorage: ContentStorage;\n private logger: Logger;\n\n constructor(contentStorage: ContentStorage, logger: Logger) {\n this.contentStorage = contentStorage;\n this.logger = logger;\n }\n\n analyzeResponse(response: unknown): ContentAnalysis {\n const contents: MCPResponseContent[] = [];\n let totalSize = 0;\n\n this.extractContentFromResponse(response, contents);\n\n totalSize = contents.reduce((sum, content) => sum + content.sizeBytes, 0);\n const largestContentSize = contents.reduce((max, content) => \n Math.max(max, content.sizeBytes), 0);\n\n const shouldProcess = contents.some(content => \n this.contentStorage.shouldUseReference(\n typeof content.content === 'string' \n ? content.content \n : JSON.stringify(content.content)\n )\n );\n\n return {\n shouldProcess,\n contents,\n totalSize,\n largestContentSize\n };\n }\n\n async processResponse(\n response: unknown,\n serverName: string,\n toolName: string\n ): Promise<ProcessedResponse> {\n try {\n const analysis = this.analyzeResponse(response);\n \n if (!analysis.shouldProcess) {\n return {\n content: response,\n wasProcessed: false\n };\n }\n\n const processedResponse = await this.createReferencedResponse(\n response,\n analysis,\n serverName,\n toolName\n );\n\n return processedResponse;\n } catch (error) {\n this.logger.error('Error processing MCP response:', error);\n return {\n content: response,\n wasProcessed: false,\n errors: [error instanceof Error ? error.message : 'Unknown processing error']\n };\n }\n }\n\n private extractContentFromResponse(obj: unknown, contents: MCPResponseContent[]): void {\n if (obj === null || obj === undefined) {\n return;\n }\n\n if (Array.isArray(obj)) {\n obj.forEach(item => this.extractContentFromResponse(item, contents));\n return;\n }\n\n if (typeof obj === 'object') {\n const record = obj as Record<string, unknown>;\n \n if (record.type === 'text' && typeof record.text === 'string') {\n contents.push({\n content: record.text,\n type: 'text',\n sizeBytes: Buffer.byteLength(record.text, 'utf8'),\n mimeType: this.detectMimeType(record.text as string)\n });\n return;\n }\n\n if (record.type === 'image' && typeof record.data === 'string') {\n contents.push({\n content: record.data,\n type: 'image',\n sizeBytes: Math.ceil(record.data.length * 0.75),\n mimeType: record.mimeType as string || 'image/jpeg'\n });\n return;\n }\n\n if (record.type === 'resource' && record.resource) {\n const resourceStr = JSON.stringify(record.resource);\n contents.push({\n content: resourceStr,\n type: 'resource',\n sizeBytes: Buffer.byteLength(resourceStr, 'utf8'),\n mimeType: 'application/json'\n });\n return;\n }\n\n Object.values(record).forEach(value => \n this.extractContentFromResponse(value, contents));\n return;\n }\n\n if (typeof obj === 'string') {\n if (obj.length > 1000) {\n contents.push({\n content: obj,\n type: 'text',\n sizeBytes: Buffer.byteLength(obj, 'utf8'),\n mimeType: this.detectMimeType(obj)\n });\n }\n }\n }\n\n private async createReferencedResponse(\n originalResponse: unknown,\n analysis: ContentAnalysis,\n serverName: string,\n toolName: string\n ): Promise<ProcessedResponse> {\n const processedResponse = this.deepClone(originalResponse);\n const errors: string[] = [];\n let referenceCreated = false;\n let totalReferenceSize = 0;\n\n for (const contentInfo of analysis.contents) {\n if (this.contentStorage.shouldUseReference(\n typeof contentInfo.content === 'string' \n ? contentInfo.content \n : JSON.stringify(contentInfo.content)\n )) {\n try {\n const contentBuffer = Buffer.from(\n typeof contentInfo.content === 'string' \n ? contentInfo.content \n : JSON.stringify(contentInfo.content),\n 'utf8'\n );\n\n const contentType = this.mapMimeTypeToContentType(contentInfo.mimeType);\n \n const metadata: Parameters<typeof this.contentStorage.storeContentIfLarge>[1] = {\n contentType,\n source: 'mcp_tool' as ContentSource,\n mcpToolName: `${serverName}::${toolName}`,\n tags: ['mcp_response', serverName, toolName]\n };\n \n if (contentInfo.mimeType !== undefined) {\n metadata.mimeType = contentInfo.mimeType;\n }\n \n const reference = await this.contentStorage.storeContentIfLarge(\n contentBuffer,\n metadata\n );\n\n if (reference) {\n this.replaceContentInResponse(\n processedResponse,\n contentInfo.content,\n this.createLightweightReference(reference)\n );\n referenceCreated = true;\n totalReferenceSize += contentBuffer.length;\n }\n } catch (error) {\n errors.push(`Failed to create reference: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n }\n\n const result: ProcessedResponse = {\n content: processedResponse,\n wasProcessed: true,\n referenceCreated,\n originalSize: totalReferenceSize\n };\n \n if (errors.length > 0) {\n result.errors = errors;\n }\n \n return result;\n }\n\n private createLightweightReference(reference: any): Record<string, unknown> {\n return {\n type: 'content_reference',\n referenceId: reference.referenceId,\n preview: reference.preview,\n size: reference.metadata.sizeBytes,\n contentType: reference.metadata.contentType,\n format: 'ref://{id}',\n _isReference: true\n };\n }\n\n private replaceContentInResponse(obj: unknown, oldContent: unknown, newContent: any): void {\n if (obj === null || obj === undefined) {\n return;\n }\n\n if (Array.isArray(obj)) {\n for (let i = 0; i < obj.length; i++) {\n if (obj[i] === oldContent) {\n obj[i] = newContent;\n } else {\n this.replaceContentInResponse(obj[i], oldContent, newContent);\n }\n }\n return;\n }\n\n if (typeof obj === 'object') {\n const record = obj as Record<string, unknown>;\n if (record.type === 'text' && record.text === oldContent) {\n for (const key of Object.keys(record)) {\n delete record[key];\n }\n for (const key of Object.keys(newContent)) {\n record[key] = newContent[key];\n }\n return;\n }\n for (const key in record) {\n if (record[key] === oldContent) {\n record[key] = newContent;\n } else {\n this.replaceContentInResponse(record[key], oldContent, newContent);\n }\n }\n }\n }\n\n private detectMimeType(content: string): string {\n if (content.trim().startsWith('{') || content.trim().startsWith('[')) {\n return 'application/json';\n }\n if (content.includes('<html>') || content.includes('<!DOCTYPE')) {\n return 'text/html';\n }\n if (content.includes('# ') || content.includes('## ')) {\n return 'text/markdown';\n }\n return 'text/plain';\n }\n\n private mapMimeTypeToContentType(mimeType?: string): ContentType {\n if (!mimeType) return 'text';\n \n if (mimeType.startsWith('text/plain')) return 'text';\n if (mimeType === 'application/json') return 'json';\n if (mimeType === 'text/html') return 'html';\n if (mimeType === 'text/markdown') return 'markdown';\n if (mimeType.startsWith('text/')) return 'text';\n \n return 'binary';\n }\n\n private deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n \n if (obj instanceof Date) {\n return new Date(obj.getTime()) as T;\n }\n \n if (Array.isArray(obj)) {\n return obj.map(item => this.deepClone(item)) as T;\n }\n \n const cloned = {} as T;\n for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n cloned[key] = this.deepClone(obj[key]);\n }\n }\n \n return cloned;\n }\n}","import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport type { MCPServerConfig, MCPToolInfo, MCPConnectionStatus } from './types';\nimport { Logger } from '@hashgraphonline/standards-sdk';\nimport type { ContentStorage } from '../memory/ContentStorage';\nimport { MCPContentProcessor, ProcessedResponse } from './ContentProcessor';\n\n/**\n * Manages connections to MCP servers and tool discovery\n */\nexport class MCPClientManager {\n private clients: Map<string, Client> = new Map();\n private tools: Map<string, MCPToolInfo[]> = new Map();\n private logger: Logger;\n private contentProcessor?: MCPContentProcessor;\n\n constructor(logger: Logger, contentStorage?: ContentStorage) {\n this.logger = logger;\n if (contentStorage) {\n this.contentProcessor = new MCPContentProcesso