{"version":3,"file":"prompt.mjs","names":["createPromptsManager"],"sources":["../../../../../../../ai/src/prompt/prompt.ts"],"sourcesContent":["import type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\nimport {\n  defaultPromptsManager,\n  prompts as createPromptsManager,\n} from \"../prompts/prompts-manager\";\nimport type { PromptsManagerContract } from \"../prompts/prompts-manager.contract\";\nimport { Instruction } from \"../system-prompt/instruction\";\nimport { renderPlaceholders } from \"../system-prompt/render-placeholders\";\nimport { SystemPrompt } from \"../system-prompt/system-prompt\";\nimport { PromptNotFoundError, PromptValidationError } from \"./errors\";\nimport {\n  syncLangfusePrompts,\n  warmLangfuse,\n} from \"./prompt-langfuse-sync\";\nimport {\n  buildValidationReport,\n  judgePrompt,\n  staticLint,\n} from \"./prompt-validate\";\nimport { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { ModelContract } from \"../contracts/model.contract\";\nimport type {\n  PromptEntry,\n  PromptRegistryContract,\n  PromptRegistryOptions,\n  PromptResolveOptions,\n  PromptValidateOptions,\n  PromptValidationReport,\n  PromptVersion,\n  ResolvedPrompt,\n} from \"./prompt.type\";\n\n/**\n * Build the one-shot judge agent the `validate()` LLM-as-judge pass runs.\n * Name-bearing (the eval `judge` scorer requires a usable agent) and seeded\n * with a strict-JSON instruction so its verdict parses even without an output\n * schema. Kept module-private so the registry's only model dependency is the\n * `agent()` factory.\n */\nfunction buildJudgeAgent(model: ModelContract): AgentContract<unknown> {\n  return agent({\n    name: \"prompt-quality-judge\",\n    model,\n    systemPrompt:\n      \"You are a strict prompt-quality grader. Respond with JSON only: \" +\n      '{ \"score\": <0..1>, \"passed\": <true|false>, \"reason\": \"<short explanation>\" }.',\n  });\n}\n\n/**\n * Build the `SystemPromptContract` a single {@link PromptVersion} maps to: its\n * `template` becomes one instruction block, and its `required` keys ride along\n * as `meta.required` so the unified manager (and `validate`) can see them.\n *\n * Deliberately ANONYMOUS (no `meta.name`) so the `SystemPrompt` constructor\n * never auto-registers this version into the process-wide `ai.prompts` default\n * manager — each `prompt()` registry owns its OWN isolated\n * {@link PromptsManagerContract}, the single storage shape behind this facade.\n */\nfunction versionToContract(version: PromptVersion): SystemPromptContract {\n  return new SystemPrompt([new Instruction(version.template)], {\n    ...(version.required ? { required: version.required } : {}),\n  });\n}\n\n/**\n * Legacy `PromptRegistryContract` — now a THIN FACADE over the unified\n * {@link PromptsManagerContract} (`ai.prompts`).\n *\n * **Role.** The store behind `ai.prompt(...)`. Historically it held a private\n * `Map<string, PromptVersion[]>`; it now delegates ALL storage to a private,\n * per-instance {@link PromptsManagerContract}, so there is exactly ONE storage\n * shape across the whole prompt surface: a `SystemPromptContract` keyed by\n * `name@version`. A version's raw `template` string maps to a single\n * instruction block and its `required` keys to `meta.required`.\n *\n * **Responsibility.**\n * - Owns: the legacy method surface (`register` / `add` / `versions` /\n *   `resolve` / `validate` / `sync`) and the back-compat behaviors — duplicate\n *   version rejection, required-key assertion on `resolve()`, the\n *   `{ score, notes }` validation report shape, and the optional Langfuse sync.\n * - Does NOT own: the actual storage (delegated to the internal manager),\n *   placeholder rendering (delegated to `renderPlaceholders`), or the unified\n *   validation primitives (delegated to `prompt-validate`).\n *\n * Each `prompt(options)` call builds its own isolated manager — so parallel\n * test suites and multi-tenant apps never share mutable global prompt state,\n * exactly as before the unification.\n *\n * Users construct via the `prompt()` factory — `new PromptRegistry()` is not\n * the public API.\n */\nclass PromptRegistry implements PromptRegistryContract {\n  /** The single backing store — one isolated unified manager per registry. */\n  private readonly manager: PromptsManagerContract;\n\n  /** Per-name version metadata mirror, kept so `versions()` returns the rich\n   * {@link PromptVersion} shape (template + required + meta) the legacy API\n   * promised — the manager itself only stores the flattened contract. */\n  private readonly versionMeta = new Map<string, PromptVersion[]>();\n\n  public constructor(private readonly options: PromptRegistryOptions = {}) {\n    this.manager = createPromptsManager();\n\n    for (const entry of options.prompts ?? []) {\n      this.register(entry);\n    }\n\n    if (options.langfuse) {\n      warmLangfuse(options.langfuse);\n    }\n  }\n\n  /**\n   * Register a whole entry. Merges onto an existing name's history; a\n   * duplicate version label throws {@link PromptValidationError}.\n   */\n  public register(entry: PromptEntry): PromptRegistryContract {\n    for (const version of entry.versions) {\n      this.add(entry.name, version);\n    }\n\n    // An entry with an empty version list still creates the name so `has`\n    // / `list` reflect it.\n    if (!this.versionMeta.has(entry.name)) {\n      this.versionMeta.set(entry.name, []);\n    }\n\n    return this;\n  }\n\n  /**\n   * Add a new version to a name (creating it when absent). A duplicate\n   * version label throws {@link PromptValidationError} — never a silent\n   * overwrite.\n   */\n  public add(name: string, version: PromptVersion): PromptRegistryContract {\n    const mirror = this.versionMeta.get(name) ?? [];\n\n    if (mirror.some(existing => existing.version === version.version)) {\n      throw new PromptValidationError(\n        `Prompt \"${name}\" already has a version labeled \"${version.version}\".`,\n        { context: { name, version: version.version } },\n      );\n    }\n\n    this.manager.register(versionToContract(version), {\n      name,\n      version: version.version,\n    });\n\n    this.versionMeta.set(name, [...mirror, version]);\n\n    return this;\n  }\n\n  /** Whether a name is registered. */\n  public has(name: string): boolean {\n    return this.versionMeta.has(name);\n  }\n\n  /** Every registered prompt name, in registration order. */\n  public list(): string[] {\n    return [...this.versionMeta.keys()];\n  }\n\n  /** Versions registered for a name, latest last. Throws on an unknown name. */\n  public versions(name: string): PromptVersion[] {\n    const mirror = this.versionMeta.get(name);\n\n    if (!mirror) {\n      throw new PromptNotFoundError(name);\n    }\n\n    return [...mirror];\n  }\n\n  /**\n   * Resolve + render. Picks the requested or latest version, validates the\n   * version's `required` keys against the merged placeholders, then renders by\n   * delegating to the shared `renderPlaceholders` over the contract's text.\n   */\n  public resolve(name: string, options: PromptResolveOptions = {}): ResolvedPrompt {\n    const picked = this.pickVersion(name, options.version);\n    const placeholders = options.placeholders ?? {};\n\n    this.assertRequired(name, picked, placeholders);\n\n    // Render the RAW template (placeholders intact) against the merged values —\n    // resolving the contract first would bake inline `{{key|default}}` defaults\n    // in and shadow an explicitly-supplied value. The stored block text is the\n    // single source of the un-rendered template.\n    const contract = this.manager.get(name, picked.version);\n    const template = contract.blocks[0]?.text ?? picked.template;\n    const text = renderPlaceholders(template, placeholders);\n\n    return {\n      name,\n      version: picked.version,\n      text,\n      toSystemPrompt: () => new SystemPrompt([new Instruction(text)]),\n    };\n  }\n\n  /**\n   * Quality-check a raw prompt body or a registered prompt (by name). Backed by\n   * the unified deterministic validate primitives plus the LLM-as-judge pass,\n   * but returns the legacy `{ score, notes }` report shape so existing callers\n   * keep working.\n   *\n   * Always runs the static lint; runs the LLM-as-judge pass too when a model is\n   * resolvable. Never throws when no judge model is available.\n   */\n  public async validate(\n    textOrName: string,\n    options: PromptValidateOptions = {},\n  ): Promise<PromptValidationReport> {\n    const text = this.resolveValidationText(textOrName, options.version);\n    const staticNotes = staticLint(text);\n\n    const model = options.model ?? this.options.judgeModel;\n\n    if (!model) {\n      return buildValidationReport(staticNotes);\n    }\n\n    const judgeResult = await judgePrompt(text, model, buildJudgeAgent);\n\n    return buildValidationReport(staticNotes, judgeResult);\n  }\n\n  /**\n   * Synchronize named prompts with Langfuse-prompts. No-op (resolves) when no\n   * `langfuse` option was configured. The resolved (rendered) body + the\n   * `name@version` label are what is pushed/pulled.\n   */\n  public async sync(): Promise<void> {\n    if (!this.options.langfuse) {\n      return;\n    }\n\n    await syncLangfusePrompts(\n      this.options.langfuse,\n      this.list(),\n      this.snapshotEntries(),\n      entry => this.register(entry),\n    );\n  }\n\n  /**\n   * Pick the requested (or latest) {@link PromptVersion} for a name from the\n   * mirror, throwing {@link PromptNotFoundError} on an unknown name or version.\n   */\n  private pickVersion(name: string, version?: string): PromptVersion {\n    const mirror = this.versionMeta.get(name);\n\n    if (!mirror || mirror.length === 0) {\n      throw new PromptNotFoundError(name);\n    }\n\n    const picked = version\n      ? mirror.find(candidate => candidate.version === version)\n      : mirror[mirror.length - 1];\n\n    if (!picked) {\n      throw new PromptNotFoundError(name, {\n        context: { name, version },\n      });\n    }\n\n    return picked;\n  }\n\n  /**\n   * Resolve the text `validate()` should grade: a registered name yields its\n   * picked version's raw `template`; anything else is treated as the raw body.\n   */\n  private resolveValidationText(textOrName: string, version?: string): string {\n    const mirror = this.versionMeta.get(textOrName);\n\n    if (!mirror || mirror.length === 0) {\n      return textOrName;\n    }\n\n    const picked = version\n      ? mirror.find(candidate => candidate.version === version)\n      : mirror[mirror.length - 1];\n\n    return picked ? picked.template : textOrName;\n  }\n\n  /**\n   * Throw {@link PromptValidationError} listing every `required` key absent\n   * from the merged placeholders. A no-op when the version declares none.\n   */\n  private assertRequired(\n    name: string,\n    version: PromptVersion,\n    placeholders: Record<string, unknown>,\n  ): void {\n    if (!version.required || version.required.length === 0) {\n      return;\n    }\n\n    const missing = version.required.filter(\n      key => placeholders[key] === undefined || placeholders[key] === null || placeholders[key] === \"\",\n    );\n\n    if (missing.length > 0) {\n      throw new PromptValidationError(\n        `Prompt \"${name}\" version \"${version.version}\" is missing required placeholder${\n          missing.length > 1 ? \"s\" : \"\"\n        }: ${missing.join(\", \")}.`,\n        { context: { name, version: version.version, missing } },\n      );\n    }\n  }\n\n  /** Snapshot the catalog as `PromptEntry[]` (for the Langfuse push path). */\n  private snapshotEntries(): PromptEntry[] {\n    return [...this.versionMeta.entries()].map(([name, versions]) => ({\n      name,\n      versions: [...versions],\n    }));\n  }\n}\n\n/**\n * Create a versioned, typed prompt registry — a thin facade over the unified\n * `ai.prompts` manager.\n *\n * **Role.** Public factory for {@link PromptRegistryContract}. Keeps\n * user-facing code free of `new` and consistent with `ai.memory`,\n * `ai.orchestrator`, `ai.batch` (all return instances). Each call returns a\n * fresh, isolated registry backed by its own unified manager, so parallel test\n * suites and multi-tenant apps never share mutable global prompt state.\n *\n * @param options - Seed entries, an optional default judge model, and an\n *   optional Langfuse sync.\n *\n * @example\n * const prompts = prompt({\n *   prompts: [\n *     {\n *       name: \"support-agent\",\n *       versions: [\n *         { version: \"1\", template: \"You are support for {{product}}. Reply in {{language|English}}.\" },\n *         { version: \"2\", template: \"You are senior support for {{product}}.\", required: [\"product\"] },\n *       ],\n *     },\n *   ],\n * });\n *\n * const resolved = prompts.resolve(\"support-agent\", { placeholders: { product: \"Warlock\" } });\n * const agent = ai.agent({ model, systemPrompt: resolved.toSystemPrompt() });\n * // resolved.version === \"2\"; a missing `product` would throw PromptValidationError.\n *\n * @example\n * // Resolve a globally-registered prompt by name from `ai.prompts`.\n * ai.systemPrompt(\"You are support.\", { name: \"support\" });\n * const sp = ai.prompt(\"support\"); // → the registered SystemPromptContract\n */\nfunction promptFactory(\n  name: string,\n  versionOrTag?: string,\n): SystemPromptContract;\nfunction promptFactory(options?: PromptRegistryOptions): PromptRegistryContract;\nfunction promptFactory(\n  first?: string | PromptRegistryOptions,\n  versionOrTag?: string,\n): SystemPromptContract | PromptRegistryContract {\n  // String form: resolve a globally-registered prompt from the process-wide\n  // `ai.prompts` manager (the single unified registry). This is the thin\n  // facade's read path onto the shared store.\n  if (typeof first === \"string\") {\n    return defaultPromptsManager().get(first, versionOrTag);\n  }\n\n  // Options form: build an isolated registry backed by its own unified manager.\n  return new PromptRegistry(first);\n}\n\n/**\n * Create a versioned prompt registry, OR resolve a globally-registered prompt\n * by name from `ai.prompts`.\n *\n * - `prompt(options?)` → a fresh, isolated {@link PromptRegistryContract}.\n * - `prompt(name, versionOrTag?)` → the `SystemPromptContract` registered under\n *   `name` in the process-wide `ai.prompts` manager (latest version by default,\n *   or a specific version / pinned tag).\n */\nexport const prompt: typeof promptFactory = promptFactory;\n"],"mappings":";;;;;;;;;;;;;;;;;AAwCA,SAAS,gBAAgB,OAA8C;CACrE,OAAO,MAAM;EACX,MAAM;EACN;EACA,cACE;CAEJ,CAAC;AACH;;;;;;;;;;;AAYA,SAAS,kBAAkB,SAA8C;CACvE,OAAO,IAAI,aAAa,CAAC,IAAI,YAAY,QAAQ,QAAQ,CAAC,GAAG,EAC3D,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC,EAC3D,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAM,iBAAN,MAAuD;CASrD,AAAO,YAAY,AAAiB,UAAiC,CAAC,GAAG;EAArC;qCAFL,IAAI,IAA6B;EAG9D,KAAK,UAAUA,QAAqB;EAEpC,KAAK,MAAM,SAAS,QAAQ,WAAW,CAAC,GACtC,KAAK,SAAS,KAAK;EAGrB,IAAI,QAAQ,UACV,aAAa,QAAQ,QAAQ;CAEjC;;;;;CAMA,AAAO,SAAS,OAA4C;EAC1D,KAAK,MAAM,WAAW,MAAM,UAC1B,KAAK,IAAI,MAAM,MAAM,OAAO;EAK9B,IAAI,CAAC,KAAK,YAAY,IAAI,MAAM,IAAI,GAClC,KAAK,YAAY,IAAI,MAAM,MAAM,CAAC,CAAC;EAGrC,OAAO;CACT;;;;;;CAOA,AAAO,IAAI,MAAc,SAAgD;EACvE,MAAM,SAAS,KAAK,YAAY,IAAI,IAAI,KAAK,CAAC;EAE9C,IAAI,OAAO,MAAK,aAAY,SAAS,YAAY,QAAQ,OAAO,GAC9D,MAAM,IAAI,sBACR,WAAW,KAAK,mCAAmC,QAAQ,QAAQ,KACnE,EAAE,SAAS;GAAE;GAAM,SAAS,QAAQ;EAAQ,EAAE,CAChD;EAGF,KAAK,QAAQ,SAAS,kBAAkB,OAAO,GAAG;GAChD;GACA,SAAS,QAAQ;EACnB,CAAC;EAED,KAAK,YAAY,IAAI,MAAM,CAAC,GAAG,QAAQ,OAAO,CAAC;EAE/C,OAAO;CACT;;CAGA,AAAO,IAAI,MAAuB;EAChC,OAAO,KAAK,YAAY,IAAI,IAAI;CAClC;;CAGA,AAAO,OAAiB;EACtB,OAAO,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC;CACpC;;CAGA,AAAO,SAAS,MAA+B;EAC7C,MAAM,SAAS,KAAK,YAAY,IAAI,IAAI;EAExC,IAAI,CAAC,QACH,MAAM,IAAI,oBAAoB,IAAI;EAGpC,OAAO,CAAC,GAAG,MAAM;CACnB;;;;;;CAOA,AAAO,QAAQ,MAAc,UAAgC,CAAC,GAAmB;EAC/E,MAAM,SAAS,KAAK,YAAY,MAAM,QAAQ,OAAO;EACrD,MAAM,eAAe,QAAQ,gBAAgB,CAAC;EAE9C,KAAK,eAAe,MAAM,QAAQ,YAAY;EAQ9C,MAAM,OAAO,mBAFI,KAAK,QAAQ,IAAI,MAAM,OAAO,OACvB,CAAC,CAAC,OAAO,EAAE,EAAE,QAAQ,OAAO,UACV,YAAY;EAEtD,OAAO;GACL;GACA,SAAS,OAAO;GAChB;GACA,sBAAsB,IAAI,aAAa,CAAC,IAAI,YAAY,IAAI,CAAC,CAAC;EAChE;CACF;;;;;;;;;;CAWA,MAAa,SACX,YACA,UAAiC,CAAC,GACD;EACjC,MAAM,OAAO,KAAK,sBAAsB,YAAY,QAAQ,OAAO;EACnE,MAAM,cAAc,WAAW,IAAI;EAEnC,MAAM,QAAQ,QAAQ,SAAS,KAAK,QAAQ;EAE5C,IAAI,CAAC,OACH,OAAO,sBAAsB,WAAW;EAK1C,OAAO,sBAAsB,aAAa,MAFhB,YAAY,MAAM,OAAO,eAAe,CAEb;CACvD;;;;;;CAOA,MAAa,OAAsB;EACjC,IAAI,CAAC,KAAK,QAAQ,UAChB;EAGF,MAAM,oBACJ,KAAK,QAAQ,UACb,KAAK,KAAK,GACV,KAAK,gBAAgB,IACrB,UAAS,KAAK,SAAS,KAAK,CAC9B;CACF;;;;;CAMA,AAAQ,YAAY,MAAc,SAAiC;EACjE,MAAM,SAAS,KAAK,YAAY,IAAI,IAAI;EAExC,IAAI,CAAC,UAAU,OAAO,WAAW,GAC/B,MAAM,IAAI,oBAAoB,IAAI;EAGpC,MAAM,SAAS,UACX,OAAO,MAAK,cAAa,UAAU,YAAY,OAAO,IACtD,OAAO,OAAO,SAAS;EAE3B,IAAI,CAAC,QACH,MAAM,IAAI,oBAAoB,MAAM,EAClC,SAAS;GAAE;GAAM;EAAQ,EAC3B,CAAC;EAGH,OAAO;CACT;;;;;CAMA,AAAQ,sBAAsB,YAAoB,SAA0B;EAC1E,MAAM,SAAS,KAAK,YAAY,IAAI,UAAU;EAE9C,IAAI,CAAC,UAAU,OAAO,WAAW,GAC/B,OAAO;EAGT,MAAM,SAAS,UACX,OAAO,MAAK,cAAa,UAAU,YAAY,OAAO,IACtD,OAAO,OAAO,SAAS;EAE3B,OAAO,SAAS,OAAO,WAAW;CACpC;;;;;CAMA,AAAQ,eACN,MACA,SACA,cACM;EACN,IAAI,CAAC,QAAQ,YAAY,QAAQ,SAAS,WAAW,GACnD;EAGF,MAAM,UAAU,QAAQ,SAAS,QAC/B,QAAO,aAAa,SAAS,UAAa,aAAa,SAAS,QAAQ,aAAa,SAAS,EAChG;EAEA,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,sBACR,WAAW,KAAK,aAAa,QAAQ,QAAQ,mCAC3C,QAAQ,SAAS,IAAI,MAAM,GAC5B,IAAI,QAAQ,KAAK,IAAI,EAAE,IACxB,EAAE,SAAS;GAAE;GAAM,SAAS,QAAQ;GAAS;EAAQ,EAAE,CACzD;CAEJ;;CAGA,AAAQ,kBAAiC;EACvC,OAAO,CAAC,GAAG,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,eAAe;GAChE;GACA,UAAU,CAAC,GAAG,QAAQ;EACxB,EAAE;CACJ;AACF;AA0CA,SAAS,cACP,OACA,cAC+C;CAI/C,IAAI,OAAO,UAAU,UACnB,OAAO,sBAAsB,CAAC,CAAC,IAAI,OAAO,YAAY;CAIxD,OAAO,IAAI,eAAe,KAAK;AACjC;;;;;;;;;;AAWA,MAAa,SAA+B"}