{"version":3,"file":"render-placeholders.mjs","names":[],"sources":["../../../../../../../ai/src/system-prompt/render-placeholders.ts"],"sourcesContent":["import type { Placeholders } from \"../contracts/placeholders.type\";\n\nconst PLACEHOLDER_PATTERN = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\n/**\n * Render a template string against a placeholders map, supporting dot-path\n * lookups and inline fallback values.\n *\n * Supported syntax:\n * - `{{key}}` — replaced by `placeholders.key`, left untouched if missing.\n * - `{{a.b.c}}` — dot-path lookup into nested objects.\n * - `{{key|default}}` — substitutes `default` when the key resolves to\n *   `undefined`, `null`, or empty string.\n *\n * Whitespace inside the braces is ignored (`{{ key }}` == `{{key}}`).\n * Values are coerced to strings via `String(value)`.\n *\n * @example\n * renderPlaceholders(\n *   \"Hello {{user.name|friend}}, your role is {{role}}\",\n *   { user: { name: \"Hasan\" }, role: \"admin\" },\n * );\n * // \"Hello Hasan, your role is admin\"\n *\n * @example\n * renderPlaceholders(\"Hello {{user.name|friend}}\", {});\n * // \"Hello friend\"\n */\nexport function renderPlaceholders(template: string, placeholders: Placeholders = {}): string {\n  return template.replace(PLACEHOLDER_PATTERN, (match, rawExpression: string) => {\n    const [rawPath = \"\", rawFallback] = rawExpression.split(\"|\");\n    const path = rawPath.trim();\n    const fallback = rawFallback?.trim();\n\n    const value = lookupPath(placeholders, path);\n\n    if (value === undefined || value === null || value === \"\") {\n      if (fallback !== undefined) {\n        return fallback;\n      }\n\n      return match;\n    }\n\n    return String(value);\n  });\n}\n\n/**\n * Walk a dot-path (`\"a.b.c\"`) through an arbitrary record, returning the\n * leaf value or `undefined` when any segment is missing or blocks traversal\n * (non-object). Never throws.\n */\nfunction lookupPath(source: Placeholders, path: string): unknown {\n  const segments = path.split(\".\");\n  let current: unknown = source;\n\n  for (const segment of segments) {\n    if (current === null || current === undefined) {\n      return undefined;\n    }\n\n    if (typeof current !== \"object\") {\n      return undefined;\n    }\n\n    current = (current as Record<string, unknown>)[segment];\n  }\n\n  return current;\n}\n"],"mappings":";AAEA,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;AA0B5B,SAAgB,mBAAmB,UAAkB,eAA6B,CAAC,GAAW;CAC5F,OAAO,SAAS,QAAQ,sBAAsB,OAAO,kBAA0B;EAC7E,MAAM,CAAC,UAAU,IAAI,eAAe,cAAc,MAAM,GAAG;EAC3D,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,WAAW,aAAa,KAAK;EAEnC,MAAM,QAAQ,WAAW,cAAc,IAAI;EAE3C,IAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,IAAI;GACzD,IAAI,aAAa,QACf,OAAO;GAGT,OAAO;EACT;EAEA,OAAO,OAAO,KAAK;CACrB,CAAC;AACH;;;;;;AAOA,SAAS,WAAW,QAAsB,MAAuB;CAC/D,MAAM,WAAW,KAAK,MAAM,GAAG;CAC/B,IAAI,UAAmB;CAEvB,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,YAAY,QAAQ,YAAY,QAClC;EAGF,IAAI,OAAO,YAAY,UACrB;EAGF,UAAW,QAAoC;CACjD;CAEA,OAAO;AACT"}