@hashgraphonline/conversational-agent
Version:
Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera. https://hol.org
107 lines (97 loc) • 3.5 kB
text/typescript
import {
EntityFormat,
type FormatConverter,
type ConversionContext,
} from '../types';
import {
HederaMirrorNode,
HRLResolver,
type NetworkType,
} from '@hashgraphonline/standards-sdk';
/**
* Generic converter that normalizes various string entity references (cdn urls, content-ref, raw topic id)
* into canonical HRL. It declares source ANY -> HRL and internally short-circuits if unsupported.
*/
export class StringNormalizationConverter
implements FormatConverter<string, string>
{
sourceFormat = EntityFormat.ANY;
targetFormat = EntityFormat.HRL;
private static standardCache: Map<string, string> = new Map();
canConvert(source: string, _context: ConversionContext): boolean {
if (typeof source !== 'string') {
return false;
}
if (/^hcs:\/\/\d\/0\.0\.\d+$/i.test(source)) {
return false;
}
if (/inscription-cdn\/(0\.0\.\d+)/i.test(source)) {
return true;
}
if (/^content-ref:(0\.0\.\d+)$/i.test(source)) {
return true;
}
if (/^0\.0\.\d+$/.test(source)) {
const hasPreference = Boolean(
(_context as unknown as { toolPreferences?: Record<string, string> })
?.toolPreferences?.inscription === 'hrl' ||
(_context as unknown as { toolPreferences?: Record<string, string> })
?.toolPreferences?.topic === 'hrl'
);
return hasPreference;
}
return false;
}
async convert(source: string, context: ConversionContext): Promise<string> {
const toolPrefs = (
context as unknown as { toolPreferences?: Record<string, string> }
).toolPreferences;
const fallbackStandard =
toolPrefs?.hrlStandard || toolPrefs?.inscriptionHrlStandard || '1';
const networkType = (context.networkType as string) || 'testnet';
const resolver = new HRLResolver();
const cdnMatch = source.match(/inscription-cdn\/(0\.0\.\d+)/i);
if (cdnMatch && cdnMatch[1]) {
try {
const mirror = new HederaMirrorNode(networkType as NetworkType);
mirror.configureRetry({
maxRetries: 3,
maxDelayMs: 1000,
});
const resolved = await mirror.getTopicInfo(cdnMatch[1]);
const memo = (resolved && (resolved as { memo?: string }).memo) || '';
const match = memo.match(/^hcs-(\d+)/);
const standard = match && match[1] ? match[1] : '1';
return `hcs://${standard}/${cdnMatch[1]}`;
} catch {
return `hcs://${fallbackStandard}/${cdnMatch[1]}`;
}
}
const contentRefMatch = source.match(/^content-ref:(0\.0\.\d+)$/i);
if (contentRefMatch && contentRefMatch[1]) {
try {
const resolved = await resolver.resolve(contentRefMatch[1], {
network: networkType as NetworkType,
});
const parsed = resolver.parseHRL(`hcs://1/${resolved.topicId}`);
const std = parsed?.standard || fallbackStandard;
return `hcs://${std}/${resolved.topicId}`;
} catch {
return `hcs://${fallbackStandard}/${contentRefMatch[1]}`;
}
}
if (/^0\.0\.\d+$/.test(source)) {
try {
const resolved = await resolver.resolve(source, {
network: networkType as NetworkType,
});
const parsed = resolver.parseHRL(`hcs://1/${resolved.topicId}`);
const std = parsed?.standard || fallbackStandard;
return `hcs://${std}/${resolved.topicId}`;
} catch {
return `hcs://${fallbackStandard}/${source}`;
}
}
return source;
}
}