@zihanw/pi-forge 0.2.0 → 0.3.1

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 (161) hide show
  1. package/README.md +233 -105
  2. package/README.zh-CN.md +215 -104
  3. package/dist/compiler.d.ts +10 -0
  4. package/dist/compiler.d.ts.map +1 -0
  5. package/dist/compiler.js +490 -0
  6. package/dist/compiler.js.map +1 -0
  7. package/dist/extension-registry.d.ts +25 -0
  8. package/dist/extension-registry.d.ts.map +1 -0
  9. package/dist/extension-registry.js +6 -0
  10. package/dist/extension-registry.js.map +1 -0
  11. package/dist/index.d.ts +7 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +364 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lifecycle.d.ts +14 -0
  16. package/dist/lifecycle.d.ts.map +1 -0
  17. package/dist/lifecycle.js +139 -0
  18. package/dist/lifecycle.js.map +1 -0
  19. package/dist/loader.d.ts +8 -0
  20. package/dist/loader.d.ts.map +1 -0
  21. package/dist/loader.js +345 -0
  22. package/dist/loader.js.map +1 -0
  23. package/dist/macro-engine.d.ts +23 -0
  24. package/dist/macro-engine.d.ts.map +1 -0
  25. package/dist/macro-engine.js +262 -0
  26. package/dist/macro-engine.js.map +1 -0
  27. package/dist/payload-capture.d.ts +15 -0
  28. package/dist/payload-capture.d.ts.map +1 -0
  29. package/dist/payload-capture.js +86 -0
  30. package/dist/payload-capture.js.map +1 -0
  31. package/dist/payload-command.d.ts +10 -0
  32. package/dist/payload-command.d.ts.map +1 -0
  33. package/dist/payload-command.js +112 -0
  34. package/dist/payload-command.js.map +1 -0
  35. package/dist/policy.d.ts +6 -0
  36. package/dist/policy.d.ts.map +1 -0
  37. package/dist/policy.js +38 -0
  38. package/dist/policy.js.map +1 -0
  39. package/dist/preset-command.d.ts +14 -0
  40. package/dist/preset-command.d.ts.map +1 -0
  41. package/dist/preset-command.js +223 -0
  42. package/dist/preset-command.js.map +1 -0
  43. package/dist/preview.d.ts +12 -0
  44. package/dist/preview.d.ts.map +1 -0
  45. package/dist/preview.js +179 -0
  46. package/dist/preview.js.map +1 -0
  47. package/dist/regex.d.ts +7 -0
  48. package/dist/regex.d.ts.map +1 -0
  49. package/dist/regex.js +432 -0
  50. package/dist/regex.js.map +1 -0
  51. package/dist/render-helpers.d.ts +44 -0
  52. package/dist/render-helpers.d.ts.map +1 -0
  53. package/dist/render-helpers.js +128 -0
  54. package/dist/render-helpers.js.map +1 -0
  55. package/dist/runtime-state.d.ts +24 -0
  56. package/dist/runtime-state.d.ts.map +1 -0
  57. package/dist/runtime-state.js +13 -0
  58. package/dist/runtime-state.js.map +1 -0
  59. package/dist/sillytavern-importer/items.d.ts +3 -0
  60. package/dist/sillytavern-importer/items.d.ts.map +1 -0
  61. package/dist/sillytavern-importer/items.js +88 -0
  62. package/dist/sillytavern-importer/items.js.map +1 -0
  63. package/dist/sillytavern-importer/macros.d.ts +15 -0
  64. package/dist/sillytavern-importer/macros.d.ts.map +1 -0
  65. package/dist/sillytavern-importer/macros.js +141 -0
  66. package/dist/sillytavern-importer/macros.js.map +1 -0
  67. package/dist/sillytavern-importer/prompt-order.d.ts +6 -0
  68. package/dist/sillytavern-importer/prompt-order.d.ts.map +1 -0
  69. package/dist/sillytavern-importer/prompt-order.js +38 -0
  70. package/dist/sillytavern-importer/prompt-order.js.map +1 -0
  71. package/dist/sillytavern-importer/regex.d.ts +3 -0
  72. package/dist/sillytavern-importer/regex.d.ts.map +1 -0
  73. package/dist/sillytavern-importer/regex.js +275 -0
  74. package/dist/sillytavern-importer/regex.js.map +1 -0
  75. package/dist/sillytavern-importer/report.d.ts +21 -0
  76. package/dist/sillytavern-importer/report.d.ts.map +1 -0
  77. package/dist/sillytavern-importer/report.js +166 -0
  78. package/dist/sillytavern-importer/report.js.map +1 -0
  79. package/dist/sillytavern-importer/types.d.ts +106 -0
  80. package/dist/sillytavern-importer/types.d.ts.map +1 -0
  81. package/dist/sillytavern-importer/types.js +2 -0
  82. package/dist/sillytavern-importer/types.js.map +1 -0
  83. package/dist/sillytavern-importer.d.ts +5 -0
  84. package/dist/sillytavern-importer.d.ts.map +1 -0
  85. package/dist/sillytavern-importer.js +117 -0
  86. package/dist/sillytavern-importer.js.map +1 -0
  87. package/dist/slot-renderers.d.ts +25 -0
  88. package/dist/slot-renderers.d.ts.map +1 -0
  89. package/dist/slot-renderers.js +334 -0
  90. package/dist/slot-renderers.js.map +1 -0
  91. package/dist/stack-migration.d.ts +29 -0
  92. package/dist/stack-migration.d.ts.map +1 -0
  93. package/dist/stack-migration.js +126 -0
  94. package/dist/stack-migration.js.map +1 -0
  95. package/dist/storage.d.ts +6 -0
  96. package/dist/storage.d.ts.map +1 -0
  97. package/dist/storage.js +23 -0
  98. package/dist/storage.js.map +1 -0
  99. package/dist/types.d.ts +173 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +2 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/web-editor/index.d.ts +3 -0
  104. package/dist/web-editor/index.d.ts.map +1 -0
  105. package/dist/web-editor/index.js +2 -0
  106. package/dist/web-editor/index.js.map +1 -0
  107. package/dist/web-editor/page.d.ts +2 -0
  108. package/dist/web-editor/page.d.ts.map +1 -0
  109. package/dist/web-editor/page.js +3331 -0
  110. package/dist/web-editor/page.js.map +1 -0
  111. package/dist/web-editor/server.d.ts +4 -0
  112. package/dist/web-editor/server.d.ts.map +1 -0
  113. package/dist/web-editor/server.js +263 -0
  114. package/dist/web-editor/server.js.map +1 -0
  115. package/dist/web-editor/types.d.ts +125 -0
  116. package/dist/web-editor/types.d.ts.map +1 -0
  117. package/dist/web-editor/types.js +2 -0
  118. package/dist/web-editor/types.js.map +1 -0
  119. package/dist/web-host.d.ts +29 -0
  120. package/dist/web-host.d.ts.map +1 -0
  121. package/dist/web-host.js +174 -0
  122. package/dist/web-host.js.map +1 -0
  123. package/examples/custom-system-status-extension/README.md +78 -0
  124. package/examples/custom-system-status-extension/index.ts +176 -0
  125. package/examples/custom-system-status-extension/prompt-stack.json +39 -0
  126. package/examples/default-prompt-stack.json +52 -93
  127. package/examples/image-reader-prompt-stack.json +124 -0
  128. package/examples/reviewer-prompt-stack.json +115 -0
  129. package/examples/sillytavern-dm-writer-prompt-stack.json +190 -0
  130. package/package.json +30 -9
  131. package/src/compiler.ts +321 -420
  132. package/src/extension-registry.ts +33 -0
  133. package/src/index.ts +310 -1110
  134. package/src/lifecycle.ts +171 -0
  135. package/src/loader.ts +141 -56
  136. package/src/macro-engine.ts +358 -0
  137. package/src/payload-capture.ts +85 -0
  138. package/src/payload-command.ts +138 -0
  139. package/src/policy.ts +42 -0
  140. package/src/preset-command.ts +271 -0
  141. package/src/preview.ts +226 -0
  142. package/src/regex.ts +500 -0
  143. package/src/render-helpers.ts +169 -0
  144. package/src/runtime-state.ts +36 -0
  145. package/src/sillytavern-importer/items.ts +98 -0
  146. package/src/sillytavern-importer/macros.ts +159 -0
  147. package/src/sillytavern-importer/prompt-order.ts +54 -0
  148. package/src/sillytavern-importer/regex.ts +270 -0
  149. package/src/sillytavern-importer/report.ts +202 -0
  150. package/src/sillytavern-importer/types.ts +120 -0
  151. package/src/sillytavern-importer.ts +57 -482
  152. package/src/slot-renderers.ts +414 -0
  153. package/src/stack-migration.ts +159 -0
  154. package/src/storage.ts +28 -0
  155. package/src/types.ts +83 -52
  156. package/src/web-editor/index.ts +2 -0
  157. package/src/web-editor/page.ts +3330 -0
  158. package/src/web-editor/server.ts +294 -0
  159. package/src/web-editor/types.ts +98 -0
  160. package/src/web-host.ts +232 -0
  161. package/src/web-editor.ts +0 -1491
