@warlock.js/ai 4.6.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/cjs/index.cjs +3 -1
- package/cjs/{src-Bmajk4Qg.cjs → src-DBn2_pbG.cjs} +1 -1
- package/cjs/{src-OZyDYHxm.cjs → src-DTlN47aO.cjs} +552 -17
- package/cjs/src-DTlN47aO.cjs.map +1 -0
- package/esm/agent/agent-input-builder.mjs +1 -0
- package/esm/agent/agent-input-builder.mjs.map +1 -1
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/system-prompt.contract.d.mts +148 -1
- package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
- package/esm/errors/error-code.type.d.mts +1 -1
- package/esm/errors/index.d.mts +1 -0
- package/esm/errors/index.mjs +1 -0
- package/esm/errors/prompt-refinement-error.d.mts +36 -0
- package/esm/errors/prompt-refinement-error.d.mts.map +1 -0
- package/esm/errors/prompt-refinement-error.mjs +27 -0
- package/esm/errors/prompt-refinement-error.mjs.map +1 -0
- package/esm/index.d.mts +4 -2
- package/esm/index.mjs +3 -1
- package/esm/prompts/prompts-manager.d.mts.map +1 -1
- package/esm/prompts/prompts-manager.mjs +1 -1
- package/esm/prompts/prompts-manager.mjs.map +1 -1
- package/esm/prompts/prompts-manager.type.d.mts +15 -0
- package/esm/prompts/prompts-manager.type.d.mts.map +1 -1
- package/esm/prompts/prompts-validate.mjs +0 -0
- package/esm/prompts/prompts-validate.mjs.map +1 -1
- package/esm/system-prompt/index.d.mts +1 -0
- package/esm/system-prompt/index.mjs +1 -0
- package/esm/system-prompt/refined-system-prompt.d.mts +184 -0
- package/esm/system-prompt/refined-system-prompt.d.mts.map +1 -0
- package/esm/system-prompt/refined-system-prompt.mjs +461 -0
- package/esm/system-prompt/refined-system-prompt.mjs.map +1 -0
- package/esm/system-prompt/system-prompt.d.mts +14 -1
- package/esm/system-prompt/system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/system-prompt.mjs +19 -0
- package/esm/system-prompt/system-prompt.mjs.map +1 -1
- package/llms-full.txt +104 -1
- package/llms.txt +2 -1
- package/package.json +3 -3
- package/skills/README.md +4 -0
- package/skills/manage-prompts/SKILL.md +8 -1
- package/skills/refine-prompts/SKILL.md +91 -0
- package/skills/write-system-prompt/SKILL.md +1 -0
- package/cjs/src-OZyDYHxm.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts-manager.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-manager.ts"],"sourcesContent":["import type { Placeholders } from \"../contracts/placeholders.type\";\nimport type {\n SystemPromptBlockContract,\n SystemPromptContract,\n SystemPromptMeta,\n} from \"../contracts/system-prompt.contract\";\nimport { InvalidRequestError } from \"../errors\";\nimport { Instruction } from \"../system-prompt/instruction\";\nimport { Persona } from \"../system-prompt/persona\";\nimport { SystemPrompt } from \"../system-prompt/system-prompt\";\nimport type {\n PromptsManagerContract,\n PromptsManagerEntry,\n PromptsManagerRegisterOptions,\n} from \"./prompts-manager.contract\";\nimport type {\n ExportedPromptVersion,\n ExportedRegistry,\n PromptDiff,\n PromptDiffBlock,\n PromptJudgeCacheLike,\n PromptsManagerOptions,\n PromptTemplateVersion,\n PromptValidateTarget,\n PromptValidationResult,\n PromptsValidateOptions,\n} from \"./prompts-manager.type\";\nimport {\n describeContractTarget,\n findMissingPlaceholders,\n findUnreferencedRequired,\n judgePromptBodyCached,\n} from \"./prompts-validate\";\n\n/**\n * Build the `name@version` registry key. Centralized so the duplicate check,\n * `get`, and `composedFrom` provenance all agree on one label shape.\n */\nexport function promptKey(name: string, version: string): string {\n return `${name}@${version}`;\n}\n\n/**\n * Serialize a prompt's observable content — its ordered blocks (discriminator\n * + raw template text) — into a stable signature. Two prompts with the same\n * blocks in the same order share a signature, which is how `register()` tells\n * an idempotent re-registration from a genuine clash. Meta is intentionally\n * excluded: provenance / description should not defeat idempotency.\n */\nfunction contentSignature(contract: SystemPromptContract): string {\n return JSON.stringify(\n contract.blocks.map(block => [block.type, block.text]),\n );\n}\n\n/**\n * Reconstruct a block from its `{ type, text }` snapshot — `persona` blocks\n * become a `Persona`, everything else an `Instruction`. The inverse of the\n * flattening `export()` performs, so an imported registry resolves identically.\n */\nfunction blockFromSnapshot(block: PromptDiffBlock): SystemPromptBlockContract {\n return block.type === \"persona\"\n ? new Persona(block.text)\n : new Instruction(block.text);\n}\n\n/**\n * Narrow a {@link PromptTemplateVersion} body to its ordered block list: a raw\n * string becomes one instruction block; an explicit block list is used verbatim.\n */\nfunction blocksFromTemplate(\n template: string | readonly SystemPromptBlockContract[],\n): SystemPromptBlockContract[] {\n if (typeof template === \"string\") {\n return [new Instruction(template)];\n }\n\n return [...template];\n}\n\n/**\n * Concrete `PromptsManagerContract` — a single registry of named, versioned\n * `SystemPromptContract` builders keyed by `name@version`.\n *\n * **Role.** The store behind `ai.prompts`. It holds one flat\n * `Map<string, PromptsManagerEntry>` keyed by `name@version`, plus a monotonic\n * counter that stamps each entry's `addedAt` so \"latest\" is deterministic\n * (highest `addedAt` for a name) without ever reading the wall clock.\n *\n * **Responsibility.**\n * - Owns: the registry map, the `addedAt` counter, the duplicate /\n * idempotency rule, default version derivation, latest selection, the\n * per-version tag pins, and the validate / diff / export / import surface.\n * - Does NOT own: prompt rendering (delegated to the contract's `resolve()`),\n * block composition, or the LLM-judge mechanics (delegated to the eval\n * `judge` scorer via `prompts-validate`).\n *\n * Users construct via the `prompts()` factory — `new PromptsManager()` is not\n * the public API.\n */\nclass PromptsManager implements PromptsManagerContract {\n /** Flat registry keyed by `name@version`. */\n private readonly entries = new Map<string, PromptsManagerEntry>();\n\n /** First-seen order of names, for a stable `list()`. */\n private readonly names: string[] = [];\n\n /** Per-name tag pins: `name` → (`tag` → `version`). */\n private readonly pins = new Map<string, Map<string, string>>();\n\n /** Optional process-level judge-verdict memo (absent ⇒ judge always runs live). */\n private readonly judgeCache?: PromptJudgeCacheLike;\n\n /** Monotonic insertion counter — the deterministic stand-in for a timestamp. */\n private counter = 0;\n\n public constructor(options: PromptsManagerOptions = {}) {\n this.judgeCache = options.judgeCache;\n }\n\n public register(\n contract: SystemPromptContract,\n options: PromptsManagerRegisterOptions = {},\n ): PromptsManagerContract {\n const meta = contract.meta();\n // An explicit override (from define() / import()) wins over the contract's\n // own meta — it lets those bulk paths register an anonymous contract under\n // a name without the SystemPrompt constructor's default-manager auto-reg.\n const name = options.name ?? meta?.name;\n\n if (!name) {\n throw new InvalidRequestError(\n \"Cannot register a prompt without a name — set meta.name via \" +\n \"systemPrompt(input, { name }) or .meta({ name }).\",\n { context: { meta } },\n );\n }\n\n const version =\n options.version ?? meta?.version ?? this.nextVersion(name);\n const key = promptKey(name, version);\n const existing = this.entries.get(key);\n\n if (existing) {\n // Idempotent re-registration: identical content under the same\n // name@version is a no-op, not an error. Anything else is a clash.\n if (contentSignature(existing.contract) === contentSignature(contract)) {\n return this;\n }\n\n throw new InvalidRequestError(\n `A different prompt is already registered as \"${key}\".`,\n { context: { name, version } },\n );\n }\n\n if (!this.names.includes(name)) {\n this.names.push(name);\n }\n\n this.entries.set(key, {\n name,\n version,\n addedAt: this.counter++,\n contract,\n ...(options.tags ? { tags: options.tags } : {}),\n });\n\n return this;\n }\n\n public create(\n input?: string | ReadonlyArray<SystemPromptBlockContract>,\n meta?: SystemPromptMeta,\n ): SystemPromptContract {\n // Mirror `systemPromptFactory` exactly (no import — `system-prompt.ts`\n // already depends on this module, so importing its factory back here would\n // close an import cycle). A name in `meta` auto-registers into the\n // process-wide default manager via the SystemPrompt constructor.\n if (input === undefined) {\n return new SystemPrompt([], meta);\n }\n\n if (typeof input === \"string\") {\n return new SystemPrompt([new Instruction(input)], meta);\n }\n\n return new SystemPrompt([...input], meta);\n }\n\n public get(name: string, versionOrTag?: string): SystemPromptContract {\n return this.requireEntry(name, versionOrTag).contract;\n }\n\n public has(name: string, versionOrTag?: string): boolean {\n const { baseName, selector } = this.parseSelector(name, versionOrTag);\n\n if (selector !== undefined) {\n return this.resolveSelector(baseName, selector) !== undefined;\n }\n\n return this.latestEntry(baseName) !== undefined;\n }\n\n public list(): string[] {\n return [...this.names];\n }\n\n public versions(name: string): string[] {\n return [...this.entries.values()]\n .filter(entry => entry.name === name)\n .sort((a, b) => a.addedAt - b.addedAt)\n .map(entry => entry.version);\n }\n\n public resolve(\n name: string,\n versionOrTag?: string,\n placeholders?: Placeholders,\n ): string {\n return this.requireEntry(name, versionOrTag).contract.resolve(placeholders);\n }\n\n public define(\n name: string,\n versions: readonly PromptTemplateVersion[],\n ): PromptsManagerContract {\n for (const entry of versions) {\n const blocks = blocksFromTemplate(entry.template);\n // Anonymous contract (no name in meta ⇒ no SystemPrompt constructor\n // auto-registration into the default manager); the name/version are\n // supplied explicitly so define() targets only THIS manager.\n const contract = new SystemPrompt(blocks);\n\n this.register(contract, { name, version: entry.version });\n }\n\n return this;\n }\n\n public tag(\n name: string,\n tag: string,\n version: string,\n ): PromptsManagerContract {\n // Validate the target exists before pinning — a tag to a missing version is\n // an authoring mistake, not a silent dangling pin.\n if (!this.entries.has(promptKey(name, version))) {\n throw new InvalidRequestError(\n `Cannot tag \"${tag}\" — no prompt registered as \"${promptKey(\n name,\n version,\n )}\".`,\n { context: { name, tag, version } },\n );\n }\n\n const nameTags = this.pins.get(name) ?? new Map<string, string>();\n nameTags.set(tag, version);\n this.pins.set(name, nameTags);\n\n return this;\n }\n\n public async validate(\n target: PromptValidateTarget,\n options: PromptsValidateOptions = {},\n ): Promise<PromptValidationResult> {\n const { text, required } = this.describeTarget(target);\n\n const provided = new Set(Object.keys(options.placeholders ?? {}));\n const declared = new Set<string>([\n ...required,\n ...(options.declare ?? []),\n ]);\n\n const missing = findMissingPlaceholders(text, provided, declared);\n\n // A declared-required key that the body never references is itself a\n // defect — surface it as an issue (it does not affect `missing` / `ok`,\n // which track unresolved placeholders).\n const unreferenced = findUnreferencedRequired(text, required);\n\n const ok = missing.length === 0;\n\n if (!options.judge) {\n if (unreferenced.length === 0) {\n return { ok, missing };\n }\n\n return {\n ok,\n missing,\n issues: unreferenced.map(\n key => `Required key \"${key}\" is never referenced in the prompt.`,\n ),\n };\n }\n\n // Per-call cache override wins over the manager-level memo.\n const cache = options.judgeCache ?? this.judgeCache;\n const judgeOutcome = await judgePromptBodyCached(text, options.judge, cache);\n\n const issues = [\n ...unreferenced.map(\n key => `Required key \"${key}\" is never referenced in the prompt.`,\n ),\n ...judgeOutcome.issues,\n ];\n\n return {\n ok,\n missing,\n ...(judgeOutcome.score !== undefined ? { score: judgeOutcome.score } : {}),\n issues,\n };\n }\n\n public diff(name: string, from: string, to: string): PromptDiff {\n const fromBlocks = this.snapshotBlocks(this.requireExact(name, from));\n const toBlocks = this.snapshotBlocks(this.requireExact(name, to));\n\n const added: PromptDiffBlock[] = [];\n const removed: PromptDiffBlock[] = [];\n const changed: { from: PromptDiffBlock; to: PromptDiffBlock }[] = [];\n\n const max = Math.max(fromBlocks.length, toBlocks.length);\n\n for (let index = 0; index < max; index++) {\n const left = fromBlocks[index];\n const right = toBlocks[index];\n\n if (left && !right) {\n removed.push(left);\n continue;\n }\n\n if (!left && right) {\n added.push(right);\n continue;\n }\n\n if (left && right && (left.type !== right.type || left.text !== right.text)) {\n changed.push({ from: left, to: right });\n }\n }\n\n return {\n name,\n from,\n to,\n added,\n removed,\n changed,\n identical:\n added.length === 0 && removed.length === 0 && changed.length === 0,\n };\n }\n\n public export(): ExportedRegistry {\n return {\n prompts: this.names.map(name => ({\n name,\n versions: this.versions(name).map(version =>\n this.exportVersion(name, version),\n ),\n })),\n };\n }\n\n public import(snapshot: ExportedRegistry): PromptsManagerContract {\n for (const exported of snapshot.prompts) {\n for (const version of exported.versions) {\n const blocks = version.blocks.map(blockFromSnapshot);\n // Anonymous (no `name` in meta) so the SystemPrompt constructor does\n // not auto-register into the default manager; description / required\n // ride along for round-trip fidelity. Name/version are explicit so the\n // import lands only on THIS manager.\n const contract = new SystemPrompt(blocks, {\n ...(version.description ? { description: version.description } : {}),\n ...(version.required ? { required: version.required } : {}),\n });\n\n this.register(contract, {\n name: exported.name,\n version: version.version,\n });\n\n for (const tag of version.tags ?? []) {\n this.tag(exported.name, tag, version.version);\n }\n }\n }\n\n return this;\n }\n\n /**\n * Flatten a registered version into its portable `{ version, blocks, tags?,\n * description?, required? }` snapshot for `export()`.\n */\n private exportVersion(name: string, version: string): ExportedPromptVersion {\n const entry = this.requireExact(name, version);\n const meta = entry.contract.meta();\n const tags = this.tagsForVersion(name, version);\n\n return {\n version,\n blocks: this.snapshotBlocks(entry),\n ...(tags.length > 0 ? { tags } : {}),\n ...(meta?.description ? { description: meta.description } : {}),\n ...(meta?.required ? { required: [...meta.required] } : {}),\n };\n }\n\n /** Every tag currently pinned to a specific `name@version`, in pin order. */\n private tagsForVersion(name: string, version: string): string[] {\n const nameTags = this.pins.get(name);\n\n if (!nameTags) {\n return [];\n }\n\n const tags: string[] = [];\n\n for (const [tag, pinnedVersion] of nameTags) {\n if (pinnedVersion === version) {\n tags.push(tag);\n }\n }\n\n return tags;\n }\n\n /** Flatten an entry's blocks to `{ type, text }` snapshots. */\n private snapshotBlocks(entry: PromptsManagerEntry): PromptDiffBlock[] {\n return entry.contract.blocks.map(block => ({\n type: block.type,\n text: block.text,\n }));\n }\n\n /**\n * Resolve the body + declared-required keys for any `validate` target: a\n * registered name (or `name@selector`), a `SystemPromptContract` instance, or\n * a raw string.\n */\n private describeTarget(target: PromptValidateTarget): {\n text: string;\n required: readonly string[];\n } {\n if (typeof target === \"string\") {\n // An inline `name@selector` (or a bare registered name) resolves through\n // the registry; anything else is a raw prompt body validated verbatim.\n const { baseName, selector } = this.parseSelector(target, undefined);\n const entry = selector\n ? this.resolveSelector(baseName, selector)\n : this.latestEntry(baseName);\n\n if (entry) {\n return describeContractTarget(entry.contract);\n }\n\n return { text: target, required: [] };\n }\n\n if (isSystemPromptContract(target)) {\n return describeContractTarget(target);\n }\n\n if (isBlock(target)) {\n return { text: target.text, required: [] };\n }\n\n throw new InvalidRequestError(\n \"validate() target must be a registered name, a SystemPromptContract, \" +\n \"a prompt block, or a raw string.\",\n { context: { target } },\n );\n }\n\n /**\n * The next integer version label for a name — `\"1\"` for the first, then the\n * count of existing versions plus one. String-typed to match the free-form\n * `version` label shape.\n */\n private nextVersion(name: string): string {\n const count = [...this.entries.values()].filter(\n entry => entry.name === name,\n ).length;\n\n return String(count + 1);\n }\n\n /** Pick the highest-`addedAt` entry for a name, or `undefined` when absent. */\n private latestEntry(name: string): PromptsManagerEntry | undefined {\n let latest: PromptsManagerEntry | undefined;\n\n for (const entry of this.entries.values()) {\n if (entry.name !== name) {\n continue;\n }\n\n if (!latest || entry.addedAt > latest.addedAt) {\n latest = entry;\n }\n }\n\n return latest;\n }\n\n /**\n * Split a name argument into its base name + optional selector. The selector\n * comes from the explicit second argument when present, else from an inline\n * `name@selector` in the first argument. A bare name yields no selector.\n */\n private parseSelector(\n name: string,\n versionOrTag: string | undefined,\n ): { baseName: string; selector: string | undefined } {\n if (versionOrTag !== undefined) {\n return { baseName: name, selector: versionOrTag };\n }\n\n const at = name.indexOf(\"@\");\n\n if (at > 0) {\n return { baseName: name.slice(0, at), selector: name.slice(at + 1) };\n }\n\n return { baseName: name, selector: undefined };\n }\n\n /**\n * Resolve a selector (a version label OR a pinned tag) to a concrete entry.\n * Version labels win over tags when both could match — the explicit label is\n * the more specific intent. Returns `undefined` when neither resolves.\n */\n private resolveSelector(\n name: string,\n selector: string,\n ): PromptsManagerEntry | undefined {\n const byVersion = this.entries.get(promptKey(name, selector));\n\n if (byVersion) {\n return byVersion;\n }\n\n const pinnedVersion = this.pins.get(name)?.get(selector);\n\n if (pinnedVersion !== undefined) {\n return this.entries.get(promptKey(name, pinnedVersion));\n }\n\n return undefined;\n }\n\n /**\n * Resolve an entry by name (+ optional version / tag / inline selector),\n * throwing {@link InvalidRequestError} when the name or the requested\n * selector is unknown. The single lookup path `get` / `resolve` share.\n */\n private requireEntry(\n name: string,\n versionOrTag?: string,\n ): PromptsManagerEntry {\n const { baseName, selector } = this.parseSelector(name, versionOrTag);\n\n if (selector !== undefined) {\n const entry = this.resolveSelector(baseName, selector);\n\n if (!entry) {\n throw new InvalidRequestError(\n `No prompt registered as \"${baseName}\" with version/tag \"${selector}\".`,\n { context: { name: baseName, selector } },\n );\n }\n\n return entry;\n }\n\n const latest = this.latestEntry(baseName);\n\n if (!latest) {\n throw new InvalidRequestError(\n `No prompt registered under name \"${baseName}\".`,\n { context: { name: baseName } },\n );\n }\n\n return latest;\n }\n\n /**\n * Resolve a name + EXACT version label to its entry (no tag fallback), for\n * `diff` / `export` where a concrete version is always required. Throws\n * {@link InvalidRequestError} on a miss.\n */\n private requireExact(name: string, version: string): PromptsManagerEntry {\n const entry = this.entries.get(promptKey(name, version));\n\n if (!entry) {\n throw new InvalidRequestError(\n `No prompt registered as \"${promptKey(name, version)}\".`,\n { context: { name, version } },\n );\n }\n\n return entry;\n }\n}\n\n/**\n * Narrow an arbitrary value to a `SystemPromptContract` — true when it exposes\n * the builder surface (`blocks` array + a callable `resolve`) AND a callable\n * `meta`. Robust across duplicate package copies (no `instanceof`).\n */\nfunction isSystemPromptContract(\n value: unknown,\n): value is SystemPromptContract {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { blocks?: unknown }).blocks) &&\n typeof (value as { resolve?: unknown }).resolve === \"function\" &&\n typeof (value as { meta?: unknown }).meta === \"function\"\n );\n}\n\n/**\n * Narrow an arbitrary value to a single `SystemPromptBlockContract` — true when\n * it carries a string `type` + `text` and a callable `resolve` but is NOT a\n * full prompt (no `blocks` array). Lets `validate` accept a lone block.\n */\nfunction isBlock(value: unknown): value is SystemPromptBlockContract {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\" &&\n typeof (value as { text?: unknown }).text === \"string\" &&\n typeof (value as { resolve?: unknown }).resolve === \"function\"\n );\n}\n\n/**\n * Create a new, isolated prompts manager.\n *\n * **Role.** Public factory for {@link PromptsManagerContract} — keeps\n * user-facing code free of `new` and consistent with the other `ai.*`\n * factories. Each call returns a fresh registry, so parallel test suites and\n * multi-tenant apps never share mutable global prompt state.\n *\n * The process-wide instance that named `systemPrompt(...)` builders\n * auto-register into is `ai.prompts` (see {@link defaultPromptsManager}).\n *\n * @param options - Optional wiring, notably a `judgeCache` that memoizes\n * LLM-judge verdicts (absent ⇒ every judge pass runs live).\n *\n * @example\n * const registry = prompts();\n * registry.register(systemPrompt(\"You are support.\", { name: \"support\" }));\n * registry.resolve(\"support\"); // \"You are support.\"\n *\n * @example\n * // Memoize judge verdicts across validations.\n * const registry = prompts({ judgeCache: new MemoryCacheDriver() });\n */\nexport function prompts(options?: PromptsManagerOptions): PromptsManagerContract {\n return new PromptsManager(options);\n}\n\n/**\n * The process-wide default manager that named prompts auto-register into.\n *\n * Held as a module-level singleton (lazily created on first access) so\n * `system-prompt.ts` can register a named builder without importing the\n * `PromptsManager` class — keeping the auto-registration seam free of a\n * runtime import cycle.\n */\nlet defaultManager: PromptsManagerContract | undefined;\n\n/** Accessor for the process-wide default {@link PromptsManagerContract}. */\nexport function defaultPromptsManager(): PromptsManagerContract {\n if (!defaultManager) {\n defaultManager = new PromptsManager();\n }\n\n return defaultManager;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prompts-manager.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-manager.ts"],"sourcesContent":["import type { Placeholders } from \"../contracts/placeholders.type\";\nimport type {\n SystemPromptBlockContract,\n SystemPromptContract,\n SystemPromptMeta,\n} from \"../contracts/system-prompt.contract\";\nimport { InvalidRequestError } from \"../errors\";\nimport { Instruction } from \"../system-prompt/instruction\";\nimport { Persona } from \"../system-prompt/persona\";\nimport { SystemPrompt } from \"../system-prompt/system-prompt\";\nimport type {\n PromptsManagerContract,\n PromptsManagerEntry,\n PromptsManagerRegisterOptions,\n} from \"./prompts-manager.contract\";\nimport type {\n ExportedPromptVersion,\n ExportedRegistry,\n PromptDiff,\n PromptDiffBlock,\n PromptJudgeCacheLike,\n PromptsManagerOptions,\n PromptTemplateVersion,\n PromptValidateTarget,\n PromptValidationResult,\n PromptsValidateOptions,\n} from \"./prompts-manager.type\";\nimport {\n describeContractTarget,\n findMissingPlaceholders,\n findUnreferencedRequired,\n judgePromptBodyCached,\n} from \"./prompts-validate\";\n\n/**\n * Build the `name@version` registry key. Centralized so the duplicate check,\n * `get`, and `composedFrom` provenance all agree on one label shape.\n */\nexport function promptKey(name: string, version: string): string {\n return `${name}@${version}`;\n}\n\n/**\n * Serialize a prompt's observable content — its ordered blocks (discriminator\n * + raw template text) — into a stable signature. Two prompts with the same\n * blocks in the same order share a signature, which is how `register()` tells\n * an idempotent re-registration from a genuine clash. Meta is intentionally\n * excluded: provenance / description should not defeat idempotency.\n */\nfunction contentSignature(contract: SystemPromptContract): string {\n return JSON.stringify(\n contract.blocks.map(block => [block.type, block.text]),\n );\n}\n\n/**\n * Reconstruct a block from its `{ type, text }` snapshot — `persona` blocks\n * become a `Persona`, everything else an `Instruction`. The inverse of the\n * flattening `export()` performs, so an imported registry resolves identically.\n */\nfunction blockFromSnapshot(block: PromptDiffBlock): SystemPromptBlockContract {\n return block.type === \"persona\"\n ? new Persona(block.text)\n : new Instruction(block.text);\n}\n\n/**\n * Narrow a {@link PromptTemplateVersion} body to its ordered block list: a raw\n * string becomes one instruction block; an explicit block list is used verbatim.\n */\nfunction blocksFromTemplate(\n template: string | readonly SystemPromptBlockContract[],\n): SystemPromptBlockContract[] {\n if (typeof template === \"string\") {\n return [new Instruction(template)];\n }\n\n return [...template];\n}\n\n/**\n * Concrete `PromptsManagerContract` — a single registry of named, versioned\n * `SystemPromptContract` builders keyed by `name@version`.\n *\n * **Role.** The store behind `ai.prompts`. It holds one flat\n * `Map<string, PromptsManagerEntry>` keyed by `name@version`, plus a monotonic\n * counter that stamps each entry's `addedAt` so \"latest\" is deterministic\n * (highest `addedAt` for a name) without ever reading the wall clock.\n *\n * **Responsibility.**\n * - Owns: the registry map, the `addedAt` counter, the duplicate /\n * idempotency rule, default version derivation, latest selection, the\n * per-version tag pins, and the validate / diff / export / import surface.\n * - Does NOT own: prompt rendering (delegated to the contract's `resolve()`),\n * block composition, or the LLM-judge mechanics (delegated to the eval\n * `judge` scorer via `prompts-validate`).\n *\n * Users construct via the `prompts()` factory — `new PromptsManager()` is not\n * the public API.\n */\nclass PromptsManager implements PromptsManagerContract {\n /** Flat registry keyed by `name@version`. */\n private readonly entries = new Map<string, PromptsManagerEntry>();\n\n /** First-seen order of names, for a stable `list()`. */\n private readonly names: string[] = [];\n\n /** Per-name tag pins: `name` → (`tag` → `version`). */\n private readonly pins = new Map<string, Map<string, string>>();\n\n /** Optional process-level judge-verdict memo (absent ⇒ judge always runs live). */\n private readonly judgeCache?: PromptJudgeCacheLike;\n\n /** Monotonic insertion counter — the deterministic stand-in for a timestamp. */\n private counter = 0;\n\n public constructor(options: PromptsManagerOptions = {}) {\n this.judgeCache = options.judgeCache;\n }\n\n public register(\n contract: SystemPromptContract,\n options: PromptsManagerRegisterOptions = {},\n ): PromptsManagerContract {\n const meta = contract.meta();\n // An explicit override (from define() / import()) wins over the contract's\n // own meta — it lets those bulk paths register an anonymous contract under\n // a name without the SystemPrompt constructor's default-manager auto-reg.\n const name = options.name ?? meta?.name;\n\n if (!name) {\n throw new InvalidRequestError(\n \"Cannot register a prompt without a name — set meta.name via \" +\n \"systemPrompt(input, { name }) or .meta({ name }).\",\n { context: { meta } },\n );\n }\n\n const version =\n options.version ?? meta?.version ?? this.nextVersion(name);\n const key = promptKey(name, version);\n const existing = this.entries.get(key);\n\n if (existing) {\n // Idempotent re-registration: identical content under the same\n // name@version is a no-op, not an error. Anything else is a clash.\n if (contentSignature(existing.contract) === contentSignature(contract)) {\n return this;\n }\n\n throw new InvalidRequestError(\n `A different prompt is already registered as \"${key}\".`,\n { context: { name, version } },\n );\n }\n\n if (!this.names.includes(name)) {\n this.names.push(name);\n }\n\n this.entries.set(key, {\n name,\n version,\n addedAt: this.counter++,\n contract,\n ...(options.tags ? { tags: options.tags } : {}),\n });\n\n return this;\n }\n\n public create(\n input?: string | ReadonlyArray<SystemPromptBlockContract>,\n meta?: SystemPromptMeta,\n ): SystemPromptContract {\n // Mirror `systemPromptFactory` exactly (no import — `system-prompt.ts`\n // already depends on this module, so importing its factory back here would\n // close an import cycle). A name in `meta` auto-registers into the\n // process-wide default manager via the SystemPrompt constructor.\n if (input === undefined) {\n return new SystemPrompt([], meta);\n }\n\n if (typeof input === \"string\") {\n return new SystemPrompt([new Instruction(input)], meta);\n }\n\n return new SystemPrompt([...input], meta);\n }\n\n public get(name: string, versionOrTag?: string): SystemPromptContract {\n return this.requireEntry(name, versionOrTag).contract;\n }\n\n public has(name: string, versionOrTag?: string): boolean {\n const { baseName, selector } = this.parseSelector(name, versionOrTag);\n\n if (selector !== undefined) {\n return this.resolveSelector(baseName, selector) !== undefined;\n }\n\n return this.latestEntry(baseName) !== undefined;\n }\n\n public list(): string[] {\n return [...this.names];\n }\n\n public versions(name: string): string[] {\n return [...this.entries.values()]\n .filter(entry => entry.name === name)\n .sort((a, b) => a.addedAt - b.addedAt)\n .map(entry => entry.version);\n }\n\n public resolve(\n name: string,\n versionOrTag?: string,\n placeholders?: Placeholders,\n ): string {\n return this.requireEntry(name, versionOrTag).contract.resolve(placeholders);\n }\n\n public define(\n name: string,\n versions: readonly PromptTemplateVersion[],\n ): PromptsManagerContract {\n for (const entry of versions) {\n const blocks = blocksFromTemplate(entry.template);\n // Anonymous contract (no name in meta ⇒ no SystemPrompt constructor\n // auto-registration into the default manager); the name/version are\n // supplied explicitly so define() targets only THIS manager.\n const contract = new SystemPrompt(blocks);\n\n this.register(contract, { name, version: entry.version });\n }\n\n return this;\n }\n\n public tag(\n name: string,\n tag: string,\n version: string,\n ): PromptsManagerContract {\n // Validate the target exists before pinning — a tag to a missing version is\n // an authoring mistake, not a silent dangling pin.\n if (!this.entries.has(promptKey(name, version))) {\n throw new InvalidRequestError(\n `Cannot tag \"${tag}\" — no prompt registered as \"${promptKey(\n name,\n version,\n )}\".`,\n { context: { name, tag, version } },\n );\n }\n\n const nameTags = this.pins.get(name) ?? new Map<string, string>();\n nameTags.set(tag, version);\n this.pins.set(name, nameTags);\n\n return this;\n }\n\n public async validate(\n target: PromptValidateTarget,\n options: PromptsValidateOptions = {},\n ): Promise<PromptValidationResult> {\n const { text, required } = this.describeTarget(target);\n\n const provided = new Set(Object.keys(options.placeholders ?? {}));\n const declared = new Set<string>([\n ...required,\n ...(options.declare ?? []),\n ]);\n\n const missing = findMissingPlaceholders(text, provided, declared);\n\n // A declared-required key that the body never references is itself a\n // defect — surface it as an issue (it does not affect `missing` / `ok`,\n // which track unresolved placeholders).\n const unreferenced = findUnreferencedRequired(text, required);\n\n const ok = missing.length === 0;\n\n if (!options.judge) {\n if (unreferenced.length === 0) {\n return { ok, missing };\n }\n\n return {\n ok,\n missing,\n issues: unreferenced.map(\n key => `Required key \"${key}\" is never referenced in the prompt.`,\n ),\n };\n }\n\n // Per-call cache override wins over the manager-level memo. `criteria`\n // (when set) replaces the built-in rubric the judge grades against.\n const cache = options.judgeCache ?? this.judgeCache;\n const judgeOutcome = await judgePromptBodyCached(\n text,\n options.judge,\n cache,\n options.criteria,\n );\n\n const issues = [\n ...unreferenced.map(\n key => `Required key \"${key}\" is never referenced in the prompt.`,\n ),\n ...judgeOutcome.issues,\n ];\n\n return {\n ok,\n missing,\n ...(judgeOutcome.score !== undefined ? { score: judgeOutcome.score } : {}),\n issues,\n };\n }\n\n public diff(name: string, from: string, to: string): PromptDiff {\n const fromBlocks = this.snapshotBlocks(this.requireExact(name, from));\n const toBlocks = this.snapshotBlocks(this.requireExact(name, to));\n\n const added: PromptDiffBlock[] = [];\n const removed: PromptDiffBlock[] = [];\n const changed: { from: PromptDiffBlock; to: PromptDiffBlock }[] = [];\n\n const max = Math.max(fromBlocks.length, toBlocks.length);\n\n for (let index = 0; index < max; index++) {\n const left = fromBlocks[index];\n const right = toBlocks[index];\n\n if (left && !right) {\n removed.push(left);\n continue;\n }\n\n if (!left && right) {\n added.push(right);\n continue;\n }\n\n if (left && right && (left.type !== right.type || left.text !== right.text)) {\n changed.push({ from: left, to: right });\n }\n }\n\n return {\n name,\n from,\n to,\n added,\n removed,\n changed,\n identical:\n added.length === 0 && removed.length === 0 && changed.length === 0,\n };\n }\n\n public export(): ExportedRegistry {\n return {\n prompts: this.names.map(name => ({\n name,\n versions: this.versions(name).map(version =>\n this.exportVersion(name, version),\n ),\n })),\n };\n }\n\n public import(snapshot: ExportedRegistry): PromptsManagerContract {\n for (const exported of snapshot.prompts) {\n for (const version of exported.versions) {\n const blocks = version.blocks.map(blockFromSnapshot);\n // Anonymous (no `name` in meta) so the SystemPrompt constructor does\n // not auto-register into the default manager; description / required\n // ride along for round-trip fidelity. Name/version are explicit so the\n // import lands only on THIS manager.\n const contract = new SystemPrompt(blocks, {\n ...(version.description ? { description: version.description } : {}),\n ...(version.required ? { required: version.required } : {}),\n });\n\n this.register(contract, {\n name: exported.name,\n version: version.version,\n });\n\n for (const tag of version.tags ?? []) {\n this.tag(exported.name, tag, version.version);\n }\n }\n }\n\n return this;\n }\n\n /**\n * Flatten a registered version into its portable `{ version, blocks, tags?,\n * description?, required? }` snapshot for `export()`.\n */\n private exportVersion(name: string, version: string): ExportedPromptVersion {\n const entry = this.requireExact(name, version);\n const meta = entry.contract.meta();\n const tags = this.tagsForVersion(name, version);\n\n return {\n version,\n blocks: this.snapshotBlocks(entry),\n ...(tags.length > 0 ? { tags } : {}),\n ...(meta?.description ? { description: meta.description } : {}),\n ...(meta?.required ? { required: [...meta.required] } : {}),\n };\n }\n\n /** Every tag currently pinned to a specific `name@version`, in pin order. */\n private tagsForVersion(name: string, version: string): string[] {\n const nameTags = this.pins.get(name);\n\n if (!nameTags) {\n return [];\n }\n\n const tags: string[] = [];\n\n for (const [tag, pinnedVersion] of nameTags) {\n if (pinnedVersion === version) {\n tags.push(tag);\n }\n }\n\n return tags;\n }\n\n /** Flatten an entry's blocks to `{ type, text }` snapshots. */\n private snapshotBlocks(entry: PromptsManagerEntry): PromptDiffBlock[] {\n return entry.contract.blocks.map(block => ({\n type: block.type,\n text: block.text,\n }));\n }\n\n /**\n * Resolve the body + declared-required keys for any `validate` target: a\n * registered name (or `name@selector`), a `SystemPromptContract` instance, or\n * a raw string.\n */\n private describeTarget(target: PromptValidateTarget): {\n text: string;\n required: readonly string[];\n } {\n if (typeof target === \"string\") {\n // An inline `name@selector` (or a bare registered name) resolves through\n // the registry; anything else is a raw prompt body validated verbatim.\n const { baseName, selector } = this.parseSelector(target, undefined);\n const entry = selector\n ? this.resolveSelector(baseName, selector)\n : this.latestEntry(baseName);\n\n if (entry) {\n return describeContractTarget(entry.contract);\n }\n\n return { text: target, required: [] };\n }\n\n if (isSystemPromptContract(target)) {\n return describeContractTarget(target);\n }\n\n if (isBlock(target)) {\n return { text: target.text, required: [] };\n }\n\n throw new InvalidRequestError(\n \"validate() target must be a registered name, a SystemPromptContract, \" +\n \"a prompt block, or a raw string.\",\n { context: { target } },\n );\n }\n\n /**\n * The next integer version label for a name — `\"1\"` for the first, then the\n * count of existing versions plus one. String-typed to match the free-form\n * `version` label shape.\n */\n private nextVersion(name: string): string {\n const count = [...this.entries.values()].filter(\n entry => entry.name === name,\n ).length;\n\n return String(count + 1);\n }\n\n /** Pick the highest-`addedAt` entry for a name, or `undefined` when absent. */\n private latestEntry(name: string): PromptsManagerEntry | undefined {\n let latest: PromptsManagerEntry | undefined;\n\n for (const entry of this.entries.values()) {\n if (entry.name !== name) {\n continue;\n }\n\n if (!latest || entry.addedAt > latest.addedAt) {\n latest = entry;\n }\n }\n\n return latest;\n }\n\n /**\n * Split a name argument into its base name + optional selector. The selector\n * comes from the explicit second argument when present, else from an inline\n * `name@selector` in the first argument. A bare name yields no selector.\n */\n private parseSelector(\n name: string,\n versionOrTag: string | undefined,\n ): { baseName: string; selector: string | undefined } {\n if (versionOrTag !== undefined) {\n return { baseName: name, selector: versionOrTag };\n }\n\n const at = name.indexOf(\"@\");\n\n if (at > 0) {\n return { baseName: name.slice(0, at), selector: name.slice(at + 1) };\n }\n\n return { baseName: name, selector: undefined };\n }\n\n /**\n * Resolve a selector (a version label OR a pinned tag) to a concrete entry.\n * Version labels win over tags when both could match — the explicit label is\n * the more specific intent. Returns `undefined` when neither resolves.\n */\n private resolveSelector(\n name: string,\n selector: string,\n ): PromptsManagerEntry | undefined {\n const byVersion = this.entries.get(promptKey(name, selector));\n\n if (byVersion) {\n return byVersion;\n }\n\n const pinnedVersion = this.pins.get(name)?.get(selector);\n\n if (pinnedVersion !== undefined) {\n return this.entries.get(promptKey(name, pinnedVersion));\n }\n\n return undefined;\n }\n\n /**\n * Resolve an entry by name (+ optional version / tag / inline selector),\n * throwing {@link InvalidRequestError} when the name or the requested\n * selector is unknown. The single lookup path `get` / `resolve` share.\n */\n private requireEntry(\n name: string,\n versionOrTag?: string,\n ): PromptsManagerEntry {\n const { baseName, selector } = this.parseSelector(name, versionOrTag);\n\n if (selector !== undefined) {\n const entry = this.resolveSelector(baseName, selector);\n\n if (!entry) {\n throw new InvalidRequestError(\n `No prompt registered as \"${baseName}\" with version/tag \"${selector}\".`,\n { context: { name: baseName, selector } },\n );\n }\n\n return entry;\n }\n\n const latest = this.latestEntry(baseName);\n\n if (!latest) {\n throw new InvalidRequestError(\n `No prompt registered under name \"${baseName}\".`,\n { context: { name: baseName } },\n );\n }\n\n return latest;\n }\n\n /**\n * Resolve a name + EXACT version label to its entry (no tag fallback), for\n * `diff` / `export` where a concrete version is always required. Throws\n * {@link InvalidRequestError} on a miss.\n */\n private requireExact(name: string, version: string): PromptsManagerEntry {\n const entry = this.entries.get(promptKey(name, version));\n\n if (!entry) {\n throw new InvalidRequestError(\n `No prompt registered as \"${promptKey(name, version)}\".`,\n { context: { name, version } },\n );\n }\n\n return entry;\n }\n}\n\n/**\n * Narrow an arbitrary value to a `SystemPromptContract` — true when it exposes\n * the builder surface (`blocks` array + a callable `resolve`) AND a callable\n * `meta`. Robust across duplicate package copies (no `instanceof`).\n */\nfunction isSystemPromptContract(\n value: unknown,\n): value is SystemPromptContract {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { blocks?: unknown }).blocks) &&\n typeof (value as { resolve?: unknown }).resolve === \"function\" &&\n typeof (value as { meta?: unknown }).meta === \"function\"\n );\n}\n\n/**\n * Narrow an arbitrary value to a single `SystemPromptBlockContract` — true when\n * it carries a string `type` + `text` and a callable `resolve` but is NOT a\n * full prompt (no `blocks` array). Lets `validate` accept a lone block.\n */\nfunction isBlock(value: unknown): value is SystemPromptBlockContract {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\" &&\n typeof (value as { text?: unknown }).text === \"string\" &&\n typeof (value as { resolve?: unknown }).resolve === \"function\"\n );\n}\n\n/**\n * Create a new, isolated prompts manager.\n *\n * **Role.** Public factory for {@link PromptsManagerContract} — keeps\n * user-facing code free of `new` and consistent with the other `ai.*`\n * factories. Each call returns a fresh registry, so parallel test suites and\n * multi-tenant apps never share mutable global prompt state.\n *\n * The process-wide instance that named `systemPrompt(...)` builders\n * auto-register into is `ai.prompts` (see {@link defaultPromptsManager}).\n *\n * @param options - Optional wiring, notably a `judgeCache` that memoizes\n * LLM-judge verdicts (absent ⇒ every judge pass runs live).\n *\n * @example\n * const registry = prompts();\n * registry.register(systemPrompt(\"You are support.\", { name: \"support\" }));\n * registry.resolve(\"support\"); // \"You are support.\"\n *\n * @example\n * // Memoize judge verdicts across validations.\n * const registry = prompts({ judgeCache: new MemoryCacheDriver() });\n */\nexport function prompts(options?: PromptsManagerOptions): PromptsManagerContract {\n return new PromptsManager(options);\n}\n\n/**\n * The process-wide default manager that named prompts auto-register into.\n *\n * Held as a module-level singleton (lazily created on first access) so\n * `system-prompt.ts` can register a named builder without importing the\n * `PromptsManager` class — keeping the auto-registration seam free of a\n * runtime import cycle.\n */\nlet defaultManager: PromptsManagerContract | undefined;\n\n/** Accessor for the process-wide default {@link PromptsManagerContract}. */\nexport function defaultPromptsManager(): PromptsManagerContract {\n if (!defaultManager) {\n defaultManager = new PromptsManager();\n }\n\n return defaultManager;\n}\n"],"mappings":"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"}
|
|
@@ -58,6 +58,21 @@ interface PromptsValidateOptions {
|
|
|
58
58
|
* `issues` note (leaving `score` undefined) on any failure.
|
|
59
59
|
*/
|
|
60
60
|
readonly judge?: ModelContract;
|
|
61
|
+
/**
|
|
62
|
+
* Your own rules to grade the prompt against — a single string or a list of
|
|
63
|
+
* short rules. When set (and a `judge` model is supplied), these REPLACE the
|
|
64
|
+
* built-in prompt-quality rubric, so the judge's `score` / `issues` reflect
|
|
65
|
+
* YOUR criteria (a failed rule is named in `issues`). Omitted ⇒ the default
|
|
66
|
+
* quality rubric. Purely advisory — like any judge output, it never flips the
|
|
67
|
+
* deterministic `ok`.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ai.prompts.validate("support@2", {
|
|
71
|
+
* judge: model,
|
|
72
|
+
* criteria: ["Addresses the user by {{name}}", "Never gives medical advice", "Under 200 words"],
|
|
73
|
+
* });
|
|
74
|
+
*/
|
|
75
|
+
readonly criteria?: string | readonly string[];
|
|
61
76
|
/**
|
|
62
77
|
* Per-call judge-verdict cache override. When set, takes precedence over the
|
|
63
78
|
* manager's `judgeCache` for this call only. Same memo semantics: only used
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts-manager.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-manager.type.ts"],"mappings":";;;;;;;AAWA;;;;;UAAiB,oBAAA;EAIkD;EAFjE,GAAA,cAAiB,GAAA,WAAc,OAAA,CAAQ,CAAA;EAAvC;EAEA,GAAA,CAAI,GAAA,UAAa,KAAA,WAAgB,YAAA,aAAyB,OAAA;AAAA;;;;UAM3C,qBAAA;EANE;;;;AAAgD;AAMnE;EANmB,SAaR,UAAA,GAAa,oBAAoB;AAAA;;AAAA;AAU5C;;;;AAAqE;KAAzD,oBAAA,YAAgC,yBAAyB;;;;UAKpD,sBAAA;
|
|
1
|
+
{"version":3,"file":"prompts-manager.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-manager.type.ts"],"mappings":";;;;;;;AAWA;;;;;UAAiB,oBAAA;EAIkD;EAFjE,GAAA,cAAiB,GAAA,WAAc,OAAA,CAAQ,CAAA;EAAvC;EAEA,GAAA,CAAI,GAAA,UAAa,KAAA,WAAgB,YAAA,aAAyB,OAAA;AAAA;;;;UAM3C,qBAAA;EANE;;;;AAAgD;AAMnE;EANmB,SAaR,UAAA,GAAa,oBAAoB;AAAA;;AAAA;AAU5C;;;;AAAqE;KAAzD,oBAAA,YAAgC,yBAAyB;;;;UAKpD,sBAAA;EA2CO;;;;;EAAA,SArCb,YAAA,GAAe,YAAA;EAcf;;;;EAAA,SARA,OAAA;EA+BiC;AAAA;AAU5C;;;;EAV4C,SAvBjC,KAAA,GAAQ,aAAA;EAsCR;;;;AAaM;AASjB;;;;;;;;AAQgE;EA9BrD,SAtBA,QAAA;EA2DqB;;;AAIjB;AAOf;EAXgC,SApDrB,UAAA,GAAa,oBAAA;AAAA;;;;;;;;UAUP,sBAAA;EA2DN;EAAA,SAzDA,EAAA;EA2DO;EAAA,SAxDP,OAAA;EA0DS;;;;;EAAA,SAnDT,KAAA;EAuDA;;AAAS;AAQpB;EARW,SAjDA,MAAA;AAAA;;;;;;;UASM,qBAAA;EAwDE;EAAA,SAtDR,OAAA;EA6DM;;;;EAAA,SAvDN,QAAA,oBAA4B,yBAAyB;AAAA;;;AAyDtB;AAO1C;UAzDiB,eAAA;;WAEN,IAAA;EAwDuB;EAAA,SAtDvB,IAAI;AAAA;;;;;UAOE,UAAA;;WAEN,IAAA;;WAEA,IAAA;;WAEA,EAAA;;WAEA,KAAA,EAAO,eAAA;;WAEP,OAAA,EAAS,eAAA;;WAET,OAAA;IAAW,IAAA,EAAM,eAAA;IAAiB,EAAA,EAAI,eAAA;EAAA;;WAEtC,SAAA;AAAA;;;;;;UAQM,qBAAA;EAAA,SACN,OAAA;EAAA,SACA,MAAA,EAAQ,eAAe;;WAEvB,IAAA;;WAEA,WAAA;;WAEA,QAAA;AAAA;;;;;UAOM,cAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA,EAAU,qBAAqB;AAAA;;;;;UAOzB,gBAAA;EAAA,SACN,OAAA,EAAS,cAAc;AAAA"}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts-validate.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-validate.ts"],"sourcesContent":["import { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { ModelContract } from \"../contracts/model.contract\";\nimport type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\nimport { judge } from \"../eval/judge-scorer\";\nimport { PROMPT_JUDGE_RUBRIC } from \"../prompt/prompt-validate\";\nimport type { PromptJudgeCacheLike } from \"./prompts-manager.type\";\n\n/**\n * Placeholder matcher — kept in lock-step with the matcher\n * `renderPlaceholders` (`src/system-prompt/render-placeholders.ts`) and the\n * legacy `prompt-validate` lint both use, so the deterministic validator sees\n * the exact same `{{key}}` / `{{a.b}}` / `{{key|default}}` set the renderer\n * substitutes. Global so every occurrence is collected.\n */\nconst PLACEHOLDER_PATTERN = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\n/**\n * One parsed placeholder occurrence — the key path (the part before any\n * `|default`) and whether the occurrence carried an inline default.\n */\ntype ParsedPlaceholder = {\n /** The dot-path key, e.g. `language` or `user.name`. */\n readonly path: string;\n /** Whether THIS occurrence declared an inline `{{key|default}}` fallback. */\n readonly hasDefault: boolean;\n};\n\n/**\n * Collect every distinct placeholder occurrence from a template, in first-seen\n * order. A key is considered to \"have a default\" only when EVERY occurrence of\n * it carries one — a single bare `{{key}}` means the renderer can leave it\n * unresolved, so the key is still required.\n */\nfunction collectPlaceholders(template: string): ParsedPlaceholder[] {\n const byPath = new Map<string, boolean>();\n const order: string[] = [];\n\n for (const match of template.matchAll(PLACEHOLDER_PATTERN)) {\n const [rawPath, rawDefault] = match[1].split(\"|\");\n const path = rawPath.trim();\n\n if (path.length === 0) {\n continue;\n }\n\n const hasDefault = rawDefault !== undefined;\n\n if (!byPath.has(path)) {\n byPath.set(path, hasDefault);\n order.push(path);\n } else {\n // A key only counts as defaulted when ALL of its occurrences default.\n byPath.set(path, (byPath.get(path) ?? false) && hasDefault);\n }\n }\n\n return order.map(path => ({ path, hasDefault: byPath.get(path) ?? false }));\n}\n\n/**\n * Run the deterministic (model-free) half of validation over a resolved prompt\n * body. Reports every `{{key}}` placeholder that has NO inline default and is\n * neither supplied in `provided` nor declared in `declared` (the prompt's\n * `meta.required` plus any caller-declared keys).\n *\n * Pure and synchronous — the only required half of `validate`; the LLM-judge\n * half is optional and layered on top.\n *\n * @param text - The resolved prompt body (placeholders may still be present).\n * @param provided - Placeholder keys the caller has supplied a value for.\n * @param declared - Placeholder keys declared as known/required (e.g. `meta.required`).\n */\nexport function findMissingPlaceholders(\n text: string,\n provided: ReadonlySet<string>,\n declared: ReadonlySet<string>,\n): string[] {\n const missing: string[] = [];\n\n for (const { path, hasDefault } of collectPlaceholders(text)) {\n if (hasDefault) {\n continue;\n }\n\n if (provided.has(path) || declared.has(path)) {\n continue;\n }\n\n missing.push(path);\n }\n\n return missing;\n}\n\n/**\n * A `meta.required` key absent from the template entirely — declared as\n * required but never referenced — is itself a defect worth surfacing. Returns\n * the declared keys that appear nowhere in the body.\n */\nexport function findUnreferencedRequired(\n text: string,\n required: readonly string[],\n): string[] {\n const present = new Set(collectPlaceholders(text).map(p => p.path));\n\n return required.filter(key => !present.has(key));\n}\n\n/**\n * Build the one-shot judge agent the optional LLM-as-judge pass runs. Mirrors\n * the legacy `prompt.ts` judge agent (strict-JSON instruction so the verdict\n * parses even without an output schema), so the two validate paths share one\n * judging contract.\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/** Outcome of the optional LLM-as-judge pass over a resolved prompt body. */\nexport type JudgeOutcome = {\n /**\n * The judge score in `[0, 1]`, or `undefined` when the judge degraded\n * (errored, returned no parseable verdict, or threw) — never a misleading\n * `0` masquerading as a real verdict.\n */\n readonly score?: number;\n /** Human-readable issues raised by the judge (its reason, or a degrade note). */\n readonly issues: string[];\n};\n\n/**\n * Run the optional LLM-as-judge pass over a resolved prompt body, REUSING the\n * eval `judge` scorer (the same path `prompt().validate` uses) so there is no\n * second judging implementation.\n *\n * **Nova-safe by contract.** The judge NEVER throws here: the eval scorer\n * already degrades a broken judge to `score: 0` with a failure reason, and any\n * exception that still escapes (model wiring, agent construction) is caught.\n * Both degrade paths surface `score: undefined` plus an issue note — so a flaky\n * judge can never fail an otherwise-valid prompt.\n *\n * @param text - The resolved prompt body under evaluation.\n * @param model - The model that powers the judge agent.\n */\nexport async function judgePromptBody(\n text: string,\n model: ModelContract,\n): Promise<JudgeOutcome> {\n try {\n const judgeAgent = buildJudgeAgent(model);\n const scorer = judge({ agent: judgeAgent, rubric: PROMPT_JUDGE_RUBRIC });\n\n const verdict = await scorer({\n case: { name: \"prompt-quality\", input: \"Grade the system prompt below.\" },\n text,\n // `result` is unused by the judge scorer's prompt builder; a minimal\n // stand-in keeps the structural contract satisfied without a real run.\n result: { text } as never,\n output: undefined,\n });\n\n // The eval scorer signals a degraded judge with score 0 + a diagnostic\n // reason (\"judge failed: …\" / \"judge returned no parseable verdict\"). Treat\n // that as \"no usable score\" rather than a real 0 verdict.\n const degraded =\n verdict.score === 0 &&\n typeof verdict.reason === \"string\" &&\n /^judge (failed|returned no parseable)/.test(verdict.reason);\n\n if (degraded) {\n return {\n issues: [`LLM-judge unavailable: ${verdict.reason}`],\n };\n }\n\n return {\n score: verdict.score,\n issues: verdict.reason ? [verdict.reason] : [],\n };\n } catch (error) {\n // Last-resort guard: never let a judge failure throw out of validate().\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n issues: [`LLM-judge unavailable: ${message}`],\n };\n }\n}\n\n/**\n * Non-cryptographic 53-bit string hash (cyrb53) — deterministic across runs\n * and platforms, with no `node:crypto` dependency (keeps the validate path\n * usable in any runtime). Mirrors the VCR request hash; collision-resistant\n * enough for a per-prompt judge-verdict keyspace. Returned as base-36.\n */\nfunction hashString(input: string): string {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input.charCodeAt(i);\n\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n\n const combined = 4294967296 * (2097151 & h2) + (h1 >>> 0);\n\n return combined.toString(36);\n}\n\n/**\n * Build the judge-verdict cache key for a resolved prompt body + judge model.\n * Combines the model's `provider:name` identity with a content hash of the\n * body, so the same prompt graded by the same judge hits the cache, while any\n * change to either misses it.\n */\nexport function judgeCacheKey(text: string, model: ModelContract): string {\n return `prompts.judge.${model.provider}:${model.name}.${hashString(text)}`;\n}\n\n/**\n * Run the judge pass with an OPTIONAL memo cache in front. On a hit, the stored\n * {@link JudgeOutcome} is returned without a model call; on a miss, the live\n * judge runs and a USABLE verdict (one carrying a `score`) is written back.\n * Degraded outcomes (no score) are NOT cached — a transient judge failure must\n * never poison the memo. A `null`/absent cache degrades to a direct judge call.\n *\n * Cache I/O is itself fault-tolerant: a `get`/`set` that rejects is swallowed\n * so a flaky cache can never break (or fail) validation.\n *\n * @param text - The resolved prompt body under evaluation.\n * @param model - The judge model.\n * @param cache - Optional verdict memo (any `CacheDriver`-like get/set surface).\n */\nexport async function judgePromptBodyCached(\n text: string,\n model: ModelContract,\n cache?: PromptJudgeCacheLike,\n): Promise<JudgeOutcome> {\n if (!cache) {\n return judgePromptBody(text, model);\n }\n\n const key = judgeCacheKey(text, model);\n\n const cached = await readJudgeCache(cache, key);\n\n if (cached) {\n return cached;\n }\n\n const outcome = await judgePromptBody(text, model);\n\n // Only memoize a usable verdict — never a degraded (scoreless) one.\n if (outcome.score !== undefined) {\n await writeJudgeCache(cache, key, outcome);\n }\n\n return outcome;\n}\n\n/** Read a cached verdict, swallowing any cache fault (treated as a miss). */\nasync function readJudgeCache(\n cache: PromptJudgeCacheLike,\n key: string,\n): Promise<JudgeOutcome | undefined> {\n try {\n const value = await cache.get<JudgeOutcome>(key);\n\n return value ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Write a verdict, swallowing any cache fault (best-effort memo). */\nasync function writeJudgeCache(\n cache: PromptJudgeCacheLike,\n key: string,\n outcome: JudgeOutcome,\n): Promise<void> {\n try {\n await cache.set(key, outcome);\n } catch {\n // Best-effort — a failed memo write never affects the validation result.\n }\n}\n\n/**\n * Resolve the body + declared-required keys for a validation target that is a\n * `SystemPromptContract` (named or anonymous). The declared set is the\n * prompt's `meta.required` (when present).\n */\nexport function describeContractTarget(contract: SystemPromptContract): {\n text: string;\n required: readonly string[];\n} {\n const meta = contract.meta();\n\n return {\n text: contract.resolve(),\n required: meta?.required ?? [],\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prompts-validate.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-validate.ts"],"sourcesContent":["import { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { ModelContract } from \"../contracts/model.contract\";\nimport type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\nimport { judge } from \"../eval/judge-scorer\";\nimport { PROMPT_JUDGE_RUBRIC } from \"../prompt/prompt-validate\";\nimport type { PromptJudgeCacheLike } from \"./prompts-manager.type\";\n\n/**\n * Placeholder matcher — kept in lock-step with the matcher\n * `renderPlaceholders` (`src/system-prompt/render-placeholders.ts`) and the\n * legacy `prompt-validate` lint both use, so the deterministic validator sees\n * the exact same `{{key}}` / `{{a.b}}` / `{{key|default}}` set the renderer\n * substitutes. Global so every occurrence is collected.\n */\nconst PLACEHOLDER_PATTERN = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\n/**\n * One parsed placeholder occurrence — the key path (the part before any\n * `|default`) and whether the occurrence carried an inline default.\n */\ntype ParsedPlaceholder = {\n /** The dot-path key, e.g. `language` or `user.name`. */\n readonly path: string;\n /** Whether THIS occurrence declared an inline `{{key|default}}` fallback. */\n readonly hasDefault: boolean;\n};\n\n/**\n * Collect every distinct placeholder occurrence from a template, in first-seen\n * order. A key is considered to \"have a default\" only when EVERY occurrence of\n * it carries one — a single bare `{{key}}` means the renderer can leave it\n * unresolved, so the key is still required.\n */\nfunction collectPlaceholders(template: string): ParsedPlaceholder[] {\n const byPath = new Map<string, boolean>();\n const order: string[] = [];\n\n for (const match of template.matchAll(PLACEHOLDER_PATTERN)) {\n const [rawPath, rawDefault] = match[1].split(\"|\");\n const path = rawPath.trim();\n\n if (path.length === 0) {\n continue;\n }\n\n const hasDefault = rawDefault !== undefined;\n\n if (!byPath.has(path)) {\n byPath.set(path, hasDefault);\n order.push(path);\n } else {\n // A key only counts as defaulted when ALL of its occurrences default.\n byPath.set(path, (byPath.get(path) ?? false) && hasDefault);\n }\n }\n\n return order.map(path => ({ path, hasDefault: byPath.get(path) ?? false }));\n}\n\n/**\n * Run the deterministic (model-free) half of validation over a resolved prompt\n * body. Reports every `{{key}}` placeholder that has NO inline default and is\n * neither supplied in `provided` nor declared in `declared` (the prompt's\n * `meta.required` plus any caller-declared keys).\n *\n * Pure and synchronous — the only required half of `validate`; the LLM-judge\n * half is optional and layered on top.\n *\n * @param text - The resolved prompt body (placeholders may still be present).\n * @param provided - Placeholder keys the caller has supplied a value for.\n * @param declared - Placeholder keys declared as known/required (e.g. `meta.required`).\n */\nexport function findMissingPlaceholders(\n text: string,\n provided: ReadonlySet<string>,\n declared: ReadonlySet<string>,\n): string[] {\n const missing: string[] = [];\n\n for (const { path, hasDefault } of collectPlaceholders(text)) {\n if (hasDefault) {\n continue;\n }\n\n if (provided.has(path) || declared.has(path)) {\n continue;\n }\n\n missing.push(path);\n }\n\n return missing;\n}\n\n/**\n * A `meta.required` key absent from the template entirely — declared as\n * required but never referenced — is itself a defect worth surfacing. Returns\n * the declared keys that appear nowhere in the body.\n */\nexport function findUnreferencedRequired(\n text: string,\n required: readonly string[],\n): string[] {\n const present = new Set(collectPlaceholders(text).map(p => p.path));\n\n return required.filter(key => !present.has(key));\n}\n\n/**\n * Build the one-shot judge agent the optional LLM-as-judge pass runs. Mirrors\n * the legacy `prompt.ts` judge agent (strict-JSON instruction so the verdict\n * parses even without an output schema), so the two validate paths share one\n * judging contract.\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 * Turn caller-supplied `criteria` into the judge rubric that replaces the\n * built-in {@link PROMPT_JUDGE_RUBRIC}. A single string is used verbatim;\n * a list is joined into a numbered rule set the judge must check ALL of.\n * Returns `undefined` for an empty/blank input, so the caller falls back\n * to the default rubric.\n *\n * @example\n * formatCriteria([\"Addresses the user by {{name}}\", \"Under 200 words\"]);\n * // → \"Grade the system prompt against ALL of these criteria …\\n1. …\\n2. …\"\n */\nexport function formatCriteria(\n criteria: string | readonly string[] | undefined,\n): string | undefined {\n if (criteria === undefined) {\n return undefined;\n }\n\n if (typeof criteria === \"string\") {\n const trimmed = criteria.trim();\n\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n const rules = criteria.map(rule => rule.trim()).filter(rule => rule.length > 0);\n\n if (rules.length === 0) {\n return undefined;\n }\n\n return (\n \"Grade the system prompt against ALL of the following criteria — it passes only if it satisfies every one:\\n\" +\n rules.map((rule, index) => `${index + 1}. ${rule}`).join(\"\\n\")\n );\n}\n\n/** Outcome of the optional LLM-as-judge pass over a resolved prompt body. */\nexport type JudgeOutcome = {\n /**\n * The judge score in `[0, 1]`, or `undefined` when the judge degraded\n * (errored, returned no parseable verdict, or threw) — never a misleading\n * `0` masquerading as a real verdict.\n */\n readonly score?: number;\n /** Human-readable issues raised by the judge (its reason, or a degrade note). */\n readonly issues: string[];\n};\n\n/**\n * Run the optional LLM-as-judge pass over a resolved prompt body, REUSING the\n * eval `judge` scorer (the same path `prompt().validate` uses) so there is no\n * second judging implementation.\n *\n * **Nova-safe by contract.** The judge NEVER throws here: the eval scorer\n * already degrades a broken judge to `score: 0` with a failure reason, and any\n * exception that still escapes (model wiring, agent construction) is caught.\n * Both degrade paths surface `score: undefined` plus an issue note — so a flaky\n * judge can never fail an otherwise-valid prompt.\n *\n * @param text - The resolved prompt body under evaluation.\n * @param model - The model that powers the judge agent.\n * @param criteria - Optional caller rules that REPLACE the built-in rubric\n * ({@link formatCriteria}). Omitted ⇒ the default prompt-quality rubric.\n */\nexport async function judgePromptBody(\n text: string,\n model: ModelContract,\n criteria?: string | readonly string[],\n): Promise<JudgeOutcome> {\n try {\n const judgeAgent = buildJudgeAgent(model);\n const scorer = judge({\n agent: judgeAgent,\n rubric: formatCriteria(criteria) ?? PROMPT_JUDGE_RUBRIC,\n });\n\n const verdict = await scorer({\n case: { name: \"prompt-quality\", input: \"Grade the system prompt below.\" },\n text,\n // `result` is unused by the judge scorer's prompt builder; a minimal\n // stand-in keeps the structural contract satisfied without a real run.\n result: { text } as never,\n output: undefined,\n });\n\n // The eval scorer signals a degraded judge with score 0 + a diagnostic\n // reason (\"judge failed: …\" / \"judge returned no parseable verdict\"). Treat\n // that as \"no usable score\" rather than a real 0 verdict.\n const degraded =\n verdict.score === 0 &&\n typeof verdict.reason === \"string\" &&\n /^judge (failed|returned no parseable)/.test(verdict.reason);\n\n if (degraded) {\n return {\n issues: [`LLM-judge unavailable: ${verdict.reason}`],\n };\n }\n\n return {\n score: verdict.score,\n issues: verdict.reason ? [verdict.reason] : [],\n };\n } catch (error) {\n // Last-resort guard: never let a judge failure throw out of validate().\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n issues: [`LLM-judge unavailable: ${message}`],\n };\n }\n}\n\n/**\n * Non-cryptographic 53-bit string hash (cyrb53) — deterministic across runs\n * and platforms, with no `node:crypto` dependency (keeps the validate path\n * usable in any runtime). Mirrors the VCR request hash; collision-resistant\n * enough for a per-prompt judge-verdict keyspace. Returned as base-36.\n */\nfunction hashString(input: string): string {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input.charCodeAt(i);\n\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n\n const combined = 4294967296 * (2097151 & h2) + (h1 >>> 0);\n\n return combined.toString(36);\n}\n\n/**\n * Build the judge-verdict cache key for a resolved prompt body + judge model\n * + the effective rubric. Combines the model's `provider:name` identity with a\n * content hash of the rubric-plus-body, so the same prompt graded by the same\n * judge against the same rules hits the cache — while a change to the prompt,\n * the model, OR the `criteria` misses it (different rules ⇒ different verdict).\n */\nexport function judgeCacheKey(\n text: string,\n model: ModelContract,\n criteria?: string | readonly string[],\n): string {\n const rubric = formatCriteria(criteria) ?? PROMPT_JUDGE_RUBRIC;\n\n return `prompts.judge.${model.provider}:${model.name}.${hashString(`${rubric}\u0000${text}`)}`;\n}\n\n/**\n * Run the judge pass with an OPTIONAL memo cache in front. On a hit, the stored\n * {@link JudgeOutcome} is returned without a model call; on a miss, the live\n * judge runs and a USABLE verdict (one carrying a `score`) is written back.\n * Degraded outcomes (no score) are NOT cached — a transient judge failure must\n * never poison the memo. A `null`/absent cache degrades to a direct judge call.\n *\n * Cache I/O is itself fault-tolerant: a `get`/`set` that rejects is swallowed\n * so a flaky cache can never break (or fail) validation.\n *\n * @param text - The resolved prompt body under evaluation.\n * @param model - The judge model.\n * @param cache - Optional verdict memo (any `CacheDriver`-like get/set surface).\n * @param criteria - Optional caller rules that REPLACE the built-in rubric; also\n * folded into the cache key so a re-validation with different rules re-runs.\n */\nexport async function judgePromptBodyCached(\n text: string,\n model: ModelContract,\n cache?: PromptJudgeCacheLike,\n criteria?: string | readonly string[],\n): Promise<JudgeOutcome> {\n if (!cache) {\n return judgePromptBody(text, model, criteria);\n }\n\n const key = judgeCacheKey(text, model, criteria);\n\n const cached = await readJudgeCache(cache, key);\n\n if (cached) {\n return cached;\n }\n\n const outcome = await judgePromptBody(text, model, criteria);\n\n // Only memoize a usable verdict — never a degraded (scoreless) one.\n if (outcome.score !== undefined) {\n await writeJudgeCache(cache, key, outcome);\n }\n\n return outcome;\n}\n\n/** Read a cached verdict, swallowing any cache fault (treated as a miss). */\nasync function readJudgeCache(\n cache: PromptJudgeCacheLike,\n key: string,\n): Promise<JudgeOutcome | undefined> {\n try {\n const value = await cache.get<JudgeOutcome>(key);\n\n return value ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Write a verdict, swallowing any cache fault (best-effort memo). */\nasync function writeJudgeCache(\n cache: PromptJudgeCacheLike,\n key: string,\n outcome: JudgeOutcome,\n): Promise<void> {\n try {\n await cache.set(key, outcome);\n } catch {\n // Best-effort — a failed memo write never affects the validation result.\n }\n}\n\n/**\n * Resolve the body + declared-required keys for a validation target that is a\n * `SystemPromptContract` (named or anonymous). The declared set is the\n * prompt's `meta.required` (when present).\n */\nexport function describeContractTarget(contract: SystemPromptContract): {\n text: string;\n required: readonly string[];\n} {\n const meta = contract.meta();\n\n return {\n text: contract.resolve(),\n required: meta?.required ?? [],\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SystemPrompt, systemPrompt } from "./system-prompt.mjs";
|
|
2
2
|
import { Instruction, instruction } from "./instruction.mjs";
|
|
3
3
|
import { Persona, persona } from "./persona.mjs";
|
|
4
|
+
import { RefinedSystemPrompt } from "./refined-system-prompt.mjs";
|
|
4
5
|
import { renderPlaceholders } from "./render-placeholders.mjs";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { renderPlaceholders } from "./render-placeholders.mjs";
|
|
2
2
|
import { Instruction, instruction } from "./instruction.mjs";
|
|
3
3
|
import { Persona, persona } from "./persona.mjs";
|
|
4
|
+
import { RefinedSystemPrompt } from "./refined-system-prompt.mjs";
|
|
4
5
|
import { SystemPrompt, systemPrompt } from "./system-prompt.mjs";
|
|
5
6
|
|
|
6
7
|
export { };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Placeholders } from "../contracts/placeholders.type.mjs";
|
|
2
|
+
import { PromptValidationResult, PromptsValidateOptions } from "../prompts/prompts-manager.type.mjs";
|
|
3
|
+
import { InstructionContract, PersonaContract, PromptRefineOptions, RefinedSystemPromptContract, RefinedSystemPromptOptions, SystemPromptBlockContract, SystemPromptContract, SystemPromptMergeOptions, SystemPromptMeta } from "../contracts/system-prompt.contract.mjs";
|
|
4
|
+
|
|
5
|
+
//#region ../@warlock.js/ai/src/system-prompt/refined-system-prompt.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Prompt-world collaborators injected by `system-prompt.ts` when it
|
|
8
|
+
* constructs the wrapper. Dependency-injected (not imported) so this module
|
|
9
|
+
* never imports `system-prompt.ts` / `prompts-manager.ts` back — both would
|
|
10
|
+
* close import cycles.
|
|
11
|
+
*/
|
|
12
|
+
type RefinedSystemPromptDeps = {
|
|
13
|
+
/** Construct a plain `SystemPrompt` (used by `refinePrompt()`). */buildPrompt(blocks: readonly SystemPromptBlockContract[], meta?: SystemPromptMeta): SystemPromptContract; /** `ai.prompts.validate(target, options)` — the contract's validate sugar. */
|
|
14
|
+
validatePrompt(target: SystemPromptContract, options?: PromptsValidateOptions): Promise<PromptValidationResult>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Concrete `RefinedSystemPromptContract` — the compiled form of a prompt.
|
|
18
|
+
*
|
|
19
|
+
* **Role.** A lazy prompt compiler: it wraps a human-authored
|
|
20
|
+
* `SystemPromptContract` and, on first use (agent path via `materialize()`,
|
|
21
|
+
* or explicitly via `refine()` / `refinePrompt()`), rewrites the raw source
|
|
22
|
+
* template into a model-optimized version through the configured refiner
|
|
23
|
+
* model, pins the result, and serves it from `resolve()` thereafter.
|
|
24
|
+
*
|
|
25
|
+
* **Responsibility.**
|
|
26
|
+
* - Owns: the compile pipeline (store lookup → refiner call → placeholder
|
|
27
|
+
* parity acceptance → single repair attempt → pin), single-flight
|
|
28
|
+
* de-duplication, and the never-throw fallback on the agent path.
|
|
29
|
+
* - Does NOT own: the source prompt's composition (delegated to the wrapped
|
|
30
|
+
* builder), placeholder rendering (each block's `resolve()`), or where a
|
|
31
|
+
* shared store persists (any `RefinedPromptStoreLike`).
|
|
32
|
+
*
|
|
33
|
+
* Trust rules (locked in `plans/warlock-4.7.0.md` §F4):
|
|
34
|
+
* 1. Lockfile posture — pinned until an input changes, never re-compiled
|
|
35
|
+
* silently over time (the store key hashes recipe version + model +
|
|
36
|
+
* criteria + source template).
|
|
37
|
+
* 2. Prose, never contract — the exact `{{placeholder}}` set must survive
|
|
38
|
+
* (`parityIssues`), or the rewrite is rejected.
|
|
39
|
+
* 3. Advisory with fallback — `materialize()` never throws; the original
|
|
40
|
+
* text is always a valid prompt. Explicit `refine()` throws
|
|
41
|
+
* `PromptRefinementError` instead (routes/CI need failures).
|
|
42
|
+
* 4. Reviewable — `refine()` exposes the compiled text; `refinePrompt()`
|
|
43
|
+
* makes it a first-class prompt with `refinedFrom` provenance.
|
|
44
|
+
*
|
|
45
|
+
* Builder chaining (`persona()` / `instruction()` / `merge()` / `meta()`)
|
|
46
|
+
* derives a NEW source and re-wraps it with the same refinement options —
|
|
47
|
+
* editing a compiled prompt naturally invalidates its pin (new source ⇒ new
|
|
48
|
+
* key). Forks follow the base builder's meta rules (they stay anonymous).
|
|
49
|
+
*
|
|
50
|
+
* Users construct via `systemPrompt(...).refined(options)` —
|
|
51
|
+
* `new RefinedSystemPrompt()` is not the public API.
|
|
52
|
+
*/
|
|
53
|
+
declare class RefinedSystemPrompt implements RefinedSystemPromptContract {
|
|
54
|
+
private readonly sourcePrompt;
|
|
55
|
+
private readonly options;
|
|
56
|
+
private readonly deps;
|
|
57
|
+
/** The pinned refined template, once compiled (in-memory mirror of the store). */
|
|
58
|
+
private refinedTemplate?;
|
|
59
|
+
/** Cached single-instruction block list for the compiled template. */
|
|
60
|
+
private refinedBlocks?;
|
|
61
|
+
/** Single-flight: the in-progress compilation shared by concurrent callers. */
|
|
62
|
+
private inflight?;
|
|
63
|
+
/**
|
|
64
|
+
* Monotonic compile-run id. Only the LATEST-started compilation may pin
|
|
65
|
+
* its result (instance + store) — a superseded run (e.g. a slow lazy
|
|
66
|
+
* compile overlapped by an explicit `{ fresh: true }`) still returns its
|
|
67
|
+
* text to its own awaiters but never overwrites the newer pin.
|
|
68
|
+
*/
|
|
69
|
+
private compileGeneration;
|
|
70
|
+
/** Settled-compile failures — gates the lazy path off after the cap. */
|
|
71
|
+
private compileFailures;
|
|
72
|
+
/** The lazy path warns at most once per instance when falling back. */
|
|
73
|
+
private warnedFallback;
|
|
74
|
+
constructor(sourcePrompt: SystemPromptContract, options: RefinedSystemPromptOptions, deps: RefinedSystemPromptDeps);
|
|
75
|
+
/** The human-authored prompt this wrapper compiles. */
|
|
76
|
+
get source(): SystemPromptContract;
|
|
77
|
+
/**
|
|
78
|
+
* Compiled blocks once materialized (a single instruction holding the
|
|
79
|
+
* refined template), the source's blocks until then — so every consumer,
|
|
80
|
+
* including the `ai.prompts` duck-type guards, always sees a real prompt.
|
|
81
|
+
*/
|
|
82
|
+
get blocks(): readonly SystemPromptBlockContract[];
|
|
83
|
+
/**
|
|
84
|
+
* Identity delegates to the source — a compiled prompt IS its source
|
|
85
|
+
* prompt (same `name@version` stamped on agent reports); the compiled text
|
|
86
|
+
* is an implementation detail of how it renders. The updater form renames
|
|
87
|
+
* the SOURCE and re-wraps, so refinement survives a rename (and the new
|
|
88
|
+
* source text registers under the new name per base-builder rules).
|
|
89
|
+
*/
|
|
90
|
+
meta(): SystemPromptMeta | undefined;
|
|
91
|
+
meta(meta: SystemPromptMeta): RefinedSystemPromptContract;
|
|
92
|
+
/** Derive a new source with the persona set, re-wrapped (pin invalidates). */
|
|
93
|
+
persona(value: PersonaContract | string): RefinedSystemPromptContract;
|
|
94
|
+
/** Derive a new source with the instruction appended, re-wrapped (pin invalidates). */
|
|
95
|
+
instruction(value: InstructionContract | string): RefinedSystemPromptContract;
|
|
96
|
+
/**
|
|
97
|
+
* Fold blocks / a contract / a registered name into the SOURCE and re-wrap
|
|
98
|
+
* — same three forms as the base builder's `merge`.
|
|
99
|
+
*/
|
|
100
|
+
merge(...blocks: readonly SystemPromptBlockContract[]): RefinedSystemPromptContract;
|
|
101
|
+
merge(source: SystemPromptContract): RefinedSystemPromptContract;
|
|
102
|
+
merge(name: string, options?: SystemPromptMergeOptions): RefinedSystemPromptContract;
|
|
103
|
+
/**
|
|
104
|
+
* Render the compiled template when pinned, the source otherwise —
|
|
105
|
+
* synchronous by contract, so laziness lives in `materialize()` /
|
|
106
|
+
* `refine()`, never here.
|
|
107
|
+
*/
|
|
108
|
+
resolve(placeholders?: Placeholders): string;
|
|
109
|
+
/**
|
|
110
|
+
* Validate THIS prompt (the compiled text once pinned, the source before)
|
|
111
|
+
* — sugar over `ai.prompts.validate(this, options)`, same as the base
|
|
112
|
+
* builder.
|
|
113
|
+
*/
|
|
114
|
+
validate(options?: PromptsValidateOptions): Promise<PromptValidationResult>;
|
|
115
|
+
/** Re-configure refinement for the same source (new options, fresh pin state). */
|
|
116
|
+
refined(options: RefinedSystemPromptOptions): RefinedSystemPromptContract;
|
|
117
|
+
/**
|
|
118
|
+
* The advisory hook the agent input builder awaits before its synchronous
|
|
119
|
+
* `resolve()`. Compiles + pins on first call; a refiner failure is warned
|
|
120
|
+
* once and swallowed — the original prompt is always a valid prompt.
|
|
121
|
+
*
|
|
122
|
+
* Bounded retries: after {@link MAX_LAZY_COMPILE_ATTEMPTS} settled compile
|
|
123
|
+
* failures this becomes a no-op for the instance lifetime, so a
|
|
124
|
+
* persistently-broken refiner can't tax every agent run with its failure
|
|
125
|
+
* latency. The explicit `refine()` stays live (and a success re-arms the
|
|
126
|
+
* pin for everyone).
|
|
127
|
+
*/
|
|
128
|
+
materialize(): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Compile now (or read the pin) and return the refined template string —
|
|
131
|
+
* placeholders intact. Throws `PromptRefinementError` on failure; pass
|
|
132
|
+
* `{ fresh: true }` to force a new take past the pin.
|
|
133
|
+
*/
|
|
134
|
+
refine(options?: PromptRefineOptions): Promise<string>;
|
|
135
|
+
/**
|
|
136
|
+
* Compile and wrap the refined template in a new plain `SystemPrompt` —
|
|
137
|
+
* one instruction block, `refinedFrom` / `refinerModel` provenance, the
|
|
138
|
+
* source's `required` keys carried over, and NO name (never
|
|
139
|
+
* auto-registers).
|
|
140
|
+
*/
|
|
141
|
+
refinePrompt(options?: PromptRefineOptions): Promise<SystemPromptContract>;
|
|
142
|
+
/** Re-wrap a derived source with the same refinement options. */
|
|
143
|
+
private rewrap;
|
|
144
|
+
/**
|
|
145
|
+
* One compilation pipeline for all three surfaces. `fresh` bypasses the
|
|
146
|
+
* instance pin AND the store read, and SUPERSEDES any compile already in
|
|
147
|
+
* flight: it claims the shared in-flight slot (so concurrent lazy callers
|
|
148
|
+
* join it instead of duplicating work) and bumps the compile generation
|
|
149
|
+
* (so the superseded run can no longer pin a stale result over it).
|
|
150
|
+
*/
|
|
151
|
+
private compile;
|
|
152
|
+
/**
|
|
153
|
+
* The actual compile run: store lookup (unless skipped) → refiner call →
|
|
154
|
+
* parity acceptance → pin. Pinning (instance + store) is gated on the
|
|
155
|
+
* run still being the latest-started generation — a superseded run
|
|
156
|
+
* returns its text but never overwrites the newer pin.
|
|
157
|
+
*/
|
|
158
|
+
private compileUncached;
|
|
159
|
+
/**
|
|
160
|
+
* The refiner model call: one attempt plus one parity-repair re-ask.
|
|
161
|
+
* Throws `PromptRefinementError` — `materialize()` is the layer that
|
|
162
|
+
* downgrades failures to a fallback.
|
|
163
|
+
*/
|
|
164
|
+
private runRefiner;
|
|
165
|
+
/** The one-shot refiner agent — named distinctively for observer reports. */
|
|
166
|
+
private buildRefinerAgent;
|
|
167
|
+
/**
|
|
168
|
+
* Deterministic pin key: any input change (recipe version, refiner model,
|
|
169
|
+
* criteria, source template) yields a new key, so stale pins are simply
|
|
170
|
+
* never read — the lockfile invalidation rule.
|
|
171
|
+
*/
|
|
172
|
+
private storeKey;
|
|
173
|
+
/** Pin the compiled template on the instance. */
|
|
174
|
+
private adopt;
|
|
175
|
+
/**
|
|
176
|
+
* One `[warlock-ai]` console warning per instance when the lazy path first
|
|
177
|
+
* falls back to the original text — mirroring the package's warn-once
|
|
178
|
+
* convention; suppressed under tests.
|
|
179
|
+
*/
|
|
180
|
+
private warnFallbackOnce;
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { RefinedSystemPrompt };
|
|
184
|
+
//# sourceMappingURL=refined-system-prompt.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refined-system-prompt.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/system-prompt/refined-system-prompt.ts"],"mappings":";;;;;;;AA0SA;;;;KAAY,uBAAA;EAKP,mEAHH,WAAA,CACE,MAAA,WAAiB,yBAAA,IACjB,IAAA,GAAO,gBAAA,GACN,oBAAA,EAKS;EAFZ,cAAA,CACE,MAAA,EAAQ,oBAAA,EACR,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,sBAAA;AAAA;;;;;;;;;;;;;;;;;AAAsB;AAwCnC;;;;;;;;;;;;;;;;;;;;cAAa,mBAAA,YAA+B,2BAAA;EAAA,iBAyBvB,YAAA;EAAA,iBACA,OAAA;EAAA,iBACA,IAAA;EAgHhB;EAAA,QAzIK,eAAA;EAgJL;EAAA,QA7IK,aAAA;EAgLgB;EAAA,QA7KhB,QAAA;EAwLI;;;;;;EAAA,QAhLJ,iBAAA;EASW;EAAA,QANX,eAAA;EAQW;EAAA,QALX,cAAA;cAGW,YAAA,EAAc,oBAAA,EACd,OAAA,EAAS,0BAAA,EACT,IAAA,EAAM,uBAAA;EAnBjB;EAAA,IAyBG,MAAA,IAAU,oBAAA;EAdb;;;;;EAAA,IAuBG,MAAA,aAAmB,yBAAA;EAhBX;;;;;;;EA2BZ,IAAA,IAAQ,gBAAA;EACR,IAAA,CAAK,IAAA,EAAM,gBAAA,GAAmB,2BAAA;EAA9B;EAYA,OAAA,CACL,KAAA,EAAO,eAAA,YACN,2BAAA;EAdS;EAmBL,WAAA,CACL,KAAA,EAAO,mBAAA,YACN,2BAAA;EATI;;;;EAiBA,KAAA,IACF,MAAA,WAAiB,yBAAA,KACnB,2BAAA;EACI,KAAA,CAAM,MAAA,EAAQ,oBAAA,GAAuB,2BAAA;EACrC,KAAA,CACL,IAAA,UACA,OAAA,GAAU,wBAAA,GACT,2BAAA;EAfA;;;;;EAkDI,OAAA,CAAQ,YAAA,GAAe,YAAA;EAvCT;;;;;EAmDd,QAAA,CACL,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,sBAAA;EAlDT;EAuDK,OAAA,CACL,OAAA,EAAS,0BAAA,GACR,2BAAA;EArBI;;;;;;;;;;;EAoCM,WAAA,IAAe,OAAA;EAAf;;;;;EAoBN,MAAA,CAAO,OAAA,GAAU,mBAAA,GAAsB,OAAA;EAUjC;;;;;;EAAA,YAAA,CACX,OAAA,GAAU,mBAAA,GACT,OAAA,CAAQ,oBAAA;EAuEG;EAAA,QAnDN,MAAA;EA8JA;;;;;AAkCgB;;EAlChB,QAnJA,OAAA;;;;;;;UAwCM,eAAA;;;;;;UAiDA,UAAA;;UA0DN,iBAAA;;;;;;UAaA,QAAA;;UAUA,KAAA;;;;;;UAWA,gBAAA;AAAA"}
|