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