{"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(\n  template: string,\n  placeholders: Placeholders = {},\n): string {\n  return template.replace(\n    PLACEHOLDER_PATTERN,\n    (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/**\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,mBACd,UACA,eAA6B,CAAC,GACtB;CACR,OAAO,SAAS,QACd,sBACC,OAAO,kBAA0B;EAChC,MAAM,CAAC,SAAS,eAAe,cAAc,MAAM,GAAG;EACtD,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,CACF;AACF;;;;;;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"}