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. https://hol.org

352 lines (331 loc) 11.4 kB
import { AIAgentCapability } from '@hashgraphonline/standards-sdk'; import { z } from 'zod'; import { BaseServiceBuilder } from 'hedera-agent-kit'; import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager'; import { HCS10Builder, RegisterAgentParams, } from '../../builders/hcs10/hcs10-builder'; import { BaseHCS10TransactionTool } from './base-hcs10-tools'; import { HCS10TransactionToolParams } from './hcs10-tool-params'; import { RegisteredAgent } from '../../state/state-types'; import { NaturalLanguageMapper } from './natural-language-mapper'; const RegisterAgentZodSchema = z.object({ name: z .string() .min(1) .max(50) .describe('A unique name for the agent (1-50 characters)'), description: z .string() .max(500) .optional() .describe('Optional bio description for the agent (max 500 characters)'), alias: z .string() .optional() .transform((val) => { if (!val || val.toLowerCase().includes('random')) { const timestamp = Date.now().toString(36); const randomChars = Math.random().toString(36); return `bot${timestamp}${randomChars}`; } return val; }) .describe( 'Optional custom username/alias for the agent. Use "random" to generate a unique alias' ), type: z .enum(['autonomous', 'manual']) .optional() .describe('Agent type (default: autonomous)'), model: z .string() .optional() .describe('AI model identifier (default: agent-model-2024)'), capabilities: z .union([ z.array(z.nativeEnum(AIAgentCapability)), z.array(z.string()), z.string(), ]) .optional() .transform((val) => { if (!val) {return undefined;} if (typeof val === 'string') { return NaturalLanguageMapper.parseCapabilities(val); } if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'string') { return NaturalLanguageMapper.parseTagsOrCapabilities(val); } return val as AIAgentCapability[]; }) .describe('Agent capabilities - can be capability names (e.g. "ai", "data processing"), capability enum values, or array of either. Common values: "ai"/"text" (TEXT_GENERATION), "data" (DATA_INTEGRATION), "analytics" (TRANSACTION_ANALYTICS)'), tags: z .union([ z.array(z.string()), z.string(), ]) .optional() .transform((val) => { if (!val) {return undefined;} if (typeof val === 'string') { return NaturalLanguageMapper.parseCapabilities(val); } return NaturalLanguageMapper.parseTagsOrCapabilities(val); }) .describe('Tags for the agent (alternative to capabilities) - e.g. "ai", "data", "analytics". Will be converted to appropriate capabilities.'), creator: z.string().optional().describe('Creator attribution for the agent'), socials: z .record( z.enum([ 'twitter', 'github', 'discord', 'telegram', 'linkedin', 'youtube', 'website', 'x', ] as const), z.string() ) .optional() .describe( 'Social media links (e.g., {"twitter": "@handle", "discord": "username"})' ), properties: z .record(z.unknown()) .optional() .describe('Custom metadata properties for the agent'), profilePicture: z .union([ z.string().describe('URL or local file path to profile picture'), z.object({ url: z.string().optional(), path: z.string().optional(), filename: z.string().optional(), }), ]) .optional() .describe( 'Optional profile picture as URL, file path, or object with url/path/filename' ), existingProfilePictureTopicId: z .string() .optional() .describe( 'Topic ID of an existing profile picture to reuse (e.g., 0.0.12345)' ), initialBalance: z .number() .positive() .optional() .describe( 'Optional initial HBAR balance for the new agent account (will create new account if provided)' ), userAccountId: z .string() .optional() .describe( 'Optional account ID (e.g., 0.0.12345) to use as the agent account instead of creating a new one' ), hbarFee: z .number() .positive() .optional() .describe( 'Optional HBAR fee amount to charge per message on the inbound topic' ), tokenFees: z .array( z.object({ amount: z.number().positive(), tokenId: z.string(), }) ) .optional() .describe('Optional token fees to charge per message'), exemptAccountIds: z .array(z.string()) .optional() .describe('Optional account IDs to exempt from fees'), setAsCurrent: z .boolean() .optional() .describe('Whether to set as current agent (default: true)'), persistence: z .object({ prefix: z.string().optional(), }) .optional() .describe('Optional persistence configuration'), }); export class RegisterAgentTool extends BaseHCS10TransactionTool< typeof RegisterAgentZodSchema > { name = 'register_agent'; description = 'Creates and registers the AI agent on the Hedera network. Returns JSON string with agent details (accountId, privateKey, topics) on success. Supports natural language for capabilities/tags like "ai", "data processing", "analytics". Note: This tool requires multiple transactions and cannot be used in returnBytes mode. If alias is set to "random" or contains "random", a unique alias will be generated.'; specificInputSchema = RegisterAgentZodSchema; private specificArgs: z.infer<typeof RegisterAgentZodSchema> | undefined; constructor(params: HCS10TransactionToolParams) { super(params); this.neverScheduleThisTool = true; this.requiresMultipleTransactions = true; } protected async callBuilderMethod( builder: BaseServiceBuilder, specificArgs: z.infer<typeof RegisterAgentZodSchema> ): Promise<void> { const hcs10Builder = builder as HCS10Builder; this.specificArgs = specificArgs; const params: RegisterAgentParams = { name: specificArgs.name, }; if (specificArgs.description !== undefined) { params.bio = specificArgs.description; } if (specificArgs.alias !== undefined) { params.alias = specificArgs.alias; } else { const randomSuffix = Date.now().toString(36); params.alias = `${specificArgs.name}${randomSuffix}`; } if (specificArgs.type !== undefined) { params.type = specificArgs.type; } if (specificArgs.model !== undefined) { params.model = specificArgs.model; } if (specificArgs.tags !== undefined) { params.capabilities = specificArgs.tags as AIAgentCapability[]; } else if (specificArgs.capabilities !== undefined) { params.capabilities = specificArgs.capabilities as AIAgentCapability[]; } if (specificArgs.creator !== undefined) { params.creator = specificArgs.creator; } if (specificArgs.socials !== undefined) { params.socials = specificArgs.socials; } if (specificArgs.properties !== undefined) { params.properties = specificArgs.properties; } if (specificArgs.profilePicture !== undefined) { if (typeof specificArgs.profilePicture === 'string') { params.profilePicture = specificArgs.profilePicture; } else { const profilePicObj: { url?: string; path?: string; filename?: string; } = {}; if (specificArgs.profilePicture.url !== undefined) { profilePicObj.url = specificArgs.profilePicture.url; } if (specificArgs.profilePicture.path !== undefined) { profilePicObj.path = specificArgs.profilePicture.path; } if (specificArgs.profilePicture.filename !== undefined) { profilePicObj.filename = specificArgs.profilePicture.filename; } params.profilePicture = profilePicObj; } } if (specificArgs.existingProfilePictureTopicId !== undefined) { params.existingProfilePictureTopicId = specificArgs.existingProfilePictureTopicId; } if (specificArgs.userAccountId !== undefined) { params.userAccountId = specificArgs.userAccountId; } if (specificArgs.hbarFee !== undefined) { params.hbarFee = specificArgs.hbarFee; } if (specificArgs.tokenFees !== undefined) { params.tokenFees = specificArgs.tokenFees; } if (specificArgs.exemptAccountIds !== undefined) { params.exemptAccountIds = specificArgs.exemptAccountIds; } if (specificArgs.initialBalance !== undefined) { params.initialBalance = specificArgs.initialBalance; } await hcs10Builder.registerAgent(params); } /** * Override _call to intercept the result and save agent to state if needed */ protected override async _call( args: z.infer<ReturnType<this['schema']>>, runManager?: CallbackManagerForToolRun ): Promise<string> { const result = await super._call(args, runManager); const shouldSetAsCurrent = this.specificArgs?.setAsCurrent !== false; if (this.specificArgs && shouldSetAsCurrent) { try { const parsed = JSON.parse(result); if (parsed.rawResult) { this._handleRegistrationResult(parsed.rawResult); } else if (parsed.state || parsed.accountId || parsed.metadata) { this._handleRegistrationResult(parsed); } } catch (e) {} } return result; } /** * Extract agent data from registration result and save to state */ private _handleRegistrationResult(rawResult: any): void { let accountId = rawResult.accountId || rawResult.metadata?.accountId; if (!accountId && rawResult.state?.createdResources) { const accountResource = rawResult.state.createdResources.find( (r: string) => r.startsWith('account:') ); if (accountResource) { accountId = accountResource.split(':')[1]; } } const inboundTopicId = rawResult.inboundTopicId || rawResult.metadata?.inboundTopicId || rawResult.state?.inboundTopicId; const outboundTopicId = rawResult.outboundTopicId || rawResult.metadata?.outboundTopicId || rawResult.state?.outboundTopicId; const profileTopicId = rawResult.profileTopicId || rawResult.metadata?.profileTopicId || rawResult.state?.profileTopicId; const privateKey = rawResult.privateKey || rawResult.metadata?.privateKey; if (accountId && inboundTopicId && outboundTopicId && this.specificArgs) { const registeredAgent: RegisteredAgent = { name: this.specificArgs.name, accountId, inboundTopicId, outboundTopicId, profileTopicId, privateKey, }; const hcs10Builder = this.getServiceBuilder() as HCS10Builder; const stateManager = hcs10Builder.getStateManager(); if (stateManager) { stateManager.setCurrentAgent(registeredAgent); if (stateManager.persistAgentData) { const prefix = this.specificArgs.persistence?.prefix || this.specificArgs.name.toUpperCase().replace(/[^A-Z0-9]/g, '_'); stateManager .persistAgentData(registeredAgent, { type: 'env-file', prefix: prefix, }) .catch(() => {}); } } } } }