@hashgraphonline/conversational-agent
Version:
Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera. https://hol.org
129 lines (106 loc) • 3.48 kB
text/typescript
import {
BasePlugin,
type GenericPluginContext,
BaseHederaQueryTool,
type HederaAgentKit,
type HederaTool,
} from 'hedera-agent-kit';
import { z } from 'zod';
const PageSnapshotSchema = z.object({
url: z.string().url(),
maxCharacters: z
.number()
.int()
.min(256, 'Minimum length is 256 characters')
.max(8000, 'Maximum length is 8000 characters')
.optional()
.default(3000),
});
class WebPageSnapshotTool extends BaseHederaQueryTool<typeof PageSnapshotSchema> {
name = 'web_page_snapshot';
description = 'Fetches the visible text content of a web page for analysis.';
namespace = 'browser';
specificInputSchema = PageSnapshotSchema;
constructor(params: {
hederaKit: HederaAgentKit;
logger?: GenericPluginContext['logger'];
fetchImpl?: typeof fetch;
}) {
const { fetchImpl, ...rest } = params;
super(rest);
this.fetchImpl = fetchImpl ?? fetch;
}
private readonly fetchImpl: typeof fetch;
protected async executeQuery(
input: z.infer<typeof PageSnapshotSchema>
): Promise<string> {
const maxChars = input.maxCharacters ?? 3000;
try {
const response = await this.fetchImpl(input.url, {
redirect: 'follow',
});
if (!response.ok) {
return `Failed to load ${input.url}: HTTP ${response.status}`;
}
const html = await response.text();
const text = this.normalizeHtml(html);
if (!text) {
return 'The fetched page did not contain readable text.';
}
return text.length > maxChars ? `${text.slice(0, maxChars)}…` : text;
} catch (error) {
this.logger.error('WebPageSnapshotTool failed', error);
return `Failed to fetch content for ${input.url}: ${
error instanceof Error ? error.message : String(error)
}`;
}
}
private normalizeHtml(html: string): string {
const withoutScripts = html
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<!--([\s\S]*?)-->/g, ' ');
const stripped = withoutScripts.replace(/<[^>]+>/g, ' ');
const decoded = stripped
.replace(/ /gi, ' ')
.replace(/&/gi, '&')
.replace(/</gi, '<')
.replace(/>/gi, '>')
.replace(/"/gi, '"')
.replace(/'/gi, "'");
return decoded.replace(/\s+/g, ' ').trim();
}
}
export class WebBrowserPlugin extends BasePlugin<GenericPluginContext> {
id = 'web-browser';
name = 'Web Browser Plugin';
description =
'Provides tools for fetching live web page content to enrich assistant understanding.';
version = '0.1.0';
author = 'Hashgraph Online';
namespace = 'browser';
private tools: HederaTool[] = [];
override async initialize(context: GenericPluginContext): Promise<void> {
await super.initialize(context);
const hederaKit = context.config.hederaKit as HederaAgentKit | undefined;
if (!hederaKit) {
this.context.logger.warn(
'WebBrowserPlugin skipped because HederaAgentKit was not present in plugin context.'
);
this.tools = [];
return;
}
const tool = new WebPageSnapshotTool({
hederaKit,
logger: this.context.logger,
});
this.tools = [tool];
this.context.logger.info('Web Browser Plugin initialized with snapshot tool');
}
override getTools(): HederaTool[] {
return this.tools;
}
override async cleanup(): Promise<void> {
this.tools = [];
}
}