@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.
333 lines (332 loc) • 12.5 kB
JavaScript
import { HCS10Client as HCS10Client$1, Logger, AgentBuilder, AIAgentCapability, InboundTopicType } from "@hashgraphonline/standards-sdk";
import { encryptMessage } from "./standards-agent-kit.es41.js";
class HCS10Client {
constructor(operatorId, operatorPrivateKey, network, options) {
this.standardClient = new HCS10Client$1({
network,
operatorId,
operatorPrivateKey,
guardedRegistryBaseUrl: options?.registryUrl,
logLevel: options?.logLevel
});
this.guardedRegistryBaseUrl = options?.registryUrl || "";
this.useEncryption = options?.useEncryption || false;
const shouldSilence = process.env.DISABLE_LOGGING === "true";
this.logger = new Logger({
level: options?.logLevel || "info",
silent: shouldSilence
});
}
getOperatorId() {
const operator = this.standardClient.getClient().operatorAccountId;
if (!operator) {
throw new Error("Operator Account ID not configured in standard client.");
}
return operator.toString();
}
getNetwork() {
return this.standardClient.getNetwork();
}
async handleConnectionRequest(inboundTopicId, requestingAccountId, connectionRequestId, feeConfig) {
try {
const result = await this.standardClient.handleConnectionRequest(
inboundTopicId,
requestingAccountId,
connectionRequestId,
feeConfig
);
return result;
} catch (error) {
this.logger.error(
`Error handling connection request #${connectionRequestId} for topic ${inboundTopicId}:`,
error
);
throw new Error(
`Failed to handle connection request: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Retrieves the profile for a given account ID using the standard SDK.
*/
async getAgentProfile(accountId) {
return this.standardClient.retrieveProfile(accountId);
}
/**
* Exposes the standard SDK's submitConnectionRequest method.
*/
async submitConnectionRequest(inboundTopicId, memo) {
return this.standardClient.submitConnectionRequest(
inboundTopicId,
memo
);
}
/**
* Exposes the standard SDK's waitForConnectionConfirmation method.
*/
async waitForConnectionConfirmation(outboundTopicId, connectionRequestId, maxAttempts = 60, delayMs = 2e3) {
return this.standardClient.waitForConnectionConfirmation(
outboundTopicId,
connectionRequestId,
maxAttempts,
delayMs
);
}
/**
* Creates and registers an agent using the standard SDK's HCS10Client.
* This handles account creation, key generation, topic setup, and registration.
*
* When metadata includes fee configuration:
* 1. The properties.feeConfig will be passed to the AgentBuilder
* 2. The properties.inboundTopicType will be set to FEE_BASED
* 3. The SDK's createAndRegisterAgent will apply the fees to the agent's inbound topic
*
* @param metadata - The agent's metadata, potentially including pfpBuffer, pfpFileName,
* and fee configuration in properties.feeConfig
* @returns The registration result from the standard SDK, containing accountId, keys, topics etc.
*/
async createAndRegisterAgent(metadata) {
const builder = new AgentBuilder().setName(metadata.name).setBio(metadata.description || "").setCapabilities(
metadata.capabilities ? metadata.capabilities : [AIAgentCapability.TEXT_GENERATION]
).setType(metadata.type || "autonomous").setModel(metadata.model || "agent-model-2024").setNetwork(this.getNetwork()).setInboundTopicType(InboundTopicType.PUBLIC);
if (metadata?.feeConfig) {
builder.setInboundTopicType(InboundTopicType.FEE_BASED);
builder.setFeeConfig(metadata.feeConfig);
}
if (metadata.pfpBuffer && metadata.pfpFileName) {
if (metadata.pfpBuffer.byteLength === 0) {
this.logger.warn(
"Provided PFP buffer is empty. Skipping profile picture."
);
} else {
this.logger.info(
`Setting profile picture: ${metadata.pfpFileName} (${metadata.pfpBuffer.byteLength} bytes)`
);
builder.setProfilePicture(metadata.pfpBuffer, metadata.pfpFileName);
}
} else {
this.logger.warn(
"Profile picture not provided in metadata. Agent creation might fail if required by the underlying SDK builder."
);
}
if (metadata.social) {
Object.entries(metadata.social).forEach(([platform, handle]) => {
builder.addSocial(platform, handle);
});
}
if (metadata.properties) {
Object.entries(metadata.properties).forEach(([key, value]) => {
builder.addProperty(key, value);
});
}
try {
const hasFees = Boolean(metadata?.feeConfig);
const result = await this.standardClient.createAndRegisterAgent(builder, {
initialBalance: hasFees ? 50 : void 0
});
if (result?.metadata?.inboundTopicId && result?.metadata?.outboundTopicId) {
this.agentChannels = {
inboundTopicId: result.metadata.inboundTopicId,
outboundTopicId: result.metadata.outboundTopicId
};
}
return result;
} catch (error) {
this.logger.error("Error during agent creation/registration:", error);
throw new Error(
`Failed to create/register agent: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Sends a structured HCS-10 message to the specified topic using the standard SDK client.
* Handles potential inscription for large messages.
*
* @param topicId - The target topic ID (likely a connection topic).
* @param operatorId - The operator ID string (e.g., "inboundTopic@accountId").
* @param data - The actual message content/data.
* @param memo - Optional memo for the message.
* @param submitKey - Optional private key for topics requiring specific submit keys.
* @returns A confirmation status string from the transaction receipt.
*/
async sendMessage(topicId, data, memo, submitKey) {
if (this.useEncryption) {
data = encryptMessage(data);
}
try {
const messageResponse = await this.standardClient.sendMessage(
topicId,
data,
memo,
submitKey
);
return messageResponse.topicSequenceNumber?.toNumber();
} catch (error) {
this.logger.error(`Error sending message to topic ${topicId}:`, error);
throw new Error(
`Failed to send message: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Retrieves messages from a topic using the standard SDK client.
*
* @param topicId - The topic ID to get messages from.
* @returns Messages from the topic, mapped to the expected format.
*/
async getMessages(topicId) {
try {
const result = await this.standardClient.getMessages(topicId);
const mappedMessages = result.messages.map((sdkMessage) => {
const timestamp = sdkMessage?.created?.getTime() || 0;
return {
...sdkMessage,
timestamp,
data: sdkMessage.data,
sequence_number: sdkMessage.sequence_number
};
});
mappedMessages.sort(
(a, b) => a.timestamp - b.timestamp
);
return { messages: mappedMessages };
} catch (error) {
this.logger.error(`Error getting messages from topic ${topicId}:`, error);
return { messages: [] };
}
}
async getMessageStream(topicId) {
const result = this.standardClient.getMessageStream(topicId);
return result;
}
/**
* Retrieves content from an inscribed message using the standard SDK client.
* @param inscriptionIdOrData - The inscription ID (hcs://...) or potentially raw data string.
* @returns The resolved message content.
*/
async getMessageContent(inscriptionIdOrData) {
try {
const content = await this.standardClient.getMessageContent(
inscriptionIdOrData
);
return content;
} catch (error) {
this.logger.error(
`Error retrieving message content for: ${inscriptionIdOrData}`,
error
);
throw new Error(
`Failed to retrieve message content: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Retrieves the inbound topic ID associated with the current operator.
* This typically involves fetching the operator's own HCS-10 profile.
* @returns A promise that resolves to the operator's inbound topic ID.
* @throws {Error} If the operator ID cannot be determined or the profile/topic cannot be retrieved.
*/
async getInboundTopicId() {
try {
const operatorId = this.getOperatorId();
this.logger.info(
`[HCS10Client] Retrieving profile for operator ${operatorId} to find inbound topic...`
);
const profileResponse = await this.getAgentProfile(operatorId);
if (profileResponse.success && profileResponse.topicInfo?.inboundTopic) {
this.logger.info(
`[HCS10Client] Found inbound topic for operator ${operatorId}: ${profileResponse.topicInfo.inboundTopic}`
);
return profileResponse.topicInfo.inboundTopic;
} else {
throw new Error(
`Could not retrieve inbound topic from profile for ${operatorId}. Profile success: ${profileResponse.success}, Error: ${profileResponse.error}`
);
}
} catch (error) {
this.logger.error(
`[HCS10Client] Error fetching operator's inbound topic ID (${this.getOperatorId()}):`,
error
);
const operatorId = this.getOperatorId();
let detailedMessage = `Failed to get inbound topic ID for operator ${operatorId}.`;
if (error instanceof Error && error.message.includes("does not have a valid HCS-11 memo")) {
detailedMessage += ` The account profile may not exist or is invalid. Please ensure this operator account (${operatorId}) is registered as an HCS-10 agent. You might need to register it first (e.g., using the 'register_agent' tool or SDK function).`;
} else if (error instanceof Error) {
detailedMessage += ` Reason: ${error.message}`;
} else {
detailedMessage += ` Unexpected error: ${String(error)}`;
}
throw new Error(detailedMessage);
}
}
/**
* Retrieves the configured operator account ID and private key.
* Required by tools needing to identify the current agent instance.
*/
getAccountAndSigner() {
const result = this.standardClient.getAccountAndSigner();
return {
accountId: result.accountId,
signer: result.signer
};
}
/**
* Retrieves the outbound topic ID for the current operator.
* Fetches the operator's profile if necessary.
* @returns The outbound topic ID string.
* @throws If the outbound topic cannot be determined.
*/
async getOutboundTopicId() {
const operatorId = this.getOperatorId();
const profile = await this.getAgentProfile(operatorId);
if (profile.success && profile.topicInfo?.outboundTopic) {
return profile.topicInfo.outboundTopic;
} else {
throw new Error(
`Could not retrieve outbound topic from profile for ${operatorId}. Profile success: ${profile.success}, Error: ${profile.error}`
);
}
}
setClient(accountId, privateKey) {
this.standardClient = new HCS10Client$1({
network: this.getNetwork(),
operatorId: accountId,
operatorPrivateKey: privateKey,
guardedRegistryBaseUrl: this.guardedRegistryBaseUrl
});
return this.standardClient;
}
/**
* Validates that the operator account exists and has proper access for agent operations
*/
async validateOperator(options) {
try {
this.setClient(options.accountId, options.privateKey);
const operatorId = this.getOperatorId();
return {
isValid: true,
operator: { accountId: operatorId }
};
} catch (error) {
this.logger.error(`Validation error: ${error}`);
return {
isValid: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
async initializeWithValidation(options) {
const validationResult = await this.validateOperator(options);
if (validationResult.isValid) {
if (options.stateManager) {
options.stateManager.initializeConnectionsManager(this.standardClient);
}
}
return validationResult;
}
}
export {
HCS10Client
};
//# sourceMappingURL=standards-agent-kit.es6.js.map