Skip to main content
Glama
template-renderer.ts4.13 kB
/** * Template Renderer * Handlebars-based template rendering for agent prompts */ import Handlebars from "handlebars"; import { readFileSync } from "fs"; import { getChildLogger } from "../utils/logger.js"; const logger = getChildLogger("agents:template-renderer"); export class TemplateRenderer { private handlebars: typeof Handlebars; constructor() { this.handlebars = Handlebars.create(); this.registerHelpers(); logger.debug("TemplateRenderer initialized"); } /** * Register custom Handlebars helpers */ private registerHelpers(): void { // Equality helper this.handlebars.registerHelper("eq", (a, b) => a === b); // Includes helper (array contains value) this.handlebars.registerHelper( "includes", (array, value) => Array.isArray(array) && array.includes(value), ); // Upper case transformation this.handlebars.registerHelper("upper", (str) => str?.toUpperCase()); // Lower case transformation this.handlebars.registerHelper("lower", (str) => str?.toLowerCase()); // JSON stringify (useful for debugging context) this.handlebars.registerHelper("json", (obj) => JSON.stringify(obj, null, 2), ); // Check if package exists in dependencies this.handlebars.registerHelper("hasPackage", (pkgName, deps) => { return deps && typeof deps === "object" && deps[pkgName] !== undefined; }); // Not helper this.handlebars.registerHelper("not", (value) => !value); // Or helper this.handlebars.registerHelper("or", (...args) => { // Last arg is Handlebars options object, exclude it const values = args.slice(0, -1); return values.some((v) => !!v); }); // And helper this.handlebars.registerHelper("and", (...args) => { // Last arg is Handlebars options object, exclude it const values = args.slice(0, -1); return values.every((v) => !!v); }); logger.debug({ helpers: ["eq", "includes", "upper", "lower", "json", "hasPackage", "not", "or", "and"], }, "PLACEHOLDER"); } /** * Register a partial template */ registerPartial(name: string, template: string): void { this.handlebars.registerPartial(name, template); logger.debug({ name }, "Partial registered"); } /** * Render a template from a file path */ render(templatePath: string, context: Record<string, any> = {}): string { try { logger.debug({ templatePath, contextKeys: Object.keys(context) }, "Rendering template from file"); const templateContent = readFileSync(templatePath, "utf-8"); const template = this.handlebars.compile(templateContent); const result = template(context); logger.debug( { templatePath, inputLength: templateContent.length, outputLength: result.length, }, "Template rendered successfully", ); return result; } catch (error) { logger.error( { err: error instanceof Error ? error : new Error(String(error)), templatePath, }, "Failed to render template from file", ); throw new Error( `Failed to render template "${templatePath}": ${error}`, ); } } /** * Render a template from a string */ renderFromString( templateString: string, context: Record<string, any> = {}, ): string { try { logger.debug({ templateLength: templateString.length, contextKeys: Object.keys(context) }, "Rendering template from string"); const template = this.handlebars.compile(templateString); const result = template(context); logger.debug( { inputLength: templateString.length, outputLength: result.length, }, "Template string rendered successfully", ); return result; } catch (error) { logger.error( { err: error instanceof Error ? error : new Error(String(error)), }, "Failed to render template from string", ); throw new Error(`Failed to render template string: ${error}`); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jenova-marie/iris-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server