promptopskit 0.4.2 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +105 -62
  2. package/SKILL.md +376 -16
  3. package/dist/{chunk-7MRMN3SW.js → chunk-55B7QQBK.js} +6 -4
  4. package/dist/chunk-55B7QQBK.js.map +1 -0
  5. package/dist/{chunk-HWEVRBKZ.js → chunk-AGTGIZMR.js} +6 -2
  6. package/dist/chunk-AGTGIZMR.js.map +1 -0
  7. package/dist/chunk-FNCIBZJF.js +130 -0
  8. package/dist/chunk-FNCIBZJF.js.map +1 -0
  9. package/dist/{chunk-XL76NHZR.js → chunk-GLT3YYWK.js} +6 -4
  10. package/dist/chunk-GLT3YYWK.js.map +1 -0
  11. package/dist/{chunk-5AAYDOPU.js → chunk-IPFD5Y3O.js} +4 -3
  12. package/dist/chunk-IPFD5Y3O.js.map +1 -0
  13. package/dist/{chunk-YI4LILPY.js → chunk-JR7252DG.js} +584 -520
  14. package/dist/chunk-JR7252DG.js.map +1 -0
  15. package/dist/{chunk-JKBWC5WJ.js → chunk-UY5OKMVP.js} +302 -21
  16. package/dist/chunk-UY5OKMVP.js.map +1 -0
  17. package/dist/{chunk-MI3M6KDD.js → chunk-VMLGUI4F.js} +6 -4
  18. package/dist/chunk-VMLGUI4F.js.map +1 -0
  19. package/dist/{chunk-Z4IPWEUU.js → chunk-XEZJZ3IG.js} +6 -4
  20. package/dist/chunk-XEZJZ3IG.js.map +1 -0
  21. package/dist/cli/index.js +435 -106
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/index.cjs +1159 -636
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +10 -9
  26. package/dist/index.d.ts +10 -9
  27. package/dist/index.js +79 -17
  28. package/dist/index.js.map +1 -1
  29. package/dist/providers/anthropic.cjs +875 -533
  30. package/dist/providers/anthropic.cjs.map +1 -1
  31. package/dist/providers/anthropic.d.cts +2 -2
  32. package/dist/providers/anthropic.d.ts +2 -2
  33. package/dist/providers/anthropic.js +3 -3
  34. package/dist/providers/gemini.cjs +875 -533
  35. package/dist/providers/gemini.cjs.map +1 -1
  36. package/dist/providers/gemini.d.cts +2 -2
  37. package/dist/providers/gemini.d.ts +2 -2
  38. package/dist/providers/gemini.js +3 -3
  39. package/dist/providers/llmasaservice.cjs +1735 -0
  40. package/dist/providers/llmasaservice.cjs.map +1 -0
  41. package/dist/providers/llmasaservice.d.cts +28 -0
  42. package/dist/providers/llmasaservice.d.ts +28 -0
  43. package/dist/providers/llmasaservice.js +20 -0
  44. package/dist/providers/llmasaservice.js.map +1 -0
  45. package/dist/providers/openai-responses.cjs +875 -533
  46. package/dist/providers/openai-responses.cjs.map +1 -1
  47. package/dist/providers/openai-responses.d.cts +2 -2
  48. package/dist/providers/openai-responses.d.ts +2 -2
  49. package/dist/providers/openai-responses.js +3 -3
  50. package/dist/providers/openai.cjs +875 -533
  51. package/dist/providers/openai.cjs.map +1 -1
  52. package/dist/providers/openai.d.cts +2 -2
  53. package/dist/providers/openai.d.ts +2 -2
  54. package/dist/providers/openai.js +3 -3
  55. package/dist/providers/openrouter.cjs +876 -533
  56. package/dist/providers/openrouter.cjs.map +1 -1
  57. package/dist/providers/openrouter.d.cts +2 -2
  58. package/dist/providers/openrouter.d.ts +2 -2
  59. package/dist/providers/openrouter.js +4 -4
  60. package/dist/{schema-B9A1zLRk.d.cts → schema-CuYsv8JA.d.cts} +853 -7
  61. package/dist/{schema-B9A1zLRk.d.ts → schema-CuYsv8JA.d.ts} +853 -7
  62. package/dist/testing.cjs +64 -9
  63. package/dist/testing.cjs.map +1 -1
  64. package/dist/testing.d.cts +1 -1
  65. package/dist/testing.d.ts +1 -1
  66. package/dist/testing.js +1 -1
  67. package/dist/{types-DjGAwJxC.d.ts → types-DpK0mTHL.d.ts} +18 -6
  68. package/dist/{types-DpbzzZO2.d.cts → types-UA_p9uJy.d.cts} +18 -6
  69. package/dist/usagetap/index.cjs +6 -1
  70. package/dist/usagetap/index.cjs.map +1 -1
  71. package/dist/usagetap/index.d.cts +4 -3
  72. package/dist/usagetap/index.d.ts +4 -3
  73. package/dist/usagetap/index.js +3 -1
  74. package/package.json +19 -2
  75. package/dist/chunk-5AAYDOPU.js.map +0 -1
  76. package/dist/chunk-7MRMN3SW.js.map +0 -1
  77. package/dist/chunk-HWEVRBKZ.js.map +0 -1
  78. package/dist/chunk-JKBWC5WJ.js.map +0 -1
  79. package/dist/chunk-MI3M6KDD.js.map +0 -1
  80. package/dist/chunk-XL76NHZR.js.map +0 -1
  81. package/dist/chunk-YI4LILPY.js.map +0 -1
  82. package/dist/chunk-Z4IPWEUU.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  loadPromptFile,
3
3
  parsePrompt
4
- } from "./chunk-JKBWC5WJ.js";
4
+ } from "./chunk-UY5OKMVP.js";
5
5
 
6
6
  // src/renderer/interpolate.ts
7
7
  var VARIABLE_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
