@funkai/prompts 0.2.0 → 0.4.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/.turbo/turbo-build.log +18 -18
- package/CHANGELOG.md +47 -0
- package/dist/lib/cli.d.mts +10 -2
- package/dist/lib/cli.d.mts.map +1 -1
- package/dist/lib/cli.mjs +21 -10
- package/dist/lib/cli.mjs.map +1 -1
- package/dist/lib/{engine-B6JHetwb.d.mts → engine-BAYeiay3.d.mts} +4 -2
- package/dist/lib/{engine-B6JHetwb.d.mts.map → engine-BAYeiay3.d.mts.map} +1 -1
- package/dist/lib/{engine-B53whAlC.mjs → engine-CPKs9QbX.mjs} +5 -3
- package/dist/lib/engine-CPKs9QbX.mjs.map +1 -0
- package/dist/lib/index.d.mts +59 -2
- package/dist/lib/index.d.mts.map +1 -1
- package/dist/lib/index.mjs +94 -19
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/runtime.d.mts +1 -1
- package/dist/lib/runtime.mjs +1 -1
- package/dist/lib/{types-CiSdNM0W.d.mts → types-DmnHC99v.d.mts} +28 -6
- package/dist/lib/types-DmnHC99v.d.mts.map +1 -0
- package/package.json +5 -2
- package/src/clean.test.ts +1 -1
- package/src/clean.ts +25 -10
- package/src/engine.test.ts +11 -6
- package/src/engine.ts +4 -1
- package/src/group.test.ts +89 -0
- package/src/group.ts +36 -0
- package/src/index.ts +3 -1
- package/src/partials-dir.test.ts +2 -2
- package/src/prompt.test.ts +90 -0
- package/src/prompt.ts +46 -0
- package/src/registry.test.ts +13 -23
- package/src/registry.ts +31 -26
- package/src/types.ts +27 -10
- package/tsconfig.json +2 -1
- package/dist/lib/engine-B53whAlC.mjs.map +0 -1
- package/dist/lib/types-CiSdNM0W.d.mts.map +0 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
|
|
2
|
-
> @funkai/prompts@0.
|
|
2
|
+
> @funkai/prompts@0.4.0 build /home/runner/work/funkai/funkai/packages/prompts
|
|
3
3
|
> tsdown && cp -r src/prompts dist/prompts
|
|
4
4
|
|
|
5
|
-
[34mℹ[39m tsdown [2mv0.21.
|
|
5
|
+
[34mℹ[39m tsdown [2mv0.21.4[22m powered by rolldown [2mv1.0.0-rc.9[22m
|
|
6
6
|
[34mℹ[39m config file: [4m/home/runner/work/funkai/funkai/packages/prompts/tsdown.config.ts[24m
|
|
7
7
|
[34mℹ[39m entry: [34msrc/index.ts, src/runtime.ts, src/cli.ts[39m
|
|
8
8
|
[34mℹ[39m target: [34mnode22[39m
|
|
9
9
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
10
10
|
[34mℹ[39m Build start
|
|
11
|
-
[34mℹ[39m [2mdist/lib/[22m[1mindex.mjs[22m [
|
|
12
|
-
[34mℹ[39m [2mdist/lib/[22m[1mcli.mjs[22m [2m1.
|
|
11
|
+
[34mℹ[39m [2mdist/lib/[22m[1mindex.mjs[22m [2m3.92 kB[22m [2m│ gzip: 1.53 kB[22m
|
|
12
|
+
[34mℹ[39m [2mdist/lib/[22m[1mcli.mjs[22m [2m1.39 kB[22m [2m│ gzip: 0.75 kB[22m
|
|
13
13
|
[34mℹ[39m [2mdist/lib/[22m[1mruntime.mjs[22m [2m0.08 kB[22m [2m│ gzip: 0.08 kB[22m
|
|
14
|
-
[34mℹ[39m [2mdist/lib/[22mindex.mjs.map [
|
|
15
|
-
[34mℹ[39m [2mdist/lib/[
|
|
16
|
-
[34mℹ[39m [2mdist/lib/[
|
|
17
|
-
[34mℹ[39m [2mdist/lib/[22mengine-
|
|
18
|
-
[34mℹ[39m [2mdist/lib/[22mtypes-
|
|
19
|
-
[34mℹ[39m [2mdist/lib/[
|
|
20
|
-
[34mℹ[39m [2mdist/lib/[
|
|
21
|
-
[34mℹ[39m [2mdist/lib/[22mcli.d.mts.map [2m0.
|
|
22
|
-
[34mℹ[39m [2mdist/lib/[22m[32m[1mindex.d.mts[22m[39m [
|
|
23
|
-
[34mℹ[39m [2mdist/lib/[22m[32m[1mcli.d.mts[22m[39m [2m0.
|
|
14
|
+
[34mℹ[39m [2mdist/lib/[22mindex.mjs.map [2m5.72 kB[22m [2m│ gzip: 2.08 kB[22m
|
|
15
|
+
[34mℹ[39m [2mdist/lib/[22mcli.mjs.map [2m1.86 kB[22m [2m│ gzip: 0.90 kB[22m
|
|
16
|
+
[34mℹ[39m [2mdist/lib/[22mengine-CPKs9QbX.mjs.map [2m1.67 kB[22m [2m│ gzip: 0.85 kB[22m
|
|
17
|
+
[34mℹ[39m [2mdist/lib/[22mengine-CPKs9QbX.mjs [2m1.17 kB[22m [2m│ gzip: 0.62 kB[22m
|
|
18
|
+
[34mℹ[39m [2mdist/lib/[22mtypes-DmnHC99v.d.mts.map [2m0.66 kB[22m [2m│ gzip: 0.34 kB[22m
|
|
19
|
+
[34mℹ[39m [2mdist/lib/[22mindex.d.mts.map [2m0.45 kB[22m [2m│ gzip: 0.25 kB[22m
|
|
20
|
+
[34mℹ[39m [2mdist/lib/[22mengine-BAYeiay3.d.mts.map [2m0.22 kB[22m [2m│ gzip: 0.17 kB[22m
|
|
21
|
+
[34mℹ[39m [2mdist/lib/[22mcli.d.mts.map [2m0.18 kB[22m [2m│ gzip: 0.15 kB[22m
|
|
22
|
+
[34mℹ[39m [2mdist/lib/[22m[32m[1mindex.d.mts[22m[39m [2m3.02 kB[22m [2m│ gzip: 1.14 kB[22m
|
|
23
|
+
[34mℹ[39m [2mdist/lib/[22m[32m[1mcli.d.mts[22m[39m [2m0.94 kB[22m [2m│ gzip: 0.53 kB[22m
|
|
24
24
|
[34mℹ[39m [2mdist/lib/[22m[32m[1mruntime.d.mts[22m[39m [2m0.08 kB[22m [2m│ gzip: 0.08 kB[22m
|
|
25
|
-
[34mℹ[39m [2mdist/lib/[22m[32mtypes-
|
|
26
|
-
[34mℹ[39m [2mdist/lib/[22m[32mengine-
|
|
27
|
-
[34mℹ[39m 16 files, total:
|
|
28
|
-
[32m✔[39m Build complete in [
|
|
25
|
+
[34mℹ[39m [2mdist/lib/[22m[32mtypes-DmnHC99v.d.mts[39m [2m2.17 kB[22m [2m│ gzip: 0.94 kB[22m
|
|
26
|
+
[34mℹ[39m [2mdist/lib/[22m[32mengine-BAYeiay3.d.mts[39m [2m1.08 kB[22m [2m│ gzip: 0.58 kB[22m
|
|
27
|
+
[34mℹ[39m 16 files, total: 24.62 kB
|
|
28
|
+
[32m✔[39m Build complete in [32m2257ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# @funkai/prompts
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c8569db: feat(prompts): add createPrompt, createPromptGroup, and config-based group assignment
|
|
8
|
+
|
|
9
|
+
- Add `createPrompt<T>(config)` factory for building prompt modules at runtime and codegen
|
|
10
|
+
- Add `createPromptGroup(name, prompts)` for grouping prompt modules into namespaces
|
|
11
|
+
- Add `PromptConfig<T>` type for prompt module configuration
|
|
12
|
+
- Codegen now uses `createPrompt()` instead of raw object literals
|
|
13
|
+
- Scope name uniqueness to group+name instead of global name
|
|
14
|
+
- Derive file slugs and import names from group+name to avoid collisions
|
|
15
|
+
- Replace `roots` config field with `includes`/`excludes` glob patterns
|
|
16
|
+
- Add `groups` config field for pattern-based group assignment via picomatch
|
|
17
|
+
- Frontmatter `group` takes precedence over config-defined groups
|
|
18
|
+
- Updated banner format for generated files
|
|
19
|
+
|
|
20
|
+
## 0.3.0
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- b1e1d01: Fix bug and correctness issues across all packages.
|
|
25
|
+
|
|
26
|
+
**@funkai/agents**
|
|
27
|
+
|
|
28
|
+
- Export `createFlowEngine` and related types (`FlowEngineConfig`, `FlowFactory`, `CustomStepFactory`, `CustomStepDefinitions`, `TypedCustomSteps`)
|
|
29
|
+
- Guard `resolvedInput` before passing to `onError` hooks (was `undefined` cast as `TInput`)
|
|
30
|
+
- Fix `onStepStart` hook asymmetry: per-call override now merged with config hook
|
|
31
|
+
- Remove deprecated unused `TraceType` alias
|
|
32
|
+
|
|
33
|
+
**@funkai/prompts**
|
|
34
|
+
|
|
35
|
+
- **Breaking:** `strictFilters` and `ownPropertyOnly` removed from `CreateEngineOptions` (now enforced as non-overridable safety defaults)
|
|
36
|
+
|
|
37
|
+
**@funkai/models**
|
|
38
|
+
|
|
39
|
+
- Add `provider/model` format validation for model IDs
|
|
40
|
+
- Wrap `languageModel()` errors with model ID context
|
|
41
|
+
|
|
42
|
+
**@funkai/cli**
|
|
43
|
+
|
|
44
|
+
- Fix `$`-substitution bug in `flattenPartials` (`String.replace` function-form)
|
|
45
|
+
- Add error context for partial render failures
|
|
46
|
+
- `readJsonFile` now throws on malformed JSON instead of silently returning `{}`
|
|
47
|
+
- Replace naive YAML line parsing with proper `yaml` parser
|
|
48
|
+
- Extract `Liquid` engine to module-level singleton in `extractVariables`
|
|
49
|
+
|
|
3
50
|
## 0.2.0
|
|
4
51
|
|
|
5
52
|
### Minor Changes
|
package/dist/lib/cli.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as Liquid, t as CreateEngineOptions } from "./types-
|
|
2
|
-
import { t as createEngine } from "./engine-
|
|
1
|
+
import { n as Liquid, t as CreateEngineOptions } from "./types-DmnHC99v.mjs";
|
|
2
|
+
import { t as createEngine } from "./engine-BAYeiay3.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/clean.d.ts
|
|
5
5
|
/**
|
|
@@ -7,6 +7,14 @@ import { t as createEngine } from "./engine-B6JHetwb.mjs";
|
|
|
7
7
|
*
|
|
8
8
|
* Runs the source through a pipeline of transforms — currently
|
|
9
9
|
* strips frontmatter, with more steps added over time.
|
|
10
|
+
*
|
|
11
|
+
* @param text - Raw `.prompt` file content including frontmatter.
|
|
12
|
+
* @returns The cleaned template string, ready for rendering.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const template = clean("---\nname: greeting\n---\nHello {{ name }}!");
|
|
16
|
+
* // "Hello {{ name }}!"
|
|
17
|
+
* ```
|
|
10
18
|
*/
|
|
11
19
|
declare function clean(text: string): string;
|
|
12
20
|
//#endregion
|
package/dist/lib/cli.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.mts","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"cli.d.mts","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"mappings":";;;;;;;;AAkBA;;;;;;;;ACdA;;iBDcgB,KAAA,CAAM,IAAA;;;;cCdT,YAAA"}
|
package/dist/lib/cli.mjs
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
import { t as createEngine } from "./engine-
|
|
1
|
+
import { t as createEngine } from "./engine-CPKs9QbX.mjs";
|
|
2
2
|
import { flow } from "es-toolkit";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/clean.ts
|
|
6
6
|
const FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
|
|
7
7
|
/**
|
|
8
|
+
* Clean a raw `.prompt` file into a render-ready template.
|
|
9
|
+
*
|
|
10
|
+
* Runs the source through a pipeline of transforms — currently
|
|
11
|
+
* strips frontmatter, with more steps added over time.
|
|
12
|
+
*
|
|
13
|
+
* @param text - Raw `.prompt` file content including frontmatter.
|
|
14
|
+
* @returns The cleaned template string, ready for rendering.
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const template = clean("---\nname: greeting\n---\nHello {{ name }}!");
|
|
18
|
+
* // "Hello {{ name }}!"
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function clean(text) {
|
|
22
|
+
return pipeline(text);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
8
25
|
* Remove YAML frontmatter from the beginning of a string.
|
|
9
26
|
*
|
|
10
27
|
* Frontmatter is a block delimited by `---` at the start of the file.
|
|
11
28
|
* If no frontmatter is present, the string is returned unchanged.
|
|
29
|
+
*
|
|
30
|
+
* @private
|
|
12
31
|
*/
|
|
13
32
|
function stripFrontmatter(text) {
|
|
14
33
|
return text.replace(FRONTMATTER_RE, "");
|
|
15
34
|
}
|
|
35
|
+
/** @private */
|
|
16
36
|
const pipeline = flow(stripFrontmatter);
|
|
17
|
-
/**
|
|
18
|
-
* Clean a raw `.prompt` file into a render-ready template.
|
|
19
|
-
*
|
|
20
|
-
* Runs the source through a pipeline of transforms — currently
|
|
21
|
-
* strips frontmatter, with more steps added over time.
|
|
22
|
-
*/
|
|
23
|
-
function clean(text) {
|
|
24
|
-
return pipeline(text);
|
|
25
|
-
}
|
|
26
37
|
//#endregion
|
|
27
38
|
//#region src/partials-dir.ts
|
|
28
39
|
/** Absolute path to the SDK's built-in partials directory. */
|
package/dist/lib/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"sourcesContent":["import { flow } from \"es-toolkit\";\n\nconst FRONTMATTER_RE = /^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/;\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"sourcesContent":["import { flow } from \"es-toolkit\";\n\nconst FRONTMATTER_RE = /^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/;\n\n/**\n * Clean a raw `.prompt` file into a render-ready template.\n *\n * Runs the source through a pipeline of transforms — currently\n * strips frontmatter, with more steps added over time.\n *\n * @param text - Raw `.prompt` file content including frontmatter.\n * @returns The cleaned template string, ready for rendering.\n * @example\n * ```ts\n * const template = clean(\"---\\nname: greeting\\n---\\nHello {{ name }}!\");\n * // \"Hello {{ name }}!\"\n * ```\n */\nexport function clean(text: string): string {\n return pipeline(text);\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Remove YAML frontmatter from the beginning of a string.\n *\n * Frontmatter is a block delimited by `---` at the start of the file.\n * If no frontmatter is present, the string is returned unchanged.\n *\n * @private\n */\nfunction stripFrontmatter(text: string): string {\n return text.replace(FRONTMATTER_RE, \"\");\n}\n\n/** @private */\nconst pipeline = flow(stripFrontmatter);\n","import { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to the SDK's built-in partials directory. */\nexport const PARTIALS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), \"../prompts\");\n"],"mappings":";;;;;AAEA,MAAM,iBAAiB;;;;;;;;;;;;;;;AAgBvB,SAAgB,MAAM,MAAsB;AAC1C,QAAO,SAAS,KAAK;;;;;;;;;;AAevB,SAAS,iBAAiB,MAAsB;AAC9C,QAAO,KAAK,QAAQ,gBAAgB,GAAG;;;AAIzC,MAAM,WAAW,KAAK,iBAAiB;;;;ACnCvC,MAAa,eAAe,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,aAAa"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as CreateEngineOptions } from "./types-
|
|
1
|
+
import { t as CreateEngineOptions } from "./types-DmnHC99v.mjs";
|
|
2
2
|
import { Liquid } from "liquidjs";
|
|
3
3
|
|
|
4
4
|
//#region src/engine.d.ts
|
|
@@ -19,8 +19,10 @@ declare function createEngine(partialsDir: string, options?: Partial<CreateEngin
|
|
|
19
19
|
* Partials are flattened at codegen time by the CLI, so this engine
|
|
20
20
|
* only needs to handle `{{ var }}` expressions and basic Liquid
|
|
21
21
|
* control flow (`{% if %}`, `{% for %}`). No filesystem access required.
|
|
22
|
+
*
|
|
23
|
+
* @type {Liquid}
|
|
22
24
|
*/
|
|
23
25
|
declare const liquidEngine: Liquid;
|
|
24
26
|
//#endregion
|
|
25
27
|
export { liquidEngine as n, createEngine as t };
|
|
26
|
-
//# sourceMappingURL=engine-
|
|
28
|
+
//# sourceMappingURL=engine-BAYeiay3.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-
|
|
1
|
+
{"version":3,"file":"engine-BAYeiay3.d.mts","names":[],"sources":["../../src/engine.ts"],"mappings":";;;;;;AAcA;;;;;;;;iBAAgB,YAAA,CAAa,WAAA,UAAqB,OAAA,GAAU,OAAA,CAAQ,mBAAA,IAAuB,MAAA;;;;;;;AAsB3F;;;cAAa,YAAA,EAAY,MAAA"}
|
|
@@ -16,9 +16,9 @@ function createEngine(partialsDir, options) {
|
|
|
16
16
|
partials: [partialsDir],
|
|
17
17
|
extname: ".prompt",
|
|
18
18
|
cache: true,
|
|
19
|
+
...options,
|
|
19
20
|
strictFilters: true,
|
|
20
|
-
ownPropertyOnly: true
|
|
21
|
-
...options
|
|
21
|
+
ownPropertyOnly: true
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
@@ -27,6 +27,8 @@ function createEngine(partialsDir, options) {
|
|
|
27
27
|
* Partials are flattened at codegen time by the CLI, so this engine
|
|
28
28
|
* only needs to handle `{{ var }}` expressions and basic Liquid
|
|
29
29
|
* control flow (`{% if %}`, `{% for %}`). No filesystem access required.
|
|
30
|
+
*
|
|
31
|
+
* @type {Liquid}
|
|
30
32
|
*/
|
|
31
33
|
const liquidEngine = new Liquid({
|
|
32
34
|
strictFilters: true,
|
|
@@ -35,4 +37,4 @@ const liquidEngine = new Liquid({
|
|
|
35
37
|
//#endregion
|
|
36
38
|
export { liquidEngine as n, createEngine as t };
|
|
37
39
|
|
|
38
|
-
//# sourceMappingURL=engine-
|
|
40
|
+
//# sourceMappingURL=engine-CPKs9QbX.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-CPKs9QbX.mjs","names":[],"sources":["../../src/engine.ts"],"sourcesContent":["import { Liquid } from \"liquidjs\";\n\nimport type { CreateEngineOptions } from \"./types.js\";\n\n/**\n * Create a LiquidJS engine with custom options.\n *\n * The `partialsDir` is used as the root for `{% render %}` resolution.\n * The `.prompt` extension is appended automatically.\n *\n * @param partialsDir - Root directory for `{% render %}` partial resolution.\n * @param options - Optional overrides for the LiquidJS engine configuration.\n * @returns A configured {@link Liquid} engine instance.\n */\nexport function createEngine(partialsDir: string, options?: Partial<CreateEngineOptions>): Liquid {\n return new Liquid({\n root: [partialsDir],\n partials: [partialsDir],\n extname: \".prompt\",\n cache: true,\n ...options,\n // Safety defaults — applied after spread so callers cannot disable them\n strictFilters: true,\n ownPropertyOnly: true,\n });\n}\n\n/**\n * Shared LiquidJS engine for rendering prompt templates at runtime.\n *\n * Partials are flattened at codegen time by the CLI, so this engine\n * only needs to handle `{{ var }}` expressions and basic Liquid\n * control flow (`{% if %}`, `{% for %}`). No filesystem access required.\n *\n * @type {Liquid}\n */\nexport const liquidEngine = new Liquid({\n strictFilters: true,\n ownPropertyOnly: true,\n});\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,aAAa,aAAqB,SAAgD;AAChG,QAAO,IAAI,OAAO;EAChB,MAAM,CAAC,YAAY;EACnB,UAAU,CAAC,YAAY;EACvB,SAAS;EACT,OAAO;EACP,GAAG;EAEH,eAAe;EACf,iBAAiB;EAClB,CAAC;;;;;;;;;;;AAYJ,MAAa,eAAe,IAAI,OAAO;CACrC,eAAe;CACf,iBAAiB;CAClB,CAAC"}
|
package/dist/lib/index.d.mts
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as PromptNamespace, i as PromptModule, o as PromptRegistry, r as PromptConfig } from "./types-DmnHC99v.mjs";
|
|
2
2
|
|
|
3
|
+
//#region src/group.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a prompt group namespace from a record of prompt modules.
|
|
6
|
+
*
|
|
7
|
+
* Sets the `group` field on each module to the given group name,
|
|
8
|
+
* producing a new namespace object without mutating the originals.
|
|
9
|
+
*
|
|
10
|
+
* @param name - Group name applied to each prompt (e.g. `'agents'`, `'agents/core'`).
|
|
11
|
+
* @param prompts - Record of prompt modules to group.
|
|
12
|
+
* @returns A new {@link PromptNamespace} with group-tagged prompt modules.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { createPrompt, createPromptGroup } from '@funkai/prompts'
|
|
17
|
+
* import { z } from 'zod'
|
|
18
|
+
*
|
|
19
|
+
* const agents = createPromptGroup('agents', {
|
|
20
|
+
* greeting: createPrompt({
|
|
21
|
+
* name: 'greeting',
|
|
22
|
+
* template: 'Hello {{ name }}!',
|
|
23
|
+
* schema: z.object({ name: z.string() }),
|
|
24
|
+
* }),
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* agents.greeting.render({ name: 'Alice' })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function createPromptGroup<T extends Record<string, PromptModule>>(name: string, prompts: T): T;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/prompt.d.ts
|
|
33
|
+
/**
|
|
34
|
+
* Create a prompt module from a config object.
|
|
35
|
+
*
|
|
36
|
+
* Encapsulates template rendering (via LiquidJS) and variable validation
|
|
37
|
+
* (via Zod) into a single {@link PromptModule}. Works for both codegen
|
|
38
|
+
* output and runtime on-the-fly prompt construction.
|
|
39
|
+
*
|
|
40
|
+
* @param config - Prompt configuration with name, template, schema, and optional group.
|
|
41
|
+
* @returns A {@link PromptModule} with `render` and `validate` methods.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { createPrompt } from '@funkai/prompts'
|
|
46
|
+
* import { z } from 'zod'
|
|
47
|
+
*
|
|
48
|
+
* const greeting = createPrompt({
|
|
49
|
+
* name: 'greeting',
|
|
50
|
+
* template: 'Hello {{ name }}, welcome to {{ place }}!',
|
|
51
|
+
* schema: z.object({ name: z.string(), place: z.string() }),
|
|
52
|
+
* })
|
|
53
|
+
*
|
|
54
|
+
* greeting.render({ name: 'Alice', place: 'Wonderland' })
|
|
55
|
+
* // => "Hello Alice, welcome to Wonderland!"
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function createPrompt<T>(config: PromptConfig<T>): PromptModule<T>;
|
|
59
|
+
//#endregion
|
|
3
60
|
//#region src/registry.d.ts
|
|
4
61
|
/**
|
|
5
62
|
* Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
|
|
@@ -22,5 +79,5 @@ import { a as PromptRegistry, i as PromptNamespace, r as PromptModule } from "./
|
|
|
22
79
|
*/
|
|
23
80
|
declare function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T>;
|
|
24
81
|
//#endregion
|
|
25
|
-
export { type PromptModule, type PromptNamespace, type PromptRegistry, createPromptRegistry };
|
|
82
|
+
export { type PromptConfig, type PromptModule, type PromptNamespace, type PromptRegistry, createPrompt, createPromptGroup, createPromptRegistry };
|
|
26
83
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/lib/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/registry.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/group.ts","../../src/prompt.ts","../../src/registry.ts"],"mappings":";;;;;AA4BA;;;;;;;;;;;;;;;;;;;;;ACAA;;;iBDAgB,iBAAA,WAA4B,MAAA,SAAe,YAAA,EAAA,CACzD,IAAA,UACA,OAAA,EAAS,CAAA,GACR,CAAA;;;;;AAHH;;;;;;;;;;;;;;;;;;;;;ACAA;;iBAAgB,YAAA,GAAA,CAAgB,MAAA,EAAQ,YAAA,CAAa,CAAA,IAAK,YAAA,CAAa,CAAA;;;;;ADAvE;;;;;;;;;;;;;;;;;iBEPgB,oBAAA,WAA+B,eAAA,CAAA,CAAiB,OAAA,EAAS,CAAA,GAAI,cAAA,CAAe,CAAA"}
|
package/dist/lib/index.mjs
CHANGED
|
@@ -1,30 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
import { n as liquidEngine } from "./engine-CPKs9QbX.mjs";
|
|
2
|
+
//#region src/group.ts
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Leaves have `name`, `schema`, and `render` — namespaces do not.
|
|
4
|
+
* Create a prompt group namespace from a record of prompt modules.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Sets the `group` field on each module to the given group name,
|
|
7
|
+
* producing a new namespace object without mutating the originals.
|
|
8
|
+
*
|
|
9
|
+
* @param name - Group name applied to each prompt (e.g. `'agents'`, `'agents/core'`).
|
|
10
|
+
* @param prompts - Record of prompt modules to group.
|
|
11
|
+
* @returns A new {@link PromptNamespace} with group-tagged prompt modules.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createPrompt, createPromptGroup } from '@funkai/prompts'
|
|
16
|
+
* import { z } from 'zod'
|
|
17
|
+
*
|
|
18
|
+
* const agents = createPromptGroup('agents', {
|
|
19
|
+
* greeting: createPrompt({
|
|
20
|
+
* name: 'greeting',
|
|
21
|
+
* template: 'Hello {{ name }}!',
|
|
22
|
+
* schema: z.object({ name: z.string() }),
|
|
23
|
+
* }),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* agents.greeting.render({ name: 'Alice' })
|
|
27
|
+
* ```
|
|
7
28
|
*/
|
|
8
|
-
function
|
|
9
|
-
return
|
|
29
|
+
function createPromptGroup(name, prompts) {
|
|
30
|
+
return Object.fromEntries(Object.entries(prompts).map(([key, module]) => [key, {
|
|
31
|
+
...module,
|
|
32
|
+
group: name
|
|
33
|
+
}]));
|
|
10
34
|
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/prompt.ts
|
|
11
37
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Only recurses into plain namespace nodes — PromptModule leaves
|
|
14
|
-
* (which contain Zod schemas) are frozen shallowly.
|
|
38
|
+
* Create a prompt module from a config object.
|
|
15
39
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
40
|
+
* Encapsulates template rendering (via LiquidJS) and variable validation
|
|
41
|
+
* (via Zod) into a single {@link PromptModule}. Works for both codegen
|
|
42
|
+
* output and runtime on-the-fly prompt construction.
|
|
18
43
|
*
|
|
19
|
-
* @
|
|
44
|
+
* @param config - Prompt configuration with name, template, schema, and optional group.
|
|
45
|
+
* @returns A {@link PromptModule} with `render` and `validate` methods.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { createPrompt } from '@funkai/prompts'
|
|
50
|
+
* import { z } from 'zod'
|
|
51
|
+
*
|
|
52
|
+
* const greeting = createPrompt({
|
|
53
|
+
* name: 'greeting',
|
|
54
|
+
* template: 'Hello {{ name }}, welcome to {{ place }}!',
|
|
55
|
+
* schema: z.object({ name: z.string(), place: z.string() }),
|
|
56
|
+
* })
|
|
57
|
+
*
|
|
58
|
+
* greeting.render({ name: 'Alice', place: 'Wonderland' })
|
|
59
|
+
* // => "Hello Alice, welcome to Wonderland!"
|
|
60
|
+
* ```
|
|
20
61
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
62
|
+
function createPrompt(config) {
|
|
63
|
+
const { name, group, template, schema } = config;
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
group,
|
|
67
|
+
schema,
|
|
68
|
+
render(variables) {
|
|
69
|
+
return liquidEngine.parseAndRenderSync(template, schema.parse(variables));
|
|
70
|
+
},
|
|
71
|
+
validate(variables) {
|
|
72
|
+
return schema.parse(variables);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
27
75
|
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/registry.ts
|
|
28
78
|
/**
|
|
29
79
|
* Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
|
|
30
80
|
*
|
|
@@ -47,7 +97,32 @@ function deepFreeze(obj) {
|
|
|
47
97
|
function createPromptRegistry(modules) {
|
|
48
98
|
return deepFreeze({ ...modules });
|
|
49
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Check whether a value looks like a PromptModule leaf.
|
|
102
|
+
* Leaves have `name`, `schema`, and `render` — namespaces do not.
|
|
103
|
+
*
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
function isPromptModule(value) {
|
|
107
|
+
return typeof value === "object" && value !== null && "render" in value && "schema" in value && "name" in value;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Recursively freeze a prompt namespace tree.
|
|
111
|
+
* Only recurses into plain namespace nodes — PromptModule leaves
|
|
112
|
+
* (which contain Zod schemas) are intentionally left unfrozen
|
|
113
|
+
* to avoid breaking Zod internal state.
|
|
114
|
+
*
|
|
115
|
+
* @param obj - The namespace object to freeze.
|
|
116
|
+
* @returns The frozen object cast to its deep-readonly type.
|
|
117
|
+
*
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
function deepFreeze(obj) {
|
|
121
|
+
Object.freeze(obj);
|
|
122
|
+
for (const value of Object.values(obj)) if (typeof value === "object" && value !== null && !Object.isFrozen(value) && !isPromptModule(value)) deepFreeze(value);
|
|
123
|
+
return obj;
|
|
124
|
+
}
|
|
50
125
|
//#endregion
|
|
51
|
-
export { createPromptRegistry };
|
|
126
|
+
export { createPrompt, createPromptGroup, createPromptRegistry };
|
|
52
127
|
|
|
53
128
|
//# sourceMappingURL=index.mjs.map
|
package/dist/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/registry.ts"],"sourcesContent":["import type { PromptModule
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/group.ts","../../src/prompt.ts","../../src/registry.ts"],"sourcesContent":["import type { PromptModule } from \"./types.js\";\n\n/**\n * Create a prompt group namespace from a record of prompt modules.\n *\n * Sets the `group` field on each module to the given group name,\n * producing a new namespace object without mutating the originals.\n *\n * @param name - Group name applied to each prompt (e.g. `'agents'`, `'agents/core'`).\n * @param prompts - Record of prompt modules to group.\n * @returns A new {@link PromptNamespace} with group-tagged prompt modules.\n *\n * @example\n * ```ts\n * import { createPrompt, createPromptGroup } from '@funkai/prompts'\n * import { z } from 'zod'\n *\n * const agents = createPromptGroup('agents', {\n * greeting: createPrompt({\n * name: 'greeting',\n * template: 'Hello {{ name }}!',\n * schema: z.object({ name: z.string() }),\n * }),\n * })\n *\n * agents.greeting.render({ name: 'Alice' })\n * ```\n */\nexport function createPromptGroup<T extends Record<string, PromptModule>>(\n name: string,\n prompts: T,\n): T {\n return Object.fromEntries(\n Object.entries(prompts).map(([key, module]) => [key, { ...module, group: name }]),\n ) as T;\n}\n","import { liquidEngine } from \"./engine.js\";\nimport type { PromptConfig, PromptModule } from \"./types.js\";\n\n/**\n * Create a prompt module from a config object.\n *\n * Encapsulates template rendering (via LiquidJS) and variable validation\n * (via Zod) into a single {@link PromptModule}. Works for both codegen\n * output and runtime on-the-fly prompt construction.\n *\n * @param config - Prompt configuration with name, template, schema, and optional group.\n * @returns A {@link PromptModule} with `render` and `validate` methods.\n *\n * @example\n * ```ts\n * import { createPrompt } from '@funkai/prompts'\n * import { z } from 'zod'\n *\n * const greeting = createPrompt({\n * name: 'greeting',\n * template: 'Hello {{ name }}, welcome to {{ place }}!',\n * schema: z.object({ name: z.string(), place: z.string() }),\n * })\n *\n * greeting.render({ name: 'Alice', place: 'Wonderland' })\n * // => \"Hello Alice, welcome to Wonderland!\"\n * ```\n */\nexport function createPrompt<T>(config: PromptConfig<T>): PromptModule<T> {\n const { name, group, template, schema } = config;\n\n return {\n name,\n group,\n schema,\n render(variables: T): string {\n return liquidEngine.parseAndRenderSync(\n template,\n schema.parse(variables) as Record<string, unknown>,\n );\n },\n validate(variables: unknown): T {\n return schema.parse(variables);\n },\n };\n}\n","import type { PromptModule, PromptNamespace, PromptRegistry } from \"./types.js\";\n\n/**\n * Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.\n *\n * The registry is typically created by generated code — the CLI produces\n * an `index.ts` that calls `createPromptRegistry()` with all discovered\n * prompt modules keyed by camelCase name, nested by group.\n *\n * @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.\n * @returns A deep-frozen, typed record with direct property access.\n *\n * @example\n * ```ts\n * const prompts = createPromptRegistry({\n * agents: { coverageAssessor },\n * greeting,\n * })\n * prompts.agents.coverageAssessor.render({ scope: 'full' })\n * ```\n */\nexport function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {\n return deepFreeze({ ...modules });\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a value looks like a PromptModule leaf.\n * Leaves have `name`, `schema`, and `render` — namespaces do not.\n *\n * @private\n */\nfunction isPromptModule(value: unknown): value is PromptModule {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"render\" in value &&\n \"schema\" in value &&\n \"name\" in value\n );\n}\n\n/**\n * Recursively freeze a prompt namespace tree.\n * Only recurses into plain namespace nodes — PromptModule leaves\n * (which contain Zod schemas) are intentionally left unfrozen\n * to avoid breaking Zod internal state.\n *\n * @param obj - The namespace object to freeze.\n * @returns The frozen object cast to its deep-readonly type.\n *\n * @private\n */\nfunction deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {\n Object.freeze(obj);\n for (const value of Object.values(obj)) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n !Object.isFrozen(value) &&\n !isPromptModule(value)\n ) {\n deepFreeze(value as PromptNamespace);\n }\n }\n return obj as PromptRegistry<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,kBACd,MACA,SACG;AACH,QAAO,OAAO,YACZ,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,KAAK;EAAE,GAAG;EAAQ,OAAO;EAAM,CAAC,CAAC,CAClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNH,SAAgB,aAAgB,QAA0C;CACxE,MAAM,EAAE,MAAM,OAAO,UAAU,WAAW;AAE1C,QAAO;EACL;EACA;EACA;EACA,OAAO,WAAsB;AAC3B,UAAO,aAAa,mBAClB,UACA,OAAO,MAAM,UAAU,CACxB;;EAEH,SAAS,WAAuB;AAC9B,UAAO,OAAO,MAAM,UAAU;;EAEjC;;;;;;;;;;;;;;;;;;;;;;;ACvBH,SAAgB,qBAAgD,SAA+B;AAC7F,QAAO,WAAW,EAAE,GAAG,SAAS,CAAC;;;;;;;;AAanC,SAAS,eAAe,OAAuC;AAC7D,QACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,YAAY,SACZ,UAAU;;;;;;;;;;;;;AAed,SAAS,WAAsC,KAA2B;AACxE,QAAO,OAAO,IAAI;AAClB,MAAK,MAAM,SAAS,OAAO,OAAO,IAAI,CACpC,KACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,OAAO,SAAS,MAAM,IACvB,CAAC,eAAe,MAAM,CAEtB,YAAW,MAAyB;AAGxC,QAAO"}
|
package/dist/lib/runtime.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as liquidEngine } from "./engine-
|
|
1
|
+
import { n as liquidEngine } from "./engine-BAYeiay3.mjs";
|
|
2
2
|
export { liquidEngine };
|
package/dist/lib/runtime.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as liquidEngine } from "./engine-
|
|
1
|
+
import { n as liquidEngine } from "./engine-CPKs9QbX.mjs";
|
|
2
2
|
export { liquidEngine };
|
|
@@ -5,9 +5,31 @@ import { ZodType } from "zod";
|
|
|
5
5
|
/**
|
|
6
6
|
* Options for creating a custom LiquidJS engine.
|
|
7
7
|
*/
|
|
8
|
-
type CreateEngineOptions = Pick<LiquidOptions, "root" | "partials" | "extname" | "cache" | "
|
|
8
|
+
type CreateEngineOptions = Pick<LiquidOptions, "root" | "partials" | "extname" | "cache" | "strictVariables">;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Configuration for creating a prompt module via {@link createPrompt}.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const config: PromptConfig<{ name: string }> = {
|
|
15
|
+
* name: 'greeting',
|
|
16
|
+
* template: 'Hello {{ name }}!',
|
|
17
|
+
* schema: z.object({ name: z.string() }),
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
interface PromptConfig<T = unknown> {
|
|
22
|
+
/** Kebab-case prompt identifier (e.g. `'greeting'`, `'worker-system'`). */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** LiquidJS template string with `{{ variable }}` expressions. */
|
|
25
|
+
readonly template: string;
|
|
26
|
+
/** Zod schema for validating template variables. */
|
|
27
|
+
readonly schema: ZodType<T>;
|
|
28
|
+
/** Optional group path (e.g. `'agents'`, `'agents/core'`). */
|
|
29
|
+
readonly group?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A single prompt module produced by {@link createPrompt} or codegen.
|
|
11
33
|
*
|
|
12
34
|
* Each `.prompt` file generates a default export conforming to this shape.
|
|
13
35
|
*/
|
|
@@ -22,9 +44,9 @@ interface PromptModule<T = unknown> {
|
|
|
22
44
|
* A nested namespace node in the prompt tree.
|
|
23
45
|
* Values are either PromptModule leaves or further nested namespaces.
|
|
24
46
|
*/
|
|
25
|
-
|
|
47
|
+
interface PromptNamespace {
|
|
26
48
|
readonly [key: string]: PromptModule | PromptNamespace;
|
|
27
|
-
}
|
|
49
|
+
}
|
|
28
50
|
/**
|
|
29
51
|
* Deep-readonly version of a prompt tree.
|
|
30
52
|
* Prevents reassignment at any nesting level.
|
|
@@ -39,5 +61,5 @@ type PromptNamespace = {
|
|
|
39
61
|
*/
|
|
40
62
|
type PromptRegistry<T extends PromptNamespace> = { readonly [K in keyof T]: T[K] extends PromptModule ? T[K] : T[K] extends PromptNamespace ? PromptRegistry<T[K]> : T[K] };
|
|
41
63
|
//#endregion
|
|
42
|
-
export {
|
|
43
|
-
//# sourceMappingURL=types-
|
|
64
|
+
export { PromptNamespace as a, PromptModule as i, Liquid$1 as n, PromptRegistry as o, PromptConfig as r, CreateEngineOptions as t };
|
|
65
|
+
//# sourceMappingURL=types-DmnHC99v.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-DmnHC99v.d.mts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;AAMA;KAAY,mBAAA,GAAsB,IAAA,CAChC,aAAA;;;;AAgBF;;;;;;;;;UAAiB,YAAA;EAQN;EAAA,SANA,IAAA;EAMK;EAAA,SAJL,QAAA;EAYkB;EAAA,SAVlB,MAAA,EAAQ,OAAA,CAAQ,CAAA;EAaA;EAAA,SAXhB,KAAA;AAAA;;;;;;UAQM,YAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA,EAAQ,OAAA,CAAQ,CAAA;EACzB,MAAA,CAAO,SAAA,EAAW,CAAA;EAClB,QAAA,CAAS,SAAA,YAAqB,CAAA;AAAA;;;;;UAOf,eAAA;EAAA,UACL,GAAA,WAAc,YAAA,GAAe,eAAA;AAAA;;;;;;;;AAezC;;;;;KAAY,cAAA,WAAyB,eAAA,2BACd,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,YAAA,GAClC,CAAA,CAAE,CAAA,IACF,CAAA,CAAE,CAAA,UAAW,eAAA,GACX,cAAA,CAAe,CAAA,CAAE,CAAA,KACjB,CAAA,CAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funkai/prompts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Prompt SDK with LiquidJS templating and Zod validation",
|
|
6
6
|
"keywords": [
|
|
@@ -37,6 +37,9 @@
|
|
|
37
37
|
"import": "./dist/lib/cli.mjs"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
40
43
|
"dependencies": {
|
|
41
44
|
"es-toolkit": "^1.45.1",
|
|
42
45
|
"liquidjs": "^10.25.0",
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@types/node": "^25.5.0",
|
|
47
50
|
"@vitest/coverage-v8": "^4.1.0",
|
|
48
|
-
"tsdown": "^0.21.
|
|
51
|
+
"tsdown": "^0.21.4",
|
|
49
52
|
"typescript": "^5.9.3",
|
|
50
53
|
"vitest": "^4.1.0"
|
|
51
54
|
},
|
package/src/clean.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import { clean } from "@/clean.js";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe(clean, () => {
|
|
6
6
|
it("should strip YAML frontmatter from a prompt file", () => {
|
|
7
7
|
const input = "---\nname: test-prompt\ngroup: agents\n---\nHello {{ name }}";
|
|
8
8
|
expect(clean(input)).toBe("Hello {{ name }}");
|
package/src/clean.ts
CHANGED
|
@@ -2,24 +2,39 @@ import { flow } from "es-toolkit";
|
|
|
2
2
|
|
|
3
3
|
const FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Clean a raw `.prompt` file into a render-ready template.
|
|
7
|
+
*
|
|
8
|
+
* Runs the source through a pipeline of transforms — currently
|
|
9
|
+
* strips frontmatter, with more steps added over time.
|
|
10
|
+
*
|
|
11
|
+
* @param text - Raw `.prompt` file content including frontmatter.
|
|
12
|
+
* @returns The cleaned template string, ready for rendering.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const template = clean("---\nname: greeting\n---\nHello {{ name }}!");
|
|
16
|
+
* // "Hello {{ name }}!"
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function clean(text: string): string {
|
|
20
|
+
return pipeline(text);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Private
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
5
27
|
/**
|
|
6
28
|
* Remove YAML frontmatter from the beginning of a string.
|
|
7
29
|
*
|
|
8
30
|
* Frontmatter is a block delimited by `---` at the start of the file.
|
|
9
31
|
* If no frontmatter is present, the string is returned unchanged.
|
|
32
|
+
*
|
|
33
|
+
* @private
|
|
10
34
|
*/
|
|
11
35
|
function stripFrontmatter(text: string): string {
|
|
12
36
|
return text.replace(FRONTMATTER_RE, "");
|
|
13
37
|
}
|
|
14
38
|
|
|
39
|
+
/** @private */
|
|
15
40
|
const pipeline = flow(stripFrontmatter);
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Clean a raw `.prompt` file into a render-ready template.
|
|
19
|
-
*
|
|
20
|
-
* Runs the source through a pipeline of transforms — currently
|
|
21
|
-
* strips frontmatter, with more steps added over time.
|
|
22
|
-
*/
|
|
23
|
-
export function clean(text: string): string {
|
|
24
|
-
return pipeline(text);
|
|
25
|
-
}
|
package/src/engine.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
|
|
|
3
3
|
|
|
4
4
|
import { createEngine, liquidEngine } from "@/engine.js";
|
|
5
5
|
|
|
6
|
-
describe(
|
|
6
|
+
describe(createEngine, () => {
|
|
7
7
|
it("should return a Liquid instance", () => {
|
|
8
8
|
const eng = createEngine("/tmp/test-partials");
|
|
9
9
|
expect(eng).toBeInstanceOf(Liquid);
|
|
@@ -18,15 +18,20 @@ describe("createEngine", () => {
|
|
|
18
18
|
it("should merge custom options with defaults", () => {
|
|
19
19
|
const eng = createEngine("/tmp/test-partials", {
|
|
20
20
|
cache: false,
|
|
21
|
-
strictFilters: false,
|
|
22
21
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const result = eng.parseAndRenderSync("Hello {{ name }}", { name: "World" });
|
|
23
|
+
expect(result).toBe("Hello World");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should enforce strictFilters regardless of options", () => {
|
|
27
|
+
const eng = createEngine("/tmp/test-partials");
|
|
28
|
+
expect(() => {
|
|
29
|
+
eng.parseAndRenderSync("{{ name | bogus }}", { name: "test" });
|
|
30
|
+
}).toThrow();
|
|
26
31
|
});
|
|
27
32
|
});
|
|
28
33
|
|
|
29
|
-
describe("liquidEngine", () => {
|
|
34
|
+
describe("liquidEngine instance", () => {
|
|
30
35
|
it("should be a Liquid instance", () => {
|
|
31
36
|
expect(liquidEngine).toBeInstanceOf(Liquid);
|
|
32
37
|
});
|
package/src/engine.ts
CHANGED
|
@@ -18,9 +18,10 @@ export function createEngine(partialsDir: string, options?: Partial<CreateEngine
|
|
|
18
18
|
partials: [partialsDir],
|
|
19
19
|
extname: ".prompt",
|
|
20
20
|
cache: true,
|
|
21
|
+
...options,
|
|
22
|
+
// Safety defaults — applied after spread so callers cannot disable them
|
|
21
23
|
strictFilters: true,
|
|
22
24
|
ownPropertyOnly: true,
|
|
23
|
-
...options,
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -30,6 +31,8 @@ export function createEngine(partialsDir: string, options?: Partial<CreateEngine
|
|
|
30
31
|
* Partials are flattened at codegen time by the CLI, so this engine
|
|
31
32
|
* only needs to handle `{{ var }}` expressions and basic Liquid
|
|
32
33
|
* control flow (`{% if %}`, `{% for %}`). No filesystem access required.
|
|
34
|
+
*
|
|
35
|
+
* @type {Liquid}
|
|
33
36
|
*/
|
|
34
37
|
export const liquidEngine = new Liquid({
|
|
35
38
|
strictFilters: true,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { createPromptGroup } from "@/group.js";
|
|
5
|
+
import { createPrompt } from "@/prompt.js";
|
|
6
|
+
|
|
7
|
+
describe(createPromptGroup, () => {
|
|
8
|
+
it("should set the group on each prompt module", () => {
|
|
9
|
+
const greeting = createPrompt({
|
|
10
|
+
name: "greeting",
|
|
11
|
+
template: "Hello {{ name }}!",
|
|
12
|
+
schema: z.object({ name: z.string() }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const group = createPromptGroup("agents", { greeting });
|
|
16
|
+
|
|
17
|
+
expect(group.greeting.group).toBe("agents");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should override existing group on prompt modules", () => {
|
|
21
|
+
const greeting = createPrompt({
|
|
22
|
+
name: "greeting",
|
|
23
|
+
group: "old-group",
|
|
24
|
+
template: "Hello {{ name }}!",
|
|
25
|
+
schema: z.object({ name: z.string() }),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const group = createPromptGroup("agents", { greeting });
|
|
29
|
+
|
|
30
|
+
expect(group.greeting.group).toBe("agents");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should not mutate the original prompt module", () => {
|
|
34
|
+
const greeting = createPrompt({
|
|
35
|
+
name: "greeting",
|
|
36
|
+
template: "Hello {{ name }}!",
|
|
37
|
+
schema: z.object({ name: z.string() }),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
createPromptGroup("agents", { greeting });
|
|
41
|
+
|
|
42
|
+
expect(greeting.group).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should preserve render functionality", () => {
|
|
46
|
+
const greeting = createPrompt({
|
|
47
|
+
name: "greeting",
|
|
48
|
+
template: "Hello {{ name }}!",
|
|
49
|
+
schema: z.object({ name: z.string() }),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const group = createPromptGroup("agents", { greeting });
|
|
53
|
+
|
|
54
|
+
expect(group.greeting.render({ name: "Alice" })).toBe("Hello Alice!");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should support multiple prompts in a group", () => {
|
|
58
|
+
const greeting = createPrompt({
|
|
59
|
+
name: "greeting",
|
|
60
|
+
template: "Hello {{ name }}!",
|
|
61
|
+
schema: z.object({ name: z.string() }),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const farewell = createPrompt({
|
|
65
|
+
name: "farewell",
|
|
66
|
+
template: "Goodbye {{ name }}!",
|
|
67
|
+
schema: z.object({ name: z.string() }),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const group = createPromptGroup("agents", { greeting, farewell });
|
|
71
|
+
|
|
72
|
+
expect(group.greeting.group).toBe("agents");
|
|
73
|
+
expect(group.farewell.group).toBe("agents");
|
|
74
|
+
expect(group.greeting.render({ name: "Alice" })).toBe("Hello Alice!");
|
|
75
|
+
expect(group.farewell.render({ name: "Bob" })).toBe("Goodbye Bob!");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should support nested group paths", () => {
|
|
79
|
+
const system = createPrompt({
|
|
80
|
+
name: "system",
|
|
81
|
+
template: "You are a {{ role }}.",
|
|
82
|
+
schema: z.object({ role: z.string() }),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const group = createPromptGroup("agents/core", { system });
|
|
86
|
+
|
|
87
|
+
expect(group.system.group).toBe("agents/core");
|
|
88
|
+
});
|
|
89
|
+
});
|
package/src/group.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PromptModule } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a prompt group namespace from a record of prompt modules.
|
|
5
|
+
*
|
|
6
|
+
* Sets the `group` field on each module to the given group name,
|
|
7
|
+
* producing a new namespace object without mutating the originals.
|
|
8
|
+
*
|
|
9
|
+
* @param name - Group name applied to each prompt (e.g. `'agents'`, `'agents/core'`).
|
|
10
|
+
* @param prompts - Record of prompt modules to group.
|
|
11
|
+
* @returns A new {@link PromptNamespace} with group-tagged prompt modules.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createPrompt, createPromptGroup } from '@funkai/prompts'
|
|
16
|
+
* import { z } from 'zod'
|
|
17
|
+
*
|
|
18
|
+
* const agents = createPromptGroup('agents', {
|
|
19
|
+
* greeting: createPrompt({
|
|
20
|
+
* name: 'greeting',
|
|
21
|
+
* template: 'Hello {{ name }}!',
|
|
22
|
+
* schema: z.object({ name: z.string() }),
|
|
23
|
+
* }),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* agents.greeting.render({ name: 'Alice' })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createPromptGroup<T extends Record<string, PromptModule>>(
|
|
30
|
+
name: string,
|
|
31
|
+
prompts: T,
|
|
32
|
+
): T {
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(prompts).map(([key, module]) => [key, { ...module, group: name }]),
|
|
35
|
+
) as T;
|
|
36
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export { createPromptGroup } from "./group.js";
|
|
2
|
+
export { createPrompt } from "./prompt.js";
|
|
1
3
|
export { createPromptRegistry } from "./registry.js";
|
|
2
|
-
export type { PromptModule, PromptNamespace, PromptRegistry } from "./types.js";
|
|
4
|
+
export type { PromptConfig, PromptModule, PromptNamespace, PromptRegistry } from "./types.js";
|
package/src/partials-dir.test.ts
CHANGED
|
@@ -4,9 +4,9 @@ import { describe, expect, it } from "vitest";
|
|
|
4
4
|
|
|
5
5
|
import { PARTIALS_DIR } from "@/partials-dir.js";
|
|
6
6
|
|
|
7
|
-
describe(
|
|
7
|
+
describe(PARTIALS_DIR, () => {
|
|
8
8
|
it("should be an absolute path", () => {
|
|
9
|
-
expect(isAbsolute(PARTIALS_DIR)).
|
|
9
|
+
expect(isAbsolute(PARTIALS_DIR)).toBeTruthy();
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
it("should point to the prompts directory", () => {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { createPrompt } from "@/prompt.js";
|
|
5
|
+
|
|
6
|
+
describe(createPrompt, () => {
|
|
7
|
+
it("should create a prompt module with name and group", () => {
|
|
8
|
+
const prompt = createPrompt({
|
|
9
|
+
name: "greeting",
|
|
10
|
+
group: "agents",
|
|
11
|
+
template: "Hello {{ name }}!",
|
|
12
|
+
schema: z.object({ name: z.string() }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
expect(prompt.name).toBe("greeting");
|
|
16
|
+
expect(prompt.group).toBe("agents");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should set group to undefined when not provided", () => {
|
|
20
|
+
const prompt = createPrompt({
|
|
21
|
+
name: "greeting",
|
|
22
|
+
template: "Hello!",
|
|
23
|
+
schema: z.object({}),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(prompt.group).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should render a template with variables", () => {
|
|
30
|
+
const prompt = createPrompt({
|
|
31
|
+
name: "greeting",
|
|
32
|
+
template: "Hello {{ name }}, welcome to {{ place }}!",
|
|
33
|
+
schema: z.object({ name: z.string(), place: z.string() }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const result = prompt.render({ name: "Alice", place: "Wonderland" });
|
|
37
|
+
expect(result).toBe("Hello Alice, welcome to Wonderland!");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should render a static template with no variables", () => {
|
|
41
|
+
const prompt = createPrompt({
|
|
42
|
+
name: "static",
|
|
43
|
+
template: "No variables here.",
|
|
44
|
+
schema: z.object({}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(prompt.render({})).toBe("No variables here.");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should validate variables against the schema", () => {
|
|
51
|
+
const prompt = createPrompt({
|
|
52
|
+
name: "greeting",
|
|
53
|
+
template: "Hello {{ name }}!",
|
|
54
|
+
schema: z.object({ name: z.string() }),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(prompt.validate({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should throw on invalid variables during render", () => {
|
|
61
|
+
const prompt = createPrompt({
|
|
62
|
+
name: "greeting",
|
|
63
|
+
template: "Hello {{ name }}!",
|
|
64
|
+
schema: z.object({ name: z.string() }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(() => prompt.render({ name: 42 } as never)).toThrow();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should throw on invalid variables during validate", () => {
|
|
71
|
+
const prompt = createPrompt({
|
|
72
|
+
name: "greeting",
|
|
73
|
+
template: "Hello {{ name }}!",
|
|
74
|
+
schema: z.object({ name: z.string() }),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(() => prompt.validate({ name: 42 })).toThrow();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should expose the schema for external validation", () => {
|
|
81
|
+
const schema = z.object({ name: z.string() });
|
|
82
|
+
const prompt = createPrompt({
|
|
83
|
+
name: "greeting",
|
|
84
|
+
template: "Hello {{ name }}!",
|
|
85
|
+
schema,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(prompt.schema).toBe(schema);
|
|
89
|
+
});
|
|
90
|
+
});
|
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { liquidEngine } from "./engine.js";
|
|
2
|
+
import type { PromptConfig, PromptModule } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a prompt module from a config object.
|
|
6
|
+
*
|
|
7
|
+
* Encapsulates template rendering (via LiquidJS) and variable validation
|
|
8
|
+
* (via Zod) into a single {@link PromptModule}. Works for both codegen
|
|
9
|
+
* output and runtime on-the-fly prompt construction.
|
|
10
|
+
*
|
|
11
|
+
* @param config - Prompt configuration with name, template, schema, and optional group.
|
|
12
|
+
* @returns A {@link PromptModule} with `render` and `validate` methods.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { createPrompt } from '@funkai/prompts'
|
|
17
|
+
* import { z } from 'zod'
|
|
18
|
+
*
|
|
19
|
+
* const greeting = createPrompt({
|
|
20
|
+
* name: 'greeting',
|
|
21
|
+
* template: 'Hello {{ name }}, welcome to {{ place }}!',
|
|
22
|
+
* schema: z.object({ name: z.string(), place: z.string() }),
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* greeting.render({ name: 'Alice', place: 'Wonderland' })
|
|
26
|
+
* // => "Hello Alice, welcome to Wonderland!"
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createPrompt<T>(config: PromptConfig<T>): PromptModule<T> {
|
|
30
|
+
const { name, group, template, schema } = config;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
group,
|
|
35
|
+
schema,
|
|
36
|
+
render(variables: T): string {
|
|
37
|
+
return liquidEngine.parseAndRenderSync(
|
|
38
|
+
template,
|
|
39
|
+
schema.parse(variables) as Record<string, unknown>,
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
validate(variables: unknown): T {
|
|
43
|
+
return schema.parse(variables);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
package/src/registry.test.ts
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { createPrompt } from "@/prompt.js";
|
|
4
5
|
import { createPromptRegistry } from "@/registry.js";
|
|
5
6
|
|
|
6
|
-
const mockPrompt = {
|
|
7
|
-
name: "test-prompt"
|
|
8
|
-
group: "agents"
|
|
7
|
+
const mockPrompt = createPrompt({
|
|
8
|
+
name: "test-prompt",
|
|
9
|
+
group: "agents",
|
|
10
|
+
template: "Hello {{ name }}",
|
|
9
11
|
schema: z.object({ name: z.string() }),
|
|
10
|
-
|
|
11
|
-
return `Hello ${variables.name}`;
|
|
12
|
-
},
|
|
13
|
-
validate(variables: unknown) {
|
|
14
|
-
return z.object({ name: z.string() }).parse(variables);
|
|
15
|
-
},
|
|
16
|
-
};
|
|
12
|
+
});
|
|
17
13
|
|
|
18
|
-
const emptyPrompt = {
|
|
19
|
-
name: "empty"
|
|
20
|
-
|
|
14
|
+
const emptyPrompt = createPrompt({
|
|
15
|
+
name: "empty",
|
|
16
|
+
template: "static",
|
|
21
17
|
schema: z.object({}),
|
|
22
|
-
|
|
23
|
-
return "static";
|
|
24
|
-
},
|
|
25
|
-
validate(variables: unknown) {
|
|
26
|
-
return z.object({}).parse(variables);
|
|
27
|
-
},
|
|
28
|
-
};
|
|
18
|
+
});
|
|
29
19
|
|
|
30
|
-
describe(
|
|
20
|
+
describe(createPromptRegistry, () => {
|
|
31
21
|
it("should provide dot-access to a registered prompt", () => {
|
|
32
22
|
const registry = createPromptRegistry({ testPrompt: mockPrompt });
|
|
33
23
|
expect(registry.testPrompt.name).toBe("test-prompt");
|
|
@@ -44,14 +34,14 @@ describe("createPromptRegistry", () => {
|
|
|
44
34
|
|
|
45
35
|
it("should freeze the top-level registry object", () => {
|
|
46
36
|
const registry = createPromptRegistry({ testPrompt: mockPrompt });
|
|
47
|
-
expect(Object.isFrozen(registry)).
|
|
37
|
+
expect(Object.isFrozen(registry)).toBeTruthy();
|
|
48
38
|
});
|
|
49
39
|
|
|
50
40
|
it("should freeze nested namespace objects", () => {
|
|
51
41
|
const registry = createPromptRegistry({
|
|
52
42
|
agents: { testPrompt: mockPrompt },
|
|
53
43
|
});
|
|
54
|
-
expect(Object.isFrozen(registry.agents)).
|
|
44
|
+
expect(Object.isFrozen(registry.agents)).toBeTruthy();
|
|
55
45
|
});
|
|
56
46
|
|
|
57
47
|
it("should expose all keys via Object.keys", () => {
|
package/src/registry.ts
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import type { PromptModule, PromptNamespace, PromptRegistry } from "./types.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
|
|
5
|
+
*
|
|
6
|
+
* The registry is typically created by generated code — the CLI produces
|
|
7
|
+
* an `index.ts` that calls `createPromptRegistry()` with all discovered
|
|
8
|
+
* prompt modules keyed by camelCase name, nested by group.
|
|
9
|
+
*
|
|
10
|
+
* @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.
|
|
11
|
+
* @returns A deep-frozen, typed record with direct property access.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const prompts = createPromptRegistry({
|
|
16
|
+
* agents: { coverageAssessor },
|
|
17
|
+
* greeting,
|
|
18
|
+
* })
|
|
19
|
+
* prompts.agents.coverageAssessor.render({ scope: 'full' })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {
|
|
23
|
+
return deepFreeze({ ...modules });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Private
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
3
30
|
/**
|
|
4
31
|
* Check whether a value looks like a PromptModule leaf.
|
|
5
32
|
* Leaves have `name`, `schema`, and `render` — namespaces do not.
|
|
@@ -19,7 +46,8 @@ function isPromptModule(value: unknown): value is PromptModule {
|
|
|
19
46
|
/**
|
|
20
47
|
* Recursively freeze a prompt namespace tree.
|
|
21
48
|
* Only recurses into plain namespace nodes — PromptModule leaves
|
|
22
|
-
* (which contain Zod schemas) are
|
|
49
|
+
* (which contain Zod schemas) are intentionally left unfrozen
|
|
50
|
+
* to avoid breaking Zod internal state.
|
|
23
51
|
*
|
|
24
52
|
* @param obj - The namespace object to freeze.
|
|
25
53
|
* @returns The frozen object cast to its deep-readonly type.
|
|
@@ -28,7 +56,7 @@ function isPromptModule(value: unknown): value is PromptModule {
|
|
|
28
56
|
*/
|
|
29
57
|
function deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {
|
|
30
58
|
Object.freeze(obj);
|
|
31
|
-
Object.values(obj)
|
|
59
|
+
for (const value of Object.values(obj)) {
|
|
32
60
|
if (
|
|
33
61
|
typeof value === "object" &&
|
|
34
62
|
value !== null &&
|
|
@@ -37,29 +65,6 @@ function deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {
|
|
|
37
65
|
) {
|
|
38
66
|
deepFreeze(value as PromptNamespace);
|
|
39
67
|
}
|
|
40
|
-
}
|
|
68
|
+
}
|
|
41
69
|
return obj as PromptRegistry<T>;
|
|
42
70
|
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
|
|
46
|
-
*
|
|
47
|
-
* The registry is typically created by generated code — the CLI produces
|
|
48
|
-
* an `index.ts` that calls `createPromptRegistry()` with all discovered
|
|
49
|
-
* prompt modules keyed by camelCase name, nested by group.
|
|
50
|
-
*
|
|
51
|
-
* @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.
|
|
52
|
-
* @returns A deep-frozen, typed record with direct property access.
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```ts
|
|
56
|
-
* const prompts = createPromptRegistry({
|
|
57
|
-
* agents: { coverageAssessor },
|
|
58
|
-
* greeting,
|
|
59
|
-
* })
|
|
60
|
-
* prompts.agents.coverageAssessor.render({ scope: 'full' })
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
export function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {
|
|
64
|
-
return deepFreeze({ ...modules });
|
|
65
|
-
}
|
package/src/types.ts
CHANGED
|
@@ -6,17 +6,34 @@ import type { ZodType } from "zod";
|
|
|
6
6
|
*/
|
|
7
7
|
export type CreateEngineOptions = Pick<
|
|
8
8
|
LiquidOptions,
|
|
9
|
-
| "
|
|
10
|
-
| "partials"
|
|
11
|
-
| "extname"
|
|
12
|
-
| "cache"
|
|
13
|
-
| "strictFilters"
|
|
14
|
-
| "strictVariables"
|
|
15
|
-
| "ownPropertyOnly"
|
|
9
|
+
"root" | "partials" | "extname" | "cache" | "strictVariables"
|
|
16
10
|
>;
|
|
17
11
|
|
|
18
12
|
/**
|
|
19
|
-
*
|
|
13
|
+
* Configuration for creating a prompt module via {@link createPrompt}.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const config: PromptConfig<{ name: string }> = {
|
|
18
|
+
* name: 'greeting',
|
|
19
|
+
* template: 'Hello {{ name }}!',
|
|
20
|
+
* schema: z.object({ name: z.string() }),
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface PromptConfig<T = unknown> {
|
|
25
|
+
/** Kebab-case prompt identifier (e.g. `'greeting'`, `'worker-system'`). */
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/** LiquidJS template string with `{{ variable }}` expressions. */
|
|
28
|
+
readonly template: string;
|
|
29
|
+
/** Zod schema for validating template variables. */
|
|
30
|
+
readonly schema: ZodType<T>;
|
|
31
|
+
/** Optional group path (e.g. `'agents'`, `'agents/core'`). */
|
|
32
|
+
readonly group?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A single prompt module produced by {@link createPrompt} or codegen.
|
|
20
37
|
*
|
|
21
38
|
* Each `.prompt` file generates a default export conforming to this shape.
|
|
22
39
|
*/
|
|
@@ -32,9 +49,9 @@ export interface PromptModule<T = unknown> {
|
|
|
32
49
|
* A nested namespace node in the prompt tree.
|
|
33
50
|
* Values are either PromptModule leaves or further nested namespaces.
|
|
34
51
|
*/
|
|
35
|
-
export
|
|
52
|
+
export interface PromptNamespace {
|
|
36
53
|
readonly [key: string]: PromptModule | PromptNamespace;
|
|
37
|
-
}
|
|
54
|
+
}
|
|
38
55
|
|
|
39
56
|
/**
|
|
40
57
|
* Deep-readonly version of a prompt tree.
|
package/tsconfig.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"engine-B53whAlC.mjs","names":[],"sources":["../../src/engine.ts"],"sourcesContent":["import { Liquid } from \"liquidjs\";\n\nimport type { CreateEngineOptions } from \"./types.js\";\n\n/**\n * Create a LiquidJS engine with custom options.\n *\n * The `partialsDir` is used as the root for `{% render %}` resolution.\n * The `.prompt` extension is appended automatically.\n *\n * @param partialsDir - Root directory for `{% render %}` partial resolution.\n * @param options - Optional overrides for the LiquidJS engine configuration.\n * @returns A configured {@link Liquid} engine instance.\n */\nexport function createEngine(partialsDir: string, options?: Partial<CreateEngineOptions>): Liquid {\n return new Liquid({\n root: [partialsDir],\n partials: [partialsDir],\n extname: \".prompt\",\n cache: true,\n strictFilters: true,\n ownPropertyOnly: true,\n ...options,\n });\n}\n\n/**\n * Shared LiquidJS engine for rendering prompt templates at runtime.\n *\n * Partials are flattened at codegen time by the CLI, so this engine\n * only needs to handle `{{ var }}` expressions and basic Liquid\n * control flow (`{% if %}`, `{% for %}`). No filesystem access required.\n */\nexport const liquidEngine = new Liquid({\n strictFilters: true,\n ownPropertyOnly: true,\n});\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,aAAa,aAAqB,SAAgD;AAChG,QAAO,IAAI,OAAO;EAChB,MAAM,CAAC,YAAY;EACnB,UAAU,CAAC,YAAY;EACvB,SAAS;EACT,OAAO;EACP,eAAe;EACf,iBAAiB;EACjB,GAAG;EACJ,CAAC;;;;;;;;;AAUJ,MAAa,eAAe,IAAI,OAAO;CACrC,eAAe;CACf,iBAAiB;CAClB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-CiSdNM0W.d.mts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;AAMA;KAAY,mBAAA,GAAsB,IAAA,CAChC,aAAA;;;;AAeF;;UAAiB,YAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA,EAAQ,OAAA,CAAQ,CAAA;EACzB,MAAA,CAAO,SAAA,EAAW,CAAA;EAClB,QAAA,CAAS,SAAA,YAAqB,CAAA;AAAA;;;;;KAOpB,eAAA;EAAA,UACA,GAAA,WAAc,YAAA,GAAe,eAAA;AAAA;;;;;;;;AADzC;;;;;KAgBY,cAAA,WAAyB,eAAA,2BACd,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,YAAA,GAClC,CAAA,CAAE,CAAA,IACF,CAAA,CAAE,CAAA,UAAW,eAAA,GACX,cAAA,CAAe,CAAA,CAAE,CAAA,KACjB,CAAA,CAAE,CAAA"}
|