@hashgraphonline/conversational-agent
Version:
Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera. https://hol.org
268 lines (267 loc) • 9.18 kB
JavaScript
import { Logger } from "@hashgraphonline/standards-sdk";
class FieldGuidanceRegistry {
constructor() {
this.configurations = [];
this.providers = [];
this.registerOrderCounter = 0;
this.logger = new Logger({ module: "FieldGuidanceRegistry" });
}
/**
* Register field guidance for a specific tool
*/
registerToolConfiguration(config) {
this.configurations.push(config);
}
/**
* Register a provider for dynamic field/global guidance
*/
registerToolProvider(toolPattern, provider, options) {
const id = options?.id ?? `provider-${this.providers.length + 1}`;
const priority = options?.priority ?? 0;
if (this.providers.some((p) => p.id === id)) {
this.logger.error("Duplicate provider id", { id });
throw new Error("DUPLICATE_PROVIDER_ID");
}
this.providers.push({
id,
priority,
pattern: toolPattern,
provider,
order: this.registerOrderCounter++
});
return id;
}
/** Unregister a provider by id */
unregisterProvider(id) {
this.providers = this.providers.filter((p) => p.id !== id);
}
/** List registered providers */
listProviders() {
return this.providers.map(({ id, priority, pattern }) => ({
id,
priority,
pattern
}));
}
/**
* Get field guidance for a specific tool and field
*/
getFieldGuidance(toolName, fieldName) {
if (process.env.CA_FORM_GUIDANCE_ENABLED === "false") {
return null;
}
for (const config of this.configurations) {
const matches = typeof config.toolPattern === "string" ? toolName.toLowerCase().includes(config.toolPattern.toLowerCase()) : config.toolPattern.test(toolName);
if (matches && config.fields[fieldName]) {
const staticGuidance = config.fields[fieldName];
const providers2 = this.pickMatchingProviders(toolName);
if (providers2.length === 0) return staticGuidance;
let merged = { ...staticGuidance };
for (const p of [...providers2].reverse()) {
const fromProvider = this.safeGetFieldGuidance(
p,
fieldName,
toolName
);
if (fromProvider) {
merged = this.mergeGuidance(merged, fromProvider);
}
}
return merged;
}
}
const providers = this.pickMatchingProviders(toolName);
if (providers.length > 0) {
let merged = {};
for (const p of [...providers].reverse()) {
const g = this.safeGetFieldGuidance(p, fieldName, toolName);
if (g) merged = this.mergeGuidance(merged, g);
}
return Object.keys(merged).length > 0 ? merged : null;
}
return null;
}
/**
* Get global guidance for a tool
*/
getGlobalGuidance(toolName) {
if (process.env.CA_FORM_GUIDANCE_ENABLED === "false") {
return null;
}
for (const config of this.configurations) {
const matches = typeof config.toolPattern === "string" ? toolName.toLowerCase().includes(config.toolPattern.toLowerCase()) : config.toolPattern.test(toolName);
if (matches && config.globalGuidance) {
const base = config.globalGuidance;
const providers2 = this.pickMatchingProviders(toolName);
if (providers2.length === 0) return base;
let mergedWarnings = base.warnings;
let mergedQuality = base.qualityStandards;
for (const p of [...providers2].reverse()) {
const fromProvider = this.safeGetGlobalGuidance(p, toolName);
if (fromProvider) {
mergedWarnings = fromProvider.warnings ?? mergedWarnings;
mergedQuality = fromProvider.qualityStandards ?? mergedQuality;
}
}
const result = {};
if (mergedWarnings !== void 0) result.warnings = mergedWarnings;
if (mergedQuality !== void 0)
result.qualityStandards = mergedQuality;
return result;
}
}
const providers = this.pickMatchingProviders(toolName);
if (providers.length > 0) {
let mergedWarnings;
let mergedQuality;
for (const p of [...providers].reverse()) {
const g = this.safeGetGlobalGuidance(p, toolName);
if (g) {
mergedWarnings = g.warnings ?? mergedWarnings;
mergedQuality = g.qualityStandards ?? mergedQuality;
}
}
const result = {};
if (mergedWarnings !== void 0) result.warnings = mergedWarnings;
if (mergedQuality !== void 0) result.qualityStandards = mergedQuality;
return Object.keys(result).length > 0 ? result : null;
}
return null;
}
/**
* Validate field value against guidance rules
*/
validateFieldValue(toolName, fieldName, value) {
const guidance = this.getFieldGuidance(toolName, fieldName);
const warnings = [];
const errors = [];
if (!guidance || typeof value !== "string") {
return { isValid: true, warnings, errors };
}
if (guidance.warnings) {
for (const warning of guidance.warnings) {
if (warning.pattern.test(value)) {
warnings.push(warning.message);
}
}
}
if (guidance.validationRules) {
const { rejectPatterns, qualityChecks } = guidance.validationRules;
if (rejectPatterns) {
for (const reject of rejectPatterns) {
if (reject.pattern.test(value)) {
errors.push(`Rejected: ${reject.reason}`);
}
}
}
if (qualityChecks) {
if (qualityChecks.forbidTechnicalTerms) {
const lowerValue = value.toLowerCase();
for (const term of qualityChecks.forbidTechnicalTerms) {
if (lowerValue.includes(term.toLowerCase())) {
errors.push(
`Avoid technical terms like "${term}" in NFT metadata`
);
}
}
}
if (qualityChecks.requireSpecificTerms) {
const lowerValue = value.toLowerCase();
const hasRequired = qualityChecks.requireSpecificTerms.some(
(term) => lowerValue.includes(term.toLowerCase())
);
if (!hasRequired) {
warnings.push(
`Consider including terms like: ${qualityChecks.requireSpecificTerms.join(
", "
)}`
);
}
}
if (qualityChecks.minNonTechnicalWords) {
const words = value.split(/\s+/).filter((word) => word.length > 2);
if (words.length < qualityChecks.minNonTechnicalWords) {
warnings.push(
`Consider providing more descriptive content (at least ${qualityChecks.minNonTechnicalWords} meaningful words)`
);
}
}
}
}
return {
isValid: errors.length === 0,
warnings,
errors
};
}
/**
* Clear all configurations (useful for testing)
*/
clear() {
this.configurations = [];
this.providers = [];
this.registerOrderCounter = 0;
}
/** Choose matching provider by priority then last-in wins */
pickMatchingProviders(toolName) {
const matches = this.providers.filter(
(p) => typeof p.pattern === "string" ? toolName.toLowerCase().includes(p.pattern.toLowerCase()) : p.pattern.test(toolName)
);
const sorted = matches.sort((a, b) => {
if (b.priority !== a.priority) return b.priority - a.priority;
return b.order - a.order;
});
return sorted.map((m) => ({
id: m.id,
provider: m.provider,
priority: m.priority,
order: m.order
}));
}
safeGetFieldGuidance(winner, fieldName, toolName) {
try {
return winner.provider.getFieldGuidance(fieldName, { toolName }) ?? null;
} catch (err) {
this.logger.warn("Provider getFieldGuidance failed", {
id: winner.id,
err
});
return null;
}
}
safeGetGlobalGuidance(winner, toolName) {
try {
return winner.provider.getGlobalGuidance?.(toolName) ?? null;
} catch (err) {
this.logger.warn("Provider getGlobalGuidance failed", {
id: winner.id,
err
});
return null;
}
}
mergeGuidance(base, over) {
const out = {};
const suggestions = over.suggestions ?? base.suggestions;
if (suggestions !== void 0) out.suggestions = suggestions;
const predefinedOptions = over.predefinedOptions ?? base.predefinedOptions;
if (predefinedOptions !== void 0)
out.predefinedOptions = predefinedOptions;
const warnings = over.warnings ?? base.warnings;
if (warnings !== void 0) out.warnings = warnings;
const validationRules = over.validationRules ?? base.validationRules;
if (validationRules !== void 0) out.validationRules = validationRules;
const fieldTypeOverride = over.fieldTypeOverride ?? base.fieldTypeOverride;
if (fieldTypeOverride !== void 0)
out.fieldTypeOverride = fieldTypeOverride;
const contextualHelpText = over.contextualHelpText ?? base.contextualHelpText;
if (contextualHelpText !== void 0)
out.contextualHelpText = contextualHelpText;
return out;
}
}
const fieldGuidanceRegistry = new FieldGuidanceRegistry();
export {
fieldGuidanceRegistry
};
//# sourceMappingURL=index14.js.map