src / title / NativeTitlePollutionPreventer.ts
src / title / NativeTitlePollutionPreventer.ts
import { PromptPreprocessorController } from "@lmstudio/sdk";
import { applyHeuristicTitleRules } from "./HeuristicTitleRules";
import { Logger } from "../utils/Logger";
/**
* Context payload required for native conversation title pollution prevention.
*/
export interface TitlePreventionContext {
/** The LM Studio prompt preprocessor controller active for the current turn. */
ctl: PromptPreprocessorController;
/** The raw, un-prepended text of the user's initial message. */
originalUserMessage: string;
/** List of prepended instruction strings injected into the message for this turn. */
prependInstructions: readonly string[];
}
/**
* Core subsystem responsible for applying native conversation titles in LM Studio.
*
* BACKGROUND & PURPOSE:
* When Browse injects time-sensitivity or tool guidance hints at the start of a user message,
* LM Studio's automatic conversation auto-titling feature reads the prepended instruction text
* and generates polluted titles (e.g., "Use `get_current_time` when...").
*
* ARCHITECTURAL DESIGN:
* This component runs exclusively during LM Studio's initial conversation naming window (`ctl.needsNaming()`).
* When prepended instructions are injected during turn 1, it computes a clean, human-readable title
* from the raw user prompt via `applyHeuristicTitleRules` and submits it directly to LM Studio
* via `ctl.suggestName()`.
*
* SAFETY & ISOLATION:
* Title generation operates as a non-blocking, best-effort enhancement. Any failures during title submission
* emit a single warning log and will never interrupt prompt preprocessing or user message delivery.
*/
export class NativeTitlePollutionPreventer {
private readonly titleCleaner: (userMessage: string) => string;
constructor(titleCleaner: (userMessage: string) => string = applyHeuristicTitleRules) {
this.titleCleaner = titleCleaner;
}
/**
* Evaluates the current turn and applies a clean title if LM Studio requires conversation naming
* and prepended instructions were injected.
*/
public async applyInitialConversationTitle(context: TitlePreventionContext): Promise<void> {
const { ctl, originalUserMessage, prependInstructions } = context;
if (!prependInstructions || prependInstructions.length === 0) {
return;
}
try {
const needsNaming = await ctl.needsNaming();
if (!needsNaming) {
return;
}
const cleanTitle = this.titleCleaner(originalUserMessage);
if (!cleanTitle) {
return;
}
try {
await ctl.suggestName(cleanTitle);
Logger.debug("Title", `Set conversation title: "${cleanTitle}"`);
} catch (suggestErr: unknown) {
const errorMsg = suggestErr instanceof Error ? suggestErr.message : String(suggestErr);
Logger.warn("Title", `Failed to set native conversation title: ${errorMsg}`);
}
} catch (err: unknown) {
const errorMsg = err instanceof Error ? err.message : String(err);
Logger.warn("Title", `Failed checking ctl.needsNaming(): ${errorMsg}`);
}
}
}
/** Singleton instance of NativeTitlePollutionPreventer for application-wide use. */
export const nativeTitlePollutionPreventer = new NativeTitlePollutionPreventer();
import { PromptPreprocessorController } from "@lmstudio/sdk";
import { applyHeuristicTitleRules } from "./HeuristicTitleRules";
import { Logger } from "../utils/Logger";
/**
* Context payload required for native conversation title pollution prevention.
*/
export interface TitlePreventionContext {
/** The LM Studio prompt preprocessor controller active for the current turn. */
ctl: PromptPreprocessorController;
/** The raw, un-prepended text of the user's initial message. */
originalUserMessage: string;
/** List of prepended instruction strings injected into the message for this turn. */
prependInstructions: readonly string[];
}
/**
* Core subsystem responsible for applying native conversation titles in LM Studio.
*
* BACKGROUND & PURPOSE:
* When Browse injects time-sensitivity or tool guidance hints at the start of a user message,
* LM Studio's automatic conversation auto-titling feature reads the prepended instruction text
* and generates polluted titles (e.g., "Use `get_current_time` when...").
*
* ARCHITECTURAL DESIGN:
* This component runs exclusively during LM Studio's initial conversation naming window (`ctl.needsNaming()`).
* When prepended instructions are injected during turn 1, it computes a clean, human-readable title
* from the raw user prompt via `applyHeuristicTitleRules` and submits it directly to LM Studio
* via `ctl.suggestName()`.
*
* SAFETY & ISOLATION:
* Title generation operates as a non-blocking, best-effort enhancement. Any failures during title submission
* emit a single warning log and will never interrupt prompt preprocessing or user message delivery.
*/
export class NativeTitlePollutionPreventer {
private readonly titleCleaner: (userMessage: string) => string;
constructor(titleCleaner: (userMessage: string) => string = applyHeuristicTitleRules) {
this.titleCleaner = titleCleaner;
}
/**
* Evaluates the current turn and applies a clean title if LM Studio requires conversation naming
* and prepended instructions were injected.
*/
public async applyInitialConversationTitle(context: TitlePreventionContext): Promise<void> {
const { ctl, originalUserMessage, prependInstructions } = context;
if (!prependInstructions || prependInstructions.length === 0) {
return;
}
try {
const needsNaming = await ctl.needsNaming();
if (!needsNaming) {
return;
}
const cleanTitle = this.titleCleaner(originalUserMessage);
if (!cleanTitle) {
return;
}
try {
await ctl.suggestName(cleanTitle);
Logger.debug("Title", `Set conversation title: "${cleanTitle}"`);
} catch (suggestErr: unknown) {
const errorMsg = suggestErr instanceof Error ? suggestErr.message : String(suggestErr);
Logger.warn("Title", `Failed to set native conversation title: ${errorMsg}`);
}
} catch (err: unknown) {
const errorMsg = err instanceof Error ? err.message : String(err);
Logger.warn("Title", `Failed checking ctl.needsNaming(): ${errorMsg}`);
}
}
}
/** Singleton instance of NativeTitlePollutionPreventer for application-wide use. */
export const nativeTitlePollutionPreventer = new NativeTitlePollutionPreventer();