src / PrependInstructionFormatter.ts
src / PrependInstructionFormatter.ts
import { defaultConfigurationService } from "./config/ConfigurationService";
import { requiresImageContext } from "./prepend/ImageContextDetector";
import { requiresCurrentTimeContext } from "./prepend/TimeDependencyDetector";
import { requiresVisionContext } from "./prepend/VisionContextDetector";
export interface PrependProviderOptions {
isFreshChat?: boolean;
}
/**
* A single prepend instruction provider.
* Each provider owns its instruction text and knows whether it is currently enabled.
*/
export interface PrependProvider {
/** The complete instruction string. Single source of truth — never duplicated elsewhere. */
readonly instruction: string;
/** Returns true when this provider's instruction should be injected into the current request. */
isEnabled(userText?: string, options?: PrependProviderOptions): boolean;
}
/**
* Primary prepend instruction provider (Time Context).
* Explicit single source of truth for TitleFix fallback detection.
*/
export const primaryPrependProvider: PrependProvider = {
instruction:
"Always call `Get Current Time` first for any request involving the current date, time, weekday, or timezone or any other time-sensitive context like latest, new, recent etc. to ensure accurate and up-to-date responses — never assume.",
isEnabled: (userText?: string) => {
if (!defaultConfigurationService.getPrependTimeContext()) {
return false;
}
if (userText === undefined || userText === null || userText === "") {
return true;
}
return requiresCurrentTimeContext(userText);
},
};
/**
* Returns the primary prepend instruction string.
* Used by TitleFix fallback detection without relying on array ordering.
*/
export function getPrimaryPrependInstruction(): string {
return primaryPrependProvider.instruction;
}
/**
* Flat, static registry of every prepend instruction provider Browse is capable of injecting.
*
* DESIGN: Simple readonly array — no DI, no factories, no registration framework.
* To add a new provider: push one object here. Both prompt injection (index.ts) and TitleFix
* pollution detection automatically cover it with no further changes required.
*/
export const knownPrependProviders: readonly PrependProvider[] = [
primaryPrependProvider,
// ── Search Images Context ───────────────────────────────────
{
instruction: "First search for the relevent image(s) using `Image Search` tool, then explain the topic, placing each image(s) where it helps explain the concept.",
isEnabled: (userText?: string) => {
const isConfigEnabled = defaultConfigurationService.getPrependSearchImagesContext();
if (!isConfigEnabled || !userText) {
return false;
}
return requiresImageContext(userText);
},
},
// ── Vision Context: ON — full vision + inspect instruction ─────────────────
{
instruction:
"Call `Inspect Image` to analyze one or more images in the chat and use the visual information in your response. Always include the returned image(s) in the final response, placing each image alongside its corresponding analysis (Spatial Contiguity).",
isEnabled: (userText?: string, options?: PrependProviderOptions) => {
const isConfigEnabled = defaultConfigurationService.getPrependVisionContext();
if (!isConfigEnabled || !userText) {
return false;
}
return requiresVisionContext(userText, options);
},
},
];
// ─────────────────────────────────────────────────────────────────────────────
// SINGLE OWNER OF INSTRUCTION BLOCK FORMAT
// `buildInstructionsBlock()` is the single source of truth for constructing
// current instruction blocks. It owns opening wrapper ("---"), closing wrapper ("---"),
// spacing, and formatting. Neither extractCleanUserText() nor index.ts construct wrappers.
// ─────────────────────────────────────────────────────────────────────────────
function buildInstructionsBlock(instructions: string[]): string {
return "---\n" + instructions.join("\n\n") + "\n---";
}
/**
* Interface representing a recognized wrapper format definition.
* Encapsulates its name, pattern, and single-pass removal logic.
*/
export interface WrapperRecognizer {
readonly name: string;
strip(text: string): string | null;
}
/**
* Triple-hyphen wrapper format definition ("---\n...\n---\n\n").
* Single Source of Truth: Pattern is declared ONCE and evaluated in a single pass via exec().
*/
export const TripleHyphenWrapper: WrapperRecognizer = {
name: "TripleHyphenWrapper",
strip(text: string): string | null {
const pattern = /^---\n[\s\S]*?\n---\n\n?/;
const match = pattern.exec(text);
if (!match) return null;
return text.substring(match[0].length).trimStart();
},
};
/**
* Registry of supported instruction block wrapper recognizers (current + legacy).
*
* WRAPPER COMPATIBILITY PHILOSOPHY:
* Adding support for a future wrapper format is a simple 1-line registration in this array.
* `extractCleanUserText()` iterates this registry in order until one matches.
*/
export const supportedWrapperRecognizers: readonly WrapperRecognizer[] = [
TripleHyphenWrapper,
];
/**
* Recognizes and removes supported instruction block formats from userMessage.
*
* Single Responsibility: Iterates supportedWrapperRecognizers until a wrapper matches,
* returning the stripped user text. Does NOT construct wrappers or know how today's wrapper is built.
*/
export function extractCleanUserText(userMessage: string): string {
if (!userMessage) return "";
for (const wrapper of supportedWrapperRecognizers) {
const cleaned = wrapper.strip(userMessage);
if (cleaned !== null) {
return cleaned;
}
}
return userMessage;
}
/**
* Canonical prompt formatter for Browse preprocessed text.
*
* Called by index.ts with the currently-enabled instruction strings.
*
* Fully Idempotent Invariant: The output always contains exactly one Browse instruction block.
* f(f(text)) === f(text)
*
* Zero instructions → pass-through: restores userMessage without instruction blocks.
* One or more → structured canonical block + userMessage.
*/
export function buildPreprocessedText(instructions: string[], userMessage: string): string {
const cleanMessage = extractCleanUserText(userMessage);
if (instructions.length > 0) {
return buildInstructionsBlock(instructions) + "\n\n" + cleanMessage;
}
return cleanMessage;
}
/**
* Derives the full structured prefix for a single provider instruction.
* Used by TitleFix to build pollution signatures.
*
* Derived from buildInstructionsBlock() — never duplicates the delimiter string.
* If the block format changes, pollution signatures update automatically.
*
* Returns: "---\n<instruction>\n---"
*/
export function buildStructuredPrefix(instruction: string): string {
return buildInstructionsBlock([instruction]);
}
/**
* Precomputed pollution signatures for TitleFix detection.
*
* Contains:
* - Current and legacy wrapper signatures.
* - Legacy wrapper-less primary instruction signature for backward compatibility.
*
* The current primary instruction prefix fallback is obtained dynamically
* via getPrimaryPrependInstruction() and is intentionally not stored here.
*/
export const pollutionSignatures: readonly string[] = [
buildStructuredPrefix(primaryPrependProvider.instruction), // Current canonical wrapper signature
primaryPrependProvider.instruction, // Legacy wrapper-less signature for primary instruction
];
import { defaultConfigurationService } from "./config/ConfigurationService";
import { requiresImageContext } from "./prepend/ImageContextDetector";
import { requiresCurrentTimeContext } from "./prepend/TimeDependencyDetector";
import { requiresVisionContext } from "./prepend/VisionContextDetector";
export interface PrependProviderOptions {
isFreshChat?: boolean;
}
/**
* A single prepend instruction provider.
* Each provider owns its instruction text and knows whether it is currently enabled.
*/
export interface PrependProvider {
/** The complete instruction string. Single source of truth — never duplicated elsewhere. */
readonly instruction: string;
/** Returns true when this provider's instruction should be injected into the current request. */
isEnabled(userText?: string, options?: PrependProviderOptions): boolean;
}
/**
* Primary prepend instruction provider (Time Context).
* Explicit single source of truth for TitleFix fallback detection.
*/
export const primaryPrependProvider: PrependProvider = {
instruction:
"Always call `Get Current Time` first for any request involving the current date, time, weekday, or timezone or any other time-sensitive context like latest, new, recent etc. to ensure accurate and up-to-date responses — never assume.",
isEnabled: (userText?: string) => {
if (!defaultConfigurationService.getPrependTimeContext()) {
return false;
}
if (userText === undefined || userText === null || userText === "") {
return true;
}
return requiresCurrentTimeContext(userText);
},
};
/**
* Returns the primary prepend instruction string.
* Used by TitleFix fallback detection without relying on array ordering.
*/
export function getPrimaryPrependInstruction(): string {
return primaryPrependProvider.instruction;
}
/**
* Flat, static registry of every prepend instruction provider Browse is capable of injecting.
*
* DESIGN: Simple readonly array — no DI, no factories, no registration framework.
* To add a new provider: push one object here. Both prompt injection (index.ts) and TitleFix
* pollution detection automatically cover it with no further changes required.
*/
export const knownPrependProviders: readonly PrependProvider[] = [
primaryPrependProvider,
// ── Search Images Context ───────────────────────────────────
{
instruction: "First search for the relevent image(s) using `Image Search` tool, then explain the topic, placing each image(s) where it helps explain the concept.",
isEnabled: (userText?: string) => {
const isConfigEnabled = defaultConfigurationService.getPrependSearchImagesContext();
if (!isConfigEnabled || !userText) {
return false;
}
return requiresImageContext(userText);
},
},
// ── Vision Context: ON — full vision + inspect instruction ─────────────────
{
instruction:
"Call `Inspect Image` to analyze one or more images in the chat and use the visual information in your response. Always include the returned image(s) in the final response, placing each image alongside its corresponding analysis (Spatial Contiguity).",
isEnabled: (userText?: string, options?: PrependProviderOptions) => {
const isConfigEnabled = defaultConfigurationService.getPrependVisionContext();
if (!isConfigEnabled || !userText) {
return false;
}
return requiresVisionContext(userText, options);
},
},
];
// ─────────────────────────────────────────────────────────────────────────────
// SINGLE OWNER OF INSTRUCTION BLOCK FORMAT
// `buildInstructionsBlock()` is the single source of truth for constructing
// current instruction blocks. It owns opening wrapper ("---"), closing wrapper ("---"),
// spacing, and formatting. Neither extractCleanUserText() nor index.ts construct wrappers.
// ─────────────────────────────────────────────────────────────────────────────
function buildInstructionsBlock(instructions: string[]): string {
return "---\n" + instructions.join("\n\n") + "\n---";
}
/**
* Interface representing a recognized wrapper format definition.
* Encapsulates its name, pattern, and single-pass removal logic.
*/
export interface WrapperRecognizer {
readonly name: string;
strip(text: string): string | null;
}
/**
* Triple-hyphen wrapper format definition ("---\n...\n---\n\n").
* Single Source of Truth: Pattern is declared ONCE and evaluated in a single pass via exec().
*/
export const TripleHyphenWrapper: WrapperRecognizer = {
name: "TripleHyphenWrapper",
strip(text: string): string | null {
const pattern = /^---\n[\s\S]*?\n---\n\n?/;
const match = pattern.exec(text);
if (!match) return null;
return text.substring(match[0].length).trimStart();
},
};
/**
* Registry of supported instruction block wrapper recognizers (current + legacy).
*
* WRAPPER COMPATIBILITY PHILOSOPHY:
* Adding support for a future wrapper format is a simple 1-line registration in this array.
* `extractCleanUserText()` iterates this registry in order until one matches.
*/
export const supportedWrapperRecognizers: readonly WrapperRecognizer[] = [
TripleHyphenWrapper,
];
/**
* Recognizes and removes supported instruction block formats from userMessage.
*
* Single Responsibility: Iterates supportedWrapperRecognizers until a wrapper matches,
* returning the stripped user text. Does NOT construct wrappers or know how today's wrapper is built.
*/
export function extractCleanUserText(userMessage: string): string {
if (!userMessage) return "";
for (const wrapper of supportedWrapperRecognizers) {
const cleaned = wrapper.strip(userMessage);
if (cleaned !== null) {
return cleaned;
}
}
return userMessage;
}
/**
* Canonical prompt formatter for Browse preprocessed text.
*
* Called by index.ts with the currently-enabled instruction strings.
*
* Fully Idempotent Invariant: The output always contains exactly one Browse instruction block.
* f(f(text)) === f(text)
*
* Zero instructions → pass-through: restores userMessage without instruction blocks.
* One or more → structured canonical block + userMessage.
*/
export function buildPreprocessedText(instructions: string[], userMessage: string): string {
const cleanMessage = extractCleanUserText(userMessage);
if (instructions.length > 0) {
return buildInstructionsBlock(instructions) + "\n\n" + cleanMessage;
}
return cleanMessage;
}
/**
* Derives the full structured prefix for a single provider instruction.
* Used by TitleFix to build pollution signatures.
*
* Derived from buildInstructionsBlock() — never duplicates the delimiter string.
* If the block format changes, pollution signatures update automatically.
*
* Returns: "---\n<instruction>\n---"
*/
export function buildStructuredPrefix(instruction: string): string {
return buildInstructionsBlock([instruction]);
}
/**
* Precomputed pollution signatures for TitleFix detection.
*
* Contains:
* - Current and legacy wrapper signatures.
* - Legacy wrapper-less primary instruction signature for backward compatibility.
*
* The current primary instruction prefix fallback is obtained dynamically
* via getPrimaryPrependInstruction() and is intentionally not stored here.
*/
export const pollutionSignatures: readonly string[] = [
buildStructuredPrefix(primaryPrependProvider.instruction), // Current canonical wrapper signature
primaryPrependProvider.instruction, // Legacy wrapper-less signature for primary instruction
];