@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
1,386 lines (1,385 loc) • 64.2 kB
JavaScript
import { BaseServiceBuilder } from "hedera-agent-kit";
import { SignerProviderRegistry } from "./standards-agent-kit.es3.js";
import { Logger, HCS10Client as HCS10Client$1, AgentBuilder, AIAgentCapability, InboundTopicType, FeeConfigBuilder } from "@hashgraphonline/standards-sdk";
import fs from "fs";
import path__default from "path";
import axios from "axios";
const NOT_INITIALIZED_ERROR = "ConnectionsManager not initialized";
class HCS10Builder extends BaseServiceBuilder {
constructor(hederaKit, stateManager, options) {
super(hederaKit);
this.stateManager = stateManager;
const network = this.hederaKit.client.network;
this.network = network.toString().includes("mainnet") ? "mainnet" : "testnet";
const operatorId = this.hederaKit.signer.getAccountId().toString();
const operatorPrivateKey = this.hederaKit.signer?.getOperatorPrivateKey() ? this.hederaKit.signer.getOperatorPrivateKey().toStringRaw() : "";
this.sdkLogger = (() => {
try {
if (typeof Logger === "function") {
return new Logger({
module: "HCS10Builder",
level: options?.logLevel || "info"
});
}
} catch {
}
return {
debug: () => {
},
info: () => {
},
warn: () => {
},
error: () => {
},
trace: () => {
},
setLogLevel: () => {
},
getLevel: () => "info",
setSilent: () => {
},
setModule: () => {
}
};
})();
this.standardClient = new HCS10Client$1({
network: this.network,
operatorId,
operatorPrivateKey,
logLevel: options?.logLevel || "info"
});
if (this.stateManager) {
this.stateManager.initializeConnectionsManager(this.standardClient);
}
}
/**
* Get the operator account ID
*/
getOperatorId() {
const operator = this.standardClient.getClient().operatorAccountId;
if (!operator) {
throw new Error("Operator Account ID not configured in standard client.");
}
return operator.toString();
}
/**
* Get the network type
*/
getNetwork() {
return this.network;
}
/**
* Get state manager instance
*/
getStateManager() {
return this.stateManager;
}
/**
* Get account and signer information
*/
getAccountAndSigner() {
const result = this.standardClient.getAccountAndSigner();
return {
accountId: result.accountId,
signer: result.signer
};
}
/**
* Get the inbound topic ID for the current operator
*/
async getInboundTopicId() {
try {
const operatorId = this.getOperatorId();
this.logger.info(
`[HCS10Builder] Retrieving profile for operator ${operatorId} to find inbound topic...`
);
const profileResponse = await this.getAgentProfile(operatorId);
if (profileResponse.success && profileResponse.topicInfo?.inboundTopic) {
this.logger.info(
`[HCS10Builder] 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(
`[HCS10Builder] 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);
}
}
/**
* Get agent profile
*/
async getAgentProfile(accountId) {
try {
return await this.standardClient.retrieveProfile(accountId);
} catch (error) {
this.logger.error(
`[HCS10Builder] Error retrieving agent profile for account ${accountId}:`,
error
);
throw error;
}
}
/**
* Submit connection request
*/
async submitConnectionRequest(inboundTopicId, memo) {
const start = SignerProviderRegistry.startHCSDelegate;
const exec = SignerProviderRegistry.walletExecutor;
const preferWallet = SignerProviderRegistry.preferWalletOnly;
const network = this.network;
try {
const { ByteBuildRegistry } = await import("./standards-agent-kit.es4.js");
if (exec && ByteBuildRegistry.has("submitConnectionRequest")) {
const built = await ByteBuildRegistry.build("submitConnectionRequest", this.hederaKit, { inboundTopicId, memo });
if (built && built.transactionBytes) {
const { transactionId } = await exec(built.transactionBytes, network);
return { transactionId };
}
}
} catch {
}
if (start && exec) {
try {
const request = { inboundTopicId, memo };
const { transactionBytes } = await start("submitConnectionRequest", request, network);
const { transactionId } = await exec(transactionBytes, network);
return { transactionId };
} catch (err) {
if (preferWallet) {
const e = new Error(`wallet_submit_failed: ${err instanceof Error ? err.message : String(err)}`);
e.code = "wallet_submit_failed";
throw e;
}
}
} else if (preferWallet) {
const e = new Error("wallet_unavailable: connect a wallet or configure StartHCSDelegate and WalletExecutor");
e.code = "wallet_unavailable";
throw e;
}
return this.standardClient.submitConnectionRequest(
inboundTopicId,
memo
);
}
/**
* Handle connection request
*/
async handleConnectionRequest(inboundTopicId, requestingAccountId, connectionRequestId, feeConfig) {
try {
const start = SignerProviderRegistry.startHCSDelegate;
const exec = SignerProviderRegistry.walletExecutor;
const preferWallet = SignerProviderRegistry.preferWalletOnly;
const network = this.network;
if (start && exec) {
try {
const request = {
inboundTopicId,
requestingAccountId,
connectionRequestId,
feeConfig: feeConfig ? "configured" : void 0
};
const { transactionBytes } = await start("handleConnectionRequest", request, network);
const { transactionId } = await exec(transactionBytes, network);
const minimal = {
success: true,
transactionId
};
return minimal;
} catch (err) {
if (preferWallet) {
const e = new Error(`wallet_submit_failed: ${err instanceof Error ? err.message : String(err)}`);
e.code = "wallet_submit_failed";
throw e;
}
}
} else if (preferWallet) {
const e = new Error("wallet_unavailable: connect a wallet or configure StartHCSDelegate and WalletExecutor");
e.code = "wallet_unavailable";
throw e;
}
const result = await this.standardClient.handleConnectionRequest(
inboundTopicId,
requestingAccountId,
connectionRequestId,
feeConfig
);
if (result && result.connectionTopicId && typeof result.connectionTopicId === "object" && "toString" in result.connectionTopicId) {
result.connectionTopicId = result.connectionTopicId.toString();
}
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)}`
);
}
}
/**
* Send message to a topic
*/
async sendMessage(topicId, data, memo) {
if (topicId && typeof topicId === "object" && "toString" in topicId) {
topicId = topicId?.toString();
}
if (!topicId || typeof topicId !== "string") {
throw new Error(
`Invalid topic ID provided to sendMessage: ${JSON.stringify(topicId)}`
);
}
try {
let prevMaxSeq = 0;
try {
const prev = await this.getMessages(topicId);
prevMaxSeq = prev.messages.reduce((max, m) => Math.max(max, m.sequence_number || 0), 0);
} catch {
}
const start = SignerProviderRegistry.startHCSDelegate;
const exec = SignerProviderRegistry.walletExecutor;
const preferWallet = SignerProviderRegistry.preferWalletOnly;
const network = this.network;
try {
const { ByteBuildRegistry } = await import("./standards-agent-kit.es4.js");
if (exec && ByteBuildRegistry.has("sendMessage")) {
const built = await ByteBuildRegistry.build("sendMessage", this.hederaKit, { topicId, data, memo });
if (built && built.transactionBytes) {
const { transactionId } = await exec(built.transactionBytes, network);
const sequenceNumber = await this.pollForNewSequence(topicId, prevMaxSeq);
return {
sequenceNumber,
receipt: { transactionId },
transactionId
};
}
}
} catch {
}
if (start && exec) {
try {
const request = { topicId, data, memo };
const { transactionBytes } = await start("sendMessage", request, network);
const { transactionId } = await exec(transactionBytes, network);
const sequenceNumber = await this.pollForNewSequence(topicId, prevMaxSeq);
return {
sequenceNumber,
receipt: { transactionId },
transactionId
};
} catch (err) {
if (preferWallet) {
const e = new Error(`wallet_submit_failed: ${err instanceof Error ? err.message : String(err)}`);
e.code = "wallet_submit_failed";
throw e;
}
}
} else if (preferWallet) {
const e = new Error("wallet_unavailable: connect a wallet or configure StartHCSDelegate and WalletExecutor");
e.code = "wallet_unavailable";
throw e;
}
const messageResponse = await this.standardClient.sendMessage(
topicId,
data,
memo,
void 0
);
return {
sequenceNumber: messageResponse.topicSequenceNumber?.toNumber(),
receipt: messageResponse,
transactionId: "transactionId" in messageResponse ? messageResponse.transactionId?.toString() : void 0
};
} 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)}`
);
}
}
/**
* Get messages from a topic
*/
async getMessages(topicId) {
if (topicId && typeof topicId === "object" && "toString" in topicId) {
topicId = topicId?.toString();
}
if (!topicId || typeof topicId !== "string") {
throw new Error(
`Invalid topic ID provided to getMessages: ${JSON.stringify(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,
p: "hcs-10"
};
}
);
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: [] };
}
}
/**
* Get message stream from a topic
*/
async getMessageStream(topicId) {
if (topicId && typeof topicId === "object" && "toString" in topicId) {
topicId = topicId?.toString();
}
if (!topicId || typeof topicId !== "string") {
throw new Error(
`Invalid topic ID provided to getMessageStream: ${JSON.stringify(
topicId
)}`
);
}
return this.standardClient.getMessageStream(topicId);
}
/**
* Get 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)}`
);
}
}
/**
* Get the standard client instance (for compatibility)
*/
getStandardClient() {
return this.standardClient;
}
/**
* Load profile picture from URL or file path
*/
async loadProfilePicture(profilePicture) {
try {
if (!profilePicture) {
return null;
}
if (typeof profilePicture === "string") {
const isUrl = profilePicture.startsWith("http://") || profilePicture.startsWith("https://");
if (isUrl) {
this.logger.info(
`Loading profile picture from URL: ${profilePicture}`
);
const response = await axios.get(profilePicture, {
responseType: "arraybuffer"
});
const buffer = Buffer.from(response.data);
const urlPathname = new URL(profilePicture).pathname;
const filename = path__default.basename(urlPathname) || "profile.png";
return { buffer, filename };
} else {
if (!fs.existsSync(profilePicture)) {
this.logger.warn(
`Profile picture file not found: ${profilePicture}`
);
return null;
}
this.logger.info(
`Loading profile picture from file: ${profilePicture}`
);
const buffer = fs.readFileSync(profilePicture);
const filename = path__default.basename(profilePicture);
return { buffer, filename };
}
} else if (profilePicture.url) {
this.logger.info(
`Loading profile picture from URL: ${profilePicture.url}`
);
const response = await axios.get(profilePicture.url, {
responseType: "arraybuffer"
});
const buffer = Buffer.from(response.data);
const filename = profilePicture.filename || "profile.png";
return { buffer, filename };
} else if (profilePicture.path) {
if (!fs.existsSync(profilePicture.path)) {
this.logger.warn(
`Profile picture file not found: ${profilePicture.path}`
);
return null;
}
this.logger.info(
`Loading profile picture from file: ${profilePicture.path}`
);
const buffer = fs.readFileSync(profilePicture.path);
const filename = profilePicture.filename || path__default.basename(profilePicture.path);
return { buffer, filename };
}
return null;
} catch (error) {
this.logger.error("Failed to load profile picture:", error);
return null;
}
}
async pollForNewSequence(topicId, prevMax) {
const maxAttempts = 10;
const delayMs = 1e3;
for (let i = 0; i < maxAttempts; i++) {
try {
const res = await this.getMessages(topicId);
const maxSeq = res.messages.reduce((m, msg) => Math.max(m, msg.sequence_number || 0), prevMax);
if (maxSeq > prevMax) return maxSeq;
} catch {
}
await new Promise((r) => setTimeout(r, delayMs));
}
return void 0;
}
/**
* Create and register an agent
*/
async createAndRegisterAgent(data) {
const builder = new AgentBuilder().setName(data.name).setBio(data.bio || "").setCapabilities(
data.capabilities || [AIAgentCapability.TEXT_GENERATION]
).setType(data.type || "autonomous").setModel(data.model || "agent-model-2024").setNetwork(this.getNetwork()).setInboundTopicType(InboundTopicType.PUBLIC);
if (data.alias) {
builder.setAlias(data.alias);
}
if (data.creator) {
builder.setCreator(data.creator);
}
if (data?.feeConfig) {
builder.setInboundTopicType(InboundTopicType.FEE_BASED);
builder.setFeeConfig(data.feeConfig);
}
if (data.existingProfilePictureTopicId) {
builder.setExistingProfilePicture(data.existingProfilePictureTopicId);
} else if (data.pfpBuffer && data.pfpFileName) {
if (data.pfpBuffer.byteLength === 0) {
this.logger.warn(
"Provided PFP buffer is empty. Skipping profile picture."
);
} else {
this.logger.info(
`Setting profile picture: ${data.pfpFileName} (${data.pfpBuffer.byteLength} bytes)`
);
builder.setProfilePicture(data.pfpBuffer, data.pfpFileName);
}
} else {
this.logger.warn(
"Profile picture not provided. Agent creation might fail if required by the underlying SDK builder."
);
}
if (data.socials) {
Object.entries(data.socials).forEach(([platform, handle]) => {
builder.addSocial(platform, handle);
});
}
if (data.properties) {
Object.entries(data.properties).forEach(([key, value]) => {
builder.addProperty(key, value);
});
}
try {
const hasFees = Boolean(data?.feeConfig);
const result = await this.standardClient.createAndRegisterAgent(builder, {
initialBalance: hasFees ? 50 : 10
});
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)}`
);
}
}
/**
* Register a new HCS-10 agent
* Note: This performs multiple transactions and requires directExecution mode
*/
async registerAgent(params) {
this.clearNotes();
if (this.hederaKit.operationalMode === "returnBytes") {
throw new Error(
"Agent registration requires multiple transactions and cannot be performed in returnBytes mode. Please use autonomous mode."
);
}
try {
let profilePictureData = null;
if (params.profilePicture) {
profilePictureData = await this.loadProfilePicture(
params.profilePicture
);
}
const registrationData = {
name: params.name,
...params.bio !== void 0 && { bio: params.bio },
...params.alias !== void 0 && { alias: params.alias },
...params.type !== void 0 && { type: params.type },
...params.model !== void 0 && { model: params.model },
...params.capabilities !== void 0 && {
capabilities: params.capabilities
},
...params.creator !== void 0 && { creator: params.creator },
...params.socials !== void 0 && { socials: params.socials },
...params.properties !== void 0 && {
properties: params.properties
},
...params.existingProfilePictureTopicId !== void 0 && {
existingProfilePictureTopicId: params.existingProfilePictureTopicId
},
...profilePictureData?.buffer !== void 0 && {
pfpBuffer: profilePictureData.buffer
},
...profilePictureData?.filename !== void 0 && {
pfpFileName: profilePictureData.filename
}
};
if (params.hbarFee && params.hbarFee > 0) {
const feeConfigBuilder = new FeeConfigBuilder({
network: this.network,
logger: this.sdkLogger
});
const { accountId: collectorAccountId } = this.getAccountAndSigner();
if (!collectorAccountId) {
throw new Error("Could not determine account ID for fee collection.");
}
this.addNote(
`Setting the operator account (${collectorAccountId}) as the fee collector since no specific collector was provided.`
);
const effectiveExemptIds = params.exemptAccountIds?.filter(
(id) => id !== collectorAccountId && id.startsWith("0.0")
) || [];
registrationData.feeConfig = feeConfigBuilder.addHbarFee(
params.hbarFee,
collectorAccountId,
effectiveExemptIds
);
}
const preferWallet = SignerProviderRegistry.preferWalletOnly;
const browserClient = SignerProviderRegistry.getBrowserHCSClient(this.network);
if (browserClient) {
try {
const aBuilder = new AgentBuilder().setNetwork(this.network).setName(registrationData.name).setAlias(registrationData.alias || `${registrationData.name}-${Date.now()}`).setBio(registrationData.bio || "").setType(registrationData.type || "autonomous").setModel(registrationData.model || "agent-model-2024");
if (registrationData.capabilities?.length) {
aBuilder.setCapabilities(registrationData.capabilities);
}
if (registrationData.creator) aBuilder.setCreator(registrationData.creator);
if (registrationData.existingProfilePictureTopicId) {
aBuilder.setExistingProfilePicture(registrationData.existingProfilePictureTopicId);
}
if (registrationData.socials) {
Object.entries(registrationData.socials).forEach(([platform, handle]) => {
if (handle && typeof handle === "string") {
aBuilder.addSocial(platform, handle);
}
});
}
if (registrationData.properties) {
Object.entries(registrationData.properties).forEach(([key, value]) => {
if (value != null) aBuilder.addProperty(key, value);
});
}
const resp = await browserClient.create(aBuilder, {
progressCallback: (_) => {
},
updateAccountMemo: true
});
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: { result: resp, name: registrationData.name }
};
return this;
} catch (walletErr) {
if (preferWallet) {
throw new Error(`wallet_registration_failed: ${walletErr instanceof Error ? walletErr.message : String(walletErr)}`);
}
}
} else if (preferWallet) {
throw new Error("wallet_unavailable: BrowserHCSClient factory not provided");
}
const result = await this.createAndRegisterAgent(registrationData);
this.executeResult = {
success: true,
transactionId: result.transactionId,
receipt: void 0,
scheduleId: void 0,
rawResult: {
...result,
name: params.name,
accountId: result?.metadata?.accountId || result.state?.agentMetadata?.accountId
}
};
} catch (error) {
this.logger.error("Failed to register agent:", error);
throw error;
}
return this;
}
/**
* Initiate a connection to another agent
*/
async initiateConnection(params) {
this.clearNotes();
try {
const targetProfile = await this.getAgentProfile(params.targetAccountId);
if (!targetProfile.success || !targetProfile.topicInfo?.inboundTopic) {
throw new Error(
`Could not retrieve inbound topic for target account ${params.targetAccountId}`
);
}
const targetInboundTopicId = targetProfile.topicInfo.inboundTopic;
let memo;
if (params.memo !== void 0) {
memo = params.memo;
} else {
memo = params.disableMonitor ? "false" : "true";
this.addNote(
`No custom memo was provided. Using default memo '${memo}' based on monitoring preference.`
);
}
if (!params.disableMonitor) {
this.addNote(
`Monitoring will be enabled for this connection request as disableMonitor was not specified.`
);
}
const result = await this.submitConnectionRequest(
targetInboundTopicId,
memo
);
this.executeResult = {
success: true,
transactionId: "transactionId" in result ? result.transactionId?.toString() : void 0,
receipt: result,
scheduleId: void 0,
rawResult: {
targetAccountId: params.targetAccountId,
targetInboundTopicId,
connectionRequestSent: true,
monitoringEnabled: !params.disableMonitor,
...result
}
};
} catch (error) {
this.logger.error("Failed to initiate connection:", error);
throw error;
}
return this;
}
/**
* Accept a connection request
* Note: This performs multiple transactions and requires directExecution mode
*/
async acceptConnection(params) {
this.clearNotes();
if (this.hederaKit.operationalMode === "returnBytes") {
throw new Error(
"Accepting connections requires multiple transactions and cannot be performed in returnBytes mode. Please use autonomous mode."
);
}
try {
const currentAgent = this.stateManager?.getCurrentAgent();
if (!currentAgent) {
throw new Error(
"Cannot accept connection request. No agent is currently active. Please register or select an agent first."
);
}
const connectionsManager = this.stateManager?.getConnectionsManager();
if (!connectionsManager) {
throw new Error(NOT_INITIALIZED_ERROR);
}
await connectionsManager.fetchConnectionData(currentAgent.accountId);
const allRequests = [
...connectionsManager.getPendingRequests(),
...connectionsManager.getConnectionsNeedingConfirmation()
];
const request = allRequests.find(
(r) => r.uniqueRequestKey === params.requestKey || r.connectionRequestId?.toString() === params.requestKey || r.inboundRequestId?.toString() === params.requestKey
);
if (!request) {
throw new Error(
`Request with key ${params.requestKey} not found or no longer pending.`
);
}
if (!request.needsConfirmation || !request.inboundRequestId) {
throw new Error(
`Request with key ${params.requestKey} is not an inbound request that can be accepted.`
);
}
const targetAccountId = request.targetAccountId;
const inboundRequestId = request.inboundRequestId;
let feeConfig;
if (params.hbarFee && params.hbarFee > 0) {
const feeConfigBuilder = new FeeConfigBuilder({
network: this.network,
logger: this.sdkLogger
});
const { accountId: collectorAccountId } = this.getAccountAndSigner();
if (!collectorAccountId) {
throw new Error("Could not determine account ID for fee collection.");
}
this.addNote(
`Setting the operator account (${collectorAccountId}) as the fee collector since no specific collector was provided.`
);
const effectiveExemptIds = params.exemptAccountIds?.filter(
(id) => id !== collectorAccountId && id.startsWith("0.0")
) || [];
feeConfig = feeConfigBuilder.addHbarFee(
params.hbarFee,
collectorAccountId,
effectiveExemptIds
);
}
const inboundTopicId = await this.getInboundTopicId();
const confirmationResult = await this.handleConnectionRequest(
inboundTopicId,
targetAccountId,
inboundRequestId,
feeConfig
);
let connectionTopicId = confirmationResult?.connectionTopicId;
if (connectionTopicId && typeof connectionTopicId === "object" && "toString" in connectionTopicId) {
connectionTopicId = connectionTopicId?.toString();
}
if (!connectionTopicId || typeof connectionTopicId !== "string") {
try {
const refreshed = await this.stateManager?.getConnectionsManager()?.fetchConnectionData(currentAgent.accountId);
const established = (refreshed || []).find(
(c) => c.targetAccountId === targetAccountId && c.status === "established"
);
if (established?.connectionTopicId) {
connectionTopicId = established.connectionTopicId;
}
} catch (e) {
this.logger.debug("Could not refresh connections after acceptance to derive topic id:", e);
}
}
if (!connectionTopicId || typeof connectionTopicId !== "string") {
throw new Error(
`Failed to create connection topic. Got: ${JSON.stringify(
connectionTopicId
)}`
);
}
if (this.stateManager) {
const targetAgentName = request.targetAgentName || `Agent ${targetAccountId}`;
if (!request.targetAgentName) {
this.addNote(
`No agent name was provided in the connection request, using default name 'Agent ${targetAccountId}'.`
);
}
let targetInboundTopicId = request.targetInboundTopicId || "";
if (!targetInboundTopicId) {
try {
const targetProfile = await this.getAgentProfile(targetAccountId);
if (targetProfile.success && targetProfile.topicInfo?.inboundTopic) {
targetInboundTopicId = targetProfile.topicInfo.inboundTopic;
}
} catch (profileError) {
this.logger.warn(
`Could not fetch profile for ${targetAccountId}:`,
profileError
);
}
}
const newConnection = {
connectionId: `conn-${Date.now()}`,
targetAccountId,
targetAgentName,
targetInboundTopicId,
connectionTopicId,
status: "established",
created: /* @__PURE__ */ new Date()
};
this.stateManager.addActiveConnection(newConnection);
connectionsManager.markConnectionRequestProcessed(
request.targetInboundTopicId || "",
inboundRequestId
);
}
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
targetAccountId,
connectionTopicId,
feeConfigured: !!params.hbarFee,
hbarFee: params.hbarFee || 0,
confirmationResult
}
};
} catch (error) {
this.logger.error("Failed to accept connection:", error);
throw error;
}
return this;
}
/**
* Send a message using HCS (for operations that need direct topic access)
*/
async sendHCS10Message(params) {
this.clearNotes();
try {
const result = await this.sendMessage(params.topicId, params.message);
this.executeResult = {
success: true,
transactionId: result.transactionId,
receipt: result.receipt,
scheduleId: void 0,
rawResult: result
};
this.addNote(`Message sent to topic ${params.topicId}.`);
} catch (error) {
this.logger.error("Failed to send message:", error);
throw error;
}
return this;
}
/**
* Send a message to a connected account with optional response monitoring
*/
async sendMessageToConnection(params) {
this.clearNotes();
if (!this.stateManager) {
throw new Error(
"StateManager is required to send messages to connections"
);
}
try {
const currentAgent = this.stateManager.getCurrentAgent();
if (!currentAgent) {
throw new Error(
"Cannot send message. No agent is currently active. Please register or select an agent first."
);
}
let connection;
const identifier = params.targetIdentifier;
if (identifier.includes("@")) {
const parts = identifier.split("@");
if (parts.length === 2) {
const accountId = parts[1];
connection = this.stateManager.getConnectionByIdentifier(accountId);
if (!connection) {
this.addNote(
`Could not find connection using request key '${identifier}', extracted account ID '${accountId}'.`
);
}
}
}
if (!connection) {
connection = this.stateManager.getConnectionByIdentifier(identifier);
}
if (!connection && !identifier.startsWith("0.0.") && /^\d+$/.test(identifier)) {
const accountIdWithPrefix = `0.0.${identifier}`;
connection = this.stateManager.getConnectionByIdentifier(accountIdWithPrefix);
if (connection) {
this.addNote(
`Found connection using account ID with prefix: ${accountIdWithPrefix}`
);
}
}
if (!connection && /^[1-9]\d*$/.test(identifier)) {
const connections = this.stateManager.listConnections();
const index = parseInt(identifier) - 1;
if (index >= 0 && index < connections.length) {
connection = connections[index];
if (connection) {
this.addNote(
`Found connection by index ${identifier}: ${connection.targetAccountId}`
);
}
}
}
if (!connection) {
const connections = this.stateManager.listConnections();
const availableIds = connections.map(
(c, i) => `${i + 1}. ${c.targetAccountId} (Topic: ${c.connectionTopicId})`
);
let errorMsg = `Connection not found for identifier: "${identifier}"
`;
errorMsg += `Available connections:
${availableIds.join("\n") || "No active connections"}`;
errorMsg += `
You can use:
`;
errorMsg += `- Connection number (e.g., "1", "2")
`;
errorMsg += `- Account ID (e.g., "0.0.6412936")
`;
errorMsg += `- Connection topic ID
`;
errorMsg += `Use 'list_connections' to see all active connections.`;
throw new Error(errorMsg);
}
let connectionTopicId = connection.connectionTopicId;
if (connectionTopicId && typeof connectionTopicId === "object" && "toString" in connectionTopicId) {
connectionTopicId = connectionTopicId?.toString();
}
if (!connectionTopicId || typeof connectionTopicId !== "string") {
throw new Error(
`Invalid connection topic ID for ${connection.targetAccountId}: ${JSON.stringify(
connectionTopicId
)} (type: ${typeof connectionTopicId})`
);
}
const targetAgentName = connection.targetAgentName;
const operatorId = `${currentAgent.inboundTopicId}@${currentAgent.accountId}`;
let baseSeq = 0;
try {
const prev = await this.getMessages(connectionTopicId);
baseSeq = prev.messages.reduce((max, m) => Math.max(max, m.sequence_number || 0), 0);
} catch {
}
const messageResult = await this.sendMessage(
connectionTopicId,
params.message,
`Agent message from ${currentAgent.name}`
);
const effectiveSeq = messageResult.sequenceNumber ?? baseSeq;
if (effectiveSeq === 0) {
throw new Error("Failed to send message");
}
let reply = null;
if (!params.disableMonitoring) {
reply = await this.monitorResponses(
connectionTopicId,
operatorId,
effectiveSeq
);
} else {
this.addNote(
`Message sent successfully. Response monitoring was disabled.`
);
}
this.executeResult = {
success: true,
transactionId: messageResult.transactionId,
receipt: messageResult.receipt,
scheduleId: void 0,
rawResult: {
targetAgentName,
targetAccountId: connection.targetAccountId,
connectionTopicId,
sequenceNumber: messageResult.sequenceNumber,
reply,
monitoringEnabled: !params.disableMonitoring,
message: params.message,
messageResult
}
};
} catch (error) {
this.logger.error("Failed to send message to connection:", error);
throw error;
}
return this;
}
/**
* Monitor responses on a topic after sending a message
*/
async monitorResponses(topicId, operatorId, sequenceNumber) {
const maxAttempts = 30;
let attempts = 0;
while (attempts < maxAttempts) {
try {
const messages = await this.getMessageStream(topicId);
for (const message of messages.messages) {
if (message.sequence_number < sequenceNumber || message.operator_id === operatorId) {
continue;
}
const content = await this.getMessageContent(message.data || "");
return content;
}
} catch (error) {
this.logger.error(`Error monitoring responses: ${error}`);
}
await new Promise((resolve) => setTimeout(resolve, 4e3));
attempts++;
}
return null;
}
/**
* Start passive monitoring for incoming connection requests
* This method monitors continuously in the background
*/
async startPassiveConnectionMonitoring() {
this.clearNotes();
if (!this.stateManager) {
throw new Error("StateManager is required for passive monitoring");
}
const inboundTopicId = await this.getInboundTopicId();
this.logger.info(
`Starting passive connection monitoring on topic ${inboundTopicId}...`
);
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
inboundTopicId,
message: `Started monitoring inbound topic ${inboundTopicId} for connection requests in the background.`
}
};
return this;
}
/**
* Monitor for incoming connection requests
*/
async monitorConnections(params) {
this.clearNotes();
const {
acceptAll = false,
targetAccountId,
monitorDurationSeconds = 120,
hbarFees = [],
tokenFees = [],
exemptAccountIds = [],
defaultCollectorAccount
} = params;
if (!this.stateManager) {
throw new Error("StateManager is required for connection monitoring");
}
const currentAgent = this.stateManager.getCurrentAgent();
if (!currentAgent) {
throw new Error(
"Cannot monitor for connections. No agent is currently active."
);
}
const inboundTopicId = await this.getInboundTopicId();
const endTime = Date.now() + monitorDurationSeconds * 1e3;
const pollIntervalMs = 3e3;
let connectionRequestsFound = 0;
let acceptedConnections = 0;
const processedRequestIds = /* @__PURE__ */ new Set();
while (Date.now() < endTime) {
try {
const messagesResult = await this.getMessages(inboundTopicId);
const connectionRequests = messagesResult.messages.filter(
(msg) => msg.op === "connection_request" && typeof msg.sequence_number === "number"
);
for (const request of connectionRequests) {
const connectionRequestId = request.sequence_number;
if (!connectionRequestId || processedRequestIds.has(connectionRequestId)) {
continue;
}
const requestingAccountId = request.operator_id?.split("@")[1];
if (!requestingAccountId) {
continue;
}
connectionRequestsFound++;
if (targetAccountId && requestingAccountId !== targetAccountId) {
this.logger.info(
`Skipping request from ${requestingAccountId} (not target account)`
);
continue;
}
if (acceptAll || targetAccountId === requestingAccountId) {
this.logger.info(
`Accepting connection request from ${requestingAccountId}`
);
let feeConfig;
if (hbarFees.length > 0 || tokenFees.length > 0) {
const builder = new FeeConfigBuilder({
network: this.network,
logger: this.sdkLogger
});
for (const fee of hbarFees) {
const collectorAccount = fee.collectorAccount || defaultCollectorAccount || this.getOperatorId();
builder.addHbarFee(
fee.amount,
collectorAccount,
exemptAccountIds
);
}
for (const fee of tokenFees) {
const collectorAccount = fee.collectorAccount || defaultCollectorAccount || this.getOperatorId();
builder.addTokenFee(
fee.amount,
fee.tokenId,
collectorAccount,
void 0,
exemptAccountIds
);
}
feeConfig = builder;
}
await this.handleConnectionRequest(
inboundTopicId,
requestingAccountId,
connectionRequestId,
feeConfig
);
processedRequestIds.add(connectionRequestId);
acceptedConnections++;
}
}
} catch (error) {
this.logger.error("Error during connection monitoring:", error);
}
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
}
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
connectionRequestsFound,
acceptedConnections,
monitorDurationSeconds,
processedRequestIds: Array.from(processedRequestIds)
}
};
this.addNote(
`Monitoring completed. Found ${connectionRequestsFound} requests, accepted ${acceptedConnections}.`
);
return this;
}
/**
* Manage connection requests (list, view, or reject)
*/
async manageConnectionRequests(params) {
this.clearNotes();
if (!this.stateManager) {
throw new Error(
"StateManager is required for managing connection requests"
);
}
const currentAgent = this.stateManager.getCurrentAgent();
if (!currentAgent) {
throw new Error(
"Cannot manage connection requests. No agent is currently active."
);
}
const connectionsManager = this.stateManager.getConnectionsManager();
if (!connectionsManager) {
throw new Error(NOT_INITIALIZED_ERROR);
}
try {
const { accountId } = this.getAccountAndSigner();
await connectionsManager.fetchConnectionData(accountId);
const pendingRequests = connectionsManager.getPendingRequests();
const needsConfirmation = connectionsManager.getConnectionsNeedingConfirmation();
const allRequests = [...pendingRequests, ...needsConfirmation];
switch (params.action) {
case "list":
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
requests: allRequests.map((request, index) => ({
index: index + 1,
type: request.needsConfirmation ? "incoming" : "outgoing",
requestKey: request.uniqueRequestKey || `${request.connectionRequestId || request.inboundRequestId || "unknown"}`,
targetAccountId: request.targetAccountId,
targetAgentName: request.targetAgentName || `Agent ${request.targetAccountId}`,
created: request.created.toISOString(),
memo: request.memo,
bio: request.profileInfo?.bio
}))
}
};
break;
case "view": {
if (!params.requestKey) {
throw new Error("Request key is required for viewing a request");
}
const viewRequest = allRequests.find(
(r) => r.uniqueRequestKey === params.requestKey || r.connectionRequestId?.toString() === params.requestKey || r.inboundRequestId?.toString() === params.requestKey
);
if (!viewRequest) {
throw new Error(`Request with key ${params.requestKey} not found`);
}
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
request: {
type: viewRequest.needsConfirmation ? "incoming" : "outgoing",
requestKey: viewRequest.uniqueRequestKey || `${viewRequest.connectionRequestId || viewRequest.inboundRequestId || "unknown"}`,
targetAccountId: viewRequest.targetAccountId,
targetAgentName: viewRequest.targetAgentName || `Agent ${viewRequest.targetAccountId}`,
created: viewRequest.created.toISOString(),
memo: viewRequest.memo,
profileInfo: viewRequest.profileInfo
}
}
};
break;
}
case "reject": {
if (!params.requestKey) {
throw new Error("Request key is required for rejecting a request");
}
const rejectRequest = allRequests.find(
(r) => r.uniqueRequestKey === params.requestKey || r.connectionRequestId?.toString() === params.requestKey || r.inboundRequestId?.toString() === params.requestKey
);
if (!rejectRequest) {
throw new Error(`Request with key ${params.requestKey} not found`);
}
if (rejectRequest.inboundRequestId) {
connectionsManager.markConnectionRequestProcessed(
rejectRequest.targetInboundTopicId || "",
rejectRequest.inboundRequestId
);
} else if (rejectRequest.connectionRequestId) {
connectionsManager.markConnectionRequestProcessed(
rejectRequest.originTopicId || "",
rejectRequest.connectionRequestId
);
}
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
rejectedRequest: {
requestKey: params.requestKey,
targetAccountId: rejectRequest.targetAccountId,
targetAgentName: rejectRequest.targetAgentName || `Agent ${rejectRequest.targetAccountId}`
}
}
};
break;
}
}
} catch (error) {
this.logger.error("Failed to manage connection requests:", error);
throw error;
}
return this;
}
/**
* List unapproved connection requests
*/
async listUnapprovedConnectionRequests() {
this.clearNotes();
if (!this.stateManager) {
throw new Error(
"StateManager is required for listing connection requests"
);
}
const currentAgent = this.stateManager.getCurrentAgent();
if (!currentAgent) {
throw new Error(
"Cannot list connection requests. No agent is currently active."
);
}
try {
const inboundTopicId = await this.getInboundTopicId();
const messages = await this.getMessages(inboundTopicId);
const unapprovedRequests = messages.messages.filter(
(msg) => msg.op === "connection_request"
).map((msg) => ({
requestId: msg.sequence_number,
fromAccountId: msg.operator_id?.split("@")[1] || "unknown",
timestamp: msg.timestamp || new Date(msg?.created || "").getTime(),
memo: msg.m || "",
data: msg.data
})).filter(
(req) => req.fromAccountId !== "unknown"
);
this.executeResult = {
success: true,
transactionId: void 0,
receipt: void 0,
scheduleId: void 0,
rawResult: {
requests: unapprovedRequests,
count: unapprovedRequests.length
}
};
if (unapprovedRequests.length === 0) {
this.addNote("No unapproved connection requests found.");
} else {
this.addNote(
`Found ${unapprovedRequests.length} unapproved connection request(s).`
);
}
} catch (error) {
this.logger.error(
"Failed to list unapproved connection requests:",
error
);
throw error;
}
return this;
}
/**
* List connections with enhanced details
*/
async listConnections(params = {}) {
this.clearNotes();
if (!this.stateManager) {
throw new Error("StateManager is required to list connections");
}
const includeDetails = params.includeDetails ?? true;
const showPending = params.showPending ?? true;
try {
const connections = await this.getEnhancedConnections();
if (connections.length === 0) {
this.executeResult = {
success: true,
rawResult: {
connections: [],
message: "There are currently no active connections."
}
};
return this;
}
const activeConnections = connections.filter(
(c) => c.status === "established"
);
const pendingConnections = connections.filter(
(c) => c.isPending
);
const needsConfirmation = connections.filter(
(c) => c.needsConfirmation
);
let output = "";
if (activeConnections.length > 0) {
output +