@zihanw/pi-forge 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -184
- package/README.zh-CN.md +405 -0
- package/examples/default-prompt-stack.json +44 -0
- package/package.json +2 -1
- package/src/compiler.ts +163 -33
- package/src/index.ts +761 -52
- package/src/loader.ts +83 -1
- package/src/sillytavern-importer.ts +83 -34
- package/src/types.ts +41 -2
- package/src/web-editor.ts +1491 -0
package/src/compiler.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
PromptStackItem,
|
|
9
9
|
PromptStackRole,
|
|
10
10
|
PromptStackSlotItem,
|
|
11
|
+
PromptStateScope,
|
|
12
|
+
PromptStateValue,
|
|
11
13
|
PromptVariableStore,
|
|
12
14
|
} from "./types.ts";
|
|
13
15
|
import { SUPPORTED_SLOTS } from "./types.ts";
|
|
@@ -21,7 +23,7 @@ const ZERO_USAGE = {
|
|
|
21
23
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
export function createPromptVariableStore(sessionVariables: Record<string,
|
|
26
|
+
export function createPromptVariableStore(sessionVariables: Record<string, PromptStateValue> = {}): PromptVariableStore {
|
|
25
27
|
return { turn: {}, session: { ...sessionVariables }, sessionDirty: false };
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -306,44 +308,163 @@ function renderVariables(
|
|
|
306
308
|
runtime: PromptRuntime,
|
|
307
309
|
): string {
|
|
308
310
|
const options = item.options ?? {};
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
const
|
|
311
|
+
const scopes = selectedVariableScopes(options);
|
|
312
|
+
const includeMetadata = options.includeMetadata === true;
|
|
313
|
+
const format = options.format === "json" ? "json" : "xml";
|
|
314
|
+
const includeNamespaces = normalizeStringArray(options.includeNamespaces);
|
|
315
|
+
const excludeNamespaces = normalizeStringArray(options.excludeNamespaces);
|
|
316
|
+
const maxValueChars = typeof options.maxValueChars === "number" && Number.isFinite(options.maxValueChars) && options.maxValueChars > 0
|
|
317
|
+
? Math.floor(options.maxValueChars)
|
|
318
|
+
: undefined;
|
|
312
319
|
const store = runtime.variables;
|
|
313
|
-
const
|
|
320
|
+
const grouped: Record<PromptStateScope, Record<string, PromptStateValue>> = {
|
|
321
|
+
static: {},
|
|
322
|
+
session: {},
|
|
323
|
+
turn: {},
|
|
324
|
+
};
|
|
314
325
|
|
|
315
|
-
|
|
326
|
+
if (scopes.has("static")) {
|
|
327
|
+
grouped.static = collectStaticVariables(stack);
|
|
328
|
+
}
|
|
329
|
+
if (scopes.has("session")) {
|
|
330
|
+
grouped.session = { ...collectDefaultVariables(stack, "session"), ...(store?.session ?? {}) };
|
|
331
|
+
}
|
|
332
|
+
if (scopes.has("turn")) {
|
|
333
|
+
grouped.turn = { ...collectDefaultVariables(stack, "turn"), ...(store?.turn ?? {}) };
|
|
334
|
+
}
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
parts.push(" </static>");
|
|
336
|
+
for (const scope of ["static", "session", "turn"] as const) {
|
|
337
|
+
grouped[scope] = Object.fromEntries(
|
|
338
|
+
Object.entries(grouped[scope]).filter(([name]) => shouldRenderVariable(name, includeNamespaces, excludeNamespaces)),
|
|
339
|
+
);
|
|
323
340
|
}
|
|
324
341
|
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
342
|
+
if (!Object.values(grouped).some((values) => Object.keys(values).length > 0)) return "";
|
|
343
|
+
|
|
344
|
+
if (format === "json") {
|
|
345
|
+
const payload: Record<string, unknown> = {};
|
|
346
|
+
for (const scope of ["static", "session", "turn"] as const) {
|
|
347
|
+
if (Object.keys(grouped[scope]).length === 0) continue;
|
|
348
|
+
payload[scope] = includeMetadata
|
|
349
|
+
? Object.fromEntries(Object.entries(grouped[scope]).sort(([a], [b]) => a.localeCompare(b)).map(([name, value]) => [
|
|
350
|
+
name,
|
|
351
|
+
{
|
|
352
|
+
value,
|
|
353
|
+
...metadataForVariable(stack, name),
|
|
354
|
+
},
|
|
355
|
+
]))
|
|
356
|
+
: Object.fromEntries(Object.entries(grouped[scope]).sort(([a], [b]) => a.localeCompare(b)));
|
|
329
357
|
}
|
|
330
|
-
|
|
358
|
+
return `<prompt_state format=\"json\">\n${escapeXml(JSON.stringify(payload, null, 2))}\n</prompt_state>`;
|
|
331
359
|
}
|
|
332
360
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
361
|
+
const parts: string[] = ["<prompt_state>"];
|
|
362
|
+
|
|
363
|
+
for (const scope of ["static", "session", "turn"] as const) {
|
|
364
|
+
const entries = Object.entries(grouped[scope]).sort(([a], [b]) => a.localeCompare(b));
|
|
365
|
+
if (entries.length === 0) continue;
|
|
366
|
+
parts.push(` <${scope}>`);
|
|
367
|
+
for (const [name, value] of entries) {
|
|
368
|
+
const metadata = includeMetadata ? metadataForVariable(stack, name) : {};
|
|
369
|
+
const attrs = [
|
|
370
|
+
`name=\"${escapeXml(name)}\"`,
|
|
371
|
+
`type=\"${escapeXml(metadata.type ?? inferStateValueType(value))}\"`,
|
|
372
|
+
];
|
|
373
|
+
if (metadata.description) attrs.push(`description=\"${escapeXml(metadata.description)}\"`);
|
|
374
|
+
if (metadata.agentWritable !== undefined) attrs.push(`agentWritable=\"${metadata.agentWritable ? "true" : "false"}\"`);
|
|
375
|
+
if (metadata.userWritable !== undefined) attrs.push(`userWritable=\"${metadata.userWritable ? "true" : "false"}\"`);
|
|
376
|
+
parts.push(` <var ${attrs.join(" ")}>${escapeXml(truncateValue(stateValueToPromptText(value), maxValueChars))}</var>`);
|
|
337
377
|
}
|
|
338
|
-
parts.push(
|
|
378
|
+
parts.push(` </${scope}>`);
|
|
339
379
|
}
|
|
340
380
|
|
|
341
|
-
parts.push("</
|
|
381
|
+
parts.push("</prompt_state>");
|
|
382
|
+
return parts.join("\n");
|
|
383
|
+
}
|
|
342
384
|
|
|
343
|
-
|
|
344
|
-
|
|
385
|
+
function selectedVariableScopes(options: Record<string, unknown>): Set<PromptStateScope> {
|
|
386
|
+
const explicit = normalizeStringArray(options.includeScopes).filter(isStateScope);
|
|
387
|
+
if (explicit.length > 0) return new Set(explicit);
|
|
345
388
|
|
|
346
|
-
|
|
389
|
+
const scopes = new Set<PromptStateScope>();
|
|
390
|
+
if (options.includeStatic !== false) scopes.add("static");
|
|
391
|
+
if (options.includeSession !== false) scopes.add("session");
|
|
392
|
+
if (options.includeTurn !== false) scopes.add("turn");
|
|
393
|
+
return scopes;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function collectStaticVariables(stack: PromptStack): Record<string, PromptStateValue> {
|
|
397
|
+
return {
|
|
398
|
+
...collectDefaultVariables(stack, "static"),
|
|
399
|
+
...(stack.variables ?? {}),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function collectDefaultVariables(stack: PromptStack, scope: PromptStateScope): Record<string, PromptStateValue> {
|
|
404
|
+
const values: Record<string, PromptStateValue> = {};
|
|
405
|
+
for (const [name, definition] of Object.entries(stack.state?.definitions ?? {})) {
|
|
406
|
+
if ((definition.scope ?? "session") !== scope) continue;
|
|
407
|
+
if (definition.default !== undefined) values[name] = definition.default;
|
|
408
|
+
}
|
|
409
|
+
return values;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function shouldRenderVariable(name: string, includePatterns: string[], excludePatterns: string[]): boolean {
|
|
413
|
+
if (includePatterns.length > 0 && !includePatterns.some((pattern) => matchesNamespace(name, pattern))) return false;
|
|
414
|
+
if (excludePatterns.some((pattern) => matchesNamespace(name, pattern))) return false;
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function matchesNamespace(name: string, pattern: string): boolean {
|
|
419
|
+
if (!pattern) return false;
|
|
420
|
+
if (pattern.endsWith("*")) return name.startsWith(pattern.slice(0, -1));
|
|
421
|
+
return name === pattern;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function metadataForVariable(stack: PromptStack, name: string): {
|
|
425
|
+
type?: string;
|
|
426
|
+
description?: string;
|
|
427
|
+
agentWritable?: boolean;
|
|
428
|
+
userWritable?: boolean;
|
|
429
|
+
} {
|
|
430
|
+
const definition = stack.state?.definitions?.[name];
|
|
431
|
+
if (!definition) return {};
|
|
432
|
+
return {
|
|
433
|
+
type: definition.type,
|
|
434
|
+
description: definition.description,
|
|
435
|
+
agentWritable: definition.agentWritable,
|
|
436
|
+
userWritable: definition.userWritable,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function normalizeStringArray(value: unknown): string[] {
|
|
441
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function isStateScope(value: string): value is PromptStateScope {
|
|
445
|
+
return value === "static" || value === "session" || value === "turn";
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function inferStateValueType(value: PromptStateValue): string {
|
|
449
|
+
if (value === null) return "null";
|
|
450
|
+
if (Array.isArray(value)) return "array";
|
|
451
|
+
return typeof value;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function truncateValue(value: string, maxChars: number | undefined): string {
|
|
455
|
+
if (!maxChars || value.length <= maxChars) return value;
|
|
456
|
+
return `${value.slice(0, maxChars)}\n[truncated]`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function stateValueToPromptText(value: PromptStateValue): string {
|
|
460
|
+
if (typeof value === "string") return value;
|
|
461
|
+
return JSON.stringify(value, null, 2);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function stateValueToMacroText(value: PromptStateValue | undefined): string {
|
|
465
|
+
if (value === undefined) return "";
|
|
466
|
+
if (typeof value === "string") return value;
|
|
467
|
+
return JSON.stringify(value);
|
|
347
468
|
}
|
|
348
469
|
|
|
349
470
|
function expandMacros(
|
|
@@ -354,7 +475,6 @@ function expandMacros(
|
|
|
354
475
|
itemId: string,
|
|
355
476
|
): string {
|
|
356
477
|
const policy = stack.defaults?.unresolvedMacroPolicy ?? "warn";
|
|
357
|
-
const staticVariables = stack.variables ?? {};
|
|
358
478
|
const unknown = new Set<string>();
|
|
359
479
|
|
|
360
480
|
const result = text.replace(/\{\{([^{}]+)\}\}/g, (full, rawName: string) => {
|
|
@@ -386,9 +506,9 @@ function expandMacros(
|
|
|
386
506
|
unknown.add(expression);
|
|
387
507
|
return full;
|
|
388
508
|
}
|
|
389
|
-
if (command === "getturnvar") return runtime.variables?.turn[name]
|
|
390
|
-
if (command === "getsessionvar") return runtime.variables?.session[name]
|
|
391
|
-
return getRuntimeVariable(runtime,
|
|
509
|
+
if (command === "getturnvar") return stateValueToMacroText(runtime.variables?.turn[name]);
|
|
510
|
+
if (command === "getsessionvar") return stateValueToMacroText(runtime.variables?.session[name]);
|
|
511
|
+
return stateValueToMacroText(getRuntimeVariable(runtime, stack, name));
|
|
392
512
|
}
|
|
393
513
|
|
|
394
514
|
if (command === "clearvar" || command === "clearturnvar" || command === "clearsessionvar") {
|
|
@@ -406,8 +526,8 @@ function expandMacros(
|
|
|
406
526
|
const dynamicValue = getBuiltinMacro(command, runtime);
|
|
407
527
|
if (dynamicValue !== undefined) return dynamicValue;
|
|
408
528
|
|
|
409
|
-
const variableValue = getRuntimeVariable(runtime,
|
|
410
|
-
if (variableValue !== undefined) return variableValue;
|
|
529
|
+
const variableValue = getRuntimeVariable(runtime, stack, command);
|
|
530
|
+
if (variableValue !== undefined) return stateValueToMacroText(variableValue);
|
|
411
531
|
|
|
412
532
|
unknown.add(expression);
|
|
413
533
|
return full;
|
|
@@ -431,6 +551,8 @@ function getBuiltinMacro(name: string, runtime: PromptRuntime): string | undefin
|
|
|
431
551
|
return runtime.options.cwd;
|
|
432
552
|
case "date":
|
|
433
553
|
return formatDate(runtime.now);
|
|
554
|
+
case "time":
|
|
555
|
+
return formatTime(runtime.now);
|
|
434
556
|
case "lastUserMessage":
|
|
435
557
|
return runtime.latestUserMessage ?? "";
|
|
436
558
|
case "selectedTools":
|
|
@@ -445,9 +567,10 @@ function getBuiltinMacro(name: string, runtime: PromptRuntime): string | undefin
|
|
|
445
567
|
}
|
|
446
568
|
}
|
|
447
569
|
|
|
448
|
-
function getRuntimeVariable(runtime: PromptRuntime,
|
|
570
|
+
function getRuntimeVariable(runtime: PromptRuntime, stack: PromptStack, name: string): PromptStateValue | undefined {
|
|
449
571
|
if (runtime.variables && Object.prototype.hasOwnProperty.call(runtime.variables.turn, name)) return runtime.variables.turn[name];
|
|
450
572
|
if (runtime.variables && Object.prototype.hasOwnProperty.call(runtime.variables.session, name)) return runtime.variables.session[name];
|
|
573
|
+
const staticVariables = collectStaticVariables(stack);
|
|
451
574
|
if (Object.prototype.hasOwnProperty.call(staticVariables, name)) return staticVariables[name];
|
|
452
575
|
return undefined;
|
|
453
576
|
}
|
|
@@ -537,6 +660,13 @@ function formatDate(now: Date): string {
|
|
|
537
660
|
return `${year}-${month}-${day}`;
|
|
538
661
|
}
|
|
539
662
|
|
|
663
|
+
function formatTime(now: Date): string {
|
|
664
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
665
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
666
|
+
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
667
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
668
|
+
}
|
|
669
|
+
|
|
540
670
|
function escapeXml(value: string): string {
|
|
541
671
|
return value
|
|
542
672
|
.replace(/&/g, "&")
|