src / preprocess.ts
import type { PromptPreprocessor } from "@lmstudio/sdk";
import { readImage, TOTAL_IMAGE_LIMIT } from "./images";
import { basename, isAbsolute } from "node:path";
export function parseImageCommands(text: string): { text: string; paths: string[] } {
const paths: string[] = [];
const lines: string[] = [];
for (const line of text.split(/\r?\n/)) {
if (!/^@strata-image(?:\s|$)/.test(line)) { lines.push(line); continue; }
const match = /^@strata-image\s+"([^"]+)"\s*$/.exec(line);
if (!match || !isAbsolute(match[1])) throw new Error('Use @strata-image "C:\\absolute\\path.png" on its own line.');
paths.push(match[1]);
}
return { text: lines.join("\n").trim(), paths };
}
// An explicit file command provides an alternative when LM Studio disables image upload
// for generator plugins. The prepared attachment is retained in the conversation.
export const preprocess: PromptPreprocessor = async (ctl, original) => {
if (!original.isUserMessage()) return original;
const parsed = parseImageCommands(original.getText());
if (!parsed.paths.length) return original;
const message = original.asMutableCopy();
let total = 0;
for (const path of parsed.paths) {
const bytes = await readImage(path, ctl.abortSignal);
total += bytes.length;
if (total > TOTAL_IMAGE_LIMIT) throw new Error("Image commands exceed 64 MiB.");
message.appendFile(await ctl.client.files.prepareImageBase64(basename(path), bytes.toString("base64")));
}
message.replaceText(parsed.text || "Describe the attached images.");
return message;
};
src / preprocess.ts
import type { PromptPreprocessor } from "@lmstudio/sdk";
import { readImage, TOTAL_IMAGE_LIMIT } from "./images";
import { basename, isAbsolute } from "node:path";
export function parseImageCommands(text: string): { text: string; paths: string[] } {
const paths: string[] = [];
const lines: string[] = [];
for (const line of text.split(/\r?\n/)) {
if (!/^@strata-image(?:\s|$)/.test(line)) { lines.push(line); continue; }
const match = /^@strata-image\s+"([^"]+)"\s*$/.exec(line);
if (!match || !isAbsolute(match[1])) throw new Error('Use @strata-image "C:\\absolute\\path.png" on its own line.');
paths.push(match[1]);
}
return { text: lines.join("\n").trim(), paths };
}
// An explicit file command provides an alternative when LM Studio disables image upload
// for generator plugins. The prepared attachment is retained in the conversation.
export const preprocess: PromptPreprocessor = async (ctl, original) => {
if (!original.isUserMessage()) return original;
const parsed = parseImageCommands(original.getText());
if (!parsed.paths.length) return original;
const message = original.asMutableCopy();
let total = 0;
for (const path of parsed.paths) {
const bytes = await readImage(path, ctl.abortSignal);
total += bytes.length;
if (total > TOTAL_IMAGE_LIMIT) throw new Error("Image commands exceed 64 MiB.");
message.appendFile(await ctl.client.files.prepareImageBase64(basename(path), bytes.toString("base64")));
}
message.replaceText(parsed.text || "Describe the attached images.");
return message;
};