promptopskit 0.3.4 → 0.3.6

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 (56) hide show
  1. package/README.md +8 -9
  2. package/SKILL.md +4 -1
  3. package/dist/{chunk-2LK6IILW.js → chunk-6FLNJVE7.js} +5 -2
  4. package/dist/chunk-6FLNJVE7.js.map +1 -0
  5. package/dist/{chunk-UJA7XBQZ.js → chunk-J32I6DSG.js} +2 -2
  6. package/dist/{chunk-VU3WKLFI.js → chunk-MN3RQ7DZ.js} +2 -2
  7. package/dist/{chunk-R5PKK6Y7.js → chunk-MYXDJMWV.js} +2 -2
  8. package/dist/{chunk-S5YHGHK5.js → chunk-SHYKSLVR.js} +172 -16
  9. package/dist/chunk-SHYKSLVR.js.map +1 -0
  10. package/dist/{chunk-U7IDX2P7.js → chunk-SOY2CEJM.js} +3 -3
  11. package/dist/cli/index.js +145 -56
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/index.cjs +203 -64
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +10 -8
  16. package/dist/index.d.ts +10 -8
  17. package/dist/index.js +48 -56
  18. package/dist/index.js.map +1 -1
  19. package/dist/providers/anthropic.cjs +144 -15
  20. package/dist/providers/anthropic.cjs.map +1 -1
  21. package/dist/providers/anthropic.d.cts +2 -2
  22. package/dist/providers/anthropic.d.ts +2 -2
  23. package/dist/providers/anthropic.js +3 -3
  24. package/dist/providers/gemini.cjs +144 -15
  25. package/dist/providers/gemini.cjs.map +1 -1
  26. package/dist/providers/gemini.d.cts +2 -2
  27. package/dist/providers/gemini.d.ts +2 -2
  28. package/dist/providers/gemini.js +3 -3
  29. package/dist/providers/openai.cjs +144 -15
  30. package/dist/providers/openai.cjs.map +1 -1
  31. package/dist/providers/openai.d.cts +2 -2
  32. package/dist/providers/openai.d.ts +2 -2
  33. package/dist/providers/openai.js +3 -3
  34. package/dist/providers/openrouter.cjs +144 -15
  35. package/dist/providers/openrouter.cjs.map +1 -1
  36. package/dist/providers/openrouter.d.cts +2 -2
  37. package/dist/providers/openrouter.d.ts +2 -2
  38. package/dist/providers/openrouter.js +4 -4
  39. package/dist/{schema-Dq0jKest.d.cts → schema-D145q3Dw.d.cts} +63 -42
  40. package/dist/{schema-Dq0jKest.d.ts → schema-D145q3Dw.d.ts} +63 -42
  41. package/dist/testing.cjs +4 -1
  42. package/dist/testing.cjs.map +1 -1
  43. package/dist/testing.d.cts +1 -1
  44. package/dist/testing.d.ts +1 -1
  45. package/dist/testing.js +1 -1
  46. package/dist/{types-CgA7_wNI.d.cts → types-B3sWHzIo.d.cts} +9 -2
  47. package/dist/{types-D_-336jx.d.ts → types-CXlVWckk.d.ts} +9 -2
  48. package/dist/usagetap/index.d.cts +2 -2
  49. package/dist/usagetap/index.d.ts +2 -2
  50. package/package.json +4 -1
  51. package/dist/chunk-2LK6IILW.js.map +0 -1
  52. package/dist/chunk-S5YHGHK5.js.map +0 -1
  53. /package/dist/{chunk-UJA7XBQZ.js.map → chunk-J32I6DSG.js.map} +0 -0
  54. /package/dist/{chunk-VU3WKLFI.js.map → chunk-MN3RQ7DZ.js.map} +0 -0
  55. /package/dist/{chunk-R5PKK6Y7.js.map → chunk-MYXDJMWV.js.map} +0 -0
  56. /package/dist/{chunk-U7IDX2P7.js.map → chunk-SOY2CEJM.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -253,7 +253,10 @@ var HistorySchema = import_zod.z.object({
253
253
  });
254
254
  var ContextInputDefinitionObjectSchema = import_zod.z.object({
255
255
  name: import_zod.z.string(),
256
- max_size: import_zod.z.number().int().positive().optional()
256
+ max_size: import_zod.z.number().int().positive().optional(),
257
+ trim: import_zod.z.union([import_zod.z.boolean(), import_zod.z.enum(["start", "end", "both"])]).optional(),
258
+ allow_regex: import_zod.z.string().optional(),
259
+ deny_regex: import_zod.z.string().optional()
257
260
  });
258
261
  var ContextInputDefinitionSchema = import_zod.z.union([
259
262
  import_zod.z.string(),
@@ -507,14 +510,24 @@ async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Se
507
510
  }
508
511
 
509
512
  // src/prompt-resolution.ts
513
+ var DEFAULT_PROMPTS_DIR = "./prompts";
514
+ var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
515
+ function withPromptResolutionDefaults(config) {
516
+ return {
517
+ ...config,
518
+ sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
519
+ compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
520
+ };
521
+ }
510
522
  var sharedPromptCache = new PromptCache();
511
523
  async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
512
- const mode = config.mode ?? "auto";
513
- if (mode !== "source-only" && config.compiledDir) {
514
- const compiledFile = (0, import_node_path3.resolve)(config.compiledDir, promptPath + ".json");
524
+ const resolvedConfig = withPromptResolutionDefaults(config);
525
+ const mode = resolvedConfig.mode ?? "auto";
526
+ if (mode !== "source-only" && resolvedConfig.compiledDir) {
527
+ const compiledFile = (0, import_node_path3.resolve)(resolvedConfig.compiledDir, promptPath + ".json");
515
528
  if ((0, import_node_fs2.existsSync)(compiledFile)) {
516
529
  if (mode === "auto") {
517
- const sourceFile = (0, import_node_path3.resolve)(config.sourceDir, promptPath + ".md");
530
+ const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
518
531
  if ((0, import_node_fs2.existsSync)(sourceFile)) {
519
532
  const compiledMtime = (0, import_node_fs2.statSync)(compiledFile).mtimeMs;
520
533
  const sourceMtime = (0, import_node_fs2.statSync)(sourceFile).mtimeMs;
@@ -537,8 +550,8 @@ Run "promptopskit compile" to generate it.`
537
550
  }
538
551
  }
539
552
  if (mode !== "compiled-only") {
540
- const sourceFile = (0, import_node_path3.resolve)(config.sourceDir, promptPath + ".md");
541
- if (config.cache !== false) {
553
+ const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
554
+ if (resolvedConfig.cache !== false) {
542
555
  const cached = promptCache.get(sourceFile);
543
556
  if (cached) {
544
557
  return cached;
@@ -546,8 +559,8 @@ Run "promptopskit compile" to generate it.`
546
559
  }
547
560
  if (!(0, import_node_fs2.existsSync)(sourceFile)) {
548
561
  const paths = [sourceFile];
549
- if (config.compiledDir) {
550
- paths.unshift((0, import_node_path3.resolve)(config.compiledDir, promptPath + ".json"));
562
+ if (resolvedConfig.compiledDir) {
563
+ paths.unshift((0, import_node_path3.resolve)(resolvedConfig.compiledDir, promptPath + ".json"));
551
564
  }
552
565
  throw new Error(
553
566
  `Prompt not found: "${promptPath}"
@@ -555,8 +568,8 @@ Searched:
555
568
  ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
556
569
  );
557
570
  }
558
- const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: config.sourceDir });
559
- if (config.cache !== false) {
571
+ const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
572
+ if (resolvedConfig.cache !== false) {
560
573
  promptCache.set(sourceFile, asset);
561
574
  }
562
575
  return asset;
@@ -564,8 +577,9 @@ ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
564
577
  throw new Error(`Prompt not found: "${promptPath}"`);
565
578
  }
566
579
  async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
567
- let asset = await loadPromptAsset(promptPath, config, promptCache);
568
- const sourceFile = (0, import_node_path3.resolve)(config.sourceDir, promptPath + ".md");
580
+ const resolvedConfig = withPromptResolutionDefaults(config);
581
+ let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
582
+ const sourceFile = (0, import_node_path3.resolve)(resolvedConfig.sourceDir, promptPath + ".md");
569
583
  if (asset.includes && asset.includes.length > 0 && (0, import_node_fs2.existsSync)(sourceFile)) {
570
584
  asset = await resolveIncludes(asset, sourceFile);
571
585
  }
@@ -585,9 +599,142 @@ function resolveInlinePromptSource(source, options = {}) {
585
599
  });
586
600
  }
587
601
 
602
+ // src/context.ts
603
+ var textEncoder = new TextEncoder();
604
+ function getContextInputs(asset) {
605
+ return (asset.context?.inputs ?? []).map(normalizeContextInput);
606
+ }
607
+ function getContextInputNames(asset) {
608
+ return getContextInputs(asset).map((input) => input.name);
609
+ }
610
+ function normalizeContextInput(input) {
611
+ if (typeof input === "string") {
612
+ return { name: input };
613
+ }
614
+ return {
615
+ name: input.name,
616
+ max_size: input.max_size,
617
+ trim: input.trim,
618
+ allow_regex: input.allow_regex,
619
+ deny_regex: input.deny_regex
620
+ };
621
+ }
622
+ function isTrimEnabled(mode) {
623
+ return mode === true || mode === "start" || mode === "end" || mode === "both";
624
+ }
625
+ function normalizeTrimMode(mode) {
626
+ if (mode === "start") {
627
+ return "start";
628
+ }
629
+ return "end";
630
+ }
631
+ function trimToMaxSize(value, maxSize, mode) {
632
+ const measured = measureContextValueSize(value);
633
+ if (measured <= maxSize) {
634
+ return value;
635
+ }
636
+ const characters = Array.from(value);
637
+ const normalizedMode = normalizeTrimMode(mode);
638
+ if (normalizedMode === "start") {
639
+ let collected2 = "";
640
+ let size2 = 0;
641
+ for (let i = characters.length - 1; i >= 0; i -= 1) {
642
+ const next = characters[i];
643
+ const charSize = measureContextValueSize(next);
644
+ if (size2 + charSize > maxSize) {
645
+ break;
646
+ }
647
+ collected2 = next + collected2;
648
+ size2 += charSize;
649
+ }
650
+ return collected2;
651
+ }
652
+ let collected = "";
653
+ let size = 0;
654
+ for (const char of characters) {
655
+ const charSize = measureContextValueSize(char);
656
+ if (size + charSize > maxSize) {
657
+ break;
658
+ }
659
+ collected += char;
660
+ size += charSize;
661
+ }
662
+ return collected;
663
+ }
664
+ function sanitizeContextVariables(asset, variables = {}, options = {}) {
665
+ const { onContextOverflow } = options;
666
+ const sanitized = { ...variables };
667
+ for (const input of getContextInputs(asset)) {
668
+ const value = sanitized[input.name];
669
+ if (value === void 0) {
670
+ continue;
671
+ }
672
+ let candidate = value;
673
+ if (input.max_size !== void 0) {
674
+ const actualSize = measureContextValueSize(candidate);
675
+ if (actualSize > input.max_size && onContextOverflow) {
676
+ candidate = onContextOverflow({
677
+ promptId: asset.id,
678
+ variable: input.name,
679
+ value: candidate,
680
+ maxSize: input.max_size,
681
+ actualSize
682
+ });
683
+ }
684
+ }
685
+ if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
686
+ candidate = trimToMaxSize(candidate, input.max_size, input.trim);
687
+ }
688
+ sanitized[input.name] = candidate;
689
+ if (input.allow_regex) {
690
+ const candidate2 = sanitized[input.name];
691
+ const matcher = new RegExp(input.allow_regex);
692
+ if (!matcher.test(candidate2)) {
693
+ throw new Error(
694
+ `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
695
+ );
696
+ }
697
+ }
698
+ if (input.deny_regex) {
699
+ const candidate2 = sanitized[input.name];
700
+ const matcher = new RegExp(input.deny_regex);
701
+ if (matcher.test(candidate2)) {
702
+ throw new Error(
703
+ `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
704
+ );
705
+ }
706
+ }
707
+ }
708
+ return sanitized;
709
+ }
710
+ function measureContextValueSize(value) {
711
+ return textEncoder.encode(value).length;
712
+ }
713
+ function collectContextSizeWarnings(asset, variables = {}) {
714
+ const warnings = [];
715
+ for (const input of getContextInputs(asset)) {
716
+ if (input.max_size === void 0) {
717
+ continue;
718
+ }
719
+ const value = variables[input.name];
720
+ if (value === void 0) {
721
+ continue;
722
+ }
723
+ const actualSize = measureContextValueSize(value);
724
+ if (actualSize > input.max_size) {
725
+ warnings.push({
726
+ variable: input.name,
727
+ maxSize: input.max_size,
728
+ actualSize
729
+ });
730
+ }
731
+ }
732
+ return warnings;
733
+ }
734
+
588
735
  // src/providers/prompt-input.ts
589
736
  function isPromptLookup(input) {
590
- return "path" in input && typeof input.path === "string" && "sourceDir" in input;
737
+ return "path" in input && typeof input.path === "string";
591
738
  }
592
739
  function isInlinePromptSource(input) {
593
740
  return "source" in input && typeof input.source === "string";
@@ -608,7 +755,13 @@ function withPromptInputSupport(adapter) {
608
755
  };
609
756
  const renderPrompt2 = async (input, runtime) => {
610
757
  const resolved = await resolveProviderPromptInput(input, runtime);
611
- return adapter.render(resolved, runtime);
758
+ const variables = sanitizeContextVariables(resolved, runtime.variables, {
759
+ onContextOverflow: runtime.onContextOverflow
760
+ });
761
+ return adapter.render(resolved, {
762
+ ...runtime,
763
+ variables
764
+ });
612
765
  };
613
766
  return {
614
767
  ...adapter,
@@ -895,48 +1048,6 @@ function getAdapter(provider) {
895
1048
  return adapter;
896
1049
  }
897
1050
 
898
- // src/context.ts
899
- var textEncoder = new TextEncoder();
900
- function getContextInputs(asset) {
901
- return (asset.context?.inputs ?? []).map(normalizeContextInput);
902
- }
903
- function getContextInputNames(asset) {
904
- return getContextInputs(asset).map((input) => input.name);
905
- }
906
- function normalizeContextInput(input) {
907
- if (typeof input === "string") {
908
- return { name: input };
909
- }
910
- return {
911
- name: input.name,
912
- max_size: input.max_size
913
- };
914
- }
915
- function measureContextValueSize(value) {
916
- return textEncoder.encode(value).length;
917
- }
918
- function collectContextSizeWarnings(asset, variables = {}) {
919
- const warnings = [];
920
- for (const input of getContextInputs(asset)) {
921
- if (input.max_size === void 0) {
922
- continue;
923
- }
924
- const value = variables[input.name];
925
- if (value === void 0) {
926
- continue;
927
- }
928
- const actualSize = measureContextValueSize(value);
929
- if (actualSize > input.max_size) {
930
- warnings.push({
931
- variable: input.name,
932
- maxSize: input.max_size,
933
- actualSize
934
- });
935
- }
936
- }
937
- return warnings;
938
- }
939
-
940
1051
  // src/validation/levenshtein.ts
941
1052
  function levenshtein(a, b) {
942
1053
  const m = a.length;
@@ -1048,6 +1159,30 @@ function validateAsset(asset, frontMatterKeys, filePath) {
1048
1159
  });
1049
1160
  }
1050
1161
  }
1162
+ for (const input of getContextInputs(asset)) {
1163
+ if (input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
1164
+ warnings.push({
1165
+ code: "POK014",
1166
+ message: `Context input "${input.name}" sets trim but has no max_size; trim-to-budget will be skipped.`,
1167
+ filePath
1168
+ });
1169
+ }
1170
+ const checks = [];
1171
+ if (input.allow_regex) checks.push({ pattern: input.allow_regex, kind: "allow_regex" });
1172
+ if (input.deny_regex) checks.push({ pattern: input.deny_regex, kind: "deny_regex" });
1173
+ for (const check of checks) {
1174
+ try {
1175
+ new RegExp(check.pattern);
1176
+ } catch (error) {
1177
+ const reason = error instanceof Error ? error.message : String(error);
1178
+ errors.push({
1179
+ code: "POK013",
1180
+ message: `Invalid context ${check.kind} for "${input.name}": ${reason}`,
1181
+ filePath
1182
+ });
1183
+ }
1184
+ }
1185
+ }
1051
1186
  return {
1052
1187
  valid: errors.length === 0,
1053
1188
  errors,
@@ -1481,10 +1616,11 @@ var PromptOpsKit = class {
1481
1616
  config;
1482
1617
  promptCache;
1483
1618
  constructor(config) {
1619
+ const resolvedConfig = withPromptResolutionDefaults(config);
1484
1620
  this.config = {
1485
- ...config,
1486
- mode: config.mode ?? "auto",
1487
- cache: config.cache ?? true
1621
+ ...resolvedConfig,
1622
+ mode: resolvedConfig.mode ?? "auto",
1623
+ cache: resolvedConfig.cache ?? true
1488
1624
  };
1489
1625
  this.promptCache = new PromptCache();
1490
1626
  }
@@ -1528,7 +1664,10 @@ var PromptOpsKit = class {
1528
1664
  ` + validation.errors.map((e) => ` - ${e}`).join("\n")
1529
1665
  );
1530
1666
  }
1531
- const contextSizeWarnings = collectContextSizeWarnings(resolved, options.variables).map(
1667
+ const sanitizedVariables = sanitizeContextVariables(resolved, options.variables, {
1668
+ onContextOverflow: options.onContextOverflow
1669
+ });
1670
+ const contextSizeWarnings = collectContextSizeWarnings(resolved, sanitizedVariables).map(
1532
1671
  (warning) => formatContextSizeWarning(resolved, warning)
1533
1672
  );
1534
1673
  const contextWarningPolicy = this.config.warnings?.contextSize;
@@ -1538,7 +1677,7 @@ var PromptOpsKit = class {
1538
1677
  }
1539
1678
  }
1540
1679
  const request = adapter.render(resolved, {
1541
- variables: options.variables,
1680
+ variables: sanitizedVariables,
1542
1681
  history: options.history,
1543
1682
  toolRegistry: options.toolRegistry,
1544
1683
  strict: options.strict
@@ -1564,12 +1703,12 @@ var PromptOpsKit = class {
1564
1703
  this.promptCache.clear();
1565
1704
  }
1566
1705
  };
1567
- function createPromptOpsKit(config) {
1706
+ function createPromptOpsKit(config = {}) {
1568
1707
  return new PromptOpsKit(config);
1569
1708
  }
1570
1709
  async function renderPrompt(options) {
1571
1710
  const kit = createPromptOpsKit({
1572
- sourceDir: options.sourceDir ?? ".",
1711
+ sourceDir: options.sourceDir ?? DEFAULT_PROMPTS_DIR,
1573
1712
  cache: false,
1574
1713
  warnings: options.warnings
1575
1714
  });