@@ -9,12 +9,13 @@ var ESCAPED_OPEN = /\\\{\\\{/g;
9
9
  var ESCAPE_PLACEHOLDER = "\0ESCAPED_OPEN\0";
10
10
  function interpolate(template, variables, options = {}) {
11
11
  const { strict = false } = options;
12
+ const optionalVariables = new Set(options.optionalVariables ?? []);
12
13
  let result = template.replace(ESCAPED_OPEN, ESCAPE_PLACEHOLDER);
13
14
  result = result.replace(VARIABLE_RE, (match, name) => {
14
15
  if (name in variables) {
15
16
  return variables[name];
16
17
  }
17
- if (strict) {
18
+ if (strict && !optionalVariables.has(name)) {
18
19
  throw new Error(`Missing required variable: "${name}"`);
19
20
  }
20
21
  return match;
@@ -32,589 +33,650 @@ function extractVariables(template) {
32
33
  return [...vars];
33
34
  }
34
35
 
35
- // src/renderer/renderer.ts
36
- function renderSections(asset, options = {}) {
37
- const { variables = {}, strict = false } = options;
38
- const result = {};
39
- if (asset.sections.system_instructions) {
40
- result.system_instructions = interpolate(
41
- asset.sections.system_instructions,
42
- variables,
43
- { strict }
44
- );
45
- }
46
- if (asset.sections.prompt_template) {
47
- result.prompt_template = interpolate(
48
- asset.sections.prompt_template,
49
- variables,
50
- { strict }
51
- );
52
- }
53
- return result;
36
+ // src/context.ts
37
+ var textEncoder = new TextEncoder();
38
+ var REJECT_SECRETS_PATTERN = "(secret|api[_-]?key|password)";
39
+ var REJECT_SECRETS_FLAGS = "i";
40
+ function getContextInputs(asset) {
41
+ return (asset.context?.inputs ?? []).map(normalizeContextInput);
54
42
  }
55
-
56
- // src/overrides/apply-overrides.ts
57
- function applyOverrides(asset, options = {}) {
58
- let result = { ...asset };
59
- if (options.environment && result.environments?.[options.environment]) {
60
- result = mergeOverride(result, result.environments[options.environment]);
43
+ function normalizeContextInput(input) {
44
+ if (typeof input === "string") {
45
+ return { name: input };
61
46
  }
62
- if (options.tier && result.tiers?.[options.tier]) {
63
- result = mergeOverride(result, result.tiers[options.tier]);
47
+ return {
48
+ name: input.name,
49
+ optional: input.optional,
50
+ warnings: input.warnings,
51
+ max_size: input.max_size,
52
+ trim: input.trim,
53
+ allow_regex: normalizeContextRegex(input.allow_regex),
54
+ deny_regex: normalizeContextRegex(input.deny_regex),
55
+ non_empty: normalizeBuiltInValidator(input.non_empty),
56
+ reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
57
+ };
58
+ }
59
+ function areContextInputWarningsEnabled(input) {
60
+ return input.warnings !== false;
61
+ }
62
+ function normalizeContextRegex(value) {
63
+ if (value === void 0) {
64
+ return void 0;
64
65
  }
65
- if (options.runtime) {
66
- result = mergeOverride(result, options.runtime);
66
+ if (typeof value === "string") {
67
+ const literal = parseRegexLiteral(value);
68
+ if (value.startsWith("/") && !literal) {
69
+ return {
70
+ pattern: value,
71
+ flags: "",
72
+ raw: value,
73
+ invalidLiteral: true
74
+ };
75
+ }
76
+ return {
77
+ pattern: literal?.pattern ?? value,
78
+ flags: literal?.flags ?? "",
79
+ raw: value
80
+ };
67
81
  }
68
- return result;
82
+ return {
83
+ pattern: value.pattern,
84
+ flags: value.flags ?? "",
85
+ raw: JSON.stringify(value),
86
+ returnMessage: value.return_message
87
+ };
69
88
  }
70
- function mergeOverride(base, override) {
71
- const result = { ...base };
72
- if (override.model !== void 0) result.model = override.model;
73
- if (override.fallback_models !== void 0) result.fallback_models = override.fallback_models;
74
- if (override.tools !== void 0) result.tools = override.tools;
75
- if (override.reasoning !== void 0) {
76
- result.reasoning = { ...result.reasoning, ...override.reasoning };
89
+ function normalizeBuiltInValidator(value) {
90
+ if (value === void 0 || value === false) {
91
+ return void 0;
77
92
  }
78
- if (override.sampling !== void 0) {
79
- result.sampling = { ...result.sampling, ...override.sampling };
93
+ if (value === true) {
94
+ return {};
80
95
  }
81
- if (override.response !== void 0) {
82
- result.response = { ...result.response, ...override.response };
96
+ return {
97
+ returnMessage: value.return_message
98
+ };
99
+ }
100
+ function parseRegexLiteral(value) {
101
+ if (!value.startsWith("/")) {
102
+ return void 0;
83
103
  }
84
- if (override.cache !== void 0) {
85
- result.cache = {
86
- ...result.cache,
87
- ...override.cache,
88
- openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
89
- anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
90
- gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
91
- google: mergeRecordBlock(result.cache?.google, override.cache.google)
104
+ for (let index = value.length - 1; index > 0; index -= 1) {
105
+ if (value[index] !== "/") {
106
+ continue;
107
+ }
108
+ let backslashCount = 0;
109
+ for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
110
+ backslashCount += 1;
111
+ }
112
+ if (backslashCount % 2 === 1) {
113
+ continue;
114
+ }
115
+ return {
116
+ pattern: value.slice(1, index),
117
+ flags: value.slice(index + 1)
92
118
  };
93
119
  }
94
- if (override.raw !== void 0) {
95
- result.raw = {
96
- ...result.raw,
97
- ...override.raw,
98
- openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
99
- "openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
100
- openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
101
- anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
102
- gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
103
- google: mergeRecordBlock(result.raw?.google, override.raw.google),
104
- openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter)
105
- };
120
+ return void 0;
121
+ }
122
+ function formatInvalidContextRegexMessage(details) {
123
+ return [
124
+ `Invalid context regex for prompt "${details.promptId}"`,
125
+ `variable "${details.variable}"`,
126
+ `field "${details.field}"`,
127
+ `value ${JSON.stringify(details.raw)}: ${details.reason}`
128
+ ].join(", ");
129
+ }
130
+ function compileContextRegex(regex, details) {
131
+ if (regex.invalidLiteral) {
132
+ throw new Error(
133
+ `POK013: ${formatInvalidContextRegexMessage({
134
+ ...details,
135
+ raw: regex.raw,
136
+ reason: "Malformed regex literal. Use /pattern/flags or { pattern, flags }."
137
+ })}`
138
+ );
106
139
  }
107
- if (override.provider_options !== void 0) {
108
- result.provider_options = {
109
- ...result.provider_options,
110
- ...override.provider_options,
111
- anthropic: {
112
- ...result.provider_options?.anthropic,
113
- ...override.provider_options.anthropic
114
- },
115
- gemini: {
116
- ...result.provider_options?.gemini,
117
- ...override.provider_options.gemini
118
- },
119
- openrouter: {
120
- ...result.provider_options?.openrouter,
121
- ...override.provider_options.openrouter
122
- }
123
- };
140
+ try {
141
+ return new RegExp(regex.pattern, regex.flags);
142
+ } catch (error) {
143
+ const reason = error instanceof Error ? error.message : String(error);
144
+ throw new Error(`POK013: ${formatInvalidContextRegexMessage({ ...details, raw: regex.raw, reason })}`);
124
145
  }
125
- return result;
126
146
  }
127
- function mergeRecordBlock(base, override) {
128
- if (override === void 0) return base;
129
- return { ...base, ...override };
147
+ function getRejectSecretsRegex() {
148
+ return {
149
+ pattern: REJECT_SECRETS_PATTERN,
150
+ flags: REJECT_SECRETS_FLAGS,
151
+ raw: JSON.stringify({ pattern: REJECT_SECRETS_PATTERN, flags: REJECT_SECRETS_FLAGS })
152
+ };
130
153
  }
131
-
132
- // src/providers/resolve-asset.ts
133
- function resolveAssetForProvider(asset, runtime = {}) {
134
- if (!runtime.environment && !runtime.tier && !runtime.runtime) {
135
- return asset;
154
+ function isTrimEnabled(mode) {
155
+ return mode === true || mode === "start" || mode === "end" || mode === "both";
156
+ }
157
+ function normalizeTrimMode(mode) {
158
+ if (mode === "start") {
159
+ return "start";
136
160
  }
137
- return applyOverrides(asset, {
138
- environment: runtime.environment,
139
- tier: runtime.tier,
140
- runtime: runtime.runtime
141
- });
161
+ return "end";
142
162
  }
143
-
144
- // src/cache.ts
145
- import { statSync } from "fs";
146
- var PromptCache = class {
147
- cache = /* @__PURE__ */ new Map();
148
- maxSize;
149
- constructor(maxSize = 100) {
150
- this.maxSize = maxSize;
163
+ function trimToMaxSize(value, maxSize, mode) {
164
+ const measured = measureContextValueSize(value);
165
+ if (measured <= maxSize) {
166
+ return value;
151
167
  }
152
- get(filePath) {
153
- const entry = this.cache.get(filePath);
154
- if (!entry) return void 0;
155
- try {
156
- const stat = statSync(filePath);
157
- if (stat.mtimeMs !== entry.mtime) {
158
- this.cache.delete(filePath);
159
- return void 0;
168
+ const characters = Array.from(value);
169
+ const normalizedMode = normalizeTrimMode(mode);
170
+ if (normalizedMode === "start") {
171
+ let collected2 = "";
172
+ let size2 = 0;
173
+ for (let i = characters.length - 1; i >= 0; i -= 1) {
174
+ const next = characters[i];
175
+ const charSize = measureContextValueSize(next);
176
+ if (size2 + charSize > maxSize) {
177
+ break;
160
178
  }
161
- } catch {
162
- this.cache.delete(filePath);
163
- return void 0;
179
+ collected2 = next + collected2;
180
+ size2 += charSize;
164
181
  }
165
- this.cache.delete(filePath);
166
- this.cache.set(filePath, entry);
167
- return entry.value;
182
+ return collected2;
168
183
  }
169
- set(filePath, value) {
170
- try {
171
- const stat = statSync(filePath);
172
- if (this.cache.has(filePath)) {
173
- this.cache.delete(filePath);
174
- } else if (this.cache.size >= this.maxSize) {
175
- const oldest = this.cache.keys().next().value;
176
- if (oldest) this.cache.delete(oldest);
177
- }
178
- this.cache.set(filePath, { value, mtime: stat.mtimeMs });
179
- } catch {
184
+ let collected = "";
185
+ let size = 0;
186
+ for (const char of characters) {
187
+ const charSize = measureContextValueSize(char);
188
+ if (size + charSize > maxSize) {
189
+ break;
180
190
  }
191
+ collected += char;
192
+ size += charSize;
181
193
  }
182
- clear() {
183
- this.cache.clear();
184
- }
185
- get size() {
186
- return this.cache.size;
187
- }
188
- };
189
-
190
- // src/composition/resolve-includes.ts
191
- import { readFile } from "fs/promises";
192
- import { resolve, dirname } from "path";
193
- async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
194
- if (!asset.includes || asset.includes.length === 0) {
195
- return asset;
196
- }
197
- const baseDir = dirname(basePath);
198
- const resolvedPath = resolve(basePath);
199
- if (visited.has(resolvedPath)) {
200
- throw new Error(`Circular include detected: ${resolvedPath}`);
201
- }
202
- visited.add(resolvedPath);
203
- let mergedSystemInstructions = "";
204
- for (const includePath of asset.includes) {
205
- const fullPath = resolve(baseDir, includePath);
206
- if (visited.has(fullPath)) {
207
- throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
194
+ return collected;
195
+ }
196
+ function sanitizeContextVariables(asset, variables = {}, options = {}) {
197
+ const { onContextOverflow } = options;
198
+ const sanitized = { ...variables };
199
+ for (const input of getContextInputs(asset)) {
200
+ const value = sanitized[input.name];
201
+ if (value === void 0) {
202
+ continue;
208
203
  }
209
- const content = await readFile(fullPath, "utf-8");
210
- const { asset: includedAsset } = parsePrompt(content, fullPath);
211
- const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
212
- if (resolved.sections?.system_instructions) {
213
- mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
204
+ let candidate = value;
205
+ if (input.max_size !== void 0) {
206
+ const actualSize = measureContextValueSize(candidate);
207
+ if (actualSize > input.max_size && onContextOverflow) {
208
+ candidate = onContextOverflow({
209
+ promptId: asset.id,
210
+ variable: input.name,
211
+ value: candidate,
212
+ maxSize: input.max_size,
213
+ actualSize
214
+ });
215
+ }
214
216
  }
215
- }
216
- const localSystem = asset.sections?.system_instructions ?? "";
217
- const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
218
- return {
219
- ...asset,
220
- sections: {
221
- ...asset.sections,
222
- system_instructions: combinedSystem
223
- },
224
- // Drop includes from the resolved asset — they've been inlined
225
- includes: void 0
226
- };
227
- }
228
-
229
- // src/prompt-resolution.ts
230
- import { readFile as readFile2 } from "fs/promises";
231
- import { existsSync, statSync as statSync2 } from "fs";
232
- import { resolve as resolve2 } from "path";
233
- var DEFAULT_PROMPTS_DIR = "./prompts";
234
- var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
235
- function withPromptResolutionDefaults(config) {
236
- return {
237
- ...config,
238
- sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
239
- compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
240
- };
241
- }
242
- var sharedPromptCache = new PromptCache();
243
- async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
244
- const resolvedConfig = withPromptResolutionDefaults(config);
245
- const mode = resolvedConfig.mode ?? "auto";
246
- if (mode !== "source-only" && resolvedConfig.compiledDir) {
247
- const compiledFile = resolve2(resolvedConfig.compiledDir, promptPath + ".json");
248
- if (existsSync(compiledFile)) {
249
- if (mode === "auto") {
250
- const sourceFile = resolve2(resolvedConfig.sourceDir, promptPath + ".md");
251
- if (existsSync(sourceFile)) {
252
- const compiledMtime = statSync2(compiledFile).mtimeMs;
253
- const sourceMtime = statSync2(sourceFile).mtimeMs;
254
- if (sourceMtime > compiledMtime) {
255
- console.warn(
256
- `[promptopskit] Warning: compiled artifact for "${promptPath}" is older than source .md file.
257
- Run "promptopskit compile" or switch to source-only mode.`
258
- );
259
- }
217
+ if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
218
+ candidate = trimToMaxSize(candidate, input.max_size, input.trim);
219
+ }
220
+ sanitized[input.name] = candidate;
221
+ if (input.allow_regex) {
222
+ const candidate2 = sanitized[input.name];
223
+ const matcher = compileContextRegex(input.allow_regex, {
224
+ promptId: asset.id,
225
+ variable: input.name,
226
+ field: "allow_regex"
227
+ });
228
+ if (!matcher.test(candidate2)) {
229
+ if (input.allow_regex.returnMessage) {
230
+ return {
231
+ variables: sanitized,
232
+ shortCircuit: {
233
+ returnMessage: input.allow_regex.returnMessage,
234
+ code: "POK031",
235
+ variable: input.name,
236
+ field: "allow_regex"
237
+ }
238
+ };
260
239
  }
240
+ throw new Error(
241
+ `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
242
+ );
261
243
  }
262
- const content = await readFile2(compiledFile, "utf-8");
263
- return JSON.parse(content);
264
- }
265
- if (mode === "compiled-only") {
266
- throw new Error(
267
- `Compiled artifact not found: ${compiledFile}
268
- Run "promptopskit compile" to generate it.`
269
- );
270
244
  }
271
- }
272
- if (mode !== "compiled-only") {
273
- const sourceFile = resolve2(resolvedConfig.sourceDir, promptPath + ".md");
274
- if (resolvedConfig.cache !== false) {
275
- const cached = promptCache.get(sourceFile);
276
- if (cached) {
277
- return cached;
245
+ if (input.deny_regex) {
246
+ const candidate2 = sanitized[input.name];
247
+ const matcher = compileContextRegex(input.deny_regex, {
248
+ promptId: asset.id,
249
+ variable: input.name,
250
+ field: "deny_regex"
251
+ });
252
+ if (matcher.test(candidate2)) {
253
+ if (input.deny_regex.returnMessage) {
254
+ return {
255
+ variables: sanitized,
256
+ shortCircuit: {
257
+ returnMessage: input.deny_regex.returnMessage,
258
+ code: "POK032",
259
+ variable: input.name,
260
+ field: "deny_regex"
261
+ }
262
+ };
263
+ }
264
+ throw new Error(
265
+ `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
266
+ );
278
267
  }
279
268
  }
280
- if (!existsSync(sourceFile)) {
281
- const paths = [sourceFile];
282
- if (resolvedConfig.compiledDir) {
283
- paths.unshift(resolve2(resolvedConfig.compiledDir, promptPath + ".json"));
269
+ if (input.non_empty && candidate.trim().length === 0) {
270
+ if (input.non_empty.returnMessage) {
271
+ return {
272
+ variables: sanitized,
273
+ shortCircuit: {
274
+ returnMessage: input.non_empty.returnMessage,
275
+ code: "POK033",
276
+ variable: input.name,
277
+ field: "non_empty"
278
+ }
279
+ };
284
280
  }
285
281
  throw new Error(
286
- `Prompt not found: "${promptPath}"
287
- Searched:
288
- ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
282
+ `POK033: Context variable "${input.name}" failed non_empty validation for prompt "${asset.id}".`
289
283
  );
290
284
  }
291
- const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
292
- if (resolvedConfig.cache !== false) {
293
- promptCache.set(sourceFile, asset);
294
- }
295
- return asset;
296
- }
297
- throw new Error(`Prompt not found: "${promptPath}"`);
298
- }
299
- async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
300
- const resolvedConfig = withPromptResolutionDefaults(config);
301
- let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
302
- const sourceFile = resolve2(resolvedConfig.sourceDir, promptPath + ".md");
303
- if (asset.includes && asset.includes.length > 0 && existsSync(sourceFile)) {
304
- asset = await resolveIncludes(asset, sourceFile);
305
- }
306
- asset = applyOverrides(asset, {
307
- environment: options.environment,
308
- tier: options.tier,
309
- runtime: options.runtime
310
- });
311
- return asset;
312
- }
313
- function resolveInlinePromptSource(source, options = {}) {
314
- const { asset } = parsePrompt(source);
315
- return applyOverrides(asset, {
316
- environment: options.environment,
317
- tier: options.tier,
318
- runtime: options.runtime
319
- });
320
- }
321
-
322
- // src/context.ts
323
- var textEncoder = new TextEncoder();
324
- var REJECT_SECRETS_PATTERN = "(secret|api[_-]?key|password)";
325
- var REJECT_SECRETS_FLAGS = "i";
326
- function getContextInputs(asset) {
327
- return (asset.context?.inputs ?? []).map(normalizeContextInput);
328
- }
329
- function getContextInputNames(asset) {
330
- return getContextInputs(asset).map((input) => input.name);
331
- }
332
- function normalizeContextInput(input) {
333
- if (typeof input === "string") {
334
- return { name: input };
335
- }
336
- return {
337
- name: input.name,
338
- max_size: input.max_size,
339
- trim: input.trim,
340
- allow_regex: normalizeContextRegex(input.allow_regex),
341
- deny_regex: normalizeContextRegex(input.deny_regex),
342
- non_empty: normalizeBuiltInValidator(input.non_empty),
343
- reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
344
- };
345
- }
346
- function normalizeContextRegex(value) {
347
- if (value === void 0) {
348
- return void 0;
349
- }
350
- if (typeof value === "string") {
351
- const literal = parseRegexLiteral(value);
352
- if (value.startsWith("/") && !literal) {
353
- return {
354
- pattern: value,
355
- flags: "",
356
- raw: value,
357
- invalidLiteral: true
358
- };
359
- }
360
- return {
361
- pattern: literal?.pattern ?? value,
362
- flags: literal?.flags ?? "",
363
- raw: value
364
- };
285
+ if (input.reject_secrets) {
286
+ const matcher = compileContextRegex(getRejectSecretsRegex(), {
287
+ promptId: asset.id,
288
+ variable: input.name,
289
+ field: "reject_secrets"
290
+ });
291
+ if (matcher.test(candidate)) {
292
+ if (input.reject_secrets.returnMessage) {
293
+ return {
294
+ variables: sanitized,
295
+ shortCircuit: {
296
+ returnMessage: input.reject_secrets.returnMessage,
297
+ code: "POK034",
298
+ variable: input.name,
299
+ field: "reject_secrets"
300
+ }
301
+ };
302
+ }
303
+ throw new Error(
304
+ `POK034: Context variable "${input.name}" matched reject_secrets validation for prompt "${asset.id}".`
305
+ );
306
+ }
307
+ }
365
308
  }
366
- return {
367
- pattern: value.pattern,
368
- flags: value.flags ?? "",
369
- raw: JSON.stringify(value),
370
- returnMessage: value.return_message
371
- };
309
+ return { variables: sanitized };
372
310
  }
373
- function normalizeBuiltInValidator(value) {
374
- if (value === void 0 || value === false) {
375
- return void 0;
376
- }
377
- if (value === true) {
378
- return {};
379
- }
380
- return {
381
- returnMessage: value.return_message
382
- };
311
+ function measureContextValueSize(value) {
312
+ return textEncoder.encode(value).length;
383
313
  }
384
- function parseRegexLiteral(value) {
385
- if (!value.startsWith("/")) {
386
- return void 0;
387
- }
388
- for (let index = value.length - 1; index > 0; index -= 1) {
389
- if (value[index] !== "/") {
314
+ function collectContextSizeWarnings(asset, variables = {}) {
315
+ const warnings = [];
316
+ for (const input of getContextInputs(asset)) {
317
+ if (!areContextInputWarningsEnabled(input)) {
390
318
  continue;
391
319
  }
392
- let backslashCount = 0;
393
- for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
394
- backslashCount += 1;
320
+ if (input.max_size === void 0) {
321
+ continue;
395
322
  }
396
- if (backslashCount % 2 === 1) {
323
+ const value = variables[input.name];
324
+ if (value === void 0) {
397
325
  continue;
398
326
  }
399
- return {
400
- pattern: value.slice(1, index),
401
- flags: value.slice(index + 1)
402
- };
327
+ const actualSize = measureContextValueSize(value);
328
+ if (actualSize > input.max_size) {
329
+ warnings.push({
330
+ variable: input.name,
331
+ maxSize: input.max_size,
332
+ actualSize
333
+ });
334
+ }
403
335
  }
404
- return void 0;
405
- }
406
- function formatInvalidContextRegexMessage(details) {
407
- return [
408
- `Invalid context regex for prompt "${details.promptId}"`,
409
- `variable "${details.variable}"`,
410
- `field "${details.field}"`,
411
- `value ${JSON.stringify(details.raw)}: ${details.reason}`
412
- ].join(", ");
336
+ return warnings;
413
337
  }
414
- function compileContextRegex(regex, details) {
415
- if (regex.invalidLiteral) {
416
- throw new Error(
417
- `POK013: ${formatInvalidContextRegexMessage({
418
- ...details,
419
- raw: regex.raw,
420
- reason: "Malformed regex literal. Use /pattern/flags or { pattern, flags }."
421
- })}`
338
+
339
+ // src/renderer/renderer.ts
340
+ function renderSections(asset, options = {}) {
341
+ const { variables = {}, strict = false } = options;
342
+ const optionalVariables = getContextInputs(asset).filter((input) => input.optional === true).map((input) => input.name);
343
+ const result = {};
344
+ if (asset.sections.system_instructions) {
345
+ result.system_instructions = interpolate(
346
+ asset.sections.system_instructions,
347
+ variables,
348
+ { strict, optionalVariables }
422
349
  );
423
350
  }
424
- try {
425
- return new RegExp(regex.pattern, regex.flags);
426
- } catch (error) {
427
- const reason = error instanceof Error ? error.message : String(error);
428
- throw new Error(`POK013: ${formatInvalidContextRegexMessage({ ...details, raw: regex.raw, reason })}`);
351
+ if (asset.sections.prompt_template) {
352
+ result.prompt_template = interpolate(
353
+ asset.sections.prompt_template,
354
+ variables,
355
+ { strict, optionalVariables }
356
+ );
429
357
  }
358
+ return result;
430
359
  }
431
- function getRejectSecretsRegex() {
432
- return {
433
- pattern: REJECT_SECRETS_PATTERN,
434
- flags: REJECT_SECRETS_FLAGS,
435
- raw: JSON.stringify({ pattern: REJECT_SECRETS_PATTERN, flags: REJECT_SECRETS_FLAGS })
436
- };
360
+
361
+ // src/overrides/apply-overrides.ts
362
+ function applyOverrides(asset, options = {}) {
363
+ let result = { ...asset };
364
+ if (options.environment && result.environments?.[options.environment]) {
365
+ result = mergeOverride(result, result.environments[options.environment]);
366
+ }
367
+ if (options.tier && result.tiers?.[options.tier]) {
368
+ result = mergeOverride(result, result.tiers[options.tier]);
369
+ }
370
+ if (options.runtime) {
371
+ result = mergeOverride(result, options.runtime);
372
+ }
373
+ return result;
437
374
  }
438
- function isTrimEnabled(mode) {
439
- return mode === true || mode === "start" || mode === "end" || mode === "both";
375
+ function mergeOverride(base, override) {
376
+ const result = { ...base };
377
+ if (override.model !== void 0) result.model = override.model;
378
+ if (override.fallback_models !== void 0) result.fallback_models = override.fallback_models;
379
+ if (override.tools !== void 0) result.tools = override.tools;
380
+ if (override.reasoning !== void 0) {
381
+ result.reasoning = { ...result.reasoning, ...override.reasoning };
382
+ }
383
+ if (override.sampling !== void 0) {
384
+ result.sampling = { ...result.sampling, ...override.sampling };
385
+ }
386
+ if (override.response !== void 0) {
387
+ result.response = { ...result.response, ...override.response };
388
+ }
389
+ if (override.cache !== void 0) {
390
+ result.cache = {
391
+ ...result.cache,
392
+ ...override.cache,
393
+ openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
394
+ anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
395
+ gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
396
+ google: mergeRecordBlock(result.cache?.google, override.cache.google)
397
+ };
398
+ }
399
+ if (override.raw !== void 0) {
400
+ result.raw = {
401
+ ...result.raw,
402
+ ...override.raw,
403
+ openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
404
+ "openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
405
+ openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
406
+ anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
407
+ gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
408
+ google: mergeRecordBlock(result.raw?.google, override.raw.google),
409
+ openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter),
410
+ llmasaservice: mergeRecordBlock(result.raw?.llmasaservice, override.raw.llmasaservice)
411
+ };
412
+ }
413
+ if (override.provider_options !== void 0) {
414
+ result.provider_options = {
415
+ ...result.provider_options,
416
+ ...override.provider_options,
417
+ anthropic: {
418
+ ...result.provider_options?.anthropic,
419
+ ...override.provider_options.anthropic
420
+ },
421
+ gemini: {
422
+ ...result.provider_options?.gemini,
423
+ ...override.provider_options.gemini
424
+ },
425
+ openrouter: {
426
+ ...result.provider_options?.openrouter,
427
+ ...override.provider_options.openrouter
428
+ },
429
+ llmasaservice: {
430
+ ...result.provider_options?.llmasaservice,
431
+ ...override.provider_options.llmasaservice
432
+ }
433
+ };
434
+ }
435
+ return result;
440
436
  }
441
- function normalizeTrimMode(mode) {
442
- if (mode === "start") {
443
- return "start";
437
+ function mergeRecordBlock(base, override) {
438
+ if (override === void 0) return base;
439
+ return { ...base, ...override };
440
+ }
441
+
442
+ // src/providers/resolve-asset.ts
443
+ function resolveAssetForProvider(asset, runtime = {}) {
444
+ if (!runtime.environment && !runtime.tier && !runtime.runtime) {
445
+ return asset;
444
446
  }
445
- return "end";
447
+ return applyOverrides(asset, {
448
+ environment: runtime.environment,
449
+ tier: runtime.tier,
450
+ runtime: runtime.runtime
451
+ });
446
452
  }
447
- function trimToMaxSize(value, maxSize, mode) {
448
- const measured = measureContextValueSize(value);
449
- if (measured <= maxSize) {
450
- return value;
453
+
454
+ // src/cache.ts
455
+ import { statSync } from "fs";
456
+ var PromptCache = class {
457
+ cache = /* @__PURE__ */ new Map();
458
+ maxSize;
459
+ constructor(maxSize = 100) {
460
+ this.maxSize = maxSize;
451
461
  }
452
- const characters = Array.from(value);
453
- const normalizedMode = normalizeTrimMode(mode);
454
- if (normalizedMode === "start") {
455
- let collected2 = "";
456
- let size2 = 0;
457
- for (let i = characters.length - 1; i >= 0; i -= 1) {
458
- const next = characters[i];
459
- const charSize = measureContextValueSize(next);
460
- if (size2 + charSize > maxSize) {
461
- break;
462
+ get(filePath) {
463
+ const entry = this.cache.get(filePath);
464
+ if (!entry) return void 0;
465
+ try {
466
+ const stat = statSync(filePath);
467
+ if (stat.mtimeMs !== entry.mtime) {
468
+ this.cache.delete(filePath);
469
+ return void 0;
462
470
  }
463
- collected2 = next + collected2;
464
- size2 += charSize;
471
+ } catch {
472
+ this.cache.delete(filePath);
473
+ return void 0;
465
474
  }
466
- return collected2;
475
+ this.cache.delete(filePath);
476
+ this.cache.set(filePath, entry);
477
+ return entry.value;
478
+ }
479
+ set(filePath, value) {
480
+ try {
481
+ const stat = statSync(filePath);
482
+ if (this.cache.has(filePath)) {
483
+ this.cache.delete(filePath);
484
+ } else if (this.cache.size >= this.maxSize) {
485
+ const oldest = this.cache.keys().next().value;
486
+ if (oldest) this.cache.delete(oldest);
487
+ }
488
+ this.cache.set(filePath, { value, mtime: stat.mtimeMs });
489
+ } catch {
490
+ }
491
+ }
492
+ clear() {
493
+ this.cache.clear();
494
+ }
495
+ get size() {
496
+ return this.cache.size;
497
+ }
498
+ };
499
+
500
+ // src/composition/resolve-includes.ts
501
+ import { readFile } from "fs/promises";
502
+ import { resolve, dirname } from "path";
503
+ async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
504
+ if (!asset.includes || asset.includes.length === 0) {
505
+ return asset;
506
+ }
507
+ const baseDir = dirname(basePath);
508
+ const resolvedPath = resolve(basePath);
509
+ if (visited.has(resolvedPath)) {
510
+ throw new Error(`Circular include detected: ${resolvedPath}`);
467
511
  }
468
- let collected = "";
469
- let size = 0;
470
- for (const char of characters) {
471
- const charSize = measureContextValueSize(char);
472
- if (size + charSize > maxSize) {
473
- break;
512
+ visited.add(resolvedPath);
513
+ let mergedSystemInstructions = "";
514
+ for (const includePath of asset.includes) {
515
+ const fullPath = resolve(baseDir, includePath);
516
+ if (visited.has(fullPath)) {
517
+ throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
518
+ }
519
+ const content = await readFile(fullPath, "utf-8");
520
+ const { asset: includedAsset } = parsePrompt(content, fullPath);
521
+ const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
522
+ if (resolved.sections?.system_instructions) {
523
+ mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
474
524
  }
475
- collected += char;
476
- size += charSize;
477
525
  }
478
- return collected;
526
+ const localSystem = asset.sections?.system_instructions ?? "";
527
+ const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
528
+ return {
529
+ ...asset,
530
+ sections: {
531
+ ...asset.sections,
532
+ system_instructions: combinedSystem
533
+ },
534
+ // Drop includes from the resolved asset — they've been inlined
535
+ includes: void 0
536
+ };
479
537
  }
480
- function sanitizeContextVariables(asset, variables = {}, options = {}) {
481
- const { onContextOverflow } = options;
482
- const sanitized = { ...variables };
483
- for (const input of getContextInputs(asset)) {
484
- const value = sanitized[input.name];
485
- if (value === void 0) {
486
- continue;
487
- }
488
- let candidate = value;
489
- if (input.max_size !== void 0) {
490
- const actualSize = measureContextValueSize(candidate);
491
- if (actualSize > input.max_size && onContextOverflow) {
492
- candidate = onContextOverflow({
493
- promptId: asset.id,
494
- variable: input.name,
495
- value: candidate,
496
- maxSize: input.max_size,
497
- actualSize
498
- });
499
- }
500
- }
501
- if (isTrimEnabled(input.trim) && input.max_size !== void 0) {
502
- candidate = trimToMaxSize(candidate, input.max_size, input.trim);
503
- }
504
- sanitized[input.name] = candidate;
505
- if (input.allow_regex) {
506
- const candidate2 = sanitized[input.name];
507
- const matcher = compileContextRegex(input.allow_regex, {
508
- promptId: asset.id,
509
- variable: input.name,
510
- field: "allow_regex"
511
- });
512
- if (!matcher.test(candidate2)) {
513
- if (input.allow_regex.returnMessage) {
514
- return {
515
- variables: sanitized,
516
- shortCircuit: {
517
- returnMessage: input.allow_regex.returnMessage,
518
- code: "POK031",
519
- variable: input.name,
520
- field: "allow_regex"
521
- }
522
- };
538
+
539
+ // src/prompt-resolution.ts
540
+ import { readFile as readFile2 } from "fs/promises";
541
+ import { existsSync, statSync as statSync2 } from "fs";
542
+ import { resolve as resolve2 } from "path";
543
+ var DEFAULT_PROMPTS_DIR = "./prompts";
544
+ var DEFAULT_COMPILED_JSON_DIR = "./.generated-prompts/json";
545
+ function withPromptResolutionDefaults(config) {
546
+ return {
547
+ ...config,
548
+ sourceDir: config.sourceDir ?? DEFAULT_PROMPTS_DIR,
549
+ compiledDir: config.compiledDir ?? DEFAULT_COMPILED_JSON_DIR
550
+ };
551
+ }
552
+ var sharedPromptCache = new PromptCache();
553
+ async function loadPromptAsset(promptPath, config, promptCache = sharedPromptCache) {
554
+ const resolvedConfig = withPromptResolutionDefaults(config);
555
+ const mode = resolvedConfig.mode ?? "auto";
556
+ if (mode !== "source-only" && resolvedConfig.compiledDir) {
557
+ const compiledFile = resolve2(resolvedConfig.compiledDir, promptPath + ".json");
558
+ if (existsSync(compiledFile)) {
559
+ if (mode === "auto") {
560
+ const sourceFile = resolve2(resolvedConfig.sourceDir, promptPath + ".md");
561
+ if (existsSync(sourceFile)) {
562
+ const compiledMtime = statSync2(compiledFile).mtimeMs;
563
+ const sourceMtime = statSync2(sourceFile).mtimeMs;
564
+ if (sourceMtime > compiledMtime) {
565
+ console.warn(
566
+ `[promptopskit] Warning: compiled artifact for "${promptPath}" is older than source .md file.
567
+ Run "promptopskit compile" or switch to source-only mode.`
568
+ );
569
+ }
523
570
  }
524
- throw new Error(
525
- `POK031: Context variable "${input.name}" failed allow_regex validation for prompt "${asset.id}".`
526
- );
527
571
  }
572
+ const content = await readFile2(compiledFile, "utf-8");
573
+ return JSON.parse(content);
528
574
  }
529
- if (input.deny_regex) {
530
- const candidate2 = sanitized[input.name];
531
- const matcher = compileContextRegex(input.deny_regex, {
532
- promptId: asset.id,
533
- variable: input.name,
534
- field: "deny_regex"
535
- });
536
- if (matcher.test(candidate2)) {
537
- if (input.deny_regex.returnMessage) {
538
- return {
539
- variables: sanitized,
540
- shortCircuit: {
541
- returnMessage: input.deny_regex.returnMessage,
542
- code: "POK032",
543
- variable: input.name,
544
- field: "deny_regex"
545
- }
546
- };
547
- }
548
- throw new Error(
549
- `POK032: Context variable "${input.name}" matched deny_regex for prompt "${asset.id}".`
550
- );
575
+ if (mode === "compiled-only") {
576
+ throw new Error(
577
+ `Compiled artifact not found: ${compiledFile}
578
+ Run "promptopskit compile" to generate it.`
579
+ );
580
+ }
581
+ }
582
+ if (mode !== "compiled-only") {
583
+ const sourceFile = resolve2(resolvedConfig.sourceDir, promptPath + ".md");
584
+ if (resolvedConfig.cache !== false) {
585
+ const cached = promptCache.get(sourceFile);
586
+ if (cached) {
587
+ return cached;
551
588
  }
552
589
  }
553
- if (input.non_empty && candidate.trim().length === 0) {
554
- if (input.non_empty.returnMessage) {
555
- return {
556
- variables: sanitized,
557
- shortCircuit: {
558
- returnMessage: input.non_empty.returnMessage,
559
- code: "POK033",
560
- variable: input.name,
561
- field: "non_empty"
562
- }
563
- };
590
+ if (!existsSync(sourceFile)) {
591
+ const paths = [sourceFile];
592
+ if (resolvedConfig.compiledDir) {
593
+ paths.unshift(resolve2(resolvedConfig.compiledDir, promptPath + ".json"));
564
594
  }
565
595
  throw new Error(
566
- `POK033: Context variable "${input.name}" failed non_empty validation for prompt "${asset.id}".`
596
+ `Prompt not found: "${promptPath}"
597
+ Searched:
598
+ ${paths.map((candidate) => ` - ${candidate}`).join("\n")}`
567
599
  );
568
600
  }
569
- if (input.reject_secrets) {
570
- const matcher = compileContextRegex(getRejectSecretsRegex(), {
571
- promptId: asset.id,
572
- variable: input.name,
573
- field: "reject_secrets"
574
- });
575
- if (matcher.test(candidate)) {
576
- if (input.reject_secrets.returnMessage) {
577
- return {
578
- variables: sanitized,
579
- shortCircuit: {
580
- returnMessage: input.reject_secrets.returnMessage,
581
- code: "POK034",
582
- variable: input.name,
583
- field: "reject_secrets"
584
- }
585
- };
586
- }
587
- throw new Error(
588
- `POK034: Context variable "${input.name}" matched reject_secrets validation for prompt "${asset.id}".`
589
- );
590
- }
601
+ const { asset } = await loadPromptFile(sourceFile, { defaultsRoot: resolvedConfig.sourceDir });
602
+ if (resolvedConfig.cache !== false) {
603
+ promptCache.set(sourceFile, asset);
591
604
  }
605
+ return asset;
592
606
  }
593
- return { variables: sanitized };
607
+ throw new Error(`Prompt not found: "${promptPath}"`);
594
608
  }
595
- function measureContextValueSize(value) {
596
- return textEncoder.encode(value).length;
609
+ async function resolvePromptAsset(promptPath, config, options = {}, promptCache = sharedPromptCache) {
610
+ const resolvedConfig = withPromptResolutionDefaults(config);
611
+ let asset = await loadPromptAsset(promptPath, resolvedConfig, promptCache);
612
+ const sourceFile = resolve2(resolvedConfig.sourceDir, promptPath + ".md");
613
+ if (asset.includes && asset.includes.length > 0 && existsSync(sourceFile)) {
614
+ asset = await resolveIncludes(asset, sourceFile);
615
+ }
616
+ asset = applyOverrides(asset, {
617
+ environment: options.environment,
618
+ tier: options.tier,
619
+ runtime: options.runtime
620
+ });
621
+ return asset;
597
622
  }
598
- function collectContextSizeWarnings(asset, variables = {}) {
599
- const warnings = [];
600
- for (const input of getContextInputs(asset)) {
601
- if (input.max_size === void 0) {
602
- continue;
603
- }
604
- const value = variables[input.name];
605
- if (value === void 0) {
606
- continue;
607
- }
608
- const actualSize = measureContextValueSize(value);
609
- if (actualSize > input.max_size) {
610
- warnings.push({
611
- variable: input.name,
612
- maxSize: input.max_size,
613
- actualSize
614
- });
615
- }
623
+ function resolveInlinePromptSource(source, options = {}) {
624
+ const { asset } = parsePrompt(source);
625
+ return applyOverrides(asset, {
626
+ environment: options.environment,
627
+ tier: options.tier,
628
+ runtime: options.runtime
629
+ });
630
+ }
631
+
632
+ // src/history.ts
633
+ function defaultCompactHistory(info) {
634
+ const content = info.overflow.map((message) => `${message.role}: ${message.content}`).join("\n\n");
635
+ return {
636
+ role: "user",
637
+ content: `Earlier conversation compacted to preserve history:
638
+
639
+ ${content}`
640
+ };
641
+ }
642
+ function normalizeCompactionResult(result) {
643
+ if (typeof result === "string") {
644
+ return {
645
+ role: "user",
646
+ content: result
647
+ };
616
648
  }
617
- return warnings;
649
+ return result;
650
+ }
651
+ function compactHistoryForPrompt(asset, runtime) {
652
+ const history = runtime.history;
653
+ const maxItems = asset.context?.history?.max_items;
654
+ if (!history || maxItems === void 0 || history.length <= maxItems) {
655
+ return history;
656
+ }
657
+ if (maxItems === 1) {
658
+ const info2 = {
659
+ promptId: asset.id,
660
+ maxItems,
661
+ overflow: history,
662
+ preserved: [],
663
+ history
664
+ };
665
+ const compacted2 = runtime.onHistoryCompaction ? normalizeCompactionResult(runtime.onHistoryCompaction(info2)) : defaultCompactHistory(info2);
666
+ return [compacted2];
667
+ }
668
+ const preservedCount = maxItems - 1;
669
+ const overflow = history.slice(0, history.length - preservedCount);
670
+ const preserved = history.slice(-preservedCount);
671
+ const info = {
672
+ promptId: asset.id,
673
+ maxItems,
674
+ overflow,
675
+ preserved,
676
+ history
677
+ };
678
+ const compacted = runtime.onHistoryCompaction ? normalizeCompactionResult(runtime.onHistoryCompaction(info)) : defaultCompactHistory(info);
679
+ return [compacted, ...preserved];
618
680
  }
619
681
 
620
682
  // src/providers/prompt-input.ts
@@ -652,7 +714,8 @@ function withPromptInputSupport(adapter) {
652
714
  }
653
715
  return adapter.render(resolved, {
654
716
  ...runtime,
655
- variables: sanitization.variables
717
+ variables: sanitization.variables,
718
+ history: compactHistoryForPrompt(resolved, runtime)
656
719
  });
657
720
  };
658
721
  return {
@@ -680,6 +743,11 @@ function getRawProviderBody(asset, provider) {
680
743
  export {
681
744
  interpolate,
682
745
  extractVariables,
746
+ getContextInputs,
747
+ areContextInputWarningsEnabled,
748
+ compileContextRegex,
749
+ sanitizeContextVariables,
750
+ collectContextSizeWarnings,
683
751
  renderSections,
684
752
  applyOverrides,
685
753
  resolveAssetForProvider,
@@ -690,12 +758,8 @@ export {
690
758
  loadPromptAsset,
691
759
  resolvePromptAsset,
692
760
  resolveInlinePromptSource,
693
- getContextInputs,
694
- getContextInputNames,
695
- compileContextRegex,
696
- sanitizeContextVariables,
697
- collectContextSizeWarnings,
761
+ compactHistoryForPrompt,
698
762
  withPromptInputSupport,
699
763
  applyRawProviderBody
700
764
  };
701
- //# sourceMappingURL=chunk-YI4LILPY.js.map
765
+ //# sourceMappingURL=chunk-JR7252DG.js.map