@pixerate/editor 0.13.0 → 0.14.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.
@@ -21,12 +21,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var grammar_exports = {};
22
22
  __export(grammar_exports, {
23
23
  INSTRUCTION_REGEX: () => INSTRUCTION_REGEX,
24
+ TEMPLATE_DEPTH_ERROR: () => TEMPLATE_DEPTH_ERROR,
25
+ TEMPLATE_LOOP_ERROR: () => TEMPLATE_LOOP_ERROR,
24
26
  TEMPLATE_REGEX: () => TEMPLATE_REGEX,
25
27
  VARIABLE_REGEX: () => VARIABLE_REGEX,
26
28
  extractInstructionNames: () => extractInstructionNames,
27
29
  extractTemplateNames: () => extractTemplateNames,
28
30
  extractVariableNames: () => extractVariableNames,
29
31
  getLatestVersion: () => getLatestVersion,
32
+ getTemplateBody: () => getTemplateBody,
30
33
  instructionRegex: () => instructionRegex,
31
34
  mapRawOffsetToResolved: () => mapRawOffsetToResolved,
32
35
  mapResolvedOffsetToRaw: () => mapResolvedOffsetToRaw,
@@ -157,6 +160,18 @@ function getLatestVersion(template) {
157
160
  }
158
161
  return template.versions[template.versions.length - 1];
159
162
  }
163
+ var TEMPLATE_LOOP_ERROR = "[Template loop detected]";
164
+ var TEMPLATE_DEPTH_ERROR = "[Template max depth exceeded]";
165
+ function getTemplateBody(template, bindings) {
166
+ const boundVersionId = bindings?.[template.name];
167
+ if (boundVersionId && template.versions) {
168
+ const boundVersion = template.versions.find(
169
+ (v) => v.id === boundVersionId || v.version === boundVersionId
170
+ );
171
+ return boundVersion?.body;
172
+ }
173
+ return getLatestVersion(template)?.body ?? template.body;
174
+ }
160
175
  function resolveTemplates(text, templates = [], options) {
161
176
  const templateMap = /* @__PURE__ */ new Map();
162
177
  for (const t of templates) {
@@ -164,50 +179,27 @@ function resolveTemplates(text, templates = [], options) {
164
179
  }
165
180
  const maxDepth = options?.maxDepth ?? 10;
166
181
  function resolveRecursive(currentText, visited, depth) {
167
- if (depth > maxDepth) {
168
- return "[Template max depth exceeded]";
169
- }
170
- let result = currentText;
171
182
  const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
172
- let match;
173
- while ((match = regex.exec(result)) !== null) {
174
- const [fullMatch, rawTemplateName] = match;
183
+ return currentText.replace(regex, (fullMatch, rawTemplateName) => {
175
184
  const templateName = rawTemplateName.trim();
176
185
  if (visited.has(templateName)) {
177
- return "[Template loop detected]";
186
+ return TEMPLATE_LOOP_ERROR;
178
187
  }
179
188
  const template = templateMap.get(templateName);
180
189
  if (!template) {
181
- continue;
182
- }
183
- let versionBody;
184
- const boundVersionId = options?.bindings?.[templateName];
185
- if (boundVersionId && template.versions) {
186
- const boundVersion = template.versions.find(
187
- (v) => v.id === boundVersionId || v.version === boundVersionId
188
- );
189
- versionBody = boundVersion?.body;
190
- } else {
191
- const latest = getLatestVersion(template);
192
- versionBody = latest?.body ?? template.body;
190
+ return fullMatch;
193
191
  }
192
+ const versionBody = getTemplateBody(template, options?.bindings);
194
193
  if (versionBody === void 0) {
195
- continue;
194
+ return fullMatch;
195
+ }
196
+ if (depth + 1 > maxDepth) {
197
+ return TEMPLATE_DEPTH_ERROR;
196
198
  }
197
199
  const nextVisited = new Set(visited);
198
200
  nextVisited.add(templateName);
199
- const resolvedBody = resolveRecursive(
200
- versionBody,
201
- nextVisited,
202
- depth + 1
203
- );
204
- if (resolvedBody === "[Template loop detected]" || resolvedBody === "[Template max depth exceeded]") {
205
- return resolvedBody;
206
- }
207
- result = result.replace(fullMatch, resolvedBody);
208
- regex.lastIndex = 0;
209
- }
210
- return result;
201
+ return resolveRecursive(versionBody, nextVisited, depth + 1);
202
+ });
211
203
  }
212
204
  return resolveRecursive(text, /* @__PURE__ */ new Set(), 0);
213
205
  }
@@ -256,7 +248,7 @@ function extractInstructionNames(text) {
256
248
  }
257
249
 
258
250
  // src/grammar/sourcemap.ts
259
- function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
251
+ function resolveTemplatesWithMapping(text, templates = [], templateBindings, options) {
260
252
  if (!templates || templates.length === 0 || !text.includes("{{")) {
261
253
  return {
262
254
  text,
@@ -272,7 +264,7 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
272
264
  };
273
265
  }
274
266
  const templateMap = new Map(templates.map((t) => [t.name, t]));
275
- const regex = /{{\s*([^}]+?)\s*}}/g;
267
+ const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
276
268
  let match;
277
269
  let lastIndex = 0;
278
270
  let resolvedText = "";
@@ -294,25 +286,13 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
294
286
  }
295
287
  let resolvedBody = fullMatch;
296
288
  let isTemplate = false;
297
- if (templateMap.has(templateName)) {
298
- const template = templateMap.get(templateName);
299
- let versionBody;
300
- const boundVersionId = templateBindings?.[templateName];
301
- if (boundVersionId && template.versions) {
302
- const boundVersion = template.versions.find(
303
- (v) => v.id === boundVersionId || v.version === boundVersionId
304
- );
305
- versionBody = boundVersion?.body;
306
- } else {
307
- const latestVersion = getLatestVersion(template);
308
- versionBody = latestVersion?.body ?? template.body;
309
- }
310
- if (versionBody !== void 0) {
311
- resolvedBody = resolveTemplates(versionBody, templates, {
312
- bindings: templateBindings
313
- });
314
- isTemplate = true;
315
- }
289
+ const template = templateMap.get(templateName);
290
+ if (template && getTemplateBody(template, templateBindings) !== void 0) {
291
+ resolvedBody = resolveTemplates(fullMatch, templates, {
292
+ bindings: templateBindings,
293
+ maxDepth: options?.maxDepth
294
+ });
295
+ isTemplate = true;
316
296
  }
317
297
  if (isTemplate) {
318
298
  map.push({
@@ -357,9 +337,11 @@ function mapResolvedOffsetToRaw(resolvedOffset, map) {
357
337
  if (inRange) {
358
338
  if (seg.source === "raw") {
359
339
  return seg.rawStart + (resolvedOffset - seg.resolvedStart);
360
- } else {
361
- return seg.rawStart;
362
340
  }
341
+ if (resolvedOffset === seg.resolvedEnd) {
342
+ return seg.rawEnd;
343
+ }
344
+ return seg.rawStart;
363
345
  }
364
346
  }
365
347
  return resolvedOffset;
@@ -372,9 +354,11 @@ function mapRawOffsetToResolved(rawOffset, map) {
372
354
  if (inRange) {
373
355
  if (seg.source === "raw") {
374
356
  return seg.resolvedStart + (rawOffset - seg.rawStart);
375
- } else {
376
- return seg.resolvedStart;
377
357
  }
358
+ if (rawOffset === seg.rawEnd) {
359
+ return seg.resolvedEnd;
360
+ }
361
+ return seg.resolvedStart;
378
362
  }
379
363
  }
380
364
  return rawOffset;
@@ -382,12 +366,15 @@ function mapRawOffsetToResolved(rawOffset, map) {
382
366
  // Annotate the CommonJS export names for ESM import in node:
383
367
  0 && (module.exports = {
384
368
  INSTRUCTION_REGEX,
369
+ TEMPLATE_DEPTH_ERROR,
370
+ TEMPLATE_LOOP_ERROR,
385
371
  TEMPLATE_REGEX,
386
372
  VARIABLE_REGEX,
387
373
  extractInstructionNames,
388
374
  extractTemplateNames,
389
375
  extractVariableNames,
390
376
  getLatestVersion,
377
+ getTemplateBody,
391
378
  instructionRegex,
392
379
  mapRawOffsetToResolved,
393
380
  mapResolvedOffsetToRaw,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/grammar/index.ts","../../src/grammar/tokens.ts","../../src/grammar/tokenizer.ts","../../src/grammar/resolver.ts","../../src/grammar/sourcemap.ts"],"sourcesContent":["export * from \"./tokens\";\nexport * from \"./tokenizer\";\nexport * from \"./resolver\";\nexport * from \"./sourcemap\";\n","/**\n * Canonical regex patterns for prompt editing tokens.\n */\nexport const TEMPLATE_REGEX = /{{\\s*([^}]+?)\\s*}}/g;\nexport const VARIABLE_REGEX = /(?<!\\{)\\{([^{}]+)\\}(?!\\})/g;\nexport const INSTRUCTION_REGEX = /__(.+?)__/g;\n\n// Backward-compatible camelCase aliases\nexport const templateRegex = TEMPLATE_REGEX;\nexport const variableRegex = VARIABLE_REGEX;\nexport const instructionRegex = INSTRUCTION_REGEX;\n\nexport type TokenType = \"template\" | \"variable\" | \"instruction\" | \"text\";\n\nexport interface Token {\n type: TokenType;\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\nexport interface TemplateVersion {\n id: string;\n version: string | number;\n body: string;\n createdAt?: any;\n [key: string]: any;\n}\n\nexport interface Template {\n id?: string;\n name: string;\n body?: string;\n latestVersion?: string | number;\n versions?: TemplateVersion[];\n color?: string;\n [key: string]: any;\n}\n\nexport interface ColorGradient {\n from: string;\n to: string;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Token,\n} from \"./tokens\";\n\ninterface RawMatch {\n type: \"template\" | \"variable\" | \"instruction\";\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\n/**\n * Parses any prompt string into an array of typed tokens in linear order.\n * Plain text between tokens is preserved as `{ type: 'text' }`.\n */\nexport function tokenizePrompt(text: string): Token[] {\n if (!text) {\n return [];\n }\n\n const rawMatches: RawMatch[] = [];\n\n // Match {{template}}\n const tmplRegex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = tmplRegex.exec(text)) !== null) {\n rawMatches.push({\n type: \"template\",\n raw: match[0],\n value: match[1].trim(),\n start: match.index,\n end: match.index + match[0].length,\n });\n }\n\n // Match {variable}\n const varRegex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n while ((match = varRegex.exec(text)) !== null) {\n // Avoid double matching if overlapping with template\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"variable\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Match __instruction__\n const instRegex = new RegExp(\n INSTRUCTION_REGEX.source,\n INSTRUCTION_REGEX.flags,\n );\n while ((match = instRegex.exec(text)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"instruction\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Sort matches by start index\n rawMatches.sort((a, b) => a.start - b.start);\n\n const tokens: Token[] = [];\n let currentIdx = 0;\n\n for (const m of rawMatches) {\n if (m.start > currentIdx) {\n const textChunk = text.slice(currentIdx, m.start);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: m.start,\n });\n }\n tokens.push({\n type: m.type,\n raw: m.raw,\n value: m.value,\n start: m.start,\n end: m.end,\n });\n currentIdx = m.end;\n }\n\n if (currentIdx < text.length) {\n const textChunk = text.slice(currentIdx);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: text.length,\n });\n }\n\n return tokens;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Template,\n TemplateVersion,\n} from \"./tokens\";\n\n/**\n * Finds the latest version of a template based on `latestVersion` or last version entry.\n */\nexport function getLatestVersion(\n template: Template,\n): TemplateVersion | undefined {\n if (!template.versions || template.versions.length === 0) {\n if (template.body !== undefined) {\n return {\n id: template.id || \"default\",\n version: \"1.0\",\n body: template.body,\n };\n }\n return undefined;\n }\n if (template.latestVersion) {\n const found = template.versions.find(\n (v) => v.version === template.latestVersion,\n );\n if (found) return found;\n }\n return template.versions[template.versions.length - 1];\n}\n\nexport interface ResolveTemplatesOptions {\n bindings?: Record<string, string>;\n maxDepth?: number;\n}\n\n/**\n * Recursively resolves template tags `{{template_name}}` in a given text.\n * Detects circular dependencies and returns \"[Template loop detected]\".\n */\nexport function resolveTemplates(\n text: string,\n templates: Template[] = [],\n options?: ResolveTemplatesOptions,\n): string {\n const templateMap = new Map<string, Template>();\n for (const t of templates) {\n templateMap.set(t.name, t);\n }\n\n const maxDepth = options?.maxDepth ?? 10;\n\n function resolveRecursive(\n currentText: string,\n visited: Set<string>,\n depth: number,\n ): string {\n if (depth > maxDepth) {\n return \"[Template max depth exceeded]\";\n }\n\n let result = currentText;\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(result)) !== null) {\n const [fullMatch, rawTemplateName] = match;\n const templateName = rawTemplateName.trim();\n\n if (visited.has(templateName)) {\n return \"[Template loop detected]\";\n }\n\n const template = templateMap.get(templateName);\n if (!template) {\n continue;\n }\n\n let versionBody: string | undefined;\n const boundVersionId = options?.bindings?.[templateName];\n\n if (boundVersionId && template.versions) {\n const boundVersion = template.versions.find(\n (v) => v.id === boundVersionId || v.version === boundVersionId,\n );\n versionBody = boundVersion?.body;\n } else {\n const latest = getLatestVersion(template);\n versionBody = latest?.body ?? template.body;\n }\n\n if (versionBody === undefined) {\n continue;\n }\n\n const nextVisited = new Set(visited);\n nextVisited.add(templateName);\n\n const resolvedBody = resolveRecursive(\n versionBody,\n nextVisited,\n depth + 1,\n );\n\n if (\n resolvedBody === \"[Template loop detected]\" ||\n resolvedBody === \"[Template max depth exceeded]\"\n ) {\n return resolvedBody;\n }\n\n result = result.replace(fullMatch, resolvedBody);\n regex.lastIndex = 0;\n }\n\n return result;\n }\n\n return resolveRecursive(text, new Set(), 0);\n}\n\n/**\n * Replaces `{variableName}` with values from a dictionary.\n */\nexport function resolveVariables(\n text: string,\n values: Record<string, string | number>,\n): string {\n if (!text) return \"\";\n return text.replace(\n new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags),\n (fullMatch, varName) => {\n const key = varName.trim();\n if (Object.prototype.hasOwnProperty.call(values, key)) {\n return String(values[key]);\n }\n return fullMatch;\n },\n );\n}\n\n/**\n * Extracts unique template names referenced in text.\n */\nexport function extractTemplateNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique variable names referenced in text.\n */\nexport function extractVariableNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique instruction names referenced in text.\n */\nexport function extractInstructionNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(INSTRUCTION_REGEX.source, INSTRUCTION_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n","import { Template } from \"./tokens\";\nimport { getLatestVersion, resolveTemplates } from \"./resolver\";\n\nexport interface SourceMapSegment {\n resolvedStart: number;\n resolvedEnd: number;\n source: \"raw\" | \"template\";\n rawStart: number;\n rawEnd: number;\n templateName?: string;\n}\n\n/**\n * Resolves templates in text while tracking character offset mapping\n * between the raw input and the fully resolved output.\n */\nexport function resolveTemplatesWithMapping(\n text: string,\n templates: Template[] = [],\n templateBindings?: Record<string, string>,\n): { text: string; map: SourceMapSegment[] } {\n if (!templates || templates.length === 0 || !text.includes(\"{{\")) {\n return {\n text,\n map: [\n {\n resolvedStart: 0,\n resolvedEnd: text.length,\n source: \"raw\",\n rawStart: 0,\n rawEnd: text.length,\n },\n ],\n };\n }\n\n const templateMap = new Map(templates.map((t) => [t.name, t]));\n const regex = /{{\\s*([^}]+?)\\s*}}/g;\n let match: RegExpExecArray | null;\n let lastIndex = 0;\n let resolvedText = \"\";\n const map: SourceMapSegment[] = [];\n\n while ((match = regex.exec(text)) !== null) {\n const [fullMatch, rawTemplateName] = match;\n const templateName = rawTemplateName.trim();\n const matchIndex = match.index;\n\n // Add raw text preceding this template match\n if (matchIndex > lastIndex) {\n const rawSegment = text.substring(lastIndex, matchIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: matchIndex,\n });\n resolvedText += rawSegment;\n }\n\n let resolvedBody = fullMatch;\n let isTemplate = false;\n\n if (templateMap.has(templateName)) {\n const template = templateMap.get(templateName)!;\n let versionBody: string | undefined;\n const boundVersionId = templateBindings?.[templateName];\n\n if (boundVersionId && template.versions) {\n const boundVersion = template.versions.find(\n (v) => v.id === boundVersionId || v.version === boundVersionId,\n );\n versionBody = boundVersion?.body;\n } else {\n const latestVersion = getLatestVersion(template);\n versionBody = latestVersion?.body ?? template.body;\n }\n\n if (versionBody !== undefined) {\n resolvedBody = resolveTemplates(versionBody, templates, {\n bindings: templateBindings,\n });\n isTemplate = true;\n }\n }\n\n if (isTemplate) {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + resolvedBody.length,\n source: \"template\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n templateName,\n });\n resolvedText += resolvedBody;\n } else {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + fullMatch.length,\n source: \"raw\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n });\n resolvedText += fullMatch;\n }\n\n lastIndex = matchIndex + fullMatch.length;\n }\n\n // Add any remaining raw text after the final match\n if (lastIndex < text.length) {\n const rawSegment = text.substring(lastIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: text.length,\n });\n resolvedText += rawSegment;\n }\n\n return { text: resolvedText, map };\n}\n\n/**\n * Maps an offset in resolved text back to the corresponding offset in raw text.\n */\nexport function mapResolvedOffsetToRaw(\n resolvedOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? resolvedOffset >= seg.resolvedStart && resolvedOffset <= seg.resolvedEnd\n : resolvedOffset >= seg.resolvedStart && resolvedOffset < seg.resolvedEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.rawStart + (resolvedOffset - seg.resolvedStart);\n } else {\n // Pointing into a resolved template - return start of template tag\n return seg.rawStart;\n }\n }\n }\n return resolvedOffset;\n}\n\n/**\n * Maps an offset in raw text to the corresponding offset in resolved text.\n */\nexport function mapRawOffsetToResolved(\n rawOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? rawOffset >= seg.rawStart && rawOffset <= seg.rawEnd\n : rawOffset >= seg.rawStart && rawOffset < seg.rawEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.resolvedStart + (rawOffset - seg.rawStart);\n } else {\n return seg.resolvedStart;\n }\n }\n }\n return rawOffset;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/grammar/index.ts","../../src/grammar/tokens.ts","../../src/grammar/tokenizer.ts","../../src/grammar/resolver.ts","../../src/grammar/sourcemap.ts"],"sourcesContent":["export * from \"./tokens\";\nexport * from \"./tokenizer\";\nexport * from \"./resolver\";\nexport * from \"./sourcemap\";\n","/**\n * Canonical regex patterns for prompt editing tokens.\n */\nexport const TEMPLATE_REGEX = /{{\\s*([^}]+?)\\s*}}/g;\nexport const VARIABLE_REGEX = /(?<!\\{)\\{([^{}]+)\\}(?!\\})/g;\nexport const INSTRUCTION_REGEX = /__(.+?)__/g;\n\n// Backward-compatible camelCase aliases\nexport const templateRegex = TEMPLATE_REGEX;\nexport const variableRegex = VARIABLE_REGEX;\nexport const instructionRegex = INSTRUCTION_REGEX;\n\nexport type TokenType = \"template\" | \"variable\" | \"instruction\" | \"text\";\n\nexport interface Token {\n type: TokenType;\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\nexport interface TemplateVersion {\n id: string;\n version: string | number;\n body: string;\n createdAt?: any;\n [key: string]: any;\n}\n\nexport interface Template {\n id?: string;\n name: string;\n body?: string;\n latestVersion?: string | number;\n versions?: TemplateVersion[];\n color?: string;\n [key: string]: any;\n}\n\nexport interface ColorGradient {\n from: string;\n to: string;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Token,\n} from \"./tokens\";\n\ninterface RawMatch {\n type: \"template\" | \"variable\" | \"instruction\";\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\n/**\n * Parses any prompt string into an array of typed tokens in linear order.\n * Plain text between tokens is preserved as `{ type: 'text' }`.\n */\nexport function tokenizePrompt(text: string): Token[] {\n if (!text) {\n return [];\n }\n\n const rawMatches: RawMatch[] = [];\n\n // Match {{template}}\n const tmplRegex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = tmplRegex.exec(text)) !== null) {\n rawMatches.push({\n type: \"template\",\n raw: match[0],\n value: match[1].trim(),\n start: match.index,\n end: match.index + match[0].length,\n });\n }\n\n // Match {variable}\n const varRegex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n while ((match = varRegex.exec(text)) !== null) {\n // Avoid double matching if overlapping with template\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"variable\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Match __instruction__\n const instRegex = new RegExp(\n INSTRUCTION_REGEX.source,\n INSTRUCTION_REGEX.flags,\n );\n while ((match = instRegex.exec(text)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"instruction\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Sort matches by start index\n rawMatches.sort((a, b) => a.start - b.start);\n\n const tokens: Token[] = [];\n let currentIdx = 0;\n\n for (const m of rawMatches) {\n if (m.start > currentIdx) {\n const textChunk = text.slice(currentIdx, m.start);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: m.start,\n });\n }\n tokens.push({\n type: m.type,\n raw: m.raw,\n value: m.value,\n start: m.start,\n end: m.end,\n });\n currentIdx = m.end;\n }\n\n if (currentIdx < text.length) {\n const textChunk = text.slice(currentIdx);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: text.length,\n });\n }\n\n return tokens;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Template,\n TemplateVersion,\n} from \"./tokens\";\n\n/**\n * Finds the latest version of a template based on `latestVersion` or last version entry.\n */\nexport function getLatestVersion(\n template: Template,\n): TemplateVersion | undefined {\n if (!template.versions || template.versions.length === 0) {\n if (template.body !== undefined) {\n return {\n id: template.id || \"default\",\n version: \"1.0\",\n body: template.body,\n };\n }\n return undefined;\n }\n if (template.latestVersion) {\n const found = template.versions.find(\n (v) => v.version === template.latestVersion,\n );\n if (found) return found;\n }\n return template.versions[template.versions.length - 1];\n}\n\nexport interface ResolveTemplatesOptions {\n bindings?: Record<string, string>;\n maxDepth?: number;\n}\n\n/** Marker substituted for a template tag that would recurse into itself. */\nexport const TEMPLATE_LOOP_ERROR = \"[Template loop detected]\";\n/** Marker substituted for a template tag nested deeper than `maxDepth`. */\nexport const TEMPLATE_DEPTH_ERROR = \"[Template max depth exceeded]\";\n\n/**\n * Returns the body a template tag resolves to, honoring version bindings.\n */\nexport function getTemplateBody(\n template: Template,\n bindings?: Record<string, string>,\n): string | undefined {\n const boundVersionId = bindings?.[template.name];\n if (boundVersionId && template.versions) {\n const boundVersion = template.versions.find(\n (v) => v.id === boundVersionId || v.version === boundVersionId,\n );\n return boundVersion?.body;\n }\n return getLatestVersion(template)?.body ?? template.body;\n}\n\n/**\n * Recursively resolves template tags `{{template_name}}` in a given text.\n * A tag that would recurse into itself is replaced with `TEMPLATE_LOOP_ERROR`,\n * and a tag nested deeper than `maxDepth` with `TEMPLATE_DEPTH_ERROR`; the rest\n * of the text still resolves normally.\n */\nexport function resolveTemplates(\n text: string,\n templates: Template[] = [],\n options?: ResolveTemplatesOptions,\n): string {\n const templateMap = new Map<string, Template>();\n for (const t of templates) {\n templateMap.set(t.name, t);\n }\n\n const maxDepth = options?.maxDepth ?? 10;\n\n function resolveRecursive(\n currentText: string,\n visited: Set<string>,\n depth: number,\n ): string {\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n\n // A function replacer keeps `$&`, `$'` etc. in template bodies literal.\n return currentText.replace(regex, (fullMatch, rawTemplateName: string) => {\n const templateName = rawTemplateName.trim();\n\n if (visited.has(templateName)) {\n return TEMPLATE_LOOP_ERROR;\n }\n\n const template = templateMap.get(templateName);\n if (!template) {\n return fullMatch;\n }\n\n const versionBody = getTemplateBody(template, options?.bindings);\n if (versionBody === undefined) {\n return fullMatch;\n }\n\n if (depth + 1 > maxDepth) {\n return TEMPLATE_DEPTH_ERROR;\n }\n\n const nextVisited = new Set(visited);\n nextVisited.add(templateName);\n\n return resolveRecursive(versionBody, nextVisited, depth + 1);\n });\n }\n\n return resolveRecursive(text, new Set(), 0);\n}\n\n/**\n * Replaces `{variableName}` with values from a dictionary.\n */\nexport function resolveVariables(\n text: string,\n values: Record<string, string | number>,\n): string {\n if (!text) return \"\";\n return text.replace(\n new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags),\n (fullMatch, varName) => {\n const key = varName.trim();\n if (Object.prototype.hasOwnProperty.call(values, key)) {\n return String(values[key]);\n }\n return fullMatch;\n },\n );\n}\n\n/**\n * Extracts unique template names referenced in text.\n */\nexport function extractTemplateNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique variable names referenced in text.\n */\nexport function extractVariableNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique instruction names referenced in text.\n */\nexport function extractInstructionNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(INSTRUCTION_REGEX.source, INSTRUCTION_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n","import { TEMPLATE_REGEX, Template } from \"./tokens\";\nimport {\n getTemplateBody,\n resolveTemplates,\n type ResolveTemplatesOptions,\n} from \"./resolver\";\n\nexport interface SourceMapSegment {\n resolvedStart: number;\n resolvedEnd: number;\n source: \"raw\" | \"template\";\n rawStart: number;\n rawEnd: number;\n templateName?: string;\n}\n\n/**\n * Resolves templates in text while tracking character offset mapping\n * between the raw input and the fully resolved output.\n */\nexport function resolveTemplatesWithMapping(\n text: string,\n templates: Template[] = [],\n templateBindings?: Record<string, string>,\n options?: Pick<ResolveTemplatesOptions, \"maxDepth\">,\n): { text: string; map: SourceMapSegment[] } {\n if (!templates || templates.length === 0 || !text.includes(\"{{\")) {\n return {\n text,\n map: [\n {\n resolvedStart: 0,\n resolvedEnd: text.length,\n source: \"raw\",\n rawStart: 0,\n rawEnd: text.length,\n },\n ],\n };\n }\n\n const templateMap = new Map(templates.map((t) => [t.name, t]));\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n let lastIndex = 0;\n let resolvedText = \"\";\n const map: SourceMapSegment[] = [];\n\n while ((match = regex.exec(text)) !== null) {\n const [fullMatch, rawTemplateName] = match;\n const templateName = rawTemplateName.trim();\n const matchIndex = match.index;\n\n // Add raw text preceding this template match\n if (matchIndex > lastIndex) {\n const rawSegment = text.substring(lastIndex, matchIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: matchIndex,\n });\n resolvedText += rawSegment;\n }\n\n let resolvedBody = fullMatch;\n let isTemplate = false;\n\n const template = templateMap.get(templateName);\n if (template && getTemplateBody(template, templateBindings) !== undefined) {\n // Resolve the tag itself so loop and depth handling match resolveTemplates.\n resolvedBody = resolveTemplates(fullMatch, templates, {\n bindings: templateBindings,\n maxDepth: options?.maxDepth,\n });\n isTemplate = true;\n }\n\n if (isTemplate) {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + resolvedBody.length,\n source: \"template\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n templateName,\n });\n resolvedText += resolvedBody;\n } else {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + fullMatch.length,\n source: \"raw\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n });\n resolvedText += fullMatch;\n }\n\n lastIndex = matchIndex + fullMatch.length;\n }\n\n // Add any remaining raw text after the final match\n if (lastIndex < text.length) {\n const rawSegment = text.substring(lastIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: text.length,\n });\n resolvedText += rawSegment;\n }\n\n return { text: resolvedText, map };\n}\n\n/**\n * Maps an offset in resolved text back to the corresponding offset in raw text.\n */\nexport function mapResolvedOffsetToRaw(\n resolvedOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? resolvedOffset >= seg.resolvedStart && resolvedOffset <= seg.resolvedEnd\n : resolvedOffset >= seg.resolvedStart && resolvedOffset < seg.resolvedEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.rawStart + (resolvedOffset - seg.resolvedStart);\n }\n // The end of the text maps to the end of the tag; anywhere inside a\n // resolved template maps to the start of its tag.\n if (resolvedOffset === seg.resolvedEnd) {\n return seg.rawEnd;\n }\n return seg.rawStart;\n }\n }\n return resolvedOffset;\n}\n\n/**\n * Maps an offset in raw text to the corresponding offset in resolved text.\n */\nexport function mapRawOffsetToResolved(\n rawOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? rawOffset >= seg.rawStart && rawOffset <= seg.rawEnd\n : rawOffset >= seg.rawStart && rawOffset < seg.rawEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.resolvedStart + (rawOffset - seg.rawStart);\n }\n if (rawOffset === seg.rawEnd) {\n return seg.resolvedEnd;\n }\n return seg.resolvedStart;\n }\n }\n return rawOffset;\n}\n"],"mappings":"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","names":[]}
@@ -15,9 +15,19 @@ interface ResolveTemplatesOptions {
15
15
  bindings?: Record<string, string>;
16
16
  maxDepth?: number;
17
17
  }
18
+ /** Marker substituted for a template tag that would recurse into itself. */
19
+ declare const TEMPLATE_LOOP_ERROR = "[Template loop detected]";
20
+ /** Marker substituted for a template tag nested deeper than `maxDepth`. */
21
+ declare const TEMPLATE_DEPTH_ERROR = "[Template max depth exceeded]";
22
+ /**
23
+ * Returns the body a template tag resolves to, honoring version bindings.
24
+ */
25
+ declare function getTemplateBody(template: Template, bindings?: Record<string, string>): string | undefined;
18
26
  /**
19
27
  * Recursively resolves template tags `{{template_name}}` in a given text.
20
- * Detects circular dependencies and returns "[Template loop detected]".
28
+ * A tag that would recurse into itself is replaced with `TEMPLATE_LOOP_ERROR`,
29
+ * and a tag nested deeper than `maxDepth` with `TEMPLATE_DEPTH_ERROR`; the rest
30
+ * of the text still resolves normally.
21
31
  */
22
32
  declare function resolveTemplates(text: string, templates?: Template[], options?: ResolveTemplatesOptions): string;
23
33
  /**
@@ -49,7 +59,7 @@ interface SourceMapSegment {
49
59
  * Resolves templates in text while tracking character offset mapping
50
60
  * between the raw input and the fully resolved output.
51
61
  */
52
- declare function resolveTemplatesWithMapping(text: string, templates?: Template[], templateBindings?: Record<string, string>): {
62
+ declare function resolveTemplatesWithMapping(text: string, templates?: Template[], templateBindings?: Record<string, string>, options?: Pick<ResolveTemplatesOptions, "maxDepth">): {
53
63
  text: string;
54
64
  map: SourceMapSegment[];
55
65
  };
@@ -62,4 +72,4 @@ declare function mapResolvedOffsetToRaw(resolvedOffset: number, map: SourceMapSe
62
72
  */
63
73
  declare function mapRawOffsetToResolved(rawOffset: number, map: SourceMapSegment[]): number;
64
74
 
65
- export { type ResolveTemplatesOptions, type SourceMapSegment, Template, TemplateVersion, Token, extractInstructionNames, extractTemplateNames, extractVariableNames, getLatestVersion, mapRawOffsetToResolved, mapResolvedOffsetToRaw, resolveTemplates, resolveTemplatesWithMapping, resolveVariables, tokenizePrompt };
75
+ export { type ResolveTemplatesOptions, type SourceMapSegment, TEMPLATE_DEPTH_ERROR, TEMPLATE_LOOP_ERROR, Template, TemplateVersion, Token, extractInstructionNames, extractTemplateNames, extractVariableNames, getLatestVersion, getTemplateBody, mapRawOffsetToResolved, mapResolvedOffsetToRaw, resolveTemplates, resolveTemplatesWithMapping, resolveVariables, tokenizePrompt };
@@ -15,9 +15,19 @@ interface ResolveTemplatesOptions {
15
15
  bindings?: Record<string, string>;
16
16
  maxDepth?: number;
17
17
  }
18
+ /** Marker substituted for a template tag that would recurse into itself. */
19
+ declare const TEMPLATE_LOOP_ERROR = "[Template loop detected]";
20
+ /** Marker substituted for a template tag nested deeper than `maxDepth`. */
21
+ declare const TEMPLATE_DEPTH_ERROR = "[Template max depth exceeded]";
22
+ /**
23
+ * Returns the body a template tag resolves to, honoring version bindings.
24
+ */
25
+ declare function getTemplateBody(template: Template, bindings?: Record<string, string>): string | undefined;
18
26
  /**
19
27
  * Recursively resolves template tags `{{template_name}}` in a given text.
20
- * Detects circular dependencies and returns "[Template loop detected]".
28
+ * A tag that would recurse into itself is replaced with `TEMPLATE_LOOP_ERROR`,
29
+ * and a tag nested deeper than `maxDepth` with `TEMPLATE_DEPTH_ERROR`; the rest
30
+ * of the text still resolves normally.
21
31
  */
22
32
  declare function resolveTemplates(text: string, templates?: Template[], options?: ResolveTemplatesOptions): string;
23
33
  /**
@@ -49,7 +59,7 @@ interface SourceMapSegment {
49
59
  * Resolves templates in text while tracking character offset mapping
50
60
  * between the raw input and the fully resolved output.
51
61
  */
52
- declare function resolveTemplatesWithMapping(text: string, templates?: Template[], templateBindings?: Record<string, string>): {
62
+ declare function resolveTemplatesWithMapping(text: string, templates?: Template[], templateBindings?: Record<string, string>, options?: Pick<ResolveTemplatesOptions, "maxDepth">): {
53
63
  text: string;
54
64
  map: SourceMapSegment[];
55
65
  };
@@ -62,4 +72,4 @@ declare function mapResolvedOffsetToRaw(resolvedOffset: number, map: SourceMapSe
62
72
  */
63
73
  declare function mapRawOffsetToResolved(rawOffset: number, map: SourceMapSegment[]): number;
64
74
 
65
- export { type ResolveTemplatesOptions, type SourceMapSegment, Template, TemplateVersion, Token, extractInstructionNames, extractTemplateNames, extractVariableNames, getLatestVersion, mapRawOffsetToResolved, mapResolvedOffsetToRaw, resolveTemplates, resolveTemplatesWithMapping, resolveVariables, tokenizePrompt };
75
+ export { type ResolveTemplatesOptions, type SourceMapSegment, TEMPLATE_DEPTH_ERROR, TEMPLATE_LOOP_ERROR, Template, TemplateVersion, Token, extractInstructionNames, extractTemplateNames, extractVariableNames, getLatestVersion, getTemplateBody, mapRawOffsetToResolved, mapResolvedOffsetToRaw, resolveTemplates, resolveTemplatesWithMapping, resolveVariables, tokenizePrompt };
@@ -116,6 +116,18 @@ function getLatestVersion(template) {
116
116
  }
117
117
  return template.versions[template.versions.length - 1];
118
118
  }
119
+ var TEMPLATE_LOOP_ERROR = "[Template loop detected]";
120
+ var TEMPLATE_DEPTH_ERROR = "[Template max depth exceeded]";
121
+ function getTemplateBody(template, bindings) {
122
+ const boundVersionId = bindings?.[template.name];
123
+ if (boundVersionId && template.versions) {
124
+ const boundVersion = template.versions.find(
125
+ (v) => v.id === boundVersionId || v.version === boundVersionId
126
+ );
127
+ return boundVersion?.body;
128
+ }
129
+ return getLatestVersion(template)?.body ?? template.body;
130
+ }
119
131
  function resolveTemplates(text, templates = [], options) {
120
132
  const templateMap = /* @__PURE__ */ new Map();
121
133
  for (const t of templates) {
@@ -123,50 +135,27 @@ function resolveTemplates(text, templates = [], options) {
123
135
  }
124
136
  const maxDepth = options?.maxDepth ?? 10;
125
137
  function resolveRecursive(currentText, visited, depth) {
126
- if (depth > maxDepth) {
127
- return "[Template max depth exceeded]";
128
- }
129
- let result = currentText;
130
138
  const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
131
- let match;
132
- while ((match = regex.exec(result)) !== null) {
133
- const [fullMatch, rawTemplateName] = match;
139
+ return currentText.replace(regex, (fullMatch, rawTemplateName) => {
134
140
  const templateName = rawTemplateName.trim();
135
141
  if (visited.has(templateName)) {
136
- return "[Template loop detected]";
142
+ return TEMPLATE_LOOP_ERROR;
137
143
  }
138
144
  const template = templateMap.get(templateName);
139
145
  if (!template) {
140
- continue;
141
- }
142
- let versionBody;
143
- const boundVersionId = options?.bindings?.[templateName];
144
- if (boundVersionId && template.versions) {
145
- const boundVersion = template.versions.find(
146
- (v) => v.id === boundVersionId || v.version === boundVersionId
147
- );
148
- versionBody = boundVersion?.body;
149
- } else {
150
- const latest = getLatestVersion(template);
151
- versionBody = latest?.body ?? template.body;
146
+ return fullMatch;
152
147
  }
148
+ const versionBody = getTemplateBody(template, options?.bindings);
153
149
  if (versionBody === void 0) {
154
- continue;
150
+ return fullMatch;
151
+ }
152
+ if (depth + 1 > maxDepth) {
153
+ return TEMPLATE_DEPTH_ERROR;
155
154
  }
156
155
  const nextVisited = new Set(visited);
157
156
  nextVisited.add(templateName);
158
- const resolvedBody = resolveRecursive(
159
- versionBody,
160
- nextVisited,
161
- depth + 1
162
- );
163
- if (resolvedBody === "[Template loop detected]" || resolvedBody === "[Template max depth exceeded]") {
164
- return resolvedBody;
165
- }
166
- result = result.replace(fullMatch, resolvedBody);
167
- regex.lastIndex = 0;
168
- }
169
- return result;
157
+ return resolveRecursive(versionBody, nextVisited, depth + 1);
158
+ });
170
159
  }
171
160
  return resolveRecursive(text, /* @__PURE__ */ new Set(), 0);
172
161
  }
@@ -215,7 +204,7 @@ function extractInstructionNames(text) {
215
204
  }
216
205
 
217
206
  // src/grammar/sourcemap.ts
218
- function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
207
+ function resolveTemplatesWithMapping(text, templates = [], templateBindings, options) {
219
208
  if (!templates || templates.length === 0 || !text.includes("{{")) {
220
209
  return {
221
210
  text,
@@ -231,7 +220,7 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
231
220
  };
232
221
  }
233
222
  const templateMap = new Map(templates.map((t) => [t.name, t]));
234
- const regex = /{{\s*([^}]+?)\s*}}/g;
223
+ const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
235
224
  let match;
236
225
  let lastIndex = 0;
237
226
  let resolvedText = "";
@@ -253,25 +242,13 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
253
242
  }
254
243
  let resolvedBody = fullMatch;
255
244
  let isTemplate = false;
256
- if (templateMap.has(templateName)) {
257
- const template = templateMap.get(templateName);
258
- let versionBody;
259
- const boundVersionId = templateBindings?.[templateName];
260
- if (boundVersionId && template.versions) {
261
- const boundVersion = template.versions.find(
262
- (v) => v.id === boundVersionId || v.version === boundVersionId
263
- );
264
- versionBody = boundVersion?.body;
265
- } else {
266
- const latestVersion = getLatestVersion(template);
267
- versionBody = latestVersion?.body ?? template.body;
268
- }
269
- if (versionBody !== void 0) {
270
- resolvedBody = resolveTemplates(versionBody, templates, {
271
- bindings: templateBindings
272
- });
273
- isTemplate = true;
274
- }
245
+ const template = templateMap.get(templateName);
246
+ if (template && getTemplateBody(template, templateBindings) !== void 0) {
247
+ resolvedBody = resolveTemplates(fullMatch, templates, {
248
+ bindings: templateBindings,
249
+ maxDepth: options?.maxDepth
250
+ });
251
+ isTemplate = true;
275
252
  }
276
253
  if (isTemplate) {
277
254
  map.push({
@@ -316,9 +293,11 @@ function mapResolvedOffsetToRaw(resolvedOffset, map) {
316
293
  if (inRange) {
317
294
  if (seg.source === "raw") {
318
295
  return seg.rawStart + (resolvedOffset - seg.resolvedStart);
319
- } else {
320
- return seg.rawStart;
321
296
  }
297
+ if (resolvedOffset === seg.resolvedEnd) {
298
+ return seg.rawEnd;
299
+ }
300
+ return seg.rawStart;
322
301
  }
323
302
  }
324
303
  return resolvedOffset;
@@ -331,21 +310,26 @@ function mapRawOffsetToResolved(rawOffset, map) {
331
310
  if (inRange) {
332
311
  if (seg.source === "raw") {
333
312
  return seg.resolvedStart + (rawOffset - seg.rawStart);
334
- } else {
335
- return seg.resolvedStart;
336
313
  }
314
+ if (rawOffset === seg.rawEnd) {
315
+ return seg.resolvedEnd;
316
+ }
317
+ return seg.resolvedStart;
337
318
  }
338
319
  }
339
320
  return rawOffset;
340
321
  }
341
322
  export {
342
323
  INSTRUCTION_REGEX,
324
+ TEMPLATE_DEPTH_ERROR,
325
+ TEMPLATE_LOOP_ERROR,
343
326
  TEMPLATE_REGEX,
344
327
  VARIABLE_REGEX,
345
328
  extractInstructionNames,
346
329
  extractTemplateNames,
347
330
  extractVariableNames,
348
331
  getLatestVersion,
332
+ getTemplateBody,
349
333
  instructionRegex,
350
334
  mapRawOffsetToResolved,
351
335
  mapResolvedOffsetToRaw,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/grammar/tokens.ts","../../src/grammar/tokenizer.ts","../../src/grammar/resolver.ts","../../src/grammar/sourcemap.ts"],"sourcesContent":["/**\n * Canonical regex patterns for prompt editing tokens.\n */\nexport const TEMPLATE_REGEX = /{{\\s*([^}]+?)\\s*}}/g;\nexport const VARIABLE_REGEX = /(?<!\\{)\\{([^{}]+)\\}(?!\\})/g;\nexport const INSTRUCTION_REGEX = /__(.+?)__/g;\n\n// Backward-compatible camelCase aliases\nexport const templateRegex = TEMPLATE_REGEX;\nexport const variableRegex = VARIABLE_REGEX;\nexport const instructionRegex = INSTRUCTION_REGEX;\n\nexport type TokenType = \"template\" | \"variable\" | \"instruction\" | \"text\";\n\nexport interface Token {\n type: TokenType;\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\nexport interface TemplateVersion {\n id: string;\n version: string | number;\n body: string;\n createdAt?: any;\n [key: string]: any;\n}\n\nexport interface Template {\n id?: string;\n name: string;\n body?: string;\n latestVersion?: string | number;\n versions?: TemplateVersion[];\n color?: string;\n [key: string]: any;\n}\n\nexport interface ColorGradient {\n from: string;\n to: string;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Token,\n} from \"./tokens\";\n\ninterface RawMatch {\n type: \"template\" | \"variable\" | \"instruction\";\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\n/**\n * Parses any prompt string into an array of typed tokens in linear order.\n * Plain text between tokens is preserved as `{ type: 'text' }`.\n */\nexport function tokenizePrompt(text: string): Token[] {\n if (!text) {\n return [];\n }\n\n const rawMatches: RawMatch[] = [];\n\n // Match {{template}}\n const tmplRegex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = tmplRegex.exec(text)) !== null) {\n rawMatches.push({\n type: \"template\",\n raw: match[0],\n value: match[1].trim(),\n start: match.index,\n end: match.index + match[0].length,\n });\n }\n\n // Match {variable}\n const varRegex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n while ((match = varRegex.exec(text)) !== null) {\n // Avoid double matching if overlapping with template\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"variable\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Match __instruction__\n const instRegex = new RegExp(\n INSTRUCTION_REGEX.source,\n INSTRUCTION_REGEX.flags,\n );\n while ((match = instRegex.exec(text)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"instruction\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Sort matches by start index\n rawMatches.sort((a, b) => a.start - b.start);\n\n const tokens: Token[] = [];\n let currentIdx = 0;\n\n for (const m of rawMatches) {\n if (m.start > currentIdx) {\n const textChunk = text.slice(currentIdx, m.start);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: m.start,\n });\n }\n tokens.push({\n type: m.type,\n raw: m.raw,\n value: m.value,\n start: m.start,\n end: m.end,\n });\n currentIdx = m.end;\n }\n\n if (currentIdx < text.length) {\n const textChunk = text.slice(currentIdx);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: text.length,\n });\n }\n\n return tokens;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Template,\n TemplateVersion,\n} from \"./tokens\";\n\n/**\n * Finds the latest version of a template based on `latestVersion` or last version entry.\n */\nexport function getLatestVersion(\n template: Template,\n): TemplateVersion | undefined {\n if (!template.versions || template.versions.length === 0) {\n if (template.body !== undefined) {\n return {\n id: template.id || \"default\",\n version: \"1.0\",\n body: template.body,\n };\n }\n return undefined;\n }\n if (template.latestVersion) {\n const found = template.versions.find(\n (v) => v.version === template.latestVersion,\n );\n if (found) return found;\n }\n return template.versions[template.versions.length - 1];\n}\n\nexport interface ResolveTemplatesOptions {\n bindings?: Record<string, string>;\n maxDepth?: number;\n}\n\n/**\n * Recursively resolves template tags `{{template_name}}` in a given text.\n * Detects circular dependencies and returns \"[Template loop detected]\".\n */\nexport function resolveTemplates(\n text: string,\n templates: Template[] = [],\n options?: ResolveTemplatesOptions,\n): string {\n const templateMap = new Map<string, Template>();\n for (const t of templates) {\n templateMap.set(t.name, t);\n }\n\n const maxDepth = options?.maxDepth ?? 10;\n\n function resolveRecursive(\n currentText: string,\n visited: Set<string>,\n depth: number,\n ): string {\n if (depth > maxDepth) {\n return \"[Template max depth exceeded]\";\n }\n\n let result = currentText;\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(result)) !== null) {\n const [fullMatch, rawTemplateName] = match;\n const templateName = rawTemplateName.trim();\n\n if (visited.has(templateName)) {\n return \"[Template loop detected]\";\n }\n\n const template = templateMap.get(templateName);\n if (!template) {\n continue;\n }\n\n let versionBody: string | undefined;\n const boundVersionId = options?.bindings?.[templateName];\n\n if (boundVersionId && template.versions) {\n const boundVersion = template.versions.find(\n (v) => v.id === boundVersionId || v.version === boundVersionId,\n );\n versionBody = boundVersion?.body;\n } else {\n const latest = getLatestVersion(template);\n versionBody = latest?.body ?? template.body;\n }\n\n if (versionBody === undefined) {\n continue;\n }\n\n const nextVisited = new Set(visited);\n nextVisited.add(templateName);\n\n const resolvedBody = resolveRecursive(\n versionBody,\n nextVisited,\n depth + 1,\n );\n\n if (\n resolvedBody === \"[Template loop detected]\" ||\n resolvedBody === \"[Template max depth exceeded]\"\n ) {\n return resolvedBody;\n }\n\n result = result.replace(fullMatch, resolvedBody);\n regex.lastIndex = 0;\n }\n\n return result;\n }\n\n return resolveRecursive(text, new Set(), 0);\n}\n\n/**\n * Replaces `{variableName}` with values from a dictionary.\n */\nexport function resolveVariables(\n text: string,\n values: Record<string, string | number>,\n): string {\n if (!text) return \"\";\n return text.replace(\n new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags),\n (fullMatch, varName) => {\n const key = varName.trim();\n if (Object.prototype.hasOwnProperty.call(values, key)) {\n return String(values[key]);\n }\n return fullMatch;\n },\n );\n}\n\n/**\n * Extracts unique template names referenced in text.\n */\nexport function extractTemplateNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique variable names referenced in text.\n */\nexport function extractVariableNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique instruction names referenced in text.\n */\nexport function extractInstructionNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(INSTRUCTION_REGEX.source, INSTRUCTION_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n","import { Template } from \"./tokens\";\nimport { getLatestVersion, resolveTemplates } from \"./resolver\";\n\nexport interface SourceMapSegment {\n resolvedStart: number;\n resolvedEnd: number;\n source: \"raw\" | \"template\";\n rawStart: number;\n rawEnd: number;\n templateName?: string;\n}\n\n/**\n * Resolves templates in text while tracking character offset mapping\n * between the raw input and the fully resolved output.\n */\nexport function resolveTemplatesWithMapping(\n text: string,\n templates: Template[] = [],\n templateBindings?: Record<string, string>,\n): { text: string; map: SourceMapSegment[] } {\n if (!templates || templates.length === 0 || !text.includes(\"{{\")) {\n return {\n text,\n map: [\n {\n resolvedStart: 0,\n resolvedEnd: text.length,\n source: \"raw\",\n rawStart: 0,\n rawEnd: text.length,\n },\n ],\n };\n }\n\n const templateMap = new Map(templates.map((t) => [t.name, t]));\n const regex = /{{\\s*([^}]+?)\\s*}}/g;\n let match: RegExpExecArray | null;\n let lastIndex = 0;\n let resolvedText = \"\";\n const map: SourceMapSegment[] = [];\n\n while ((match = regex.exec(text)) !== null) {\n const [fullMatch, rawTemplateName] = match;\n const templateName = rawTemplateName.trim();\n const matchIndex = match.index;\n\n // Add raw text preceding this template match\n if (matchIndex > lastIndex) {\n const rawSegment = text.substring(lastIndex, matchIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: matchIndex,\n });\n resolvedText += rawSegment;\n }\n\n let resolvedBody = fullMatch;\n let isTemplate = false;\n\n if (templateMap.has(templateName)) {\n const template = templateMap.get(templateName)!;\n let versionBody: string | undefined;\n const boundVersionId = templateBindings?.[templateName];\n\n if (boundVersionId && template.versions) {\n const boundVersion = template.versions.find(\n (v) => v.id === boundVersionId || v.version === boundVersionId,\n );\n versionBody = boundVersion?.body;\n } else {\n const latestVersion = getLatestVersion(template);\n versionBody = latestVersion?.body ?? template.body;\n }\n\n if (versionBody !== undefined) {\n resolvedBody = resolveTemplates(versionBody, templates, {\n bindings: templateBindings,\n });\n isTemplate = true;\n }\n }\n\n if (isTemplate) {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + resolvedBody.length,\n source: \"template\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n templateName,\n });\n resolvedText += resolvedBody;\n } else {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + fullMatch.length,\n source: \"raw\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n });\n resolvedText += fullMatch;\n }\n\n lastIndex = matchIndex + fullMatch.length;\n }\n\n // Add any remaining raw text after the final match\n if (lastIndex < text.length) {\n const rawSegment = text.substring(lastIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: text.length,\n });\n resolvedText += rawSegment;\n }\n\n return { text: resolvedText, map };\n}\n\n/**\n * Maps an offset in resolved text back to the corresponding offset in raw text.\n */\nexport function mapResolvedOffsetToRaw(\n resolvedOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? resolvedOffset >= seg.resolvedStart && resolvedOffset <= seg.resolvedEnd\n : resolvedOffset >= seg.resolvedStart && resolvedOffset < seg.resolvedEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.rawStart + (resolvedOffset - seg.resolvedStart);\n } else {\n // Pointing into a resolved template - return start of template tag\n return seg.rawStart;\n }\n }\n }\n return resolvedOffset;\n}\n\n/**\n * Maps an offset in raw text to the corresponding offset in resolved text.\n */\nexport function mapRawOffsetToResolved(\n rawOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? rawOffset >= seg.rawStart && rawOffset <= seg.rawEnd\n : rawOffset >= seg.rawStart && rawOffset < seg.rawEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.resolvedStart + (rawOffset - seg.rawStart);\n } else {\n return seg.resolvedStart;\n }\n }\n }\n return rawOffset;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/grammar/tokens.ts","../../src/grammar/tokenizer.ts","../../src/grammar/resolver.ts","../../src/grammar/sourcemap.ts"],"sourcesContent":["/**\n * Canonical regex patterns for prompt editing tokens.\n */\nexport const TEMPLATE_REGEX = /{{\\s*([^}]+?)\\s*}}/g;\nexport const VARIABLE_REGEX = /(?<!\\{)\\{([^{}]+)\\}(?!\\})/g;\nexport const INSTRUCTION_REGEX = /__(.+?)__/g;\n\n// Backward-compatible camelCase aliases\nexport const templateRegex = TEMPLATE_REGEX;\nexport const variableRegex = VARIABLE_REGEX;\nexport const instructionRegex = INSTRUCTION_REGEX;\n\nexport type TokenType = \"template\" | \"variable\" | \"instruction\" | \"text\";\n\nexport interface Token {\n type: TokenType;\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\nexport interface TemplateVersion {\n id: string;\n version: string | number;\n body: string;\n createdAt?: any;\n [key: string]: any;\n}\n\nexport interface Template {\n id?: string;\n name: string;\n body?: string;\n latestVersion?: string | number;\n versions?: TemplateVersion[];\n color?: string;\n [key: string]: any;\n}\n\nexport interface ColorGradient {\n from: string;\n to: string;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Token,\n} from \"./tokens\";\n\ninterface RawMatch {\n type: \"template\" | \"variable\" | \"instruction\";\n raw: string;\n value: string;\n start: number;\n end: number;\n}\n\n/**\n * Parses any prompt string into an array of typed tokens in linear order.\n * Plain text between tokens is preserved as `{ type: 'text' }`.\n */\nexport function tokenizePrompt(text: string): Token[] {\n if (!text) {\n return [];\n }\n\n const rawMatches: RawMatch[] = [];\n\n // Match {{template}}\n const tmplRegex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = tmplRegex.exec(text)) !== null) {\n rawMatches.push({\n type: \"template\",\n raw: match[0],\n value: match[1].trim(),\n start: match.index,\n end: match.index + match[0].length,\n });\n }\n\n // Match {variable}\n const varRegex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n while ((match = varRegex.exec(text)) !== null) {\n // Avoid double matching if overlapping with template\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"variable\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Match __instruction__\n const instRegex = new RegExp(\n INSTRUCTION_REGEX.source,\n INSTRUCTION_REGEX.flags,\n );\n while ((match = instRegex.exec(text)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n const overlaps = rawMatches.some(\n (m) => !(end <= m.start || start >= m.end),\n );\n if (!overlaps) {\n rawMatches.push({\n type: \"instruction\",\n raw: match[0],\n value: match[1].trim(),\n start,\n end,\n });\n }\n }\n\n // Sort matches by start index\n rawMatches.sort((a, b) => a.start - b.start);\n\n const tokens: Token[] = [];\n let currentIdx = 0;\n\n for (const m of rawMatches) {\n if (m.start > currentIdx) {\n const textChunk = text.slice(currentIdx, m.start);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: m.start,\n });\n }\n tokens.push({\n type: m.type,\n raw: m.raw,\n value: m.value,\n start: m.start,\n end: m.end,\n });\n currentIdx = m.end;\n }\n\n if (currentIdx < text.length) {\n const textChunk = text.slice(currentIdx);\n tokens.push({\n type: \"text\",\n raw: textChunk,\n value: textChunk,\n start: currentIdx,\n end: text.length,\n });\n }\n\n return tokens;\n}\n","import {\n TEMPLATE_REGEX,\n VARIABLE_REGEX,\n INSTRUCTION_REGEX,\n Template,\n TemplateVersion,\n} from \"./tokens\";\n\n/**\n * Finds the latest version of a template based on `latestVersion` or last version entry.\n */\nexport function getLatestVersion(\n template: Template,\n): TemplateVersion | undefined {\n if (!template.versions || template.versions.length === 0) {\n if (template.body !== undefined) {\n return {\n id: template.id || \"default\",\n version: \"1.0\",\n body: template.body,\n };\n }\n return undefined;\n }\n if (template.latestVersion) {\n const found = template.versions.find(\n (v) => v.version === template.latestVersion,\n );\n if (found) return found;\n }\n return template.versions[template.versions.length - 1];\n}\n\nexport interface ResolveTemplatesOptions {\n bindings?: Record<string, string>;\n maxDepth?: number;\n}\n\n/** Marker substituted for a template tag that would recurse into itself. */\nexport const TEMPLATE_LOOP_ERROR = \"[Template loop detected]\";\n/** Marker substituted for a template tag nested deeper than `maxDepth`. */\nexport const TEMPLATE_DEPTH_ERROR = \"[Template max depth exceeded]\";\n\n/**\n * Returns the body a template tag resolves to, honoring version bindings.\n */\nexport function getTemplateBody(\n template: Template,\n bindings?: Record<string, string>,\n): string | undefined {\n const boundVersionId = bindings?.[template.name];\n if (boundVersionId && template.versions) {\n const boundVersion = template.versions.find(\n (v) => v.id === boundVersionId || v.version === boundVersionId,\n );\n return boundVersion?.body;\n }\n return getLatestVersion(template)?.body ?? template.body;\n}\n\n/**\n * Recursively resolves template tags `{{template_name}}` in a given text.\n * A tag that would recurse into itself is replaced with `TEMPLATE_LOOP_ERROR`,\n * and a tag nested deeper than `maxDepth` with `TEMPLATE_DEPTH_ERROR`; the rest\n * of the text still resolves normally.\n */\nexport function resolveTemplates(\n text: string,\n templates: Template[] = [],\n options?: ResolveTemplatesOptions,\n): string {\n const templateMap = new Map<string, Template>();\n for (const t of templates) {\n templateMap.set(t.name, t);\n }\n\n const maxDepth = options?.maxDepth ?? 10;\n\n function resolveRecursive(\n currentText: string,\n visited: Set<string>,\n depth: number,\n ): string {\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n\n // A function replacer keeps `$&`, `$'` etc. in template bodies literal.\n return currentText.replace(regex, (fullMatch, rawTemplateName: string) => {\n const templateName = rawTemplateName.trim();\n\n if (visited.has(templateName)) {\n return TEMPLATE_LOOP_ERROR;\n }\n\n const template = templateMap.get(templateName);\n if (!template) {\n return fullMatch;\n }\n\n const versionBody = getTemplateBody(template, options?.bindings);\n if (versionBody === undefined) {\n return fullMatch;\n }\n\n if (depth + 1 > maxDepth) {\n return TEMPLATE_DEPTH_ERROR;\n }\n\n const nextVisited = new Set(visited);\n nextVisited.add(templateName);\n\n return resolveRecursive(versionBody, nextVisited, depth + 1);\n });\n }\n\n return resolveRecursive(text, new Set(), 0);\n}\n\n/**\n * Replaces `{variableName}` with values from a dictionary.\n */\nexport function resolveVariables(\n text: string,\n values: Record<string, string | number>,\n): string {\n if (!text) return \"\";\n return text.replace(\n new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags),\n (fullMatch, varName) => {\n const key = varName.trim();\n if (Object.prototype.hasOwnProperty.call(values, key)) {\n return String(values[key]);\n }\n return fullMatch;\n },\n );\n}\n\n/**\n * Extracts unique template names referenced in text.\n */\nexport function extractTemplateNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique variable names referenced in text.\n */\nexport function extractVariableNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n\n/**\n * Extracts unique instruction names referenced in text.\n */\nexport function extractInstructionNames(text: string): string[] {\n if (!text) return [];\n const set = new Set<string>();\n const regex = new RegExp(INSTRUCTION_REGEX.source, INSTRUCTION_REGEX.flags);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n set.add(match[1].trim());\n }\n return Array.from(set);\n}\n","import { TEMPLATE_REGEX, Template } from \"./tokens\";\nimport {\n getTemplateBody,\n resolveTemplates,\n type ResolveTemplatesOptions,\n} from \"./resolver\";\n\nexport interface SourceMapSegment {\n resolvedStart: number;\n resolvedEnd: number;\n source: \"raw\" | \"template\";\n rawStart: number;\n rawEnd: number;\n templateName?: string;\n}\n\n/**\n * Resolves templates in text while tracking character offset mapping\n * between the raw input and the fully resolved output.\n */\nexport function resolveTemplatesWithMapping(\n text: string,\n templates: Template[] = [],\n templateBindings?: Record<string, string>,\n options?: Pick<ResolveTemplatesOptions, \"maxDepth\">,\n): { text: string; map: SourceMapSegment[] } {\n if (!templates || templates.length === 0 || !text.includes(\"{{\")) {\n return {\n text,\n map: [\n {\n resolvedStart: 0,\n resolvedEnd: text.length,\n source: \"raw\",\n rawStart: 0,\n rawEnd: text.length,\n },\n ],\n };\n }\n\n const templateMap = new Map(templates.map((t) => [t.name, t]));\n const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);\n let match: RegExpExecArray | null;\n let lastIndex = 0;\n let resolvedText = \"\";\n const map: SourceMapSegment[] = [];\n\n while ((match = regex.exec(text)) !== null) {\n const [fullMatch, rawTemplateName] = match;\n const templateName = rawTemplateName.trim();\n const matchIndex = match.index;\n\n // Add raw text preceding this template match\n if (matchIndex > lastIndex) {\n const rawSegment = text.substring(lastIndex, matchIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: matchIndex,\n });\n resolvedText += rawSegment;\n }\n\n let resolvedBody = fullMatch;\n let isTemplate = false;\n\n const template = templateMap.get(templateName);\n if (template && getTemplateBody(template, templateBindings) !== undefined) {\n // Resolve the tag itself so loop and depth handling match resolveTemplates.\n resolvedBody = resolveTemplates(fullMatch, templates, {\n bindings: templateBindings,\n maxDepth: options?.maxDepth,\n });\n isTemplate = true;\n }\n\n if (isTemplate) {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + resolvedBody.length,\n source: \"template\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n templateName,\n });\n resolvedText += resolvedBody;\n } else {\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + fullMatch.length,\n source: \"raw\",\n rawStart: matchIndex,\n rawEnd: matchIndex + fullMatch.length,\n });\n resolvedText += fullMatch;\n }\n\n lastIndex = matchIndex + fullMatch.length;\n }\n\n // Add any remaining raw text after the final match\n if (lastIndex < text.length) {\n const rawSegment = text.substring(lastIndex);\n map.push({\n resolvedStart: resolvedText.length,\n resolvedEnd: resolvedText.length + rawSegment.length,\n source: \"raw\",\n rawStart: lastIndex,\n rawEnd: text.length,\n });\n resolvedText += rawSegment;\n }\n\n return { text: resolvedText, map };\n}\n\n/**\n * Maps an offset in resolved text back to the corresponding offset in raw text.\n */\nexport function mapResolvedOffsetToRaw(\n resolvedOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? resolvedOffset >= seg.resolvedStart && resolvedOffset <= seg.resolvedEnd\n : resolvedOffset >= seg.resolvedStart && resolvedOffset < seg.resolvedEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.rawStart + (resolvedOffset - seg.resolvedStart);\n }\n // The end of the text maps to the end of the tag; anywhere inside a\n // resolved template maps to the start of its tag.\n if (resolvedOffset === seg.resolvedEnd) {\n return seg.rawEnd;\n }\n return seg.rawStart;\n }\n }\n return resolvedOffset;\n}\n\n/**\n * Maps an offset in raw text to the corresponding offset in resolved text.\n */\nexport function mapRawOffsetToResolved(\n rawOffset: number,\n map: SourceMapSegment[],\n): number {\n for (let i = 0; i < map.length; i++) {\n const seg = map[i];\n const isLast = i === map.length - 1;\n const inRange = isLast\n ? rawOffset >= seg.rawStart && rawOffset <= seg.rawEnd\n : rawOffset >= seg.rawStart && rawOffset < seg.rawEnd;\n\n if (inRange) {\n if (seg.source === \"raw\") {\n return seg.resolvedStart + (rawOffset - seg.rawStart);\n }\n if (rawOffset === seg.rawEnd) {\n return seg.resolvedEnd;\n }\n return seg.resolvedStart;\n }\n }\n return rawOffset;\n}\n"],"mappings":"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","names":[]}