src / promptPreprocessor.ts
import {
type ChatMessage,
type PromptPreprocessorController,
} from "@lmstudio/sdk";
import { homedir } from "os";
import { join } from "path";
import { loadAlerts, getDueAlerts } from "./store";
// Uses default path — custom dataPath from config only applies to tool calls.
const DEFAULT_DATA_PATH = join(homedir(), ".lmstudio-alerts");
export async function promptPreprocessor(
ctl: PromptPreprocessorController,
userMessage: ChatMessage,
): Promise<string | ChatMessage> {
const history = await ctl.pullHistory();
// Only inject on the first message of a session
if (history.length !== 0) return userMessage;
try {
const all = await loadAlerts(DEFAULT_DATA_PATH);
const due = getDueAlerts(all);
if (due.length === 0) return userMessage;
const sorted = [...due].sort((a, b) => {
const priority = { high: 0, normal: 1, low: 2 };
return (priority[a.priority] - priority[b.priority]) ||
new Date(a.dueAt).getTime() - new Date(b.dueAt).getTime();
});
const lines = sorted.map(a => {
const overdue = new Date(a.dueAt) < new Date();
const timeStr = new Date(a.dueAt).toLocaleString([], { dateStyle: "short", timeStyle: "short" });
const flag = a.priority === "high" ? "[HIGH]" : a.priority === "normal" ? "[NORMAL]" : "[LOW]";
const status = overdue ? `OVERDUE (was due ${timeStr})` : `due ${timeStr}`;
return ` ${flag} [id:${a.id}] ${a.message} — ${status}`;
});
const alertBlock = `[ALERTS — ${due.length} pending reminder${due.length > 1 ? "s" : ""}:\n${lines.join("\n")}\nTo dismiss: alert(action="dismiss", id="<id>")]`;
return `${alertBlock}\n\n${userMessage.getText()}`;
} catch {
return userMessage;
}
}