promptopskit 0.5.0 → 0.6.2

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.
Files changed (70) hide show
  1. package/README.md +9 -7
  2. package/SKILL.md +16 -10
  3. package/dist/{chunk-6VLKZCNS.js → chunk-55B7QQBK.js} +2 -2
  4. package/dist/{chunk-6BOUQVSX.js → chunk-FNCIBZJF.js} +3 -3
  5. package/dist/{chunk-D75HKF64.js → chunk-GLT3YYWK.js} +2 -2
  6. package/dist/{chunk-IN4KT5EU.js → chunk-IPFD5Y3O.js} +3 -3
  7. package/dist/{chunk-KN7H4JRK.js → chunk-JR7252DG.js} +393 -386
  8. package/dist/chunk-JR7252DG.js.map +1 -0
  9. package/dist/{chunk-K6ZU3CGV.js → chunk-UY5OKMVP.js} +281 -17
  10. package/dist/chunk-UY5OKMVP.js.map +1 -0
  11. package/dist/{chunk-QK3WKE3K.js → chunk-VMLGUI4F.js} +2 -2
  12. package/dist/{chunk-LNGHGGJN.js → chunk-XEZJZ3IG.js} +2 -2
  13. package/dist/cli/index.js +410 -103
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/index.cjs +971 -660
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +5 -4
  18. package/dist/index.d.ts +5 -4
  19. package/dist/index.js +55 -15
  20. package/dist/index.js.map +1 -1
  21. package/dist/providers/anthropic.cjs +675 -407
  22. package/dist/providers/anthropic.cjs.map +1 -1
  23. package/dist/providers/anthropic.d.cts +2 -2
  24. package/dist/providers/anthropic.d.ts +2 -2
  25. package/dist/providers/anthropic.js +3 -3
  26. package/dist/providers/gemini.cjs +675 -407
  27. package/dist/providers/gemini.cjs.map +1 -1
  28. package/dist/providers/gemini.d.cts +2 -2
  29. package/dist/providers/gemini.d.ts +2 -2
  30. package/dist/providers/gemini.js +3 -3
  31. package/dist/providers/llmasaservice.cjs +675 -407
  32. package/dist/providers/llmasaservice.cjs.map +1 -1
  33. package/dist/providers/llmasaservice.d.cts +2 -2
  34. package/dist/providers/llmasaservice.d.ts +2 -2
  35. package/dist/providers/llmasaservice.js +4 -4
  36. package/dist/providers/openai-responses.cjs +675 -407
  37. package/dist/providers/openai-responses.cjs.map +1 -1
  38. package/dist/providers/openai-responses.d.cts +2 -2
  39. package/dist/providers/openai-responses.d.ts +2 -2
  40. package/dist/providers/openai-responses.js +3 -3
  41. package/dist/providers/openai.cjs +675 -407
  42. package/dist/providers/openai.cjs.map +1 -1
  43. package/dist/providers/openai.d.cts +2 -2
  44. package/dist/providers/openai.d.ts +2 -2
  45. package/dist/providers/openai.js +3 -3
  46. package/dist/providers/openrouter.cjs +675 -407
  47. package/dist/providers/openrouter.cjs.map +1 -1
  48. package/dist/providers/openrouter.d.cts +2 -2
  49. package/dist/providers/openrouter.d.ts +2 -2
  50. package/dist/providers/openrouter.js +4 -4
  51. package/dist/{schema-D-RI4w-k.d.cts → schema-CuYsv8JA.d.cts} +540 -206
  52. package/dist/{schema-D-RI4w-k.d.ts → schema-CuYsv8JA.d.ts} +540 -206
  53. package/dist/testing.cjs +43 -5
  54. package/dist/testing.cjs.map +1 -1
  55. package/dist/testing.d.cts +1 -1
  56. package/dist/testing.d.ts +1 -1
  57. package/dist/testing.js +1 -1
  58. package/dist/{types-B3dxBGu6.d.ts → types-DpK0mTHL.d.ts} +1 -1
  59. package/dist/{types-DbTAjlTN.d.cts → types-UA_p9uJy.d.cts} +1 -1
  60. package/dist/usagetap/index.d.cts +2 -2
  61. package/dist/usagetap/index.d.ts +2 -2
  62. package/package.json +9 -2
  63. package/dist/chunk-K6ZU3CGV.js.map +0 -1
  64. package/dist/chunk-KN7H4JRK.js.map +0 -1
  65. /package/dist/{chunk-6VLKZCNS.js.map → chunk-55B7QQBK.js.map} +0 -0
  66. /package/dist/{chunk-6BOUQVSX.js.map → chunk-FNCIBZJF.js.map} +0 -0
  67. /package/dist/{chunk-D75HKF64.js.map → chunk-GLT3YYWK.js.map} +0 -0
  68. /package/dist/{chunk-IN4KT5EU.js.map → chunk-IPFD5Y3O.js.map} +0 -0
  69. /package/dist/{chunk-QK3WKE3K.js.map → chunk-VMLGUI4F.js.map} +0 -0
  70. /package/dist/{chunk-LNGHGGJN.js.map → chunk-XEZJZ3IG.js.map} +0 -0
