promptopskit 0.3.9 → 0.4.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 +91 -7
  2. package/SKILL.md +69 -6
  3. package/dist/chunk-5AAYDOPU.js +60 -0
  4. package/dist/chunk-5AAYDOPU.js.map +1 -0
  5. package/dist/{chunk-YNIAA4ZJ.js → chunk-7MRMN3SW.js} +5 -3
  6. package/dist/chunk-7MRMN3SW.js.map +1 -0
  7. package/dist/{chunk-4LEPCCSA.js → chunk-JKBWC5WJ.js} +176 -4
  8. package/dist/chunk-JKBWC5WJ.js.map +1 -0
  9. package/dist/{chunk-AN3OR7YC.js → chunk-MI3M6KDD.js} +7 -5
  10. package/dist/chunk-MI3M6KDD.js.map +1 -0
  11. package/dist/{chunk-LMURMZUA.js → chunk-XL76NHZR.js} +5 -3
  12. package/dist/chunk-XL76NHZR.js.map +1 -0
  13. package/dist/{chunk-V7QY7MRM.js → chunk-YI4LILPY.js} +50 -3
  14. package/dist/chunk-YI4LILPY.js.map +1 -0
  15. package/dist/{chunk-O6RQZYS4.js → chunk-Z4IPWEUU.js} +16 -5
  16. package/dist/chunk-Z4IPWEUU.js.map +1 -0
  17. package/dist/cli/index.js +440 -52
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/index.cjs +430 -17
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +4 -4
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.js +162 -10
  24. package/dist/index.js.map +1 -1
  25. package/dist/providers/anthropic.cjs +232 -6
  26. package/dist/providers/anthropic.cjs.map +1 -1
  27. package/dist/providers/anthropic.d.cts +2 -2
  28. package/dist/providers/anthropic.d.ts +2 -2
  29. package/dist/providers/anthropic.js +3 -3
  30. package/dist/providers/gemini.cjs +223 -6
  31. package/dist/providers/gemini.cjs.map +1 -1
  32. package/dist/providers/gemini.d.cts +2 -2
  33. package/dist/providers/gemini.d.ts +2 -2
  34. package/dist/providers/gemini.js +3 -3
  35. package/dist/providers/openai-responses.cjs +221 -4
  36. package/dist/providers/openai-responses.cjs.map +1 -1
  37. package/dist/providers/openai-responses.d.cts +2 -2
  38. package/dist/providers/openai-responses.d.ts +2 -2
  39. package/dist/providers/openai-responses.js +3 -3
  40. package/dist/providers/openai.cjs +221 -4
  41. package/dist/providers/openai.cjs.map +1 -1
  42. package/dist/providers/openai.d.cts +2 -2
  43. package/dist/providers/openai.d.ts +2 -2
  44. package/dist/providers/openai.js +3 -3
  45. package/dist/providers/openrouter.cjs +256 -7
  46. package/dist/providers/openrouter.cjs.map +1 -1
  47. package/dist/providers/openrouter.d.cts +2 -2
  48. package/dist/providers/openrouter.d.ts +2 -2
  49. package/dist/providers/openrouter.js +4 -4
  50. package/dist/{schema-BKkg93rL.d.cts → schema-B9A1zLRk.d.cts} +482 -2
  51. package/dist/{schema-BKkg93rL.d.ts → schema-B9A1zLRk.d.ts} +482 -2
  52. package/dist/testing.cjs +217 -4
  53. package/dist/testing.cjs.map +1 -1
  54. package/dist/testing.d.cts +59 -2
  55. package/dist/testing.d.ts +59 -2
  56. package/dist/testing.js +70 -2
  57. package/dist/testing.js.map +1 -1
  58. package/dist/{types-CsQ8tON_.d.ts → types-DjGAwJxC.d.ts} +1 -1
  59. package/dist/{types-7cC17GWp.d.cts → types-DpbzzZO2.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 +2 -2
  63. package/dist/chunk-4LEPCCSA.js.map +0 -1
  64. package/dist/chunk-AN3OR7YC.js.map +0 -1
  65. package/dist/chunk-LMURMZUA.js.map +0 -1
  66. package/dist/chunk-O6RQZYS4.js.map +0 -1
  67. package/dist/chunk-V7QY7MRM.js.map +0 -1
  68. package/dist/chunk-YAD6COQX.js +0 -26
  69. package/dist/chunk-YAD6COQX.js.map +0 -1
  70. package/dist/chunk-YNIAA4ZJ.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -145,6 +145,29 @@ function mergeOverride(base, override) {
145
145
  if (override.response !== void 0) {
146
146
  result.response = { ...result.response, ...override.response };
147
147
  }
148
+ if (override.cache !== void 0) {
149
+ result.cache = {
150
+ ...result.cache,
151
+ ...override.cache,
152
+ openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
153
+ anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
154
+ gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
155
+ google: mergeRecordBlock(result.cache?.google, override.cache.google)
156
+ };
157
+ }
158
+ if (override.raw !== void 0) {
159
+ result.raw = {
160
+ ...result.raw,
161
+ ...override.raw,
162
+ openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
163
+ "openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
164
+ openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
165
+ anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
166
+ gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
167
+ google: mergeRecordBlock(result.raw?.google, override.raw.google),
168
+ openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter)
169
+ };
170
+ }
148
171
  if (override.provider_options !== void 0) {
149
172
  result.provider_options = {
150
173
  ...result.provider_options,
@@ -156,11 +179,19 @@ function mergeOverride(base, override) {
156
179
  gemini: {
157
180
  ...result.provider_options?.gemini,
158
181
  ...override.provider_options.gemini
182
+ },
183
+ openrouter: {
184
+ ...result.provider_options?.openrouter,
185
+ ...override.provider_options.openrouter
159
186
  }
160
187
  };
161
188
  }
162
189
  return result;
163
190
  }
191
+ function mergeRecordBlock(base, override) {
192
+ if (override === void 0) return base;
193
+ return { ...base, ...override };
194
+ }
164
195
 
165
196
  // src/providers/resolve-asset.ts
166
197
  function resolveAssetForProvider(asset, runtime = {}) {
@@ -264,23 +295,42 @@ var ResponseSchema = import_zod.z.object({
264
295
  stream: import_zod.z.boolean().optional(),
265
296
  schema: import_zod.z.record(import_zod.z.unknown()).optional(),
266
297
  schema_name: import_zod.z.string().optional(),
298
+ schema_description: import_zod.z.string().optional(),
267
299
  schema_strict: import_zod.z.boolean().optional()
268
300
  });
269
301
  var AnthropicProviderOptionsSchema = import_zod.z.object({
270
302
  top_k: import_zod.z.number().int().min(0).optional(),
271
- tool_choice: import_zod.z.record(import_zod.z.unknown()).optional()
303
+ tool_choice: import_zod.z.record(import_zod.z.unknown()).optional(),
304
+ output_config: import_zod.z.record(import_zod.z.unknown()).optional()
272
305
  });
273
306
  var GeminiProviderOptionsSchema = import_zod.z.object({
274
307
  candidate_count: import_zod.z.number().int().positive().optional(),
275
308
  top_k: import_zod.z.number().int().min(0).optional(),
276
309
  seed: import_zod.z.number().int().optional(),
277
310
  response_schema: import_zod.z.record(import_zod.z.unknown()).optional(),
311
+ response_json_schema: import_zod.z.record(import_zod.z.unknown()).optional(),
278
312
  response_modalities: import_zod.z.array(import_zod.z.string()).optional(),
279
313
  thinking_budget_tokens: import_zod.z.number().int().positive().optional()
280
314
  });
315
+ var OpenRouterProviderOptionsSchema = import_zod.z.object({
316
+ provider: import_zod.z.record(import_zod.z.unknown()).optional(),
317
+ transforms: import_zod.z.array(import_zod.z.string()).optional(),
318
+ plugins: import_zod.z.array(import_zod.z.record(import_zod.z.unknown())).optional(),
319
+ models: import_zod.z.array(import_zod.z.string()).optional()
320
+ });
281
321
  var ProviderOptionsSchema = import_zod.z.object({
282
322
  anthropic: AnthropicProviderOptionsSchema.optional(),
283
- gemini: GeminiProviderOptionsSchema.optional()
323
+ gemini: GeminiProviderOptionsSchema.optional(),
324
+ openrouter: OpenRouterProviderOptionsSchema.optional()
325
+ });
326
+ var RawProviderBodySchema = import_zod.z.object({
327
+ openai: import_zod.z.record(import_zod.z.unknown()).optional(),
328
+ "openai-responses": import_zod.z.record(import_zod.z.unknown()).optional(),
329
+ openai_responses: import_zod.z.record(import_zod.z.unknown()).optional(),
330
+ anthropic: import_zod.z.record(import_zod.z.unknown()).optional(),
331
+ gemini: import_zod.z.record(import_zod.z.unknown()).optional(),
332
+ google: import_zod.z.record(import_zod.z.unknown()).optional(),
333
+ openrouter: import_zod.z.record(import_zod.z.unknown()).optional()
284
334
  });
285
335
  var OpenAICacheSchema = import_zod.z.object({
286
336
  prompt_cache_key: import_zod.z.string().min(1).optional(),
@@ -353,6 +403,7 @@ var PromptAssetOverridesSchema = import_zod.z.object({
353
403
  sampling: SamplingSchema.optional(),
354
404
  response: ResponseSchema.optional(),
355
405
  cache: CacheSchema.optional(),
406
+ raw: RawProviderBodySchema.optional(),
356
407
  tools: import_zod.z.array(ToolRefSchema).optional(),
357
408
  provider_options: ProviderOptionsSchema.optional()
358
409
  });
@@ -385,6 +436,7 @@ var PromptAssetSchema = import_zod.z.object({
385
436
  sampling: SamplingSchema.optional(),
386
437
  response: ResponseSchema.optional(),
387
438
  cache: CacheSchema.optional(),
439
+ raw: RawProviderBodySchema.optional(),
388
440
  tools: import_zod.z.array(ToolRefSchema).optional(),
389
441
  provider_options: ProviderOptionsSchema.optional(),
390
442
  mcp: MCPSchema.optional(),
@@ -398,6 +450,123 @@ var PromptAssetSchema = import_zod.z.object({
398
450
  source: SourceSchema.optional()
399
451
  });
400
452
 
453
+ // src/parser/frontmatter-guard.ts
454
+ var FRONT_MATTER_DELIMITER = /^---[ \t]*$/;
455
+ var REGEX_FIELD = /^(?<indent>\s*)(?<field>allow_regex|deny_regex)\s*:\s*(?<value>.*)$/;
456
+ var PATTERN_FIELD = /^(?<indent>\s*)pattern\s*:\s*(?<value>.*)$/;
457
+ var INLINE_PATTERN_FIELD = /(?:^|[{,]\s*)pattern\s*:\s*(?<value>"(?:\\.|[^"\\])*")/;
458
+ function assertRegexFrontMatterQuoting(content, filePath) {
459
+ const frontMatter = extractFrontMatter(content);
460
+ if (!frontMatter) {
461
+ return;
462
+ }
463
+ let pendingRegex;
464
+ for (const [index, line] of frontMatter.lines.entries()) {
465
+ const lineNumber = frontMatter.startLine + index;
466
+ const regexMatch = line.match(REGEX_FIELD);
467
+ if (regexMatch?.groups) {
468
+ const field = regexMatch.groups.field;
469
+ const indent2 = regexMatch.groups.indent.length;
470
+ const value = regexMatch.groups.value.trim();
471
+ pendingRegex = value === "" ? { field, indent: indent2 } : void 0;
472
+ assertNoUnescapedBackslashInDoubleQuotedRegex(value, field, filePath, lineNumber);
473
+ assertNoUnescapedBackslashInInlinePattern(value, field, filePath, lineNumber);
474
+ continue;
475
+ }
476
+ if (!pendingRegex) {
477
+ continue;
478
+ }
479
+ if (line.trim() === "" || line.trimStart().startsWith("#")) {
480
+ continue;
481
+ }
482
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
483
+ if (indent <= pendingRegex.indent) {
484
+ pendingRegex = void 0;
485
+ continue;
486
+ }
487
+ const patternMatch = line.match(PATTERN_FIELD);
488
+ if (!patternMatch?.groups) {
489
+ continue;
490
+ }
491
+ assertNoUnescapedBackslashInDoubleQuotedRegex(
492
+ patternMatch.groups.value.trim(),
493
+ `${pendingRegex.field}.pattern`,
494
+ filePath,
495
+ lineNumber
496
+ );
497
+ }
498
+ }
499
+ function assertNoUnescapedBackslashInInlinePattern(value, field, filePath, lineNumber) {
500
+ const match = value.match(INLINE_PATTERN_FIELD);
501
+ const pattern = match?.groups?.value;
502
+ if (!pattern) {
503
+ return;
504
+ }
505
+ assertNoUnescapedBackslashInDoubleQuotedRegex(pattern, `${field}.pattern`, filePath, lineNumber);
506
+ }
507
+ function extractFrontMatter(content) {
508
+ const lines = content.split(/\r?\n/);
509
+ if (!FRONT_MATTER_DELIMITER.test(lines[0] ?? "")) {
510
+ return void 0;
511
+ }
512
+ for (let index = 1; index < lines.length; index += 1) {
513
+ if (FRONT_MATTER_DELIMITER.test(lines[index] ?? "")) {
514
+ return {
515
+ lines: lines.slice(1, index),
516
+ startLine: 2
517
+ };
518
+ }
519
+ }
520
+ return void 0;
521
+ }
522
+ function assertNoUnescapedBackslashInDoubleQuotedRegex(value, field, filePath, lineNumber) {
523
+ if (!value.startsWith('"')) {
524
+ return;
525
+ }
526
+ const quoted = readDoubleQuotedScalar(value);
527
+ if (!quoted) {
528
+ return;
529
+ }
530
+ if (!hasUnescapedBackslash(quoted)) {
531
+ return;
532
+ }
533
+ const location = filePath ? `${filePath}:${lineNumber}` : `line ${lineNumber}`;
534
+ throw new Error(
535
+ `POK013: Invalid context regex YAML at ${location}, field "${field}": double-quoted regex strings treat backslashes as YAML escapes. Use unquoted /pattern/i literal form, single quotes, or double each backslash.`
536
+ );
537
+ }
538
+ function readDoubleQuotedScalar(value) {
539
+ let result = "";
540
+ for (let index = 1; index < value.length; index += 1) {
541
+ const char = value[index];
542
+ if (char === '"') {
543
+ return result;
544
+ }
545
+ result += char;
546
+ if (char === "\\" && index + 1 < value.length) {
547
+ index += 1;
548
+ result += value[index];
549
+ }
550
+ }
551
+ return void 0;
552
+ }
553
+ function hasUnescapedBackslash(value) {
554
+ for (let index = 0; index < value.length; index += 1) {
555
+ if (value[index] !== "\\") {
556
+ continue;
557
+ }
558
+ let count = 1;
559
+ while (value[index + count] === "\\") {
560
+ count += 1;
561
+ }
562
+ if (count % 2 === 1) {
563
+ return true;
564
+ }
565
+ index += count - 1;
566
+ }
567
+ return false;
568
+ }
569
+
401
570
  // src/parser/sections.ts
402
571
  var SECTION_MAP = {
403
572
  "system instructions": "system_instructions",
@@ -438,6 +607,7 @@ function extractSections(body) {
438
607
 
439
608
  // src/parser/parser.ts
440
609
  function parsePrompt(content, filePath) {
610
+ assertRegexFrontMatterQuoting(content, filePath);
441
611
  const { data: frontMatter, content: body } = (0, import_gray_matter.default)(content);
442
612
  const sections = extractSections(body);
443
613
  const raw = {
@@ -515,6 +685,7 @@ function mergeDefaults(base, local) {
515
685
  return {
516
686
  provider: local.provider ?? base.provider,
517
687
  model: local.model ?? base.model,
688
+ cache: mergeCache(base.cache, local.cache),
518
689
  metadata: {
519
690
  ...base.metadata ?? {},
520
691
  ...local.metadata ?? {}
@@ -526,10 +697,12 @@ function mergeDefaults(base, local) {
526
697
  };
527
698
  }
528
699
  function applyDefaults(asset, defaults) {
700
+ const cache = mergeCache(defaults.cache, asset.cache);
529
701
  const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
530
702
  const hasDefaultSystem = !!defaults.sections?.system_instructions;
531
703
  const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0;
532
- if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars) {
704
+ const hasDefaultCache = cache !== void 0;
705
+ if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultCache) {
533
706
  return asset;
534
707
  }
535
708
  const mergedMetadata = {
@@ -543,10 +716,38 @@ function applyDefaults(asset, defaults) {
543
716
  ...asset,
544
717
  provider: asset.provider ?? defaults.provider,
545
718
  model: asset.model ?? defaults.model,
719
+ cache,
546
720
  metadata,
547
721
  sections
548
722
  };
549
723
  }
724
+ function mergeCache(base, local) {
725
+ const merged = {
726
+ ...base ?? {},
727
+ ...local ?? {},
728
+ openai: {
729
+ ...base?.openai ?? {},
730
+ ...local?.openai ?? {}
731
+ },
732
+ anthropic: {
733
+ ...base?.anthropic ?? {},
734
+ ...local?.anthropic ?? {}
735
+ },
736
+ gemini: {
737
+ ...base?.gemini ?? {},
738
+ ...local?.gemini ?? {}
739
+ },
740
+ google: {
741
+ ...base?.google ?? {},
742
+ ...local?.google ?? {}
743
+ }
744
+ };
745
+ if (merged.openai && Object.keys(merged.openai).length === 0) delete merged.openai;
746
+ if (merged.anthropic && Object.keys(merged.anthropic).length === 0) delete merged.anthropic;
747
+ if (merged.gemini && Object.keys(merged.gemini).length === 0) delete merged.gemini;
748
+ if (merged.google && Object.keys(merged.google).length === 0) delete merged.google;
749
+ return Object.keys(merged).length > 0 ? merged : void 0;
750
+ }
550
751
 
551
752
  // src/composition/resolve-includes.ts
552
753
  async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
@@ -1018,6 +1219,21 @@ function withPromptInputSupport(adapter) {
1018
1219
  };
1019
1220
  }
1020
1221
 
1222
+ // src/providers/raw.ts
1223
+ function applyRawProviderBody(body, asset, provider) {
1224
+ const raw = getRawProviderBody(asset, provider);
1225
+ return raw ? { ...body, ...raw } : body;
1226
+ }
1227
+ function getRawProviderBody(asset, provider) {
1228
+ if (provider === "openai-responses") {
1229
+ return asset.raw?.["openai-responses"] ?? asset.raw?.openai_responses;
1230
+ }
1231
+ if (provider === "gemini") {
1232
+ return asset.raw?.gemini ?? asset.raw?.google;
1233
+ }
1234
+ return asset.raw?.[provider];
1235
+ }
1236
+
1021
1237
  // src/providers/openai.ts
1022
1238
  var openaiAdapter = withPromptInputSupport({
1023
1239
  name: "openai",
@@ -1073,6 +1289,7 @@ var openaiAdapter = withPromptInputSupport({
1073
1289
  type: "json_schema",
1074
1290
  json_schema: {
1075
1291
  name: resolvedAsset.response.schema_name ?? `${resolvedAsset.id}_response`,
1292
+ ...resolvedAsset.response.schema_description ? { description: resolvedAsset.response.schema_description } : {},
1076
1293
  schema: resolvedAsset.response.schema,
1077
1294
  strict: resolvedAsset.response.schema_strict ?? true
1078
1295
  }
@@ -1107,7 +1324,7 @@ var openaiAdapter = withPromptInputSupport({
1107
1324
  });
1108
1325
  }
1109
1326
  return {
1110
- body,
1327
+ body: applyRawProviderBody(body, resolvedAsset, "openai"),
1111
1328
  provider: "openai",
1112
1329
  model: resolvedAsset.model ?? "unknown"
1113
1330
  };
@@ -1174,6 +1391,7 @@ var openaiResponsesAdapter = withPromptInputSupport({
1174
1391
  format: {
1175
1392
  type: "json_schema",
1176
1393
  name: resolvedAsset.response.schema_name ?? `${resolvedAsset.id}_response`,
1394
+ ...resolvedAsset.response.schema_description ? { description: resolvedAsset.response.schema_description } : {},
1177
1395
  schema: resolvedAsset.response.schema,
1178
1396
  strict: resolvedAsset.response.schema_strict ?? true
1179
1397
  }
@@ -1208,7 +1426,7 @@ var openaiResponsesAdapter = withPromptInputSupport({
1208
1426
  if (responseOptions?.include !== void 0) body.include = responseOptions.include;
1209
1427
  if (responseOptions?.background !== void 0) body.background = responseOptions.background;
1210
1428
  return {
1211
- body,
1429
+ body: applyRawProviderBody(body, resolvedAsset, "openai-responses"),
1212
1430
  provider: "openai-responses",
1213
1431
  model: resolvedAsset.model ?? "unknown"
1214
1432
  };
@@ -1234,8 +1452,8 @@ var anthropicAdapter = withPromptInputSupport({
1234
1452
  if (resolvedAsset.reasoning?.effort !== void 0) {
1235
1453
  warnings.push("Anthropic uses budget_tokens for thinking, not effort. effort will be mapped approximately.");
1236
1454
  }
1237
- if (resolvedAsset.response?.schema !== void 0) {
1238
- warnings.push("Anthropic does not support response.schema structured output in this adapter. It will be ignored.");
1455
+ if (resolvedAsset.response?.schema !== void 0 && resolvedAsset.response?.format !== "json") {
1456
+ warnings.push("Anthropic response.schema is mapped to output_config.format and should usually be paired with response.format: json.");
1239
1457
  }
1240
1458
  if (resolvedAsset.provider_options?.anthropic?.top_k !== void 0 && resolvedAsset.provider_options.anthropic.top_k < 0) {
1241
1459
  errors.push("Anthropic provider_options.top_k must be >= 0.");
@@ -1299,6 +1517,16 @@ var anthropicAdapter = withPromptInputSupport({
1299
1517
  if (resolvedAsset.provider_options?.anthropic?.top_k !== void 0) {
1300
1518
  body.top_k = resolvedAsset.provider_options.anthropic.top_k;
1301
1519
  }
1520
+ if (resolvedAsset.provider_options?.anthropic?.output_config !== void 0) {
1521
+ body.output_config = resolvedAsset.provider_options.anthropic.output_config;
1522
+ } else if (resolvedAsset.response?.schema !== void 0) {
1523
+ body.output_config = {
1524
+ format: {
1525
+ type: "json_schema",
1526
+ schema: resolvedAsset.response.schema
1527
+ }
1528
+ };
1529
+ }
1302
1530
  if (resolvedAsset.response?.stream !== void 0) {
1303
1531
  body.stream = resolvedAsset.response.stream;
1304
1532
  }
@@ -1332,7 +1560,7 @@ var anthropicAdapter = withPromptInputSupport({
1332
1560
  body.tool_choice = resolvedAsset.provider_options.anthropic.tool_choice;
1333
1561
  }
1334
1562
  return {
1335
- body,
1563
+ body: applyRawProviderBody(body, resolvedAsset, "anthropic"),
1336
1564
  provider: "anthropic",
1337
1565
  model: resolvedAsset.model ?? "unknown"
1338
1566
  };
@@ -1404,9 +1632,10 @@ var geminiAdapter = withPromptInputSupport({
1404
1632
  if (geminiOptions?.candidate_count !== void 0) generationConfig.candidateCount = geminiOptions.candidate_count;
1405
1633
  if (geminiOptions?.top_k !== void 0) generationConfig.topK = geminiOptions.top_k;
1406
1634
  if (geminiOptions?.seed !== void 0) generationConfig.seed = geminiOptions.seed;
1407
- if (geminiOptions?.response_schema !== void 0) generationConfig.responseSchema = geminiOptions.response_schema;
1408
1635
  if (geminiOptions?.response_modalities !== void 0) generationConfig.responseModalities = geminiOptions.response_modalities;
1409
- if (resolvedAsset.response?.schema !== void 0) generationConfig.responseSchema = resolvedAsset.response.schema;
1636
+ if (resolvedAsset.response?.schema !== void 0) generationConfig.responseJsonSchema = resolvedAsset.response.schema;
1637
+ if (geminiOptions?.response_schema !== void 0) generationConfig.responseSchema = geminiOptions.response_schema;
1638
+ if (geminiOptions?.response_json_schema !== void 0) generationConfig.responseJsonSchema = geminiOptions.response_json_schema;
1410
1639
  if (resolvedAsset.response?.format === "json") {
1411
1640
  generationConfig.responseMimeType = "application/json";
1412
1641
  }
@@ -1441,7 +1670,7 @@ var geminiAdapter = withPromptInputSupport({
1441
1670
  body.tools = [{ functionDeclarations }];
1442
1671
  }
1443
1672
  return {
1444
- body,
1673
+ body: applyRawProviderBody(body, resolvedAsset, "gemini"),
1445
1674
  provider: "gemini",
1446
1675
  model: resolvedAsset.model ?? "unknown"
1447
1676
  };
@@ -1452,13 +1681,45 @@ var geminiAdapter = withPromptInputSupport({
1452
1681
  var openrouterAdapter = withPromptInputSupport({
1453
1682
  name: "openrouter",
1454
1683
  validate(asset, runtime) {
1455
- return openaiAdapter.validate(asset, runtime);
1684
+ const resolvedAsset = resolveAssetForProvider(asset, runtime);
1685
+ const validation = openaiAdapter.validate(asset, runtime);
1686
+ const hasOpenRouterModels = (resolvedAsset.provider_options?.openrouter?.models?.length ?? 0) > 0;
1687
+ if (!hasOpenRouterModels) {
1688
+ return validation;
1689
+ }
1690
+ const modelRequiredError = "OpenAI adapter requires a model to be specified.";
1691
+ const errors = validation.errors.filter((error) => error !== modelRequiredError);
1692
+ const warnings = validation.warnings.filter((warning) => warning !== modelRequiredError);
1693
+ return {
1694
+ valid: errors.length === 0,
1695
+ errors,
1696
+ warnings
1697
+ };
1456
1698
  },
1457
1699
  render(asset, runtime) {
1458
- const result = openaiAdapter.render(asset, runtime);
1700
+ const resolvedAsset = resolveAssetForProvider(asset, runtime);
1701
+ const result = openaiAdapter.render(
1702
+ { ...resolvedAsset, raw: void 0 },
1703
+ {
1704
+ variables: runtime.variables,
1705
+ history: runtime.history,
1706
+ toolRegistry: runtime.toolRegistry,
1707
+ strict: runtime.strict
1708
+ }
1709
+ );
1710
+ const openrouterOptions = resolvedAsset.provider_options?.openrouter;
1711
+ let body = { ...result.body };
1712
+ if (openrouterOptions?.provider !== void 0) body.provider = openrouterOptions.provider;
1713
+ if (openrouterOptions?.transforms !== void 0) body.transforms = openrouterOptions.transforms;
1714
+ if (openrouterOptions?.plugins !== void 0) body.plugins = openrouterOptions.plugins;
1715
+ if (openrouterOptions?.models !== void 0) body.models = openrouterOptions.models;
1716
+ if (body.model === void 0) delete body.model;
1717
+ body = applyRawProviderBody(body, resolvedAsset, "openrouter");
1459
1718
  return {
1460
1719
  ...result,
1461
- provider: "openrouter"
1720
+ body,
1721
+ provider: "openrouter",
1722
+ model: resolvedAsset.model ?? openrouterOptions?.models?.[0] ?? "unknown"
1462
1723
  };
1463
1724
  }
1464
1725
  });
@@ -1522,8 +1783,21 @@ var KNOWN_FRONT_MATTER_KEYS = /* @__PURE__ */ new Set([
1522
1783
  "environments",
1523
1784
  "tiers",
1524
1785
  "metadata",
1525
- "cache"
1786
+ "cache",
1787
+ "provider_options",
1788
+ "raw"
1526
1789
  ]);
1790
+ var RISKY_UNBOUNDED_INPUT_NAMES = [
1791
+ "message",
1792
+ "prompt",
1793
+ "history",
1794
+ "transcript",
1795
+ "document",
1796
+ "content",
1797
+ "input",
1798
+ "body",
1799
+ "context"
1800
+ ];
1527
1801
  function validateAsset(asset, frontMatterKeys, filePath) {
1528
1802
  const errors = [];
1529
1803
  const warnings = [];
@@ -1594,7 +1868,32 @@ function validateAsset(asset, frontMatterKeys, filePath) {
1594
1868
  });
1595
1869
  }
1596
1870
  }
1871
+ if (usedVars.size > 0 && (!asset.context?.inputs || asset.context.inputs.length === 0)) {
1872
+ warnings.push({
1873
+ code: "POK046",
1874
+ message: `Template uses ${usedVars.size === 1 ? "a variable" : "variables"} but context.inputs is not declared.`,
1875
+ filePath,
1876
+ suggestion: "Declare context.inputs to enable input policy validation."
1877
+ });
1878
+ }
1597
1879
  for (const input of getContextInputs(asset)) {
1880
+ const lowerName = input.name.toLowerCase();
1881
+ if (input.max_size === void 0 && RISKY_UNBOUNDED_INPUT_NAMES.some((needle) => lowerName.includes(needle))) {
1882
+ warnings.push({
1883
+ code: "POK040",
1884
+ message: `Context input "${input.name}" has no max_size and appears unbounded.`,
1885
+ filePath,
1886
+ suggestion: "Add max_size to constrain prompt payload growth."
1887
+ });
1888
+ }
1889
+ if (input.allow_regex === void 0 && input.deny_regex === void 0 && input.non_empty === void 0 && input.reject_secrets === void 0) {
1890
+ warnings.push({
1891
+ code: "POK041",
1892
+ message: `Context input "${input.name}" has no input hardening validators.`,
1893
+ filePath,
1894
+ suggestion: "Consider non_empty/reject_secrets and allow/deny regex validators."
1895
+ });
1896
+ }
1598
1897
  if (input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
1599
1898
  warnings.push({
1600
1899
  code: "POK014",
@@ -1622,6 +1921,91 @@ function validateAsset(asset, frontMatterKeys, filePath) {
1622
1921
  }
1623
1922
  }
1624
1923
  }
1924
+ if (asset.provider) {
1925
+ let providerCache;
1926
+ let cacheSuggestionField;
1927
+ switch (asset.provider) {
1928
+ case "openai":
1929
+ providerCache = asset.cache?.openai;
1930
+ cacheSuggestionField = "cache.openai";
1931
+ break;
1932
+ case "anthropic":
1933
+ providerCache = asset.cache?.anthropic;
1934
+ cacheSuggestionField = "cache.anthropic";
1935
+ break;
1936
+ case "gemini":
1937
+ case "google":
1938
+ providerCache = asset.cache?.gemini ?? asset.cache?.google;
1939
+ cacheSuggestionField = "cache.gemini";
1940
+ break;
1941
+ default:
1942
+ break;
1943
+ }
1944
+ if (cacheSuggestionField && providerCache === void 0) {
1945
+ warnings.push({
1946
+ code: "POK042",
1947
+ message: `Provider "${asset.provider}" has no provider-specific cache settings.`,
1948
+ filePath,
1949
+ suggestion: `Consider configuring ${cacheSuggestionField} for better cache-hit behavior.`
1950
+ });
1951
+ }
1952
+ if (!asset.model) {
1953
+ warnings.push({
1954
+ code: "POK044",
1955
+ message: `Provider "${asset.provider}" is configured without a model.`,
1956
+ filePath,
1957
+ suggestion: "Set model in prompt or defaults to avoid adapter-time errors."
1958
+ });
1959
+ }
1960
+ }
1961
+ if (asset.cache?.gemini?.cached_content && asset.cache.google?.cached_content && asset.cache.gemini.cached_content !== asset.cache.google.cached_content) {
1962
+ warnings.push({
1963
+ code: "POK043",
1964
+ message: "cache.gemini.cached_content and cache.google.cached_content are both set to different values.",
1965
+ filePath,
1966
+ suggestion: "Use one canonical value; Gemini prefers cache.gemini.cached_content."
1967
+ });
1968
+ }
1969
+ for (const [envName, overrides] of Object.entries(asset.environments ?? {})) {
1970
+ if (asset.cache && (!isRecord(overrides) || !overrides.cache)) {
1971
+ warnings.push({
1972
+ code: "POK045",
1973
+ message: `Environment "${envName}" does not override cache while prompt-level cache is defined.`,
1974
+ filePath,
1975
+ suggestion: "Confirm cache strategy is intentionally shared across environments."
1976
+ });
1977
+ }
1978
+ }
1979
+ for (const [tierName, overrides] of Object.entries(asset.tiers ?? {})) {
1980
+ if (asset.cache && (!isRecord(overrides) || !overrides.cache)) {
1981
+ warnings.push({
1982
+ code: "POK045",
1983
+ message: `Tier "${tierName}" does not override cache while prompt-level cache is defined.`,
1984
+ filePath,
1985
+ suggestion: "Confirm cache strategy is intentionally shared across tiers."
1986
+ });
1987
+ }
1988
+ }
1989
+ for (const tool of asset.tools ?? []) {
1990
+ if (isRecord(tool)) {
1991
+ if (!tool.description) {
1992
+ warnings.push({
1993
+ code: "POK047",
1994
+ message: `Inline tool "${tool.name}" is missing a description.`,
1995
+ filePath,
1996
+ suggestion: "Add description to improve model tool-selection quality."
1997
+ });
1998
+ }
1999
+ if (!tool.input_schema) {
2000
+ warnings.push({
2001
+ code: "POK047",
2002
+ message: `Inline tool "${tool.name}" is missing input_schema.`,
2003
+ filePath,
2004
+ suggestion: "Add input_schema so tool inputs are strongly typed."
2005
+ });
2006
+ }
2007
+ }
2008
+ }
1625
2009
  return {
1626
2010
  valid: errors.length === 0,
1627
2011
  errors,
@@ -1658,6 +2042,9 @@ function findClosestMatch(input, candidates) {
1658
2042
  }
1659
2043
  return best;
1660
2044
  }
2045
+ function isRecord(value) {
2046
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2047
+ }
1661
2048
 
1662
2049
  // src/usagetap/client.ts
1663
2050
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
@@ -2141,9 +2528,21 @@ var PromptOpsKit = class {
2141
2528
  * Validate a prompt file.
2142
2529
  */
2143
2530
  async validatePrompt(promptPath) {
2144
- const asset = await this.loadPrompt(promptPath);
2145
2531
  const sourceFile = (0, import_node_path4.resolve)(this.config.sourceDir, promptPath + ".md");
2146
- return validateAssetWithIncludes(asset, sourceFile);
2532
+ try {
2533
+ const asset = await this.loadPrompt(promptPath);
2534
+ return validateAssetWithIncludes(asset, sourceFile);
2535
+ } catch (error) {
2536
+ const validationError = toPromptValidationError(error, sourceFile);
2537
+ if (validationError) {
2538
+ return {
2539
+ valid: false,
2540
+ errors: [validationError],
2541
+ warnings: []
2542
+ };
2543
+ }
2544
+ throw error;
2545
+ }
2147
2546
  }
2148
2547
  /**
2149
2548
  * Clear the internal cache.
@@ -2155,6 +2554,20 @@ var PromptOpsKit = class {
2155
2554
  function createPromptOpsKit(config = {}) {
2156
2555
  return new PromptOpsKit(config);
2157
2556
  }
2557
+ function toPromptValidationError(error, filePath) {
2558
+ if (!(error instanceof Error)) {
2559
+ return void 0;
2560
+ }
2561
+ const match = error.message.match(/^(POK\d+):\s*(.*)$/);
2562
+ if (!match) {
2563
+ return void 0;
2564
+ }
2565
+ return {
2566
+ code: match[1],
2567
+ message: match[2],
2568
+ filePath
2569
+ };
2570
+ }
2158
2571
  async function renderPrompt(options) {
2159
2572
  const kit = createPromptOpsKit({
2160
2573
  sourceDir: options.sourceDir ?? DEFAULT_PROMPTS_DIR,