@@ -0,0 +1,179 @@
1
+ import { agentMessageToPreviewText, compileMessages, compileSystemPrompt, createPromptVariableStore, getLatestUserMessage, } from "./compiler.js";
2
+ import { estimatePayloadTokens } from "./payload-capture.js";
3
+ export function renderPreview(ctx, target, sessionVariables) {
4
+ return buildPreview(ctx, target, sessionVariables).text;
5
+ }
6
+ export function buildPreview(ctx, target, sessionVariables, optionsOverride) {
7
+ const options = optionsOverride ?? ctx.getSystemPromptOptions();
8
+ const sessionMessages = getPreviewSessionMessages(ctx);
9
+ const latestUserMessage = getLatestUserMessage(sessionMessages);
10
+ const previewVariables = createPromptVariableStore(sessionVariables);
11
+ const runtime = { options, ctx, latestUserMessage, now: new Date(), variables: previewVariables };
12
+ const system = compileSystemPrompt(target.stack, runtime, ctx.getSystemPrompt());
13
+ const messages = compileMessages(target.stack, runtime, sessionMessages);
14
+ const diagnostics = [...target.diagnostics, ...system.diagnostics, ...messages.diagnostics];
15
+ const messageSections = messages.messages.map((message, index) => {
16
+ const content = agentMessageToPreviewText(message);
17
+ const source = messages.messageSources[index];
18
+ return previewSection(`message-${index}`, previewMessageTitle(source, index), content, message.role);
19
+ });
20
+ const systemSection = previewSection("system", "System prompt", system.systemPrompt || "(empty)");
21
+ const totalChars = systemSection.chars + messageSections.reduce((sum, section) => sum + section.chars, 0);
22
+ const preview = {
23
+ stackId: target.stack.id,
24
+ generatedAt: new Date().toISOString(),
25
+ system: systemSection,
26
+ messages: messageSections,
27
+ totalChars,
28
+ approxTokens: estimatePayloadTokens(`${system.systemPrompt}\n${messageSections.map((section) => section.content).join("\n")}`),
29
+ };
30
+ const text = [
31
+ `# Prompt stack preview: ${target.stack.id}`,
32
+ "",
33
+ "## System prompt",
34
+ "",
35
+ system.systemPrompt || "(empty)",
36
+ "",
37
+ "## Message layout",
38
+ "",
39
+ renderPreviewSectionText(messageSections),
40
+ "",
41
+ "## Diagnostics",
42
+ "",
43
+ renderDiagnostics(diagnostics),
44
+ ].join("\n");
45
+ return { text, preview, diagnostics };
46
+ }
47
+ function getPreviewSessionMessages(ctx) {
48
+ const entries = getCurrentBranchEntries(ctx).map(asSessionEntry);
49
+ const compaction = latestCompactionEntry(entries);
50
+ const messages = [];
51
+ const appendMessage = (entry) => {
52
+ if (entry.type === "message" && isAgentMessage(entry.message)) {
53
+ messages.push(entry.message);
54
+ return;
55
+ }
56
+ if (entry.type === "custom_message" && typeof entry.customType === "string") {
57
+ messages.push({
58
+ role: "custom",
59
+ customType: entry.customType,
60
+ content: typeof entry.content === "string" || Array.isArray(entry.content) ? entry.content : "",
61
+ display: entry.display === true,
62
+ details: entry.details,
63
+ timestamp: entryTimestamp(entry),
64
+ });
65
+ return;
66
+ }
67
+ if (entry.type === "branch_summary" && typeof entry.summary === "string" && entry.summary) {
68
+ messages.push({
69
+ role: "branchSummary",
70
+ summary: entry.summary,
71
+ fromId: typeof entry.fromId === "string" ? entry.fromId : "",
72
+ timestamp: entryTimestamp(entry),
73
+ });
74
+ }
75
+ };
76
+ if (compaction && typeof compaction.summary === "string") {
77
+ messages.push({
78
+ role: "compactionSummary",
79
+ summary: compaction.summary,
80
+ tokensBefore: typeof compaction.tokensBefore === "number" ? compaction.tokensBefore : 0,
81
+ timestamp: entryTimestamp(compaction),
82
+ });
83
+ const compactionIndex = entries.findIndex((entry) => entry.type === "compaction" && entry.id === compaction.id);
84
+ let foundFirstKept = false;
85
+ for (let index = 0; index < compactionIndex; index++) {
86
+ const entry = entries[index];
87
+ if (entry.id === compaction.firstKeptEntryId)
88
+ foundFirstKept = true;
89
+ if (foundFirstKept)
90
+ appendMessage(entry);
91
+ }
92
+ for (let index = compactionIndex + 1; index < entries.length; index++) {
93
+ appendMessage(entries[index]);
94
+ }
95
+ return messages;
96
+ }
97
+ for (const entry of entries)
98
+ appendMessage(entry);
99
+ return messages;
100
+ }
101
+ function latestCompactionEntry(entries) {
102
+ for (let index = entries.length - 1; index >= 0; index--) {
103
+ const entry = entries[index];
104
+ if (entry.type === "compaction")
105
+ return entry;
106
+ }
107
+ return undefined;
108
+ }
109
+ function getCurrentBranchEntries(ctx) {
110
+ const leafId = ctx.sessionManager.getLeafId();
111
+ if (leafId === null)
112
+ return [];
113
+ const sessionManager = ctx.sessionManager;
114
+ return sessionManager.getBranch ? sessionManager.getBranch(leafId ?? undefined) : sessionManager.getEntries();
115
+ }
116
+ function asSessionEntry(value) {
117
+ return value && typeof value === "object" ? value : {};
118
+ }
119
+ function isAgentMessage(value) {
120
+ return !!value && typeof value === "object" && typeof value.role === "string";
121
+ }
122
+ function entryTimestamp(entry) {
123
+ if (typeof entry.timestamp === "number")
124
+ return entry.timestamp;
125
+ if (typeof entry.timestamp === "string") {
126
+ const timestamp = new Date(entry.timestamp).getTime();
127
+ if (Number.isFinite(timestamp))
128
+ return timestamp;
129
+ }
130
+ return Date.now();
131
+ }
132
+ function previewMessageTitle(source, index) {
133
+ if (source?.kind === "stack-item") {
134
+ return source.itemName?.trim() || source.itemId || `Stack item ${index + 1}`;
135
+ }
136
+ if (source?.kind === "chat-history") {
137
+ const label = source.itemName?.trim() || "Chat history";
138
+ return `${label} #${source.historyIndex ?? index + 1}`;
139
+ }
140
+ if (source?.kind === "implicit-history") {
141
+ return `Conversation history #${source.historyIndex ?? index + 1}`;
142
+ }
143
+ return `Message ${index + 1}`;
144
+ }
145
+ function renderPreviewSectionText(sections, maxChars = 8000) {
146
+ let text = "";
147
+ for (const section of sections) {
148
+ const role = section.role ? ` (${section.role})` : "";
149
+ text += `\n--- ${section.title}${role} ---\n`;
150
+ text += section.content;
151
+ text += "\n";
152
+ if (text.length > maxChars)
153
+ return `${text.slice(0, maxChars)}\n\n[preview truncated]`;
154
+ }
155
+ return text.trimStart();
156
+ }
157
+ function previewSection(id, title, content, role) {
158
+ return {
159
+ id,
160
+ title,
161
+ role,
162
+ content,
163
+ chars: content.length,
164
+ approxTokens: estimatePayloadTokens(content),
165
+ };
166
+ }
167
+ export function renderDiagnostics(diagnostics) {
168
+ if (diagnostics.length === 0)
169
+ return "No diagnostics.";
170
+ return diagnostics.map((d) => `${d.level.toUpperCase()}${d.itemId ? ` [${d.itemId}]` : ""}: ${d.message}`).join("\n");
171
+ }
172
+ export async function showText(ctx, title, text) {
173
+ if (ctx.hasUI) {
174
+ await ctx.ui.editor(title, text);
175
+ return;
176
+ }
177
+ console.log(text);
178
+ }
179
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { PromptRegexStage, PromptRegexTarget, PromptStack, PromptStackDiagnostic } from "./types.ts";
3
+ export declare function validateRegexConfig(config: unknown): PromptStackDiagnostic[];
4
+ export declare function applyRegexRulesToString(stack: PromptStack, text: string, stage: PromptRegexStage, target: PromptRegexTarget, diagnostics: PromptStackDiagnostic[]): string;
5
+ export declare function applyRegexRulesToMessages(stack: PromptStack, messages: AgentMessage[], stage: PromptRegexStage, diagnostics: PromptStackDiagnostic[]): AgentMessage[];
6
+ export declare function applyFinalizeRegexRulesToMessage(stack: PromptStack, message: AgentMessage, diagnostics: PromptStackDiagnostic[]): AgentMessage | undefined;
7
+ //# sourceMappingURL=regex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../src/regex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAGX,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,qBAAqB,EACrB,MAAM,YAAY,CAAC;AAiCpB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,qBAAqB,EAAE,CAwB5E;AAED,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,qBAAqB,EAAE,GAClC,MAAM,CAWR;AAED,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,YAAY,EAAE,EACxB,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,qBAAqB,EAAE,GAClC,YAAY,EAAE,CAQhB;AAED,wBAAgB,gCAAgC,CAC/C,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,qBAAqB,EAAE,GAClC,YAAY,GAAG,SAAS,CAiB1B"}
package/dist/regex.js ADDED
@@ -0,0 +1,432 @@
1
+ const ALLOWED_REGEX_FLAGS = new Set(["g", "i", "m", "s", "u"]);
2
+ const VALID_STAGES = new Set(["history", "compiled"]);
3
+ const VALID_EFFECTS = new Set(["outgoing", "display", "both", "finalize"]);
4
+ const VALID_TARGETS = new Set(["system", "messages"]);
5
+ export function validateRegexConfig(config) {
6
+ const diagnostics = [];
7
+ if (config === undefined)
8
+ return diagnostics;
9
+ if (!isPlainObject(config)) {
10
+ diagnostics.push({ level: "warning", message: "regex must be an object when provided." });
11
+ return diagnostics;
12
+ }
13
+ if (config.schemaVersion !== undefined && config.schemaVersion !== 1) {
14
+ diagnostics.push({ level: "warning", message: "Missing or unsupported regex.schemaVersion; assuming 1." });
15
+ }
16
+ const rawRules = config.rules;
17
+ if (rawRules === undefined)
18
+ return diagnostics;
19
+ if (!Array.isArray(rawRules)) {
20
+ diagnostics.push({ level: "error", message: "regex.rules must be an array when provided." });
21
+ return diagnostics;
22
+ }
23
+ const seenIds = new Set();
24
+ for (const [index, rawRule] of rawRules.entries()) {
25
+ validateRule(rawRule, index, seenIds, diagnostics);
26
+ }
27
+ return diagnostics;
28
+ }
29
+ export function applyRegexRulesToString(stack, text, stage, target, diagnostics) {
30
+ let result = text;
31
+ for (const rule of regexRulesFor(stack, stage, target, "outgoing", diagnostics)) {
32
+ const transformed = transformString(result, rule);
33
+ result = transformed.text;
34
+ addRuleStats(diagnostics, rule, stage, target, {
35
+ matches: transformed.matches,
36
+ changedSegments: transformed.changed ? 1 : 0,
37
+ });
38
+ }
39
+ return result;
40
+ }
41
+ export function applyRegexRulesToMessages(stack, messages, stage, diagnostics) {
42
+ let result = messages;
43
+ for (const rule of regexRulesFor(stack, stage, "messages", "outgoing", diagnostics)) {
44
+ const stats = { matches: 0, changedSegments: 0 };
45
+ result = transformMessages(result, rule, stats);
46
+ addRuleStats(diagnostics, rule, stage, "messages", stats);
47
+ }
48
+ return result;
49
+ }
50
+ export function applyFinalizeRegexRulesToMessage(stack, message, diagnostics) {
51
+ if (String(message.role) !== "assistant")
52
+ return undefined;
53
+ let result = message;
54
+ for (const rule of regexRulesFor(stack, "compiled", "messages", "finalize", diagnostics)) {
55
+ const stats = { matches: 0, changedSegments: 0 };
56
+ const [next = result] = transformMessages([result], rule, stats);
57
+ result = next;
58
+ addRuleStats(diagnostics, rule, "finalize", "messages", stats);
59
+ }
60
+ if (result === message)
61
+ return undefined;
62
+ diagnostics.push({
63
+ level: "warning",
64
+ message: "Finalize regex replaced finalized assistant message content; original model output is not preserved in the stored transcript.",
65
+ });
66
+ return result;
67
+ }
68
+ function validateRule(rawRule, index, seenIds, diagnostics) {
69
+ const label = `regex rule ${index + 1}`;
70
+ if (!isPlainObject(rawRule)) {
71
+ diagnostics.push({ level: "error", message: `${label} must be an object.` });
72
+ return;
73
+ }
74
+ const id = typeof rawRule.id === "string" ? rawRule.id.trim() : "";
75
+ if (!id) {
76
+ diagnostics.push({ level: "error", message: `${label} must have a non-empty id.` });
77
+ }
78
+ else if (seenIds.has(id)) {
79
+ diagnostics.push({ level: "error", message: `Duplicate regex rule id: ${id}.` });
80
+ }
81
+ else {
82
+ seenIds.add(id);
83
+ }
84
+ if (rawRule.enabled !== undefined && typeof rawRule.enabled !== "boolean") {
85
+ diagnostics.push({ level: "warning", message: `${regexRuleLabel(id, index)} enabled must be a boolean when provided.` });
86
+ }
87
+ if (!VALID_STAGES.has(rawRule.stage)) {
88
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} stage must be "history" or "compiled".` });
89
+ }
90
+ const effect = typeof rawRule.effect === "string" ? rawRule.effect : undefined;
91
+ if (rawRule.effect !== undefined) {
92
+ if (!VALID_EFFECTS.has(rawRule.effect)) {
93
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} effect must be "outgoing", "display", "both", or "finalize".` });
94
+ }
95
+ else if (rawRule.effect === "display") {
96
+ diagnostics.push({ level: "warning", message: `${regexRuleLabel(id, index)} effect "display" is not a runtime effect in pi-forge yet and will be ignored. Use "finalize" only when destructive transcript cleanup is intended.` });
97
+ }
98
+ else if (rawRule.effect === "both") {
99
+ diagnostics.push({ level: "warning", message: `${regexRuleLabel(id, index)} effect "both" is not implemented and will be ignored; use separate outgoing and finalize rules.` });
100
+ }
101
+ else if (rawRule.effect === "finalize") {
102
+ diagnostics.push({ level: "warning", message: `${regexRuleLabel(id, index)} effect "finalize" rewrites finalized assistant messages and replaces the original model output in the stored transcript.` });
103
+ }
104
+ }
105
+ if (effect === "finalize" && rawRule.stage !== "compiled") {
106
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} effect "finalize" requires stage "compiled".` });
107
+ }
108
+ if (typeof rawRule.pattern !== "string" || rawRule.pattern.length === 0) {
109
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} pattern must be a non-empty string.` });
110
+ }
111
+ else {
112
+ const flagsError = validateRegexFlags(rawRule.flags, id, index);
113
+ if (flagsError)
114
+ diagnostics.push({ level: "error", message: flagsError });
115
+ else {
116
+ try {
117
+ new RegExp(rawRule.pattern, typeof rawRule.flags === "string" ? rawRule.flags : "");
118
+ }
119
+ catch (error) {
120
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} pattern failed to compile: ${error instanceof Error ? error.message : String(error)}` });
121
+ }
122
+ }
123
+ }
124
+ if (rawRule.replace !== undefined && typeof rawRule.replace !== "string") {
125
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} replace must be a string when provided.` });
126
+ }
127
+ else if (typeof rawRule.replace === "string") {
128
+ if (/\{\{\s*match\s*\}\}/i.test(rawRule.replace)) {
129
+ diagnostics.push({ level: "warning", message: `${regexRuleLabel(id, index)} replacement contains SillyTavern {{match}}; use JavaScript $& or re-import the SillyTavern preset to convert it.` });
130
+ }
131
+ }
132
+ if (rawRule.trimStrings !== undefined && !isStringArray(rawRule.trimStrings)) {
133
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} trimStrings must be an array of strings when provided.` });
134
+ }
135
+ if (rawRule.roles !== undefined && !isStringArray(rawRule.roles)) {
136
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} roles must be an array of strings when provided.` });
137
+ }
138
+ if (rawRule.targets !== undefined) {
139
+ if (!isStringArray(rawRule.targets)) {
140
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} targets must be an array of strings when provided.` });
141
+ }
142
+ else {
143
+ for (const target of rawRule.targets) {
144
+ if (!VALID_TARGETS.has(target)) {
145
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} target must be "system" or "messages": ${target}.` });
146
+ }
147
+ else if (effect === "finalize" && target !== "messages") {
148
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} effect "finalize" only supports target "messages": ${target}.` });
149
+ }
150
+ }
151
+ }
152
+ }
153
+ if (effect === "finalize" && isStringArray(rawRule.roles) && !rawRule.roles.includes("assistant")) {
154
+ diagnostics.push({ level: "warning", message: `${regexRuleLabel(id, index)} effect "finalize" only runs for finalized assistant messages, but roles does not include "assistant".` });
155
+ }
156
+ if (rawRule.maxMessages !== undefined && !isPositiveInteger(rawRule.maxMessages)) {
157
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} maxMessages must be a positive integer when provided.` });
158
+ }
159
+ if (rawRule.maxChars !== undefined && !isPositiveInteger(rawRule.maxChars)) {
160
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} maxChars must be a positive integer when provided.` });
161
+ }
162
+ if (rawRule.minDepth !== undefined && !isNonNegativeInteger(rawRule.minDepth)) {
163
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} minDepth must be a non-negative integer when provided.` });
164
+ }
165
+ if (rawRule.maxDepth !== undefined && !isNonNegativeInteger(rawRule.maxDepth)) {
166
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} maxDepth must be a non-negative integer when provided.` });
167
+ }
168
+ if (isNonNegativeInteger(rawRule.minDepth) && isNonNegativeInteger(rawRule.maxDepth) && rawRule.maxDepth < rawRule.minDepth) {
169
+ diagnostics.push({ level: "error", message: `${regexRuleLabel(id, index)} maxDepth must be greater than or equal to minDepth.` });
170
+ }
171
+ }
172
+ function regexRulesFor(stack, stage, target, effect, diagnostics) {
173
+ const rules = Array.isArray(stack.regex?.rules) ? stack.regex.rules : [];
174
+ const compiled = [];
175
+ for (const rawRule of rules) {
176
+ if (!isPlainObject(rawRule))
177
+ continue;
178
+ if (rawRule.enabled === false)
179
+ continue;
180
+ if ((rawRule.effect ?? "outgoing") !== effect)
181
+ continue;
182
+ if (rawRule.stage !== stage)
183
+ continue;
184
+ if (stage === "compiled" && Array.isArray(rawRule.targets) && !rawRule.targets.includes(target))
185
+ continue;
186
+ const rule = compileRuntimeRule(rawRule, diagnostics);
187
+ if (rule)
188
+ compiled.push(rule);
189
+ }
190
+ return compiled;
191
+ }
192
+ function compileRuntimeRule(rule, diagnostics) {
193
+ if (typeof rule.id !== "string" || !rule.id.trim())
194
+ return undefined;
195
+ if (rule.stage !== "history" && rule.stage !== "compiled")
196
+ return undefined;
197
+ const effect = rule.effect ?? "outgoing";
198
+ if (!VALID_EFFECTS.has(effect))
199
+ return undefined;
200
+ if (typeof rule.pattern !== "string" || rule.pattern.length === 0)
201
+ return undefined;
202
+ const flags = typeof rule.flags === "string" ? rule.flags : "";
203
+ const flagsError = validateRegexFlags(flags, rule.id, -1);
204
+ if (flagsError)
205
+ return undefined;
206
+ try {
207
+ return {
208
+ id: rule.id.trim(),
209
+ stage: rule.stage,
210
+ effect,
211
+ targets: normalizeTargets(rule.targets),
212
+ roles: isStringArray(rule.roles) ? rule.roles : undefined,
213
+ maxMessages: isPositiveInteger(rule.maxMessages) ? Math.floor(rule.maxMessages) : undefined,
214
+ maxChars: isPositiveInteger(rule.maxChars) ? Math.floor(rule.maxChars) : undefined,
215
+ minDepth: isNonNegativeInteger(rule.minDepth) ? Math.floor(rule.minDepth) : undefined,
216
+ maxDepth: isNonNegativeInteger(rule.maxDepth) ? Math.floor(rule.maxDepth) : undefined,
217
+ regexp: new RegExp(rule.pattern, flags),
218
+ replace: typeof rule.replace === "string" ? rule.replace : "",
219
+ trimStrings: isStringArray(rule.trimStrings) ? rule.trimStrings : undefined,
220
+ };
221
+ }
222
+ catch (error) {
223
+ diagnostics.push({ level: "error", message: `Regex rule ${rule.id} failed to compile: ${error instanceof Error ? error.message : String(error)}` });
224
+ return undefined;
225
+ }
226
+ }
227
+ function transformMessages(messages, rule, stats) {
228
+ const eligible = eligibleMessageIndexes(messages, rule);
229
+ const eligibleSet = new Set(rule.maxMessages ? eligible.slice(-rule.maxMessages) : eligible);
230
+ let changed = false;
231
+ const transformed = messages.map((message, index) => {
232
+ if (!eligibleSet.has(index))
233
+ return message;
234
+ const next = transformMessage(message, rule, stats);
235
+ if (next !== message)
236
+ changed = true;
237
+ return next;
238
+ });
239
+ return changed ? transformed : messages;
240
+ }
241
+ function eligibleMessageIndexes(messages, rule) {
242
+ const indexes = [];
243
+ for (const [index, message] of messages.entries()) {
244
+ if (rule.roles && !rule.roles.includes(String(message.role)))
245
+ continue;
246
+ const depth = messages.length - 1 - index;
247
+ if (rule.minDepth !== undefined && depth < rule.minDepth)
248
+ continue;
249
+ if (rule.maxDepth !== undefined && depth > rule.maxDepth)
250
+ continue;
251
+ indexes.push(index);
252
+ }
253
+ return indexes;
254
+ }
255
+ function transformMessage(message, rule, stats) {
256
+ const content = message.content;
257
+ if (typeof content === "string") {
258
+ const result = transformString(content, rule);
259
+ mergeStringStats(stats, result);
260
+ return result.changed ? { ...message, content: result.text } : message;
261
+ }
262
+ if (!Array.isArray(content))
263
+ return message;
264
+ let changed = false;
265
+ const nextContent = content.map((part) => {
266
+ if (!isPlainObject(part) || part.type !== "text" || typeof part.text !== "string")
267
+ return part;
268
+ const result = transformString(part.text, rule);
269
+ mergeStringStats(stats, result);
270
+ if (!result.changed)
271
+ return part;
272
+ changed = true;
273
+ return { ...part, text: result.text };
274
+ });
275
+ return changed ? { ...message, content: nextContent } : message;
276
+ }
277
+ function transformString(text, rule) {
278
+ const headLength = rule.maxChars && text.length > rule.maxChars ? text.length - rule.maxChars : 0;
279
+ const head = headLength > 0 ? text.slice(0, headLength) : "";
280
+ const body = headLength > 0 ? text.slice(headLength) : text;
281
+ const matches = countReplacementMatches(body, rule.regexp);
282
+ const replaced = rule.trimStrings && rule.trimStrings.length > 0
283
+ ? replaceWithTrimStrings(body, rule)
284
+ : body.replace(rule.regexp, convertDollarZeroToFullMatch(rule.replace));
285
+ const result = head + replaced;
286
+ return {
287
+ text: result,
288
+ matches,
289
+ changed: result !== text,
290
+ };
291
+ }
292
+ /**
293
+ * Normalizes a JavaScript replacement string so that `$0` (not followed by another
294
+ * digit) behaves as the full match, matching the custom trimStrings expander and
295
+ * SillyTavern conventions. `$$0` stays a literal `$0`. This keeps `$0` consistent
296
+ * across the native and trimStrings replacement paths.
297
+ */
298
+ function convertDollarZeroToFullMatch(replace) {
299
+ let result = "";
300
+ for (let i = 0; i < replace.length;) {
301
+ const ch = replace[i];
302
+ if (ch !== undefined && ch !== "$") {
303
+ result += ch;
304
+ i++;
305
+ continue;
306
+ }
307
+ const next = replace[i + 1];
308
+ if (next === "$") {
309
+ result += "$$";
310
+ i += 2;
311
+ continue;
312
+ }
313
+ if (next === "0" && !isDigitCode(replace.charCodeAt(i + 2))) {
314
+ result += "$&";
315
+ i += 2;
316
+ continue;
317
+ }
318
+ result += "$";
319
+ i++;
320
+ }
321
+ return result;
322
+ }
323
+ function isDigitCode(code) {
324
+ return code >= 48 && code <= 57;
325
+ }
326
+ function replaceWithTrimStrings(text, rule) {
327
+ return text.replace(rule.regexp, (...args) => {
328
+ const groups = isPlainObject(args.at(-1)) ? args.pop() : undefined;
329
+ const input = String(args.pop() ?? "");
330
+ const offset = Number(args.pop() ?? 0);
331
+ const [match = "", ...captures] = args.map((arg) => typeof arg === "string" ? arg : "");
332
+ return expandReplacementTemplate(rule.replace, {
333
+ match,
334
+ captures,
335
+ offset,
336
+ input,
337
+ groups,
338
+ trimStrings: rule.trimStrings ?? [],
339
+ });
340
+ });
341
+ }
342
+ function expandReplacementTemplate(template, context) {
343
+ return template.replace(/\$([$&`']|0(?!\d)|[1-9]\d?|<[^>]+>)/g, (token) => {
344
+ if (token === "$$")
345
+ return "$";
346
+ if (token === "$&" || token === "$0")
347
+ return trimMatchedValue(context.match, context.trimStrings);
348
+ if (token === "$`")
349
+ return context.input.slice(0, context.offset);
350
+ if (token === "$'")
351
+ return context.input.slice(context.offset + context.match.length);
352
+ if (token.startsWith("$<")) {
353
+ const name = token.slice(2, -1);
354
+ const value = context.groups?.[name];
355
+ return typeof value === "string" ? trimMatchedValue(value, context.trimStrings) : "";
356
+ }
357
+ const captureIndex = Number(token.slice(1)) - 1;
358
+ const capture = context.captures[captureIndex];
359
+ return capture === undefined ? "" : trimMatchedValue(capture, context.trimStrings);
360
+ });
361
+ }
362
+ function trimMatchedValue(value, trimStrings) {
363
+ let result = value;
364
+ for (const trimString of trimStrings) {
365
+ if (!trimString)
366
+ continue;
367
+ result = result.split(trimString).join("");
368
+ }
369
+ return result;
370
+ }
371
+ function countReplacementMatches(text, regexp) {
372
+ if (!regexp.global)
373
+ return new RegExp(regexp.source, regexp.flags.replace("g", "")).test(text) ? 1 : 0;
374
+ const matcher = new RegExp(regexp.source, regexp.flags);
375
+ let count = 0;
376
+ let match;
377
+ while ((match = matcher.exec(text)) !== null) {
378
+ count++;
379
+ if (match[0] === "")
380
+ matcher.lastIndex++;
381
+ }
382
+ return count;
383
+ }
384
+ function mergeStringStats(stats, result) {
385
+ stats.matches += result.matches;
386
+ if (result.changed)
387
+ stats.changedSegments++;
388
+ }
389
+ function addRuleStats(diagnostics, rule, stage, target, stats) {
390
+ if (stats.matches === 0)
391
+ return;
392
+ diagnostics.push({
393
+ level: "info",
394
+ message: `Regex rule ${rule.id} matched ${stats.matches} time(s) and changed ${stats.changedSegments} text segment(s) in ${stage}/${target}.`,
395
+ });
396
+ }
397
+ function validateRegexFlags(value, id, index) {
398
+ if (value === undefined)
399
+ return undefined;
400
+ if (typeof value !== "string")
401
+ return `${regexRuleLabel(id, index)} flags must be a string when provided.`;
402
+ const seen = new Set();
403
+ for (const flag of value) {
404
+ if (!ALLOWED_REGEX_FLAGS.has(flag))
405
+ return `${regexRuleLabel(id, index)} has unsupported regex flag: ${flag}.`;
406
+ if (seen.has(flag))
407
+ return `${regexRuleLabel(id, index)} has duplicate regex flag: ${flag}.`;
408
+ seen.add(flag);
409
+ }
410
+ return undefined;
411
+ }
412
+ function normalizeTargets(value) {
413
+ if (!isStringArray(value))
414
+ return undefined;
415
+ return value.filter((target) => target === "system" || target === "messages");
416
+ }
417
+ function regexRuleLabel(id, index) {
418
+ return id ? `Regex rule ${id}` : `regex rule ${index + 1}`;
419
+ }
420
+ function isStringArray(value) {
421
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
422
+ }
423
+ function isPositiveInteger(value) {
424
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
425
+ }
426
+ function isNonNegativeInteger(value) {
427
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
428
+ }
429
+ function isPlainObject(value) {
430
+ return !!value && typeof value === "object" && !Array.isArray(value);
431
+ }
432
+ //# sourceMappingURL=regex.js.map