@@ -40,12 +40,13 @@ var ESCAPED_OPEN = /\\\{\\\{/g;
40
40
  var ESCAPE_PLACEHOLDER = "\0ESCAPED_OPEN\0";
41
41
  function interpolate(template, variables, options = {}) {
42
42
  const { strict = false } = options;
43
+ const optionalVariables = new Set(options.optionalVariables ?? []);
43
44
  let result = template.replace(ESCAPED_OPEN, ESCAPE_PLACEHOLDER);
44
45
  result = result.replace(VARIABLE_RE, (match, name) => {
45
46
  if (name in variables) {
46
47
  return variables[name];
47
48
  }
48
- if (strict) {
49
+ if (strict && !optionalVariables.has(name)) {
49
50
  throw new Error(`Missing required variable: "${name}"`);
50
51
  }
51
52
  return match;
@@ -54,22 +55,299 @@ function interpolate(template, variables, options = {}) {
54
55
  return result;
55
56
  }
56
57
 
58
+ // src/context.ts
59
+ var textEncoder = new TextEncoder();
60
+ var REJECT_SECRETS_PATTERN = "(secret|api[_-]?key|password)";
61
+ var REJECT_SECRETS_FLAGS = "i";
62
+ function getContextInputs(asset) {
63
+ return (asset.context?.inputs ?? []).map(normalizeContextInput);
64
+ }
65
+ function normalizeContextInput(input) {
66
+ if (typeof input === "string") {
67
+ return { name: input };
68
+ }
69
+ return {
70
+ name: input.name,
71
+ optional: input.optional,
72
+ warnings: input.warnings,
73
+ max_size: input.max_size,
74
+ trim: input.trim,
75
+ allow_regex: normalizeContextRegex(input.allow_regex),
76
+ deny_regex: normalizeContextRegex(input.deny_regex),
77
+ non_empty: normalizeBuiltInValidator(input.non_empty),
78
+ reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
79
+ };
80
+ }
81
+ function normalizeContextRegex(value) {
82
+ if (value === void 0) {
83
+ return void 0;
84
+ }
85
+ if (typeof value === "string") {
86
+ const literal = parseRegexLiteral(value);
87
+ if (value.startsWith("/") && !literal) {
88
+ return {
89
+ pattern: value,
90
+ flags: "",
91
+ raw: value,
92
+ invalidLiteral: true
93
+ };
94
+ }
95
+ return {
96
+ pattern: literal?.pattern ?? value,
97
+ flags: literal?.flags ?? "",
98
+ raw: value
99
+ };
100
+ }
101
+ return {
102
+ pattern: value.pattern,
103
+ flags: value.flags ?? "",
104
+ raw: JSON.stringify(value),
105
+ returnMessage: value.return_message
106
+ };
107
+ }
108
+ function normalizeBuiltInValidator(value) {
109
+ if (value === void 0 || value === false) {
110
+ return void 0;
111
+ }
112
+ if (value === true) {
113
+ return {};
114
+ }
115
+ return {
116
+ returnMessage: value.return_message
117
+ };
118
+ }
119
+ function parseRegexLiteral(value) {
120
+ if (!value.startsWith("/")) {
121
+ return void 0;
122
+ }
123
+ for (let index = value.length - 1; index > 0; index -= 1) {
124
+ if (value[index] !== "/") {
125
+ continue;
126
+ }
127
+ let backslashCount = 0;
128
+ for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
129
+ backslashCount += 1;
130
+ }
131
+ if (backslashCount % 2 === 1) {
132
+ continue;
133
+ }
134
+ return {
135
+ pattern: value.slice(1, index),
136
+ flags: value.slice(index + 1)
137
+ };
138
+ }
139
+ return void 0;
140
+ }
141
+ function formatInvalidContextRegexMessage(details) {
142
+ return [
143
+ `Invalid context regex for prompt "${details.promptId}"`,
144
+ `variable "${details.variable}"`,
145
+ `field "${details.field}"`,
146
+ `value ${JSON.stringify(details.raw)}: ${details.reason}`
147
+ ].join(", ");
148
+ }
149
+ function compileContextRegex(regex, details) {
150
+ if (regex.invalidLiteral) {
151
+ throw new Error(
152
+ `POK013: ${formatInvalidContextRegexMessage({
153
+ ...details,
154
+ raw: regex.raw,
155
+ reason: "Malformed regex literal. Use /pattern/flags or { pattern, flags }."
156
+ })}`
157
+ );
158
+ }
159
+ try {
160
+ return new RegExp(regex.pattern, regex.flags);
161
+ } catch (error) {
162
+ const reason = error instanceof Error ? error.message : String(error);
163
+ throw new Error(`POK013: ${formatInvalidContextRegexMessage({ ...details, raw: regex.raw, reason })}`);
164
+ }
165
+ }
166
+ function getRejectSecretsRegex() {
167
+ return {
168
+ pattern: REJECT_SECRETS_PATTERN,
169
+ flags: REJECT_SECRETS_FLAGS,
170
+ raw: JSON.stringify({ pattern: REJECT_SECRETS_PATTERN, flags: REJECT_SECRETS_FLAGS })
171
+ };
172
+ }
173
+ function isTrimEnabled(mode) {
174
+ return mode === true || mode === "start" || mode === "end" || mode === "both";
175
+ }
176
+ function normalizeTrimMode(mode) {
177
+ if (mode === "start") {
178
+ return "start";
179
+ }
180
+ return "end";
181
+ }
182
+ function trimToMaxSize(value, maxSize, mode) {
183
+ const measured = measureContextValueSize(value);
184
+ if (measured <= maxSize) {
185
+ return value;
186
+ }
187
+ const characters = Array.from(value);
188
+ const normalizedMode = normalizeTrimMode(mode);
189
+ if (normalizedMode === "start") {
190
+ let collected2 = "";
191
+ let size2 = 0;
192
+ for (let i = characters.length - 1; i >= 0; i -= 1) {
193
+ const next = characters[i];
194
+ const charSize = measureContextValueSize(next);
195
+ if (size2 + charSize > maxSize) {
196
+ break;
197
+ }
198
+ collected2 = next + collected2;
199
+ size2 += charSize;
200
+ }
201
+ return collected2;
202
+ }
203
+ let collected = "";
204
+ let size = 0;
205
+ for (const char of characters) {
206
+ const charSize = measureContextValueSize(char);
207
+ if (size + charSize > maxSize) {
208
+ break;
209
+ }
210
+ collected += char;
211
+ size += charSize;
212
+ }
213
+ return collected;
214
+ }
215
+ function sanitizeContextVariables(asset, variables = {}, options = {}) {
216
+ const { onContextOverflow } = options;
217
+ const sanitized = { ...variables };
218
+ for (const input of getContextInputs(asset)) {
219
+ const value = sanitized[input.name];
220
+ if (value === void 0) {
221
+ continue;
222
+ }
223
+ let candidate = value;
224
+ if (input.max_size !== void 0) {
225
+ const actualSize = measureContextValueSize(candidate);
226
+ if (actualSize > input.max_size && onContextOverflow) {
227
+ candidate = onContextOverflow({
228
+ promptId: asset.id,
229
+ variable: input.name,
230
+ value: candidate,
231
+ maxSize: input.max_size,
232
+ actualSize
233
+ });
234
+ }
235
+ }
236
+ if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
237
+ candidate = trimToMaxSize(candidate, input.max_size, input.trim);
238
+ }
239
+ sanitized[input.name] = candidate;
240
+ if (input.allow_regex) {
241
+ const candidate2 = sanitized[input.name];
242
+ const matcher = compileContextRegex(input.allow_regex, {
243
+ promptId: asset.id,
244
+ variable: input.name,
245
+ field: "allow_regex"
246
+ });
247
+ if (!matcher.test(candidate2)) {
248
+ if (input.allow_regex.returnMessage) {
249
+ return {
250
+ variables: sanitized,
251
+ shortCircuit: {
252
+ returnMessage: input.allow_regex.returnMessage,
253
+ code: "POK031",
254
+ variable: input.name,
255
+ field: "allow_regex"
256
+ }
257
+ };
258
+ }
259
+ throw new Error(
260
+ `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
261
+ );
262
+ }
263
+ }
264
+ if (input.deny_regex) {
265
+ const candidate2 = sanitized[input.name];
266
+ const matcher = compileContextRegex(input.deny_regex, {
267
+ promptId: asset.id,
268
+ variable: input.name,
269
+ field: "deny_regex"
270
+ });
271
+ if (matcher.test(candidate2)) {
272
+ if (input.deny_regex.returnMessage) {
273
+ return {
274
+ variables: sanitized,
275
+ shortCircuit: {
276
+ returnMessage: input.deny_regex.returnMessage,
277
+ code: "POK032",
278
+ variable: input.name,
279
+ field: "deny_regex"
280
+ }
281
+ };
282
+ }
283
+ throw new Error(
284
+ `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
285
+ );
286
+ }
287
+ }
288
+ if (input.non_empty && candidate.trim().length === 0) {
289
+ if (input.non_empty.returnMessage) {
290
+ return {
291
+ variables: sanitized,
292
+ shortCircuit: {
293
+ returnMessage: input.non_empty.returnMessage,
294
+ code: "POK033",
295
+ variable: input.name,
296
+ field: "non_empty"
297
+ }
298
+ };
299
+ }
300
+ throw new Error(
301
+ `POK033: Context variable "${input.name}" failed non_empty validation for prompt "${asset.id}".`
302
+ );
303
+ }
304
+ if (input.reject_secrets) {
305
+ const matcher = compileContextRegex(getRejectSecretsRegex(), {
306
+ promptId: asset.id,
307
+ variable: input.name,
308
+ field: "reject_secrets"
309
+ });
310
+ if (matcher.test(candidate)) {
311
+ if (input.reject_secrets.returnMessage) {
312
+ return {
313
+ variables: sanitized,
314
+ shortCircuit: {
315
+ returnMessage: input.reject_secrets.returnMessage,
316
+ code: "POK034",
317
+ variable: input.name,
318
+ field: "reject_secrets"
319
+ }
320
+ };
321
+ }
322
+ throw new Error(
323
+ `POK034: Context variable "${input.name}" matched reject_secrets validation for prompt "${asset.id}".`
324
+ );
325
+ }
326
+ }
327
+ }
328
+ return { variables: sanitized };
329
+ }
330
+ function measureContextValueSize(value) {
331
+ return textEncoder.encode(value).length;
332
+ }
333
+
57
334
  // src/renderer/renderer.ts
58
335
  function renderSections(asset, options = {}) {
59
336
  const { variables = {}, strict = false } = options;
337
+ const optionalVariables = getContextInputs(asset).filter((input) => input.optional === true).map((input) => input.name);
60
338
  const result = {};
61
339
  if (asset.sections.system_instructions) {
62
340
  result.system_instructions = interpolate(
63
341
  asset.sections.system_instructions,
64
342
  variables,
65
- { strict }
343
+ { strict, optionalVariables }
66
344
  );
67
345
  }
68
346
  if (asset.sections.prompt_template) {
69
347
  result.prompt_template = interpolate(
70
348
  asset.sections.prompt_template,
71
349
  variables,
72
- { strict }
350
+ { strict, optionalVariables }
73
351
  );
74
352
  }
75
353
  return result;
@@ -169,9 +447,9 @@ function resolveAssetForProvider(asset, runtime = {}) {
169
447
  }
170
448
 
171
449
  // src/prompt-resolution.ts
172
- var import_promises3 = require("fs/promises");
450
+ var import_promises4 = require("fs/promises");
173
451
  var import_node_fs2 = require("fs");
174
- var import_node_path3 = require("path");
452
+ var import_node_path4 = require("path");
175
453
 
176
454
  // src/cache.ts
177
455
  var import_node_fs = require("fs");
@@ -220,8 +498,8 @@ var PromptCache = class {
220
498
  };
221
499
 
222
500
  // src/composition/resolve-includes.ts
223
- var import_promises2 = require("fs/promises");
224
- var import_node_path2 = require("path");
501
+ var import_promises3 = require("fs/promises");
502
+ var import_node_path3 = require("path");
225
503
 
226
504
  // src/parser/parser.ts
227
505
  var import_gray_matter = __toESM(require("gray-matter"), 1);
@@ -257,10 +535,26 @@ var ResponseSchema = import_zod.z.object({
257
535
  format: import_zod.z.enum(["text", "json", "markdown"]).optional(),
258
536
  stream: import_zod.z.boolean().optional(),
259
537
  schema: import_zod.z.record(import_zod.z.unknown()).optional(),
538
+ schema_ref: import_zod.z.string().min(1).optional(),
539
+ schema_source: import_zod.z.object({
540
+ mode: import_zod.z.enum(["inline", "schema_ref_json", "schema_ref_zod_module"]).optional(),
541
+ ref: import_zod.z.string().optional(),
542
+ resolved_path: import_zod.z.string().optional(),
543
+ hash: import_zod.z.string().optional()
544
+ }).optional(),
260
545
  schema_name: import_zod.z.string().optional(),
261
546
  schema_description: import_zod.z.string().optional(),
262
547
  schema_strict: import_zod.z.boolean().optional()
263
548
  });
549
+ var ResponseSchemaWithValidation = ResponseSchema.superRefine((value, ctx) => {
550
+ if (value.schema !== void 0 && value.schema_ref !== void 0) {
551
+ ctx.addIssue({
552
+ code: import_zod.z.ZodIssueCode.custom,
553
+ message: "response.schema and response.schema_ref are mutually exclusive",
554
+ path: ["schema_ref"]
555
+ });
556
+ }
557
+ });
264
558
  var AnthropicProviderOptionsSchema = import_zod.z.object({
265
559
  top_k: import_zod.z.number().int().min(0).optional(),
266
560
  tool_choice: import_zod.z.record(import_zod.z.unknown()).optional(),
@@ -352,6 +646,8 @@ var ContextBuiltInValidatorSchema = import_zod.z.union([
352
646
  ]);
353
647
  var ContextInputDefinitionObjectSchema = import_zod.z.object({
354
648
  name: import_zod.z.string(),
649
+ optional: import_zod.z.boolean().optional(),
650
+ warnings: import_zod.z.boolean().optional(),
355
651
  max_size: import_zod.z.number().int().positive().optional(),
356
652
  trim: import_zod.z.union([import_zod.z.boolean(), import_zod.z.enum(["start", "end", "both"])]).optional(),
357
653
  allow_regex: ContextRegexSchema.optional(),
@@ -381,7 +677,7 @@ var PromptAssetOverridesSchema = import_zod.z.object({
381
677
  fallback_models: import_zod.z.array(import_zod.z.string()).optional(),
382
678
  reasoning: ReasoningSchema.optional(),
383
679
  sampling: SamplingSchema.optional(),
384
- response: ResponseSchema.optional(),
680
+ response: ResponseSchemaWithValidation.optional(),
385
681
  cache: CacheSchema.optional(),
386
682
  raw: RawProviderBodySchema.optional(),
387
683
  tools: import_zod.z.array(ToolRefSchema).optional(),
@@ -399,7 +695,19 @@ var SectionsSchema = import_zod.z.object({
399
695
  var PromptDefaultsSchema = import_zod.z.object({
400
696
  provider: import_zod.z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "llmasaservice", "any"]).optional(),
401
697
  model: import_zod.z.string().optional(),
698
+ fallback_models: import_zod.z.array(import_zod.z.string()).optional(),
699
+ reasoning: ReasoningSchema.optional(),
700
+ sampling: SamplingSchema.optional(),
701
+ response: ResponseSchema.optional(),
402
702
  cache: CacheSchema.optional(),
703
+ raw: RawProviderBodySchema.optional(),
704
+ tools: import_zod.z.array(ToolRefSchema).optional(),
705
+ provider_options: ProviderOptionsSchema.optional(),
706
+ mcp: MCPSchema.optional(),
707
+ context: ContextSchema.optional(),
708
+ includes: import_zod.z.array(import_zod.z.string()).optional(),
709
+ environments: import_zod.z.record(PromptAssetOverridesSchema).optional(),
710
+ tiers: import_zod.z.record(PromptAssetOverridesSchema).optional(),
403
711
  metadata: MetadataSchema.optional(),
404
712
  sections: import_zod.z.object({
405
713
  system_instructions: import_zod.z.string().optional()
@@ -414,7 +722,7 @@ var PromptAssetSchema = import_zod.z.object({
414
722
  fallback_models: import_zod.z.array(import_zod.z.string()).optional(),
415
723
  reasoning: ReasoningSchema.optional(),
416
724
  sampling: SamplingSchema.optional(),
417
- response: ResponseSchema.optional(),
725
+ response: ResponseSchemaWithValidation.optional(),
418
726
  cache: CacheSchema.optional(),
419
727
  raw: RawProviderBodySchema.optional(),
420
728
  tools: import_zod.z.array(ToolRefSchema).optional(),
@@ -595,27 +903,133 @@ function parsePrompt(content, filePath) {
595
903
  sections,
596
904
  source: filePath ? { file_path: filePath } : void 0
597
905
  };
598
- const asset = PromptAssetSchema.parse(raw);
906
+ const asset = PromptAssetSchema.parse(raw);
907
+ return {
908
+ asset,
909
+ raw: {
910
+ frontMatter,
911
+ body
912
+ }
913
+ };
914
+ }
915
+
916
+ // src/parser/loader.ts
917
+ var import_promises2 = require("fs/promises");
918
+ var import_node_path2 = require("path");
919
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
920
+
921
+ // src/parser/response-schema-ref.ts
922
+ var import_promises = require("fs/promises");
923
+ var import_node_crypto = require("crypto");
924
+ var import_node_path = require("path");
925
+ var import_node_url = require("url");
926
+ var import_zod2 = require("zod");
927
+ var import_zod_to_json_schema = require("zod-to-json-schema");
928
+ var SUPPORTED_SCHEMA_REF_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
929
+ async function resolveJsonSchemaRef(schemaRef, promptFilePath) {
930
+ const resolvedPath = (0, import_node_path.resolve)((0, import_node_path.dirname)(promptFilePath), schemaRef);
931
+ let raw;
932
+ try {
933
+ raw = await (0, import_promises.readFile)(resolvedPath, "utf-8");
934
+ } catch (error) {
935
+ const code = error.code;
936
+ if (code === "ENOENT") {
937
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" not found (resolved from ${promptFilePath})`);
938
+ }
939
+ throw error;
940
+ }
941
+ let parsed;
942
+ try {
943
+ parsed = JSON.parse(raw);
944
+ } catch {
945
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" is not valid JSON`);
946
+ }
947
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
948
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" must resolve to a JSON object schema`);
949
+ }
950
+ return {
951
+ schema: parsed,
952
+ resolvedPath,
953
+ hash: (0, import_node_crypto.createHash)("sha256").update(raw).digest("hex")
954
+ };
955
+ }
956
+ async function resolveZodSchemaRef(schemaRef, promptFilePath) {
957
+ const resolvedPath = (0, import_node_path.resolve)((0, import_node_path.dirname)(promptFilePath), schemaRef);
958
+ let moduleSource;
959
+ try {
960
+ moduleSource = await (0, import_promises.readFile)(resolvedPath, "utf-8");
961
+ } catch (error) {
962
+ const code = error.code;
963
+ if (code === "ENOENT") {
964
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" not found (resolved from ${promptFilePath})`);
965
+ }
966
+ throw error;
967
+ }
968
+ let imported;
969
+ try {
970
+ imported = await import((0, import_node_url.pathToFileURL)(resolvedPath).href);
971
+ } catch (error) {
972
+ const message = error instanceof Error ? error.message : String(error);
973
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" could not be imported as a module (${message})`);
974
+ }
975
+ const mod = imported;
976
+ const candidate = mod.default ?? mod.schema;
977
+ if (!(candidate instanceof import_zod2.z.ZodType)) {
978
+ throw new Error('POK051: zod schema modules must export a Zod schema as default export or named export "schema"');
979
+ }
980
+ const jsonSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(candidate, {
981
+ target: "jsonSchema7",
982
+ $refStrategy: "none"
983
+ });
984
+ if (!jsonSchema || typeof jsonSchema !== "object" || Array.isArray(jsonSchema)) {
985
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" did not produce a valid JSON schema object`);
986
+ }
987
+ return {
988
+ schema: jsonSchema,
989
+ resolvedPath,
990
+ hash: (0, import_node_crypto.createHash)("sha256").update(moduleSource).digest("hex")
991
+ };
992
+ }
993
+ async function resolveResponseSchemaRef(asset, promptFilePath) {
994
+ const schemaRef = asset.response?.schema_ref;
995
+ if (!schemaRef) return asset;
996
+ const ext = (0, import_node_path.extname)(schemaRef).toLowerCase();
997
+ if (!SUPPORTED_SCHEMA_REF_EXTENSIONS.has(ext)) {
998
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" has unsupported extension "${ext}". Use .json, .js, .mjs, or .cjs`);
999
+ }
1000
+ const resolved = ext === ".json" ? await resolveJsonSchemaRef(schemaRef, promptFilePath) : await resolveZodSchemaRef(schemaRef, promptFilePath);
599
1001
  return {
600
- asset,
601
- raw: {
602
- frontMatter,
603
- body
1002
+ ...asset,
1003
+ response: {
1004
+ ...asset.response,
1005
+ schema: resolved.schema,
1006
+ schema_ref: void 0,
1007
+ schema_source: {
1008
+ mode: ext === ".json" ? "schema_ref_json" : "schema_ref_zod_module",
1009
+ ref: schemaRef,
1010
+ resolved_path: resolved.resolvedPath,
1011
+ hash: resolved.hash
1012
+ }
604
1013
  }
605
1014
  };
606
1015
  }
607
1016
 
608
1017
  // src/parser/loader.ts
609
- var import_promises = require("fs/promises");
610
- var import_node_path = require("path");
611
- var import_gray_matter2 = __toESM(require("gray-matter"), 1);
612
1018
  var DEFAULTS_FILE_NAME = "defaults.md";
613
1019
  async function loadPromptFile(filePath, options = {}) {
614
- const content = await (0, import_promises.readFile)(filePath, "utf-8");
1020
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
615
1021
  const parsed = parsePrompt(content, filePath);
616
- const root = options.defaultsRoot ?? (0, import_node_path.dirname)(filePath);
1022
+ const root = options.defaultsRoot ?? (0, import_node_path2.dirname)(filePath);
617
1023
  const defaults = await loadDefaultsForPath(filePath, root);
618
- const asset = applyDefaults(parsed.asset, defaults);
1024
+ const withDefaults = applyDefaults(parsed.asset, defaults);
1025
+ const resolved = await resolveResponseSchemaRef(withDefaults, filePath);
1026
+ const asset = resolved.response?.schema !== void 0 && !resolved.response?.schema_source ? {
1027
+ ...resolved,
1028
+ response: {
1029
+ ...resolved.response,
1030
+ schema_source: { mode: "inline" }
1031
+ }
1032
+ } : resolved;
619
1033
  return {
620
1034
  ...parsed,
621
1035
  asset
@@ -625,10 +1039,10 @@ async function loadDefaultsForPath(filePath, defaultsRoot) {
625
1039
  const directories = getDirectoriesToCheck(filePath, defaultsRoot);
626
1040
  let merged = {};
627
1041
  for (const dir of directories) {
628
- const defaultsPath = (0, import_node_path.join)(dir, DEFAULTS_FILE_NAME);
1042
+ const defaultsPath = (0, import_node_path2.join)(dir, DEFAULTS_FILE_NAME);
629
1043
  try {
630
- const defaultsContent = await (0, import_promises.readFile)(defaultsPath, "utf-8");
631
- const defaults = parseDefaults(defaultsContent);
1044
+ const defaultsContent = await (0, import_promises2.readFile)(defaultsPath, "utf-8");
1045
+ const defaults = normalizeDefaultIncludes(parseDefaults(defaultsContent), defaultsPath, filePath);
632
1046
  merged = mergeDefaults(merged, defaults);
633
1047
  } catch (error) {
634
1048
  if (error.code !== "ENOENT") {
@@ -640,14 +1054,14 @@ async function loadDefaultsForPath(filePath, defaultsRoot) {
640
1054
  }
641
1055
  function getDirectoriesToCheck(filePath, defaultsRoot) {
642
1056
  const dirs = [];
643
- let current = (0, import_node_path.resolve)((0, import_node_path.dirname)(filePath));
644
- const boundary = defaultsRoot ? (0, import_node_path.resolve)(defaultsRoot) : void 0;
1057
+ let current = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(filePath));
1058
+ const boundary = defaultsRoot ? (0, import_node_path2.resolve)(defaultsRoot) : void 0;
645
1059
  while (true) {
646
1060
  dirs.unshift(current);
647
- if (boundary && current === boundary || current === (0, import_node_path.dirname)(current)) {
1061
+ if (boundary && current === boundary || current === (0, import_node_path2.dirname)(current)) {
648
1062
  break;
649
1063
  }
650
- current = (0, import_node_path.dirname)(current);
1064
+ current = (0, import_node_path2.dirname)(current);
651
1065
  }
652
1066
  return dirs;
653
1067
  }
@@ -665,7 +1079,19 @@ function mergeDefaults(base, local) {
665
1079
  return {
666
1080
  provider: local.provider ?? base.provider,
667
1081
  model: local.model ?? base.model,
1082
+ fallback_models: local.fallback_models ?? base.fallback_models,
1083
+ reasoning: mergeRecordBlock2(base.reasoning, local.reasoning),
1084
+ sampling: mergeRecordBlock2(base.sampling, local.sampling),
1085
+ response: mergeRecordBlock2(base.response, local.response),
668
1086
  cache: mergeCache(base.cache, local.cache),
1087
+ raw: mergeRaw(base.raw, local.raw),
1088
+ tools: local.tools ?? base.tools,
1089
+ provider_options: mergeProviderOptions(base.provider_options, local.provider_options),
1090
+ mcp: mergeMcp(base.mcp, local.mcp),
1091
+ context: mergeContext(base.context, local.context),
1092
+ includes: local.includes ?? base.includes,
1093
+ environments: mergeOverrideMap(base.environments, local.environments),
1094
+ tiers: mergeOverrideMap(base.tiers, local.tiers),
669
1095
  metadata: {
670
1096
  ...base.metadata ?? {},
671
1097
  ...local.metadata ?? {}
@@ -678,11 +1104,20 @@ function mergeDefaults(base, local) {
678
1104
  }
679
1105
  function applyDefaults(asset, defaults) {
680
1106
  const cache = mergeCache(defaults.cache, asset.cache);
1107
+ const raw = mergeRaw(defaults.raw, asset.raw);
1108
+ const providerOptions = mergeProviderOptions(defaults.provider_options, asset.provider_options);
1109
+ const reasoning = mergeRecordBlock2(defaults.reasoning, asset.reasoning);
1110
+ const sampling = mergeRecordBlock2(defaults.sampling, asset.sampling);
1111
+ const response = mergeRecordBlock2(defaults.response, asset.response);
1112
+ const mcp = mergeMcp(defaults.mcp, asset.mcp);
1113
+ const context = mergeContext(defaults.context, asset.context);
1114
+ const environments = mergeOverrideMap(defaults.environments, asset.environments);
1115
+ const tiers = mergeOverrideMap(defaults.tiers, asset.tiers);
681
1116
  const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
682
1117
  const hasDefaultSystem = !!defaults.sections?.system_instructions;
683
- const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0;
684
- const hasDefaultCache = cache !== void 0;
685
- if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultCache) {
1118
+ const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0 || defaults.fallback_models !== void 0 || defaults.tools !== void 0 || defaults.includes !== void 0;
1119
+ const hasDefaultObjects = cache !== void 0 || raw !== void 0 || providerOptions !== void 0 || reasoning !== void 0 || sampling !== void 0 || response !== void 0 || mcp !== void 0 || context !== void 0 || environments !== void 0 || tiers !== void 0;
1120
+ if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultObjects) {
686
1121
  return asset;
687
1122
  }
688
1123
  const mergedMetadata = {
@@ -696,11 +1131,52 @@ function applyDefaults(asset, defaults) {
696
1131
  ...asset,
697
1132
  provider: asset.provider ?? defaults.provider,
698
1133
  model: asset.model ?? defaults.model,
1134
+ fallback_models: asset.fallback_models ?? defaults.fallback_models,
1135
+ reasoning,
1136
+ sampling,
1137
+ response,
699
1138
  cache,
1139
+ raw,
1140
+ tools: asset.tools ?? defaults.tools,
1141
+ provider_options: providerOptions,
1142
+ mcp,
1143
+ context,
1144
+ includes: asset.includes ?? defaults.includes,
1145
+ environments,
1146
+ tiers,
700
1147
  metadata,
701
1148
  sections
702
1149
  };
703
1150
  }
1151
+ function normalizeDefaultIncludes(defaults, defaultsPath, filePath) {
1152
+ if (!defaults.includes) return defaults;
1153
+ const promptDir = (0, import_node_path2.dirname)(filePath);
1154
+ const defaultsDir = (0, import_node_path2.dirname)(defaultsPath);
1155
+ const includes = defaults.includes.map((includePath) => {
1156
+ const relativePath = (0, import_node_path2.relative)(promptDir, (0, import_node_path2.resolve)(defaultsDir, includePath)).replace(/\\/g, "/");
1157
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
1158
+ });
1159
+ return {
1160
+ ...defaults,
1161
+ includes
1162
+ };
1163
+ }
1164
+ function mergeRecordBlock2(base, local) {
1165
+ if (base === void 0) return local;
1166
+ if (local === void 0) return base;
1167
+ return { ...base, ...local };
1168
+ }
1169
+ function mergeMcp(base, local) {
1170
+ return mergeRecordBlock2(base, local);
1171
+ }
1172
+ function mergeContext(base, local) {
1173
+ const merged = {
1174
+ ...base ?? {},
1175
+ ...local ?? {},
1176
+ history: mergeRecordBlock2(base?.history, local?.history)
1177
+ };
1178
+ return Object.keys(merged).length > 0 ? merged : void 0;
1179
+ }
704
1180
  function mergeCache(base, local) {
705
1181
  const merged = {
706
1182
  ...base ?? {},
@@ -728,406 +1204,198 @@ function mergeCache(base, local) {
728
1204
  if (merged.google && Object.keys(merged.google).length === 0) delete merged.google;
729
1205
  return Object.keys(merged).length > 0 ? merged : void 0;
730
1206
  }
731
-
732
- // src/composition/resolve-includes.ts
733
- async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
734
- if (!asset.includes || asset.includes.length === 0) {
735
- return asset;
736
- }
737
- const baseDir = (0, import_node_path2.dirname)(basePath);
738
- const resolvedPath = (0, import_node_path2.resolve)(basePath);
739
- if (visited.has(resolvedPath)) {
740
- throw new Error(`Circular include detected: ${resolvedPath}`);
741
- }
742
- visited.add(resolvedPath);
743
- let mergedSystemInstructions = "";
744
- for (const includePath of asset.includes) {
745
- const fullPath = (0, import_node_path2.resolve)(baseDir, includePath);
746
- if (visited.has(fullPath)) {
747
- throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
748
- }
749
- const content = await (0, import_promises2.readFile)(fullPath, "utf-8");
750
- const { asset: includedAsset } = parsePrompt(content, fullPath);
751
- const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
752
- if (resolved.sections?.system_instructions) {
753
- mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
754
- }
755
- }
756
- const localSystem = asset.sections?.system_instructions ?? "";
757
- const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
758
- return {
759
- ...asset,
760
- sections: {
761
- ...asset.sections,
762
- system_instructions: combinedSystem
763
- },
764
- // Drop includes from the resolved asset — they've been inlined
765
- includes: void 0
766
- };
767
- }
768
-
769
- // src/prompt-resolution.ts
770
- var DEFAULT_PROMPTS_DIR = "./prompts";
771
- var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
772
- function withPromptResolutionDefaults(config) {
773
- return {
774
- ...config,
775
- sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
776
- compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
777
- };
778
- }
779
- var sharedPromptCache = new PromptCache();
780
- async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
781
- const resolvedConfig = withPromptResolutionDefaults(config);
782
- const mode = resolvedConfig.mode ?? "auto";
783
- if (mode !== "source-only" && resolvedConfig.compiledDir) {
784
- const compiledFile = (0, import_node_path3.resolve)(resolvedConfig.compiledDir, promptPath + ".json");
785
- if ((0, import_node_fs2.existsSync)(compiledFile)) {
786
- if (mode === "auto") {
787
- const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
788
- if ((0, import_node_fs2.existsSync)(sourceFile)) {
789
- const compiledMtime = (0, import_node_fs2.statSync)(compiledFile).mtimeMs;
790
- const sourceMtime = (0, import_node_fs2.statSync)(sourceFile).mtimeMs;
791
- if (sourceMtime > compiledMtime) {
792
- console.warn(
793
- `[promptopskit] Warning: compiled artifact for "${promptPath}" is older than source .md file.
794
- Run "promptopskit compile" or switch to source-only mode.`
795
- );
796
- }
797
- }
798
- }
799
- const content = await (0, import_promises3.readFile)(compiledFile, "utf-8");
800
- return JSON.parse(content);
801
- }
802
- if (mode === "compiled-only") {
803
- throw new Error(
804
- `Compiled artifact not found: ${compiledFile}
805
- Run "promptopskit compile" to generate it.`
806
- );
807
- }
808
- }
809
- if (mode !== "compiled-only") {
810
- const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
811
- if (resolvedConfig.cache !== false) {
812
- const cached = promptCache.get(sourceFile);
813
- if (cached) {
814
- return cached;
815
- }
816
- }
817
- if (!(0, import_node_fs2.existsSync)(sourceFile)) {
818
- const paths = [sourceFile];
819
- if (resolvedConfig.compiledDir) {
820
- paths.unshift((0, import_node_path3.resolve)(resolvedConfig.compiledDir, promptPath + ".json"));
821
- }
822
- throw new Error(
823
- `Prompt not found: "${promptPath}"
824
- Searched:
825
- ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
826
- );
827
- }
828
- const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
829
- if (resolvedConfig.cache !== false) {
830
- promptCache.set(sourceFile, asset);
831
- }
832
- return asset;
833
- }
834
- throw new Error(`Prompt not found: "${promptPath}"`);
835
- }
836
- async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
837
- const resolvedConfig = withPromptResolutionDefaults(config);
838
- let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
839
- const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
840
- if (asset.includes && asset.includes.length > 0 && (0, import_node_fs2.existsSync)(sourceFile)) {
841
- asset = await resolveIncludes(asset, sourceFile);
842
- }
843
- asset = applyOverrides(asset, {
844
- environment: options.environment,
845
- tier: options.tier,
846
- runtime: options.runtime
847
- });
848
- return asset;
849
- }
850
- function resolveInlinePromptSource(source, options = {}) {
851
- const { asset } = parsePrompt(source);
852
- return applyOverrides(asset, {
853
- environment: options.environment,
854
- tier: options.tier,
855
- runtime: options.runtime
856
- });
857
- }
858
-
859
- // src/context.ts
860
- var textEncoder = new TextEncoder();
861
- var REJECT_SECRETS_PATTERN = "(secret|api[_-]?key|password)";
862
- var REJECT_SECRETS_FLAGS = "i";
863
- function getContextInputs(asset) {
864
- return (asset.context?.inputs ?? []).map(normalizeContextInput);
865
- }
866
- function normalizeContextInput(input) {
867
- if (typeof input === "string") {
868
- return { name: input };
869
- }
870
- return {
871
- name: input.name,
872
- max_size: input.max_size,
873
- trim: input.trim,
874
- allow_regex: normalizeContextRegex(input.allow_regex),
875
- deny_regex: normalizeContextRegex(input.deny_regex),
876
- non_empty: normalizeBuiltInValidator(input.non_empty),
877
- reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
878
- };
879
- }
880
- function normalizeContextRegex(value) {
881
- if (value === void 0) {
882
- return void 0;
883
- }
884
- if (typeof value === "string") {
885
- const literal = parseRegexLiteral(value);
886
- if (value.startsWith("/") && !literal) {
887
- return {
888
- pattern: value,
889
- flags: "",
890
- raw: value,
891
- invalidLiteral: true
892
- };
893
- }
894
- return {
895
- pattern: literal?.pattern ?? value,
896
- flags: literal?.flags ?? "",
897
- raw: value
898
- };
899
- }
900
- return {
901
- pattern: value.pattern,
902
- flags: value.flags ?? "",
903
- raw: JSON.stringify(value),
904
- returnMessage: value.return_message
905
- };
906
- }
907
- function normalizeBuiltInValidator(value) {
908
- if (value === void 0 || value === false) {
909
- return void 0;
910
- }
911
- if (value === true) {
912
- return {};
913
- }
914
- return {
915
- returnMessage: value.return_message
916
- };
917
- }
918
- function parseRegexLiteral(value) {
919
- if (!value.startsWith("/")) {
920
- return void 0;
921
- }
922
- for (let index = value.length - 1; index > 0; index -= 1) {
923
- if (value[index] !== "/") {
924
- continue;
925
- }
926
- let backslashCount = 0;
927
- for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
928
- backslashCount += 1;
929
- }
930
- if (backslashCount % 2 === 1) {
931
- continue;
932
- }
933
- return {
934
- pattern: value.slice(1, index),
935
- flags: value.slice(index + 1)
936
- };
937
- }
938
- return void 0;
939
- }
940
- function formatInvalidContextRegexMessage(details) {
941
- return [
942
- `Invalid context regex for prompt "${details.promptId}"`,
943
- `variable "${details.variable}"`,
944
- `field "${details.field}"`,
945
- `value ${JSON.stringify(details.raw)}: ${details.reason}`
946
- ].join(", ");
947
- }
948
- function compileContextRegex(regex, details) {
949
- if (regex.invalidLiteral) {
950
- throw new Error(
951
- `POK013: ${formatInvalidContextRegexMessage({
952
- ...details,
953
- raw: regex.raw,
954
- reason: "Malformed regex literal. Use /pattern/flags or { pattern, flags }."
955
- })}`
956
- );
957
- }
958
- try {
959
- return new RegExp(regex.pattern, regex.flags);
960
- } catch (error) {
961
- const reason = error instanceof Error ? error.message : String(error);
962
- throw new Error(`POK013: ${formatInvalidContextRegexMessage({ ...details, raw: regex.raw, reason })}`);
963
- }
964
- }
965
- function getRejectSecretsRegex() {
966
- return {
967
- pattern: REJECT_SECRETS_PATTERN,
968
- flags: REJECT_SECRETS_FLAGS,
969
- raw: JSON.stringify({ pattern: REJECT_SECRETS_PATTERN, flags: REJECT_SECRETS_FLAGS })
1207
+ function mergeRaw(base, local) {
1208
+ const merged = {
1209
+ ...base ?? {},
1210
+ ...local ?? {},
1211
+ openai: mergeRecordBlock2(base?.openai, local?.openai),
1212
+ "openai-responses": mergeRecordBlock2(base?.["openai-responses"], local?.["openai-responses"]),
1213
+ openai_responses: mergeRecordBlock2(base?.openai_responses, local?.openai_responses),
1214
+ anthropic: mergeRecordBlock2(base?.anthropic, local?.anthropic),
1215
+ gemini: mergeRecordBlock2(base?.gemini, local?.gemini),
1216
+ google: mergeRecordBlock2(base?.google, local?.google),
1217
+ openrouter: mergeRecordBlock2(base?.openrouter, local?.openrouter),
1218
+ llmasaservice: mergeRecordBlock2(base?.llmasaservice, local?.llmasaservice)
970
1219
  };
1220
+ removeEmptyProviderBlocks(merged);
1221
+ return Object.keys(merged).length > 0 ? merged : void 0;
971
1222
  }
972
- function isTrimEnabled(mode) {
973
- return mode === true || mode === "start" || mode === "end" || mode === "both";
1223
+ function mergeProviderOptions(base, local) {
1224
+ const merged = {
1225
+ ...base ?? {},
1226
+ ...local ?? {},
1227
+ anthropic: mergeRecordBlock2(base?.anthropic, local?.anthropic),
1228
+ gemini: mergeRecordBlock2(base?.gemini, local?.gemini),
1229
+ openrouter: mergeRecordBlock2(base?.openrouter, local?.openrouter),
1230
+ llmasaservice: mergeRecordBlock2(base?.llmasaservice, local?.llmasaservice)
1231
+ };
1232
+ removeEmptyProviderBlocks(merged);
1233
+ return Object.keys(merged).length > 0 ? merged : void 0;
974
1234
  }
975
- function normalizeTrimMode(mode) {
976
- if (mode === "start") {
977
- return "start";
1235
+ function mergeOverrideMap(base, local) {
1236
+ const merged = { ...base ?? {} };
1237
+ for (const [name, localOverride] of Object.entries(local ?? {})) {
1238
+ merged[name] = mergeOverrideConfig(merged[name], localOverride);
978
1239
  }
979
- return "end";
1240
+ return Object.keys(merged).length > 0 ? merged : void 0;
980
1241
  }
981
- function trimToMaxSize(value, maxSize, mode) {
982
- const measured = measureContextValueSize(value);
983
- if (measured <= maxSize) {
984
- return value;
985
- }
986
- const characters = Array.from(value);
987
- const normalizedMode = normalizeTrimMode(mode);
988
- if (normalizedMode === "start") {
989
- let collected2 = "";
990
- let size2 = 0;
991
- for (let i = characters.length - 1; i >= 0; i -= 1) {
992
- const next = characters[i];
993
- const charSize = measureContextValueSize(next);
994
- if (size2 + charSize > maxSize) {
995
- break;
996
- }
997
- collected2 = next + collected2;
998
- size2 += charSize;
1242
+ function mergeOverrideConfig(base, local) {
1243
+ const merged = {
1244
+ ...base ?? {},
1245
+ ...local
1246
+ };
1247
+ const reasoning = mergeRecordBlock2(base?.reasoning, local.reasoning);
1248
+ const sampling = mergeRecordBlock2(base?.sampling, local.sampling);
1249
+ const response = mergeRecordBlock2(base?.response, local.response);
1250
+ const cache = mergeCache(base?.cache, local.cache);
1251
+ const raw = mergeRaw(base?.raw, local.raw);
1252
+ const providerOptions = mergeProviderOptions(base?.provider_options, local.provider_options);
1253
+ if (reasoning !== void 0) merged.reasoning = reasoning;
1254
+ if (sampling !== void 0) merged.sampling = sampling;
1255
+ if (response !== void 0) merged.response = response;
1256
+ if (cache !== void 0) merged.cache = cache;
1257
+ if (raw !== void 0) merged.raw = raw;
1258
+ if (providerOptions !== void 0) merged.provider_options = providerOptions;
1259
+ return merged;
1260
+ }
1261
+ function removeEmptyProviderBlocks(value) {
1262
+ for (const key of Object.keys(value)) {
1263
+ const block = value[key];
1264
+ if (block === void 0) {
1265
+ delete value[key];
1266
+ continue;
999
1267
  }
1000
- return collected2;
1001
- }
1002
- let collected = "";
1003
- let size = 0;
1004
- for (const char of characters) {
1005
- const charSize = measureContextValueSize(char);
1006
- if (size + charSize > maxSize) {
1007
- break;
1268
+ if (block !== null && typeof block === "object" && !Array.isArray(block) && Object.keys(block).length === 0) {
1269
+ delete value[key];
1008
1270
  }
1009
- collected += char;
1010
- size += charSize;
1011
1271
  }
1012
- return collected;
1013
1272
  }
1014
- function sanitizeContextVariables(asset, variables = {}, options = {}) {
1015
- const { onContextOverflow } = options;
1016
- const sanitized = { ...variables };
1017
- for (const input of getContextInputs(asset)) {
1018
- const value = sanitized[input.name];
1019
- if (value === void 0) {
1020
- continue;
1021
- }
1022
- let candidate = value;
1023
- if (input.max_size !== void 0) {
1024
- const actualSize = measureContextValueSize(candidate);
1025
- if (actualSize > input.max_size && onContextOverflow) {
1026
- candidate = onContextOverflow({
1027
- promptId: asset.id,
1028
- variable: input.name,
1029
- value: candidate,
1030
- maxSize: input.max_size,
1031
- actualSize
1032
- });
1033
- }
1273
+
1274
+ // src/composition/resolve-includes.ts
1275
+ async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
1276
+ if (!asset.includes || asset.includes.length === 0) {
1277
+ return asset;
1278
+ }
1279
+ const baseDir = (0, import_node_path3.dirname)(basePath);
1280
+ const resolvedPath = (0, import_node_path3.resolve)(basePath);
1281
+ if (visited.has(resolvedPath)) {
1282
+ throw new Error(`Circular include detected: ${resolvedPath}`);
1283
+ }
1284
+ visited.add(resolvedPath);
1285
+ let mergedSystemInstructions = "";
1286
+ for (const includePath of asset.includes) {
1287
+ const fullPath = (0, import_node_path3.resolve)(baseDir, includePath);
1288
+ if (visited.has(fullPath)) {
1289
+ throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
1034
1290
  }
1035
- if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
1036
- candidate = trimToMaxSize(candidate, input.max_size, input.trim);
1291
+ const content = await (0, import_promises3.readFile)(fullPath, "utf-8");
1292
+ const { asset: includedAsset } = parsePrompt(content, fullPath);
1293
+ const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
1294
+ if (resolved.sections?.system_instructions) {
1295
+ mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
1037
1296
  }
1038
- sanitized[input.name] = candidate;
1039
- if (input.allow_regex) {
1040
- const candidate2 = sanitized[input.name];
1041
- const matcher = compileContextRegex(input.allow_regex, {
1042
- promptId: asset.id,
1043
- variable: input.name,
1044
- field: "allow_regex"
1045
- });
1046
- if (!matcher.test(candidate2)) {
1047
- if (input.allow_regex.returnMessage) {
1048
- return {
1049
- variables: sanitized,
1050
- shortCircuit: {
1051
- returnMessage: input.allow_regex.returnMessage,
1052
- code: "POK031",
1053
- variable: input.name,
1054
- field: "allow_regex"
1055
- }
1056
- };
1297
+ }
1298
+ const localSystem = asset.sections?.system_instructions ?? "";
1299
+ const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
1300
+ return {
1301
+ ...asset,
1302
+ sections: {
1303
+ ...asset.sections,
1304
+ system_instructions: combinedSystem
1305
+ },
1306
+ // Drop includes from the resolved asset — they've been inlined
1307
+ includes: void 0
1308
+ };
1309
+ }
1310
+
1311
+ // src/prompt-resolution.ts
1312
+ var DEFAULT_PROMPTS_DIR = "./prompts";
1313
+ var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
1314
+ function withPromptResolutionDefaults(config) {
1315
+ return {
1316
+ ...config,
1317
+ sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
1318
+ compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
1319
+ };
1320
+ }
1321
+ var sharedPromptCache = new PromptCache();
1322
+ async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
1323
+ const resolvedConfig = withPromptResolutionDefaults(config);
1324
+ const mode = resolvedConfig.mode ?? "auto";
1325
+ if (mode !== "source-only" && resolvedConfig.compiledDir) {
1326
+ const compiledFile = (0, import_node_path4.resolve)(resolvedConfig.compiledDir, promptPath + ".json");
1327
+ if ((0, import_node_fs2.existsSync)(compiledFile)) {
1328
+ if (mode === "auto") {
1329
+ const sourceFile = (0, import_node_path4.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
1330
+ if ((0, import_node_fs2.existsSync)(sourceFile)) {
1331
+ const compiledMtime = (0, import_node_fs2.statSync)(compiledFile).mtimeMs;
1332
+ const sourceMtime = (0, import_node_fs2.statSync)(sourceFile).mtimeMs;
1333
+ if (sourceMtime > compiledMtime) {
1334
+ console.warn(
1335
+ `[promptopskit] Warning: compiled artifact for "${promptPath}" is older than source .md file.
1336
+ Run "promptopskit compile" or switch to source-only mode.`
1337
+ );
1338
+ }
1057
1339
  }
1058
- throw new Error(
1059
- `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
1060
- );
1061
1340
  }
1341
+ const content = await (0, import_promises4.readFile)(compiledFile, "utf-8");
1342
+ return JSON.parse(content);
1062
1343
  }
1063
- if (input.deny_regex) {
1064
- const candidate2 = sanitized[input.name];
1065
- const matcher = compileContextRegex(input.deny_regex, {
1066
- promptId: asset.id,
1067
- variable: input.name,
1068
- field: "deny_regex"
1069
- });
1070
- if (matcher.test(candidate2)) {
1071
- if (input.deny_regex.returnMessage) {
1072
- return {
1073
- variables: sanitized,
1074
- shortCircuit: {
1075
- returnMessage: input.deny_regex.returnMessage,
1076
- code: "POK032",
1077
- variable: input.name,
1078
- field: "deny_regex"
1079
- }
1080
- };
1081
- }
1082
- throw new Error(
1083
- `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
1084
- );
1344
+ if (mode === "compiled-only") {
1345
+ throw new Error(
1346
+ `Compiled artifact not found: ${compiledFile}
1347
+ Run "promptopskit compile" to generate it.`
1348
+ );
1349
+ }
1350
+ }
1351
+ if (mode !== "compiled-only") {
1352
+ const sourceFile = (0, import_node_path4.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
1353
+ if (resolvedConfig.cache !== false) {
1354
+ const cached = promptCache.get(sourceFile);
1355
+ if (cached) {
1356
+ return cached;
1085
1357
  }
1086
1358
  }
1087
- if (input.non_empty && candidate.trim().length === 0) {
1088
- if (input.non_empty.returnMessage) {
1089
- return {
1090
- variables: sanitized,
1091
- shortCircuit: {
1092
- returnMessage: input.non_empty.returnMessage,
1093
- code: "POK033",
1094
- variable: input.name,
1095
- field: "non_empty"
1096
- }
1097
- };
1359
+ if (!(0, import_node_fs2.existsSync)(sourceFile)) {
1360
+ const paths = [sourceFile];
1361
+ if (resolvedConfig.compiledDir) {
1362
+ paths.unshift((0, import_node_path4.resolve)(resolvedConfig.compiledDir, promptPath + ".json"));
1098
1363
  }
1099
1364
  throw new Error(
1100
- `POK033: Context variable "${input.name}" failed non_empty validation for prompt "${asset.id}".`
1365
+ `Prompt not found: "${promptPath}"
1366
+ Searched:
1367
+ ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
1101
1368
  );
1102
1369
  }
1103
- if (input.reject_secrets) {
1104
- const matcher = compileContextRegex(getRejectSecretsRegex(), {
1105
- promptId: asset.id,
1106
- variable: input.name,
1107
- field: "reject_secrets"
1108
- });
1109
- if (matcher.test(candidate)) {
1110
- if (input.reject_secrets.returnMessage) {
1111
- return {
1112
- variables: sanitized,
1113
- shortCircuit: {
1114
- returnMessage: input.reject_secrets.returnMessage,
1115
- code: "POK034",
1116
- variable: input.name,
1117
- field: "reject_secrets"
1118
- }
1119
- };
1120
- }
1121
- throw new Error(
1122
- `POK034: Context variable "${input.name}" matched reject_secrets validation for prompt "${asset.id}".`
1123
- );
1124
- }
1370
+ const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
1371
+ if (resolvedConfig.cache !== false) {
1372
+ promptCache.set(sourceFile, asset);
1125
1373
  }
1374
+ return asset;
1126
1375
  }
1127
- return { variables: sanitized };
1376
+ throw new Error(`Prompt not found: "${promptPath}"`);
1128
1377
  }
1129
- function measureContextValueSize(value) {
1130
- return textEncoder.encode(value).length;
1378
+ async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
1379
+ const resolvedConfig = withPromptResolutionDefaults(config);
1380
+ let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
1381
+ const sourceFile = (0, import_node_path4.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
1382
+ if (asset.includes && asset.includes.length > 0 && (0, import_node_fs2.existsSync)(sourceFile)) {
1383
+ asset = await resolveIncludes(asset, sourceFile);
1384
+ }
1385
+ asset = applyOverrides(asset, {
1386
+ environment: options.environment,
1387
+ tier: options.tier,
1388
+ runtime: options.runtime
1389
+ });
1390
+ return asset;
1391
+ }
1392
+ function resolveInlinePromptSource(source, options = {}) {
1393
+ const { asset } = parsePrompt(source);
1394
+ return applyOverrides(asset, {
1395
+ environment: options.environment,
1396
+ tier: options.tier,
1397
+ runtime: options.runtime
1398
+ });
1131
1399
  }
1132
1400
 
1133
1401
  // src/history.ts