promptopskit 0.3.8 → 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 +97 -10
- package/SKILL.md +69 -6
- package/dist/chunk-F6UJ73Y6.js +113 -0
- package/dist/chunk-F6UJ73Y6.js.map +1 -0
- package/dist/chunk-HLNVKKMS.js +145 -0
- package/dist/chunk-HLNVKKMS.js.map +1 -0
- package/dist/{chunk-5OYWNRNC.js → chunk-JBLUPKY4.js} +191 -5
- package/dist/chunk-JBLUPKY4.js.map +1 -0
- package/dist/{chunk-UJU5XERR.js → chunk-L2V7LDKD.js} +25 -4
- package/dist/chunk-L2V7LDKD.js.map +1 -0
- package/dist/{chunk-E6NPZOJL.js → chunk-RY5DVTLV.js} +29 -4
- 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-X73VG645.js → chunk-XNNDGDPI.js} +64 -3
- package/dist/chunk-XNNDGDPI.js.map +1 -0
- package/dist/cli/index.js +423 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +655 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +172 -11
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.cjs +306 -10
- 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 +274 -6
- 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 +1245 -0
- package/dist/providers/openai-responses.cjs.map +1 -0
- package/dist/providers/openai-responses.d.cts +11 -0
- package/dist/providers/openai-responses.d.ts +11 -0
- package/dist/providers/openai-responses.js +9 -0
- package/dist/providers/openai-responses.js.map +1 -0
- package/dist/providers/openai.cjs +270 -6
- 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 +305 -9
- 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-B9A1zLRk.d.cts +2403 -0
- package/dist/schema-B9A1zLRk.d.ts +2403 -0
- package/dist/testing.cjs +264 -6
- 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-fBvo_d4k.d.ts → types-DjGAwJxC.d.ts} +13 -1
- package/dist/{types-C6RG0Si0.d.cts → types-DpbzzZO2.d.cts} +13 -1
- package/dist/usagetap/index.d.cts +2 -2
- package/dist/usagetap/index.d.ts +2 -2
- package/package.json +12 -2
- package/dist/chunk-5OYWNRNC.js.map +0 -1
- package/dist/chunk-BBVVA27J.js +0 -92
- package/dist/chunk-BBVVA27J.js.map +0 -1
- package/dist/chunk-E2NXJLS6.js +0 -26
- package/dist/chunk-E2NXJLS6.js.map +0 -1
- package/dist/chunk-E6NPZOJL.js.map +0 -1
- package/dist/chunk-UJU5XERR.js.map +0 -1
- package/dist/chunk-X73VG645.js.map +0 -1
- package/dist/schema-DwzYbZba.d.cts +0 -859
- package/dist/schema-DwzYbZba.d.ts +0 -859
package/dist/cli/index.js
CHANGED
|
@@ -39,7 +39,66 @@ var SamplingSchema = z.object({
|
|
|
39
39
|
});
|
|
40
40
|
var ResponseSchema = z.object({
|
|
41
41
|
format: z.enum(["text", "json", "markdown"]).optional(),
|
|
42
|
-
stream: z.boolean().optional()
|
|
42
|
+
stream: z.boolean().optional(),
|
|
43
|
+
schema: z.record(z.unknown()).optional(),
|
|
44
|
+
schema_name: z.string().optional(),
|
|
45
|
+
schema_description: z.string().optional(),
|
|
46
|
+
schema_strict: z.boolean().optional()
|
|
47
|
+
});
|
|
48
|
+
var AnthropicProviderOptionsSchema = z.object({
|
|
49
|
+
top_k: z.number().int().min(0).optional(),
|
|
50
|
+
tool_choice: z.record(z.unknown()).optional(),
|
|
51
|
+
output_config: z.record(z.unknown()).optional()
|
|
52
|
+
});
|
|
53
|
+
var GeminiProviderOptionsSchema = z.object({
|
|
54
|
+
candidate_count: z.number().int().positive().optional(),
|
|
55
|
+
top_k: z.number().int().min(0).optional(),
|
|
56
|
+
seed: z.number().int().optional(),
|
|
57
|
+
response_schema: z.record(z.unknown()).optional(),
|
|
58
|
+
response_json_schema: z.record(z.unknown()).optional(),
|
|
59
|
+
response_modalities: z.array(z.string()).optional(),
|
|
60
|
+
thinking_budget_tokens: z.number().int().positive().optional()
|
|
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
|
+
});
|
|
68
|
+
var ProviderOptionsSchema = z.object({
|
|
69
|
+
anthropic: AnthropicProviderOptionsSchema.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()
|
|
81
|
+
});
|
|
82
|
+
var OpenAICacheSchema = z.object({
|
|
83
|
+
prompt_cache_key: z.string().min(1).optional(),
|
|
84
|
+
retention: z.enum(["in_memory", "24h"]).optional()
|
|
85
|
+
});
|
|
86
|
+
var AnthropicCacheSchema = z.object({
|
|
87
|
+
mode: z.enum(["automatic", "explicit"]).optional(),
|
|
88
|
+
type: z.literal("ephemeral").optional(),
|
|
89
|
+
ttl: z.enum(["5m", "1h"]).optional(),
|
|
90
|
+
cache_system_instructions: z.boolean().optional(),
|
|
91
|
+
cache_tools: z.boolean().optional(),
|
|
92
|
+
cache_prompt_template: z.boolean().optional()
|
|
93
|
+
});
|
|
94
|
+
var GeminiCacheSchema = z.object({
|
|
95
|
+
cached_content: z.string().min(1).optional()
|
|
96
|
+
});
|
|
97
|
+
var CacheSchema = z.object({
|
|
98
|
+
openai: OpenAICacheSchema.optional(),
|
|
99
|
+
anthropic: AnthropicCacheSchema.optional(),
|
|
100
|
+
gemini: GeminiCacheSchema.optional(),
|
|
101
|
+
google: GeminiCacheSchema.optional()
|
|
43
102
|
});
|
|
44
103
|
var HistorySchema = z.object({
|
|
45
104
|
max_items: z.number().int().positive().optional()
|
|
@@ -90,7 +149,10 @@ var PromptAssetOverridesSchema = z.object({
|
|
|
90
149
|
reasoning: ReasoningSchema.optional(),
|
|
91
150
|
sampling: SamplingSchema.optional(),
|
|
92
151
|
response: ResponseSchema.optional(),
|
|
93
|
-
|
|
152
|
+
cache: CacheSchema.optional(),
|
|
153
|
+
raw: RawProviderBodySchema.optional(),
|
|
154
|
+
tools: z.array(ToolRefSchema).optional(),
|
|
155
|
+
provider_options: ProviderOptionsSchema.optional()
|
|
94
156
|
});
|
|
95
157
|
var SourceSchema = z.object({
|
|
96
158
|
file_path: z.string().optional(),
|
|
@@ -102,8 +164,9 @@ var SectionsSchema = z.object({
|
|
|
102
164
|
notes: z.string().optional()
|
|
103
165
|
});
|
|
104
166
|
var PromptDefaultsSchema = z.object({
|
|
105
|
-
provider: z.enum(["openai", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
|
|
167
|
+
provider: z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
|
|
106
168
|
model: z.string().optional(),
|
|
169
|
+
cache: CacheSchema.optional(),
|
|
107
170
|
metadata: MetadataSchema.optional(),
|
|
108
171
|
sections: z.object({
|
|
109
172
|
system_instructions: z.string().optional()
|
|
@@ -113,13 +176,16 @@ var PromptAssetSchema = z.object({
|
|
|
113
176
|
id: z.string(),
|
|
114
177
|
schema_version: z.number().int().positive().default(1),
|
|
115
178
|
description: z.string().optional(),
|
|
116
|
-
provider: z.enum(["openai", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
|
|
179
|
+
provider: z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
|
|
117
180
|
model: z.string().optional(),
|
|
118
181
|
fallback_models: z.array(z.string()).optional(),
|
|
119
182
|
reasoning: ReasoningSchema.optional(),
|
|
120
183
|
sampling: SamplingSchema.optional(),
|
|
121
184
|
response: ResponseSchema.optional(),
|
|
185
|
+
cache: CacheSchema.optional(),
|
|
186
|
+
raw: RawProviderBodySchema.optional(),
|
|
122
187
|
tools: z.array(ToolRefSchema).optional(),
|
|
188
|
+
provider_options: ProviderOptionsSchema.optional(),
|
|
123
189
|
mcp: MCPSchema.optional(),
|
|
124
190
|
context: ContextSchema.optional(),
|
|
125
191
|
includes: z.array(z.string()).optional(),
|
|
@@ -131,6 +197,123 @@ var PromptAssetSchema = z.object({
|
|
|
131
197
|
source: SourceSchema.optional()
|
|
132
198
|
});
|
|
133
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
|
+
|
|
134
317
|
// src/parser/sections.ts
|
|
135
318
|
var SECTION_MAP = {
|
|
136
319
|
"system instructions": "system_instructions",
|
|
@@ -171,6 +354,7 @@ function extractSections(body) {
|
|
|
171
354
|
|
|
172
355
|
// src/parser/parser.ts
|
|
173
356
|
function parsePrompt(content, filePath) {
|
|
357
|
+
assertRegexFrontMatterQuoting(content, filePath);
|
|
174
358
|
const { data: frontMatter, content: body } = matter(content);
|
|
175
359
|
const sections = extractSections(body);
|
|
176
360
|
const raw = {
|
|
@@ -496,8 +680,22 @@ var KNOWN_FRONT_MATTER_KEYS = /* @__PURE__ */ new Set([
|
|
|
496
680
|
"includes",
|
|
497
681
|
"environments",
|
|
498
682
|
"tiers",
|
|
499
|
-
"metadata"
|
|
683
|
+
"metadata",
|
|
684
|
+
"cache",
|
|
685
|
+
"provider_options",
|
|
686
|
+
"raw"
|
|
500
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
|
+
];
|
|
501
699
|
function validateAsset(asset, frontMatterKeys, filePath) {
|
|
502
700
|
const errors = [];
|
|
503
701
|
const warnings = [];
|
|
@@ -568,7 +766,32 @@ function validateAsset(asset, frontMatterKeys, filePath) {
|
|
|
568
766
|
});
|
|
569
767
|
}
|
|
570
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
|
+
}
|
|
571
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
|
+
}
|
|
572
795
|
if (input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
|
|
573
796
|
warnings.push({
|
|
574
797
|
code: "POK014",
|
|
@@ -596,6 +819,91 @@ function validateAsset(asset, frontMatterKeys, filePath) {
|
|
|
596
819
|
}
|
|
597
820
|
}
|
|
598
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
|
+
}
|
|
599
907
|
return {
|
|
600
908
|
valid: errors.length === 0,
|
|
601
909
|
errors,
|
|
@@ -632,6 +940,9 @@ function findClosestMatch(input, candidates) {
|
|
|
632
940
|
}
|
|
633
941
|
return best;
|
|
634
942
|
}
|
|
943
|
+
function isRecord(value) {
|
|
944
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
945
|
+
}
|
|
635
946
|
|
|
636
947
|
// src/prompt-resolution.ts
|
|
637
948
|
import { readFile as readFile3 } from "fs/promises";
|
|
@@ -712,8 +1023,53 @@ function mergeOverride(base, override) {
|
|
|
712
1023
|
if (override.response !== void 0) {
|
|
713
1024
|
result.response = { ...result.response, ...override.response };
|
|
714
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
|
+
}
|
|
1049
|
+
if (override.provider_options !== void 0) {
|
|
1050
|
+
result.provider_options = {
|
|
1051
|
+
...result.provider_options,
|
|
1052
|
+
...override.provider_options,
|
|
1053
|
+
anthropic: {
|
|
1054
|
+
...result.provider_options?.anthropic,
|
|
1055
|
+
...override.provider_options.anthropic
|
|
1056
|
+
},
|
|
1057
|
+
gemini: {
|
|
1058
|
+
...result.provider_options?.gemini,
|
|
1059
|
+
...override.provider_options.gemini
|
|
1060
|
+
},
|
|
1061
|
+
openrouter: {
|
|
1062
|
+
...result.provider_options?.openrouter,
|
|
1063
|
+
...override.provider_options.openrouter
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
715
1067
|
return result;
|
|
716
1068
|
}
|
|
1069
|
+
function mergeRecordBlock(base, override) {
|
|
1070
|
+
if (override === void 0) return base;
|
|
1071
|
+
return { ...base, ...override };
|
|
1072
|
+
}
|
|
717
1073
|
|
|
718
1074
|
// src/prompt-resolution.ts
|
|
719
1075
|
var DEFAULT_PROMPTS_DIR = "./prompts";
|
|
@@ -1097,7 +1453,7 @@ async function inspect(args) {
|
|
|
1097
1453
|
|
|
1098
1454
|
// src/cli/commands/init.ts
|
|
1099
1455
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1100
|
-
import { join as join5, dirname as dirname5 } from "path";
|
|
1456
|
+
import { join as join5, dirname as dirname5, relative as relative2 } from "path";
|
|
1101
1457
|
import { existsSync as existsSync4, readFileSync } from "fs";
|
|
1102
1458
|
var HELP5 = `
|
|
1103
1459
|
promptopskit init [dir]
|
|
@@ -1118,6 +1474,11 @@ context:
|
|
|
1118
1474
|
- app_context
|
|
1119
1475
|
includes:
|
|
1120
1476
|
- ./shared/tone.md
|
|
1477
|
+
cache:
|
|
1478
|
+
openai:
|
|
1479
|
+
# Keep this stable across requests that share a long static prefix.
|
|
1480
|
+
prompt_cache_key: hello-v1
|
|
1481
|
+
retention: in_memory
|
|
1121
1482
|
reasoning:
|
|
1122
1483
|
effort: high
|
|
1123
1484
|
environments:
|
|
@@ -1164,10 +1525,14 @@ var TEST_SIDECAR = `cases:
|
|
|
1164
1525
|
variables:
|
|
1165
1526
|
name: "World"
|
|
1166
1527
|
app_context: "Welcome screen"
|
|
1528
|
+
response:
|
|
1529
|
+
message: "Hello, World! How can I help you today?"
|
|
1167
1530
|
- name: named-greeting
|
|
1168
1531
|
variables:
|
|
1169
1532
|
name: "Alice"
|
|
1170
1533
|
app_context: "Settings page"
|
|
1534
|
+
response:
|
|
1535
|
+
message: "Hello, Alice! How can I help you today?"
|
|
1171
1536
|
`;
|
|
1172
1537
|
var EXAMPLE_USAGE = `// Example: render the hello prompt and send it to OpenAI
|
|
1173
1538
|
// Full docs: https://promptopskit.com/docs/index.html#/
|
|
@@ -1239,12 +1604,16 @@ async function init(args) {
|
|
|
1239
1604
|
return;
|
|
1240
1605
|
}
|
|
1241
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);
|
|
1242
1610
|
const files = [
|
|
1243
1611
|
{ path: join5(dir, "defaults.md"), content: DEFAULTS },
|
|
1244
1612
|
{ path: join5(dir, "hello.md"), content: HELLO_PROMPT },
|
|
1245
1613
|
{ path: join5(dir, "hello.test.yaml"), content: TEST_SIDECAR },
|
|
1246
1614
|
{ path: join5(dir, "shared", "tone.md"), content: TONE_INCLUDE },
|
|
1247
|
-
{ path: join5(dir, "example-usage.ts"), content: EXAMPLE_USAGE }
|
|
1615
|
+
{ path: join5(dir, "example-usage.ts"), content: EXAMPLE_USAGE },
|
|
1616
|
+
{ path: testFilePath, content: helloPromptTest }
|
|
1248
1617
|
];
|
|
1249
1618
|
let created = 0;
|
|
1250
1619
|
let skipped = 0;
|
|
@@ -1269,10 +1638,57 @@ async function init(args) {
|
|
|
1269
1638
|
console.log(`Tip: Add to your package.json scripts:`);
|
|
1270
1639
|
console.log(` "build:prompts": "promptopskit compile ${dir}"`);
|
|
1271
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
|
+
}
|
|
1272
1646
|
} catch {
|
|
1273
1647
|
}
|
|
1274
1648
|
}
|
|
1275
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
|
+
}
|
|
1276
1692
|
|
|
1277
1693
|
// src/cli/commands/skill.ts
|
|
1278
1694
|
import { writeFile as writeFile3, readFile as readFile5, mkdir as mkdir3 } from "fs/promises";
|