@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.
276 lines (232 loc) • 9.6 kB
text/typescript
import { StructuredTool, ToolParams } from '@langchain/core/tools';
import { z } from 'zod';
import { HCS10Client } from '../hcs10/HCS10Client';
import { IStateManager } from '../state/state-types';
import { Logger } from '@hashgraphonline/standards-sdk';
export interface ManageConnectionRequestsToolParams extends ToolParams {
hcsClient: HCS10Client;
stateManager: IStateManager;
}
/**
* A tool for managing incoming connection requests in a LangChain-compatible way.
* This tool allows an agent to list, view details of, and accept/reject incoming connection requests.
*/
export class ManageConnectionRequestsTool extends StructuredTool {
name = 'manage_connection_requests';
description =
'Manage incoming connection requests. List pending requests, view details about requesting agents, and reject connection requests. Use the separate "accept_connection_request" tool to accept.';
schema = z.object({
action: z
.enum(['list', 'view', 'reject'])
.describe(
'The action to perform: list all requests, view details of a specific request, or reject a request'
),
requestKey: z
.string()
.optional()
.describe(
'The unique request key to view or reject (required for view and reject actions)'
),
});
private hcsClient: HCS10Client;
private stateManager: IStateManager;
private logger: Logger;
private lastRefreshTime: number = 0;
private refreshIntervalMs = 30000;
constructor({
hcsClient,
stateManager,
...rest
}: ManageConnectionRequestsToolParams) {
super(rest);
this.hcsClient = hcsClient;
this.stateManager = stateManager;
this.logger = Logger.getInstance({
module: 'ManageConnectionRequestsTool',
level: 'debug',
});
}
protected async _call({
action,
requestKey,
}: z.infer<this['schema']>): Promise<string> {
const currentAgent = this.stateManager.getCurrentAgent();
if (!currentAgent) {
return 'Error: Cannot manage connection requests. No agent is currently active. Please register or select an agent first.';
}
if ((action === 'view' || action === 'reject') && requestKey === undefined) {
return `Error: Request key is required for the "${action}" action. Use the "list" action first to see available requests.`;
}
try {
await this.refreshRequestsIfNeeded();
switch (action) {
case 'list':
return this.listRequests();
case 'view':
return this.viewRequest(requestKey!);
case 'reject':
return this.rejectRequest(requestKey!);
default:
return `Error: Unsupported action: ${action}`;
}
} catch (error) {
this.logger.error(`Error in ManageConnectionRequestsTool: ${error}`);
return `Error managing connection requests: ${
error instanceof Error ? error.message : String(error)
}`;
}
}
private async refreshRequestsIfNeeded(): Promise<void> {
const now = Date.now();
if (now - this.lastRefreshTime > this.refreshIntervalMs) {
await this.refreshRequests();
this.lastRefreshTime = now;
}
}
private async refreshRequests(): Promise<void> {
try {
const { accountId } = this.hcsClient.getAccountAndSigner();
if (!accountId) {
throw new Error('Could not determine account ID for current agent');
}
const connectionManager = this.stateManager.getConnectionsManager();
if (!connectionManager) {
throw new Error('ConnectionsManager not initialized');
}
await connectionManager.fetchConnectionData(accountId);
} catch (error) {
this.logger.error(`Error refreshing connection requests: ${error}`);
throw error;
}
}
private listRequests(): string {
const connectionsManager = this.stateManager.getConnectionsManager();
if (!connectionsManager) {
return 'Error: ConnectionsManager not initialized';
}
const pendingRequests = connectionsManager.getPendingRequests();
const needsConfirmation =
connectionsManager.getConnectionsNeedingConfirmation();
const allRequests = [...pendingRequests, ...needsConfirmation];
if (allRequests.length === 0) {
console.log('No pending connection requests found.', allRequests);
return 'No pending connection requests found.';
}
let output = `Found ${allRequests.length} pending connection request(s):\n\n`;
const sortedRequests = [...allRequests].sort(
(a, b) => b.created.getTime() - a.created.getTime()
);
sortedRequests.forEach((request, index) => {
// Create a display ID for the connection request
const requestType = request.needsConfirmation ? '🟠 Incoming' : '⚪️ Outgoing';
const requestIdDisplay = request.uniqueRequestKey ||
`${request.connectionRequestId || request.inboundRequestId || 'unknown'}`;
output += `${index + 1}. ${requestType} - Key: ${requestIdDisplay}\n`;
output += ` ${request.needsConfirmation ? 'From' : 'To'}: ${
request.targetAgentName || `Agent ${request.targetAccountId}`
} (${request.targetAccountId})\n`;
output += ` Sent/Rcvd: ${request.created.toLocaleString()}\n`;
if (request.memo) {
output += ` Memo: ${request.memo}\n`;
}
if (request.profileInfo && request.profileInfo.bio) {
output += ` Bio: ${request.profileInfo.bio}\n`;
}
output += '\n';
});
output +=
'To view more details about a request, use action="view" with the specific requestKey.\n';
output +=
'To reject a request, use action="reject" with the specific requestKey.';
return output;
}
private viewRequest(requestKey: string): string {
const connectionsManager = this.stateManager.getConnectionsManager();
if (!connectionsManager) {
return 'Error: ConnectionsManager not initialized';
}
const pendingRequests = connectionsManager.getPendingRequests();
const needsConfirmation =
connectionsManager.getConnectionsNeedingConfirmation();
const allRequests = [...pendingRequests, ...needsConfirmation];
// Find the request with the matching unique key or fallback to sequence number
const request = allRequests.find(
(r) =>
(r.uniqueRequestKey === requestKey) ||
(r.connectionRequestId?.toString() === requestKey) ||
(r.inboundRequestId?.toString() === requestKey)
);
if (!request) {
return `Error: Request with key ${requestKey} not found or no longer pending.`;
}
// Create a display ID for the connection request
const requestType = request.needsConfirmation ? 'Incoming' : 'Outgoing';
const uniqueKey = request.uniqueRequestKey ||
`${request.connectionRequestId || request.inboundRequestId || 'unknown'}`;
let output = `Details for ${requestType} connection request: ${uniqueKey}\n\n`;
output += `${request.needsConfirmation ? 'Requestor' : 'Target'} ID: ${request.targetAccountId}\n`;
output += `${request.needsConfirmation ? 'Requestor' : 'Target'} Name: ${
request.targetAgentName || `Agent ${request.targetAccountId}`
}\n`;
output += `Received: ${request.created.toLocaleString()}\n`;
if (request.memo) {
output += `Memo: ${request.memo}\n`;
}
if (request.profileInfo) {
output += '\nAgent Profile Information:\n';
if (request.profileInfo.display_name || request.profileInfo.alias) {
output += `Name: ${
request.profileInfo.display_name || request.profileInfo.alias
}\n`;
}
if (request.profileInfo.type !== undefined) {
output += `Type: ${request.profileInfo.type}\n`;
}
if (request.profileInfo.bio) {
output += `Bio: ${request.profileInfo.bio}\n`;
}
}
output += '\nActions:\n';
output += `- To reject this request: action="reject", requestKey="${uniqueKey}"\n`;
output +=
'Use the separate "accept_connection_request" tool to accept requests.';
return output;
}
private async rejectRequest(requestKey: string): Promise<string> {
const connectionsManager = this.stateManager.getConnectionsManager();
if (!connectionsManager) {
return 'Error: ConnectionsManager not initialized';
}
const pendingRequests = connectionsManager.getPendingRequests();
const needsConfirmation =
connectionsManager.getConnectionsNeedingConfirmation();
const allRequests = [...pendingRequests, ...needsConfirmation];
// Find the request with the matching unique key or fallback to sequence number
const request = allRequests.find(
(r) =>
(r.uniqueRequestKey === requestKey) ||
(r.connectionRequestId?.toString() === requestKey) ||
(r.inboundRequestId?.toString() === requestKey)
);
if (!request) {
return `Error: Request with key ${requestKey} not found or no longer pending.`;
}
// Mark as processed in ConnectionsManager based on the appropriate ID
if (request.inboundRequestId) {
// For needs_confirmation requests
connectionsManager.markConnectionRequestProcessed(
request.targetInboundTopicId || '',
request.inboundRequestId
);
} else if (request.connectionRequestId) {
// For pending requests
connectionsManager.markConnectionRequestProcessed(
request.originTopicId || '',
request.connectionRequestId
);
}
return `Connection request from ${
request.targetAgentName || `Agent ${request.targetAccountId}`
} was rejected.`;
}
}