promptopskit 0.3.9 → 0.4.0
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.
- package/README.md +91 -7
- package/SKILL.md +69 -6
- package/dist/{chunk-YNIAA4ZJ.js → chunk-F6UJ73Y6.js} +5 -3
- package/dist/chunk-F6UJ73Y6.js.map +1 -0
- package/dist/{chunk-O6RQZYS4.js → chunk-HLNVKKMS.js} +16 -5
- package/dist/chunk-HLNVKKMS.js.map +1 -0
- package/dist/{chunk-4LEPCCSA.js → chunk-JBLUPKY4.js} +144 -3
- package/dist/chunk-JBLUPKY4.js.map +1 -0
- package/dist/{chunk-LMURMZUA.js → chunk-L2V7LDKD.js} +5 -3
- package/dist/chunk-L2V7LDKD.js.map +1 -0
- package/dist/{chunk-AN3OR7YC.js → chunk-RY5DVTLV.js} +7 -5
- package/dist/chunk-RY5DVTLV.js.map +1 -0
- package/dist/chunk-UPTCSWK3.js +60 -0
- package/dist/chunk-UPTCSWK3.js.map +1 -0
- package/dist/{chunk-V7QY7MRM.js → chunk-XNNDGDPI.js} +50 -3
- package/dist/chunk-XNNDGDPI.js.map +1 -0
- package/dist/cli/index.js +356 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +398 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +162 -10
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.cjs +200 -5
- package/dist/providers/anthropic.cjs.map +1 -1
- package/dist/providers/anthropic.d.cts +2 -2
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +3 -3
- package/dist/providers/gemini.cjs +191 -5
- package/dist/providers/gemini.cjs.map +1 -1
- package/dist/providers/gemini.d.cts +2 -2
- package/dist/providers/gemini.d.ts +2 -2
- package/dist/providers/gemini.js +3 -3
- package/dist/providers/openai-responses.cjs +189 -3
- package/dist/providers/openai-responses.cjs.map +1 -1
- package/dist/providers/openai-responses.d.cts +2 -2
- package/dist/providers/openai-responses.d.ts +2 -2
- package/dist/providers/openai-responses.js +3 -3
- package/dist/providers/openai.cjs +189 -3
- package/dist/providers/openai.cjs.map +1 -1
- package/dist/providers/openai.d.cts +2 -2
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +3 -3
- package/dist/providers/openrouter.cjs +224 -6
- package/dist/providers/openrouter.cjs.map +1 -1
- package/dist/providers/openrouter.d.cts +2 -2
- package/dist/providers/openrouter.d.ts +2 -2
- package/dist/providers/openrouter.js +4 -4
- package/dist/{schema-BKkg93rL.d.cts → schema-B9A1zLRk.d.cts} +482 -2
- package/dist/{schema-BKkg93rL.d.ts → schema-B9A1zLRk.d.ts} +482 -2
- package/dist/testing.cjs +217 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +59 -2
- package/dist/testing.d.ts +59 -2
- package/dist/testing.js +70 -2
- package/dist/testing.js.map +1 -1
- package/dist/{types-CsQ8tON_.d.ts → types-DjGAwJxC.d.ts} +1 -1
- package/dist/{types-7cC17GWp.d.cts → types-DpbzzZO2.d.cts} +1 -1
- package/dist/usagetap/index.d.cts +2 -2
- package/dist/usagetap/index.d.ts +2 -2
- package/package.json +2 -2
- package/dist/chunk-4LEPCCSA.js.map +0 -1
- package/dist/chunk-AN3OR7YC.js.map +0 -1
- package/dist/chunk-LMURMZUA.js.map +0 -1
- package/dist/chunk-O6RQZYS4.js.map +0 -1
- package/dist/chunk-V7QY7MRM.js.map +0 -1
- package/dist/chunk-YAD6COQX.js +0 -26
- package/dist/chunk-YAD6COQX.js.map +0 -1
- package/dist/chunk-YNIAA4ZJ.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -42,23 +42,42 @@ var ResponseSchema = z.object({
|
|
|
42
42
|
stream: z.boolean().optional(),
|
|
43
43
|
schema: z.record(z.unknown()).optional(),
|
|
44
44
|
schema_name: z.string().optional(),
|
|
45
|
+
schema_description: z.string().optional(),
|
|
45
46
|
schema_strict: z.boolean().optional()
|
|
46
47
|
});
|
|
47
48
|
var AnthropicProviderOptionsSchema = z.object({
|
|
48
49
|
top_k: z.number().int().min(0).optional(),
|
|
49
|
-
tool_choice: z.record(z.unknown()).optional()
|
|
50
|
+
tool_choice: z.record(z.unknown()).optional(),
|
|
51
|
+
output_config: z.record(z.unknown()).optional()
|
|
50
52
|
});
|
|
51
53
|
var GeminiProviderOptionsSchema = z.object({
|
|
52
54
|
candidate_count: z.number().int().positive().optional(),
|
|
53
55
|
top_k: z.number().int().min(0).optional(),
|
|
54
56
|
seed: z.number().int().optional(),
|
|
55
57
|
response_schema: z.record(z.unknown()).optional(),
|
|
58
|
+
response_json_schema: z.record(z.unknown()).optional(),
|
|
56
59
|
response_modalities: z.array(z.string()).optional(),
|
|
57
60
|
thinking_budget_tokens: z.number().int().positive().optional()
|
|
58
61
|
});
|
|
62
|
+
var OpenRouterProviderOptionsSchema = z.object({
|
|
63
|
+
provider: z.record(z.unknown()).optional(),
|
|
64
|
+
transforms: z.array(z.string()).optional(),
|
|
65
|
+
plugins: z.array(z.record(z.unknown())).optional(),
|
|
66
|
+
models: z.array(z.string()).optional()
|
|
67
|
+
});
|
|
59
68
|
var ProviderOptionsSchema = z.object({
|
|
60
69
|
anthropic: AnthropicProviderOptionsSchema.optional(),
|
|
61
|
-
gemini: GeminiProviderOptionsSchema.optional()
|
|
70
|
+
gemini: GeminiProviderOptionsSchema.optional(),
|
|
71
|
+
openrouter: OpenRouterProviderOptionsSchema.optional()
|
|
72
|
+
});
|
|
73
|
+
var RawProviderBodySchema = z.object({
|
|
74
|
+
openai: z.record(z.unknown()).optional(),
|
|
75
|
+
"openai-responses": z.record(z.unknown()).optional(),
|
|
76
|
+
openai_responses: z.record(z.unknown()).optional(),
|
|
77
|
+
anthropic: z.record(z.unknown()).optional(),
|
|
78
|
+
gemini: z.record(z.unknown()).optional(),
|
|
79
|
+
google: z.record(z.unknown()).optional(),
|
|
80
|
+
openrouter: z.record(z.unknown()).optional()
|
|
62
81
|
});
|
|
63
82
|
var OpenAICacheSchema = z.object({
|
|
64
83
|
prompt_cache_key: z.string().min(1).optional(),
|
|
@@ -131,6 +150,7 @@ var PromptAssetOverridesSchema = z.object({
|
|
|
131
150
|
sampling: SamplingSchema.optional(),
|
|
132
151
|
response: ResponseSchema.optional(),
|
|
133
152
|
cache: CacheSchema.optional(),
|
|
153
|
+
raw: RawProviderBodySchema.optional(),
|
|
134
154
|
tools: z.array(ToolRefSchema).optional(),
|
|
135
155
|
provider_options: ProviderOptionsSchema.optional()
|
|
136
156
|
});
|
|
@@ -163,6 +183,7 @@ var PromptAssetSchema = z.object({
|
|
|
163
183
|
sampling: SamplingSchema.optional(),
|
|
164
184
|
response: ResponseSchema.optional(),
|
|
165
185
|
cache: CacheSchema.optional(),
|
|
186
|
+
raw: RawProviderBodySchema.optional(),
|
|
166
187
|
tools: z.array(ToolRefSchema).optional(),
|
|
167
188
|
provider_options: ProviderOptionsSchema.optional(),
|
|
168
189
|
mcp: MCPSchema.optional(),
|
|
@@ -176,6 +197,123 @@ var PromptAssetSchema = z.object({
|
|
|
176
197
|
source: SourceSchema.optional()
|
|
177
198
|
});
|
|
178
199
|
|
|
200
|
+
// src/parser/frontmatter-guard.ts
|
|
201
|
+
var FRONT_MATTER_DELIMITER = /^---[ \t]*$/;
|
|
202
|
+
var REGEX_FIELD = /^(?<indent>\s*)(?<field>allow_regex|deny_regex)\s*:\s*(?<value>.*)$/;
|
|
203
|
+
var PATTERN_FIELD = /^(?<indent>\s*)pattern\s*:\s*(?<value>.*)$/;
|
|
204
|
+
var INLINE_PATTERN_FIELD = /(?:^|[{,]\s*)pattern\s*:\s*(?<value>"(?:\\.|[^"\\])*")/;
|
|
205
|
+
function assertRegexFrontMatterQuoting(content, filePath) {
|
|
206
|
+
const frontMatter = extractFrontMatter(content);
|
|
207
|
+
if (!frontMatter) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
let pendingRegex;
|
|
211
|
+
for (const [index, line] of frontMatter.lines.entries()) {
|
|
212
|
+
const lineNumber = frontMatter.startLine + index;
|
|
213
|
+
const regexMatch = line.match(REGEX_FIELD);
|
|
214
|
+
if (regexMatch?.groups) {
|
|
215
|
+
const field = regexMatch.groups.field;
|
|
216
|
+
const indent2 = regexMatch.groups.indent.length;
|
|
217
|
+
const value = regexMatch.groups.value.trim();
|
|
218
|
+
pendingRegex = value === "" ? { field, indent: indent2 } : void 0;
|
|
219
|
+
assertNoUnescapedBackslashInDoubleQuotedRegex(value, field, filePath, lineNumber);
|
|
220
|
+
assertNoUnescapedBackslashInInlinePattern(value, field, filePath, lineNumber);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (!pendingRegex) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (line.trim() === "" || line.trimStart().startsWith("#")) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
230
|
+
if (indent <= pendingRegex.indent) {
|
|
231
|
+
pendingRegex = void 0;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const patternMatch = line.match(PATTERN_FIELD);
|
|
235
|
+
if (!patternMatch?.groups) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
assertNoUnescapedBackslashInDoubleQuotedRegex(
|
|
239
|
+
patternMatch.groups.value.trim(),
|
|
240
|
+
`${pendingRegex.field}.pattern`,
|
|
241
|
+
filePath,
|
|
242
|
+
lineNumber
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function assertNoUnescapedBackslashInInlinePattern(value, field, filePath, lineNumber) {
|
|
247
|
+
const match = value.match(INLINE_PATTERN_FIELD);
|
|
248
|
+
const pattern = match?.groups?.value;
|
|
249
|
+
if (!pattern) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
assertNoUnescapedBackslashInDoubleQuotedRegex(pattern, `${field}.pattern`, filePath, lineNumber);
|
|
253
|
+
}
|
|
254
|
+
function extractFrontMatter(content) {
|
|
255
|
+
const lines = content.split(/\r?\n/);
|
|
256
|
+
if (!FRONT_MATTER_DELIMITER.test(lines[0] ?? "")) {
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
260
|
+
if (FRONT_MATTER_DELIMITER.test(lines[index] ?? "")) {
|
|
261
|
+
return {
|
|
262
|
+
lines: lines.slice(1, index),
|
|
263
|
+
startLine: 2
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
function assertNoUnescapedBackslashInDoubleQuotedRegex(value, field, filePath, lineNumber) {
|
|
270
|
+
if (!value.startsWith('"')) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const quoted = readDoubleQuotedScalar(value);
|
|
274
|
+
if (!quoted) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (!hasUnescapedBackslash(quoted)) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const location = filePath ? `${filePath}:${lineNumber}` : `line ${lineNumber}`;
|
|
281
|
+
throw new Error(
|
|
282
|
+
`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.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
function readDoubleQuotedScalar(value) {
|
|
286
|
+
let result = "";
|
|
287
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
288
|
+
const char = value[index];
|
|
289
|
+
if (char === '"') {
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
result += char;
|
|
293
|
+
if (char === "\\" && index + 1 < value.length) {
|
|
294
|
+
index += 1;
|
|
295
|
+
result += value[index];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
function hasUnescapedBackslash(value) {
|
|
301
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
302
|
+
if (value[index] !== "\\") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
let count = 1;
|
|
306
|
+
while (value[index + count] === "\\") {
|
|
307
|
+
count += 1;
|
|
308
|
+
}
|
|
309
|
+
if (count % 2 === 1) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
index += count - 1;
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
179
317
|
// src/parser/sections.ts
|
|
180
318
|
var SECTION_MAP = {
|
|
181
319
|
"system instructions": "system_instructions",
|
|
@@ -216,6 +354,7 @@ function extractSections(body) {
|
|
|
216
354
|
|
|
217
355
|
// src/parser/parser.ts
|
|
218
356
|
function parsePrompt(content, filePath) {
|
|
357
|
+
assertRegexFrontMatterQuoting(content, filePath);
|
|
219
358
|
const { data: frontMatter, content: body } = matter(content);
|
|
220
359
|
const sections = extractSections(body);
|
|
221
360
|
const raw = {
|
|
@@ -542,8 +681,21 @@ var KNOWN_FRONT_MATTER_KEYS = /* @__PURE__ */ new Set([
|
|
|
542
681
|
"environments",
|
|
543
682
|
"tiers",
|
|
544
683
|
"metadata",
|
|
545
|
-
"cache"
|
|
684
|
+
"cache",
|
|
685
|
+
"provider_options",
|
|
686
|
+
"raw"
|
|
546
687
|
]);
|
|
688
|
+
var RISKY_UNBOUNDED_INPUT_NAMES = [
|
|
689
|
+
"message",
|
|
690
|
+
"prompt",
|
|
691
|
+
"history",
|
|
692
|
+
"transcript",
|
|
693
|
+
"document",
|
|
694
|
+
"content",
|
|
695
|
+
"input",
|
|
696
|
+
"body",
|
|
697
|
+
"context"
|
|
698
|
+
];
|
|
547
699
|
function validateAsset(asset, frontMatterKeys, filePath) {
|
|
548
700
|
const errors = [];
|
|
549
701
|
const warnings = [];
|
|
@@ -614,7 +766,32 @@ function validateAsset(asset, frontMatterKeys, filePath) {
|
|
|
614
766
|
});
|
|
615
767
|
}
|
|
616
768
|
}
|
|
769
|
+
if (usedVars.size > 0 && (!asset.context?.inputs || asset.context.inputs.length === 0)) {
|
|
770
|
+
warnings.push({
|
|
771
|
+
code: "POK046",
|
|
772
|
+
message: `Template uses ${usedVars.size === 1 ? "a variable" : "variables"} but context.inputs is not declared.`,
|
|
773
|
+
filePath,
|
|
774
|
+
suggestion: "Declare context.inputs to enable input policy validation."
|
|
775
|
+
});
|
|
776
|
+
}
|
|
617
777
|
for (const input of getContextInputs(asset)) {
|
|
778
|
+
const lowerName = input.name.toLowerCase();
|
|
779
|
+
if (input.max_size === void 0 && RISKY_UNBOUNDED_INPUT_NAMES.some((needle) => lowerName.includes(needle))) {
|
|
780
|
+
warnings.push({
|
|
781
|
+
code: "POK040",
|
|
782
|
+
message: `Context input "${input.name}" has no max_size and appears unbounded.`,
|
|
783
|
+
filePath,
|
|
784
|
+
suggestion: "Add max_size to constrain prompt payload growth."
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (input.allow_regex === void 0 && input.deny_regex === void 0 && input.non_empty === void 0 && input.reject_secrets === void 0) {
|
|
788
|
+
warnings.push({
|
|
789
|
+
code: "POK041",
|
|
790
|
+
message: `Context input "${input.name}" has no input hardening validators.`,
|
|
791
|
+
filePath,
|
|
792
|
+
suggestion: "Consider non_empty/reject_secrets and allow/deny regex validators."
|
|
793
|
+
});
|
|
794
|
+
}
|
|
618
795
|
if (input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
|
|
619
796
|
warnings.push({
|
|
620
797
|
code: "POK014",
|
|
@@ -642,6 +819,91 @@ function validateAsset(asset, frontMatterKeys, filePath) {
|
|
|
642
819
|
}
|
|
643
820
|
}
|
|
644
821
|
}
|
|
822
|
+
if (asset.provider) {
|
|
823
|
+
let providerCache;
|
|
824
|
+
let cacheSuggestionField;
|
|
825
|
+
switch (asset.provider) {
|
|
826
|
+
case "openai":
|
|
827
|
+
providerCache = asset.cache?.openai;
|
|
828
|
+
cacheSuggestionField = "cache.openai";
|
|
829
|
+
break;
|
|
830
|
+
case "anthropic":
|
|
831
|
+
providerCache = asset.cache?.anthropic;
|
|
832
|
+
cacheSuggestionField = "cache.anthropic";
|
|
833
|
+
break;
|
|
834
|
+
case "gemini":
|
|
835
|
+
case "google":
|
|
836
|
+
providerCache = asset.cache?.gemini ?? asset.cache?.google;
|
|
837
|
+
cacheSuggestionField = "cache.gemini";
|
|
838
|
+
break;
|
|
839
|
+
default:
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
if (cacheSuggestionField && providerCache === void 0) {
|
|
843
|
+
warnings.push({
|
|
844
|
+
code: "POK042",
|
|
845
|
+
message: `Provider "${asset.provider}" has no provider-specific cache settings.`,
|
|
846
|
+
filePath,
|
|
847
|
+
suggestion: `Consider configuring ${cacheSuggestionField} for better cache-hit behavior.`
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
if (!asset.model) {
|
|
851
|
+
warnings.push({
|
|
852
|
+
code: "POK044",
|
|
853
|
+
message: `Provider "${asset.provider}" is configured without a model.`,
|
|
854
|
+
filePath,
|
|
855
|
+
suggestion: "Set model in prompt or defaults to avoid adapter-time errors."
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (asset.cache?.gemini?.cached_content && asset.cache.google?.cached_content && asset.cache.gemini.cached_content !== asset.cache.google.cached_content) {
|
|
860
|
+
warnings.push({
|
|
861
|
+
code: "POK043",
|
|
862
|
+
message: "cache.gemini.cached_content and cache.google.cached_content are both set to different values.",
|
|
863
|
+
filePath,
|
|
864
|
+
suggestion: "Use one canonical value; Gemini prefers cache.gemini.cached_content."
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
for (const [envName, overrides] of Object.entries(asset.environments ?? {})) {
|
|
868
|
+
if (asset.cache && (!isRecord(overrides) || !overrides.cache)) {
|
|
869
|
+
warnings.push({
|
|
870
|
+
code: "POK045",
|
|
871
|
+
message: `Environment "${envName}" does not override cache while prompt-level cache is defined.`,
|
|
872
|
+
filePath,
|
|
873
|
+
suggestion: "Confirm cache strategy is intentionally shared across environments."
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
for (const [tierName, overrides] of Object.entries(asset.tiers ?? {})) {
|
|
878
|
+
if (asset.cache && (!isRecord(overrides) || !overrides.cache)) {
|
|
879
|
+
warnings.push({
|
|
880
|
+
code: "POK045",
|
|
881
|
+
message: `Tier "${tierName}" does not override cache while prompt-level cache is defined.`,
|
|
882
|
+
filePath,
|
|
883
|
+
suggestion: "Confirm cache strategy is intentionally shared across tiers."
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (const tool of asset.tools ?? []) {
|
|
888
|
+
if (isRecord(tool)) {
|
|
889
|
+
if (!tool.description) {
|
|
890
|
+
warnings.push({
|
|
891
|
+
code: "POK047",
|
|
892
|
+
message: `Inline tool "${tool.name}" is missing a description.`,
|
|
893
|
+
filePath,
|
|
894
|
+
suggestion: "Add description to improve model tool-selection quality."
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
if (!tool.input_schema) {
|
|
898
|
+
warnings.push({
|
|
899
|
+
code: "POK047",
|
|
900
|
+
message: `Inline tool "${tool.name}" is missing input_schema.`,
|
|
901
|
+
filePath,
|
|
902
|
+
suggestion: "Add input_schema so tool inputs are strongly typed."
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
645
907
|
return {
|
|
646
908
|
valid: errors.length === 0,
|
|
647
909
|
errors,
|
|
@@ -678,6 +940,9 @@ function findClosestMatch(input, candidates) {
|
|
|
678
940
|
}
|
|
679
941
|
return best;
|
|
680
942
|
}
|
|
943
|
+
function isRecord(value) {
|
|
944
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
945
|
+
}
|
|
681
946
|
|
|
682
947
|
// src/prompt-resolution.ts
|
|
683
948
|
import { readFile as readFile3 } from "fs/promises";
|
|
@@ -758,6 +1023,29 @@ function mergeOverride(base, override) {
|
|
|
758
1023
|
if (override.response !== void 0) {
|
|
759
1024
|
result.response = { ...result.response, ...override.response };
|
|
760
1025
|
}
|
|
1026
|
+
if (override.cache !== void 0) {
|
|
1027
|
+
result.cache = {
|
|
1028
|
+
...result.cache,
|
|
1029
|
+
...override.cache,
|
|
1030
|
+
openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
|
|
1031
|
+
anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
|
|
1032
|
+
gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
|
|
1033
|
+
google: mergeRecordBlock(result.cache?.google, override.cache.google)
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
if (override.raw !== void 0) {
|
|
1037
|
+
result.raw = {
|
|
1038
|
+
...result.raw,
|
|
1039
|
+
...override.raw,
|
|
1040
|
+
openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
|
|
1041
|
+
"openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
|
|
1042
|
+
openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
|
|
1043
|
+
anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
|
|
1044
|
+
gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
|
|
1045
|
+
google: mergeRecordBlock(result.raw?.google, override.raw.google),
|
|
1046
|
+
openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter)
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
761
1049
|
if (override.provider_options !== void 0) {
|
|
762
1050
|
result.provider_options = {
|
|
763
1051
|
...result.provider_options,
|
|
@@ -769,11 +1057,19 @@ function mergeOverride(base, override) {
|
|
|
769
1057
|
gemini: {
|
|
770
1058
|
...result.provider_options?.gemini,
|
|
771
1059
|
...override.provider_options.gemini
|
|
1060
|
+
},
|
|
1061
|
+
openrouter: {
|
|
1062
|
+
...result.provider_options?.openrouter,
|
|
1063
|
+
...override.provider_options.openrouter
|
|
772
1064
|
}
|
|
773
1065
|
};
|
|
774
1066
|
}
|
|
775
1067
|
return result;
|
|
776
1068
|
}
|
|
1069
|
+
function mergeRecordBlock(base, override) {
|
|
1070
|
+
if (override === void 0) return base;
|
|
1071
|
+
return { ...base, ...override };
|
|
1072
|
+
}
|
|
777
1073
|
|
|
778
1074
|
// src/prompt-resolution.ts
|
|
779
1075
|
var DEFAULT_PROMPTS_DIR = "./prompts";
|
|
@@ -1157,7 +1453,7 @@ async function inspect(args) {
|
|
|
1157
1453
|
|
|
1158
1454
|
// src/cli/commands/init.ts
|
|
1159
1455
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1160
|
-
import { join as join5, dirname as dirname5 } from "path";
|
|
1456
|
+
import { join as join5, dirname as dirname5, relative as relative2 } from "path";
|
|
1161
1457
|
import { existsSync as existsSync4, readFileSync } from "fs";
|
|
1162
1458
|
var HELP5 = `
|
|
1163
1459
|
promptopskit init [dir]
|
|
@@ -1229,10 +1525,14 @@ var TEST_SIDECAR = `cases:
|
|
|
1229
1525
|
variables:
|
|
1230
1526
|
name: "World"
|
|
1231
1527
|
app_context: "Welcome screen"
|
|
1528
|
+
response:
|
|
1529
|
+
message: "Hello, World! How can I help you today?"
|
|
1232
1530
|
- name: named-greeting
|
|
1233
1531
|
variables:
|
|
1234
1532
|
name: "Alice"
|
|
1235
1533
|
app_context: "Settings page"
|
|
1534
|
+
response:
|
|
1535
|
+
message: "Hello, Alice! How can I help you today?"
|
|
1236
1536
|
`;
|
|
1237
1537
|
var EXAMPLE_USAGE = `// Example: render the hello prompt and send it to OpenAI
|
|
1238
1538
|
// Full docs: https://promptopskit.com/docs/index.html#/
|
|
@@ -1304,12 +1604,16 @@ async function init(args) {
|
|
|
1304
1604
|
return;
|
|
1305
1605
|
}
|
|
1306
1606
|
const dir = args.find((a) => !a.startsWith("--")) ?? "./prompts";
|
|
1607
|
+
const testFilePath = join5(dirname5(dir), "tests", "hello.prompt.test.mjs");
|
|
1608
|
+
const promptsDirFromTest = relative2(dirname5(testFilePath), dir) || ".";
|
|
1609
|
+
const helloPromptTest = createHelloPromptTest(promptsDirFromTest);
|
|
1307
1610
|
const files = [
|
|
1308
1611
|
{ path: join5(dir, "defaults.md"), content: DEFAULTS },
|
|
1309
1612
|
{ path: join5(dir, "hello.md"), content: HELLO_PROMPT },
|
|
1310
1613
|
{ path: join5(dir, "hello.test.yaml"), content: TEST_SIDECAR },
|
|
1311
1614
|
{ path: join5(dir, "shared", "tone.md"), content: TONE_INCLUDE },
|
|
1312
|
-
{ path: join5(dir, "example-usage.ts"), content: EXAMPLE_USAGE }
|
|
1615
|
+
{ path: join5(dir, "example-usage.ts"), content: EXAMPLE_USAGE },
|
|
1616
|
+
{ path: testFilePath, content: helloPromptTest }
|
|
1313
1617
|
];
|
|
1314
1618
|
let created = 0;
|
|
1315
1619
|
let skipped = 0;
|
|
@@ -1334,10 +1638,57 @@ async function init(args) {
|
|
|
1334
1638
|
console.log(`Tip: Add to your package.json scripts:`);
|
|
1335
1639
|
console.log(` "build:prompts": "promptopskit compile ${dir}"`);
|
|
1336
1640
|
}
|
|
1641
|
+
if (!pkg.scripts?.test) {
|
|
1642
|
+
console.log();
|
|
1643
|
+
console.log(`Tip: Add a test script to run the generated prompt test:`);
|
|
1644
|
+
console.log(` "test": "node --test tests/*.test.mjs"`);
|
|
1645
|
+
}
|
|
1337
1646
|
} catch {
|
|
1338
1647
|
}
|
|
1339
1648
|
}
|
|
1340
1649
|
}
|
|
1650
|
+
function createHelloPromptTest(promptsDirFromTest) {
|
|
1651
|
+
return `import assert from 'node:assert/strict';
|
|
1652
|
+
import { dirname, resolve } from 'node:path';
|
|
1653
|
+
import test from 'node:test';
|
|
1654
|
+
import { fileURLToPath } from 'node:url';
|
|
1655
|
+
import { createPromptOpsKit } from 'promptopskit';
|
|
1656
|
+
import {
|
|
1657
|
+
getHardcodedPromptResponse,
|
|
1658
|
+
loadPromptTestSidecar,
|
|
1659
|
+
renderPromptTestCase,
|
|
1660
|
+
} from 'promptopskit/testing';
|
|
1661
|
+
|
|
1662
|
+
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
1663
|
+
const promptsDir = resolve(testDir, ${JSON.stringify(promptsDirFromTest)});
|
|
1664
|
+
|
|
1665
|
+
test('hello prompt renders every sidecar case', async () => {
|
|
1666
|
+
const kit = createPromptOpsKit({ sourceDir: promptsDir, cache: false });
|
|
1667
|
+
const sidecar = await loadPromptTestSidecar(resolve(promptsDir, 'hello.test.yaml'));
|
|
1668
|
+
|
|
1669
|
+
for (const testCase of sidecar.cases) {
|
|
1670
|
+
const { rendered } = await renderPromptTestCase(kit, {
|
|
1671
|
+
sidecar,
|
|
1672
|
+
caseName: testCase.name,
|
|
1673
|
+
path: 'hello',
|
|
1674
|
+
provider: 'openai',
|
|
1675
|
+
environment: 'dev',
|
|
1676
|
+
strict: true,
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
assert.ok(rendered.request?.body?.messages);
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
test('hello prompt can return a deterministic response without calling a model', async () => {
|
|
1684
|
+
const sidecar = await loadPromptTestSidecar(resolve(promptsDir, 'hello.test.yaml'));
|
|
1685
|
+
|
|
1686
|
+
assert.deepEqual(getHardcodedPromptResponse(sidecar, 'basic-greeting'), {
|
|
1687
|
+
message: 'Hello, World! How can I help you today?',
|
|
1688
|
+
});
|
|
1689
|
+
});
|
|
1690
|
+
`;
|
|
1691
|
+
}
|
|
1341
1692
|
|
|
1342
1693
|
// src/cli/commands/skill.ts
|
|
1343
1694
|
import { writeFile as writeFile3, readFile as readFile5, mkdir as mkdir3 } from "fs/promises";
|