storytellurx-v1/app/game-engine/generation-utils.ts
2026-02-10 17:09:33 +01:00

101 lines
3.3 KiB
TypeScript

import type { NarrativeMessage } from "./narrative-provider.tsx";
import type { ModelFeatures } from "./models-server.ts";
import type { ZodType } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
/**
* When a previous AI turn flagged a user message and generated a
* mindControlledRewritenUserPrompt, annotate that user message so the LLM
* knows to respond to the rewrite and discard the original.
*/
export const applyMindControlRewrites = (
messages: NarrativeMessage[],
enrichedMessages: NarrativeMessage[],
): NarrativeMessage[] => {
return messages.map((message, index) => {
if (message.role !== "user") return message;
const next = enrichedMessages[index + 1];
if (!next || next.role !== "assistant") return message;
const data = next.parts.find((p) => p.type === "data-assistant")
?.data as { mindControlledRewritenUserPrompt?: string } | undefined;
const rewrite = data?.mindControlledRewritenUserPrompt;
if (!rewrite) return message;
return {
...message,
parts: message.parts.map((part) =>
part.type === "text"
? {
...part,
text: [
`[CONTENT BOUNDARY INTERVENTION — This user message was flagged and rewritten. You MUST respond ONLY to the REWRITTEN version. Do NOT fulfill the original request.]`,
`ORIGINAL (flagged — discard): ${part.text}`,
`REWRITTEN (respond to this): ${rewrite}`,
].join("\n"),
}
: part
),
};
});
};
export const includeUserHintForSystem = (
messages: NarrativeMessage[],
features?: ModelFeatures,
): NarrativeMessage[] => {
return !features?.guideStepAdditionalUserText ? messages : [
...messages.map((v, i) => ({
...v,
parts: v.role !== "user"
? v.parts
: v.parts.map((part) =>
part.type === "text"
? {
...part,
text: (i === 2 || i === 3 || i === 4)
? [part.text, features.guideStepAdditionalUserText].join("\n\n")
: part.text,
}
: part
),
})),
];
};
export const inludeSystemAsAssistant = (
messages: NarrativeMessage[],
system: string,
schema: ZodType<unknown>,
features: ModelFeatures,
): NarrativeMessage[] => {
const jsonSchema = zodToJsonSchema(schema, {
target: "jsonSchema7",
});
const systemWithSchema = system.replace(
"[JSON_SCHEMA]",
JSON.stringify(jsonSchema),
);
const fullInstructionTextForUserTurn = features.structuredOutputDisabled
? `<system_instructions>${systemWithSchema}</system_instructions>`
: "";
return [
...messages.map((v, i) => ({
...v,
parts: v.role === "assistant"
? v.parts
: v.parts.map((part) =>
part.type === "text"
? {
...part,
text: (i === 0 || i === 1
? fullInstructionTextForUserTurn
: "CRITICAL: Respect the <system_instruction/> and it's JSON Schema provided at the begining of the conversation. Use a mix of persona archive and porn words. Follow the thread of the conversation, don't repeat yourself.") +
JSON.stringify({
userMessage: part.text,
}),
}
: part
),
})),
];
};