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
package/dist/index.cjs CHANGED
@@ -72,7 +72,7 @@ __export(src_exports, {
72
72
  withUsageTapCall: () => withUsageTapCall
73
73
  });
74
74
  module.exports = __toCommonJS(src_exports);
75
- var import_node_path4 = require("path");
75
+ var import_node_path5 = require("path");
76
76
 
77
77
  // src/renderer/interpolate.ts
78
78
  var VARIABLE_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
@@ -80,12 +80,13 @@ var ESCAPED_OPEN = /\\\{\\\{/g;
80
80
  var ESCAPE_PLACEHOLDER = "\0ESCAPED_OPEN\0";
81
81
  function interpolate(template, variables, options = {}) {
82
82
  const { strict = false } = options;
83
+ const optionalVariables = new Set(options.optionalVariables ?? []);
83
84
  let result = template.replace(ESCAPED_OPEN, ESCAPE_PLACEHOLDER);
84
85
  result = result.replace(VARIABLE_RE, (match, name) => {
85
86
  if (name in variables) {
86
87
  return variables[name];
87
88
  }
88
- if (strict) {
89
+ if (strict && !optionalVariables.has(name)) {
89
90
  throw new Error(`Missing required variable: "${name}"`);
90
91
  }
91
92
  return match;
@@ -103,159 +104,463 @@ function extractVariables(template) {
103
104
  return [...vars];
104
105
  }
105
106
 
106
- // src/renderer/renderer.ts
107
- function renderSections(asset, options = {}) {
108
- const { variables = {}, strict = false } = options;
109
- const result = {};
110
- if (asset.sections.system_instructions) {
111
- result.system_instructions = interpolate(
112
- asset.sections.system_instructions,
113
- variables,
114
- { strict }
115
- );
116
- }
117
- if (asset.sections.prompt_template) {
118
- result.prompt_template = interpolate(
119
- asset.sections.prompt_template,
120
- variables,
121
- { strict }
122
- );
123
- }
124
- return result;
107
+ // src/context.ts
108
+ var textEncoder = new TextEncoder();
109
+ var REJECT_SECRETS_PATTERN = "(secret|api[_-]?key|password)";
110
+ var REJECT_SECRETS_FLAGS = "i";
111
+ function getContextInputs(asset) {
112
+ return (asset.context?.inputs ?? []).map(normalizeContextInput);
125
113
  }
126
-
127
- // src/overrides/apply-overrides.ts
128
- function applyOverrides(asset, options = {}) {
129
- let result = { ...asset };
130
- if (options.environment && result.environments?.[options.environment]) {
131
- result = mergeOverride(result, result.environments[options.environment]);
114
+ function normalizeContextInput(input) {
115
+ if (typeof input === "string") {
116
+ return { name: input };
132
117
  }
133
- if (options.tier && result.tiers?.[options.tier]) {
134
- result = mergeOverride(result, result.tiers[options.tier]);
118
+ return {
119
+ name: input.name,
120
+ optional: input.optional,
121
+ warnings: input.warnings,
122
+ max_size: input.max_size,
123
+ trim: input.trim,
124
+ allow_regex: normalizeContextRegex(input.allow_regex),
125
+ deny_regex: normalizeContextRegex(input.deny_regex),
126
+ non_empty: normalizeBuiltInValidator(input.non_empty),
127
+ reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
128
+ };
129
+ }
130
+ function areContextInputWarningsEnabled(input) {
131
+ return input.warnings !== false;
132
+ }
133
+ function normalizeContextRegex(value) {
134
+ if (value === void 0) {
135
+ return void 0;
135
136
  }
136
- if (options.runtime) {
137
- result = mergeOverride(result, options.runtime);
137
+ if (typeof value === "string") {
138
+ const literal = parseRegexLiteral(value);
139
+ if (value.startsWith("/") && !literal) {
140
+ return {
141
+ pattern: value,
142
+ flags: "",
143
+ raw: value,
144
+ invalidLiteral: true
145
+ };
146
+ }
147
+ return {
148
+ pattern: literal?.pattern ?? value,
149
+ flags: literal?.flags ?? "",
150
+ raw: value
151
+ };
138
152
  }
139
- return result;
153
+ return {
154
+ pattern: value.pattern,
155
+ flags: value.flags ?? "",
156
+ raw: JSON.stringify(value),
157
+ returnMessage: value.return_message
158
+ };
140
159
  }
141
- function mergeOverride(base, override) {
142
- const result = { ...base };
143
- if (override.model !== void 0) result.model = override.model;
144
- if (override.fallback_models !== void 0) result.fallback_models = override.fallback_models;
145
- if (override.tools !== void 0) result.tools = override.tools;
146
- if (override.reasoning !== void 0) {
147
- result.reasoning = { ...result.reasoning, ...override.reasoning };
160
+ function normalizeBuiltInValidator(value) {
161
+ if (value === void 0 || value === false) {
162
+ return void 0;
148
163
  }
149
- if (override.sampling !== void 0) {
150
- result.sampling = { ...result.sampling, ...override.sampling };
164
+ if (value === true) {
165
+ return {};
151
166
  }
152
- if (override.response !== void 0) {
153
- result.response = { ...result.response, ...override.response };
167
+ return {
168
+ returnMessage: value.return_message
169
+ };
170
+ }
171
+ function parseRegexLiteral(value) {
172
+ if (!value.startsWith("/")) {
173
+ return void 0;
154
174
  }
155
- if (override.cache !== void 0) {
156
- result.cache = {
157
- ...result.cache,
158
- ...override.cache,
159
- openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
160
- anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
161
- gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
162
- google: mergeRecordBlock(result.cache?.google, override.cache.google)
175
+ for (let index = value.length - 1; index > 0; index -= 1) {
176
+ if (value[index] !== "/") {
177
+ continue;
178
+ }
179
+ let backslashCount = 0;
180
+ for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
181
+ backslashCount += 1;
182
+ }
183
+ if (backslashCount % 2 === 1) {
184
+ continue;
185
+ }
186
+ return {
187
+ pattern: value.slice(1, index),
188
+ flags: value.slice(index + 1)
163
189
  };
164
190
  }
165
- if (override.raw !== void 0) {
166
- result.raw = {
167
- ...result.raw,
168
- ...override.raw,
169
- openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
170
- "openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
171
- openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
172
- anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
173
- gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
174
- google: mergeRecordBlock(result.raw?.google, override.raw.google),
175
- openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter),
176
- llmasaservice: mergeRecordBlock(result.raw?.llmasaservice, override.raw.llmasaservice)
177
- };
191
+ return void 0;
192
+ }
193
+ function formatInvalidContextRegexMessage(details) {
194
+ return [
195
+ `Invalid context regex for prompt "${details.promptId}"`,
196
+ `variable "${details.variable}"`,
197
+ `field "${details.field}"`,
198
+ `value ${JSON.stringify(details.raw)}: ${details.reason}`
199
+ ].join(", ");
200
+ }
201
+ function compileContextRegex(regex, details) {
202
+ if (regex.invalidLiteral) {
203
+ throw new Error(
204
+ `POK013: ${formatInvalidContextRegexMessage({
205
+ ...details,
206
+ raw: regex.raw,
207
+ reason: "Malformed regex literal. Use /pattern/flags or { pattern, flags }."
208
+ })}`
209
+ );
178
210
  }
179
- if (override.provider_options !== void 0) {
180
- result.provider_options = {
181
- ...result.provider_options,
182
- ...override.provider_options,
183
- anthropic: {
184
- ...result.provider_options?.anthropic,
185
- ...override.provider_options.anthropic
186
- },
187
- gemini: {
188
- ...result.provider_options?.gemini,
189
- ...override.provider_options.gemini
190
- },
191
- openrouter: {
192
- ...result.provider_options?.openrouter,
193
- ...override.provider_options.openrouter
194
- },
195
- llmasaservice: {
196
- ...result.provider_options?.llmasaservice,
197
- ...override.provider_options.llmasaservice
198
- }
199
- };
211
+ try {
212
+ return new RegExp(regex.pattern, regex.flags);
213
+ } catch (error) {
214
+ const reason = error instanceof Error ? error.message : String(error);
215
+ throw new Error(`POK013: ${formatInvalidContextRegexMessage({ ...details, raw: regex.raw, reason })}`);
200
216
  }
201
- return result;
202
217
  }
203
- function mergeRecordBlock(base, override) {
204
- if (override === void 0) return base;
205
- return { ...base, ...override };
218
+ function getRejectSecretsRegex() {
219
+ return {
220
+ pattern: REJECT_SECRETS_PATTERN,
221
+ flags: REJECT_SECRETS_FLAGS,
222
+ raw: JSON.stringify({ pattern: REJECT_SECRETS_PATTERN, flags: REJECT_SECRETS_FLAGS })
223
+ };
206
224
  }
207
-
208
- // src/providers/resolve-asset.ts
209
- function resolveAssetForProvider(asset, runtime = {}) {
210
- if (!runtime.environment && !runtime.tier && !runtime.runtime) {
211
- return asset;
225
+ function isTrimEnabled(mode) {
226
+ return mode === true || mode === "start" || mode === "end" || mode === "both";
227
+ }
228
+ function normalizeTrimMode(mode) {
229
+ if (mode === "start") {
230
+ return "start";
212
231
  }
213
- return applyOverrides(asset, {
214
- environment: runtime.environment,
215
- tier: runtime.tier,
216
- runtime: runtime.runtime
217
- });
232
+ return "end";
218
233
  }
219
-
220
- // src/prompt-resolution.ts
221
- var import_promises3 = require("fs/promises");
222
- var import_node_fs2 = require("fs");
223
- var import_node_path3 = require("path");
224
-
225
- // src/cache.ts
226
- var import_node_fs = require("fs");
227
- var PromptCache = class {
228
- cache = /* @__PURE__ */ new Map();
229
- maxSize;
230
- constructor(maxSize = 100) {
231
- this.maxSize = maxSize;
234
+ function trimToMaxSize(value, maxSize, mode) {
235
+ const measured = measureContextValueSize(value);
236
+ if (measured <= maxSize) {
237
+ return value;
232
238
  }
233
- get(filePath) {
234
- const entry = this.cache.get(filePath);
235
- if (!entry) return void 0;
236
- try {
237
- const stat = (0, import_node_fs.statSync)(filePath);
238
- if (stat.mtimeMs !== entry.mtime) {
239
- this.cache.delete(filePath);
240
- return void 0;
239
+ const characters = Array.from(value);
240
+ const normalizedMode = normalizeTrimMode(mode);
241
+ if (normalizedMode === "start") {
242
+ let collected2 = "";
243
+ let size2 = 0;
244
+ for (let i = characters.length - 1; i >= 0; i -= 1) {
245
+ const next = characters[i];
246
+ const charSize = measureContextValueSize(next);
247
+ if (size2 + charSize > maxSize) {
248
+ break;
241
249
  }
242
- } catch {
243
- this.cache.delete(filePath);
244
- return void 0;
250
+ collected2 = next + collected2;
251
+ size2 += charSize;
245
252
  }
246
- this.cache.delete(filePath);
247
- this.cache.set(filePath, entry);
248
- return entry.value;
253
+ return collected2;
249
254
  }
250
- set(filePath, value) {
251
- try {
252
- const stat = (0, import_node_fs.statSync)(filePath);
253
- if (this.cache.has(filePath)) {
254
- this.cache.delete(filePath);
255
- } else if (this.cache.size >= this.maxSize) {
256
- const oldest = this.cache.keys().next().value;
257
- if (oldest) this.cache.delete(oldest);
258
- }
255
+ let collected = "";
256
+ let size = 0;
257
+ for (const char of characters) {
258
+ const charSize = measureContextValueSize(char);
259
+ if (size + charSize > maxSize) {
260
+ break;
261
+ }
262
+ collected += char;
263
+ size += charSize;
264
+ }
265
+ return collected;
266
+ }
267
+ function sanitizeContextVariables(asset, variables = {}, options = {}) {
268
+ const { onContextOverflow } = options;
269
+ const sanitized = { ...variables };
270
+ for (const input of getContextInputs(asset)) {
271
+ const value = sanitized[input.name];
272
+ if (value === void 0) {
273
+ continue;
274
+ }
275
+ let candidate = value;
276
+ if (input.max_size !== void 0) {
277
+ const actualSize = measureContextValueSize(candidate);
278
+ if (actualSize > input.max_size && onContextOverflow) {
279
+ candidate = onContextOverflow({
280
+ promptId: asset.id,
281
+ variable: input.name,
282
+ value: candidate,
283
+ maxSize: input.max_size,
284
+ actualSize
285
+ });
286
+ }
287
+ }
288
+ if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
289
+ candidate = trimToMaxSize(candidate, input.max_size, input.trim);
290
+ }
291
+ sanitized[input.name] = candidate;
292
+ if (input.allow_regex) {
293
+ const candidate2 = sanitized[input.name];
294
+ const matcher = compileContextRegex(input.allow_regex, {
295
+ promptId: asset.id,
296
+ variable: input.name,
297
+ field: "allow_regex"
298
+ });
299
+ if (!matcher.test(candidate2)) {
300
+ if (input.allow_regex.returnMessage) {
301
+ return {
302
+ variables: sanitized,
303
+ shortCircuit: {
304
+ returnMessage: input.allow_regex.returnMessage,
305
+ code: "POK031",
306
+ variable: input.name,
307
+ field: "allow_regex"
308
+ }
309
+ };
310
+ }
311
+ throw new Error(
312
+ `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
313
+ );
314
+ }
315
+ }
316
+ if (input.deny_regex) {
317
+ const candidate2 = sanitized[input.name];
318
+ const matcher = compileContextRegex(input.deny_regex, {
319
+ promptId: asset.id,
320
+ variable: input.name,
321
+ field: "deny_regex"
322
+ });
323
+ if (matcher.test(candidate2)) {
324
+ if (input.deny_regex.returnMessage) {
325
+ return {
326
+ variables: sanitized,
327
+ shortCircuit: {
328
+ returnMessage: input.deny_regex.returnMessage,
329
+ code: "POK032",
330
+ variable: input.name,
331
+ field: "deny_regex"
332
+ }
333
+ };
334
+ }
335
+ throw new Error(
336
+ `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
337
+ );
338
+ }
339
+ }
340
+ if (input.non_empty && candidate.trim().length === 0) {
341
+ if (input.non_empty.returnMessage) {
342
+ return {
343
+ variables: sanitized,
344
+ shortCircuit: {
345
+ returnMessage: input.non_empty.returnMessage,
346
+ code: "POK033",
347
+ variable: input.name,
348
+ field: "non_empty"
349
+ }
350
+ };
351
+ }
352
+ throw new Error(
353
+ `POK033: Context variable "${input.name}" failed non_empty validation for prompt "${asset.id}".`
354
+ );
355
+ }
356
+ if (input.reject_secrets) {
357
+ const matcher = compileContextRegex(getRejectSecretsRegex(), {
358
+ promptId: asset.id,
359
+ variable: input.name,
360
+ field: "reject_secrets"
361
+ });
362
+ if (matcher.test(candidate)) {
363
+ if (input.reject_secrets.returnMessage) {
364
+ return {
365
+ variables: sanitized,
366
+ shortCircuit: {
367
+ returnMessage: input.reject_secrets.returnMessage,
368
+ code: "POK034",
369
+ variable: input.name,
370
+ field: "reject_secrets"
371
+ }
372
+ };
373
+ }
374
+ throw new Error(
375
+ `POK034: Context variable "${input.name}" matched reject_secrets validation for prompt "${asset.id}".`
376
+ );
377
+ }
378
+ }
379
+ }
380
+ return { variables: sanitized };
381
+ }
382
+ function measureContextValueSize(value) {
383
+ return textEncoder.encode(value).length;
384
+ }
385
+ function collectContextSizeWarnings(asset, variables = {}) {
386
+ const warnings = [];
387
+ for (const input of getContextInputs(asset)) {
388
+ if (!areContextInputWarningsEnabled(input)) {
389
+ continue;
390
+ }
391
+ if (input.max_size === void 0) {
392
+ continue;
393
+ }
394
+ const value = variables[input.name];
395
+ if (value === void 0) {
396
+ continue;
397
+ }
398
+ const actualSize = measureContextValueSize(value);
399
+ if (actualSize > input.max_size) {
400
+ warnings.push({
401
+ variable: input.name,
402
+ maxSize: input.max_size,
403
+ actualSize
404
+ });
405
+ }
406
+ }
407
+ return warnings;
408
+ }
409
+
410
+ // src/renderer/renderer.ts
411
+ function renderSections(asset, options = {}) {
412
+ const { variables = {}, strict = false } = options;
413
+ const optionalVariables = getContextInputs(asset).filter((input) => input.optional === true).map((input) => input.name);
414
+ const result = {};
415
+ if (asset.sections.system_instructions) {
416
+ result.system_instructions = interpolate(
417
+ asset.sections.system_instructions,
418
+ variables,
419
+ { strict, optionalVariables }
420
+ );
421
+ }
422
+ if (asset.sections.prompt_template) {
423
+ result.prompt_template = interpolate(
424
+ asset.sections.prompt_template,
425
+ variables,
426
+ { strict, optionalVariables }
427
+ );
428
+ }
429
+ return result;
430
+ }
431
+
432
+ // src/overrides/apply-overrides.ts
433
+ function applyOverrides(asset, options = {}) {
434
+ let result = { ...asset };
435
+ if (options.environment && result.environments?.[options.environment]) {
436
+ result = mergeOverride(result, result.environments[options.environment]);
437
+ }
438
+ if (options.tier && result.tiers?.[options.tier]) {
439
+ result = mergeOverride(result, result.tiers[options.tier]);
440
+ }
441
+ if (options.runtime) {
442
+ result = mergeOverride(result, options.runtime);
443
+ }
444
+ return result;
445
+ }
446
+ function mergeOverride(base, override) {
447
+ const result = { ...base };
448
+ if (override.model !== void 0) result.model = override.model;
449
+ if (override.fallback_models !== void 0) result.fallback_models = override.fallback_models;
450
+ if (override.tools !== void 0) result.tools = override.tools;
451
+ if (override.reasoning !== void 0) {
452
+ result.reasoning = { ...result.reasoning, ...override.reasoning };
453
+ }
454
+ if (override.sampling !== void 0) {
455
+ result.sampling = { ...result.sampling, ...override.sampling };
456
+ }
457
+ if (override.response !== void 0) {
458
+ result.response = { ...result.response, ...override.response };
459
+ }
460
+ if (override.cache !== void 0) {
461
+ result.cache = {
462
+ ...result.cache,
463
+ ...override.cache,
464
+ openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
465
+ anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
466
+ gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
467
+ google: mergeRecordBlock(result.cache?.google, override.cache.google)
468
+ };
469
+ }
470
+ if (override.raw !== void 0) {
471
+ result.raw = {
472
+ ...result.raw,
473
+ ...override.raw,
474
+ openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
475
+ "openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
476
+ openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
477
+ anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
478
+ gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
479
+ google: mergeRecordBlock(result.raw?.google, override.raw.google),
480
+ openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter),
481
+ llmasaservice: mergeRecordBlock(result.raw?.llmasaservice, override.raw.llmasaservice)
482
+ };
483
+ }
484
+ if (override.provider_options !== void 0) {
485
+ result.provider_options = {
486
+ ...result.provider_options,
487
+ ...override.provider_options,
488
+ anthropic: {
489
+ ...result.provider_options?.anthropic,
490
+ ...override.provider_options.anthropic
491
+ },
492
+ gemini: {
493
+ ...result.provider_options?.gemini,
494
+ ...override.provider_options.gemini
495
+ },
496
+ openrouter: {
497
+ ...result.provider_options?.openrouter,
498
+ ...override.provider_options.openrouter
499
+ },
500
+ llmasaservice: {
501
+ ...result.provider_options?.llmasaservice,
502
+ ...override.provider_options.llmasaservice
503
+ }
504
+ };
505
+ }
506
+ return result;
507
+ }
508
+ function mergeRecordBlock(base, override) {
509
+ if (override === void 0) return base;
510
+ return { ...base, ...override };
511
+ }
512
+
513
+ // src/providers/resolve-asset.ts
514
+ function resolveAssetForProvider(asset, runtime = {}) {
515
+ if (!runtime.environment && !runtime.tier && !runtime.runtime) {
516
+ return asset;
517
+ }
518
+ return applyOverrides(asset, {
519
+ environment: runtime.environment,
520
+ tier: runtime.tier,
521
+ runtime: runtime.runtime
522
+ });
523
+ }
524
+
525
+ // src/prompt-resolution.ts
526
+ var import_promises4 = require("fs/promises");
527
+ var import_node_fs2 = require("fs");
528
+ var import_node_path4 = require("path");
529
+
530
+ // src/cache.ts
531
+ var import_node_fs = require("fs");
532
+ var PromptCache = class {
533
+ cache = /* @__PURE__ */ new Map();
534
+ maxSize;
535
+ constructor(maxSize = 100) {
536
+ this.maxSize = maxSize;
537
+ }
538
+ get(filePath) {
539
+ const entry = this.cache.get(filePath);
540
+ if (!entry) return void 0;
541
+ try {
542
+ const stat = (0, import_node_fs.statSync)(filePath);
543
+ if (stat.mtimeMs !== entry.mtime) {
544
+ this.cache.delete(filePath);
545
+ return void 0;
546
+ }
547
+ } catch {
548
+ this.cache.delete(filePath);
549
+ return void 0;
550
+ }
551
+ this.cache.delete(filePath);
552
+ this.cache.set(filePath, entry);
553
+ return entry.value;
554
+ }
555
+ set(filePath, value) {
556
+ try {
557
+ const stat = (0, import_node_fs.statSync)(filePath);
558
+ if (this.cache.has(filePath)) {
559
+ this.cache.delete(filePath);
560
+ } else if (this.cache.size >= this.maxSize) {
561
+ const oldest = this.cache.keys().next().value;
562
+ if (oldest) this.cache.delete(oldest);
563
+ }
259
564
  this.cache.set(filePath, { value, mtime: stat.mtimeMs });
260
565
  } catch {
261
566
  }
@@ -269,8 +574,8 @@ var PromptCache = class {
269
574
  };
270
575
 
271
576
  // src/composition/resolve-includes.ts
272
- var import_promises2 = require("fs/promises");
273
- var import_node_path2 = require("path");
577
+ var import_promises3 = require("fs/promises");
578
+ var import_node_path3 = require("path");
274
579
 
275
580
  // src/parser/parser.ts
276
581
  var import_gray_matter = __toESM(require("gray-matter"), 1);
@@ -306,10 +611,26 @@ var ResponseSchema = import_zod.z.object({
306
611
  format: import_zod.z.enum(["text", "json", "markdown"]).optional(),
307
612
  stream: import_zod.z.boolean().optional(),
308
613
  schema: import_zod.z.record(import_zod.z.unknown()).optional(),
614
+ schema_ref: import_zod.z.string().min(1).optional(),
615
+ schema_source: import_zod.z.object({
616
+ mode: import_zod.z.enum(["inline", "schema_ref_json", "schema_ref_zod_module"]).optional(),
617
+ ref: import_zod.z.string().optional(),
618
+ resolved_path: import_zod.z.string().optional(),
619
+ hash: import_zod.z.string().optional()
620
+ }).optional(),
309
621
  schema_name: import_zod.z.string().optional(),
310
622
  schema_description: import_zod.z.string().optional(),
311
623
  schema_strict: import_zod.z.boolean().optional()
312
624
  });
625
+ var ResponseSchemaWithValidation = ResponseSchema.superRefine((value, ctx) => {
626
+ if (value.schema !== void 0 && value.schema_ref !== void 0) {
627
+ ctx.addIssue({
628
+ code: import_zod.z.ZodIssueCode.custom,
629
+ message: "response.schema and response.schema_ref are mutually exclusive",
630
+ path: ["schema_ref"]
631
+ });
632
+ }
633
+ });
313
634
  var AnthropicProviderOptionsSchema = import_zod.z.object({
314
635
  top_k: import_zod.z.number().int().min(0).optional(),
315
636
  tool_choice: import_zod.z.record(import_zod.z.unknown()).optional(),
@@ -401,6 +722,8 @@ var ContextBuiltInValidatorSchema = import_zod.z.union([
401
722
  ]);
402
723
  var ContextInputDefinitionObjectSchema = import_zod.z.object({
403
724
  name: import_zod.z.string(),
725
+ optional: import_zod.z.boolean().optional(),
726
+ warnings: import_zod.z.boolean().optional(),
404
727
  max_size: import_zod.z.number().int().positive().optional(),
405
728
  trim: import_zod.z.union([import_zod.z.boolean(), import_zod.z.enum(["start", "end", "both"])]).optional(),
406
729
  allow_regex: ContextRegexSchema.optional(),
@@ -430,7 +753,7 @@ var PromptAssetOverridesSchema = import_zod.z.object({
430
753
  fallback_models: import_zod.z.array(import_zod.z.string()).optional(),
431
754
  reasoning: ReasoningSchema.optional(),
432
755
  sampling: SamplingSchema.optional(),
433
- response: ResponseSchema.optional(),
756
+ response: ResponseSchemaWithValidation.optional(),
434
757
  cache: CacheSchema.optional(),
435
758
  raw: RawProviderBodySchema.optional(),
436
759
  tools: import_zod.z.array(ToolRefSchema).optional(),
@@ -448,7 +771,19 @@ var SectionsSchema = import_zod.z.object({
448
771
  var PromptDefaultsSchema = import_zod.z.object({
449
772
  provider: import_zod.z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "llmasaservice", "any"]).optional(),
450
773
  model: import_zod.z.string().optional(),
774
+ fallback_models: import_zod.z.array(import_zod.z.string()).optional(),
775
+ reasoning: ReasoningSchema.optional(),
776
+ sampling: SamplingSchema.optional(),
777
+ response: ResponseSchema.optional(),
451
778
  cache: CacheSchema.optional(),
779
+ raw: RawProviderBodySchema.optional(),
780
+ tools: import_zod.z.array(ToolRefSchema).optional(),
781
+ provider_options: ProviderOptionsSchema.optional(),
782
+ mcp: MCPSchema.optional(),
783
+ context: ContextSchema.optional(),
784
+ includes: import_zod.z.array(import_zod.z.string()).optional(),
785
+ environments: import_zod.z.record(PromptAssetOverridesSchema).optional(),
786
+ tiers: import_zod.z.record(PromptAssetOverridesSchema).optional(),
452
787
  metadata: MetadataSchema.optional(),
453
788
  sections: import_zod.z.object({
454
789
  system_instructions: import_zod.z.string().optional()
@@ -463,7 +798,7 @@ var PromptAssetSchema = import_zod.z.object({
463
798
  fallback_models: import_zod.z.array(import_zod.z.string()).optional(),
464
799
  reasoning: ReasoningSchema.optional(),
465
800
  sampling: SamplingSchema.optional(),
466
- response: ResponseSchema.optional(),
801
+ response: ResponseSchemaWithValidation.optional(),
467
802
  cache: CacheSchema.optional(),
468
803
  raw: RawProviderBodySchema.optional(),
469
804
  tools: import_zod.z.array(ToolRefSchema).optional(),
@@ -640,567 +975,503 @@ function parsePrompt(content, filePath) {
640
975
  const { data: frontMatter, content: body } = (0, import_gray_matter.default)(content);
641
976
  const sections = extractSections(body);
642
977
  const raw = {
643
- ...frontMatter,
644
- sections,
645
- source: filePath ? { file_path: filePath } : void 0
646
- };
647
- const asset = PromptAssetSchema.parse(raw);
648
- return {
649
- asset,
650
- raw: {
651
- frontMatter,
652
- body
653
- }
654
- };
655
- }
656
-
657
- // src/parser/loader.ts
658
- var import_promises = require("fs/promises");
659
- var import_node_path = require("path");
660
- var import_gray_matter2 = __toESM(require("gray-matter"), 1);
661
- var DEFAULTS_FILE_NAME = "defaults.md";
662
- async function loadPromptFile(filePath, options = {}) {
663
- const content = await (0, import_promises.readFile)(filePath, "utf-8");
664
- const parsed = parsePrompt(content, filePath);
665
- const root = options.defaultsRoot ?? (0, import_node_path.dirname)(filePath);
666
- const defaults = await loadDefaultsForPath(filePath, root);
667
- const asset = applyDefaults(parsed.asset, defaults);
668
- return {
669
- ...parsed,
670
- asset
671
- };
672
- }
673
- async function loadDefaultsForPath(filePath, defaultsRoot) {
674
- const directories = getDirectoriesToCheck(filePath, defaultsRoot);
675
- let merged = {};
676
- for (const dir of directories) {
677
- const defaultsPath = (0, import_node_path.join)(dir, DEFAULTS_FILE_NAME);
678
- try {
679
- const defaultsContent = await (0, import_promises.readFile)(defaultsPath, "utf-8");
680
- const defaults = parseDefaults(defaultsContent);
681
- merged = mergeDefaults(merged, defaults);
682
- } catch (error) {
683
- if (error.code !== "ENOENT") {
684
- throw error;
685
- }
686
- }
687
- }
688
- return merged;
689
- }
690
- function getDirectoriesToCheck(filePath, defaultsRoot) {
691
- const dirs = [];
692
- let current = (0, import_node_path.resolve)((0, import_node_path.dirname)(filePath));
693
- const boundary = defaultsRoot ? (0, import_node_path.resolve)(defaultsRoot) : void 0;
694
- while (true) {
695
- dirs.unshift(current);
696
- if (boundary && current === boundary || current === (0, import_node_path.dirname)(current)) {
697
- break;
698
- }
699
- current = (0, import_node_path.dirname)(current);
700
- }
701
- return dirs;
702
- }
703
- function parseDefaults(content) {
704
- const { data: frontMatter, content: body } = (0, import_gray_matter2.default)(content);
705
- const sections = extractSections(body);
706
- return PromptDefaultsSchema.parse({
707
- ...frontMatter,
708
- sections: {
709
- system_instructions: sections.system_instructions
710
- }
711
- });
712
- }
713
- function mergeDefaults(base, local) {
714
- return {
715
- provider: local.provider ?? base.provider,
716
- model: local.model ?? base.model,
717
- cache: mergeCache(base.cache, local.cache),
718
- metadata: {
719
- ...base.metadata ?? {},
720
- ...local.metadata ?? {}
721
- },
722
- sections: {
723
- ...base.sections ?? {},
724
- ...local.sections ?? {}
725
- }
726
- };
727
- }
728
- function applyDefaults(asset, defaults) {
729
- const cache = mergeCache(defaults.cache, asset.cache);
730
- const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
731
- const hasDefaultSystem = !!defaults.sections?.system_instructions;
732
- const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0;
733
- const hasDefaultCache = cache !== void 0;
734
- if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultCache) {
735
- return asset;
736
- }
737
- const mergedMetadata = {
738
- ...defaults.metadata ?? {},
739
- ...asset.metadata ?? {}
740
- };
741
- const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
742
- const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;
743
- const sections = asset.sections ? { ...asset.sections, system_instructions: systemInstructions } : systemInstructions ? { system_instructions: systemInstructions } : void 0;
744
- return {
745
- ...asset,
746
- provider: asset.provider ?? defaults.provider,
747
- model: asset.model ?? defaults.model,
748
- cache,
749
- metadata,
750
- sections
751
- };
752
- }
753
- function mergeCache(base, local) {
754
- const merged = {
755
- ...base ?? {},
756
- ...local ?? {},
757
- openai: {
758
- ...base?.openai ?? {},
759
- ...local?.openai ?? {}
760
- },
761
- anthropic: {
762
- ...base?.anthropic ?? {},
763
- ...local?.anthropic ?? {}
764
- },
765
- gemini: {
766
- ...base?.gemini ?? {},
767
- ...local?.gemini ?? {}
768
- },
769
- google: {
770
- ...base?.google ?? {},
771
- ...local?.google ?? {}
772
- }
773
- };
774
- if (merged.openai && Object.keys(merged.openai).length === 0) delete merged.openai;
775
- if (merged.anthropic && Object.keys(merged.anthropic).length === 0) delete merged.anthropic;
776
- if (merged.gemini && Object.keys(merged.gemini).length === 0) delete merged.gemini;
777
- if (merged.google && Object.keys(merged.google).length === 0) delete merged.google;
778
- return Object.keys(merged).length > 0 ? merged : void 0;
779
- }
780
-
781
- // src/composition/resolve-includes.ts
782
- async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
783
- if (!asset.includes || asset.includes.length === 0) {
784
- return asset;
785
- }
786
- const baseDir = (0, import_node_path2.dirname)(basePath);
787
- const resolvedPath = (0, import_node_path2.resolve)(basePath);
788
- if (visited.has(resolvedPath)) {
789
- throw new Error(`Circular include detected: ${resolvedPath}`);
790
- }
791
- visited.add(resolvedPath);
792
- let mergedSystemInstructions = "";
793
- for (const includePath of asset.includes) {
794
- const fullPath = (0, import_node_path2.resolve)(baseDir, includePath);
795
- if (visited.has(fullPath)) {
796
- throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
797
- }
798
- const content = await (0, import_promises2.readFile)(fullPath, "utf-8");
799
- const { asset: includedAsset } = parsePrompt(content, fullPath);
800
- const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
801
- if (resolved.sections?.system_instructions) {
802
- mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
803
- }
804
- }
805
- const localSystem = asset.sections?.system_instructions ?? "";
806
- const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
807
- return {
808
- ...asset,
809
- sections: {
810
- ...asset.sections,
811
- system_instructions: combinedSystem
812
- },
813
- // Drop includes from the resolved asset — they've been inlined
814
- includes: void 0
815
- };
816
- }
817
-
818
- // src/prompt-resolution.ts
819
- var DEFAULT_PROMPTS_DIR = "./prompts";
820
- var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
821
- function withPromptResolutionDefaults(config) {
822
- return {
823
- ...config,
824
- sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
825
- compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
826
- };
827
- }
828
- var sharedPromptCache = new PromptCache();
829
- async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
830
- const resolvedConfig = withPromptResolutionDefaults(config);
831
- const mode = resolvedConfig.mode ?? "auto";
832
- if (mode !== "source-only" && resolvedConfig.compiledDir) {
833
- const compiledFile = (0, import_node_path3.resolve)(resolvedConfig.compiledDir, promptPath + ".json");
834
- if ((0, import_node_fs2.existsSync)(compiledFile)) {
835
- if (mode === "auto") {
836
- const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
837
- if ((0, import_node_fs2.existsSync)(sourceFile)) {
838
- const compiledMtime = (0, import_node_fs2.statSync)(compiledFile).mtimeMs;
839
- const sourceMtime = (0, import_node_fs2.statSync)(sourceFile).mtimeMs;
840
- if (sourceMtime > compiledMtime) {
841
- console.warn(
842
- `[promptopskit] Warning: compiled artifact for "${promptPath}" is older than source .md file.
843
- Run "promptopskit compile" or switch to source-only mode.`
844
- );
845
- }
846
- }
847
- }
848
- const content = await (0, import_promises3.readFile)(compiledFile, "utf-8");
849
- return JSON.parse(content);
850
- }
851
- if (mode === "compiled-only") {
852
- throw new Error(
853
- `Compiled artifact not found: ${compiledFile}
854
- Run "promptopskit compile" to generate it.`
855
- );
856
- }
857
- }
858
- if (mode !== "compiled-only") {
859
- const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
860
- if (resolvedConfig.cache !== false) {
861
- const cached = promptCache.get(sourceFile);
862
- if (cached) {
863
- return cached;
864
- }
865
- }
866
- if (!(0, import_node_fs2.existsSync)(sourceFile)) {
867
- const paths = [sourceFile];
868
- if (resolvedConfig.compiledDir) {
869
- paths.unshift((0, import_node_path3.resolve)(resolvedConfig.compiledDir, promptPath + ".json"));
870
- }
871
- throw new Error(
872
- `Prompt not found: "${promptPath}"
873
- Searched:
874
- ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
875
- );
876
- }
877
- const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
878
- if (resolvedConfig.cache !== false) {
879
- promptCache.set(sourceFile, asset);
880
- }
881
- return asset;
882
- }
883
- throw new Error(`Prompt not found: "${promptPath}"`);
884
- }
885
- async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
886
- const resolvedConfig = withPromptResolutionDefaults(config);
887
- let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
888
- const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
889
- if (asset.includes && asset.includes.length > 0 && (0, import_node_fs2.existsSync)(sourceFile)) {
890
- asset = await resolveIncludes(asset, sourceFile);
891
- }
892
- asset = applyOverrides(asset, {
893
- environment: options.environment,
894
- tier: options.tier,
895
- runtime: options.runtime
896
- });
897
- return asset;
898
- }
899
- function resolveInlinePromptSource(source, options = {}) {
900
- const { asset } = parsePrompt(source);
901
- return applyOverrides(asset, {
902
- environment: options.environment,
903
- tier: options.tier,
904
- runtime: options.runtime
905
- });
906
- }
907
-
908
- // src/context.ts
909
- var textEncoder = new TextEncoder();
910
- var REJECT_SECRETS_PATTERN = "(secret|api[_-]?key|password)";
911
- var REJECT_SECRETS_FLAGS = "i";
912
- function getContextInputs(asset) {
913
- return (asset.context?.inputs ?? []).map(normalizeContextInput);
914
- }
915
- function getContextInputNames(asset) {
916
- return getContextInputs(asset).map((input) => input.name);
917
- }
918
- function normalizeContextInput(input) {
919
- if (typeof input === "string") {
920
- return { name: input };
921
- }
978
+ ...frontMatter,
979
+ sections,
980
+ source: filePath ? { file_path: filePath } : void 0
981
+ };
982
+ const asset = PromptAssetSchema.parse(raw);
922
983
  return {
923
- name: input.name,
924
- max_size: input.max_size,
925
- trim: input.trim,
926
- allow_regex: normalizeContextRegex(input.allow_regex),
927
- deny_regex: normalizeContextRegex(input.deny_regex),
928
- non_empty: normalizeBuiltInValidator(input.non_empty),
929
- reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
984
+ asset,
985
+ raw: {
986
+ frontMatter,
987
+ body
988
+ }
930
989
  };
931
990
  }
932
- function normalizeContextRegex(value) {
933
- if (value === void 0) {
934
- return void 0;
935
- }
936
- if (typeof value === "string") {
937
- const literal = parseRegexLiteral(value);
938
- if (value.startsWith("/") && !literal) {
939
- return {
940
- pattern: value,
941
- flags: "",
942
- raw: value,
943
- invalidLiteral: true
944
- };
991
+
992
+ // src/parser/loader.ts
993
+ var import_promises2 = require("fs/promises");
994
+ var import_node_path2 = require("path");
995
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
996
+
997
+ // src/parser/response-schema-ref.ts
998
+ var import_promises = require("fs/promises");
999
+ var import_node_crypto = require("crypto");
1000
+ var import_node_path = require("path");
1001
+ var import_node_url = require("url");
1002
+ var import_zod2 = require("zod");
1003
+ var import_zod_to_json_schema = require("zod-to-json-schema");
1004
+ var SUPPORTED_SCHEMA_REF_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
1005
+ async function resolveJsonSchemaRef(schemaRef, promptFilePath) {
1006
+ const resolvedPath = (0, import_node_path.resolve)((0, import_node_path.dirname)(promptFilePath), schemaRef);
1007
+ let raw;
1008
+ try {
1009
+ raw = await (0, import_promises.readFile)(resolvedPath, "utf-8");
1010
+ } catch (error) {
1011
+ const code = error.code;
1012
+ if (code === "ENOENT") {
1013
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" not found (resolved from ${promptFilePath})`);
945
1014
  }
946
- return {
947
- pattern: literal?.pattern ?? value,
948
- flags: literal?.flags ?? "",
949
- raw: value
950
- };
1015
+ throw error;
1016
+ }
1017
+ let parsed;
1018
+ try {
1019
+ parsed = JSON.parse(raw);
1020
+ } catch {
1021
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" is not valid JSON`);
1022
+ }
1023
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1024
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" must resolve to a JSON object schema`);
951
1025
  }
952
1026
  return {
953
- pattern: value.pattern,
954
- flags: value.flags ?? "",
955
- raw: JSON.stringify(value),
956
- returnMessage: value.return_message
1027
+ schema: parsed,
1028
+ resolvedPath,
1029
+ hash: (0, import_node_crypto.createHash)("sha256").update(raw).digest("hex")
957
1030
  };
958
1031
  }
959
- function normalizeBuiltInValidator(value) {
960
- if (value === void 0 || value === false) {
961
- return void 0;
1032
+ async function resolveZodSchemaRef(schemaRef, promptFilePath) {
1033
+ const resolvedPath = (0, import_node_path.resolve)((0, import_node_path.dirname)(promptFilePath), schemaRef);
1034
+ let moduleSource;
1035
+ try {
1036
+ moduleSource = await (0, import_promises.readFile)(resolvedPath, "utf-8");
1037
+ } catch (error) {
1038
+ const code = error.code;
1039
+ if (code === "ENOENT") {
1040
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" not found (resolved from ${promptFilePath})`);
1041
+ }
1042
+ throw error;
962
1043
  }
963
- if (value === true) {
964
- return {};
1044
+ let imported;
1045
+ try {
1046
+ imported = await import((0, import_node_url.pathToFileURL)(resolvedPath).href);
1047
+ } catch (error) {
1048
+ const message = error instanceof Error ? error.message : String(error);
1049
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" could not be imported as a module (${message})`);
1050
+ }
1051
+ const mod = imported;
1052
+ const candidate = mod.default ?? mod.schema;
1053
+ if (!(candidate instanceof import_zod2.z.ZodType)) {
1054
+ throw new Error('POK051: zod schema modules must export a Zod schema as default export or named export "schema"');
1055
+ }
1056
+ const jsonSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(candidate, {
1057
+ target: "jsonSchema7",
1058
+ $refStrategy: "none"
1059
+ });
1060
+ if (!jsonSchema || typeof jsonSchema !== "object" || Array.isArray(jsonSchema)) {
1061
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" did not produce a valid JSON schema object`);
965
1062
  }
966
1063
  return {
967
- returnMessage: value.return_message
1064
+ schema: jsonSchema,
1065
+ resolvedPath,
1066
+ hash: (0, import_node_crypto.createHash)("sha256").update(moduleSource).digest("hex")
968
1067
  };
969
1068
  }
970
- function parseRegexLiteral(value) {
971
- if (!value.startsWith("/")) {
972
- return void 0;
1069
+ async function resolveResponseSchemaRef(asset, promptFilePath) {
1070
+ const schemaRef = asset.response?.schema_ref;
1071
+ if (!schemaRef) return asset;
1072
+ const ext = (0, import_node_path.extname)(schemaRef).toLowerCase();
1073
+ if (!SUPPORTED_SCHEMA_REF_EXTENSIONS.has(ext)) {
1074
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" has unsupported extension "${ext}". Use .json, .js, .mjs, or .cjs`);
973
1075
  }
974
- for (let index = value.length - 1; index > 0; index -= 1) {
975
- if (value[index] !== "/") {
976
- continue;
1076
+ const resolved = ext === ".json" ? await resolveJsonSchemaRef(schemaRef, promptFilePath) : await resolveZodSchemaRef(schemaRef, promptFilePath);
1077
+ return {
1078
+ ...asset,
1079
+ response: {
1080
+ ...asset.response,
1081
+ schema: resolved.schema,
1082
+ schema_ref: void 0,
1083
+ schema_source: {
1084
+ mode: ext === ".json" ? "schema_ref_json" : "schema_ref_zod_module",
1085
+ ref: schemaRef,
1086
+ resolved_path: resolved.resolvedPath,
1087
+ hash: resolved.hash
1088
+ }
977
1089
  }
978
- let backslashCount = 0;
979
- for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
980
- backslashCount += 1;
1090
+ };
1091
+ }
1092
+
1093
+ // src/parser/loader.ts
1094
+ var DEFAULTS_FILE_NAME = "defaults.md";
1095
+ async function loadPromptFile(filePath, options = {}) {
1096
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
1097
+ const parsed = parsePrompt(content, filePath);
1098
+ const root = options.defaultsRoot ?? (0, import_node_path2.dirname)(filePath);
1099
+ const defaults = await loadDefaultsForPath(filePath, root);
1100
+ const withDefaults = applyDefaults(parsed.asset, defaults);
1101
+ const resolved = await resolveResponseSchemaRef(withDefaults, filePath);
1102
+ const asset = resolved.response?.schema !== void 0 && !resolved.response?.schema_source ? {
1103
+ ...resolved,
1104
+ response: {
1105
+ ...resolved.response,
1106
+ schema_source: { mode: "inline" }
1107
+ }
1108
+ } : resolved;
1109
+ return {
1110
+ ...parsed,
1111
+ asset
1112
+ };
1113
+ }
1114
+ async function loadDefaultsForPath(filePath, defaultsRoot) {
1115
+ const directories = getDirectoriesToCheck(filePath, defaultsRoot);
1116
+ let merged = {};
1117
+ for (const dir of directories) {
1118
+ const defaultsPath = (0, import_node_path2.join)(dir, DEFAULTS_FILE_NAME);
1119
+ try {
1120
+ const defaultsContent = await (0, import_promises2.readFile)(defaultsPath, "utf-8");
1121
+ const defaults = normalizeDefaultIncludes(parseDefaults(defaultsContent), defaultsPath, filePath);
1122
+ merged = mergeDefaults(merged, defaults);
1123
+ } catch (error) {
1124
+ if (error.code !== "ENOENT") {
1125
+ throw error;
1126
+ }
981
1127
  }
982
- if (backslashCount % 2 === 1) {
983
- continue;
1128
+ }
1129
+ return merged;
1130
+ }
1131
+ function getDirectoriesToCheck(filePath, defaultsRoot) {
1132
+ const dirs = [];
1133
+ let current = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(filePath));
1134
+ const boundary = defaultsRoot ? (0, import_node_path2.resolve)(defaultsRoot) : void 0;
1135
+ while (true) {
1136
+ dirs.unshift(current);
1137
+ if (boundary && current === boundary || current === (0, import_node_path2.dirname)(current)) {
1138
+ break;
984
1139
  }
985
- return {
986
- pattern: value.slice(1, index),
987
- flags: value.slice(index + 1)
988
- };
1140
+ current = (0, import_node_path2.dirname)(current);
989
1141
  }
990
- return void 0;
1142
+ return dirs;
991
1143
  }
992
- function formatInvalidContextRegexMessage(details) {
993
- return [
994
- `Invalid context regex for prompt "${details.promptId}"`,
995
- `variable "${details.variable}"`,
996
- `field "${details.field}"`,
997
- `value ${JSON.stringify(details.raw)}: ${details.reason}`
998
- ].join(", ");
1144
+ function parseDefaults(content) {
1145
+ const { data: frontMatter, content: body } = (0, import_gray_matter2.default)(content);
1146
+ const sections = extractSections(body);
1147
+ return PromptDefaultsSchema.parse({
1148
+ ...frontMatter,
1149
+ sections: {
1150
+ system_instructions: sections.system_instructions
1151
+ }
1152
+ });
999
1153
  }
1000
- function compileContextRegex(regex, details) {
1001
- if (regex.invalidLiteral) {
1002
- throw new Error(
1003
- `POK013: ${formatInvalidContextRegexMessage({
1004
- ...details,
1005
- raw: regex.raw,
1006
- reason: "Malformed regex literal. Use /pattern/flags or { pattern, flags }."
1007
- })}`
1008
- );
1009
- }
1010
- try {
1011
- return new RegExp(regex.pattern, regex.flags);
1012
- } catch (error) {
1013
- const reason = error instanceof Error ? error.message : String(error);
1014
- throw new Error(`POK013: ${formatInvalidContextRegexMessage({ ...details, raw: regex.raw, reason })}`);
1154
+ function mergeDefaults(base, local) {
1155
+ return {
1156
+ provider: local.provider ?? base.provider,
1157
+ model: local.model ?? base.model,
1158
+ fallback_models: local.fallback_models ?? base.fallback_models,
1159
+ reasoning: mergeRecordBlock2(base.reasoning, local.reasoning),
1160
+ sampling: mergeRecordBlock2(base.sampling, local.sampling),
1161
+ response: mergeRecordBlock2(base.response, local.response),
1162
+ cache: mergeCache(base.cache, local.cache),
1163
+ raw: mergeRaw(base.raw, local.raw),
1164
+ tools: local.tools ?? base.tools,
1165
+ provider_options: mergeProviderOptions(base.provider_options, local.provider_options),
1166
+ mcp: mergeMcp(base.mcp, local.mcp),
1167
+ context: mergeContext(base.context, local.context),
1168
+ includes: local.includes ?? base.includes,
1169
+ environments: mergeOverrideMap(base.environments, local.environments),
1170
+ tiers: mergeOverrideMap(base.tiers, local.tiers),
1171
+ metadata: {
1172
+ ...base.metadata ?? {},
1173
+ ...local.metadata ?? {}
1174
+ },
1175
+ sections: {
1176
+ ...base.sections ?? {},
1177
+ ...local.sections ?? {}
1178
+ }
1179
+ };
1180
+ }
1181
+ function applyDefaults(asset, defaults) {
1182
+ const cache = mergeCache(defaults.cache, asset.cache);
1183
+ const raw = mergeRaw(defaults.raw, asset.raw);
1184
+ const providerOptions = mergeProviderOptions(defaults.provider_options, asset.provider_options);
1185
+ const reasoning = mergeRecordBlock2(defaults.reasoning, asset.reasoning);
1186
+ const sampling = mergeRecordBlock2(defaults.sampling, asset.sampling);
1187
+ const response = mergeRecordBlock2(defaults.response, asset.response);
1188
+ const mcp = mergeMcp(defaults.mcp, asset.mcp);
1189
+ const context = mergeContext(defaults.context, asset.context);
1190
+ const environments = mergeOverrideMap(defaults.environments, asset.environments);
1191
+ const tiers = mergeOverrideMap(defaults.tiers, asset.tiers);
1192
+ const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
1193
+ const hasDefaultSystem = !!defaults.sections?.system_instructions;
1194
+ const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0 || defaults.fallback_models !== void 0 || defaults.tools !== void 0 || defaults.includes !== void 0;
1195
+ 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;
1196
+ if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultObjects) {
1197
+ return asset;
1015
1198
  }
1199
+ const mergedMetadata = {
1200
+ ...defaults.metadata ?? {},
1201
+ ...asset.metadata ?? {}
1202
+ };
1203
+ const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
1204
+ const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;
1205
+ const sections = asset.sections ? { ...asset.sections, system_instructions: systemInstructions } : systemInstructions ? { system_instructions: systemInstructions } : void 0;
1206
+ return {
1207
+ ...asset,
1208
+ provider: asset.provider ?? defaults.provider,
1209
+ model: asset.model ?? defaults.model,
1210
+ fallback_models: asset.fallback_models ?? defaults.fallback_models,
1211
+ reasoning,
1212
+ sampling,
1213
+ response,
1214
+ cache,
1215
+ raw,
1216
+ tools: asset.tools ?? defaults.tools,
1217
+ provider_options: providerOptions,
1218
+ mcp,
1219
+ context,
1220
+ includes: asset.includes ?? defaults.includes,
1221
+ environments,
1222
+ tiers,
1223
+ metadata,
1224
+ sections
1225
+ };
1016
1226
  }
1017
- function getRejectSecretsRegex() {
1227
+ function normalizeDefaultIncludes(defaults, defaultsPath, filePath) {
1228
+ if (!defaults.includes) return defaults;
1229
+ const promptDir = (0, import_node_path2.dirname)(filePath);
1230
+ const defaultsDir = (0, import_node_path2.dirname)(defaultsPath);
1231
+ const includes = defaults.includes.map((includePath) => {
1232
+ const relativePath = (0, import_node_path2.relative)(promptDir, (0, import_node_path2.resolve)(defaultsDir, includePath)).replace(/\\/g, "/");
1233
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
1234
+ });
1018
1235
  return {
1019
- pattern: REJECT_SECRETS_PATTERN,
1020
- flags: REJECT_SECRETS_FLAGS,
1021
- raw: JSON.stringify({ pattern: REJECT_SECRETS_PATTERN, flags: REJECT_SECRETS_FLAGS })
1236
+ ...defaults,
1237
+ includes
1022
1238
  };
1023
1239
  }
1024
- function isTrimEnabled(mode) {
1025
- return mode === true || mode === "start" || mode === "end" || mode === "both";
1240
+ function mergeRecordBlock2(base, local) {
1241
+ if (base === void 0) return local;
1242
+ if (local === void 0) return base;
1243
+ return { ...base, ...local };
1026
1244
  }
1027
- function normalizeTrimMode(mode) {
1028
- if (mode === "start") {
1029
- return "start";
1030
- }
1031
- return "end";
1245
+ function mergeMcp(base, local) {
1246
+ return mergeRecordBlock2(base, local);
1032
1247
  }
1033
- function trimToMaxSize(value, maxSize, mode) {
1034
- const measured = measureContextValueSize(value);
1035
- if (measured <= maxSize) {
1036
- return value;
1037
- }
1038
- const characters = Array.from(value);
1039
- const normalizedMode = normalizeTrimMode(mode);
1040
- if (normalizedMode === "start") {
1041
- let collected2 = "";
1042
- let size2 = 0;
1043
- for (let i = characters.length - 1; i >= 0; i -= 1) {
1044
- const next = characters[i];
1045
- const charSize = measureContextValueSize(next);
1046
- if (size2 + charSize > maxSize) {
1047
- break;
1048
- }
1049
- collected2 = next + collected2;
1050
- size2 += charSize;
1051
- }
1052
- return collected2;
1053
- }
1054
- let collected = "";
1055
- let size = 0;
1056
- for (const char of characters) {
1057
- const charSize = measureContextValueSize(char);
1058
- if (size + charSize > maxSize) {
1059
- break;
1248
+ function mergeContext(base, local) {
1249
+ const merged = {
1250
+ ...base ?? {},
1251
+ ...local ?? {},
1252
+ history: mergeRecordBlock2(base?.history, local?.history)
1253
+ };
1254
+ return Object.keys(merged).length > 0 ? merged : void 0;
1255
+ }
1256
+ function mergeCache(base, local) {
1257
+ const merged = {
1258
+ ...base ?? {},
1259
+ ...local ?? {},
1260
+ openai: {
1261
+ ...base?.openai ?? {},
1262
+ ...local?.openai ?? {}
1263
+ },
1264
+ anthropic: {
1265
+ ...base?.anthropic ?? {},
1266
+ ...local?.anthropic ?? {}
1267
+ },
1268
+ gemini: {
1269
+ ...base?.gemini ?? {},
1270
+ ...local?.gemini ?? {}
1271
+ },
1272
+ google: {
1273
+ ...base?.google ?? {},
1274
+ ...local?.google ?? {}
1060
1275
  }
1061
- collected += char;
1062
- size += charSize;
1276
+ };
1277
+ if (merged.openai && Object.keys(merged.openai).length === 0) delete merged.openai;
1278
+ if (merged.anthropic && Object.keys(merged.anthropic).length === 0) delete merged.anthropic;
1279
+ if (merged.gemini && Object.keys(merged.gemini).length === 0) delete merged.gemini;
1280
+ if (merged.google && Object.keys(merged.google).length === 0) delete merged.google;
1281
+ return Object.keys(merged).length > 0 ? merged : void 0;
1282
+ }
1283
+ function mergeRaw(base, local) {
1284
+ const merged = {
1285
+ ...base ?? {},
1286
+ ...local ?? {},
1287
+ openai: mergeRecordBlock2(base?.openai, local?.openai),
1288
+ "openai-responses": mergeRecordBlock2(base?.["openai-responses"], local?.["openai-responses"]),
1289
+ openai_responses: mergeRecordBlock2(base?.openai_responses, local?.openai_responses),
1290
+ anthropic: mergeRecordBlock2(base?.anthropic, local?.anthropic),
1291
+ gemini: mergeRecordBlock2(base?.gemini, local?.gemini),
1292
+ google: mergeRecordBlock2(base?.google, local?.google),
1293
+ openrouter: mergeRecordBlock2(base?.openrouter, local?.openrouter),
1294
+ llmasaservice: mergeRecordBlock2(base?.llmasaservice, local?.llmasaservice)
1295
+ };
1296
+ removeEmptyProviderBlocks(merged);
1297
+ return Object.keys(merged).length > 0 ? merged : void 0;
1298
+ }
1299
+ function mergeProviderOptions(base, local) {
1300
+ const merged = {
1301
+ ...base ?? {},
1302
+ ...local ?? {},
1303
+ anthropic: mergeRecordBlock2(base?.anthropic, local?.anthropic),
1304
+ gemini: mergeRecordBlock2(base?.gemini, local?.gemini),
1305
+ openrouter: mergeRecordBlock2(base?.openrouter, local?.openrouter),
1306
+ llmasaservice: mergeRecordBlock2(base?.llmasaservice, local?.llmasaservice)
1307
+ };
1308
+ removeEmptyProviderBlocks(merged);
1309
+ return Object.keys(merged).length > 0 ? merged : void 0;
1310
+ }
1311
+ function mergeOverrideMap(base, local) {
1312
+ const merged = { ...base ?? {} };
1313
+ for (const [name, localOverride] of Object.entries(local ?? {})) {
1314
+ merged[name] = mergeOverrideConfig(merged[name], localOverride);
1063
1315
  }
1064
- return collected;
1316
+ return Object.keys(merged).length > 0 ? merged : void 0;
1065
1317
  }
1066
- function sanitizeContextVariables(asset, variables = {}, options = {}) {
1067
- const { onContextOverflow } = options;
1068
- const sanitized = { ...variables };
1069
- for (const input of getContextInputs(asset)) {
1070
- const value = sanitized[input.name];
1071
- if (value === void 0) {
1318
+ function mergeOverrideConfig(base, local) {
1319
+ const merged = {
1320
+ ...base ?? {},
1321
+ ...local
1322
+ };
1323
+ const reasoning = mergeRecordBlock2(base?.reasoning, local.reasoning);
1324
+ const sampling = mergeRecordBlock2(base?.sampling, local.sampling);
1325
+ const response = mergeRecordBlock2(base?.response, local.response);
1326
+ const cache = mergeCache(base?.cache, local.cache);
1327
+ const raw = mergeRaw(base?.raw, local.raw);
1328
+ const providerOptions = mergeProviderOptions(base?.provider_options, local.provider_options);
1329
+ if (reasoning !== void 0) merged.reasoning = reasoning;
1330
+ if (sampling !== void 0) merged.sampling = sampling;
1331
+ if (response !== void 0) merged.response = response;
1332
+ if (cache !== void 0) merged.cache = cache;
1333
+ if (raw !== void 0) merged.raw = raw;
1334
+ if (providerOptions !== void 0) merged.provider_options = providerOptions;
1335
+ return merged;
1336
+ }
1337
+ function removeEmptyProviderBlocks(value) {
1338
+ for (const key of Object.keys(value)) {
1339
+ const block = value[key];
1340
+ if (block === void 0) {
1341
+ delete value[key];
1072
1342
  continue;
1073
1343
  }
1074
- let candidate = value;
1075
- if (input.max_size !== void 0) {
1076
- const actualSize = measureContextValueSize(candidate);
1077
- if (actualSize > input.max_size && onContextOverflow) {
1078
- candidate = onContextOverflow({
1079
- promptId: asset.id,
1080
- variable: input.name,
1081
- value: candidate,
1082
- maxSize: input.max_size,
1083
- actualSize
1084
- });
1085
- }
1344
+ if (block !== null && typeof block === "object" && !Array.isArray(block) && Object.keys(block).length === 0) {
1345
+ delete value[key];
1086
1346
  }
1087
- if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
1088
- candidate = trimToMaxSize(candidate, input.max_size, input.trim);
1347
+ }
1348
+ }
1349
+
1350
+ // src/composition/resolve-includes.ts
1351
+ async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
1352
+ if (!asset.includes || asset.includes.length === 0) {
1353
+ return asset;
1354
+ }
1355
+ const baseDir = (0, import_node_path3.dirname)(basePath);
1356
+ const resolvedPath = (0, import_node_path3.resolve)(basePath);
1357
+ if (visited.has(resolvedPath)) {
1358
+ throw new Error(`Circular include detected: ${resolvedPath}`);
1359
+ }
1360
+ visited.add(resolvedPath);
1361
+ let mergedSystemInstructions = "";
1362
+ for (const includePath of asset.includes) {
1363
+ const fullPath = (0, import_node_path3.resolve)(baseDir, includePath);
1364
+ if (visited.has(fullPath)) {
1365
+ throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
1089
1366
  }
1090
- sanitized[input.name] = candidate;
1091
- if (input.allow_regex) {
1092
- const candidate2 = sanitized[input.name];
1093
- const matcher = compileContextRegex(input.allow_regex, {
1094
- promptId: asset.id,
1095
- variable: input.name,
1096
- field: "allow_regex"
1097
- });
1098
- if (!matcher.test(candidate2)) {
1099
- if (input.allow_regex.returnMessage) {
1100
- return {
1101
- variables: sanitized,
1102
- shortCircuit: {
1103
- returnMessage: input.allow_regex.returnMessage,
1104
- code: "POK031",
1105
- variable: input.name,
1106
- field: "allow_regex"
1107
- }
1108
- };
1367
+ const content = await (0, import_promises3.readFile)(fullPath, "utf-8");
1368
+ const { asset: includedAsset } = parsePrompt(content, fullPath);
1369
+ const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
1370
+ if (resolved.sections?.system_instructions) {
1371
+ mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
1372
+ }
1373
+ }
1374
+ const localSystem = asset.sections?.system_instructions ?? "";
1375
+ const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
1376
+ return {
1377
+ ...asset,
1378
+ sections: {
1379
+ ...asset.sections,
1380
+ system_instructions: combinedSystem
1381
+ },
1382
+ // Drop includes from the resolved asset — they've been inlined
1383
+ includes: void 0
1384
+ };
1385
+ }
1386
+
1387
+ // src/prompt-resolution.ts
1388
+ var DEFAULT_PROMPTS_DIR = "./prompts";
1389
+ var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
1390
+ function withPromptResolutionDefaults(config) {
1391
+ return {
1392
+ ...config,
1393
+ sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
1394
+ compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
1395
+ };
1396
+ }
1397
+ var sharedPromptCache = new PromptCache();
1398
+ async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
1399
+ const resolvedConfig = withPromptResolutionDefaults(config);
1400
+ const mode = resolvedConfig.mode ?? "auto";
1401
+ if (mode !== "source-only" && resolvedConfig.compiledDir) {
1402
+ const compiledFile = (0, import_node_path4.resolve)(resolvedConfig.compiledDir, promptPath + ".json");
1403
+ if ((0, import_node_fs2.existsSync)(compiledFile)) {
1404
+ if (mode === "auto") {
1405
+ const sourceFile = (0, import_node_path4.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
1406
+ if ((0, import_node_fs2.existsSync)(sourceFile)) {
1407
+ const compiledMtime = (0, import_node_fs2.statSync)(compiledFile).mtimeMs;
1408
+ const sourceMtime = (0, import_node_fs2.statSync)(sourceFile).mtimeMs;
1409
+ if (sourceMtime > compiledMtime) {
1410
+ console.warn(
1411
+ `[promptopskit] Warning: compiled artifact for "${promptPath}" is older than source .md file.
1412
+ Run "promptopskit compile" or switch to source-only mode.`
1413
+ );
1414
+ }
1109
1415
  }
1110
- throw new Error(
1111
- `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
1112
- );
1113
1416
  }
1417
+ const content = await (0, import_promises4.readFile)(compiledFile, "utf-8");
1418
+ return JSON.parse(content);
1114
1419
  }
1115
- if (input.deny_regex) {
1116
- const candidate2 = sanitized[input.name];
1117
- const matcher = compileContextRegex(input.deny_regex, {
1118
- promptId: asset.id,
1119
- variable: input.name,
1120
- field: "deny_regex"
1121
- });
1122
- if (matcher.test(candidate2)) {
1123
- if (input.deny_regex.returnMessage) {
1124
- return {
1125
- variables: sanitized,
1126
- shortCircuit: {
1127
- returnMessage: input.deny_regex.returnMessage,
1128
- code: "POK032",
1129
- variable: input.name,
1130
- field: "deny_regex"
1131
- }
1132
- };
1133
- }
1134
- throw new Error(
1135
- `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
1136
- );
1420
+ if (mode === "compiled-only") {
1421
+ throw new Error(
1422
+ `Compiled artifact not found: ${compiledFile}
1423
+ Run "promptopskit compile" to generate it.`
1424
+ );
1425
+ }
1426
+ }
1427
+ if (mode !== "compiled-only") {
1428
+ const sourceFile = (0, import_node_path4.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
1429
+ if (resolvedConfig.cache !== false) {
1430
+ const cached = promptCache.get(sourceFile);
1431
+ if (cached) {
1432
+ return cached;
1137
1433
  }
1138
1434
  }
1139
- if (input.non_empty && candidate.trim().length === 0) {
1140
- if (input.non_empty.returnMessage) {
1141
- return {
1142
- variables: sanitized,
1143
- shortCircuit: {
1144
- returnMessage: input.non_empty.returnMessage,
1145
- code: "POK033",
1146
- variable: input.name,
1147
- field: "non_empty"
1148
- }
1149
- };
1435
+ if (!(0, import_node_fs2.existsSync)(sourceFile)) {
1436
+ const paths = [sourceFile];
1437
+ if (resolvedConfig.compiledDir) {
1438
+ paths.unshift((0, import_node_path4.resolve)(resolvedConfig.compiledDir, promptPath + ".json"));
1150
1439
  }
1151
1440
  throw new Error(
1152
- `POK033: Context variable "${input.name}" failed non_empty validation for prompt "${asset.id}".`
1441
+ `Prompt not found: "${promptPath}"
1442
+ Searched:
1443
+ ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
1153
1444
  );
1154
1445
  }
1155
- if (input.reject_secrets) {
1156
- const matcher = compileContextRegex(getRejectSecretsRegex(), {
1157
- promptId: asset.id,
1158
- variable: input.name,
1159
- field: "reject_secrets"
1160
- });
1161
- if (matcher.test(candidate)) {
1162
- if (input.reject_secrets.returnMessage) {
1163
- return {
1164
- variables: sanitized,
1165
- shortCircuit: {
1166
- returnMessage: input.reject_secrets.returnMessage,
1167
- code: "POK034",
1168
- variable: input.name,
1169
- field: "reject_secrets"
1170
- }
1171
- };
1172
- }
1173
- throw new Error(
1174
- `POK034: Context variable "${input.name}" matched reject_secrets validation for prompt "${asset.id}".`
1175
- );
1176
- }
1446
+ const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
1447
+ if (resolvedConfig.cache !== false) {
1448
+ promptCache.set(sourceFile, asset);
1177
1449
  }
1450
+ return asset;
1178
1451
  }
1179
- return { variables: sanitized };
1180
- }
1181
- function measureContextValueSize(value) {
1182
- return textEncoder.encode(value).length;
1452
+ throw new Error(`Prompt not found: "${promptPath}"`);
1183
1453
  }
1184
- function collectContextSizeWarnings(asset, variables = {}) {
1185
- const warnings = [];
1186
- for (const input of getContextInputs(asset)) {
1187
- if (input.max_size === void 0) {
1188
- continue;
1189
- }
1190
- const value = variables[input.name];
1191
- if (value === void 0) {
1192
- continue;
1193
- }
1194
- const actualSize = measureContextValueSize(value);
1195
- if (actualSize > input.max_size) {
1196
- warnings.push({
1197
- variable: input.name,
1198
- maxSize: input.max_size,
1199
- actualSize
1200
- });
1201
- }
1454
+ async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
1455
+ const resolvedConfig = withPromptResolutionDefaults(config);
1456
+ let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
1457
+ const sourceFile = (0, import_node_path4.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
1458
+ if (asset.includes && asset.includes.length > 0 && (0, import_node_fs2.existsSync)(sourceFile)) {
1459
+ asset = await resolveIncludes(asset, sourceFile);
1202
1460
  }
1203
- return warnings;
1461
+ asset = applyOverrides(asset, {
1462
+ environment: options.environment,
1463
+ tier: options.tier,
1464
+ runtime: options.runtime
1465
+ });
1466
+ return asset;
1467
+ }
1468
+ function resolveInlinePromptSource(source, options = {}) {
1469
+ const { asset } = parsePrompt(source);
1470
+ return applyOverrides(asset, {
1471
+ environment: options.environment,
1472
+ tier: options.tier,
1473
+ runtime: options.runtime
1474
+ });
1204
1475
  }
1205
1476
 
1206
1477
  // src/history.ts
@@ -1985,6 +2256,18 @@ var KNOWN_FRONT_MATTER_KEYS = /* @__PURE__ */ new Set([
1985
2256
  "provider_options",
1986
2257
  "raw"
1987
2258
  ]);
2259
+ function collectSchemaKeywords(value, acc) {
2260
+ if (!value || typeof value !== "object") return;
2261
+ if (Array.isArray(value)) {
2262
+ for (const item of value) collectSchemaKeywords(item, acc);
2263
+ return;
2264
+ }
2265
+ const record = value;
2266
+ for (const [key, child] of Object.entries(record)) {
2267
+ acc.add(key);
2268
+ collectSchemaKeywords(child, acc);
2269
+ }
2270
+ }
1988
2271
  var RISKY_UNBOUNDED_INPUT_NAMES = [
1989
2272
  "message",
1990
2273
  "prompt",
@@ -2036,7 +2319,9 @@ function validateAsset(asset, frontMatterKeys, filePath) {
2036
2319
  }
2037
2320
  }
2038
2321
  }
2039
- const declaredInputs = new Set(getContextInputNames(asset));
2322
+ const contextInputs = getContextInputs(asset);
2323
+ const declaredInputs = new Set(contextInputs.map((input) => input.name));
2324
+ const declaredInputsByName = new Map(contextInputs.map((input) => [input.name, input]));
2040
2325
  const usedVars = /* @__PURE__ */ new Set();
2041
2326
  if (asset.sections?.system_instructions) {
2042
2327
  for (const v of extractVariables(asset.sections.system_instructions)) {
@@ -2058,7 +2343,8 @@ function validateAsset(asset, frontMatterKeys, filePath) {
2058
2343
  }
2059
2344
  }
2060
2345
  for (const v of declaredInputs) {
2061
- if (!usedVars.has(v)) {
2346
+ const input = declaredInputsByName.get(v);
2347
+ if (!usedVars.has(v) && input?.optional !== true && areContextInputWarningsEnabled(input ?? {})) {
2062
2348
  warnings.push({
2063
2349
  code: "POK012",
2064
2350
  message: `Variable "${v}" is declared in context.inputs but never used`,
@@ -2074,9 +2360,10 @@ function validateAsset(asset, frontMatterKeys, filePath) {
2074
2360
  suggestion: "Declare context.inputs to enable input policy validation."
2075
2361
  });
2076
2362
  }
2077
- for (const input of getContextInputs(asset)) {
2363
+ for (const input of contextInputs) {
2078
2364
  const lowerName = input.name.toLowerCase();
2079
- if (input.max_size === void 0 && RISKY_UNBOUNDED_INPUT_NAMES.some((needle) => lowerName.includes(needle))) {
2365
+ const inputWarningsEnabled = areContextInputWarningsEnabled(input);
2366
+ if (inputWarningsEnabled && input.max_size === void 0 && RISKY_UNBOUNDED_INPUT_NAMES.some((needle) => lowerName.includes(needle))) {
2080
2367
  warnings.push({
2081
2368
  code: "POK040",
2082
2369
  message: `Context input "${input.name}" has no max_size and appears unbounded.`,
@@ -2084,7 +2371,7 @@ function validateAsset(asset, frontMatterKeys, filePath) {
2084
2371
  suggestion: "Add max_size to constrain prompt payload growth."
2085
2372
  });
2086
2373
  }
2087
- if (input.allow_regex === void 0 && input.deny_regex === void 0 && input.non_empty === void 0 && input.reject_secrets === void 0) {
2374
+ if (inputWarningsEnabled && input.optional !== true && input.allow_regex === void 0 && input.deny_regex === void 0 && input.non_empty === void 0 && input.reject_secrets === void 0) {
2088
2375
  warnings.push({
2089
2376
  code: "POK041",
2090
2377
  message: `Context input "${input.name}" has no input hardening validators.`,
@@ -2092,7 +2379,7 @@ function validateAsset(asset, frontMatterKeys, filePath) {
2092
2379
  suggestion: "Consider non_empty/reject_secrets and allow/deny regex validators."
2093
2380
  });
2094
2381
  }
2095
- if (input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
2382
+ if (inputWarningsEnabled && input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
2096
2383
  warnings.push({
2097
2384
  code: "POK014",
2098
2385
  message: `Context input "${input.name}" sets trim but has no max_size; trim-to-budget will be skipped.`,
@@ -2119,6 +2406,30 @@ function validateAsset(asset, frontMatterKeys, filePath) {
2119
2406
  }
2120
2407
  }
2121
2408
  }
2409
+ if (asset.response?.schema && asset.provider) {
2410
+ const keywords = /* @__PURE__ */ new Set();
2411
+ collectSchemaKeywords(asset.response.schema, keywords);
2412
+ const openAIUnsupported = ["patternProperties", "unevaluatedProperties", "$dynamicRef"];
2413
+ const geminiUnsupported = ["patternProperties", "$dynamicRef", "dependentSchemas"];
2414
+ const anthropicCaution = ["if", "then", "else"];
2415
+ const checks = [
2416
+ { provider: ["openai", "openai-responses", "openrouter", "llmasaservice"], unsupported: openAIUnsupported, code: "POK052" },
2417
+ { provider: ["gemini", "google"], unsupported: geminiUnsupported, code: "POK053" },
2418
+ { provider: ["anthropic"], unsupported: anthropicCaution, code: "POK054" }
2419
+ ];
2420
+ for (const check of checks) {
2421
+ if (!check.provider.includes(asset.provider)) continue;
2422
+ const hits = check.unsupported.filter((k) => keywords.has(k));
2423
+ if (hits.length > 0) {
2424
+ warnings.push({
2425
+ code: check.code,
2426
+ message: `response.schema includes provider-sensitive JSON Schema keywords for "${asset.provider}": ${hits.join(", ")}`,
2427
+ filePath,
2428
+ suggestion: "Validate schema compatibility with your provider docs or simplify the schema dialect."
2429
+ });
2430
+ }
2431
+ }
2432
+ }
2122
2433
  if (asset.provider) {
2123
2434
  let providerCache;
2124
2435
  let cacheSuggestionField;
@@ -2733,7 +3044,7 @@ var PromptOpsKit = class {
2733
3044
  * Validate a prompt file.
2734
3045
  */
2735
3046
  async validatePrompt(promptPath) {
2736
- const sourceFile = (0, import_node_path4.resolve)(this.config.sourceDir, promptPath + ".md");
3047
+ const sourceFile = (0, import_node_path5.resolve)(this.config.sourceDir, promptPath + ".md");
2737
3048
  try {
2738
3049
  const asset = await this.loadPrompt(promptPath);
2739
3050
  return validateAssetWithIncludes(asset, sourceFile);