{"version":3,"file":"prompt-langfuse-sync.mjs","names":[],"sources":["../../../../../../../ai/src/prompt/prompt-langfuse-sync.ts"],"sourcesContent":["import type { PromptEntry, PromptLangfuseSyncOptions } from \"./prompt.type\";\nimport type {\n  LangfuseClientLike,\n  LangfusePromptLike,\n} from \"./prompt-langfuse-sync.type\";\n\n// ============================================================\n// Lazily-loaded langfuse SDK (OPTIONAL peer)\n// ============================================================\n\nlet LangfuseSdk: typeof import(\"langfuse\");\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst PROMPT_LANGFUSE_INSTALL_INSTRUCTIONS = `\nThe prompt registry's Langfuse sync requires the langfuse package.\nInstall it with:\n\n  npm install langfuse\n\nOr with your preferred package manager:\n\n  pnpm add langfuse\n  yarn add langfuse\n`.trim();\n\n/**\n * Settle the lazy import of `langfuse` once, concurrency-safe. Only needed\n * when the caller did not pass a ready `client`. A bare `catch` flips the\n * flag to `false`; the curated install string surfaces at use time, never a\n * raw module-resolution stack trace.\n */\nfunction loadLangfuse(): Promise<void> {\n  if (isModuleExists !== null) {\n    return Promise.resolve();\n  }\n\n  if (loadingPromise) {\n    return loadingPromise;\n  }\n\n  loadingPromise = (async () => {\n    try {\n      LangfuseSdk = await import(\"langfuse\");\n      isModuleExists = true;\n    } catch {\n      isModuleExists = false;\n    }\n  })();\n\n  return loadingPromise;\n}\n\n/**\n * Resolve the Langfuse client — the caller-supplied one when present,\n * otherwise a lazily-constructed client from credentials. Throws the curated\n * install error when the SDK is missing and no client was supplied.\n */\nasync function resolveClient(\n  options: PromptLangfuseSyncOptions,\n): Promise<LangfuseClientLike> {\n  if (options.client) {\n    return options.client;\n  }\n\n  await loadLangfuse();\n\n  if (!isModuleExists) {\n    throw new Error(PROMPT_LANGFUSE_INSTALL_INSTRUCTIONS);\n  }\n\n  return new LangfuseSdk.Langfuse({\n    publicKey: options.publicKey,\n    secretKey: options.secretKey,\n    baseUrl: options.baseUrl,\n  }) as unknown as LangfuseClientLike;\n}\n\n/**\n * Map one Langfuse prompt handle onto a {@link PromptEntry} version snapshot.\n * Langfuse versions are numeric; they become the string `version` label.\n */\nfunction toEntry(remote: LangfusePromptLike): PromptEntry {\n  return {\n    name: remote.name,\n    versions: [{ version: String(remote.version), template: remote.prompt }],\n  };\n}\n\n/**\n * Warm the lazy `langfuse` import without blocking — call when a registry is\n * constructed with a `langfuse` option but no pre-built client, so the first\n * `.sync()` does not pay the resolution cost. A bare miss is tolerated.\n */\nexport function warmLangfuse(options: PromptLangfuseSyncOptions): void {\n  if (!options.client) {\n    void loadLangfuse();\n  }\n}\n\n/**\n * Run one Langfuse-prompts sync pass.\n *\n * **Pull** (`direction: \"pull\"` | `\"both\"`) fetches each named prompt from\n * Langfuse and hands the mapped {@link PromptEntry} to `upsert`. **Push**\n * (`direction: \"push\"` | `\"both\"`) writes the latest version of each local\n * entry back as a new Langfuse text prompt. Default direction is `\"pull\"`.\n *\n * Lazily imports `langfuse` (unless a `client` was supplied) and throws a\n * curated install error when the peer is missing.\n *\n * @param options - The configured sync options (client / credentials / direction).\n * @param names - The prompt names to pull (ignored for push-only).\n * @param localEntries - Snapshot of the local catalog, for push.\n * @param upsert - Callback receiving each pulled entry to merge into the catalog.\n */\nexport async function syncLangfusePrompts(\n  options: PromptLangfuseSyncOptions,\n  names: string[],\n  localEntries: PromptEntry[],\n  upsert: (entry: PromptEntry) => void,\n): Promise<void> {\n  const direction = options.direction ?? \"pull\";\n  const client = await resolveClient(options);\n\n  if (direction === \"pull\" || direction === \"both\") {\n    for (const name of names) {\n      const remote = await client.getPrompt(name);\n      upsert(toEntry(remote));\n    }\n  }\n\n  if (direction === \"push\" || direction === \"both\") {\n    for (const entry of localEntries) {\n      const latest = entry.versions[entry.versions.length - 1];\n\n      if (!latest) {\n        continue;\n      }\n\n      await client.createPrompt({\n        name: entry.name,\n        prompt: latest.template,\n        type: \"text\",\n      });\n    }\n  }\n}\n"],"mappings":";AAUA,IAAI;AACJ,IAAI,iBAAiC;AACrC,IAAI;AAEJ,MAAM,uCAAuC;;;;;;;;;;EAU3C,KAAK;;;;;;;AAQP,SAAS,eAA8B;CACrC,IAAI,mBAAmB,MACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GACF,cAAc,MAAM,OAAO;GAC3B,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;AAOA,eAAe,cACb,SAC6B;CAC7B,IAAI,QAAQ,QACV,OAAO,QAAQ;CAGjB,MAAM,aAAa;CAEnB,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,oCAAoC;CAGtD,OAAO,IAAI,YAAY,SAAS;EAC9B,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB,SAAS,QAAQ;CACnB,CAAC;AACH;;;;;AAMA,SAAS,QAAQ,QAAyC;CACxD,OAAO;EACL,MAAM,OAAO;EACb,UAAU,CAAC;GAAE,SAAS,OAAO,OAAO,OAAO;GAAG,UAAU,OAAO;EAAO,CAAC;CACzE;AACF;;;;;;AAOA,SAAgB,aAAa,SAA0C;CACrE,IAAI,CAAC,QAAQ,QACX,AAAK,aAAa;AAEtB;;;;;;;;;;;;;;;;;AAkBA,eAAsB,oBACpB,SACA,OACA,cACA,QACe;CACf,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,SAAS,MAAM,cAAc,OAAO;CAE1C,IAAI,cAAc,UAAU,cAAc,QACxC,KAAK,MAAM,QAAQ,OAEjB,OAAO,QAAQ,MADM,OAAO,UAAU,IAAI,CACrB,CAAC;CAI1B,IAAI,cAAc,UAAU,cAAc,QACxC,KAAK,MAAM,SAAS,cAAc;EAChC,MAAM,SAAS,MAAM,SAAS,MAAM,SAAS,SAAS;EAEtD,IAAI,CAAC,QACH;EAGF,MAAM,OAAO,aAAa;GACxB,MAAM,MAAM;GACZ,QAAQ,OAAO;GACf,MAAM;EACR,CAAC;CACH;AAEJ"}