101 lines
3.3 KiB
TypeScript
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
|
|
),
|
|
})),
|
|
];
|
|
};
|