Project Files
src / core-bundle.mjs
// src/capabilities.ts
import fs2 from "fs";
import path2 from "path";
// src/helpers/paths.ts
import fs from "fs";
import path from "path";
function resolveProjectRootFrom(startDir) {
try {
{
let dir = startDir;
for (let i = 0; i < 50; i++) {
try {
if (fs.existsSync(path.join(dir, "manifest.json"))) return dir;
} catch {
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
{
let dir = startDir;
for (let i = 0; i < 50; i++) {
try {
if (fs.existsSync(path.join(dir, "package.json"))) return dir;
} catch {
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
{
let dir = startDir;
for (let i = 0; i < 30; i++) {
const base = path.basename(dir);
if (base === "dist" || base === "src") return path.dirname(dir);
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
} catch {
}
return startDir;
}
function getProjectRoot() {
try {
if (fs.existsSync(path.join(process.cwd(), "manifest.json"))) {
return process.cwd();
}
} catch {
}
try {
const filePath = typeof __filename !== "undefined" && __filename ? __filename : process.argv && process.argv[1] ? process.argv[1] : process.cwd();
const moduleDir = path.dirname(filePath);
return resolveProjectRootFrom(moduleDir);
} catch {
return resolveProjectRootFrom(process.cwd());
}
}
function getLogsDir() {
return path.join(getProjectRoot(), "logs");
}
function ensureLogsDir() {
const dir = getLogsDir();
try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
} catch {
}
}
// src/capabilities.ts
var cachedSelfPluginIdentifier;
var FALLBACK_SELF_PLUGIN_IDENTIFIER = "ceveyne/draw-things-chat";
function getSelfPluginIdentifier() {
if (cachedSelfPluginIdentifier !== void 0)
return cachedSelfPluginIdentifier;
try {
const root = getProjectRoot();
const manifestPath = path2.join(root, "manifest.json");
const raw = fs2.readFileSync(manifestPath, "utf-8");
const parsed = JSON.parse(raw);
const owner = typeof parsed?.owner === "string" ? parsed.owner.trim() : "";
const name = typeof parsed?.name === "string" ? parsed.name.trim() : "";
if (owner && name) {
cachedSelfPluginIdentifier = `${owner}/${name}`;
return cachedSelfPluginIdentifier;
}
} catch (err) {
console.warn(
"[Capabilities] Failed to resolve pluginIdentifier from manifest.json; using fallback.",
err
);
cachedSelfPluginIdentifier = FALLBACK_SELF_PLUGIN_IDENTIFIER;
return cachedSelfPluginIdentifier;
}
console.warn(
"[Capabilities] manifest.json did not contain valid owner/name; using fallback."
);
cachedSelfPluginIdentifier = FALLBACK_SELF_PLUGIN_IDENTIFIER;
return cachedSelfPluginIdentifier;
}
var MEDIA_HARVESTING_CONFIG = {
/**
* Attachments: User-uploaded files.
* allow: "all" because LM Studio controls the source.
* SSOT Location: User-Turns → message.files[]
*/
attachment: {
allow: "all"
},
/**
* Variants: Our own generate_image outputs.
* allow: "all" because we control the source (ourselves).
*/
variant: {
allow: "all"
// Implicitly: only our own plugin via getSelfPluginIdentifier()
},
/**
* Images: 3rd-party image generators.
* allow: "all" because LM Studio is the gatekeeper:
* If base64 arrives and gets decoded to ChatWd, we trust it.
*/
image: {
allow: "all"
// Universal: any tool that delivers base64/filename
},
/**
* Pictures: External URLs from web searches.
* allow: "only" because extraction logic is tool-specific
* (Brave JSON structure ≠Google JSON structure ≠...).
*/
picture: {
allow: "only",
tools: {
"mcp/brave-search-mcp": ["brave_image_search"],
// NUR brave_image_search
"ceveyne/draw-things-index": ["index_image"]
// Add new picture tools here...
}
}
};
function isToolAllowlistedForMediaType(mediaType, pluginIdentifier, toolName) {
const config = MEDIA_HARVESTING_CONFIG[mediaType];
if (config.allow === "all") {
return true;
}
const tools = config.tools[pluginIdentifier];
if (!tools) return false;
return tools.includes(toolName);
}
function getMediaTypeForTool(pluginIdentifier, toolName) {
if (isToolAllowlistedForMediaType("picture", pluginIdentifier, toolName)) {
return "picture";
}
const selfPlugin2 = getSelfPluginIdentifier();
if (selfPlugin2 && pluginIdentifier === selfPlugin2) {
return "variant";
}
return "image";
}
function isAllowlistedSsotToolCall(input) {
const name = input?.name;
const pluginIdentifier = input?.pluginIdentifier;
if (typeof pluginIdentifier !== "string" || !pluginIdentifier.trim()) {
return false;
}
if (typeof name !== "string" || !name.trim()) {
return false;
}
return getMediaTypeForTool(pluginIdentifier, name) !== void 0;
}
var CAPABILITY_MAP = {
// Local LM Studio Models (Orchestrator/Agent)
"qwen/qwen3.5-35b-a3b": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: true,
thinking: {
levels: [],
// No explicit levels, model decides spontaneously
defaultLevel: "",
reasoningSectionParsing: {
enabled: true,
startString: "<think>",
endString: "</think>"
},
// Qwen3.5 does NOT support the /think soft-switch of Qwen3.
// The Jinja template opens <think> by default, but only if
// `enable_thinking` is NOT explicitly set to false.
// To be safe and explicit, we always send the flag as true.
requiresEnableThinkingFlag: true
},
supportsStreaming: true
},
"qwen/qwen3.5-27b": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: true,
thinking: {
levels: [],
// No explicit levels, model decides spontaneously
defaultLevel: "",
reasoningSectionParsing: {
enabled: true,
startString: "<think>",
endString: "</think>"
},
requiresEnableThinkingFlag: true
},
supportsStreaming: true
},
"qwen/qwen3.5-9b": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: true,
thinking: {
levels: [],
// No explicit levels, model decides spontaneously
defaultLevel: "",
reasoningSectionParsing: {
enabled: true,
startString: "<think>",
endString: "</think>"
},
requiresEnableThinkingFlag: true
},
supportsStreaming: true
},
"qwen/qwen3-vl-30b": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: false,
supportsStreaming: true
},
"google/gemma-3-27b": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: false,
supportsStreaming: true
},
"mistralai/magistral-small-2509": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: true,
thinking: {
levels: [],
// No explicit levels, model decides spontaneously
defaultLevel: "",
reasoningSectionParsing: {
enabled: true,
startString: "[THINK]",
endString: "[/THINK]"
}
},
supportsStreaming: true
},
"mistralai/devstral-small-2-2512": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: true,
thinking: {
levels: [],
// No explicit levels, model decides spontaneously
defaultLevel: "",
reasoningSectionParsing: {
enabled: true,
startString: "<think>",
endString: "</think>"
}
},
supportsStreaming: true
},
"zai-org/glm-4.6v-flash": {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: true,
// Spontaneous <think>...</think> reasoning
thinking: {
levels: [],
// No explicit levels, model decides spontaneously
defaultLevel: "",
reasoningSectionParsing: {
enabled: true,
startString: "<think>",
endString: "</think>"
}
},
supportsStreaming: true
}
};
var DEFAULT_CAPABILITIES = {
supportsTools: true,
supportsVision: true,
supportsImage: false,
supportsThinking: false,
supportsStreaming: true
};
function stripPublisher(modelId) {
const idx = modelId.indexOf("/");
return idx >= 0 ? modelId.slice(idx + 1) : modelId;
}
function detectCapabilities(modelId) {
const input = (modelId || "").trim();
const inputLower = input.toLowerCase();
const inputStripped = stripPublisher(input).toLowerCase();
if (CAPABILITY_MAP[input]) {
return CAPABILITY_MAP[input];
}
for (const key of Object.keys(CAPABILITY_MAP)) {
if (key.toLowerCase() === inputLower) {
return CAPABILITY_MAP[key];
}
}
for (const key of Object.keys(CAPABILITY_MAP)) {
const keyStripped = stripPublisher(key).toLowerCase();
if (keyStripped === inputStripped) {
return CAPABILITY_MAP[key];
}
}
console.warn(`[Capabilities] Unknown model ID "${modelId}", using defaults.`);
return DEFAULT_CAPABILITIES;
}
function shouldUseFilesApiForModel(modelId, userToggle) {
if (!userToggle) return false;
return true;
}
var IMAGE_MODEL_CAPABILITY_MAP = {
// IMPORTANT: keys are Draw Things model filenames without extension,
// using hyphens instead of underscores, and without quantization suffix.
"z-image-turbo-1.0": {
modes: {
text2image: true,
image2image: true,
edit: false,
text2video: false,
image2video: false
},
modelFilenames: [
"z_image_turbo_1.0_q8p.ckpt",
"z_image_turbo_1.0_q6p.ckpt"
]
},
"qwen-image-1.0": {
modes: {
text2image: true,
image2image: true,
edit: true,
// Supported via qwen_image_edit_2509_q6p.ckpt / qwen_image_edit_2511_q6p.ckpt
text2video: false,
image2video: false
},
edit: {
maxReferenceImages: 4
},
image2image: {
maxReferenceImages: 4
},
modelFilenames: [
// Legacy / stable names
"qwen_image_1.0_q8p.ckpt",
"qwen_image_1.0_q6p.ckpt",
// Current Draw Things builds
"qwen_image_2512_bf16_q8p.ckpt",
"qwen_image_2512_bf16_q6p.ckpt",
// Edit models (used by defaults/overlays depending on mode and version)
"qwen_image_edit_2509_q8p.ckpt",
"qwen_image_edit_2509_q6p.ckpt",
"qwen_image_edit_2511_q8p.ckpt",
"qwen_image_edit_2511_q6p.ckpt"
]
},
// Unified qwen-edit preset (covers both 2509 and 2511 edit models)
"qwen-edit": {
modes: {
text2image: false,
image2image: true,
// Supports img2img with multi-reference
edit: true,
text2video: false,
image2video: false
},
edit: {
maxReferenceImages: 4
},
image2image: {
maxReferenceImages: 4
},
modelFilenames: [
"qwen_image_edit_2509_q6p.ckpt",
"qwen_image_edit_2511_q6p.ckpt"
]
},
"qwen-image-edit-2509": {
modes: {
text2image: false,
image2image: true,
edit: true,
text2video: false,
image2video: false
},
edit: {
maxReferenceImages: 4
},
image2image: {
maxReferenceImages: 4
},
modelFilenames: ["qwen_image_edit_2509_q6p.ckpt"]
},
"qwen-image-edit-2511": {
modes: {
text2image: false,
image2image: true,
edit: true,
text2video: false,
image2video: false
},
edit: {
maxReferenceImages: 4
},
image2image: {
maxReferenceImages: 4
},
modelFilenames: ["qwen_image_edit_2511_q6p.ckpt"]
},
"flux-1-schnell": {
modes: {
text2image: true,
image2image: false,
// Not supported by flux-schnell
edit: false,
text2video: false,
image2video: false
},
modelFilenames: ["flux_1_schnell_q8p.ckpt"]
},
"flux-2-klein": {
modes: {
text2image: true,
image2image: true,
edit: true,
text2video: false,
image2video: false
},
edit: {
// Multi-reference edit mode (canvas + moodboard). Default is 4 if omitted,
// but we keep it explicit for clarity.
maxReferenceImages: 4
},
image2image: {
// Multi-reference img2img mode (canvas + moodboard). Same as edit for now.
maxReferenceImages: 4
},
modelFilenames: [
// Common variants
"flux_2_klein_9b_q6p.ckpt",
"flux_2_klein_9b_q8p.ckpt",
"flux_2_klein_9b_kv_q6p.ckpt",
"flux_2_klein_9b_kv_q8p.ckpt",
"flux_2_klein_base_9b_q8p.ckpt",
// Seen in logs for custom presets (still in the Flux family)
"flux_2_dev_q6p.ckpt"
]
},
"ltx-2-distilled": {
modes: {
text2image: true,
image2image: true,
edit: false,
text2video: true,
image2video: true
},
image2image: {
maxReferenceImages: 1
},
video: {
defaultFps: 25,
audioSampleRate: 24e3
},
// ModelZoo: framesPerSecondForModel(.ltx2) = 25, audioSampleRateForModel(.ltx2) = 24 000
modelFilenames: [
"ltx_2_19b_distilled_q6p.ckpt",
"ltx_2_19b_distilled_q8p.ckpt"
]
},
"ltx-2-dev": {
modes: {
text2image: true,
image2image: true,
edit: false,
text2video: true,
image2video: true
},
image2image: {
maxReferenceImages: 1
},
video: {
defaultFps: 25,
audioSampleRate: 24e3
},
// ModelZoo: framesPerSecondForModel(.ltx2) = 25, audioSampleRateForModel(.ltx2) = 24 000
modelFilenames: [
"ltx_2_19b_dev_q6p.ckpt",
"ltx_2_19b_dev_q8p.ckpt"
]
},
"ltx-2.3-distilled": {
modes: {
text2image: true,
image2image: true,
edit: false,
text2video: true,
image2video: true
},
image2image: {
maxReferenceImages: 1
},
video: {
defaultFps: 25,
audioSampleRate: 48e3
},
// ModelZoo: framesPerSecondForModel(.ltx2_3) = 25, audioSampleRateForModel(.ltx2_3) = 48 000
modelFilenames: [
"ltx_2.3_22b_distilled_q6p.ckpt",
"ltx_2.3_22b_distilled_q8p.ckpt"
]
},
"ltx-2.3-dev": {
modes: {
text2image: true,
image2image: true,
edit: false,
text2video: true,
image2video: true
},
image2image: {
maxReferenceImages: 1
},
video: {
defaultFps: 25,
audioSampleRate: 48e3
},
// ModelZoo: framesPerSecondForModel(.ltx2_3) = 25, audioSampleRateForModel(.ltx2_3) = 48 000
modelFilenames: [
"ltx_2.3_22b_dev_q6p.ckpt",
"ltx_2.3_22b_dev_q8p.ckpt"
]
}
};
var DEFAULT_IMAGE_MODEL_CAPABILITIES = {
modes: {
text2image: false,
image2image: false,
edit: false,
text2video: false,
image2video: false
}
};
var MODEL_PRESET_TO_CAPABILITY_KEY = {
"z-image": "z-image-turbo-1.0",
"qwen-image": "qwen-image-1.0",
"qwen-edit": "qwen-edit",
// qwen_image_edit_2509_q6p.ckpt or qwen_image_edit_2511_q6p.ckpt
flux: "flux-2-klein",
ltx: "ltx-2-distilled"
// Default model of the LTX family
// Note: 'custom' is not in this map - it uses custom_configs.json only
};
function detectImageModelCapabilities(modelId) {
const m = (modelId || "").toLowerCase();
if (IMAGE_MODEL_CAPABILITY_MAP[modelId]) {
return IMAGE_MODEL_CAPABILITY_MAP[modelId];
}
const key = Object.keys(IMAGE_MODEL_CAPABILITY_MAP).find(
(k) => k.toLowerCase() === m
);
if (key) {
return IMAGE_MODEL_CAPABILITY_MAP[key];
}
return DEFAULT_IMAGE_MODEL_CAPABILITIES;
}
function getDefaultFpsForModel(modelId) {
const direct = detectImageModelCapabilities(modelId);
if (direct !== DEFAULT_IMAGE_MODEL_CAPABILITIES) {
return direct.video?.defaultFps;
}
const lower = (modelId || "").toLowerCase();
for (const caps of Object.values(IMAGE_MODEL_CAPABILITY_MAP)) {
if (caps.modelFilenames?.some((f) => f.toLowerCase() === lower)) {
return caps.video?.defaultFps;
}
}
return void 0;
}
function getAudioSampleRateForModel(modelId) {
const direct = detectImageModelCapabilities(modelId);
if (direct !== DEFAULT_IMAGE_MODEL_CAPABILITIES) {
return direct.video?.audioSampleRate;
}
const lower = (modelId || "").toLowerCase();
for (const caps of Object.values(IMAGE_MODEL_CAPABILITY_MAP)) {
if (caps.modelFilenames?.some((f) => f.toLowerCase() === lower)) {
return caps.video?.audioSampleRate;
}
}
return void 0;
}
function getCapabilityKeyForPreset(preset) {
if (preset === "auto") return void 0;
return MODEL_PRESET_TO_CAPABILITY_KEY[preset];
}
function checkModeSupport(modelPreset, mode) {
if (modelPreset === "auto") {
return { supported: true };
}
const capKey = MODEL_PRESET_TO_CAPABILITY_KEY[modelPreset];
if (!capKey) {
return {
supported: false,
reason: `Unknown model preset '${modelPreset}'. Available: auto, z-image, qwen-image, flux, custom.`
};
}
const caps = detectImageModelCapabilities(capKey);
if (!caps.modes[mode]) {
const supportedModes = Object.entries(caps.modes).filter(([, v]) => v).map(([k]) => k);
return {
supported: false,
reason: `Model '${modelPreset}' does not support mode '${mode}'. Supported modes for this model: ${supportedModes.join(", ") || "none"}.`
};
}
return { supported: true };
}
function checkModeSupportWithCustom(modelPreset, mode, getAvailableCombos) {
if (getAvailableCombos) {
try {
const customCombos = getAvailableCombos();
const hasCustomConfig = customCombos.some(
([customMode, customModelId]) => customMode === mode && customModelId === modelPreset
);
if (hasCustomConfig) {
return { supported: true };
}
} catch {
}
}
const baseCheck = checkModeSupport(modelPreset, mode);
if (baseCheck.supported || !getAvailableCombos) {
return baseCheck;
}
let reason = baseCheck.reason;
try {
const customCombos = getAvailableCombos();
const customForModel = customCombos.filter(
([, modelId]) => modelId === modelPreset
);
if (customForModel.length > 0) {
const customModes = customForModel.map(([m]) => m).join(", ");
reason += ` Custom configs available for '${modelPreset}': ${customModes}.`;
} else {
const customForMode = customCombos.filter(([m]) => m === mode);
if (customForMode.length > 0) {
const customModels = customForMode.map(([, modelId]) => modelId);
reason += ` Mode '${mode}' is available via custom configs for: ${customModels.join(
", "
)}.`;
}
}
} catch {
}
return {
supported: false,
reason
};
}
function selectAutoModel(mode) {
switch (mode) {
case "edit":
case "image2image":
return "qwen-edit";
case "text2video":
case "image2video":
return "ltx";
case "text2image":
default:
return "z-image";
}
}
var VISION_WINDOW_SIZES = {
/** Window 1: User attachments */
attachments: 5,
/** Window 2: Variants + Images (shared cap) */
everythingElse: 4
};
var MEDIA_SOURCE_REGISTRY = {
attachment: {
kind: "attachment",
indexPrefix: "a",
stateArrayKey: "attachments",
counterKey: "nextAttachmentA",
dedupeKey: "originAbs",
visionPromoted: true,
window: 1
},
variant: {
kind: "variant",
indexPrefix: "v",
stateArrayKey: "variants",
counterKey: "nextVariantV",
dedupeKey: "filename",
visionPromoted: true,
window: 2
},
picture: {
kind: "picture",
indexPrefix: "p",
stateArrayKey: "pictures",
counterKey: "nextPictureP",
dedupeKey: "sourceUrl",
visionPromoted: false
// No window assignment: not vision-promoted
},
image: {
kind: "image",
indexPrefix: "i",
stateArrayKey: "images",
counterKey: "nextImageI",
dedupeKey: "filename",
visionPromoted: true,
window: 2
}
};
function getAttachmentWindowSize() {
return VISION_WINDOW_SIZES.attachments;
}
function getEverythingElseWindowSize() {
return VISION_WINDOW_SIZES.everythingElse;
}
function getWindowSizeForKind(kind) {
const config = MEDIA_SOURCE_REGISTRY[kind];
if (!config.visionPromoted || config.window === void 0) return void 0;
return config.window === 1 ? VISION_WINDOW_SIZES.attachments : VISION_WINDOW_SIZES.everythingElse;
}
var FASTVLM_DEFAULT_ENDPOINT = "http://localhost:8765/analyze";
var MLX_VISION_ANALYZE_IMAGE_PROMPT = "";
var MLX_VISION_INDEX_IMAGE_PROMPT = "";
// src/config.ts
import { createConfigSchematics } from "@lmstudio/sdk";
var configSchematics = createConfigSchematics().field(
"model",
"string",
{
displayName: "Agent Model",
subtitle: "Enter the vision model to use as orchestrator. Default: Qwen3.5 35B.",
placeholder: "qwen/qwen3.5-35b-a3b"
},
"qwen/qwen3.5-35b-a3b"
).field(
"visionPromotionPersistent",
"boolean",
{
displayName: "Vision Promotion: Persistent",
subtitle: "ON: promote up to 5 attachments + 4 variants every turn. OFF: promote only when new.",
engineDoesNotSupport: true
},
false
).field(
"logRequests",
"boolean",
{
displayName: "Debug: Log requests/response",
subtitle: "Logs full request/response JSON; may include sensitive data.",
engineDoesNotSupport: true
},
false
).field(
"debugPromotion",
"boolean",
{
displayName: "Debug: Media promotion",
subtitle: "Verbose logs for media state, previews and cleanup.",
engineDoesNotSupport: true
},
false
).field(
"debugChunks",
"boolean",
{
displayName: "Debug: Stream chunk logs",
subtitle: "Log raw streaming chunks to console (verbose).",
engineDoesNotSupport: true
},
false
).build();
var globalConfigSchematics = createConfigSchematics().field(
"baseUrl",
"string",
{
displayName: "LM Studio API base-URL",
subtitle: "Local LM Studio server base-URL. Default: http://127.0.0.1:1234/v1",
placeholder: "http://127.0.0.1:1234/v1"
},
"http://127.0.0.1:1234/v1"
).field(
"apiKey",
"string",
{
displayName: "(Optional) API Key",
subtitle: "Only needed if your LM Studio server requires authentication.",
isProtected: true,
placeholder: "sk-..."
},
""
).field(
"PREVIEW_IN_CHAT",
"boolean",
{
displayName: "Simple Previews in Chat",
subtitle: "When enabled, tool responses include client-based image previews.",
engineDoesNotSupport: false
},
false
).field(
"unloadAgentModelDuringRender",
"boolean",
{
displayName: "Unload Agent Model During Render",
subtitle: "When enabled, unloads the agent model from VRAM before long renders (text2video, image2video). Only applies to local LM Studio instances.",
engineDoesNotSupport: false
},
true
).field(
"DRAW_THINGS_HOST",
"string",
{
displayName: "Draw Things Host",
subtitle: "Hostname or IP of the Draw Things backend server.",
placeholder: "127.0.0.1"
},
"127.0.0.1"
).field(
"DRAW_THINGS_HTTP_PORT",
"numeric",
{
displayName: "Draw Things HTTP Port",
subtitle: "HTTP API port (default: 7860)."
},
7860
).field(
"DRAW_THINGS_GRPC_PORT",
"numeric",
{
displayName: "Draw Things gRPC Port",
subtitle: "gRPC port (default: 7859)."
},
7859
).field(
"customConfigsPath",
"string",
{
displayName: "Custom Configs Path",
subtitle: "Path to custom_configs.json from Draw Things. Change to: `none` to disable.",
placeholder: "~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models/custom_configs.json",
engineDoesNotSupport: false
},
"~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models/custom_configs.json"
).field(
"HTTP_SERVER_PORT",
"numeric",
{
displayName: "Local HTTP Server Port",
subtitle: "Port for serving generated images over localhost (default: 54760).",
engineDoesNotSupport: true
},
54760
).build();
// src/preprocessor.ts
async function preprocess(ctl, userMessage) {
try {
const originalText = userMessage.getText();
const files = userMessage.consumeFiles(ctl.client, (file) => file.type !== "image");
let injected = "";
for (const file of files) {
const { content } = await ctl.client.files.parseDocument(file, { signal: ctl.abortSignal });
const snippet = content.length > 2e5 ? content.slice(0, 2e5) + "\n...[truncated]" : content;
injected += `
[Attachment: ${file.name}]
${snippet}
`;
}
const wrappers = [];
const inlineImageUrls = /* @__PURE__ */ new Set();
const mdImg = /!\[[^\]]*\]\((https?:[^\s)]+)\)/g;
let m;
while ((m = mdImg.exec(originalText)) !== null) inlineImageUrls.add(m[1]);
const tagImg = /\[image:\s*(https?:[^\]\s]+)\s*\]/gi;
while ((m = tagImg.exec(originalText)) !== null) inlineImageUrls.add(m[1]);
for (const url of inlineImageUrls) {
wrappers.push(`[[LMSTUDIO_ATTACHMENT: ${JSON.stringify({ kind: "image", url })}]]`);
}
if (injected.length === 0 && wrappers.length === 0) return userMessage;
const combined = `${originalText}${injected}${wrappers.length ? "\n\n" + wrappers.join("\n") : ""}`.trim();
userMessage.replaceText(combined);
return userMessage;
} catch (err) {
return userMessage;
}
}
// src/vision-promotion.ts
import fs3 from "fs";
import path3 from "path";
async function toOpenAIPromptParts(items) {
const parts = [];
for (const it of items) {
const label = it.label || "Image";
parts.push({ type: "text", text: label });
const buf = await fs3.promises.readFile(it.previewAbs);
const b64 = buf.toString("base64");
const ext = path3.extname(it.previewAbs).toLowerCase();
const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
parts.push({ type: "image_url", image_url: { url: `data:${mime};base64,${b64}` } });
}
return parts;
}
// src/helpers/agentModelUnload.ts
import axios from "axios";
import fs4 from "fs";
import path4 from "path";
function normalizeLmApiRoot(baseUrl) {
return String(baseUrl || "").trim().replace(/\/(api\/v1|v1)\/?$/i, "").replace(/\/+$/, "");
}
function buildLmApiAuthHeaders(apiKey) {
if (typeof apiKey === "string" && apiKey.trim()) {
return { Authorization: `Bearer ${apiKey.trim()}` };
}
return {};
}
function appendToPluginLog(line) {
try {
const logsDir = getLogsDir();
fs4.mkdirSync(logsDir, { recursive: true });
fs4.appendFileSync(
path4.join(logsDir, "generate-image-plugin.log"),
`${(/* @__PURE__ */ new Date()).toISOString()} - ${line}
`
);
} catch {
}
}
async function unloadAgentModel(opts) {
const apiRoot = normalizeLmApiRoot(opts.baseUrl);
const unloadUrl = `${apiRoot}/api/v1/models/unload`;
try {
await axios.post(
unloadUrl,
{ instance_id: opts.modelKey },
{
timeout: 15e3,
headers: {
"Content-Type": "application/json",
...buildLmApiAuthHeaders(opts.apiKey)
}
}
);
appendToPluginLog(`[INFO] [AgentModel] \u2713 Unloaded: ${opts.modelKey}`);
console.info(`[INFO] [AgentModel] Unloaded model to free VRAM: ${opts.modelKey}`);
return true;
} catch (e) {
console.warn(`[AgentModel] Unload failed (non-fatal):`, e?.message || e);
return false;
}
}
// src/helpers/activeChatContext.ts
var activeChatContext = null;
function setActiveChatContext(ctx) {
activeChatContext = {
chatId: String(ctx.chatId),
workingDir: String(ctx.workingDir),
setAtMs: Date.now(),
requestId: ctx.requestId
};
}
function getActiveChatContext(opts) {
const maxAgeMs = typeof opts?.maxAgeMs === "number" && Number.isFinite(opts.maxAgeMs) ? Math.max(0, Math.floor(opts.maxAgeMs)) : 60 * 1e3;
if (!activeChatContext) return null;
if (maxAgeMs === 0) return activeChatContext;
const ageMs = Date.now() - activeChatContext.setAtMs;
if (!Number.isFinite(ageMs) || ageMs < 0) return null;
if (ageMs > maxAgeMs) return null;
return activeChatContext;
}
// src/helpers/attachments.ts
import { promises as fsp2 } from "fs";
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
import path5, { join as join2, extname } from "path";
import os from "os";
// src/helpers/findLMStudioHome.ts
import {
existsSync,
readFileSync,
realpathSync,
writeFileSync
} from "fs";
import { homedir } from "os";
import { join } from "path";
var lmstudioHome = null;
function findLMStudioHome() {
if (lmstudioHome !== null) {
return lmstudioHome;
}
const resolvedHomeDir = realpathSync(homedir());
const pointerFilePath = join(resolvedHomeDir, ".lmstudio-home-pointer");
if (existsSync(pointerFilePath)) {
const candidate = readFileSync(pointerFilePath, "utf-8").trim();
try {
if (candidate && existsSync(candidate)) {
const hasConversations = existsSync(join(candidate, "conversations"));
const hasUserFiles = existsSync(join(candidate, "user-files"));
if (hasConversations || hasUserFiles) {
lmstudioHome = candidate;
return lmstudioHome;
}
}
} catch {
}
}
const dotHome = join(resolvedHomeDir, ".lmstudio");
const cacheHome = join(resolvedHomeDir, ".cache", "lm-studio");
const looksValid = (p) => {
try {
if (!existsSync(p)) return false;
const conv = join(p, "conversations");
const files = join(p, "user-files");
return existsSync(conv) || existsSync(files);
} catch {
return false;
}
};
if (looksValid(dotHome)) {
lmstudioHome = dotHome;
try {
writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
if (looksValid(cacheHome)) {
lmstudioHome = cacheHome;
try {
writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
const home = dotHome;
lmstudioHome = home;
try {
writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
function getLMStudioWorkingDir(chatId) {
const home = findLMStudioHome();
return join(home, "working-directories", chatId);
}
// src/helpers/attachments.ts
var loadedSharp = void 0;
var loadedJimp = void 0;
async function tryLoadSharp() {
if (loadedSharp !== void 0) return loadedSharp;
try {
const mod = await import("sharp");
loadedSharp = mod?.default || mod;
return loadedSharp;
} catch {
loadedSharp = null;
return null;
}
}
async function tryLoadJimp() {
if (loadedJimp !== void 0) return loadedJimp;
try {
const mod = await import("jimp");
loadedJimp = mod.default || mod.Jimp || mod;
return loadedJimp;
} catch {
loadedJimp = null;
return null;
}
}
async function validateImageBuffer(buf) {
const sharp = await tryLoadSharp();
if (sharp) {
try {
const meta = await sharp(buf).metadata();
if (meta && (meta.width || meta.height)) {
return { ok: true, width: meta.width, height: meta.height };
}
} catch {
}
return { ok: false };
}
const Jimp = await tryLoadJimp();
if (Jimp && typeof Jimp.read === "function") {
try {
const img = await Jimp.read(buf);
const w = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const h = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
if (w || h) return { ok: true, width: w, height: h };
} catch {
}
return { ok: false };
}
return { ok: true };
}
function projectRootFromHere() {
const currentDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
if (currentDir.endsWith("helpers") || currentDir.endsWith("src")) {
return path5.resolve(currentDir, "..", "..");
}
return currentDir;
}
async function readLastAttachmentRef() {
try {
const root = projectRootFromHere();
const p = join2(root, "logs", "last-attachment.json");
if (!existsSync2(p)) return null;
const raw = await fsp2.readFile(p, "utf-8");
const json = JSON.parse(raw);
if (!json || typeof json !== "object" || !json.client) return null;
return json;
} catch {
return null;
}
}
function inferMimeFromExt(name, fallback = "image/png") {
if (!name) return fallback;
const ext = extname(name).toLowerCase().replace(/^\./, "");
switch (ext) {
case "jpg":
case "jpeg":
return "image/jpeg";
case "png":
return "image/png";
case "webp":
return "image/webp";
case "gif":
return "image/gif";
case "bmp":
return "image/bmp";
case "tif":
case "tiff":
return "image/tiff";
case "heic":
return "image/heic";
default:
return fallback;
}
}
async function rehydrateLastAttachment() {
try {
const ref = await readLastAttachmentRef();
if (!ref) return { ok: false, reason: "No persisted attachment reference" };
if (ref.client === "lmstudio") {
if (!ref.fileIdentifier)
return {
ok: false,
reason: "Persisted LM attachment has no fileIdentifier"
};
const LM_HOME2 = findLMStudioHome() || join2(os.homedir(), ".lmstudio");
const userFilesDir = join2(LM_HOME2, "user-files");
const metaPath = join2(
userFilesDir,
`${ref.fileIdentifier}.metadata.json`
);
let buffer = null;
let mime;
if (existsSync2(metaPath)) {
try {
const metaRaw = readFileSync2(metaPath, "utf-8");
const meta = JSON.parse(metaRaw);
const dataStr = typeof meta?.preview?.data === "string" ? meta.preview.data : typeof meta?.content === "string" ? meta.content : typeof meta?.base64 === "string" ? meta.base64 : typeof meta?.data === "string" ? meta.data : void 0;
if (dataStr) {
const s = dataStr.trim();
let b64 = null;
let mimeFromHeader;
if (s.startsWith("data:")) {
const comma = s.indexOf(",");
if (comma > 0) {
b64 = s.slice(comma + 1);
const header = s.slice(5, comma);
const semi = header.indexOf(";");
mimeFromHeader = (semi >= 0 ? header.slice(0, semi) : header) || void 0;
}
} else {
const stripped = s.replace(/\s+/g, "");
if (/^[A-Za-z0-9+/]+={0,2}$/.test(stripped) && stripped.length >= 8) {
b64 = stripped;
}
}
if (b64) {
try {
const buf = Buffer.from(b64, "base64");
const im = await validateImageBuffer(buf);
if (im.ok) {
buffer = buf;
mime = mimeFromHeader || meta?.preview?.mimeType || meta?.mimeType || meta?.mimetype || meta?.contentType;
}
} catch {
}
}
}
} catch {
}
}
if (!buffer) {
const orig = join2(userFilesDir, ref.fileIdentifier);
if (existsSync2(orig)) buffer = readFileSync2(orig);
}
if (!buffer)
return {
ok: false,
reason: "Persisted LM attachment not found on disk"
};
const imgCheck = await validateImageBuffer(buffer);
if (!imgCheck.ok) {
return {
ok: false,
reason: "Persisted LM attachment is not a valid image"
};
}
const mimeType = mime || inferMimeFromExt(ref.fileIdentifier, "image/png");
return {
ok: true,
buffer,
mimeType,
tag: `lmstudio_attachment:${ref.fileIdentifier} (persisted)`
};
}
return {
ok: false,
reason: `Unknown client in last-attachment: ${ref.client}`
};
} catch (e) {
return { ok: false, reason: e?.message || String(e) };
}
}
async function getLMStudioFileMetadata(fileIdentifier) {
try {
const home = findLMStudioHome() || join2(os.homedir(), ".lmstudio");
const metadataPath = join2(
home,
"user-files",
`${fileIdentifier}.metadata.json`
);
if (!existsSync2(metadataPath)) return null;
const raw = await fsp2.readFile(metadataPath, "utf-8");
const meta = JSON.parse(raw);
if (typeof meta.originalName !== "string" || typeof meta.fileIdentifier !== "string") {
return null;
}
return meta;
} catch {
return null;
}
}
async function getOriginalFileName(fileIdentifier) {
const meta = await getLMStudioFileMetadata(fileIdentifier);
return meta?.originalName ?? null;
}
function getLMStudioUserFilePath(fileIdentifier) {
const home = findLMStudioHome() || join2(os.homedir(), ".lmstudio");
const filePath = join2(home, "user-files", fileIdentifier);
return existsSync2(filePath) ? filePath : null;
}
// src/helpers/auditLog.ts
import fs5 from "fs";
import path6 from "path";
function buildPaths() {
const logsDir = getLogsDir();
const filePath = path6.resolve(logsDir, "generate-image-plugin.audit.jsonl");
return { logsDir, filePath };
}
function localTimestamp() {
try {
return (/* @__PURE__ */ new Date()).toLocaleString(void 0, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
timeZoneName: "short"
});
} catch {
return (/* @__PURE__ */ new Date()).toString();
}
}
function buildAuditLogger({
backend,
mode
}) {
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const entry = {
timestamp: localTimestamp(),
requestId,
backend,
mode
};
function setChatId(id) {
if (typeof id === "string" && id.trim().length > 0) entry.chat_id = id;
}
function setUserRequest(req) {
entry.user_request = req;
}
function setRenderTarget(target) {
entry.render_target = target;
}
function setInputs(inputs) {
entry.inputs = inputs;
}
function setOutput(output) {
entry.output = { ...entry.output, ...output };
}
function setError(err) {
let message = "unknown error";
let status = void 0;
if (typeof err === "string") message = err;
else if (err && typeof err === "object") {
const anyErr = err;
message = anyErr.message || JSON.stringify(anyErr);
if (typeof anyErr.status === "number") status = anyErr.status;
}
entry.error = status ? { message, status } : { message };
}
async function write() {
try {
const { logsDir, filePath } = buildPaths();
await fs5.promises.mkdir(logsDir, { recursive: true });
const block = JSON.stringify(entry, null, 2) + "\n\n";
await fs5.promises.appendFile(filePath, block, { encoding: "utf8" });
} catch (e) {
console.error(
"auditLog write failed:",
e instanceof Error ? e.message : String(e)
);
}
}
return {
requestId,
setChatId,
setUserRequest,
setRenderTarget,
setInputs,
setOutput,
setError,
write
};
}
// src/helpers/drawThingsGrpcAssetCheck.ts
import path7 from "node:path";
function normBasename(file) {
return path7.basename(String(file || "").trim());
}
function uniqNonEmptyBasenames(files) {
const seen = /* @__PURE__ */ new Set();
const out = [];
for (const f of files) {
if (!f) continue;
const b = normBasename(f);
if (!b) continue;
if (seen.has(b)) continue;
seen.add(b);
out.push(b);
}
return out;
}
function decodeJsonBytes(buffer) {
if (!buffer) return [];
try {
const b = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
if (!b.length) return [];
const txt = b.toString("utf8");
const parsed = JSON.parse(txt);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function extractFileLikeStrings(items) {
const out = [];
for (const it of items) {
if (!it || typeof it !== "object") continue;
for (const key of ["file", "filename", "name", "path"]) {
const v = it[key];
if (typeof v === "string" && v.trim()) out.push(v);
}
}
return out;
}
function buildMissingDetails({
modelFile,
missingModel,
missingLoras,
hintModels,
hintLoras
}) {
const parts = [];
parts.push("Draw Things gRPC: requested assets not found on server.");
if (missingModel) {
parts.push(`Missing model: ${missingModel}`);
}
if (missingLoras && missingLoras.length) {
parts.push(`Missing LoRA(s): ${missingLoras.join(", ")}`);
}
parts.push(
"The Draw Things gRPC server may silently fall back to a different model/without LoRA; generation was aborted to avoid producing misleading output."
);
if (hintModels && hintModels.length) {
parts.push(`Available model files (sample): ${hintModels.join(", ")}`);
}
if (hintLoras && hintLoras.length) {
parts.push(`Available LoRA files (sample): ${hintLoras.join(", ")}`);
}
if (modelFile) {
parts.push(
"Fix: copy the missing .ckpt into the server's model/LoRA directories (or adjust the requested filenames)."
);
}
return parts.join("\n");
}
async function filesExist(client, files, sharedSecret) {
try {
const fn = client?.filesExist?.bind(client);
if (typeof fn !== "function") return null;
return await new Promise((resolve, reject) => {
const req = { files, filesWithHash: [] };
if (sharedSecret) req.sharedSecret = sharedSecret;
fn(req, (err, resp) => {
if (err) return reject(err);
const existArr = Array.isArray(resp?.existences) ? resp.existences : [];
resolve(existArr.slice(0, files.length));
});
});
} catch {
return null;
}
}
async function checkDrawThingsGrpcFilesExist(input) {
const checked = uniqNonEmptyBasenames(input.files || []);
if (checked.length === 0) {
return { ok: true, checked: [], missing: [], usedFilesExist: true };
}
const existArr = await filesExist(input.client, checked, input.sharedSecret);
if (!existArr) {
return { ok: true, checked, missing: [], usedFilesExist: false };
}
const missing = [];
for (let i = 0; i < checked.length; i++) {
if (existArr[i] !== true) missing.push(checked[i]);
}
return {
ok: missing.length === 0,
checked,
missing,
usedFilesExist: true
};
}
async function tryBrowseViaEcho(client, sharedSecret) {
try {
const echo = client?.echo?.bind(client);
if (typeof echo !== "function") return null;
const req = { name: "generate-image-plugin" };
if (sharedSecret) req.sharedSecret = sharedSecret;
const reply = await new Promise((resolve, reject) => {
echo(req, (err, resp) => err ? reject(err) : resolve(resp));
});
const modelFiles = uniqNonEmptyBasenames(
Array.isArray(reply?.files) ? reply.files : []
);
const ov = reply?.override || {};
const lorasJson = decodeJsonBytes(ov?.loras);
const modelsJson = decodeJsonBytes(ov?.models);
const loraFiles = uniqNonEmptyBasenames(extractFileLikeStrings(lorasJson));
const modelFilesFromJson = uniqNonEmptyBasenames(
extractFileLikeStrings(modelsJson)
);
return {
modelFiles: uniqNonEmptyBasenames([...modelFiles, ...modelFilesFromJson]),
loraFiles
};
} catch {
return null;
}
}
function pickSample(list, limit) {
if (!list.length) return [];
return list.slice(0, Math.max(0, limit));
}
async function checkDrawThingsGrpcAssets(req) {
const requestedModel = req.modelFile ? normBasename(req.modelFile) : "";
const requestedLoras = uniqNonEmptyBasenames(req.loraFiles || []);
if (!requestedModel && requestedLoras.length === 0) return { ok: true };
const filesToCheck = uniqNonEmptyBasenames([
requestedModel || void 0,
...requestedLoras
]);
const existArr = await filesExist(req.client, filesToCheck, req.sharedSecret);
if (existArr) {
const missing = [];
for (let i = 0; i < filesToCheck.length; i++) {
if (existArr[i] !== true) missing.push(filesToCheck[i]);
}
if (missing.length === 0) return { ok: true };
const missingModel2 = requestedModel && missing.includes(requestedModel) ? requestedModel : void 0;
const missingLoras2 = requestedLoras.filter((x) => missing.includes(x));
return {
ok: false,
missingModel: missingModel2,
missingLoras: missingLoras2.length ? missingLoras2 : void 0,
details: buildMissingDetails({
modelFile: requestedModel || void 0,
missingModel: missingModel2,
missingLoras: missingLoras2
})
};
}
const browse = await tryBrowseViaEcho(req.client, req.sharedSecret);
if (!browse) {
return { ok: true };
}
const missingModel = requestedModel && !browse.modelFiles.includes(requestedModel) ? requestedModel : void 0;
const missingLoras = requestedLoras.filter(
(x) => !browse.loraFiles.includes(x)
);
if (!missingModel && missingLoras.length === 0) return { ok: true };
return {
ok: false,
missingModel,
missingLoras: missingLoras.length ? missingLoras : void 0,
details: buildMissingDetails({
modelFile: requestedModel || void 0,
missingModel,
missingLoras,
hintModels: pickSample(browse.modelFiles, 12),
hintLoras: pickSample(browse.loraFiles, 12)
})
};
}
// src/helpers/httpServer.ts
import http from "http";
import fs6 from "fs";
import path8 from "path";
var DEFAULT_HOST = "127.0.0.1";
function envPort() {
const v = process.env.HTTP_SERVER_PORT;
if (v == null || String(v).trim() === "") return void 0;
const n = Number(v);
return Number.isInteger(n) && n >= 1024 && n <= 65535 ? n : void 0;
}
async function healthCheck(port, host = DEFAULT_HOST) {
return new Promise((resolve) => {
const req = http.get(
{ host, port, path: "/__healthz", timeout: 600 },
(res) => {
try {
const ok = (res.statusCode || 0) === 200 && String(res.headers["x-mcp-image-server"]) === "1";
res.resume();
res.once("end", () => resolve(ok));
} catch {
resolve(false);
}
}
);
req.on("timeout", () => {
try {
req.destroy();
} catch {
}
resolve(false);
});
req.on("error", () => resolve(false));
});
}
function toHttpOriginalUrl(fileName, baseUrl, chatId) {
if (chatId) {
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(
chatId
)}/${encodeURIComponent(fileName)}`;
}
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(fileName)}`;
}
function toHttpPreviewUrl(fileName, baseUrl, chatId) {
if (chatId) {
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(
chatId
)}/${encodeURIComponent(fileName)}`;
}
return `${baseUrl.replace(/\/$/, "")}/previews/${encodeURIComponent(
fileName
)}`;
}
async function getHealthyServerBaseUrl(host = DEFAULT_HOST) {
try {
const fixedPort = envPort();
if (fixedPort == null) return "";
const ok = await healthCheck(fixedPort, host).catch(() => false);
if (ok) return `http://127.0.0.1:${fixedPort}`;
return "";
} catch {
return "";
}
}
async function isHttpServerHealthy(host = DEFAULT_HOST) {
try {
const fixedPort = envPort();
if (fixedPort == null) return false;
const ok = await healthCheck(fixedPort, host).catch(() => false);
return !!ok;
} catch {
return false;
}
}
// src/helpers/imageUtils.ts
var loadedSharp2 = void 0;
var loadedJimp2 = void 0;
var libLogged = false;
var loggedFns = /* @__PURE__ */ new Set();
function logOnce(msg) {
if (loggedFns.has(msg)) return;
loggedFns.add(msg);
try {
console.debug(msg);
} catch {
}
}
async function tryLoadSharp2() {
if (loadedSharp2 !== void 0) return loadedSharp2;
try {
const mod = await import("sharp");
loadedSharp2 = mod?.default || mod;
if (!libLogged) {
try {
console.debug(`[imageUtils] using lib=sharp`);
libLogged = true;
} catch {
}
}
return loadedSharp2;
} catch {
loadedSharp2 = null;
return null;
}
}
async function tryLoadJimp2() {
if (loadedJimp2 !== void 0) return loadedJimp2;
try {
const mod = await import("jimp");
const candidate = mod && (mod.default || mod.Jimp || mod);
loadedJimp2 = candidate;
if (!libLogged) {
try {
console.debug(`[imageUtils] using lib=jimp`);
libLogged = true;
} catch {
}
}
return loadedJimp2;
} catch {
loadedJimp2 = null;
return null;
}
}
function isPng(buf) {
try {
return buf && buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71 && buf[4] === 13 && buf[5] === 10 && buf[6] === 26 && buf[7] === 10;
} catch {
return false;
}
}
function jimpHasFn(obj, name) {
try {
return obj && typeof obj[name] === "function";
} catch {
return false;
}
}
async function jimpResizeCompat(img, w, h) {
if (!jimpHasFn(img, "resize")) {
throw new Error("Jimp resize not available");
}
let lastError;
try {
await img.resize({ w, h });
return;
} catch (e) {
lastError = e;
}
try {
await img.resize(w, h);
return;
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp resize failed for both API variants: ${lastError?.message || String(lastError)}`
);
}
async function jimpCropCompat(img, x, y, w, h) {
if (!jimpHasFn(img, "crop")) {
throw new Error("Jimp crop not available");
}
let lastError;
try {
await img.crop({ x, y, w, h });
return;
} catch (e) {
lastError = e;
}
try {
await img.crop(x, y, w, h);
return;
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp crop failed for both API variants: ${lastError?.message || String(lastError)}`
);
}
async function jimpAutoRotate(img) {
try {
if (jimpHasFn(img, "exifRotate")) {
await img.exifRotate();
} else if (jimpHasFn(img, "rotate")) {
}
} catch (e) {
try {
console.error(`[imageUtils] Jimp EXIF rotation failed: ${String(e)}`);
} catch {
}
}
}
async function jimpGetBufferCompat(img, mime, options) {
let lastError;
try {
if (jimpHasFn(img, "getBufferAsync")) {
return await img.getBufferAsync(mime, options);
}
} catch (e) {
lastError = e;
}
try {
if (jimpHasFn(img, "getBuffer")) {
return await img.getBuffer(mime, options);
}
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp getBuffer failed for both API variants (mime=${mime}): ${lastError?.message || String(lastError)}`
);
}
async function getSize(buffer) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.getSize] using Sharp`);
const meta = await sharp(buffer).metadata();
return { width: meta.width || 0, height: meta.height || 0 };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.getSize] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const w = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const h = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
return { width: w || 0, height: h || 0 };
}
return { width: 0, height: 0 };
}
async function toPng(buffer) {
if (isPng(buffer)) return buffer;
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.toPng] using Sharp`);
return await sharp(buffer).png().toBuffer();
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.toPng] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
return await jimpGetBufferCompat(img, "image/png");
}
return buffer;
}
async function resizeAndEncode(buffer, format, quality, width, _maxBytes) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.resizeAndEncode] using Sharp`);
const pipeline = sharp(buffer).rotate().resize({ width, fit: "inside", withoutEnlargement: false });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : width;
const outH = typeof info.height === "number" ? info.height : Math.round(width * 0.75);
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncode] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = width / Math.max(1, origW);
const outW = Math.max(1, Math.round(origW * scale));
const outH = Math.max(1, Math.round(origH * scale));
await jimpResizeCompat(img, outW, outH);
const q = clampQuality(quality);
if (format === "jpeg") {
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: outW, height: outH };
} else {
try {
const data = await jimpGetBufferCompat(img, "image/webp", {
quality: q
});
return { data, width: outW, height: outH };
} catch {
const data = await jimpGetBufferCompat(img, "image/jpeg", {
quality: q
});
return { data, width: outW, height: outH };
}
}
}
return { data: buffer, width, height: Math.round(width * 0.75) };
}
async function resizeAndEncodeByHeight(buffer, format, quality, maxHeight) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.resizeAndEncodeByHeight] using Sharp`);
const pipeline = sharp(buffer).rotate().resize({ height: maxHeight, fit: "inside", withoutEnlargement: false });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : Math.round(maxHeight * 1.33);
const outH = typeof info.height === "number" ? info.height : maxHeight;
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncodeByHeight] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = maxHeight / Math.max(1, origH);
const outH = Math.max(1, Math.round(maxHeight));
const outW = Math.max(1, Math.round(origW * scale));
await jimpResizeCompat(img, outW, outH);
const q = clampQuality(quality);
if (format === "jpeg") {
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: outW, height: outH };
} else {
try {
const data = await jimpGetBufferCompat(img, "image/webp", {
quality: q
});
return { data, width: outW, height: outH };
} catch {
const data = await jimpGetBufferCompat(img, "image/jpeg", {
quality: q
});
return { data, width: outW, height: outH };
}
}
}
return {
data: buffer,
width: Math.round(maxHeight * 1.33),
height: maxHeight
};
}
async function resizeAndEncodeBySum(buffer, format, quality, maxSum) {
const sharp = await tryLoadSharp2();
const ALIGN = 64;
const calcDims = (origW, origH) => {
let w = Math.max(1, Math.round(origW));
let h = Math.max(1, Math.round(origH));
const currentSum = w + h;
if (currentSum > maxSum) {
const scale = maxSum / currentSum;
w = Math.max(1, Math.round(w * scale));
h = Math.max(1, Math.round(h * scale));
}
w = Math.max(ALIGN, Math.floor(w / ALIGN) * ALIGN);
h = Math.max(ALIGN, Math.floor(h / ALIGN) * ALIGN);
while (w + h > maxSum && (w > ALIGN || h > ALIGN)) {
if (w >= h) w = Math.max(ALIGN, w - ALIGN);
else h = Math.max(ALIGN, h - ALIGN);
}
return { w, h };
};
if (sharp) {
logOnce(`[imageUtils.resizeAndEncodeBySum] using Sharp`);
const metadata = await sharp(buffer).metadata();
const origW = metadata.width ?? 640;
const origH = metadata.height ?? 640;
const { w, h } = calcDims(origW, origH);
const pipeline = sharp(buffer).rotate().resize({ width: w, height: h, fit: "cover", position: "centre" });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : w;
const outH = typeof info.height === "number" ? info.height : h;
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncodeBySum] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 640;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 640;
const { w, h } = calcDims(origW, origH);
if (!origW || !origH) {
await jimpResizeCompat(img, w, h);
} else {
const scale = Math.max(w / origW, h / origH);
const newW = Math.max(1, Math.round(origW * scale));
const newH = Math.max(1, Math.round(origH * scale));
await jimpResizeCompat(img, newW, newH);
const x = Math.max(0, Math.floor((newW - w) / 2));
const y = Math.max(0, Math.floor((newH - h) / 2));
await jimpCropCompat(img, x, y, w, h);
}
const q = clampQuality(quality);
if (format === "jpeg") {
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: w, height: h };
} else {
try {
const data = await jimpGetBufferCompat(img, "image/webp", {
quality: q
});
return { data, width: w, height: h };
} catch {
const data = await jimpGetBufferCompat(img, "image/jpeg", {
quality: q
});
return { data, width: w, height: h };
}
}
}
return {
data: buffer,
width: Math.round(maxSum / 2),
height: Math.round(maxSum / 2)
};
}
async function resizeInsideToPng(buffer, targetW, targetH) {
const sharp = await tryLoadSharp2();
const w = Math.max(1, Math.round(targetW));
const h = Math.max(1, Math.round(targetH));
if (sharp) {
logOnce(`[imageUtils.resizeInsideToPng] using Sharp`);
return await sharp(buffer).rotate().resize({ width: w, height: h, fit: "inside", withoutEnlargement: false }).png().toBuffer();
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeInsideToPng] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = Math.min(w / Math.max(1, origW), h / Math.max(1, origH));
const newW = Math.max(1, Math.round(origW * scale));
const newH = Math.max(1, Math.round(origH * scale));
await jimpResizeCompat(img, newW, newH);
return await jimpGetBufferCompat(img, "image/png");
}
return buffer;
}
async function resizeCoverToPng(buffer, targetW, targetH) {
const sharp = await tryLoadSharp2();
const w = Math.max(1, Math.round(targetW));
const h = Math.max(1, Math.round(targetH));
if (sharp) {
logOnce(`[imageUtils.resizeCoverToPng] using Sharp`);
return await sharp(buffer).rotate().resize({ width: w, height: h, fit: "cover", position: "centre" }).png().toBuffer();
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeCoverToPng] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
if (!origW || !origH) {
await jimpResizeCompat(img, w, h);
return await jimpGetBufferCompat(img, "image/png");
}
const scale = Math.max(w / origW, h / origH);
const newW = Math.max(1, Math.round(origW * scale));
const newH = Math.max(1, Math.round(origH * scale));
await jimpResizeCompat(img, newW, newH);
const x = Math.max(0, Math.floor((newW - w) / 2));
const y = Math.max(0, Math.floor((newH - h) / 2));
await jimpCropCompat(img, x, y, w, h);
return await jimpGetBufferCompat(img, "image/png");
}
return buffer;
}
function clampQuality(q) {
if (!Number.isFinite(q)) return 80;
q = Math.round(q);
if (q < 1) q = 1;
if (q > 100) q = 100;
return q;
}
// src/helpers/injectionMarker.ts
var injectedRegistry = /* @__PURE__ */ new Map();
function stripKnownMarkdownInjections(text) {
if (!text) return text;
const lines = text.split(/\r?\n/);
const outLines = [];
const injectedImageLine = /^!\[(Image Variant v\d+|Image i\d+)\]\(\.(?:\/)?(preview-[^)\s]+\.(?:jpg|jpeg|webp|png)|generated-image-[^)\s]+\.(?:png|jpg|jpeg|webp))\)\s*$/i;
for (const line of lines) {
if (injectedImageLine.test(line.trim())) continue;
outLines.push(line);
}
let s = outLines.join("\n");
const kept = [];
const ls = s.split(/\r?\n/);
for (let i = 0; i < ls.length; i++) {
const line = ls[i];
const header = line.trim();
const isOurHeader = header === "| Preview | ID / Title | Source |" || header === "| Preview | ID / Title | Source |" || header === "| Preview | ID / Generation Data |" || header === "| Index Image Results |";
if (!isOurHeader) {
kept.push(line);
continue;
}
const sep = (ls[i + 1] ?? "").trim();
const isSeparator = /^\|[-\s|]+\|$/.test(sep) || sep === "|---|---|---|" || sep === "|---|---|" || sep === "|---|";
if (!isSeparator) {
kept.push(line);
continue;
}
let j = i + 2;
let sawImageCell = false;
for (; j < ls.length; j++) {
const row = ls[j];
const t = row.trim();
if (!t.startsWith("|") || !t.endsWith("|")) break;
if (t.includes(") sawImageCell = true;
}
if (sawImageCell) {
i = j - 1;
continue;
}
kept.push(line);
}
return kept.join("\n");
}
function trackInjection(chatId, markdown, _mediaType) {
if (!chatId || !markdown) return;
let set = injectedRegistry.get(chatId);
if (!set) {
set = /* @__PURE__ */ new Set();
injectedRegistry.set(chatId, set);
}
set.add(markdown);
}
function stripInjections(chatId, text) {
if (!chatId || !text) return text;
const set = injectedRegistry.get(chatId);
if (!set || set.size === 0) return text;
let result = text;
for (const injection of set) {
result = result.split(injection + "\n").join("");
result = result.split(injection).join("");
}
result = stripKnownMarkdownInjections(result);
return result.trim();
}
function hasTrackedInjections(chatId) {
const set = injectedRegistry.get(chatId);
return !!set && set.size > 0;
}
function clearInjections(chatId) {
injectedRegistry.delete(chatId);
}
function getInjectionCount(chatId) {
return injectedRegistry.get(chatId)?.size ?? 0;
}
var INJECTION_START = "";
var INJECTION_END = "";
function wrapInjection(markdown, _mediaType) {
return markdown;
}
function hasInjections(_text) {
return false;
}
// src/helpers/pluginMeta.ts
import fs7 from "node:fs";
import path9 from "node:path";
function getModuleDir() {
try {
return __dirname;
} catch {
return process.cwd();
}
}
function readJsonFile(filePath) {
try {
const raw = fs7.readFileSync(filePath, "utf8");
return JSON.parse(raw);
} catch {
return null;
}
}
function findUpwards(startDir, fileName, maxDepth = 8) {
let dir = startDir;
for (let i = 0; i < maxDepth; i++) {
const candidate = path9.join(dir, fileName);
if (fs7.existsSync(candidate)) return candidate;
const parent = path9.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}
function getPluginMeta() {
const moduleDir = getModuleDir();
const packageJsonPath = findUpwards(moduleDir, "package.json");
const manifestJsonPath = findUpwards(moduleDir, "manifest.json");
const packageJson = packageJsonPath ? readJsonFile(packageJsonPath) : null;
const manifestJson = manifestJsonPath ? readJsonFile(manifestJsonPath) : null;
const version = String(packageJson?.version || "unknown");
const owner = String(manifestJson?.owner || "").trim();
const name = String(manifestJson?.name || "").trim();
const revisionsUrl = owner && name ? `https://raw.githubusercontent.com/${owner}/${name}-docs/main/docs/CHANGELOG.md` : "https://raw.githubusercontent.com";
const pluginIdentifier = owner && name ? `${owner}/${name}` : name ? name : "unknown";
return { version, owner, name, revisionsUrl, pluginIdentifier };
}
function formatToolMetaBlock(meta = getPluginMeta()) {
return `Plugin-Identifier: ${meta.pluginIdentifier}
Plugin version: ${meta.version}`;
}
// src/helpers/projectUriResolver.ts
import initSqlJs from "sql.js";
import { existsSync as existsSync3, readFileSync as readFileSync3, unlinkSync } from "fs";
import { execFileSync } from "child_process";
import { tmpdir } from "os";
import { join as join3 } from "path";
var SQL = null;
async function getSqlJs() {
if (!SQL) {
SQL = await initSqlJs();
}
return SQL;
}
function isProjectUri(uri) {
return typeof uri === "string" && uri.startsWith("project://");
}
function parseProjectUri(uri) {
if (!isProjectUri(uri)) {
return null;
}
const withoutScheme = uri.slice("project://".length);
const hashIndex = withoutScheme.lastIndexOf("#");
if (hashIndex === -1) {
return null;
}
const projectPath = withoutScheme.slice(0, hashIndex);
const thumbnailId = parseInt(withoutScheme.slice(hashIndex + 1), 10);
if (isNaN(thumbnailId) || thumbnailId < 0) {
return null;
}
return { projectPath, thumbnailId };
}
function extractImageFromThumbnailBlob(blob) {
const jpegMarker = Buffer.from([255, 216, 255]);
const jpegStart = blob.indexOf(jpegMarker);
if (jpegStart !== -1) {
const jpegEnd = blob.lastIndexOf(Buffer.from([255, 217]));
if (jpegEnd !== -1 && jpegEnd > jpegStart) {
return blob.slice(jpegStart, jpegEnd + 2);
}
return blob.slice(jpegStart);
}
const pngMarker = Buffer.from([137, 80, 78, 71]);
const pngStart = blob.indexOf(pngMarker);
if (pngStart !== -1) {
const iendMarker = Buffer.from([
73,
69,
78,
68,
174,
66,
96,
130
]);
const pngEnd = blob.lastIndexOf(iendMarker);
if (pngEnd !== -1 && pngEnd > pngStart) {
return blob.slice(pngStart, pngEnd + 8);
}
return blob.slice(pngStart);
}
return null;
}
async function getThumbnailFromProject(projectPath, thumbnailId) {
let db = null;
let tempFile;
try {
const SQL2 = await getSqlJs();
const walPath = projectPath + "-wal";
if (existsSync3(walPath)) {
try {
tempFile = join3(tmpdir(), `dt-thumb-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.sqlite3`);
execFileSync("sqlite3", [projectPath, `VACUUM INTO '${tempFile}'`], { timeout: 1e4 });
const fileBuffer = readFileSync3(tempFile);
db = new SQL2.Database(fileBuffer);
} catch (e) {
console.warn(`[ProjectUriResolver] VACUUM INTO failed, falling back to direct read:`, e);
const fileBuffer = readFileSync3(projectPath);
db = new SQL2.Database(fileBuffer);
}
} else {
const fileBuffer = readFileSync3(projectPath);
db = new SQL2.Database(fileBuffer);
}
const result = db.exec(
`SELECT p FROM thumbnailhistorynode WHERE __pk0 = ${thumbnailId}`
);
if (result.length && result[0].values.length) {
const blob = Buffer.from(result[0].values[0][0]);
return extractImageFromThumbnailBlob(blob);
}
} catch (error) {
console.error(
`[ProjectUriResolver] Failed to get thumbnail from ${projectPath}:`,
error
);
} finally {
db?.close();
if (tempFile) try {
unlinkSync(tempFile);
} catch {
}
}
return null;
}
async function resolveProjectUri(uri) {
const parts = parseProjectUri(uri);
if (!parts) {
console.error(`[ProjectUriResolver] Invalid project URI: ${uri}`);
return null;
}
return getThumbnailFromProject(parts.projectPath, parts.thumbnailId);
}
function detectProjectImageType(buf) {
if (!buf || buf.length < 8) return null;
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) {
return "jpeg";
}
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) {
return "png";
}
return null;
}
// src/helpers/resolveImg2ImgSourceLMStudio.ts
import { promises as fsp4 } from "fs";
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
import { join as join5, basename as basename2, extname as extname2 } from "path";
import os2 from "os";
// src/helpers/resolveLMStudioChatId.ts
import { promises as fsp3 } from "fs";
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
import { join as join4, basename } from "path";
function scoreToConfidence(score) {
if (score >= 2) return "high";
if (score === 1) return "medium";
return "low";
}
async function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function resolveActiveLMStudioChatId(opts) {
const retries = typeof opts?.retries === "number" ? opts.retries : 4;
const delayMs = typeof opts?.delayMs === "number" ? opts.delayMs : 200;
const recentSec = typeof opts?.requireRecentMtimeSec === "number" ? opts.requireRecentMtimeSec : 120;
try {
const home = findLMStudioHome();
const convDir = join4(home, "conversations");
if (!existsSync4(convDir)) {
return {
ok: false,
reason: `LM Studio conversations dir not found: ${convDir}`
};
}
let chosenPath = null;
let mtimeMs = 0;
for (let attempt = 0; attempt < Math.max(1, retries); attempt++) {
const entries = await fsp3.readdir(convDir).catch(() => []);
const convFiles = entries.filter((f) => f.endsWith(".conversation.json")).map((f) => join4(convDir, f));
if (convFiles.length === 0) {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
return { ok: false, reason: "No conversation files found" };
}
const withTimes = convFiles.map((p) => {
try {
const s = statSync2(p);
return s.isFile() ? { p, t: s.mtimeMs } : null;
} catch {
return null;
}
}).filter(Boolean);
if (withTimes.length === 0) {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
return { ok: false, reason: "No readable conversation files" };
}
withTimes.sort((a, b) => b.t - a.t);
chosenPath = withTimes[0].p;
mtimeMs = withTimes[0].t;
try {
const raw = await fsp3.readFile(chosenPath, "utf8");
JSON.parse(raw);
break;
} catch {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
break;
}
}
if (!chosenPath)
return { ok: false, reason: "Failed to pick conversation" };
const chatId = basename(chosenPath).replace(/\.conversation\.json$/i, "");
let score = 0;
let reason = [];
if (mtimeMs > 0) {
const ageSec = (Date.now() - mtimeMs) / 1e3;
if (ageSec <= recentSec) {
score += 1;
reason.push(`recent:${Math.round(ageSec)}s`);
} else {
reason.push(`stale:${Math.round(ageSec)}s`);
}
}
try {
const raw = await fsp3.readFile(chosenPath, "utf8");
JSON.parse(raw);
score += 1;
reason.push("parse_ok");
} catch {
reason.push("parse_uncertain");
}
return {
ok: true,
chatId,
filePath: chosenPath,
mtimeMs,
confidence: scoreToConfidence(score),
reason: reason.join(",")
};
} catch (e) {
return { ok: false, reason: e?.message || String(e) };
}
}
// src/helpers/resolveImg2ImgSourceLMStudio.ts
var LM_HOME = join5(os2.homedir(), ".lmstudio");
try {
const h = findLMStudioHome();
if (h && typeof h === "string") LM_HOME = h;
} catch {
}
async function sleep2(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function waitForStableFile(filePath, {
maxWaitMs = 2400,
pollMs = 120,
stableRounds = 2
} = {}) {
const started = Date.now();
let lastSize = -1;
let sameCount = 0;
while (Date.now() - started <= maxWaitMs) {
try {
const st = statSync3(filePath);
if (!st.isFile()) return { ok: false, reason: "not a file" };
const size = st.size;
if (size > 0 && size === lastSize) {
sameCount++;
if (sameCount >= stableRounds) return { ok: true, size };
} else {
if (lastSize >= 0 && size !== lastSize) {
console.error(
`[LM] waitForStableFile: size changed ${lastSize} -> ${size} for ${filePath}`
);
}
sameCount = 0;
}
lastSize = size;
} catch (e) {
}
await sleep2(pollMs);
}
return { ok: false, reason: "timeout waiting for stable file" };
}
async function ensureFullyDecodable(input) {
try {
const meta = await getSize(input);
const width = typeof meta?.width === "number" ? meta.width : void 0;
const height = typeof meta?.height === "number" ? meta.height : void 0;
return {
ok: true,
buffer: input,
format: meta?.format,
width,
height
};
} catch (e) {
return { ok: false, reason: e?.message || String(e) };
}
}
async function readStableAndDecode(filePath) {
const stable = await waitForStableFile(filePath);
if (!stable.ok) {
console.error(`[LM] readStableAndDecode: ${stable.reason} for ${filePath}`);
}
const maxAttempts = 5;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const buf = await fsp4.readFile(filePath);
const validated = await ensureFullyDecodable(buf);
if (validated.ok) return { ok: true, buffer: buf };
console.error(
`[LM] decode attempt ${attempt} failed for ${filePath}: ${validated.reason}`
);
} catch (e) {
console.error(
`[LM] read attempt ${attempt} failed for ${filePath}: ${e?.message || e}`
);
}
await sleep2(150);
}
return { ok: false, reason: `failed to read/fully-decode: ${filePath}` };
}
function inferMimeFromExt2(name, fallback = "image/png") {
const ext = extname2(name).toLowerCase().replace(/^\./, "");
switch (ext) {
case "jpg":
case "jpeg":
return "image/jpeg";
case "png":
return "image/png";
case "webp":
return "image/webp";
case "gif":
return "image/gif";
case "bmp":
return "image/bmp";
case "tif":
case "tiff":
return "image/tiff";
case "heic":
return "image/heic";
default:
return fallback;
}
}
async function readJSONSafe(p) {
try {
const raw = await fsp4.readFile(p, "utf-8");
return JSON.parse(raw);
} catch {
return null;
}
}
async function resolveImg2ImgSourceLMStudio(opts) {
const options = {
freshOnly: opts?.freshOnly !== void 0 ? opts.freshOnly : false,
freshSeconds: typeof opts?.freshSeconds === "number" ? opts.freshSeconds : 180,
chatId: typeof opts?.chatId === "string" ? opts.chatId : void 0,
explicitAttachmentSource: opts?.explicitAttachmentSource === true,
attachmentIndex: typeof opts?.attachmentIndex === "number" && opts.attachmentIndex >= 1 ? Math.round(opts.attachmentIndex) : void 0
};
try {
const home = findLMStudioHome();
const convDir = join5(home, "conversations");
if (!existsSync5(convDir)) {
console.error(`[LM] Conversations dir not found: ${convDir}`);
return {
ok: false,
client: "lmstudio",
reason: `Conversations dir not found: ${convDir}`
};
}
let chatId = options.chatId || null;
let chosenConv = null;
if (chatId) {
const p = join5(convDir, `${chatId}.conversation.json`);
if (existsSync5(p)) {
chosenConv = p;
} else {
console.error(`[LM] Provided chatId has no conversation file: ${p}`);
}
}
if (!chosenConv) {
const best = await resolveActiveLMStudioChatId().catch(() => null);
if (!best || !best.ok) {
console.error(`[LM] No conversation files found in ${convDir}`);
return {
ok: false,
client: "lmstudio",
reason: "No conversation files found"
};
}
chosenConv = best.filePath;
chatId = best.chatId;
console.error(
`[LM] Heuristic selected conversation: ${chosenConv} (chatId=${chatId})`
);
}
if (!chatId) {
return {
ok: false,
client: "lmstudio",
reason: "Failed to resolve LM Studio chatId"
};
}
const workingDir = join5(home, "working-directories", chatId);
if (options.explicitAttachmentSource) {
const mediaStatePath = join5(workingDir, "chat_media_state.json");
if (existsSync5(mediaStatePath)) {
console.log(`[LM] Checking chat_media_state.json: ${mediaStatePath}`);
const mediaState = await readJSONSafe(mediaStatePath);
if (mediaState && Array.isArray(mediaState.attachments) && mediaState.attachments.length > 0) {
const sortedByA = [...mediaState.attachments].sort(
(a, b) => (b.a ?? 0) - (a.a ?? 0)
);
const highestA = sortedByA[0]?.n ?? mediaState.attachments.length;
const targetA = options.attachmentIndex ?? highestA;
const selectedAtt = mediaState.attachments.find(
(a) => a.a === targetA
);
if (!selectedAtt) {
const availableNs = mediaState.attachments.map((a) => a.a).filter((n) => typeof n === "number").sort((a, b) => a - b);
return {
ok: false,
client: "lmstudio",
reason: `Requested attachment a${targetA} not found. Available: a${availableNs.join(
", a"
)}`
};
}
const lastAtt = selectedAtt;
let imagePath = null;
if (typeof lastAtt.originAbs === "string" && existsSync5(lastAtt.originAbs)) {
imagePath = lastAtt.originAbs;
} else if (typeof lastAtt.filename === "string") {
const copyPath = join5(workingDir, lastAtt.filename);
if (existsSync5(copyPath)) imagePath = copyPath;
}
if (imagePath) {
try {
const read = await readStableAndDecode(imagePath);
if (read.ok) {
const buffer = read.buffer;
const mimeType = inferMimeFromExt2(imagePath, "image/png");
const fileIdentifier = typeof lastAtt.originAbs === "string" && lastAtt.originAbs.trim() ? lastAtt.originAbs : imagePath;
console.log(
`[LM] Resolved attachment from chat_media_state.json: ${fileIdentifier} (path=${imagePath}, a${targetA})`
);
return {
ok: true,
client: "lmstudio",
buffer,
mimeType,
sourceName: basename2(imagePath),
chatId,
fileIdentifier,
originalPath: imagePath,
originalName: typeof lastAtt.originalName === "string" && lastAtt.originalName ? lastAtt.originalName : void 0
};
} else {
console.error(
`[LM] chat_media_state attachment not decodable (${read.reason}), will try conversation.json`
);
}
} catch (e) {
console.error(
`[LM] Failed to read chat_media_state attachment ${imagePath}: ${e.message}`
);
}
} else {
console.error(
`[LM] chat_media_state.json has attachment but file not found: originAbs=${lastAtt.originAbs}, filename=${lastAtt.filename}`
);
}
}
}
}
console.error(
`[LM] No attachment available. Generator must process conversation.json first and write chat_media_state.json.`
);
return {
ok: false,
client: "lmstudio",
reason: "No attachment in chat_media_state.json or pending file. Wait for Generator to process conversation.json."
};
} catch (e) {
return { ok: false, client: "lmstudio", reason: e?.message || String(e) };
}
}
// src/helpers/toolResultImageHarvest.ts
import fs9 from "fs";
import path11 from "path";
import crypto from "crypto";
// src/media-promotion-core/image.ts
import fs8 from "fs";
import path10 from "path";
function isAllowedOriginalExt(p) {
return /(\.(png|jpe?g|webp|mov))$/i.test(p);
}
function guessExtForOriginal(maybePath) {
const e = path10.extname(maybePath).toLowerCase();
if (e === ".png" || e === ".jpg" || e === ".jpeg" || e === ".webp") return e;
return ".png";
}
function previewFilenameFrom(originalFilename) {
const hasPrefix = originalFilename.toLowerCase().startsWith("preview-");
const base = hasPrefix ? originalFilename : `preview-${originalFilename}`;
return base.replace(/\.(png|jpg|jpeg|webp|gif)$/i, ".jpg");
}
async function encodeJpegPreviewFromBuffer(srcBuf, opts) {
const q = Math.max(1, Math.min(100, opts.quality));
if (opts.mode === "height") {
const targetH = Math.max(1, Math.round(opts.maxDim));
const { data: data2, width: width2, height: height2 } = await resizeAndEncodeByHeight(
srcBuf,
"jpeg",
q,
targetH
);
return { data: data2, width: width2, height: height2 };
}
if (opts.mode === "sum" && opts.maxSum) {
const { data: data2, width: width2, height: height2 } = await resizeAndEncodeBySum(
srcBuf,
"jpeg",
q,
opts.maxSum
);
return { data: data2, width: width2, height: height2 };
}
const maxW = Math.max(1, Math.round(opts.maxDim));
const { data, width, height } = await resizeAndEncode(
srcBuf,
"jpeg",
q,
maxW,
Number.MAX_SAFE_INTEGER
);
return { data, width, height };
}
async function encodeJpegPreview(srcAbs, dstAbs, opts) {
const srcBuf = await fs8.promises.readFile(srcAbs);
const { data } = await encodeJpegPreviewFromBuffer(srcBuf, opts);
await fs8.promises.writeFile(dstAbs, data);
}
function isPreviewOptions(x) {
return typeof x === "object" && x !== null && "maxDim" in x && typeof x.maxDim === "number";
}
function normalizeToPreviewOptions(input) {
if (isPreviewOptions(input)) return input;
return {
maxDim: input.maxWidth ?? 640,
quality: input.quality ?? 80,
mode: input.maxSum ? "sum" : "width",
maxSum: input.maxSum
};
}
async function generatePreview(srcAbs, chatWd, optsInput, options) {
const debug = options?.debug ?? false;
const opts = normalizeToPreviewOptions(optsInput);
if (!fs8.existsSync(srcAbs)) {
if (debug) console.warn(`[Preview] Source not found: ${srcAbs}`);
return null;
}
const originalFilename = path10.basename(srcAbs);
const previewFilename = options?.customFilename ?? previewFilenameFrom(originalFilename);
const previewAbs = path10.join(chatWd, previewFilename);
if (!options?.force && fs8.existsSync(previewAbs)) {
if (debug) console.info(`[Preview] Exists, skipping: ${previewFilename}`);
return previewFilename;
}
try {
await encodeJpegPreview(srcAbs, previewAbs, opts);
if (debug) console.info(`[Preview] Generated: ${previewFilename}`);
return previewFilename;
} catch (e) {
if (debug)
console.warn(
`[Preview] Failed for ${originalFilename}:`,
e.message
);
throw e;
}
}
async function generatePreviewFromBuffer(srcBuf, chatWd, originalFilename, optsInput, options) {
const debug = options?.debug ?? false;
const opts = normalizeToPreviewOptions(optsInput);
const previewFilename = options?.customFilename ?? previewFilenameFrom(originalFilename);
const previewAbs = path10.join(chatWd, previewFilename);
if (!options?.force && fs8.existsSync(previewAbs)) {
if (debug) console.info(`[Preview] Exists, skipping: ${previewFilename}`);
try {
const existingData = await fs8.promises.readFile(previewAbs);
return {
previewFilename,
previewAbs,
data: existingData,
width: 0,
// Unknown for existing file
height: 0
};
} catch {
}
}
try {
const { data, width, height } = await encodeJpegPreviewFromBuffer(
srcBuf,
opts
);
await fs8.promises.writeFile(previewAbs, data);
if (debug)
console.info(`[Preview] Generated from buffer: ${previewFilename}`);
return {
previewFilename,
previewAbs,
data,
width,
height
};
} catch (e) {
if (debug)
console.warn(
`[Preview] Failed for ${originalFilename}:`,
e.message
);
throw e;
}
}
// src/helpers/toolResultImageHarvest.ts
function canonicalizeRef(ref) {
let s = String(ref || "").trim();
s = s.replace(/[)\],.;]+$/g, "");
s = s.replace(/^"(.+)"$/g, "$1");
s = s.replace(/^'(.+)'$/g, "$1");
return s.trim();
}
function isAllowedUrl(url) {
if (/^https:\/\//i.test(url)) return true;
if (/^http:\/\//i.test(url)) {
try {
const host = new URL(url).hostname;
return host === "localhost" || host === "127.0.0.1" || host === "::1";
} catch {
return false;
}
}
if (isProjectUri(url)) return true;
return false;
}
function detectImageMagic(buf) {
if (!buf || buf.length < 12) return null;
if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71 && buf[4] === 13 && buf[5] === 10 && buf[6] === 26 && buf[7] === 10)
return "png";
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "jpeg";
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70 && buf[3] === 56)
return "gif";
if (buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80)
return "webp";
return null;
}
async function pathExists(p) {
try {
await fs9.promises.access(p, fs9.constants.F_OK);
return true;
} catch {
return false;
}
}
async function fetchWithLimits(url, timeoutMs, maxBytes) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, {
method: "GET",
redirect: "follow",
signal: controller.signal
});
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
const cl = res.headers.get("content-length");
if (cl) {
const n = Number(cl);
if (Number.isFinite(n) && n > maxBytes) {
throw new Error(`content-length ${n} exceeds maxBytes ${maxBytes}`);
}
}
const body = res.body;
if (!body) {
const ab = await res.arrayBuffer();
const buf = Buffer.from(ab);
if (buf.length > maxBytes) {
throw new Error(
`response size ${buf.length} exceeds maxBytes ${maxBytes}`
);
}
return buf;
}
const reader = body.getReader();
const chunks = [];
let total = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
total += value.byteLength;
if (total > maxBytes) {
try {
controller.abort();
} catch {
}
throw new Error(`response size exceeds maxBytes ${maxBytes}`);
}
chunks.push(value);
}
}
return Buffer.concat(chunks.map((c) => Buffer.from(c)));
} finally {
clearTimeout(timeout);
}
}
function extractUrlsFromText(text) {
const out = [];
const re = /https:\/\/[\w\-./:%?#@!$&'()*+,;=]+/gi;
let m;
while ((m = re.exec(text)) !== null) {
out.push(m[0]);
}
return out;
}
function deepParseMaybeJson(input, maxDepth = 4) {
let cur = input;
for (let depth = 0; depth < maxDepth; depth++) {
if (typeof cur !== "string") return cur;
const t = cur.trim();
if (!(t.startsWith("{") || t.startsWith("[") || t.startsWith('"'))) return cur;
try {
cur = JSON.parse(t);
} catch {
return cur;
}
}
return cur;
}
function parseToolResultPayload(raw, validator) {
const segments = toolResultToTextSegments(raw);
for (const seg of segments) {
const parsed = deepParseMaybeJson(seg, 4);
if (validator(parsed)) {
return parsed;
}
}
return null;
}
function extractImageUrlsStructured(value) {
const urls = [];
const v = value;
if (v && typeof v === "object") {
if (Array.isArray(v.items)) {
for (const it of v.items) {
const u = it?.properties?.url;
if (typeof u === "string" && u.trim()) urls.push(u);
}
}
}
return urls;
}
function extractCandidatesStructured(value) {
const out = [];
const v = value;
if (!v || typeof v !== "object") return out;
if (!Array.isArray(v.items)) return out;
for (const it of v.items) {
const url = typeof it?.properties?.url === "string" ? String(it.properties.url) : "";
if (!url.trim()) continue;
const cand = { url: url.trim() };
if (typeof it?.title === "string" && it.title.trim()) cand.title = it.title;
if (typeof it?.confidence === "string" && it.confidence.trim()) {
cand.confidence = it.confidence;
}
const w = it?.properties?.width;
const h = it?.properties?.height;
if (typeof w === "number" && Number.isFinite(w)) cand.width = w;
if (typeof h === "number" && Number.isFinite(h)) cand.height = h;
if (typeof it?.url === "string" && it.url.trim()) cand.pageUrl = it.url;
if (typeof it?.page_fetched === "string" && it.page_fetched.trim()) {
cand.pageFetched = it.page_fetched;
}
const thumb = it?.thumbnail?.url;
if (typeof thumb === "string" && thumb.trim()) cand.thumbnailUrl = thumb;
out.push(cand);
}
return out;
}
function dedupePreserveOrder(items) {
const seen = /* @__PURE__ */ new Set();
const out = [];
for (const it of items) {
const c = canonicalizeRef(it);
if (!c) continue;
if (seen.has(c)) continue;
seen.add(c);
out.push(c);
}
return out;
}
function dedupeCandidatesPreserveOrder(items) {
const seen = /* @__PURE__ */ new Set();
const out = [];
for (const it of items) {
const u = canonicalizeRef(it?.url);
if (!u) continue;
if (seen.has(u)) continue;
seen.add(u);
out.push({ ...it, url: u });
}
return out;
}
function toolResultToTextSegments(toolResult) {
const rawText = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult);
try {
const parsed = JSON.parse(rawText);
if (Array.isArray(parsed)) {
const parts = [];
for (const p of parsed) {
if (p && typeof p === "object" && typeof p.text === "string") {
parts.push(String(p.text));
}
}
if (parts.length) return parts;
}
} catch {
}
return [rawText];
}
function extractImageRefsFromToolResult(toolResult) {
const segments = toolResultToTextSegments(toolResult);
const urls = [];
for (const seg of segments) {
const deep = deepParseMaybeJson(seg, 4);
urls.push(...extractImageUrlsStructured(deep));
if (typeof deep === "string") {
urls.push(...extractUrlsFromText(deep));
} else {
const stack = [
{ v: deep, depth: 0 }
];
const maxDepth = 4;
let nodes = 0;
const maxNodes = 2e3;
while (stack.length) {
const { v, depth } = stack.pop();
nodes++;
if (nodes > maxNodes) break;
if (typeof v === "string") {
urls.push(...extractUrlsFromText(v));
} else if (v && typeof v === "object" && depth < maxDepth) {
if (Array.isArray(v)) {
for (const it of v) stack.push({ v: it, depth: depth + 1 });
} else {
for (const k of Object.keys(v)) {
stack.push({ v: v[k], depth: depth + 1 });
}
}
}
}
}
}
return dedupePreserveOrder(urls);
}
function extractToolResultImageCandidates(toolResult) {
const segments = toolResultToTextSegments(toolResult);
const candidates = [];
const urlFallbacks = [];
for (const seg of segments) {
const deep = deepParseMaybeJson(seg, 4);
candidates.push(...extractCandidatesStructured(deep));
if (typeof deep === "string") {
urlFallbacks.push(...extractUrlsFromText(deep));
} else {
const stack = [
{ v: deep, depth: 0 }
];
const maxDepth = 4;
let nodes = 0;
const maxNodes = 2e3;
while (stack.length) {
const { v, depth } = stack.pop();
nodes++;
if (nodes > maxNodes) break;
if (typeof v === "string") {
urlFallbacks.push(...extractUrlsFromText(v));
} else if (v && typeof v === "object" && depth < maxDepth) {
if (Array.isArray(v)) {
for (const it of v) stack.push({ v: it, depth: depth + 1 });
} else {
for (const k of Object.keys(v)) {
stack.push({ v: v[k], depth: depth + 1 });
}
}
}
}
}
}
const structured = dedupeCandidatesPreserveOrder(candidates);
const structuredUrls = new Set(structured.map((c) => c.url));
const fallback = dedupePreserveOrder(urlFallbacks).filter((u) => !structuredUrls.has(canonicalizeRef(u))).map((u) => ({ url: canonicalizeRef(u) }));
return [...structured, ...fallback].filter((c) => !!c.url);
}
async function materializeToolResultImageToFiles(input) {
const timeoutMs = typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? Math.floor(input.timeoutMs) : 1e4;
const maxBytes = typeof input.maxBytes === "number" && input.maxBytes > 0 ? Math.floor(input.maxBytes) : 15 * 1024 * 1024;
const ref = canonicalizeRef(input.url);
if (!isAllowedUrl(ref)) {
throw new Error("scheme not allowed (https or project:// only)");
}
let buf;
if (isProjectUri(ref)) {
const resolved = await resolveProjectUri(ref);
if (!resolved) {
throw new Error("failed to extract thumbnail from project file");
}
buf = resolved;
} else {
buf = await fetchWithLimits(ref, timeoutMs, maxBytes);
}
const magic = detectImageMagic(buf);
if (!magic) {
throw new Error("not a supported image (magic bytes)");
}
const bytesHash = crypto.createHash("sha256").update(buf).digest("hex");
const png = await toPng(buf);
await fs9.promises.writeFile(input.originalAbs, png);
const chatWd = path11.dirname(input.originalAbs);
await generatePreview(input.originalAbs, chatWd, input.preview, {
customFilename: path11.basename(input.previewAbs),
force: true
// Always generate (we just downloaded the original)
});
return { bytesHash };
}
function shortHexSha256(input, chars = 12) {
const h = crypto.createHash("sha256").update(input).digest("hex");
return h.slice(0, Math.max(4, Math.min(64, chars)));
}
async function harvestToolResultImages(input) {
const debug = !!input.debug;
const maxImages = Math.max(0, Math.floor(input.maxImages));
const timeoutMs = typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? Math.floor(input.timeoutMs) : 1e4;
const maxBytes = typeof input.maxBytes === "number" && input.maxBytes > 0 ? Math.floor(input.maxBytes) : 15 * 1024 * 1024;
const skipped = [];
const harvested = [];
if (!maxImages) return { harvested, skipped };
if (!input.chatWd || !String(input.chatWd).trim()) {
return {
harvested,
skipped: [{ ref: "(chatWd)", reason: "missing chatWd" }]
};
}
const refs = extractImageRefsFromToolResult(input.toolResult);
if (debug) {
console.info(
`[ToolResultHarvest] tool=${input.toolName} extractedRefs=${refs.length} cap=${maxImages}`
);
}
const capped = refs.slice(0, maxImages);
await fs9.promises.mkdir(input.chatWd, { recursive: true });
for (const rawRef of capped) {
const ref = canonicalizeRef(rawRef);
if (!ref) continue;
if (!isAllowedUrl(ref)) {
skipped.push({ ref, reason: "scheme not allowed (https only)" });
continue;
}
const urlHash = shortHexSha256(ref, 12);
const ts = `h${urlHash}Z`;
const vNum = 1;
const originalBaseName = `generated-image-${ts}-v${vNum}.png`;
const previewBaseName = `preview-generated-image-${ts}-v${vNum}.jpg`;
const originalAbs = path11.join(input.chatWd, originalBaseName);
const previewAbs = path11.join(input.chatWd, previewBaseName);
const originalOk = await pathExists(originalAbs);
const previewOk = await pathExists(previewAbs);
if (originalOk && previewOk) {
harvested.push({
originalBaseName,
previewBaseName,
sourceRef: ref
});
continue;
}
try {
const { bytesHash } = await materializeToolResultImageToFiles({
url: ref,
originalAbs,
previewAbs,
preview: input.preview,
timeoutMs,
maxBytes
});
harvested.push({
originalBaseName,
previewBaseName,
sourceRef: ref,
bytesHash
});
if (debug) {
console.info(
`[ToolResultHarvest] saved ${originalBaseName} + ${previewBaseName} (tool=${input.toolName})`
);
}
} catch (e) {
skipped.push({
ref,
reason: e?.message || String(e)
});
continue;
}
}
return { harvested, skipped };
}
// src/helpers/visionCapabilityPrimer.ts
import { exec } from "child_process";
import { promisify } from "util";
import path12 from "path";
import os3 from "os";
import fs10 from "fs";
import axios2 from "axios";
var execAsync = promisify(exec);
function appendPluginLog(line) {
try {
ensureLogsDir();
const logFile = path12.join(getLogsDir(), "generate-image-plugin.log");
fs10.appendFileSync(logFile, `${(/* @__PURE__ */ new Date()).toISOString()} - ${line}
`);
} catch (e) {
console.error(
"[AgentModel] Failed to append to generate-image-plugin.log:",
e?.message || String(e)
);
}
}
function normalizeLmApiRoot2(baseUrl) {
const u = String(baseUrl || "").trim();
return u.replace(/\/(api\/v1|v1)\/?$/i, "").replace(/\/+$/, "");
}
function buildLmApiAuthHeaders2(apiKey) {
if (typeof apiKey === "string" && apiKey.trim()) {
return { Authorization: `Bearer ${apiKey.trim()}` };
}
return {};
}
async function findLmsCli() {
const candidates = [
path12.join(os3.homedir(), ".lmstudio", "bin", "lms"),
"/usr/local/bin/lms",
"/opt/homebrew/bin/lms"
];
for (const candidate of candidates) {
try {
await execAsync(`"${candidate}" -h`);
return candidate;
} catch {
}
}
try {
const { stdout } = await execAsync("which lms");
const lmsPath = stdout.trim();
if (lmsPath) {
await execAsync(`"${lmsPath}" -h`);
return lmsPath;
}
} catch {
}
return null;
}
async function isModelInstalled(lmsCli, modelKey, timeoutMs = 5e3) {
const normalizedKey = modelKey.toLowerCase().trim();
try {
const { stdout } = await execAsync(`"${lmsCli}" ls --json`, {
timeout: timeoutMs,
maxBuffer: 10 * 1024 * 1024
});
let models;
try {
models = JSON.parse(stdout);
} catch (parseErr) {
try {
const { stdout: textOut, stderr: textErr } = await execAsync(
`"${lmsCli}" ls`,
{
timeout: Math.max(1e3, timeoutMs),
maxBuffer: 10 * 1024 * 1024
}
);
const haystack = `${textOut}
${textErr}`.toLowerCase();
if (haystack.includes(normalizedKey)) {
return { installed: true, modelInfo: { modelKey } };
}
return {
installed: false,
error: `Failed to parse lms ls --json output (fallback used): ${parseErr?.message || parseErr}`
};
} catch (fallbackErr) {
return {
installed: false,
error: `Failed to parse lms ls --json output and fallback ls failed: ${parseErr?.message || parseErr} / ${fallbackErr?.message || String(fallbackErr)}`
};
}
}
if (!Array.isArray(models)) {
return {
installed: false,
error: "lms ls --json returned non-array"
};
}
const found = models.find((m) => {
const key = String(m?.modelKey || "").toLowerCase().trim();
return key === normalizedKey;
});
if (found) {
return {
installed: true,
modelInfo: {
modelKey: found.modelKey,
displayName: found.displayName,
sizeBytes: found.sizeBytes,
vision: found.vision === true
}
};
}
return { installed: false };
} catch (e) {
return {
installed: false,
error: e?.message || String(e)
};
}
}
async function isModelLoaded(lmsCli, identifier, timeoutMs = 5e3) {
const normalizedId = identifier.toLowerCase().trim();
try {
const { stdout } = await execAsync(`"${lmsCli}" ps --json`, {
timeout: timeoutMs
});
let instances;
try {
instances = JSON.parse(stdout);
} catch {
return false;
}
if (!Array.isArray(instances)) {
return false;
}
return instances.some((inst) => {
const instId = String(inst?.identifier || "").toLowerCase().trim();
return instId === normalizedId;
});
} catch {
return false;
}
}
async function checkVisionPrimerStatus(config) {
const { modelKey, identifier = "vision-capability-priming" } = config;
console.debug("[VisionPrimer] Quick status check...");
const lmsCli = await findLmsCli();
if (!lmsCli) {
console.warn("[VisionPrimer] lms CLI not found (infrastructure error, silent)");
return {
lmsCli: null,
installed: false,
alreadyLoaded: false,
needsLoad: false,
infrastructureError: true,
error: "lms CLI not found. Is LM Studio installed?"
};
}
console.debug("[VisionPrimer] Found lms CLI:", lmsCli);
const installResult = await isModelInstalled(lmsCli, modelKey, 5e3);
if (installResult.error) {
console.warn("[VisionPrimer] Installation check failed:", installResult.error);
return {
lmsCli,
installed: false,
alreadyLoaded: false,
needsLoad: false,
infrastructureError: true,
error: `Installation check failed: ${installResult.error}`
};
}
if (!installResult.installed) {
const userMsg = `**Vision Attachment Support:**
The vision-capability-priming model \`${modelKey}\` is not installed.
This model enables image attachments in the chat UI. To install it:
1. Open LM Studio
2. Search for \`${modelKey}\`
3. Download the model
Without this model, you can still use text prompts for image generation.`;
console.warn(`[VisionPrimer] Model not installed: ${modelKey}`);
return {
lmsCli,
installed: false,
alreadyLoaded: false,
needsLoad: false,
notInstalled: true,
userFacingError: userMsg,
error: `Model '${modelKey}' is not installed locally.`
};
}
console.debug(`[VisionPrimer] Model installed: ${modelKey}`);
const loaded = await isModelLoaded(lmsCli, identifier, 5e3);
if (loaded) {
console.debug(`[VisionPrimer] Model already loaded with identifier: ${identifier}`);
return {
lmsCli,
installed: true,
alreadyLoaded: true,
needsLoad: false
};
}
console.debug(`[VisionPrimer] Model installed but not loaded, needs load`);
return {
lmsCli,
installed: true,
alreadyLoaded: false,
needsLoad: true
};
}
async function loadVisionPrimerModel(lmsCli, config) {
const startTs = Date.now();
const {
modelKey,
contextLength = 4096,
gpuMode = "off",
ttlSeconds = 3600,
identifier = "vision-capability-priming"
} = config;
const gpuArg = gpuMode === "off" ? "--gpu off" : gpuMode === "max" ? "--gpu max" : `--gpu ${gpuMode}`;
const cmd = [
`"${lmsCli}"`,
"load",
modelKey,
`--context-length ${contextLength}`,
gpuArg,
`--ttl ${ttlSeconds}`,
`--identifier "${identifier}"`
].join(" ");
console.debug("[VisionPrimer] Running:", cmd);
try {
const { stdout, stderr } = await execAsync(cmd, {
timeout: 12e4
});
const output = stdout + stderr;
const loadTimeSec = Math.round((Date.now() - startTs) / 1e3 * 100) / 100;
const sizeMatch = output.match(/\(([0-9.]+ [A-Z]+)\)/i);
const size = sizeMatch ? sizeMatch[1] : void 0;
console.debug(`[VisionPrimer] \u2713 Model loaded in ${loadTimeSec}s`);
if (size) console.debug(`[VisionPrimer] Size: ${size}`);
return {
ok: true,
alreadyLoaded: false,
identifier,
size,
loadTimeSec
};
} catch (e) {
const error = e.stderr || e.message || String(e);
console.debug("[VisionPrimer] Load command failed, checking if model is now loaded anyway...");
const nowLoaded = await isModelLoaded(lmsCli, identifier, 5e3);
if (nowLoaded) {
console.debug("[VisionPrimer] Model is now loaded (concurrent load succeeded), treating as success");
return {
ok: true,
alreadyLoaded: true,
identifier
};
}
const userMsg = `**Vision Attachment Support:**
The vision-capability-priming model \`${modelKey}\` could not be loaded.
Error: ${error}
Please check LM Studio logs for details. Without this model, you can still use text prompts for image generation.`;
console.warn("[VisionPrimer] Failed to load model:", error);
return {
ok: false,
loadFailed: true,
userFacingError: userMsg,
error: `Failed to load model: ${error}`
};
}
}
async function primeVisionCapability(config) {
const TOTAL_TIMEOUT_MS = 15e3;
const startTs = Date.now();
const remainingMs = () => Math.max(0, TOTAL_TIMEOUT_MS - (Date.now() - startTs));
const {
modelKey,
contextLength = 4096,
gpuMode = "off",
ttlSeconds = 3600,
identifier = "vision-capability-priming"
} = config;
console.debug("[VisionPrimer] Starting vision capability priming...");
console.debug("[VisionPrimer] Config:", {
modelKey,
contextLength,
gpuMode,
ttlSeconds,
identifier
});
const lmsCli = await findLmsCli();
if (!lmsCli) {
console.warn("[VisionPrimer] lms CLI not found (infrastructure error, silent)");
return {
ok: false,
infrastructureError: true,
error: "lms CLI not found. Is LM Studio installed?"
};
}
console.debug("[VisionPrimer] Found lms CLI:", lmsCli);
if (remainingMs() < 1e3) {
console.warn("[VisionPrimer] Timeout before installation check");
return {
ok: false,
infrastructureError: true,
error: "Timeout before installation check"
};
}
const installResult = await isModelInstalled(lmsCli, modelKey, Math.min(5e3, remainingMs()));
if (installResult.error) {
console.warn("[VisionPrimer] Installation check failed:", installResult.error);
return {
ok: false,
infrastructureError: true,
error: `Installation check failed: ${installResult.error}`
};
}
if (!installResult.installed) {
const userMsg = `**Vision Attachment Support:**
The vision-capability-priming model \`${modelKey}\` is not installed.
This model enables image attachments in the chat UI. To install it:
1. Open LM Studio
2. Search for \`${modelKey}\`
3. Download the model
Without this model, you can still use text prompts for image generation.`;
console.warn(`[VisionPrimer] Model not installed: ${modelKey}`);
return {
ok: false,
notInstalled: true,
userFacingError: userMsg,
error: `Model '${modelKey}' is not installed locally.`
};
}
console.debug(`[VisionPrimer] Model installed: ${modelKey}`);
if (remainingMs() < 1e3) {
console.warn("[VisionPrimer] Timeout before loaded check");
return {
ok: false,
infrastructureError: true,
error: "Timeout before loaded check"
};
}
const loaded = await isModelLoaded(lmsCli, identifier, Math.min(5e3, remainingMs()));
if (loaded) {
console.debug(
`[VisionPrimer] Model already loaded with identifier: ${identifier}`
);
return {
ok: true,
alreadyLoaded: true,
identifier
};
}
if (remainingMs() < 2e3) {
console.warn("[VisionPrimer] Insufficient time remaining to load model");
return {
ok: false,
infrastructureError: true,
error: "Insufficient time remaining to load model"
};
}
const gpuArg = gpuMode === "off" ? "--gpu off" : gpuMode === "max" ? "--gpu max" : `--gpu ${gpuMode}`;
const cmd = [
`"${lmsCli}"`,
"load",
modelKey,
`--context-length ${contextLength}`,
gpuArg,
`--ttl ${ttlSeconds}`,
`--identifier "${identifier}"`
].join(" ");
console.debug("[VisionPrimer] Running:", cmd);
try {
const { stdout, stderr } = await execAsync(cmd, {
timeout: remainingMs()
});
const output = stdout + stderr;
const loadTimeSec = Math.round((Date.now() - startTs) / 1e3 * 100) / 100;
const sizeMatch = output.match(/\(([0-9.]+ [A-Z]+)\)/i);
const size = sizeMatch ? sizeMatch[1] : void 0;
console.debug(`[VisionPrimer] \u2713 Model loaded in ${loadTimeSec}s`);
if (size) console.debug(`[VisionPrimer] Size: ${size}`);
return {
ok: true,
alreadyLoaded: false,
identifier,
size,
loadTimeSec
};
} catch (e) {
const error = e.stderr || e.message || String(e);
const userMsg = `**Vision Attachment Support:**
The vision-capability-priming model \`${modelKey}\` could not be loaded.
Error: ${error}
Please check LM Studio logs for details. Without this model, you can still use text prompts for image generation.`;
console.warn("[VisionPrimer] Failed to load model:", error);
return {
ok: false,
loadFailed: true,
userFacingError: userMsg,
error: `Failed to load model: ${error}`
};
}
}
async function updateLastUsedModelForAgentModel(config) {
const { modelKey, instanceId, contextLength, gpuOffloadRatio, pluginId } = config;
const effectiveInstanceId = instanceId ?? modelKey;
console.debug("[VisionPrimer] Updating lastUsedModel to agent model:", {
modelKey,
instanceId: effectiveInstanceId,
contextLength
});
try {
const conversationsDir = path12.join(findLMStudioHome(), "conversations");
const dirExists = await fs10.promises.stat(conversationsDir).then(() => true).catch(() => false);
if (!dirExists) {
return { ok: false, error: "Conversations directory not found" };
}
const files = await fs10.promises.readdir(conversationsDir);
const conversationFiles = files.filter(
(f) => f.endsWith(".conversation.json")
);
if (conversationFiles.length === 0) {
return { ok: false, error: "No conversation files found" };
}
let newestFile = null;
let newestMtime = 0;
for (const file of conversationFiles) {
const filePath2 = path12.join(conversationsDir, file);
try {
const stats = await fs10.promises.stat(filePath2);
if (stats.mtimeMs > newestMtime) {
newestMtime = stats.mtimeMs;
newestFile = file;
}
} catch {
}
}
if (!newestFile) {
return { ok: false, error: "Could not determine newest conversation" };
}
const filePath = path12.join(conversationsDir, newestFile);
const chatId = newestFile.replace(".conversation.json", "");
const raw = await fs10.promises.readFile(filePath, "utf8");
const conversation = JSON.parse(raw);
if (pluginId) {
const isActive = Array.isArray(conversation.plugins) && conversation.plugins.includes(pluginId);
if (!isActive) {
console.debug(
`[VisionPrimer] We're not active in newest chat (${newestFile}), skipping update`
);
return { ok: false, error: "Plugin not active in this chat" };
}
}
const existingModel = conversation.lastUsedModel;
if (existingModel?.indexedModelIdentifier === modelKey && existingModel?.identifier === effectiveInstanceId) {
const existingCtx = existingModel?.instanceLoadTimeConfig?.fields?.find(
(f) => f.key === "llm.load.contextLength"
)?.value;
if (contextLength === void 0 || existingCtx === contextLength) {
console.debug(
`[VisionPrimer] lastUsedModel already correct for ${modelKey}, skipping`
);
return { ok: true, alreadyCorrect: true, chatId };
}
}
const fields = [];
if (typeof contextLength === "number" && contextLength > 0) {
fields.push({ key: "llm.load.contextLength", value: contextLength });
}
if (typeof gpuOffloadRatio === "number") {
fields.push({
key: "llm.load.llama.acceleration.offloadRatio",
value: gpuOffloadRatio
});
}
conversation.lastUsedModel = {
indexedModelIdentifier: modelKey,
identifier: effectiveInstanceId,
instanceLoadTimeConfig: { fields },
instanceOperationTimeConfig: { fields: [] }
};
await fs10.promises.writeFile(
filePath,
JSON.stringify(conversation, null, 2),
"utf8"
);
const now = /* @__PURE__ */ new Date();
await fs10.promises.utimes(filePath, now, now);
console.debug(
`[VisionPrimer] \u2713 Updated lastUsedModel to agent model: ${modelKey} (ctx=${contextLength ?? "default"}) in ${newestFile}`
);
setTimeout(async () => {
try {
const now2 = /* @__PURE__ */ new Date();
await fs10.promises.utimes(filePath, now2, now2);
console.debug(`[VisionPrimer] \u2713 Second touch for agent model update`);
} catch {
}
}, 1500);
return { ok: true, chatId };
} catch (e) {
console.warn(
"[VisionPrimer] Failed to update lastUsedModel:",
e.message || e
);
return { ok: false, error: e.message || String(e) };
}
}
async function unloadVisionPrimer(identifier = "vision-capability-priming", opts) {
if (opts?.baseUrl) {
const apiRoot = normalizeLmApiRoot2(opts.baseUrl);
const unloadUrl = `${apiRoot}/api/v1/models/unload`;
try {
await axios2.post(
unloadUrl,
{ instance_id: identifier },
{
timeout: 3e4,
headers: {
"Content-Type": "application/json",
...buildLmApiAuthHeaders2(opts.apiKey)
}
}
);
console.debug(`[VisionPrimer] \u2713 Unloaded model via API: ${identifier}`);
return true;
} catch (e) {
const resp = e?.response;
const respText = resp?.data ? typeof resp.data === "string" ? resp.data : JSON.stringify(resp.data) : void 0;
console.warn(
"[VisionPrimer] Failed to unload via API:",
respText || e?.message || e
);
return false;
}
}
const lmsCli = await findLmsCli();
if (!lmsCli) return false;
try {
await execAsync(`"${lmsCli}" unload "${identifier}"`);
console.debug(`[VisionPrimer] \u2713 Unloaded model: ${identifier}`);
return true;
} catch (e) {
console.warn("[VisionPrimer] Failed to unload:", e.message || e);
return false;
}
}
async function ensureAgentModelLoaded(config) {
const startTs = Date.now();
const { modelKey } = config;
appendPluginLog(
`[AgentModel] ensureAgentModelLoaded start modelKey=${String(
modelKey
)} baseUrl=${String(config.baseUrl || "").trim() || "(missing)"}`
);
console.log("[AgentModel] Ensuring agent model is loaded...");
console.log("[AgentModel] Model:", modelKey);
const formatVisionModelsTable = (models) => {
const rows = models.map((m) => {
const id = String(m?.key || m?.id || "?");
const name = String(m?.display_name || id);
const params = String(m?.params_string || "?");
const size = m?.size_bytes ? (Number(m.size_bytes) / 1024 / 1024 / 1024).toFixed(2) + " GB" : "?";
const caps2 = m?.capabilities;
const hasVision = caps2?.vision === true;
const hasToolUse = caps2?.trained_for_tool_use === true;
const visionMark = hasVision ? "\u2713" : "";
const toolsMark = hasToolUse ? "\u2713" : "";
const PRIMER_INSTANCE_ID = "vision-capability-priming";
const loadedAsId = Array.isArray(m?.loaded_instances) ? m.loaded_instances.some((inst) => {
const instId = typeof inst === "string" ? inst : typeof inst?.id === "string" ? inst.id : void 0;
return typeof instId === "string" && instId === id;
}) : false;
const loadedAsPrimer = Array.isArray(m?.loaded_instances) ? m.loaded_instances.some((inst) => {
const instId = typeof inst === "string" ? inst : typeof inst?.id === "string" ? inst.id : void 0;
return typeof instId === "string" && instId === PRIMER_INSTANCE_ID;
}) : false;
const status = loadedAsId ? "\u2713" : loadedAsPrimer ? "primer" : "";
return `| ${id} | ${name} | ${params} | ${size} | ${visionMark} | ${toolsMark} | ${status} |`;
});
return `
**Vision Models:**
| ID | Name | Params | Size | Vision | Tools | Loaded |
|---|---|---|---|---|---|---|
${rows.join(
"\n"
)}
`;
};
if (!config.baseUrl) {
return {
ok: false,
error: "Configuration error: Missing baseUrl for Model API check."
};
}
const requestedKeyRaw = String(modelKey || "").trim();
const requestedKey = requestedKeyRaw.toLowerCase();
if (!requestedKeyRaw) {
return { ok: false, error: "Configuration error: Empty model key." };
}
if (requestedKey === "vision-capability-priming") {
return {
ok: false,
error: "Invalid agent model: 'vision-capability-priming' is an infrastructure primer model and must not be used for chat orchestration."
};
}
const apiRoot = normalizeLmApiRoot2(config.baseUrl);
const modelsUrl = `${apiRoot}/api/v1/models`;
const loadUrl = `${apiRoot}/api/v1/models/load`;
const headers = {};
if (typeof config.apiKey === "string" && config.apiKey.trim()) {
headers.Authorization = `Bearer ${config.apiKey.trim()}`;
}
let foundModel;
let visionModels = [];
try {
console.debug(`[AgentModel] Checking status via ${modelsUrl}`);
const response = await axios2.get(modelsUrl, {
timeout: 5e3,
headers
});
const data = response.data;
const models = Array.isArray(data) ? data : Array.isArray(data?.models) ? data.models : Array.isArray(data?.data) ? data.data : [];
visionModels = models.filter((m) => m?.capabilities?.vision === true);
foundModel = models.find((m) => {
const key = String(m?.key || "").trim();
return key && key.toLowerCase() === requestedKey;
});
if (foundModel) {
const li = Array.isArray(foundModel?.loaded_instances) ? foundModel.loaded_instances : [];
const ids = li.map((inst) => {
if (typeof inst === "string") return inst;
if (inst && typeof inst.id === "string") return inst.id;
return void 0;
}).filter((v) => typeof v === "string").slice(0, 5).join(", ");
appendPluginLog(
`[AgentModel] status key=${requestedKeyRaw} found=true loaded_instances=${li.length}${ids ? ` ids=[${ids}]` : ""}`
);
} else {
appendPluginLog(`[AgentModel] status key=${requestedKeyRaw} found=false`);
}
} catch (e) {
const resp = e?.response;
const respText = resp?.data ? typeof resp.data === "string" ? resp.data : JSON.stringify(resp.data) : void 0;
const msg = respText || e?.message || String(e);
appendPluginLog(
`[AgentModel] status query failed url=${modelsUrl} error=${msg}`
);
return {
ok: false,
error: `Could not query model list via API (${modelsUrl}): ${msg}`
};
}
const modelTable = visionModels.length > 0 ? formatVisionModelsTable(visionModels) : "\n\n(No vision-capable models found with capabilities { vision: true }.)";
if (!foundModel) {
appendPluginLog(`[AgentModel] model not found: ${requestedKeyRaw}`);
return {
ok: false,
error: `Model '${requestedKeyRaw}' not found on server.${modelTable}`
};
}
const caps = foundModel?.capabilities;
const isSuitable = caps?.vision === true;
if (!isSuitable) {
appendPluginLog(
`[AgentModel] model not suitable (no vision): ${requestedKeyRaw}`
);
return {
ok: false,
error: `Model '${requestedKeyRaw}' is not suitable (requires vision).${modelTable}`
};
}
const loadedInstances = Array.isArray(foundModel?.loaded_instances) ? foundModel.loaded_instances : [];
const getInstanceId = (inst) => {
if (typeof inst === "string") return inst;
if (inst && typeof inst.id === "string") return inst.id;
return void 0;
};
const getInstanceContextLength = (inst) => {
if (!inst || typeof inst === "string") return void 0;
const cfg = inst?.config;
const cfgLen = typeof cfg?.context_length === "number" ? cfg.context_length : typeof cfg?.contextLength === "number" ? cfg.contextLength : void 0;
if (typeof cfgLen === "number" && Number.isFinite(cfgLen)) return cfgLen;
const v = typeof inst.context_length === "number" ? inst.context_length : typeof inst.contextLength === "number" ? inst.contextLength : void 0;
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
};
const loadedInstance = loadedInstances.find((inst) => {
const instId = getInstanceId(inst);
return typeof instId === "string" && instId.toLowerCase() === requestedKey;
});
const fallbackLoadedInstance = loadedInstance ?? (loadedInstances.length > 0 ? loadedInstances[0] : void 0);
const alreadyLoaded = loadedInstances.length > 0;
if (alreadyLoaded) {
if (!loadedInstance && loadedInstances.length > 0) {
const ids = loadedInstances.map((inst) => getInstanceId(inst)).filter((v) => typeof v === "string").slice(0, 5).join(", ");
appendPluginLog(
`[AgentModel] alreadyLoaded=true via loaded_instances.length (${loadedInstances.length}) but no exact id match. requestedKey=${requestedKeyRaw} ids=[${ids}]`
);
}
console.log(
`[AgentModel] Model already loaded (via API): ${requestedKeyRaw}`
);
appendPluginLog(`[AgentModel] already loaded: ${requestedKeyRaw}`);
return {
ok: true,
alreadyLoaded: true,
identifier: requestedKeyRaw,
loadedInstanceId: getInstanceId(fallbackLoadedInstance) ?? requestedKeyRaw,
loadedInstanceContextLength: getInstanceContextLength(
fallbackLoadedInstance
),
maxContextLength: typeof foundModel?.max_context_length === "number" ? foundModel.max_context_length : typeof foundModel?.maxContextLength === "number" ? foundModel.maxContextLength : void 0,
size: foundModel?.size_bytes ? (Number(foundModel.size_bytes) / 1024 / 1024 / 1024).toFixed(2) + " GB" : void 0
};
}
console.log("[AgentModel] Loading model via API:", requestedKeyRaw);
appendPluginLog(`[AgentModel] loading via API: ${requestedKeyRaw}`);
try {
const loadResp = await axios2.post(
loadUrl,
{
model: requestedKeyRaw,
echo_load_config: true
},
{
timeout: 3e5,
headers: {
"Content-Type": "application/json",
...headers
}
}
);
const loadData = loadResp.data;
const loadTimeSecApi = typeof loadData?.load_time_seconds === "number" ? loadData.load_time_seconds : void 0;
const loadTimeSecLocal = Math.round((Date.now() - startTs) / 1e3 * 100) / 100;
const loadTimeSec = loadTimeSecApi ?? loadTimeSecLocal;
let loadedInstanceId;
let loadedInstanceContextLength;
let maxContextLength;
try {
const response2 = await axios2.get(modelsUrl, {
timeout: 5e3,
headers
});
const data2 = response2.data;
const models2 = Array.isArray(data2) ? data2 : Array.isArray(data2?.models) ? data2.models : Array.isArray(data2?.data) ? data2.data : [];
const foundModel2 = models2.find((m) => {
const key = String(m?.key || "").trim();
return key && key.toLowerCase() === requestedKey;
});
const loadedInstances2 = Array.isArray(foundModel2?.loaded_instances) ? foundModel2.loaded_instances : [];
const loadedInstance2 = loadedInstances2.find((inst) => {
const instId = getInstanceId(inst);
return typeof instId === "string" && instId.toLowerCase() === requestedKey;
});
loadedInstanceId = getInstanceId(loadedInstance2);
loadedInstanceContextLength = getInstanceContextLength(loadedInstance2);
maxContextLength = typeof foundModel2?.max_context_length === "number" ? foundModel2.max_context_length : typeof foundModel2?.maxContextLength === "number" ? foundModel2.maxContextLength : void 0;
} catch (e) {
console.debug(
"[AgentModel] Post-load model status re-check failed (skipping context warning)."
);
}
return {
ok: true,
alreadyLoaded: false,
identifier: requestedKeyRaw,
loadedInstanceId: loadedInstanceId ?? requestedKeyRaw,
loadedInstanceContextLength,
maxContextLength,
size: foundModel?.size_bytes ? (Number(foundModel.size_bytes) / 1024 / 1024 / 1024).toFixed(2) + " GB" : void 0,
loadTimeSec
};
} catch (e) {
const resp = e?.response;
const respText = resp?.data ? typeof resp.data === "string" ? resp.data : JSON.stringify(resp.data) : void 0;
const msg = respText || e?.message || String(e);
const UNSPECIFIC = "Error: Error loading model.";
const FRIENDLY = "Model load failed. Please open LM Studio Debug logs; the model configuration (e.g. context length / GPU offload) may not match your Model Loading Guardrails settings.";
const safeMsg = typeof msg === "string" && msg.includes(UNSPECIFIC) ? msg.split(UNSPECIFIC).join(FRIENDLY) : msg;
appendPluginLog(
`[AgentModel] load failed: ${requestedKeyRaw} error=${safeMsg}`
);
return {
ok: false,
error: `Failed to load model via API: ${safeMsg}`
};
}
}
// src/interfaces/compression-method.ts
var CompressionMethod = /* @__PURE__ */ ((CompressionMethod2) => {
CompressionMethod2[CompressionMethod2["Disabled"] = 0] = "Disabled";
CompressionMethod2[CompressionMethod2["H264"] = 1] = "H264";
CompressionMethod2[CompressionMethod2["H265"] = 2] = "H265";
CompressionMethod2[CompressionMethod2["Jpeg"] = 3] = "Jpeg";
return CompressionMethod2;
})(CompressionMethod || {});
// src/interfaces/control.ts
import * as flatbuffers from "flatbuffers";
// src/interfaces/control-input-type.ts
var ControlInputType = /* @__PURE__ */ ((ControlInputType2) => {
ControlInputType2[ControlInputType2["Unspecified"] = 0] = "Unspecified";
ControlInputType2[ControlInputType2["Custom"] = 1] = "Custom";
ControlInputType2[ControlInputType2["Depth"] = 2] = "Depth";
ControlInputType2[ControlInputType2["Canny"] = 3] = "Canny";
ControlInputType2[ControlInputType2["Scribble"] = 4] = "Scribble";
ControlInputType2[ControlInputType2["Pose"] = 5] = "Pose";
ControlInputType2[ControlInputType2["Normalbae"] = 6] = "Normalbae";
ControlInputType2[ControlInputType2["Color"] = 7] = "Color";
ControlInputType2[ControlInputType2["Lineart"] = 8] = "Lineart";
ControlInputType2[ControlInputType2["Softedge"] = 9] = "Softedge";
ControlInputType2[ControlInputType2["Seg"] = 10] = "Seg";
ControlInputType2[ControlInputType2["Inpaint"] = 11] = "Inpaint";
ControlInputType2[ControlInputType2["Ip2p"] = 12] = "Ip2p";
ControlInputType2[ControlInputType2["Shuffle"] = 13] = "Shuffle";
ControlInputType2[ControlInputType2["Mlsd"] = 14] = "Mlsd";
ControlInputType2[ControlInputType2["Tile"] = 15] = "Tile";
ControlInputType2[ControlInputType2["Blur"] = 16] = "Blur";
ControlInputType2[ControlInputType2["Lowquality"] = 17] = "Lowquality";
ControlInputType2[ControlInputType2["Gray"] = 18] = "Gray";
return ControlInputType2;
})(ControlInputType || {});
// src/interfaces/control-mode.ts
var ControlMode = /* @__PURE__ */ ((ControlMode2) => {
ControlMode2[ControlMode2["Balanced"] = 0] = "Balanced";
ControlMode2[ControlMode2["Prompt"] = 1] = "Prompt";
ControlMode2[ControlMode2["Control"] = 2] = "Control";
return ControlMode2;
})(ControlMode || {});
// src/interfaces/control.ts
var Control = class _Control {
bb = null;
bb_pos = 0;
__init(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsControl(bb, obj) {
return (obj || new _Control()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsControl(bb, obj) {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new _Control()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
file(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
weight() {
const offset = this.bb.__offset(this.bb_pos, 6);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
guidanceStart() {
const offset = this.bb.__offset(this.bb_pos, 8);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0;
}
guidanceEnd() {
const offset = this.bb.__offset(this.bb_pos, 10);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
noPrompt() {
const offset = this.bb.__offset(this.bb_pos, 12);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
globalAveragePooling() {
const offset = this.bb.__offset(this.bb_pos, 14);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true;
}
downSamplingRate() {
const offset = this.bb.__offset(this.bb_pos, 16);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
controlMode() {
const offset = this.bb.__offset(this.bb_pos, 18);
return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* Balanced */;
}
targetBlocks(index, optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 20);
return offset ? this.bb.__string(this.bb.__vector(this.bb_pos + offset) + index * 4, optionalEncoding) : null;
}
targetBlocksLength() {
const offset = this.bb.__offset(this.bb_pos, 20);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
}
inputOverride() {
const offset = this.bb.__offset(this.bb_pos, 22);
return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* Unspecified */;
}
static startControl(builder) {
builder.startObject(10);
}
static addFile(builder, fileOffset) {
builder.addFieldOffset(0, fileOffset, 0);
}
static addWeight(builder, weight) {
builder.addFieldFloat32(1, weight, 1);
}
static addGuidanceStart(builder, guidanceStart) {
builder.addFieldFloat32(2, guidanceStart, 0);
}
static addGuidanceEnd(builder, guidanceEnd) {
builder.addFieldFloat32(3, guidanceEnd, 1);
}
static addNoPrompt(builder, noPrompt) {
builder.addFieldInt8(4, +noPrompt, 0);
}
static addGlobalAveragePooling(builder, globalAveragePooling) {
builder.addFieldInt8(5, +globalAveragePooling, 1);
}
static addDownSamplingRate(builder, downSamplingRate) {
builder.addFieldFloat32(6, downSamplingRate, 1);
}
static addControlMode(builder, controlMode) {
builder.addFieldInt8(7, controlMode, 0 /* Balanced */);
}
static addTargetBlocks(builder, targetBlocksOffset) {
builder.addFieldOffset(8, targetBlocksOffset, 0);
}
static createTargetBlocksVector(builder, data) {
builder.startVector(4, data.length, 4);
for (let i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]);
}
return builder.endVector();
}
static startTargetBlocksVector(builder, numElems) {
builder.startVector(4, numElems, 4);
}
static addInputOverride(builder, inputOverride) {
builder.addFieldInt8(9, inputOverride, 0 /* Unspecified */);
}
static endControl(builder) {
const offset = builder.endObject();
return offset;
}
static createControl(builder, fileOffset, weight, guidanceStart, guidanceEnd, noPrompt, globalAveragePooling, downSamplingRate, controlMode, targetBlocksOffset, inputOverride) {
_Control.startControl(builder);
_Control.addFile(builder, fileOffset);
_Control.addWeight(builder, weight);
_Control.addGuidanceStart(builder, guidanceStart);
_Control.addGuidanceEnd(builder, guidanceEnd);
_Control.addNoPrompt(builder, noPrompt);
_Control.addGlobalAveragePooling(builder, globalAveragePooling);
_Control.addDownSamplingRate(builder, downSamplingRate);
_Control.addControlMode(builder, controlMode);
_Control.addTargetBlocks(builder, targetBlocksOffset);
_Control.addInputOverride(builder, inputOverride);
return _Control.endControl(builder);
}
};
// src/interfaces/generation-configuration.ts
import * as flatbuffers3 from "flatbuffers";
// src/interfaces/lo-ra.ts
import * as flatbuffers2 from "flatbuffers";
// src/interfaces/lo-ramode.ts
var LoRAMode = /* @__PURE__ */ ((LoRAMode2) => {
LoRAMode2[LoRAMode2["All"] = 0] = "All";
LoRAMode2[LoRAMode2["Base"] = 1] = "Base";
LoRAMode2[LoRAMode2["Refiner"] = 2] = "Refiner";
return LoRAMode2;
})(LoRAMode || {});
// src/interfaces/lo-ra.ts
var LoRA = class _LoRA {
bb = null;
bb_pos = 0;
__init(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsLoRA(bb, obj) {
return (obj || new _LoRA()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsLoRA(bb, obj) {
bb.setPosition(bb.position() + flatbuffers2.SIZE_PREFIX_LENGTH);
return (obj || new _LoRA()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
file(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
weight() {
const offset = this.bb.__offset(this.bb_pos, 6);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0.6;
}
mode() {
const offset = this.bb.__offset(this.bb_pos, 8);
return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* All */;
}
static startLoRA(builder) {
builder.startObject(3);
}
static addFile(builder, fileOffset) {
builder.addFieldOffset(0, fileOffset, 0);
}
static addWeight(builder, weight) {
builder.addFieldFloat32(1, weight, 0.6);
}
static addMode(builder, mode) {
builder.addFieldInt8(2, mode, 0 /* All */);
}
static endLoRA(builder) {
const offset = builder.endObject();
return offset;
}
static createLoRA(builder, fileOffset, weight, mode) {
_LoRA.startLoRA(builder);
_LoRA.addFile(builder, fileOffset);
_LoRA.addWeight(builder, weight);
_LoRA.addMode(builder, mode);
return _LoRA.endLoRA(builder);
}
};
// src/interfaces/sampler-type.ts
var SamplerType = /* @__PURE__ */ ((SamplerType2) => {
SamplerType2[SamplerType2["DPMPP2MKarras"] = 0] = "DPMPP2MKarras";
SamplerType2[SamplerType2["EulerA"] = 1] = "EulerA";
SamplerType2[SamplerType2["DDIM"] = 2] = "DDIM";
SamplerType2[SamplerType2["PLMS"] = 3] = "PLMS";
SamplerType2[SamplerType2["DPMPPSDEKarras"] = 4] = "DPMPPSDEKarras";
SamplerType2[SamplerType2["UniPC"] = 5] = "UniPC";
SamplerType2[SamplerType2["LCM"] = 6] = "LCM";
SamplerType2[SamplerType2["EulerASubstep"] = 7] = "EulerASubstep";
SamplerType2[SamplerType2["DPMPPSDESubstep"] = 8] = "DPMPPSDESubstep";
SamplerType2[SamplerType2["TCD"] = 9] = "TCD";
SamplerType2[SamplerType2["EulerATrailing"] = 10] = "EulerATrailing";
SamplerType2[SamplerType2["DPMPPSDETrailing"] = 11] = "DPMPPSDETrailing";
SamplerType2[SamplerType2["DPMPP2MAYS"] = 12] = "DPMPP2MAYS";
SamplerType2[SamplerType2["EulerAAYS"] = 13] = "EulerAAYS";
SamplerType2[SamplerType2["DPMPPSDEAYS"] = 14] = "DPMPPSDEAYS";
SamplerType2[SamplerType2["DPMPP2MTrailing"] = 15] = "DPMPP2MTrailing";
SamplerType2[SamplerType2["DDIMTrailing"] = 16] = "DDIMTrailing";
SamplerType2[SamplerType2["UniPCTrailing"] = 17] = "UniPCTrailing";
SamplerType2[SamplerType2["UniPCAYS"] = 18] = "UniPCAYS";
SamplerType2[SamplerType2["TCDTrailing"] = 19] = "TCDTrailing";
return SamplerType2;
})(SamplerType || {});
// src/interfaces/seed-mode.ts
var SeedMode = /* @__PURE__ */ ((SeedMode2) => {
SeedMode2[SeedMode2["Legacy"] = 0] = "Legacy";
SeedMode2[SeedMode2["TorchCpuCompatible"] = 1] = "TorchCpuCompatible";
SeedMode2[SeedMode2["ScaleAlike"] = 2] = "ScaleAlike";
SeedMode2[SeedMode2["NvidiaGpuCompatible"] = 3] = "NvidiaGpuCompatible";
return SeedMode2;
})(SeedMode || {});
// src/interfaces/generation-configuration.ts
var GenerationConfiguration = class _GenerationConfiguration {
bb = null;
bb_pos = 0;
__init(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsGenerationConfiguration(bb, obj) {
return (obj || new _GenerationConfiguration()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsGenerationConfiguration(bb, obj) {
bb.setPosition(bb.position() + flatbuffers3.SIZE_PREFIX_LENGTH);
return (obj || new _GenerationConfiguration()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id() {
const offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readInt64(this.bb_pos + offset) : BigInt("0");
}
startWidth() {
const offset = this.bb.__offset(this.bb_pos, 6);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 0;
}
startHeight() {
const offset = this.bb.__offset(this.bb_pos, 8);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 0;
}
seed() {
const offset = this.bb.__offset(this.bb_pos, 10);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
steps() {
const offset = this.bb.__offset(this.bb_pos, 12);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
guidanceScale() {
const offset = this.bb.__offset(this.bb_pos, 14);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0;
}
strength() {
const offset = this.bb.__offset(this.bb_pos, 16);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0;
}
model(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 18);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
sampler() {
const offset = this.bb.__offset(this.bb_pos, 20);
return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* DPMPP2MKarras */;
}
batchCount() {
const offset = this.bb.__offset(this.bb_pos, 22);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 1;
}
batchSize() {
const offset = this.bb.__offset(this.bb_pos, 24);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 1;
}
hiresFix() {
const offset = this.bb.__offset(this.bb_pos, 26);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
hiresFixStartWidth() {
const offset = this.bb.__offset(this.bb_pos, 28);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 0;
}
hiresFixStartHeight() {
const offset = this.bb.__offset(this.bb_pos, 30);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 0;
}
hiresFixStrength() {
const offset = this.bb.__offset(this.bb_pos, 32);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0.7;
}
upscaler(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 34);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
imageGuidanceScale() {
const offset = this.bb.__offset(this.bb_pos, 36);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1.5;
}
seedMode() {
const offset = this.bb.__offset(this.bb_pos, 38);
return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* Legacy */;
}
clipSkip() {
const offset = this.bb.__offset(this.bb_pos, 40);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 1;
}
controls(index, obj) {
const offset = this.bb.__offset(this.bb_pos, 42);
return offset ? (obj || new Control()).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos + offset) + index * 4), this.bb) : null;
}
controlsLength() {
const offset = this.bb.__offset(this.bb_pos, 42);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
}
loras(index, obj) {
const offset = this.bb.__offset(this.bb_pos, 44);
return offset ? (obj || new LoRA()).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos + offset) + index * 4), this.bb) : null;
}
lorasLength() {
const offset = this.bb.__offset(this.bb_pos, 44);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
}
maskBlur() {
const offset = this.bb.__offset(this.bb_pos, 46);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0;
}
faceRestoration(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 48);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
clipWeight() {
const offset = this.bb.__offset(this.bb_pos, 54);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
negativePromptForImagePrior() {
const offset = this.bb.__offset(this.bb_pos, 56);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true;
}
imagePriorSteps() {
const offset = this.bb.__offset(this.bb_pos, 58);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 5;
}
refinerModel(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 60);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
originalImageHeight() {
const offset = this.bb.__offset(this.bb_pos, 62);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
originalImageWidth() {
const offset = this.bb.__offset(this.bb_pos, 64);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
cropTop() {
const offset = this.bb.__offset(this.bb_pos, 66);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 0;
}
cropLeft() {
const offset = this.bb.__offset(this.bb_pos, 68);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 0;
}
targetImageHeight() {
const offset = this.bb.__offset(this.bb_pos, 70);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
targetImageWidth() {
const offset = this.bb.__offset(this.bb_pos, 72);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
aestheticScore() {
const offset = this.bb.__offset(this.bb_pos, 74);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 6;
}
negativeAestheticScore() {
const offset = this.bb.__offset(this.bb_pos, 76);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 2.5;
}
zeroNegativePrompt() {
const offset = this.bb.__offset(this.bb_pos, 78);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
refinerStart() {
const offset = this.bb.__offset(this.bb_pos, 80);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0.7;
}
negativeOriginalImageHeight() {
const offset = this.bb.__offset(this.bb_pos, 82);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
negativeOriginalImageWidth() {
const offset = this.bb.__offset(this.bb_pos, 84);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
}
name(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 86);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
fpsId() {
const offset = this.bb.__offset(this.bb_pos, 88);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 5;
}
motionBucketId() {
const offset = this.bb.__offset(this.bb_pos, 90);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 127;
}
condAug() {
const offset = this.bb.__offset(this.bb_pos, 92);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0.02;
}
startFrameCfg() {
const offset = this.bb.__offset(this.bb_pos, 94);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
numFrames() {
const offset = this.bb.__offset(this.bb_pos, 96);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 14;
}
maskBlurOutset() {
const offset = this.bb.__offset(this.bb_pos, 98);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 0;
}
sharpness() {
const offset = this.bb.__offset(this.bb_pos, 100);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0;
}
shift() {
const offset = this.bb.__offset(this.bb_pos, 102);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
stage2Steps() {
const offset = this.bb.__offset(this.bb_pos, 104);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 10;
}
stage2Cfg() {
const offset = this.bb.__offset(this.bb_pos, 106);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
stage2Shift() {
const offset = this.bb.__offset(this.bb_pos, 108);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 1;
}
tiledDecoding() {
const offset = this.bb.__offset(this.bb_pos, 110);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
decodingTileWidth() {
const offset = this.bb.__offset(this.bb_pos, 112);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 10;
}
decodingTileHeight() {
const offset = this.bb.__offset(this.bb_pos, 114);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 10;
}
decodingTileOverlap() {
const offset = this.bb.__offset(this.bb_pos, 116);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 2;
}
stochasticSamplingGamma() {
const offset = this.bb.__offset(this.bb_pos, 118);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0.3;
}
preserveOriginalAfterInpaint() {
const offset = this.bb.__offset(this.bb_pos, 120);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true;
}
tiledDiffusion() {
const offset = this.bb.__offset(this.bb_pos, 122);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
diffusionTileWidth() {
const offset = this.bb.__offset(this.bb_pos, 124);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 16;
}
diffusionTileHeight() {
const offset = this.bb.__offset(this.bb_pos, 126);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 16;
}
diffusionTileOverlap() {
const offset = this.bb.__offset(this.bb_pos, 128);
return offset ? this.bb.readUint16(this.bb_pos + offset) : 2;
}
upscalerScaleFactor() {
const offset = this.bb.__offset(this.bb_pos, 130);
return offset ? this.bb.readUint8(this.bb_pos + offset) : 0;
}
t5TextEncoder() {
const offset = this.bb.__offset(this.bb_pos, 132);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true;
}
separateClipL() {
const offset = this.bb.__offset(this.bb_pos, 134);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
clipLText(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 136);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
separateOpenClipG() {
const offset = this.bb.__offset(this.bb_pos, 138);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
openClipGText(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 140);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
speedUpWithGuidanceEmbed() {
const offset = this.bb.__offset(this.bb_pos, 142);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true;
}
guidanceEmbed() {
const offset = this.bb.__offset(this.bb_pos, 144);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 3.5;
}
resolutionDependentShift() {
const offset = this.bb.__offset(this.bb_pos, 146);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true;
}
teaCacheStart() {
const offset = this.bb.__offset(this.bb_pos, 148);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 5;
}
teaCacheEnd() {
const offset = this.bb.__offset(this.bb_pos, 150);
return offset ? this.bb.readInt32(this.bb_pos + offset) : -1;
}
teaCacheThreshold() {
const offset = this.bb.__offset(this.bb_pos, 152);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0.06;
}
teaCache() {
const offset = this.bb.__offset(this.bb_pos, 154);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
separateT5() {
const offset = this.bb.__offset(this.bb_pos, 156);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
t5Text(optionalEncoding) {
const offset = this.bb.__offset(this.bb_pos, 158);
return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null;
}
teaCacheMaxSkipSteps() {
const offset = this.bb.__offset(this.bb_pos, 160);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 3;
}
causalInferenceEnabled() {
const offset = this.bb.__offset(this.bb_pos, 162);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
causalInference() {
const offset = this.bb.__offset(this.bb_pos, 164);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 3;
}
causalInferencePad() {
const offset = this.bb.__offset(this.bb_pos, 166);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 0;
}
cfgZeroStar() {
const offset = this.bb.__offset(this.bb_pos, 168);
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
}
cfgZeroInitSteps() {
const offset = this.bb.__offset(this.bb_pos, 170);
return offset ? this.bb.readInt32(this.bb_pos + offset) : 0;
}
compressionArtifacts() {
const offset = this.bb.__offset(this.bb_pos, 172);
return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* Disabled */;
}
compressionArtifactsQuality() {
const offset = this.bb.__offset(this.bb_pos, 174);
return offset ? this.bb.readFloat32(this.bb_pos + offset) : 43.1;
}
static startGenerationConfiguration(builder) {
builder.startObject(86);
}
static addId(builder, id) {
builder.addFieldInt64(0, id, BigInt("0"));
}
static addStartWidth(builder, startWidth) {
builder.addFieldInt16(1, startWidth, 0);
}
static addStartHeight(builder, startHeight) {
builder.addFieldInt16(2, startHeight, 0);
}
static addSeed(builder, seed) {
builder.addFieldInt32(3, seed, 0);
}
static addSteps(builder, steps) {
builder.addFieldInt32(4, steps, 0);
}
static addGuidanceScale(builder, guidanceScale) {
builder.addFieldFloat32(5, guidanceScale, 0);
}
static addStrength(builder, strength) {
builder.addFieldFloat32(6, strength, 0);
}
static addModel(builder, modelOffset) {
builder.addFieldOffset(7, modelOffset, 0);
}
static addSampler(builder, sampler) {
builder.addFieldInt8(8, sampler, 0 /* DPMPP2MKarras */);
}
static addBatchCount(builder, batchCount) {
builder.addFieldInt32(9, batchCount, 1);
}
static addBatchSize(builder, batchSize) {
builder.addFieldInt32(10, batchSize, 1);
}
static addHiresFix(builder, hiresFix) {
builder.addFieldInt8(11, +hiresFix, 0);
}
static addHiresFixStartWidth(builder, hiresFixStartWidth) {
builder.addFieldInt16(12, hiresFixStartWidth, 0);
}
static addHiresFixStartHeight(builder, hiresFixStartHeight) {
builder.addFieldInt16(13, hiresFixStartHeight, 0);
}
static addHiresFixStrength(builder, hiresFixStrength) {
builder.addFieldFloat32(14, hiresFixStrength, 0.7);
}
static addUpscaler(builder, upscalerOffset) {
builder.addFieldOffset(15, upscalerOffset, 0);
}
static addImageGuidanceScale(builder, imageGuidanceScale) {
builder.addFieldFloat32(16, imageGuidanceScale, 1.5);
}
static addSeedMode(builder, seedMode) {
builder.addFieldInt8(17, seedMode, 0 /* Legacy */);
}
static addClipSkip(builder, clipSkip) {
builder.addFieldInt32(18, clipSkip, 1);
}
static addControls(builder, controlsOffset) {
builder.addFieldOffset(19, controlsOffset, 0);
}
static createControlsVector(builder, data) {
builder.startVector(4, data.length, 4);
for (let i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]);
}
return builder.endVector();
}
static startControlsVector(builder, numElems) {
builder.startVector(4, numElems, 4);
}
static addLoras(builder, lorasOffset) {
builder.addFieldOffset(20, lorasOffset, 0);
}
static createLorasVector(builder, data) {
builder.startVector(4, data.length, 4);
for (let i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]);
}
return builder.endVector();
}
static startLorasVector(builder, numElems) {
builder.startVector(4, numElems, 4);
}
static addMaskBlur(builder, maskBlur) {
builder.addFieldFloat32(21, maskBlur, 0);
}
static addFaceRestoration(builder, faceRestorationOffset) {
builder.addFieldOffset(22, faceRestorationOffset, 0);
}
static addClipWeight(builder, clipWeight) {
builder.addFieldFloat32(25, clipWeight, 1);
}
static addNegativePromptForImagePrior(builder, negativePromptForImagePrior) {
builder.addFieldInt8(26, +negativePromptForImagePrior, 1);
}
static addImagePriorSteps(builder, imagePriorSteps) {
builder.addFieldInt32(27, imagePriorSteps, 5);
}
static addRefinerModel(builder, refinerModelOffset) {
builder.addFieldOffset(28, refinerModelOffset, 0);
}
static addOriginalImageHeight(builder, originalImageHeight) {
builder.addFieldInt32(29, originalImageHeight, 0);
}
static addOriginalImageWidth(builder, originalImageWidth) {
builder.addFieldInt32(30, originalImageWidth, 0);
}
static addCropTop(builder, cropTop) {
builder.addFieldInt32(31, cropTop, 0);
}
static addCropLeft(builder, cropLeft) {
builder.addFieldInt32(32, cropLeft, 0);
}
static addTargetImageHeight(builder, targetImageHeight) {
builder.addFieldInt32(33, targetImageHeight, 0);
}
static addTargetImageWidth(builder, targetImageWidth) {
builder.addFieldInt32(34, targetImageWidth, 0);
}
static addAestheticScore(builder, aestheticScore) {
builder.addFieldFloat32(35, aestheticScore, 6);
}
static addNegativeAestheticScore(builder, negativeAestheticScore) {
builder.addFieldFloat32(36, negativeAestheticScore, 2.5);
}
static addZeroNegativePrompt(builder, zeroNegativePrompt) {
builder.addFieldInt8(37, +zeroNegativePrompt, 0);
}
static addRefinerStart(builder, refinerStart) {
builder.addFieldFloat32(38, refinerStart, 0.7);
}
static addNegativeOriginalImageHeight(builder, negativeOriginalImageHeight) {
builder.addFieldInt32(39, negativeOriginalImageHeight, 0);
}
static addNegativeOriginalImageWidth(builder, negativeOriginalImageWidth) {
builder.addFieldInt32(40, negativeOriginalImageWidth, 0);
}
static addName(builder, nameOffset) {
builder.addFieldOffset(41, nameOffset, 0);
}
static addFpsId(builder, fpsId) {
builder.addFieldInt32(42, fpsId, 5);
}
static addMotionBucketId(builder, motionBucketId) {
builder.addFieldInt32(43, motionBucketId, 127);
}
static addCondAug(builder, condAug) {
builder.addFieldFloat32(44, condAug, 0.02);
}
static addStartFrameCfg(builder, startFrameCfg) {
builder.addFieldFloat32(45, startFrameCfg, 1);
}
static addNumFrames(builder, numFrames) {
builder.addFieldInt32(46, numFrames, 14);
}
static addMaskBlurOutset(builder, maskBlurOutset) {
builder.addFieldInt32(47, maskBlurOutset, 0);
}
static addSharpness(builder, sharpness) {
builder.addFieldFloat32(48, sharpness, 0);
}
static addShift(builder, shift) {
builder.addFieldFloat32(49, shift, 1);
}
static addStage2Steps(builder, stage2Steps) {
builder.addFieldInt32(50, stage2Steps, 10);
}
static addStage2Cfg(builder, stage2Cfg) {
builder.addFieldFloat32(51, stage2Cfg, 1);
}
static addStage2Shift(builder, stage2Shift) {
builder.addFieldFloat32(52, stage2Shift, 1);
}
static addTiledDecoding(builder, tiledDecoding) {
builder.addFieldInt8(53, +tiledDecoding, 0);
}
static addDecodingTileWidth(builder, decodingTileWidth) {
builder.addFieldInt16(54, decodingTileWidth, 10);
}
static addDecodingTileHeight(builder, decodingTileHeight) {
builder.addFieldInt16(55, decodingTileHeight, 10);
}
static addDecodingTileOverlap(builder, decodingTileOverlap) {
builder.addFieldInt16(56, decodingTileOverlap, 2);
}
static addStochasticSamplingGamma(builder, stochasticSamplingGamma) {
builder.addFieldFloat32(57, stochasticSamplingGamma, 0.3);
}
static addPreserveOriginalAfterInpaint(builder, preserveOriginalAfterInpaint) {
builder.addFieldInt8(58, +preserveOriginalAfterInpaint, 1);
}
static addTiledDiffusion(builder, tiledDiffusion) {
builder.addFieldInt8(59, +tiledDiffusion, 0);
}
static addDiffusionTileWidth(builder, diffusionTileWidth) {
builder.addFieldInt16(60, diffusionTileWidth, 16);
}
static addDiffusionTileHeight(builder, diffusionTileHeight) {
builder.addFieldInt16(61, diffusionTileHeight, 16);
}
static addDiffusionTileOverlap(builder, diffusionTileOverlap) {
builder.addFieldInt16(62, diffusionTileOverlap, 2);
}
static addUpscalerScaleFactor(builder, upscalerScaleFactor) {
builder.addFieldInt8(63, upscalerScaleFactor, 0);
}
static addT5TextEncoder(builder, t5TextEncoder) {
builder.addFieldInt8(64, +t5TextEncoder, 1);
}
static addSeparateClipL(builder, separateClipL) {
builder.addFieldInt8(65, +separateClipL, 0);
}
static addClipLText(builder, clipLTextOffset) {
builder.addFieldOffset(66, clipLTextOffset, 0);
}
static addSeparateOpenClipG(builder, separateOpenClipG) {
builder.addFieldInt8(67, +separateOpenClipG, 0);
}
static addOpenClipGText(builder, openClipGTextOffset) {
builder.addFieldOffset(68, openClipGTextOffset, 0);
}
static addSpeedUpWithGuidanceEmbed(builder, speedUpWithGuidanceEmbed) {
builder.addFieldInt8(69, +speedUpWithGuidanceEmbed, 1);
}
static addGuidanceEmbed(builder, guidanceEmbed) {
builder.addFieldFloat32(70, guidanceEmbed, 3.5);
}
static addResolutionDependentShift(builder, resolutionDependentShift) {
builder.addFieldInt8(71, +resolutionDependentShift, 1);
}
static addTeaCacheStart(builder, teaCacheStart) {
builder.addFieldInt32(72, teaCacheStart, 5);
}
static addTeaCacheEnd(builder, teaCacheEnd) {
builder.addFieldInt32(73, teaCacheEnd, -1);
}
static addTeaCacheThreshold(builder, teaCacheThreshold) {
builder.addFieldFloat32(74, teaCacheThreshold, 0.06);
}
static addTeaCache(builder, teaCache) {
builder.addFieldInt8(75, +teaCache, 0);
}
static addSeparateT5(builder, separateT5) {
builder.addFieldInt8(76, +separateT5, 0);
}
static addT5Text(builder, t5TextOffset) {
builder.addFieldOffset(77, t5TextOffset, 0);
}
static addTeaCacheMaxSkipSteps(builder, teaCacheMaxSkipSteps) {
builder.addFieldInt32(78, teaCacheMaxSkipSteps, 3);
}
static addCausalInferenceEnabled(builder, causalInferenceEnabled) {
builder.addFieldInt8(79, +causalInferenceEnabled, 0);
}
static addCausalInference(builder, causalInference) {
builder.addFieldInt32(80, causalInference, 3);
}
static addCausalInferencePad(builder, causalInferencePad) {
builder.addFieldInt32(81, causalInferencePad, 0);
}
static addCfgZeroStar(builder, cfgZeroStar) {
builder.addFieldInt8(82, +cfgZeroStar, 0);
}
static addCfgZeroInitSteps(builder, cfgZeroInitSteps) {
builder.addFieldInt32(83, cfgZeroInitSteps, 0);
}
static addCompressionArtifacts(builder, compressionArtifacts) {
builder.addFieldInt8(84, compressionArtifacts, 0 /* Disabled */);
}
static addCompressionArtifactsQuality(builder, compressionArtifactsQuality) {
builder.addFieldFloat32(85, compressionArtifactsQuality, 43.1);
}
static endGenerationConfiguration(builder) {
const offset = builder.endObject();
return offset;
}
static finishGenerationConfigurationBuffer(builder, offset) {
builder.finish(offset);
}
static finishSizePrefixedGenerationConfigurationBuffer(builder, offset) {
builder.finish(offset, void 0, true);
}
static createGenerationConfiguration(builder, id, startWidth, startHeight, seed, steps, guidanceScale, strength, modelOffset, sampler, batchCount, batchSize, hiresFix, hiresFixStartWidth, hiresFixStartHeight, hiresFixStrength, upscalerOffset, imageGuidanceScale, seedMode, clipSkip, controlsOffset, lorasOffset, maskBlur, faceRestorationOffset, clipWeight, negativePromptForImagePrior, imagePriorSteps, refinerModelOffset, originalImageHeight, originalImageWidth, cropTop, cropLeft, targetImageHeight, targetImageWidth, aestheticScore, negativeAestheticScore, zeroNegativePrompt, refinerStart, negativeOriginalImageHeight, negativeOriginalImageWidth, nameOffset, fpsId, motionBucketId, condAug, startFrameCfg, numFrames, maskBlurOutset, sharpness, shift, stage2Steps, stage2Cfg, stage2Shift, tiledDecoding, decodingTileWidth, decodingTileHeight, decodingTileOverlap, stochasticSamplingGamma, preserveOriginalAfterInpaint, tiledDiffusion, diffusionTileWidth, diffusionTileHeight, diffusionTileOverlap, upscalerScaleFactor, t5TextEncoder, separateClipL, clipLTextOffset, separateOpenClipG, openClipGTextOffset, speedUpWithGuidanceEmbed, guidanceEmbed, resolutionDependentShift, teaCacheStart, teaCacheEnd, teaCacheThreshold, teaCache, separateT5, t5TextOffset, teaCacheMaxSkipSteps, causalInferenceEnabled, causalInference, causalInferencePad, cfgZeroStar, cfgZeroInitSteps) {
_GenerationConfiguration.startGenerationConfiguration(builder);
_GenerationConfiguration.addId(builder, id);
_GenerationConfiguration.addStartWidth(builder, startWidth);
_GenerationConfiguration.addStartHeight(builder, startHeight);
_GenerationConfiguration.addSeed(builder, seed);
_GenerationConfiguration.addSteps(builder, steps);
_GenerationConfiguration.addGuidanceScale(builder, guidanceScale);
_GenerationConfiguration.addStrength(builder, strength);
_GenerationConfiguration.addModel(builder, modelOffset);
_GenerationConfiguration.addSampler(builder, sampler);
_GenerationConfiguration.addBatchCount(builder, batchCount);
_GenerationConfiguration.addBatchSize(builder, batchSize);
_GenerationConfiguration.addHiresFix(builder, hiresFix);
_GenerationConfiguration.addHiresFixStartWidth(builder, hiresFixStartWidth);
_GenerationConfiguration.addHiresFixStartHeight(builder, hiresFixStartHeight);
_GenerationConfiguration.addHiresFixStrength(builder, hiresFixStrength);
_GenerationConfiguration.addUpscaler(builder, upscalerOffset);
_GenerationConfiguration.addImageGuidanceScale(builder, imageGuidanceScale);
_GenerationConfiguration.addSeedMode(builder, seedMode);
_GenerationConfiguration.addClipSkip(builder, clipSkip);
_GenerationConfiguration.addControls(builder, controlsOffset);
_GenerationConfiguration.addLoras(builder, lorasOffset);
_GenerationConfiguration.addMaskBlur(builder, maskBlur);
_GenerationConfiguration.addFaceRestoration(builder, faceRestorationOffset);
_GenerationConfiguration.addClipWeight(builder, clipWeight);
_GenerationConfiguration.addNegativePromptForImagePrior(builder, negativePromptForImagePrior);
_GenerationConfiguration.addImagePriorSteps(builder, imagePriorSteps);
_GenerationConfiguration.addRefinerModel(builder, refinerModelOffset);
_GenerationConfiguration.addOriginalImageHeight(builder, originalImageHeight);
_GenerationConfiguration.addOriginalImageWidth(builder, originalImageWidth);
_GenerationConfiguration.addCropTop(builder, cropTop);
_GenerationConfiguration.addCropLeft(builder, cropLeft);
_GenerationConfiguration.addTargetImageHeight(builder, targetImageHeight);
_GenerationConfiguration.addTargetImageWidth(builder, targetImageWidth);
_GenerationConfiguration.addAestheticScore(builder, aestheticScore);
_GenerationConfiguration.addNegativeAestheticScore(builder, negativeAestheticScore);
_GenerationConfiguration.addZeroNegativePrompt(builder, zeroNegativePrompt);
_GenerationConfiguration.addRefinerStart(builder, refinerStart);
_GenerationConfiguration.addNegativeOriginalImageHeight(builder, negativeOriginalImageHeight);
_GenerationConfiguration.addNegativeOriginalImageWidth(builder, negativeOriginalImageWidth);
_GenerationConfiguration.addName(builder, nameOffset);
_GenerationConfiguration.addFpsId(builder, fpsId);
_GenerationConfiguration.addMotionBucketId(builder, motionBucketId);
_GenerationConfiguration.addCondAug(builder, condAug);
_GenerationConfiguration.addStartFrameCfg(builder, startFrameCfg);
_GenerationConfiguration.addNumFrames(builder, numFrames);
_GenerationConfiguration.addMaskBlurOutset(builder, maskBlurOutset);
_GenerationConfiguration.addSharpness(builder, sharpness);
_GenerationConfiguration.addShift(builder, shift);
_GenerationConfiguration.addStage2Steps(builder, stage2Steps);
_GenerationConfiguration.addStage2Cfg(builder, stage2Cfg);
_GenerationConfiguration.addStage2Shift(builder, stage2Shift);
_GenerationConfiguration.addTiledDecoding(builder, tiledDecoding);
_GenerationConfiguration.addDecodingTileWidth(builder, decodingTileWidth);
_GenerationConfiguration.addDecodingTileHeight(builder, decodingTileHeight);
_GenerationConfiguration.addDecodingTileOverlap(builder, decodingTileOverlap);
_GenerationConfiguration.addStochasticSamplingGamma(builder, stochasticSamplingGamma);
_GenerationConfiguration.addPreserveOriginalAfterInpaint(builder, preserveOriginalAfterInpaint);
_GenerationConfiguration.addTiledDiffusion(builder, tiledDiffusion);
_GenerationConfiguration.addDiffusionTileWidth(builder, diffusionTileWidth);
_GenerationConfiguration.addDiffusionTileHeight(builder, diffusionTileHeight);
_GenerationConfiguration.addDiffusionTileOverlap(builder, diffusionTileOverlap);
_GenerationConfiguration.addUpscalerScaleFactor(builder, upscalerScaleFactor);
_GenerationConfiguration.addT5TextEncoder(builder, t5TextEncoder);
_GenerationConfiguration.addSeparateClipL(builder, separateClipL);
_GenerationConfiguration.addClipLText(builder, clipLTextOffset);
_GenerationConfiguration.addSeparateOpenClipG(builder, separateOpenClipG);
_GenerationConfiguration.addOpenClipGText(builder, openClipGTextOffset);
_GenerationConfiguration.addSpeedUpWithGuidanceEmbed(builder, speedUpWithGuidanceEmbed);
_GenerationConfiguration.addGuidanceEmbed(builder, guidanceEmbed);
_GenerationConfiguration.addResolutionDependentShift(builder, resolutionDependentShift);
_GenerationConfiguration.addTeaCacheStart(builder, teaCacheStart);
_GenerationConfiguration.addTeaCacheEnd(builder, teaCacheEnd);
_GenerationConfiguration.addTeaCacheThreshold(builder, teaCacheThreshold);
_GenerationConfiguration.addTeaCache(builder, teaCache);
_GenerationConfiguration.addSeparateT5(builder, separateT5);
_GenerationConfiguration.addT5Text(builder, t5TextOffset);
_GenerationConfiguration.addTeaCacheMaxSkipSteps(builder, teaCacheMaxSkipSteps);
_GenerationConfiguration.addCausalInferenceEnabled(builder, causalInferenceEnabled);
_GenerationConfiguration.addCausalInference(builder, causalInference);
_GenerationConfiguration.addCausalInferencePad(builder, causalInferencePad);
_GenerationConfiguration.addCfgZeroStar(builder, cfgZeroStar);
_GenerationConfiguration.addCfgZeroInitSteps(builder, cfgZeroInitSteps);
return _GenerationConfiguration.endGenerationConfiguration(builder);
}
};
// src/media-promotion-core/attachments.ts
import fs12 from "fs";
import path14 from "path";
// src/media-promotion-core/state.ts
import fs11 from "fs";
import path13 from "path";
function localTimestamp2(d = /* @__PURE__ */ new Date()) {
const pad = (n, len = 2) => String(n).padStart(len, "0");
const year = d.getFullYear();
const month = pad(d.getMonth() + 1);
const day = pad(d.getDate());
const hours = pad(d.getHours());
const minutes = pad(d.getMinutes());
const seconds = pad(d.getSeconds());
const millis = pad(d.getMilliseconds(), 3);
const tzOffset = -d.getTimezoneOffset();
const tzSign = tzOffset >= 0 ? "+" : "-";
const tzHours = pad(Math.floor(Math.abs(tzOffset) / 60));
const tzMins = pad(Math.abs(tzOffset) % 60);
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${millis}${tzSign}${tzHours}:${tzMins}`;
}
function tsForFilename(d = /* @__PURE__ */ new Date()) {
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
const day = String(d.getUTCDate()).padStart(2, "0");
const hours = String(d.getUTCHours()).padStart(2, "0");
const minutes = String(d.getUTCMinutes()).padStart(2, "0");
const seconds = String(d.getUTCSeconds()).padStart(2, "0");
const millis = String(d.getUTCMilliseconds()).padStart(3, "0");
return `${year}${month}${day}T${hours}${minutes}${seconds}${millis}Z`;
}
async function readState(chatWd) {
const p = path13.join(chatWd, "chat_media_state.json");
try {
const raw = await fs11.promises.readFile(p, "utf-8");
const json = JSON.parse(raw);
return normalizeState(json);
} catch {
return {
attachments: [],
variants: [],
pictures: [],
images: [],
counters: {}
};
}
}
async function writeStateAtomic(chatWd, state) {
const tmp = path13.join(chatWd, "chat_media_state.json.tmp");
const dst = path13.join(chatWd, "chat_media_state.json");
if (Array.isArray(state.attachments) && state.attachments.length > 1) {
state.attachments.sort((a, b) => (a.a ?? 0) - (b.a ?? 0));
}
if (Array.isArray(state.variants) && state.variants.length > 1) {
state.variants.sort((a, b) => (a.v ?? 0) - (b.v ?? 0));
}
if (Array.isArray(state.pictures) && state.pictures.length > 1) {
state.pictures.sort((a, b) => (a.p ?? 0) - (b.p ?? 0));
}
if (Array.isArray(state.images) && state.images.length > 1) {
state.images.sort((a, b) => (a.i ?? 0) - (b.i ?? 0));
}
let pretty = JSON.stringify(state, null, 2);
pretty = pretty.replace(
/"(lastPromotedAttachmentAs|lastPromotedVariantVs|lastPromotedImageIs|lastPixelPromotedAttachmentAs|lastPixelPromotedVariantVs|lastPixelPromotedImageIs|injectedMarkdown)":\s*\[\s*\n([\s\S]*?)\n\s*\]/g,
(match, key, content) => {
const items = content.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
return `"${key}": [${items.join(", ")}]`;
}
);
await fs11.promises.writeFile(tmp, pretty, "utf-8");
await fs11.promises.rename(tmp, dst);
}
function normalizeState(s) {
let lastPromotedAttachmentAs;
if (Array.isArray(s?.lastPromotedAttachmentAs)) {
lastPromotedAttachmentAs = s.lastPromotedAttachmentAs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (Array.isArray(s?.lastPromotedAttachmentNs)) {
lastPromotedAttachmentAs = s.lastPromotedAttachmentNs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (typeof s?.lastPromotedAttachmentN === "number") {
lastPromotedAttachmentAs = [s.lastPromotedAttachmentN];
} else if (typeof s?.lastPromotedAttachmentA === "number") {
lastPromotedAttachmentAs = [s.lastPromotedAttachmentA];
}
let lastPromotedVariantVs;
if (Array.isArray(s?.lastPromotedVariantVs)) {
lastPromotedVariantVs = s.lastPromotedVariantVs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPromotedImageIs;
if (Array.isArray(s?.lastPromotedImageIs)) {
lastPromotedImageIs = s.lastPromotedImageIs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedAttachmentAs;
if (Array.isArray(s?.lastPixelPromotedAttachmentAs)) {
lastPixelPromotedAttachmentAs = s.lastPixelPromotedAttachmentAs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (Array.isArray(s?.lastPixelPromotedAttachmentNs)) {
lastPixelPromotedAttachmentAs = s.lastPixelPromotedAttachmentNs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedVariantVs;
if (Array.isArray(s?.lastPixelPromotedVariantVs)) {
lastPixelPromotedVariantVs = s.lastPixelPromotedVariantVs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedImageIs;
if (Array.isArray(s?.lastPixelPromotedImageIs)) {
lastPixelPromotedImageIs = s.lastPixelPromotedImageIs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
const normalizeNumberArray = (v) => {
if (!Array.isArray(v)) return void 0;
const a = v.filter((x) => typeof x === "number" && Number.isFinite(x)).map((x) => Math.floor(x)).filter((x) => x > 0);
if (!a.length) return void 0;
return Array.from(new Set(a)).sort((x, y) => x - y);
};
const pendingReviewPromotion = (() => {
const pr = s?.pendingReviewPromotion;
if (!pr || typeof pr !== "object") return void 0;
const requestedAt = typeof pr.requestedAt === "string" && pr.requestedAt.trim() ? String(pr.requestedAt) : "";
const targets = pr.targets && typeof pr.targets === "object" ? pr.targets : null;
if (!requestedAt || !targets) return void 0;
const a = normalizeNumberArray(targets.a);
const v = normalizeNumberArray(targets.v);
const i = normalizeNumberArray(targets.i);
const p = normalizeNumberArray(targets.p);
if (!a && !v && !i && !p) return void 0;
const ttlMs = typeof pr.ttlMs === "number" && Number.isFinite(pr.ttlMs) && pr.ttlMs > 0 ? pr.ttlMs : void 0;
return {
requestedAt,
requestedByToolCallId: typeof pr.requestedByToolCallId === "string" && pr.requestedByToolCallId.trim() ? String(pr.requestedByToolCallId) : void 0,
reason: typeof pr.reason === "string" && pr.reason.trim() ? String(pr.reason) : void 0,
ttlMs,
targets: {
a,
v,
i,
p
}
};
})();
const pendingSequenceReview = (() => {
const ps = s?.pendingSequenceReview;
if (!ps || typeof ps !== "object") return void 0;
const requestedAt = typeof ps.requestedAt === "string" && ps.requestedAt.trim() ? String(ps.requestedAt) : "";
const movAbs = typeof ps.movAbs === "string" && ps.movAbs.trim() ? String(ps.movAbs) : "";
const variant = typeof ps.variant === "number" && Number.isFinite(ps.variant) ? Math.floor(ps.variant) : 0;
if (!requestedAt || !movAbs || variant <= 0) return void 0;
const fps = typeof ps.fps === "number" && Number.isFinite(ps.fps) && ps.fps > 0 ? ps.fps : 2;
const ttlMs = typeof ps.ttlMs === "number" && Number.isFinite(ps.ttlMs) && ps.ttlMs > 0 ? ps.ttlMs : void 0;
return {
requestedAt,
movAbs,
variant,
variantLabel: typeof ps.variantLabel === "string" && ps.variantLabel.trim() ? String(ps.variantLabel) : void 0,
fps,
ttlMs
};
})();
const n = {
attachments: Array.isArray(s?.attachments) ? s.attachments : [],
variants: Array.isArray(s?.variants) ? s.variants : [],
pictures: Array.isArray(s?.pictures) ? s.pictures : [],
images: Array.isArray(s?.images) ? s.images : [],
pendingReviewPromotion,
pendingSequenceReview,
lastEvent: s?.lastEvent,
lastCanvasNotation: typeof s?.lastCanvasNotation === "string" ? s.lastCanvasNotation : void 0,
lastCanvasAt: typeof s?.lastCanvasAt === "string" ? s.lastCanvasAt : void 0,
counters: typeof s?.counters === "object" && s?.counters ? s.counters : {},
injectedMarkdown: Array.isArray(s?.injectedMarkdown) ? s.injectedMarkdown : void 0,
injectedContent: Array.isArray(s?.injectedContent) ? s.injectedContent.filter((x) => typeof x === "string" && x.trim()) : void 0,
lastVariantsTs: typeof s?.lastVariantsTs === "string" ? s.lastVariantsTs : void 0,
lastPromotedTs: typeof s?.lastPromotedTs === "string" ? s.lastPromotedTs : void 0,
lastPromotedAttachmentAs,
// Keep deprecated field for backward compat during transition
lastPromotedAttachmentA: typeof s?.lastPromotedAttachmentA === "number" ? s.lastPromotedAttachmentA : typeof s?.lastPromotedAttachmentN === "number" ? s.lastPromotedAttachmentN : void 0,
lastPromotedVariantVs,
lastPromotedImageIs,
lastPixelPromotedAt: typeof s?.lastPixelPromotedAt === "string" ? s.lastPixelPromotedAt : void 0,
lastPixelPromotedAttachmentAs,
lastPixelPromotedVariantVs,
lastPixelPromotedImageIs,
forcePixelPromotionNextTurn: typeof s?.forcePixelPromotionNextTurn === "boolean" ? s.forcePixelPromotionNextTurn : void 0,
forcePixelPromotionSetAt: typeof s?.forcePixelPromotionSetAt === "string" ? s.forcePixelPromotionSetAt : void 0,
forcePixelPromotionReason: typeof s?.forcePixelPromotionReason === "string" ? s.forcePixelPromotionReason : void 0
};
if (n.attachments.length > 1) {
n.attachments.sort((a, b) => (a.a ?? 0) - (b.a ?? 0));
}
if (n.variants.length > 1) {
n.variants.sort((a, b) => (a.v ?? 0) - (b.v ?? 0));
}
if (n.pictures.length > 1) {
n.pictures.sort((a, b) => (a.p ?? 0) - (b.p ?? 0));
}
if (n.images.length > 1) {
n.images.sort((a, b) => (a.i ?? 0) - (b.i ?? 0));
}
return n;
}
function upsertAttachment(state, rec) {
const existing = Array.isArray(state.attachments) ? state.attachments : [];
const createdAt = rec.createdAt ?? localTimestamp2();
const matches = (a) => {
if (rec.originAbs && a.originAbs && a.originAbs === rec.originAbs)
return true;
return a.filename === rec.filename && a.preview === rec.preview;
};
const idx = existing.findIndex(matches);
if (idx >= 0) {
const keep = existing[idx];
const updated = {
...keep,
filename: rec.filename,
origin: rec.origin,
originAbs: rec.originAbs,
originalName: rec.originalName ?? keep.originalName,
turnId: rec.turnId ?? keep.turnId,
preview: rec.preview,
createdAt: keep.createdAt,
a: keep.a
};
const next2 = [updated, ...existing.filter((_, i) => i !== idx)];
const changed = JSON.stringify(next2) !== JSON.stringify(existing);
if (changed) {
state.attachments = next2;
state.lastEvent = { type: "attachment", at: localTimestamp2() };
}
return { changed, state, record: updated };
}
const nextA = rec.a ?? Math.max(1, state.counters.nextAttachmentA ?? 1);
const finalRec = {
filename: rec.filename,
origin: rec.origin,
originAbs: rec.originAbs,
originalName: rec.originalName,
turnId: rec.turnId,
preview: rec.preview,
createdAt,
a: nextA
};
const next = [finalRec, ...existing];
state.attachments = next;
state.counters.nextAttachmentA = nextA + 1;
state.lastEvent = { type: "attachment", at: localTimestamp2() };
return { changed: true, state, record: finalRec };
}
function normalizeString(val) {
return typeof val === "string" ? String(val) : void 0;
}
function normalizeNumber(val) {
return typeof val === "number" && Number.isFinite(val) ? val : void 0;
}
function buildMediaRecord(input, index, indexField, createdAt, existingIndex) {
const base = {
filename: input.filename,
preview: input.preview,
sourceTool: normalizeString(input.sourceTool),
pluginId: normalizeString(input.pluginId),
sourceUrl: normalizeString(input.sourceUrl),
title: normalizeString(input.title),
confidence: normalizeString(input.confidence),
width: normalizeNumber(input.width),
height: normalizeNumber(input.height),
pageUrl: normalizeString(input.pageUrl),
turnId: normalizeNumber(input.turnId),
createdAt: input.createdAt ?? createdAt
};
if (input.kind === "tool_result" || input.kind === "generated") {
base.kind = input.kind;
}
base[indexField] = existingIndex ?? index;
return base;
}
function upgradeExistingRecord(existing, incoming, indexField) {
const incomingIndex = incoming[indexField];
if (existing[indexField] == null && typeof incomingIndex === "number") {
existing[indexField] = incomingIndex;
}
const fieldsToUpgrade = [
"sourceTool",
"pluginId",
"sourceUrl",
"title",
"confidence",
"pageUrl",
"kind"
];
for (const field of fieldsToUpgrade) {
if (existing[field] == null && incoming[field] != null) {
existing[field] = incoming[field];
}
}
if (existing.width == null && typeof incoming.width === "number") {
existing.width = incoming.width;
}
if (existing.height == null && typeof incoming.height === "number") {
existing.height = incoming.height;
}
if (existing.turnId == null && typeof incoming.turnId === "number") {
existing.turnId = incoming.turnId;
}
}
function appendMediaItems(state, items, config) {
if (!items.length) return { changed: false, state, records: [] };
const {
stateArrayKey,
counterKey,
indexField,
eventType,
getDedupeKey,
filter
} = config;
const existing = Array.isArray(
state[stateArrayKey]
) ? [...state[stateArrayKey]] : [];
const maxExistingIndex = existing.reduce((max, r) => {
const idx = r[indexField];
return Math.max(max, typeof idx === "number" && Number.isFinite(idx) ? idx : 0);
}, 0);
const counterIndex = state.counters[counterKey] ?? 1;
const baseIndex = Math.max(1, counterIndex, maxExistingIndex + 1);
const createdAt = localTimestamp2();
let newOnes = items.map(
(it, idx) => buildMediaRecord(
it,
baseIndex + idx,
indexField,
createdAt,
it[indexField]
)
);
if (filter) {
newOnes = newOnes.filter(filter);
}
const existingByKey = /* @__PURE__ */ new Map();
for (const item of existing) {
const key = getDedupeKey(item);
if (key) existingByKey.set(key, item);
}
const all = [...existing];
for (const r of newOnes) {
const key = getDedupeKey(r);
const existingItem = key ? existingByKey.get(key) : void 0;
if (!existingItem) {
all.push(r);
if (key) existingByKey.set(key, r);
} else {
upgradeExistingRecord(
existingItem,
r,
indexField
);
}
}
all.sort((a, b) => {
const aIdx = a[indexField] ?? 0;
const bIdx = b[indexField] ?? 0;
return aIdx - bIdx || String(a.createdAt || "").localeCompare(String(b.createdAt || ""));
});
const changed = JSON.stringify(all) !== JSON.stringify(state[stateArrayKey] || []);
if (changed) {
state[stateArrayKey] = all;
const lastIndex = all.at(-1)?.[indexField];
state.counters[counterKey] = (typeof lastIndex === "number" ? lastIndex : baseIndex - 1) + 1;
state.lastEvent = { type: eventType, at: localTimestamp2() };
}
return { changed, state, records: newOnes };
}
var VARIANT_APPEND_CONFIG = {
stateArrayKey: "variants",
counterKey: "nextVariantV",
indexField: "v",
eventType: "variants",
getDedupeKey: (item) => `${item.filename}|${item.preview}`
};
var PICTURE_APPEND_CONFIG = {
stateArrayKey: "pictures",
counterKey: "nextPictureP",
indexField: "p",
eventType: "pictures",
getDedupeKey: (item) => item.sourceUrl ? item.sourceUrl : `${item.filename}|${item.preview}`,
filter: (item) => !!String(item.sourceUrl || "").trim()
};
var IMAGE_APPEND_CONFIG = {
stateArrayKey: "images",
counterKey: "nextImageI",
indexField: "i",
eventType: "images",
getDedupeKey: (item) => `${item.filename}|${item.preview}`
};
function appendVariants(state, items) {
return appendMediaItems(
state,
items,
VARIANT_APPEND_CONFIG
);
}
function appendPictures(state, items) {
return appendMediaItems(
state,
items,
PICTURE_APPEND_CONFIG
);
}
function appendImages(state, items) {
return appendMediaItems(
state,
items,
IMAGE_APPEND_CONFIG
);
}
function referencedFiles(state) {
const s = /* @__PURE__ */ new Set();
for (const a of state.attachments) {
if (a.filename) s.add(a.filename);
if (a.preview) s.add(a.preview);
}
for (const v of state.variants) {
if (v.filename) s.add(v.filename);
if (v.preview) s.add(v.preview);
}
for (const p of state.pictures || []) {
if (p.filename) s.add(p.filename);
if (p.preview) s.add(p.preview);
}
for (const i of state.images || []) {
if (i.filename) s.add(i.filename);
if (i.preview) s.add(i.preview);
}
s.add("chat_media_state.json");
return s;
}
function fileInWd(absOrRel, chatWd) {
return path13.isAbsolute(absOrRel) ? absOrRel : path13.join(chatWd, absOrRel);
}
// src/media-promotion-core/attachments.ts
async function pathExists2(p) {
try {
await fs12.promises.access(p, fs12.constants.F_OK);
return true;
} catch {
return false;
}
}
async function importAttachmentBatch(chatWd, state, sourcePaths, turnIdByOriginAbs, previewOpts, maxPreviewAttachments = 0, debug = false) {
const normalizeAbs = (p) => {
try {
return path14.resolve(p);
} catch {
return p;
}
};
const normalizedSource = sourcePaths.filter((p) => typeof p === "string" && p.trim().length > 0).map(normalizeAbs);
const normalizeTurnIdMap = (m) => {
if (!m) return {};
const out = {};
for (const [k, v] of Object.entries(m)) {
if (typeof v === "number" && Number.isFinite(v)) {
out[normalizeAbs(k)] = v;
}
}
return out;
};
const normalizedTurnIdByAbs = normalizeTurnIdMap(turnIdByOriginAbs);
const normalizedSourceDeduped = [];
{
const seen = /* @__PURE__ */ new Set();
for (const p of normalizedSource) {
if (!seen.has(p)) {
seen.add(p);
normalizedSourceDeduped.push(p);
}
}
}
if (normalizedSourceDeduped.length === 0) {
if (debug)
console.info(
"Batch import: No new attachments in current turn; keeping existing state."
);
return { changed: false };
}
try {
const current = Array.isArray(state.attachments) ? state.attachments : [];
const currentOrigins = current.map(
(a) => a && typeof a.originAbs === "string" ? String(a.originAbs) : ""
).filter((p) => p.trim().length > 0).map(normalizeAbs);
const same = currentOrigins.length === normalizedSourceDeduped.length && currentOrigins.every((p, i) => p === normalizedSourceDeduped[i]);
if (same && current.length > 0) {
let allOk = true;
for (let i = 0; i < current.length; i++) {
const a = current[i];
const pv = a && typeof a.preview === "string" ? String(a.preview) : "";
if (i < Math.max(0, Math.floor(maxPreviewAttachments))) {
if (!pv) {
allOk = false;
break;
}
const pvAbs = path14.join(chatWd, pv);
const okPv = await pathExists2(pvAbs);
if (!okPv) {
allOk = false;
break;
}
}
}
if (allOk) {
try {
let anyMetaChanged = false;
const nextAttachments = current.map((a) => {
const originAbs = a && typeof a.originAbs === "string" ? String(a.originAbs) : "";
const key = originAbs ? normalizeAbs(originAbs) : "";
const nextTurnId = key ? normalizedTurnIdByAbs[key] : void 0;
if (typeof nextTurnId !== "number") return a;
if (a && a.turnId === nextTurnId) return a;
anyMetaChanged = true;
return { ...a, turnId: nextTurnId };
});
if (anyMetaChanged) {
state.attachments = nextAttachments;
await writeStateAtomic(chatWd, state);
if (debug)
console.info(
"Batch import: updated attachment turnId metadata (idempotent, no re-import)."
);
return { changed: false, metadataChanged: true };
}
} catch (e) {
if (debug)
console.warn(
"Batch import: turnId metadata update failed; continuing idempotent skip:",
e.message
);
}
if (debug)
console.info(
"Batch import: SSOT matches current state; skipping re-import (idempotent)."
);
return { changed: false };
}
}
} catch (e) {
if (debug)
console.warn(
"Batch import: idempotence check failed; continuing with import:",
e.message
);
}
const existingByOrigin = /* @__PURE__ */ new Map();
for (const a of state.attachments || []) {
if (a && typeof a.originAbs === "string") {
existingByOrigin.set(normalizeAbs(a.originAbs), a);
}
}
const usedAs = /* @__PURE__ */ new Set();
for (const a of existingByOrigin.values()) {
const av = a?.a;
if (typeof av === "number" && Number.isFinite(av) && av > 0) {
usedAs.add(av);
}
}
const ensureNextA = () => {
const current = state?.counters?.nextAttachmentA;
if (typeof current === "number" && Number.isFinite(current) && current > 0) {
return Math.floor(current);
}
const maxExisting = usedAs.size ? Math.max(...Array.from(usedAs)) : 0;
return maxExisting + 1;
};
let nextA = ensureNextA();
const allocateA = () => {
while (usedAs.has(nextA)) nextA++;
const a = nextA;
usedAs.add(a);
nextA++;
return a;
};
const imported = [];
for (let i = 0; i < normalizedSourceDeduped.length; i++) {
const abs = normalizedSourceDeduped[i];
if (!await pathExists2(abs)) {
if (debug)
console.warn(`Batch import: source not found, skipping: ${abs}`);
continue;
}
if (!isAllowedOriginalExt(abs)) {
if (debug)
console.warn(`Batch import: extension not allowed, skipping: ${abs}`);
continue;
}
const existing = existingByOrigin.get(normalizeAbs(abs));
if (existing) {
if (existing.preview) {
const previewAbs = path14.join(chatWd, existing.preview);
if (!await pathExists2(previewAbs)) {
if (debug)
console.info(
`Batch import: regenerating missing preview for a${existing.a}`
);
await generatePreview(abs, chatWd, previewOpts, {
customFilename: existing.preview,
force: true,
debug
});
}
}
const nextTurnId = normalizedTurnIdByAbs[normalizeAbs(abs)];
imported.push({
...existing,
// Keep stable `a`
a: typeof existing.a === "number" && Number.isFinite(existing.a) && existing.a > 0 ? existing.a : allocateA(),
turnId: typeof nextTurnId === "number" ? nextTurnId : existing.turnId
});
if (debug)
console.info(
`Batch import: reusing existing attachment as a${existing.a}: ${path14.basename(abs)}`
);
continue;
}
const origin = path14.basename(abs);
let previewName = void 0;
if (i < Math.max(0, Math.floor(maxPreviewAttachments))) {
previewName = await generatePreview(abs, chatWd, previewOpts, { debug }) ?? void 0;
}
let originalName = void 0;
const lmHome = findLMStudioHome();
const userFilesDir = path14.join(lmHome, "user-files");
if (abs.startsWith(userFilesDir)) {
const fileIdentifier = path14.basename(abs);
const resolvedName = await getOriginalFileName(fileIdentifier);
if (!resolvedName || !resolvedName.trim()) {
throw new Error(
`Missing originalName in LM Studio metadata for fileIdentifier='${fileIdentifier}' (abs='${abs}')`
);
}
originalName = resolvedName;
if (debug)
console.info(
`Resolved original filename: ${fileIdentifier} \u2192 ${originalName}`
);
} else {
originalName = path14.basename(abs);
}
imported.push({
origin,
originAbs: abs,
originalName,
turnId: normalizedTurnIdByAbs[normalizeAbs(abs)],
preview: previewName,
createdAt: localTimestamp2(),
a: allocateA()
// Stable, monotonically increasing id
});
if (debug)
console.info(
`Batch import: new attachment as a${imported[imported.length - 1].a}: ${path14.basename(abs)}`
);
}
if (imported.length === 0) {
if (debug) console.warn("Batch import: no valid attachments imported");
return { changed: false };
}
state.attachments = imported;
state.counters.nextAttachmentA = nextA;
state.lastEvent = { type: "attachment", at: localTimestamp2() };
await writeStateAtomic(chatWd, state);
if (debug)
console.info(
`Batch imported ${imported.length} attachment(s) from SSOT (replaced array)`
);
return { changed: true };
}
// src/media-promotion-core/promotion.ts
import fs13 from "fs";
import path15 from "path";
async function pathExists3(p) {
try {
await fs13.promises.access(p, fs13.constants.F_OK);
return true;
} catch {
return false;
}
}
async function recordGeneratedVariants(chatWd, state, discoveredOriginals, previewOpts, turnIdByOriginAbs, debug = false) {
if (!discoveredOriginals.length) return { changed: false };
const prepared = [];
for (const src of discoveredOriginals) {
const abs = src.startsWith("file://") ? fileUriToPath(src) : path15.isAbsolute(src) ? src : path15.join(chatWd, src);
if (debug)
console.info(
`[recordGeneratedVariants] Processing src: ${src} \u2192 abs: ${abs}`
);
if (!await pathExists3(abs)) {
if (debug)
console.warn(
`[recordGeneratedVariants] Original not found, skipping: ${abs}`
);
continue;
}
if (!isAllowedOriginalExt(abs)) {
if (debug)
console.warn(
`[recordGeneratedVariants] Extension not allowed, skipping: ${abs}`
);
continue;
}
const base = path15.basename(abs);
if (debug) console.info(`[recordGeneratedVariants] basename: ${base}`);
const vName = base;
const vAbs = path15.join(chatWd, vName);
if (!await pathExists3(vAbs)) {
if (debug)
console.warn(
`[recordGeneratedVariants] Original missing in chatWd, skipping: ${vAbs}`
);
continue;
}
const m = /^generated-image-(.+)-v(\d+)\.\w+$/i.exec(base);
const ts = m ? m[1] : "unknown";
const vNum = parseInt(m ? m[2] : "1", 10) || 1;
if (debug)
console.info(`[recordGeneratedVariants] Parsed: ts=${ts}, vNum=${vNum}`);
const previewCandidates = [`preview-generated-image-${ts}-v${vNum}.jpg`];
let foundPreview = null;
for (const cand of previewCandidates) {
const pAbs = path15.join(chatWd, cand);
if (debug)
console.info(
`[recordGeneratedVariants] Looking for preview in chatWd: ${pAbs}`
);
if (await pathExists3(pAbs)) {
foundPreview = cand;
break;
}
}
if (foundPreview) {
const maybeTurnId = (() => {
try {
const key = path15.resolve(abs);
const tid = turnIdByOriginAbs ? turnIdByOriginAbs[key] : void 0;
return typeof tid === "number" && Number.isFinite(tid) ? tid : void 0;
} catch {
return void 0;
}
})();
prepared.push({
filename: vName,
preview: foundPreview,
v: vNum,
ts,
turnId: maybeTurnId
});
} else if (debug) {
console.warn(
`[recordGeneratedVariants] Preview not found in chatWd, skipping variant v${vNum}`
);
}
}
if (!prepared.length) return { changed: false };
const toAppend = prepared.map((r) => ({
filename: r.filename,
preview: r.preview,
turnId: r.turnId,
v: r.v
// Use the v-number parsed from filename for stable identity
}));
const result = appendVariants(state, toAppend);
if (result.changed) {
state.lastEvent = { type: "variants", at: localTimestamp2() };
if (debug)
console.info(
"Recorded variants (imported from SSOT):",
result.records.map((x) => `v${x.v}: ${x.filename}`).join(", ")
);
await writeStateAtomic(chatWd, state);
}
return { changed: result.changed };
}
function buildPromotionItems(chatWd, state, {
labels = true,
onlyAttachmentNs,
onlyVariantVs,
onlyImageIs
} = {}) {
const items = [];
const maxAttachments = getAttachmentWindowSize();
const attachments = Array.isArray(state.attachments) ? state.attachments : [];
const sortedAttachments = [...attachments].sort(
(a, b) => (a.a ?? 0) - (b.a ?? 0)
);
const attachmentCap = Math.max(0, Math.floor(maxAttachments));
let cappedAttachments = attachmentCap > 0 ? sortedAttachments.slice(-attachmentCap) : [];
if (onlyAttachmentNs !== void 0) {
const allowedNs = new Set(onlyAttachmentNs);
cappedAttachments = cappedAttachments.filter((a) => allowedNs.has(a.a ?? 0));
}
for (const a of cappedAttachments) {
if (!a) continue;
const stableA = typeof a.a === "number" ? a.a : 0;
const abs = typeof a.originAbs === "string" && String(a.originAbs) ? String(a.originAbs) : "";
const pRel = typeof a.preview === "string" && String(a.preview) ? String(a.preview) : "";
const pAbs = pRel ? path15.join(chatWd, pRel) : "";
if (!abs) {
throw new Error(
`Attachment a${stableA} is missing originAbs (origin=${String(
a.origin
)})`
);
}
if (!pAbs) {
throw new Error(
`Attachment a${stableA} is missing preview (origin=${String(
a.origin
)}, originAbs=${String(a.originAbs)})`
);
}
if (labels && (!(a.originalName && typeof a.originalName === "string") || !String(a.originalName).trim())) {
throw new Error(
`Attachment a${stableA} is missing originalName (filename=${String(
a.filename
)}, origin=${String(a.origin)}, originAbs=${String(
a.originAbs
)})`
);
}
const label = labels ? `Attachment [a${stableA}] ${String(a.originalName)}` : void 0;
items.push({
abs,
previewAbs: pAbs,
label
});
}
const maxEverythingElse = getEverythingElseWindowSize();
if (maxEverythingElse > 0) {
const m = typeof state.lastCanvasNotation === "string" ? /^p(\d+)$/i.exec(String(state.lastCanvasNotation).trim()) : null;
const pNum = m ? parseInt(m[1], 10) : NaN;
if (Number.isFinite(pNum) && pNum > 0) {
const pics = Array.isArray(state.pictures) ? state.pictures : [];
const pic = pics.find(
(p) => typeof p?.p === "number" && p.p === pNum
);
if (pic && typeof pic.filename === "string" && typeof pic.preview === "string") {
items.push({
abs: path15.join(chatWd, pic.filename),
previewAbs: path15.join(chatWd, pic.preview),
label: labels ? `Canvas Picture [p${pNum}]` : void 0
});
}
}
}
const pool = [];
const variants = Array.isArray(state.variants) ? state.variants : [];
for (const v of variants) {
if (v.kind === "tool_result") continue;
if (!v.preview) continue;
pool.push({
kind: "variant",
key: `v${v.v}`,
index: v.v,
createdAt: v.createdAt ?? "",
filename: v.filename,
preview: v.preview
});
}
const images = Array.isArray(state.images) ? state.images : [];
for (const img of images) {
if (!img.preview) continue;
pool.push({
kind: "image",
key: `i${img.i}`,
index: img.i,
createdAt: img.createdAt ?? "",
filename: img.filename,
preview: img.preview
});
}
pool.sort((a, b) => {
const cmp = a.createdAt.localeCompare(b.createdAt);
if (cmp !== 0) return cmp;
if (a.kind !== b.kind) return a.kind === "variant" ? -1 : 1;
return a.index - b.index;
});
const everythingElseCap = Math.max(0, Math.floor(maxEverythingElse));
let windowItems = everythingElseCap > 0 ? pool.slice(-everythingElseCap) : [];
if (onlyVariantVs !== void 0 || onlyImageIs !== void 0) {
const allowedVs = new Set(onlyVariantVs ?? []);
const allowedIs = new Set(onlyImageIs ?? []);
windowItems = windowItems.filter((item) => {
if (item.kind === "variant") {
return onlyVariantVs === void 0 ? true : allowedVs.has(item.index);
}
return onlyImageIs === void 0 ? true : allowedIs.has(item.index);
});
}
for (const item of windowItems) {
const abs = path15.join(chatWd, item.filename);
const pAbs = path15.join(chatWd, item.preview);
const label = labels ? item.kind === "variant" ? `Variant [v${item.index}]` : `Image [i${item.index}]` : void 0;
items.push({
abs,
previewAbs: pAbs,
label
});
}
return items;
}
function getPromotableAttachmentNs(state) {
const maxAttachments = getAttachmentWindowSize();
const attachments = Array.isArray(state.attachments) ? state.attachments : [];
const sortedAttachments = [...attachments].sort(
(a, b) => (a.a ?? 0) - (b.a ?? 0)
);
const cap = Math.max(0, Math.floor(maxAttachments));
const cappedAttachments = cap > 0 ? sortedAttachments.slice(-cap) : [];
const ns = cappedAttachments.map((a) => typeof a.a === "number" ? a.a : 0).filter((n) => n > 0);
ns.sort((a, b) => a - b);
return ns;
}
function shouldPromoteAttachments(state) {
const currentNs = getPromotableAttachmentNs(state);
const lastNs = Array.isArray(state.lastPromotedAttachmentAs) ? [...state.lastPromotedAttachmentAs].sort((a, b) => a - b) : [];
if (currentNs.length !== lastNs.length) return true;
for (let i = 0; i < currentNs.length; i++) {
if (currentNs[i] !== lastNs[i]) return true;
}
return false;
}
function getPromotableEverythingElse(state) {
const maxEverythingElse = getEverythingElseWindowSize();
const pool = [];
const variants = Array.isArray(state.variants) ? state.variants : [];
for (const v of variants) {
if (v.kind === "tool_result") continue;
if (!v.preview) continue;
pool.push({
kind: "variant",
index: v.v,
createdAt: v.createdAt ?? ""
});
}
const images = Array.isArray(state.images) ? state.images : [];
for (const img of images) {
if (!img.preview) continue;
pool.push({
kind: "image",
index: img.i,
createdAt: img.createdAt ?? ""
});
}
pool.sort((a, b) => {
const cmp = a.createdAt.localeCompare(b.createdAt);
if (cmp !== 0) return cmp;
if (a.kind !== b.kind) return a.kind === "variant" ? -1 : 1;
return a.index - b.index;
});
const cap = Math.max(0, Math.floor(maxEverythingElse));
const windowItems = cap > 0 ? pool.slice(-cap) : [];
const variantVs = windowItems.filter((x) => x.kind === "variant").map((x) => x.index).sort((a, b) => a - b);
const imageIs = windowItems.filter((x) => x.kind === "image").map((x) => x.index).sort((a, b) => a - b);
return { variantVs, imageIs };
}
function getPromotableVariantVs(state) {
return getPromotableEverythingElse(state).variantVs;
}
function getPromotableImageIs(state) {
return getPromotableEverythingElse(state).imageIs;
}
function shouldPromoteEverythingElseIdempotent(state) {
const { variantVs, imageIs } = getPromotableEverythingElse(state);
const lastVs = Array.isArray(state.lastPromotedVariantVs) ? [...state.lastPromotedVariantVs].sort((a, b) => a - b) : [];
const lastIs = Array.isArray(state.lastPromotedImageIs) ? [...state.lastPromotedImageIs].sort((a, b) => a - b) : [];
if (variantVs.length !== lastVs.length) return true;
for (let i = 0; i < variantVs.length; i++) {
if (variantVs[i] !== lastVs[i]) return true;
}
if (imageIs.length !== lastIs.length) return true;
for (let i = 0; i < imageIs.length; i++) {
if (imageIs[i] !== lastIs[i]) return true;
}
return false;
}
function shouldPromoteVariantsIdempotent(state) {
return shouldPromoteEverythingElseIdempotent(state);
}
function fileUriToPath(uri) {
const p = decodeURIComponent(uri.replace(/^file:\/\//i, ""));
return p.startsWith("/") ? p : "/" + p;
}
// src/services/schemas.ts
import { z } from "zod";
// src/services/drawthingsLimits.ts
var drawthingsLimits = {
// Limits for RENDERED output (requested_effective)
min: 256,
minDim: 256,
// Minimum dimension per side
maxWidth: 1536,
// Maximum render width
maxHeight: 1536,
// Maximum render height (note: was 2048 in old docs)
// Limits for INPUT images (adjusted) - Canvas & Moodboard
targetSum: 1344,
// Sum constraint: width + height ≤ 1344px
align: 64,
// Dimensions must be multiples of 64
// Postprocessing when requested_raw exceeds render limits
upscaler: "4x_ultrasharp_f16.ckpt",
upscalerScaleFactor: 2
};
// src/services/schemas.ts
var TOOL_MIN_RENDER_DIM = drawthingsLimits.min;
var TOOL_MAX_WIDTH = drawthingsLimits.maxWidth;
var TOOL_MAX_HEIGHT = drawthingsLimits.maxHeight;
var TOOL_MAX_PREVIEW_W = drawthingsLimits.maxWidth;
function validateImageGenerationParams(params) {
const errors = [];
if (params.prompt !== void 0 && typeof params.prompt !== "string") {
errors.push("prompt must be a string");
}
if (params.negative_prompt !== void 0 && typeof params.negative_prompt !== "string") {
errors.push("negative_prompt must be a string");
}
if (params.width !== void 0 && (!Number.isInteger(params.width) || params.width <= 0)) {
errors.push("width must be a positive integer");
}
if (params.height !== void 0 && (!Number.isInteger(params.height) || params.height <= 0)) {
errors.push("height must be a positive integer");
}
if (params.steps !== void 0 && (!Number.isInteger(params.steps) || params.steps <= 0)) {
errors.push("steps must be a positive integer");
}
if (params.seed !== void 0 && !Number.isInteger(params.seed)) {
errors.push("seed must be an integer");
}
if (params.guidance_scale !== void 0 && (typeof params.guidance_scale !== "number" || params.guidance_scale < 0)) {
errors.push("guidance_scale must be a number >= 0");
}
if (params.model !== void 0 && typeof params.model !== "string") {
errors.push("model must be a string");
}
if (params.sampler !== void 0 && typeof params.sampler !== "string" && typeof params.sampler !== "number") {
errors.push("sampler must be a string or number");
}
if (params.random_string !== void 0 && typeof params.random_string !== "string") {
errors.push("random_string must be a string");
}
return { valid: errors.length === 0, errors };
}
function normalizeQualityToInt(q, def) {
let n = typeof q === "string" ? parseFloat(q) : typeof q === "number" ? q : def;
if (!Number.isFinite(n)) n = def;
if (n > 0 && n <= 1) n = n * 100;
n = Math.round(n);
if (n < 1) n = 1;
if (n > 100) n = 100;
return n;
}
function clampNumber(n, min, max) {
return Math.min(max, Math.max(min, n));
}
function formatZodError(err) {
return err.issues.map((i) => {
const path19 = i.path && i.path.length ? i.path.join(".") : "(root)";
return `${path19}: ${i.message}`;
}).join("; ");
}
var GenerateToolParamsSchemaBase = z.object({
prompt: z.string().optional(),
negative_prompt: z.string().optional(),
width: z.coerce.number().int().min(TOOL_MIN_RENDER_DIM, `width must be >= ${TOOL_MIN_RENDER_DIM}`).max(TOOL_MAX_WIDTH, `width must be <= ${TOOL_MAX_WIDTH}`).optional(),
height: z.coerce.number().int().min(TOOL_MIN_RENDER_DIM, `height must be >= ${TOOL_MIN_RENDER_DIM}`).max(TOOL_MAX_HEIGHT, `height must be <= ${TOOL_MAX_HEIGHT}`).optional(),
steps: z.coerce.number().int().min(1, "steps must be >= 1").max(50, "steps must be <= 50").optional(),
seed: z.coerce.number().int().optional(),
guidance_scale: z.coerce.number().min(0, "guidance_scale must be >= 0").max(50, "guidance_scale must be <= 50").optional(),
model: z.string().optional(),
sampler: z.union([z.string(), z.number()]).optional(),
numFrames: z.coerce.number().int().min(1, "numFrames must be >= 1").max(641, "numFrames must be <= 641").optional(),
random_string: z.string().optional(),
// preview controls (validation only; defaults applied by caller)
previewFormat: z.enum(["jpeg", "webp"], {
errorMap: () => ({ message: "previewFormat must be 'jpeg' or 'webp'" })
}).optional(),
previewMaxWidth: z.coerce.number().int().min(128).max(TOOL_MAX_PREVIEW_W).optional(),
previewMinWidth: z.coerce.number().int().min(128).max(TOOL_MAX_PREVIEW_W).optional(),
previewMaxBytes: z.coerce.number().int().min(2e3).max(2e5).optional(),
previewQuality: z.union([z.coerce.number(), z.string()]).transform((v) => normalizeQualityToInt(v, NaN)).optional(),
previewMinQuality: z.union([z.coerce.number(), z.string()]).transform((v) => normalizeQualityToInt(v, NaN)).optional(),
previewQualityStep: z.coerce.number().int().min(1).max(20).optional(),
previewScaleStep: z.coerce.number().min(0.5).max(0.98).optional(),
previewInChat: z.coerce.boolean().optional(),
alt: z.string().max(120).optional(),
// saving
saveOriginal: z.coerce.boolean().optional(),
saveDir: z.string().optional()
}).passthrough();
var GenerateToolParamsSchema = GenerateToolParamsSchemaBase.superRefine((d, ctx) => {
if (d.previewMinWidth !== void 0 && d.previewMaxWidth !== void 0 && d.previewMinWidth > d.previewMaxWidth) {
ctx.addIssue({
code: "custom",
path: ["previewMinWidth"],
message: "previewMinWidth must be <= previewMaxWidth"
});
}
if (d.previewMinQuality !== void 0 && d.previewQuality !== void 0 && d.previewMinQuality > d.previewQuality) {
ctx.addIssue({
code: "custom",
path: ["previewMinQuality"],
message: "previewMinQuality must be <= previewQuality"
});
}
});
var multiIndexSchema = z.union([
z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => parseInt(x.trim(), 10)).filter((n) => Number.isInteger(n) && n >= 1)
),
z.coerce.number().int().min(1).transform((n) => [n]),
z.array(z.coerce.number().int().min(1))
]).optional();
function normalizeSourceToken(raw) {
let s = String(raw ?? "").trim();
if (!s) return "";
s = s.replace(/^[\[{(]+/, "").replace(/[\]})]+$/, "");
s = s.replace(/^['"`]+|['"`]+$/g, "");
s = s.replace(/[;:.!?]+$/, "");
s = s.trim();
if (/^a$/i.test(s)) return "a1";
if (/^v$/i.test(s)) return "v1";
if (/^p$/i.test(s)) return "p1";
return s;
}
var SourceNotation = z.preprocess(
(v) => typeof v === "string" ? normalizeSourceToken(v) : v,
z.string().trim().regex(
/^([avp]|[avp]?[1-9]\d*)$/i,
"Source notation: 'a1', 'v2', 'p1', or digit when unambiguous"
)
);
var SourceNotationList = z.union([
// String form: split by comma/space and validate each part
z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => normalizeSourceToken(x)).filter((x) => x.length > 0)
),
// Array form: validate each element
z.array(SourceNotation)
]).refine(
(arr) => arr.every((x) => /^([avp]|[avp]?[1-9]\d*)$/i.test(String(x))),
"moodboard contains invalid source notation(s)"
);
var GenerateToolParamsShapeMinimal = {
prompt: z.string().optional().describe(
"Image description (mode: 'text2image') OR description of desired changes (mode: 'image2image')."
),
width: z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(TOOL_MAX_WIDTH).optional().describe(`Width in pixels (max ${TOOL_MAX_WIDTH}). Has sensible default.`),
height: z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(TOOL_MAX_HEIGHT).optional().describe(`Height in pixels (max ${TOOL_MAX_HEIGHT}). Has sensible default.`),
imageFormat: z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe("Aspect ratio shorthand. Override if context suggests. '16:9' yields 1024\xD7576 (video-optimized)."),
quality: z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
// model preset selection — default 'auto' uses built-in settings
model: z.enum(["auto", "z-image", "qwen-image", "flux", "ltx", "custom"]).optional().describe("Model preset. 'ltx' selects LTX-2 for video generation. Default 'auto' selects best model for the mode."),
// number of images to generate in one call
variants: z.coerce.number().int().min(1).max(4).optional().describe("Number of images (1-4). Default is 1."),
// number of video frames
numFrames: z.coerce.number().int().min(1, "numFrames must be >= 1").max(641, "numFrames must be <= 641").optional().describe("Number of video frames. Must be a multiple of 32 (or multiple of 32 + 1). Default: 1 (image). Silently ignored for non-video modes."),
// image2image controls (only allow selecting a prior variant)
mode: z.enum(["text2image", "image2image", "edit", "text2video", "image2video"]).optional().describe("Generation mode. 'text2video'/'image2video' require a video-capable model (e.g. 'ltx'). Required when sources exist."),
// Primary source for image2image/edit
canvas: z.union([
SourceNotation,
z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Primary source image. Notation: 'a1', 'v2', 'p1'. Shorthand: 'a'\u2192'a1', 'v'\u2192'v1', 'p'\u2192'p1'. Digit-only allowed only when unambiguous. Tolerates single-item array input."
),
// Additional style references for image2image/edit modes (gRPC only for image2image)
moodboard: SourceNotationList.optional().describe(
"Additional style references for image2image/edit modes. Array of source notations (same format as canvas). Note: moodboard for image2image requires gRPC transport."
)
};
var GenerateToolParamsSchemaMinimalStrict = z.object(GenerateToolParamsShapeMinimal).strict();
// src/services/fluxParamsDrawThings.ts
var fluxOverlays = {
txt2img: {
model: "flux_2_klein_9b_kv_q6p.ckpt",
guidance_scale: 1,
sampler: "DDIM Trailing",
steps: 6,
shift: 3,
sharpness: 0,
strength: 1,
mask_blur: 2.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: []
},
img2img: {
model: "flux_2_klein_9b_kv_q6p.ckpt",
guidance_scale: 1,
sampler: "DDIM Trailing",
steps: 6,
shift: 3,
sharpness: 0,
strength: 0.925,
mask_blur: 2.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: []
},
edit: {
model: "flux_2_klein_9b_kv_q6p.ckpt",
guidance_scale: 1,
sampler: "DDIM Trailing",
steps: 12,
shift: 3,
sharpness: 0,
strength: 1,
mask_blur: 2.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: []
}
};
// src/services/qwenImageParamsDrawThings.ts
var qwenImageOverlays = {
txt2img: {
model: "qwen_image_2512_bf16_q6p.ckpt",
negative_prompt: "\u4F4E\u5206\u8FA8\u7387\uFF0C\u4F4E\u753B\u8D28\uFF0C\u80A2\u4F53\u7578\u5F62\uFF0C\u624B\u6307\u7578\u5F62\uFF0C\u753B\u9762\u8FC7\u9971\u548C\uFF0C\u8721\u50CF\u611F\uFF0C\u4EBA\u8138\u65E0\u7EC6\u8282\uFF0C\u8FC7\u5EA6\u5149\u6ED1\uFF0C\u753B\u9762\u5177\u6709AI\u611F\u3002\u6784\u56FE\u6DF7\u4E71\u3002\u6587\u5B57\u6A21\u7CCA\uFF0C\u626D\u66F2\u3002",
guidance_scale: 1,
sampler: "UniPC Trailing",
steps: 10,
shift: 3,
sharpness: 0,
strength: 1,
mask_blur: 1.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: [
{
mode: "all",
file: "qwen_image_2512_lightning_4_step_v1.0_lora_f16.ckpt",
weight: 1
}
]
},
img2img: {
model: "qwen_image_edit_2511_q6p.ckpt",
guidance_scale: 1,
sampler: "UniPC Trailing",
steps: 8,
shift: 3,
sharpness: 0,
strength: 0.9,
mask_blur: 1.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: [
{
mode: "all",
file: "qwen_image_edit_2511_lightning_4_step_v1.0_lora_f16.ckpt",
weight: 1
}
]
},
edit: {
model: "qwen_image_edit_2511_q6p.ckpt",
guidance_scale: 1,
sampler: "UniPC Trailing",
steps: 8,
shift: 3,
sharpness: 0,
strength: 1,
mask_blur: 1.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: [
{
mode: "all",
file: "qwen_image_edit_2511_lightning_4_step_v1.0_lora_f16.ckpt",
weight: 1
}
]
}
};
// src/services/zImageParamsDrawThings.ts
var zImageOverlays = {
txt2img: {
model: "z_image_turbo_1.0_q6p.ckpt",
guidance_scale: 1,
sampler: "UniPC Trailing",
steps: 8,
shift: 3,
sharpness: 0,
strength: 1,
mask_blur: 1.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: []
},
img2img: {
model: "z_image_turbo_1.0_q6p.ckpt",
guidance_scale: 1,
sampler: "UniPC Trailing",
steps: 8,
shift: 3,
sharpness: 0,
strength: 0.925,
mask_blur: 1.5,
mask_blur_outset: 0,
refiner_model: null,
controls: [],
loras: []
}
};
// src/services/defaultParamsDrawThingsEdit.ts
var defaultParamsImg2Img = {
aesthetic_score: 6.5,
batch_count: 1,
batch_size: 1,
causal_inference: 0,
causal_inference_pad: 0,
cfg_zero_init_steps: 1,
cfg_zero_star: true,
clip_l_text: "80s poster, high quality, best quality, detailed, professional photograph, award-winning, trending on artstation",
clip_skip: 1,
clip_weight: 1,
controls: [],
crop_left: 0,
crop_top: 0,
// decoding_tile_height: 1024,
// decoding_tile_overlap: 128,
// decoding_tile_width: 1024,
// diffusion_tile_height: 640,
// diffusion_tile_overlap: 128,
// diffusion_tile_width: 640,
fps: 25,
guidance_embed: 1,
guidance_scale: 1,
guiding_frame_noise: 0.019999999552965164,
// Ensure size keys exist for filtering; service overwrites with actual PNG size
width: 768,
height: 1024,
hires_fix: false,
hires_fix_height: 512,
hires_fix_strength: 0.4000000059604645,
// hires_fix_width: 384,
image_guidance: 1.5,
image_prior_steps: 5,
loras: [
{
mode: "all",
file: "qwen_image_edit_2509_lightning_4_step_v1.0_lora_f16.ckpt",
weight: 1
}
],
mask_blur: 1.5,
mask_blur_outset: 0,
model: "qwen_image_edit_2509_q6p.ckpt",
motion_scale: 127,
negative_aesthetic_score: 0,
// negative_original_height: 512,
// negative_original_width: 512,
negative_prompt: "",
negative_prompt_for_image_prior: true,
num_frames: 1,
open_clip_g_text: "Could this be it, or not, I am not sure really.",
// original_height: 512,
// original_width: 512,
preserve_original_after_inpaint: true,
prompt: "Transfer image style from photograph to manga.",
refiner_model: null,
refiner_start: 0.4000000059604645,
resolution_dependent_shift: true,
sampler: "UniPC Trailing",
seed: 175308301,
seed_mode: "Scale Alike",
separate_clip_l: false,
separate_open_clip_g: false,
separate_t5: false,
sharpness: 0,
shift: 3,
speed_up_with_guidance_embed: true,
stage_2_guidance: 1,
stage_2_shift: 1,
start_frame_guidance: 1.2000000476837158,
steps: 8,
stochastic_sampling_gamma: 0.3,
strength: 1,
t5_text: "80s poster, high quality, best quality, detailed, professional photograph, award-winning, trending on artstation",
t5_text_encoder_decoding: true,
// target_height: 512,
// target_width: 512,
tea_cache: false,
tea_cache_end: 100,
tea_cache_max_skip_steps: 3,
tea_cache_start: 5,
tea_cache_threshold: 0.20000000298023224,
tiled_decoding: false,
tiled_diffusion: false,
upscaler: null,
upscaler_scale: 0,
zero_negative_prompt: false
};
// src/services/defaultParamsDrawThingsImg2Img.ts
var defaultParamsImg2Img2 = {
aesthetic_score: 6.5,
batch_count: 1,
batch_size: 1,
causal_inference: 0,
causal_inference_pad: 0,
cfg_zero_init_steps: 1,
cfg_zero_star: true,
clip_l_text: "80s poster, high quality, best quality, detailed, professional photograph, award-winning, trending on artstation",
clip_skip: 1,
clip_weight: 1,
controls: [],
crop_left: 0,
crop_top: 0,
// decoding_tile_height: 1024,
// decoding_tile_overlap: 128,
// decoding_tile_width: 1024,
// diffusion_tile_height: 640,
// diffusion_tile_overlap: 128,
// diffusion_tile_width: 640,
fps: 25,
guidance_embed: 1,
guidance_scale: 1,
guiding_frame_noise: 0.019999999552965164,
// Ensure size keys exist for filtering; service overwrites with actual PNG size
width: 768,
height: 1024,
hires_fix: false,
hires_fix_height: 512,
hires_fix_strength: 0.4000000059604645,
// hires_fix_width: 384,
image_guidance: 1.5,
image_prior_steps: 5,
loras: [],
mask_blur: 1.5,
mask_blur_outset: 0,
model: "qwen_image_edit_2509_q6p.ckpt",
motion_scale: 127,
negative_aesthetic_score: 0,
// negative_original_height: 512,
// negative_original_width: 512,
negative_prompt: "",
negative_prompt_for_image_prior: true,
num_frames: 1,
open_clip_g_text: "Could this be it, or not, I am not sure really.",
// original_height: 512,
// original_width: 512,
preserve_original_after_inpaint: true,
prompt: "",
refiner_model: null,
refiner_start: 0.4000000059604645,
resolution_dependent_shift: true,
sampler: "UniPC Trailing",
seed: 175308301,
seed_mode: "Scale Alike",
separate_clip_l: false,
separate_open_clip_g: false,
separate_t5: false,
sharpness: 0,
shift: 3,
speed_up_with_guidance_embed: true,
stage_2_guidance: 1,
stage_2_shift: 1,
start_frame_guidance: 1.2000000476837158,
steps: 6,
stochastic_sampling_gamma: 0.3,
strength: 0.9,
t5_text: "80s poster, high quality, best quality, detailed, professional photograph, award-winning, trending on artstation",
t5_text_encoder_decoding: true,
// target_height: 512,
// target_width: 512,
tea_cache: false,
tea_cache_end: 100,
tea_cache_max_skip_steps: 3,
tea_cache_start: 5,
tea_cache_threshold: 0.20000000298023224,
tiled_decoding: false,
tiled_diffusion: false,
upscaler: null,
upscaler_scale: 0,
zero_negative_prompt: false
};
// src/services/defaultParamsDrawThingsTxt2Img.ts
var defaultParams = {
aesthetic_score: 6.5,
batch_count: 1,
batch_size: 1,
causal_inference: 0,
causal_inference_pad: 0,
cfg_zero_init_steps: 1,
cfg_zero_star: true,
clip_l_text: "80s poster, high quality, best quality, detailed, professional photograph, award-winning, trending on artstation",
clip_skip: 1,
clip_weight: 1,
controls: [],
crop_left: 0,
crop_top: 0,
// decoding_tile_height: 1024,
// decoding_tile_overlap: 128,
// decoding_tile_width: 1024,
// diffusion_tile_height: 640,
// diffusion_tile_overlap: 128,
// diffusion_tile_width: 640,
fps: 25,
guidance_embed: 1,
guidance_scale: 1,
guiding_frame_noise: 0.019999999552965164,
height: 1024,
hires_fix: false,
hires_fix_height: 512,
hires_fix_strength: 0.4000000059604645,
hires_fix_width: 384,
image_guidance: 1.5,
image_prior_steps: 5,
loras: [],
mask_blur: 1.5,
mask_blur_outset: 0,
model: "z_image_turbo_1.0_q6p.ckpt",
motion_scale: 127,
negative_aesthetic_score: 0,
// negative_original_height: 512,
// negative_original_width: 512,
negative_prompt: "",
negative_prompt_for_image_prior: true,
num_frames: 1,
open_clip_g_text: "Could this be it, or not, I am not sure really.",
// original_height: 768,
// original_width: 576,
preserve_original_after_inpaint: true,
prompt: "3d fluffy llama, closeup cute and adorable, cute big circular reflective eyes, long fuzzy fur, pixar render, unreal engine cinematic smooth, intricate detail, cinematic",
refiner_model: null,
refiner_start: 0.4000000059604645,
resolution_dependent_shift: true,
sampler: "UniPC Trailing",
seed: 175308301,
seed_mode: "Scale Alike",
separate_clip_l: false,
separate_open_clip_g: false,
separate_t5: false,
sharpness: 0,
shift: 3,
speed_up_with_guidance_embed: true,
stage_2_guidance: 1,
stage_2_shift: 1,
start_frame_guidance: 1.2000000476837158,
steps: 8,
stochastic_sampling_gamma: 0.3,
strength: 1,
t5_text: "80s poster, high quality, best quality, detailed, professional photograph, award-winning, trending on artstation",
t5_text_encoder_decoding: true,
// target_height: 768,
// target_width: 576,
tea_cache: false,
tea_cache_end: 100,
tea_cache_max_skip_steps: 3,
tea_cache_start: 5,
tea_cache_threshold: 0.20000000298023224,
tiled_decoding: true,
tiled_diffusion: false,
upscaler: null,
upscaler_scale: 0,
width: 768,
zero_negative_prompt: false
};
var generateRuntimeDefaults = {
saveOriginal: true,
previewInChat: true
};
var engineConnectionDefaults = {
transport: "auto",
http: {
baseUrl: "http://127.0.0.1:7860",
host: "127.0.0.1",
port: 7860
},
grpc: {
target: "127.0.0.1:7859",
service: "ImageGenerationService",
compression: "identity",
acceptEncoding: "",
protoPath: void 0
},
sharedSecret: null
};
function getEngineConnectionDefaults(overrides) {
const host = overrides?.host || engineConnectionDefaults.http.host;
const httpPort = overrides?.httpPort || engineConnectionDefaults.http.port;
const grpcPort = overrides?.grpcPort || parseInt(engineConnectionDefaults.grpc.target.split(":")[1], 10);
return {
...engineConnectionDefaults,
http: {
...engineConnectionDefaults.http,
baseUrl: `http://${host}:${httpPort}`,
host,
port: httpPort
},
grpc: {
...engineConnectionDefaults.grpc,
target: `${host}:${grpcPort}`
}
};
}
// src/services/defaultParamsPreviewAnalyse.ts
var previewDefaults = {
previewFormat: "jpeg",
previewMaxWidth: 1024,
previewQuality: 85
};
// src/services/mediaReconcile/reconcile.ts
import path16 from "path";
// src/services/mediaReconcile/types.ts
var DEFAULT_RECONCILE_OPTIONS = {
attachment: {
strategy: "replace",
generatePreviews: true,
maxPreviews: 5
// Only top 5 attachments get previews (for Vision)
},
variant: {
strategy: "merge",
generatePreviews: false
// Previews created by generate_image tool
},
picture: {
strategy: "merge",
generatePreviews: true
// Download and create preview from URL
},
image: {
strategy: "merge",
generatePreviews: false
// Previews created by generating tool
}
};
// src/services/mediaReconcile/reconcile.ts
function getIdentifierKey(mediaType) {
switch (mediaType) {
case "attachment":
return "originAbs";
// Absolute path to original file
case "variant":
return "filename";
// Filename in chatWd
case "picture":
return "sourceUrl";
// Original URL
case "image":
return "filename";
// Filename in chatWd
default:
return "filename";
}
}
function getIndexKey(mediaType) {
const config = MEDIA_SOURCE_REGISTRY[mediaType];
return config?.indexPrefix ?? "n";
}
function normalizeIdentifier(id, mediaType) {
if (mediaType === "attachment") {
try {
return path16.resolve(id);
} catch {
return id;
}
}
return id;
}
function candidateToRecord(candidate, index, mediaType) {
const indexKey = getIndexKey(mediaType);
const base = {
[indexKey]: index,
turnId: candidate.turnId
};
switch (mediaType) {
case "attachment":
return {
...base,
originAbs: candidate.identifier
// filename and preview will be set during import
};
case "variant":
return {
...base,
filename: candidate.identifier,
// preview expected to exist alongside original
preview: `preview-${path16.basename(candidate.identifier, ".png")}.jpg`
};
case "picture":
return {
sourceUrl: candidate.identifier,
title: candidate.title,
sourceTool: candidate.sourceTool,
pluginId: candidate.pluginId,
turnId: candidate.turnId,
[indexKey]: index
};
case "image": {
const sourceTool = candidate.pluginId && candidate.sourceTool ? `${candidate.pluginId}, ${candidate.sourceTool}` : candidate.sourceTool;
return {
filename: candidate.identifier,
preview: `preview-${path16.basename(
candidate.identifier,
path16.extname(candidate.identifier)
)}.jpg`,
sourceTool,
// kind: undefined - set by caller if needed
turnId: candidate.turnId,
// createdAt: undefined - set by appendImages if missing
[indexKey]: index
};
}
default:
return base;
}
}
async function reconcileMedia(chatWd, stateArray, mediaType, candidates, options) {
const { strategy, debug } = options;
const identifierKey = getIdentifierKey(mediaType);
const indexKey = getIndexKey(mediaType);
if (debug) {
console.info(
`[reconcileMedia] ${mediaType}: ${candidates.length} candidates, strategy=${strategy}`
);
}
if (strategy === "replace") {
return reconcileReplace(
chatWd,
stateArray,
mediaType,
candidates,
identifierKey,
indexKey,
options
);
} else {
return reconcileMerge(
chatWd,
stateArray,
mediaType,
candidates,
identifierKey,
indexKey,
options
);
}
}
async function reconcileReplace(chatWd, stateArray, mediaType, candidates, identifierKey, indexKey, options) {
const { debug } = options;
if (candidates.length === 0) {
if (debug) {
console.info(
`[reconcileMedia] ${mediaType}: No candidates, keeping existing state`
);
}
return {
result: {
added: 0,
removed: 0,
unchanged: stateArray.length,
stateChanged: false
},
newArray: stateArray
};
}
const existingByIdentifier = /* @__PURE__ */ new Map();
for (const record of stateArray) {
const id = record[identifierKey];
if (typeof id === "string") {
existingByIdentifier.set(normalizeIdentifier(id, mediaType), record);
}
}
const newArray = [];
let added = 0;
let unchanged = 0;
for (let i = 0; i < candidates.length; i++) {
const candidate = candidates[i];
const normalizedId = normalizeIdentifier(candidate.identifier, mediaType);
const existing = existingByIdentifier.get(normalizedId);
if (existing) {
const updated = { ...existing };
updated[indexKey] = i + 1;
if (candidate.turnId !== void 0) {
updated.turnId = candidate.turnId;
}
newArray.push(updated);
unchanged++;
} else {
const record = candidateToRecord(candidate, i + 1, mediaType);
newArray.push(record);
added++;
}
}
const removed = stateArray.length - unchanged;
const stateChanged = added > 0 || removed > 0;
if (debug) {
console.info(
`[reconcileMedia] ${mediaType}: added=${added}, removed=${removed}, unchanged=${unchanged}`
);
}
return {
result: { added, removed, unchanged, stateChanged },
newArray
};
}
async function reconcileMerge(chatWd, stateArray, mediaType, candidates, identifierKey, indexKey, options) {
const { debug } = options;
const candidateIds = /* @__PURE__ */ new Set();
for (const c of candidates) {
candidateIds.add(normalizeIdentifier(c.identifier, mediaType));
}
const existingByIdentifier = /* @__PURE__ */ new Map();
for (const record of stateArray) {
const id = record[identifierKey];
if (typeof id === "string") {
existingByIdentifier.set(normalizeIdentifier(id, mediaType), record);
}
}
const newArray = [];
let removed = 0;
let unchanged = 0;
for (const record of stateArray) {
const id = record[identifierKey];
if (typeof id !== "string") {
removed++;
continue;
}
const normalizedId = normalizeIdentifier(id, mediaType);
if (candidateIds.has(normalizedId)) {
newArray.push(record);
unchanged++;
} else {
if (debug) {
console.info(
`[reconcileMedia] ${mediaType}: Removing deleted item: ${id}`
);
}
removed++;
}
}
let added = 0;
let nextIndex = Math.max(
0,
...newArray.map((r) => {
const idx = r[indexKey];
return typeof idx === "number" ? idx : 0;
})
) + 1;
for (const candidate of candidates) {
const normalizedId = normalizeIdentifier(candidate.identifier, mediaType);
if (!existingByIdentifier.has(normalizedId)) {
const record = candidateToRecord(candidate, nextIndex, mediaType);
newArray.push(record);
added++;
nextIndex++;
}
}
const stateChanged = added > 0 || removed > 0;
if (debug) {
console.info(
`[reconcileMedia] ${mediaType}: added=${added}, removed=${removed}, unchanged=${unchanged}`
);
}
return {
result: { added, removed, unchanged, stateChanged },
newArray
};
}
function getStateArrayKey(mediaType) {
switch (mediaType) {
case "attachment":
return "attachments";
case "variant":
return "variants";
case "picture":
return "pictures";
case "image":
return "images";
default:
return `${mediaType}s`;
}
}
// src/services/mediaScanner/conversationParser.ts
import fs14 from "fs";
import path17 from "path";
import os4 from "os";
function findConversationPath(chatWd) {
const chatId = path17.basename(chatWd);
const lmHome = findLMStudioHome() || path17.join(os4.homedir(), ".lmstudio");
const conversationsDir = path17.join(lmHome, "conversations");
const candidates = [
path17.join(conversationsDir, `${chatId}.conversation.json`),
path17.join(chatWd, ".conversation.json"),
path17.join(chatWd, "conversation.json")
];
for (const p of candidates) {
try {
fs14.accessSync(p, fs14.constants.F_OK);
return p;
} catch {
}
}
return void 0;
}
async function readConversation(chatWd) {
const p = findConversationPath(chatWd);
if (!p) return void 0;
try {
const raw = await fs14.promises.readFile(p, "utf-8");
const json = JSON.parse(raw);
return { json, path: p };
} catch {
return void 0;
}
}
function findMessagesArray(json) {
if (!json || typeof json !== "object") return void 0;
const obj = json;
const candidates = [
obj.messages,
obj.conversation?.messages,
obj.chat?.messages,
obj.history,
obj.turns,
obj.items
];
for (const arr of candidates) {
if (Array.isArray(arr) && arr.length > 0) {
return arr;
}
}
return void 0;
}
function resolveMessageVersion(raw) {
if (!raw || typeof raw !== "object") return raw;
const obj = raw;
const versions = obj.versions;
if (!Array.isArray(versions) || versions.length === 0) {
return raw;
}
const selRaw = obj.currentlySelected;
const sel = typeof selRaw === "number" && Number.isFinite(selRaw) ? selRaw : 0;
if (sel >= 0 && sel < versions.length) {
return versions[sel];
}
return versions[versions.length - 1];
}
function getMessageRole(msg) {
if (!msg || typeof msg !== "object") return "unknown";
const obj = msg;
if (obj.type === "contentBlock") {
const arr = obj.content;
if (Array.isArray(arr)) {
for (const it of arr) {
if (!it || typeof it !== "object") continue;
const t = it.type;
if (t === "toolCallRequest" || t === "toolCallResult") {
return "tool";
}
}
}
}
const role = obj.role ?? obj.author ?? obj.sender;
if (typeof role === "string") return role.toLowerCase();
const type = obj.type ?? obj.messageType;
if (typeof type === "string") {
if (type === "user" || type === "user_message") return "user";
if (type === "assistant" || type === "assistant_message")
return "assistant";
if (type === "system") return "system";
if (type === "tool" || type === "tool_result") return "tool";
}
return "unknown";
}
function parseMessages(json) {
const messages = findMessagesArray(json);
if (!messages) return [];
const result = [];
for (let i = 0; i < messages.length; i++) {
const raw = messages[i];
const resolved = resolveMessageVersion(raw);
const role = getMessageRole(resolved);
result.push({
index: i,
turnId: i + 1,
// 1-based
role,
content: resolved,
raw
});
}
return result;
}
function extractToolCalls(msg) {
const result = [];
const content = msg.content;
if (!content || typeof content !== "object") return result;
const obj = content;
const toolCallResults = obj.toolCallResults;
if (Array.isArray(toolCallResults)) {
for (const tcr of toolCallResults) {
if (!tcr || typeof tcr !== "object") continue;
const tc = tcr;
const toolCallId = typeof tc.toolCallId === "string" ? tc.toolCallId : void 0;
const toolName = typeof tc.name === "string" ? tc.name : "";
const pluginId = typeof tc.pluginIdentifier === "string" ? tc.pluginIdentifier : void 0;
const resultContent = tc.result ?? tc.content;
if (toolName) {
result.push({
toolCallId,
toolName,
pluginId,
content: resultContent,
turnId: msg.turnId
});
}
}
}
if (Array.isArray(obj.steps)) {
const requestMetaByKey = /* @__PURE__ */ new Map();
const remember = (key, meta) => {
const k = typeof key === "string" || typeof key === "number" ? String(key) : "";
if (!k) return;
const prev = requestMetaByKey.get(k) ?? {};
requestMetaByKey.set(k, {
pluginId: meta.pluginId ?? prev.pluginId,
toolName: meta.toolName ?? prev.toolName
});
};
for (const step of obj.steps) {
if (!step || typeof step !== "object") continue;
const st = step;
if (st.type === "contentBlock" && Array.isArray(st.content)) {
for (const bi of st.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
for (const step of obj.steps) {
if (!step || typeof step !== "object") continue;
const st = step;
if (st.type === "contentBlock" && Array.isArray(st.content)) {
for (const bi of st.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallResult") continue;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
const toolCallId = typeof callId === "string" || typeof callId === "number" ? String(callId) : typeof reqId === "string" || typeof reqId === "number" ? String(reqId) : void 0;
const fallbackMeta = typeof callId === "string" || typeof callId === "number" ? requestMetaByKey.get(String(callId)) : void 0;
const fallbackMeta2 = fallbackMeta ?? (typeof reqId === "string" || typeof reqId === "number" ? requestMetaByKey.get(String(reqId)) : void 0);
const toolName = typeof bo.name === "string" ? bo.name : fallbackMeta2?.toolName ?? "";
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : fallbackMeta2?.pluginId;
const resultContent = bo.result ?? bo.content ?? bo.output;
if (toolName || toolCallId) {
result.push({
toolCallId,
toolName,
pluginId,
content: resultContent,
turnId: msg.turnId
});
}
}
}
}
}
const contentArray = obj.content;
if (Array.isArray(contentArray)) {
const requestMetaByKey = /* @__PURE__ */ new Map();
const remember = (key, meta) => {
const k = typeof key === "string" || typeof key === "number" ? String(key) : "";
if (!k) return;
const prev = requestMetaByKey.get(k) ?? {};
requestMetaByKey.set(k, {
pluginId: meta.pluginId ?? prev.pluginId,
toolName: meta.toolName ?? prev.toolName
});
};
for (const item of contentArray) {
if (!item || typeof item !== "object") continue;
const it = item;
if (it.type === "contentBlock" && Array.isArray(it.content)) {
const blockItems = it.content;
for (const bi of blockItems) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
for (const item of contentArray) {
if (!item || typeof item !== "object") continue;
const it = item;
if (it.type === "contentBlock" && Array.isArray(it.content)) {
const blockItems = it.content;
for (const bi of blockItems) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallResult") continue;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
const toolCallId = typeof callId === "string" || typeof callId === "number" ? String(callId) : typeof reqId === "string" || typeof reqId === "number" ? String(reqId) : void 0;
const fallbackMeta = typeof callId === "string" || typeof callId === "number" ? requestMetaByKey.get(String(callId)) : void 0;
const fallbackMeta2 = fallbackMeta ?? (typeof reqId === "string" || typeof reqId === "number" ? requestMetaByKey.get(String(reqId)) : void 0);
const toolName = typeof bo.name === "string" ? bo.name : fallbackMeta2?.toolName ?? "";
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : fallbackMeta2?.pluginId;
const resultContent = bo.result ?? bo.content ?? bo.output;
if (toolName || toolCallId) {
result.push({
toolCallId,
toolName,
pluginId,
content: resultContent,
turnId: msg.turnId
});
}
}
continue;
}
if (it.type === "tool_result" || it.type === "tool-result") {
const toolCallId = typeof it.tool_call_id === "string" ? it.tool_call_id : typeof it.toolCallId === "string" ? it.toolCallId : void 0;
const toolName = typeof it.name === "string" ? it.name : typeof it.tool_name === "string" ? it.tool_name : "";
const pluginId = typeof it.pluginIdentifier === "string" ? it.pluginIdentifier : typeof it.plugin === "string" ? it.plugin : void 0;
const resultContent = it.result ?? it.content ?? it.output;
if (toolName || toolCallId) {
result.push({
toolCallId,
toolName,
pluginId,
content: resultContent,
turnId: msg.turnId
});
}
}
}
}
if (obj.type === "contentBlock" && Array.isArray(obj.content)) {
const blockMsg = { type: "contentBlock", content: obj.content };
const fake = { content: [blockMsg] };
const nested = extractToolCalls({ ...msg, content: fake });
result.push(...nested);
}
const toolResults = obj.tool_results ?? obj.toolResults;
if (Array.isArray(toolResults)) {
for (const tr of toolResults) {
if (!tr || typeof tr !== "object") continue;
const t = tr;
const toolCallId = typeof t.tool_call_id === "string" ? t.tool_call_id : typeof t.id === "string" ? t.id : void 0;
const toolName = typeof t.name === "string" ? t.name : typeof t.tool === "string" ? t.tool : "";
const pluginId = typeof t.pluginIdentifier === "string" ? t.pluginIdentifier : void 0;
const resultContent = t.result ?? t.content ?? t.output;
if (toolName || toolCallId) {
result.push({
toolCallId,
toolName,
pluginId,
content: resultContent,
turnId: msg.turnId
});
}
}
}
return result;
}
function extractUserAttachments(msg, lmHome) {
const result = [];
const home = lmHome || findLMStudioHome() || path17.join(os4.homedir(), ".lmstudio");
const userFilesDir = path17.join(home, "user-files");
const content = msg.content;
if (!content || typeof content !== "object") return result;
const collectFromObject = (obj) => {
if (!obj || typeof obj !== "object") return;
if (Array.isArray(obj)) {
for (const item of obj) {
collectFromObject(item);
}
return;
}
const o = obj;
const fileId = o.fileIdentifier ?? o.file_identifier ?? o.identifier;
const fileType = o.fileType ?? o.file_type ?? o.type;
if (typeof fileId === "string" && fileId.trim()) {
if (fileType === "image" || /\.(png|jpg|jpeg|webp|gif|bmp|tiff?)$/i.test(fileId)) {
result.push(path17.join(userFilesDir, fileId));
}
}
const filePath = o.path ?? o.filePath ?? o.file_path ?? o.uri ?? o.url;
if (typeof filePath === "string" && filePath.trim()) {
let resolved = filePath;
if (filePath.startsWith("file://")) {
try {
resolved = decodeURIComponent(filePath.replace(/^file:\/\//, ""));
} catch {
resolved = filePath.replace(/^file:\/\//, "");
}
}
if (/\.(png|jpg|jpeg|webp|gif|bmp|tiff?)$/i.test(resolved)) {
result.push(resolved);
}
}
for (const key of Object.keys(o)) {
if (key !== "content" || !Array.isArray(o[key])) {
collectFromObject(o[key]);
}
}
};
const contentArray = content.content;
if (Array.isArray(contentArray)) {
for (const part of contentArray) {
collectFromObject(part);
}
} else {
collectFromObject(content);
}
const files = content.files;
if (Array.isArray(files)) {
for (const f of files) {
collectFromObject(f);
}
}
const attachments = content.attachments;
if (Array.isArray(attachments)) {
for (const a of attachments) {
collectFromObject(a);
}
}
return result;
}
function extractPendingAttachments(json, lmHome) {
const result = [];
const home = lmHome || findLMStudioHome() || path17.join(os4.homedir(), ".lmstudio");
const userFilesDir = path17.join(home, "user-files");
if (!json || typeof json !== "object") return result;
const files = json.clientInputFiles;
if (!Array.isArray(files)) return result;
for (const f of files) {
if (!f || typeof f !== "object") continue;
const fo = f;
const id = fo.fileIdentifier;
const type = fo.fileType;
if (typeof id === "string" && id.trim() && type === "image") {
result.push(path17.join(userFilesDir, id));
}
}
return result;
}
// src/services/toolParams/attachments.ts
function extractAttachmentFromSsot(raw) {
const candidates = [];
if (Array.isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
const item = raw[i];
if (typeof item === "string" && item.trim()) {
candidates.push({ filename: item.trim(), index: i + 1 });
} else if (item && typeof item === "object") {
const obj = item;
if (typeof obj.identifier === "string" && obj.identifier.trim()) {
candidates.push({ filename: obj.identifier.trim(), index: i + 1 });
} else if (typeof obj.filename === "string" && obj.filename.trim()) {
candidates.push({ filename: obj.filename.trim(), index: i + 1 });
}
}
}
}
return candidates;
}
var ATTACHMENT_FULL_CONFIG = {
mediaType: "attachment",
allow: "all",
ssotJoin: {
source: "conversation.json",
messageRole: "user",
jsonPath: "message.files[]",
extractFromSource: extractAttachmentFromSsot
},
scan: {
trigger: "on-turn-start",
scope: "all-turns"
// Full scan for reconcile (user can delete attachments)
},
harvesting: {
defaultExtractor: extractAttachmentFromSsot,
tools: {}
// Attachments kommen nicht aus Tools
},
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
// Labels (a1, a2) ALWAYS included
pixel: true
// Base64 pixels in rolling window
}
},
injectMdInAgentResponse: {
format: "none",
labelGenerator: (item, i) => `a${item.index ?? i + 1}`
},
preview: {
generate: true,
namingPattern: "preview-{basename}.jpg",
format: "jpeg",
mimeType: "image/jpeg",
// Sum constraint: w + h ≤ 1536 (e.g., 768x768, 1024x512)
maxSum: 1536,
quality: 80,
outputDir: "."
},
toggles: {
toggles: []
// No toggle dependencies for attachments
}
};
var ATTACHMENT_CONFIG = {
mediaType: "attachment",
allow: "all",
defaultExtractor: extractAttachmentFromSsot,
defaultActions: ATTACHMENT_FULL_CONFIG.actions,
tools: {}
};
// src/services/toolParams/variants.ts
function extractVariantUrisFromContent(raw) {
const candidates = [];
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
const uriRegex = /file:\/\/[^\s"')]+generated-image-[^\s"')]*-v(\d+)\.png/gi;
let match;
while ((match = uriRegex.exec(text)) !== null) {
const uri = match[0];
const variantNum = parseInt(match[1], 10);
const filename = uri.split("/").pop() ?? "";
candidates.push({
filename,
index: variantNum
});
}
return candidates;
}
function extractGenerateImageResult(raw) {
const candidates = [];
if (raw && typeof raw === "object") {
const obj = raw;
if (Array.isArray(obj.filenames)) {
for (let i = 0; i < obj.filenames.length; i++) {
const fn = obj.filenames[i];
if (typeof fn === "string") {
candidates.push({ filename: fn, index: i + 1 });
}
}
}
if (Array.isArray(obj.content)) {
for (const item of obj.content) {
if (item && typeof item === "object" && "text" in item) {
const nested = extractVariantUrisFromContent(item.text);
candidates.push(...nested);
}
}
}
}
if (candidates.length === 0) {
return extractVariantUrisFromContent(raw);
}
return candidates;
}
var selfPlugin = getSelfPluginIdentifier() ?? "ceveyne/draw-things-chat";
var VARIANT_FULL_CONFIG = {
mediaType: "variant",
allow: "all",
ssotJoin: {
source: "conversation.json",
messageRole: "assistant",
jsonPath: "content",
// Regex scan for file:// URIs
extractFromSource: extractVariantUrisFromContent
},
scan: {
trigger: "both",
scope: "all-turns"
},
harvesting: {
defaultExtractor: extractGenerateImageResult,
tools: {
[`${selfPlugin}/generate_image`]: {
extractCandidates: extractGenerateImageResult,
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
pixel: true
}
}
}
}
},
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
// Labels (v1, v2) ALWAYS included
pixel: true
// Base64 pixels in rolling window
}
},
injectMdInAgentResponse: {
format: "none",
// Toggle-dependent: !PREVIEW_IN_CHAT
itemTemplate: "",
labelGenerator: (item, i) => `v${item.index ?? i + 1}`
},
preview: {
generate: true,
namingPattern: "preview-{basename}.jpg",
format: "jpeg",
mimeType: "image/jpeg",
// Sum constraint: w + h ≤ 1536 (e.g., 768x768, 1024x512)
maxSum: 1536,
quality: 80,
outputDir: "."
},
toggles: {
toggles: []
}
};
var VARIANT_CONFIG = {
mediaType: "variant",
allow: "all",
defaultExtractor: extractGenerateImageResult,
defaultActions: VARIANT_FULL_CONFIG.actions,
tools: VARIANT_FULL_CONFIG.harvesting.tools
};
// src/services/toolParams/pictures.ts
function renderBraveResultsTable(items) {
if (items.length === 0) return "";
const toDisplayUrl = (u) => {
const s = typeof u === "string" ? u.trim() : "";
if (!s) return "";
if (/^https?:\/\//i.test(s) || /^file:\/\//i.test(s)) return s;
if (s.startsWith("./") || s.startsWith("/")) return s;
return `./${s}`;
};
const escapeCell = (s) => String(s ?? "").replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ").trim();
const sourceLabel = (u) => {
try {
if (!/^https?:\/\//i.test(u)) return "source";
const host = new URL(u).host;
return host || "source";
} catch {
return "source";
}
};
const header = "| Preview | ID / Title | Source |\n|---|---|---|";
const rows = items.map((item, i) => {
const label = typeof item.index === "number" && Number.isFinite(item.index) ? `p${item.index}` : `p${i + 1}`;
const previewUrl = toDisplayUrl(item.url);
const linkTarget = (
// Prefer the external provenance link (HTTP) when present.
toDisplayUrl(item.sourceUrl) || // Fallbacks: if we don't have provenance, link to local original.
toDisplayUrl(item.filename) || // Last resort: link to whatever we have.
previewUrl
);
const previewImg = previewUrl ? `` : "";
const preview = previewImg && linkTarget ? `[${previewImg}](${linkTarget})` : previewImg;
const titleText = escapeCell(item.title ?? "");
const title = titleText ? `\`${label}\` ${titleText}` : `\`${label}\``;
const sourceUrl = typeof item.sourceUrl === "string" && item.sourceUrl.trim() ? item.sourceUrl.trim() : "";
const source = sourceUrl ? `[${escapeCell(sourceLabel(sourceUrl))}](${toDisplayUrl(sourceUrl)})` : "";
return `| ${preview} | ${title} | ${source} |`;
});
return [header, ...rows].join("\n");
}
function renderDrawThingsIndexResultsTable(items) {
if (items.length === 0) return "";
const escapeCell = (s) => String(s ?? "").replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ").trim();
const hasAnyPreview = items.some(
(item) => item.filename && item.filename.trim() || item.url && item.url.trim()
);
if (hasAnyPreview) {
const header = "| Preview | ID / Generation Data |\n|---|---|";
const rows = items.map((item, i) => {
const label = typeof item.index === "number" && Number.isFinite(item.index) ? `p${item.index}` : `p${i + 1}`;
const localPath = item.filename || "";
const httpPreview = item.url || "";
const imgSrc = localPath || httpPreview;
const linkTarget = httpPreview || localPath;
const previewImg = imgSrc ? `` : "";
const videoLinkTarget = item.videoPath && item.videoPath.trim() ? item.videoPath.trim() : void 0;
const effectiveLinkTarget = videoLinkTarget ?? linkTarget;
const preview = previewImg && effectiveLinkTarget && effectiveLinkTarget !== imgSrc ? `[${previewImg}](${effectiveLinkTarget})` : previewImg;
const metadataCell = buildMetadataCell(item, label, escapeCell);
return `| ${preview} | ${metadataCell} |`;
});
return [header, ...rows, ""].join("\n");
} else {
const header = "| Index Image Results |\n|---|";
const rows = items.map((item, i) => {
const label = typeof item.index === "number" && Number.isFinite(item.index) ? `p${item.index}` : `p${i + 1}`;
const metadataCell = buildMetadataCell(item, label, escapeCell);
return `| ${metadataCell} |`;
});
return [header, ...rows, ""].join("\n");
}
}
function buildMetadataCell(item, label, escapeCell) {
const parts = [`\`${label}\``];
if (item.prompt) {
parts.push(`**Prompt:** ${escapeCell(item.prompt)}`);
}
if (typeof item.numFrames === "number" && item.numFrames > 1) {
parts.push(`**Type:** video (${item.numFrames} frames)`);
}
if (typeof item.width === "number" || typeof item.height === "number") {
const w = typeof item.width === "number" && Number.isFinite(item.width) ? item.width : 0;
const h = typeof item.height === "number" && Number.isFinite(item.height) ? item.height : 0;
parts.push(`**Size:** ${w}\xD7${h}`);
if (typeof item.numFrames === "number" && item.numFrames > 1) {
parts.push(`**Frames:** ${item.numFrames}`);
}
}
if (item.model) {
const display = item.modelDisplay && item.modelDisplay.trim() ? item.modelDisplay : item.model;
parts.push(`**Model:** ${escapeCell(display)}`);
}
if (item.modelMeta) {
const mm = item.modelMeta;
if (mm.model_used) {
parts.push(`**Model used:** ${escapeCell(mm.model_used)}`);
}
if (mm.model_family) {
parts.push(`**Family:** ${escapeCell(mm.model_family)}`);
}
const customLabels = Array.isArray(mm.custom_config_labels) ? mm.custom_config_labels.map((s) => typeof s === "string" ? s.trim() : "").filter((s) => !!s) : [];
if (customLabels.length) {
parts.push(`**Custom configs:** ${customLabels.map(escapeCell).join(", ")}`);
}
}
if (item.loras && item.loras.length > 0) {
parts.push(`**LoRAs:** ${item.loras.map((l) => escapeCell(l)).join(", ")}`);
}
if (item.matchType) {
parts.push(`**Match:** ${escapeCell(item.matchType)}`);
}
if (typeof item.score === "number") {
parts.push(`**Score:** ${item.score}`);
}
if (item.sourceInfo) {
const src = item.sourceInfo;
const srcLabel = src.type.replace(/_/g, " ");
const extractProjectName = (path19) => {
const filename = path19.split("/").pop() || "";
return filename.replace(/\.(drawthings|sqlite3|dtproj|json)$/i, "");
};
const extractFromProjectUri = (uri) => {
if (!uri.startsWith("project://")) return null;
const withoutScheme = uri.slice("project://".length);
const hashIndex = withoutScheme.lastIndexOf("#");
const projectPath = hashIndex !== -1 ? withoutScheme.slice(0, hashIndex) : withoutScheme;
return extractProjectName(projectPath);
};
const projectNameFromUri = item.filename ? extractFromProjectUri(item.filename) : null;
const srcExtra = src.chatId ? `(chat: ${escapeCell(src.chatId)})` : projectNameFromUri ? `(${escapeCell(projectNameFromUri)})` : src.projectFile ? `(${escapeCell(extractProjectName(src.projectFile))})` : src.originalName ? `(${escapeCell(extractProjectName(src.originalName))})` : src.filePath ? `(${escapeCell(extractProjectName(src.filePath))})` : "";
parts.push(`**Source:** ${srcLabel}${srcExtra ? " " + srcExtra : ""}`);
const imageType = typeof src.imageType === "string" ? src.imageType.trim() : "";
if (imageType) {
parts.push(`**Origin:** ${escapeCell(imageType)}`);
}
}
if (item.timestamp) {
parts.push(`**Timestamp:** ${escapeCell(item.timestamp)}`);
}
return parts.join(" \u2022 ");
}
function renderDefaultPictureTable(items) {
return renderBraveResultsTable(items);
}
var braveSearchExtractor = {
extractCandidates: extractToolResultImageCandidates,
actions: {
generatePreview: true,
visionPromotion: {
metadata: false,
// Pictures are NOT vision-promoted
pixel: false
}
},
renderMarkdownTable: renderBraveResultsTable
};
function isDrawThingsIndexPayload(p) {
if (!p || typeof p !== "object") return false;
const obj = p;
return Array.isArray(obj.images);
}
function extractDrawThingsIndexCandidates(raw) {
const p = parseToolResultPayload(raw, isDrawThingsIndexPayload);
if (!p) {
console.info(`[DrawThingsIndexExtractor] No valid payload found`);
return [];
}
console.info(
`[DrawThingsIndexExtractor] Found payload: images=${p.images?.length ?? 0}, totalFound=${p.totalFound ?? 0}`
);
const out = [];
const seen = /* @__PURE__ */ new Set();
for (const img of p.images ?? []) {
if (!img || typeof img !== "object") continue;
const paths = img.imagePaths ?? [];
const previews = img.httpPreviewUrls ?? [];
for (let i = 0; i < Math.max(paths.length, previews.length); i++) {
const imgPath = paths[i]?.trim() ?? "";
const previewUrl = previews[i]?.trim() ?? "";
if (!imgPath && !previewUrl) continue;
const key = imgPath || previewUrl;
if (seen.has(key)) continue;
seen.add(key);
out.push({
filename: imgPath || void 0,
url: previewUrl || void 0,
title: img.prompt?.trim(),
prompt: img.prompt?.trim(),
model: img.model?.trim(),
modelDisplay: img.model_display?.trim(),
modelMeta: img.model_meta,
loras: img.loras,
width: typeof img.width === "number" ? img.width : void 0,
height: typeof img.height === "number" ? img.height : void 0,
numFrames: typeof img.numFrames === "number" && img.numFrames > 1 ? img.numFrames : void 0,
matchType: img.matchType?.trim(),
score: img.matchScore,
sourceInfo: img.sourceInfo,
timestamp: img.timestamp?.trim()
});
}
}
if (out.length > 0) {
console.info(`[DrawThingsIndexExtractor] Extracted ${out.length} candidates`);
}
return out;
}
var drawThingsIndexExtractor = {
extractCandidates: extractDrawThingsIndexCandidates,
actions: {
generatePreview: true,
// Required for project:// URIs (no httpPreviewUrl)
visionPromotion: {
metadata: false,
pixel: false
}
},
renderMarkdownTable: renderDrawThingsIndexResultsTable
};
var pictureTools = {
"mcp/brave-search-mcp/brave_image_search": braveSearchExtractor,
// NUR brave_image_search
"ceveyne/draw-things-index/index_image": drawThingsIndexExtractor
// Add new picture tools here...
};
var PICTURE_FULL_CONFIG = {
mediaType: "picture",
allow: "only",
ssotJoin: {
source: "tool-result",
messageRole: "tool",
jsonPath: "content[].text",
extractFromSource: extractToolResultImageCandidates
},
scan: {
trigger: "on-tool-result",
scope: "current-tool-result"
},
harvesting: {
// No defaultExtractor (allow: "only")
tools: pictureTools
},
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
// Labels (p1, p2) ALWAYS included
pixel: false
// No pixel data for pictures
}
},
injectMdInAgentResponse: {
format: "markdown-table",
tableHeader: "| Preview | ID / Title | Source |\n|---|---|---|",
itemTemplate: "|  | {title} | {source} |",
labelGenerator: (item, i) => `p${item.index ?? i + 1}`
},
preview: {
generate: true,
namingPattern: "preview-picture-{hash}.jpg",
format: "jpeg",
mimeType: "image/jpeg",
maxWidth: 256,
quality: 70,
outputDir: "."
},
toggles: {
toggles: [
{
name: "PREVIEW_IN_CHAT",
affects: "format",
// When LM Studio already renders tool-result previews inline, don't inject our table.
transform: (previewInChat, defaultFormat) => previewInChat ? "none" : defaultFormat
},
{
name: "PREVIEW_IN_CHAT",
affects: "generatePreview",
// PREVIEW_IN_CHAT=true: do not download images and do not generate previews.
transform: (previewInChat, defaultValue) => previewInChat ? false : defaultValue
}
]
}
};
var PICTURE_CONFIG = {
mediaType: "picture",
allow: "only",
tools: pictureTools
};
function getAllowlistedPictureTools() {
return Object.keys(pictureTools);
}
function renderPictureMarkdown(items) {
return renderDefaultPictureTable(items);
}
// src/services/toolParams/images.ts
function extractImageFromToolResult(raw) {
const candidates = [];
let parsed = raw;
if (typeof raw === "string") {
try {
parsed = JSON.parse(raw);
} catch {
parsed = raw;
}
}
const rawType = Array.isArray(parsed) ? "array" : typeof parsed;
const rawPreview = JSON.stringify(parsed)?.slice(0, 500) ?? "undefined";
console.info(
`[ImageExtractor] parsed type=${rawType}, preview=${rawPreview}`
);
let items;
if (Array.isArray(parsed)) {
items = parsed;
} else if (parsed && typeof parsed === "object" && Array.isArray(parsed.content)) {
items = parsed.content;
} else {
items = [parsed];
}
for (const item of items) {
if (item && typeof item === "object" && item.type === "image" && typeof item.fileName === "string") {
const fileName = item.fileName;
if (/^generated-image-\d{4}-\d{2}-\d{2}T[\d-]+Z-v\d+\.\w+$/i.test(fileName)) {
continue;
}
if (fileName.toLowerCase().startsWith("preview-")) {
continue;
}
candidates.push({ filename: fileName, index: candidates.length + 1 });
}
}
if (candidates.length === 0) {
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
const base64Regex = /data:image\/[a-z]+;base64,[A-Za-z0-9+/=]+/g;
const base64Matches = text.match(base64Regex) ?? [];
for (let i = 0; i < base64Matches.length; i++) {
candidates.push({ base64: base64Matches[i], index: i + 1 });
}
}
const seen = /* @__PURE__ */ new Set();
return candidates.filter((c) => {
const key = c.filename ?? c.base64 ?? "";
if (!key || seen.has(key)) return false;
seen.add(key);
return true;
});
}
var IMAGE_FULL_CONFIG = {
mediaType: "image",
allow: "all",
ssotJoin: {
source: "tool-result",
messageRole: "tool",
jsonPath: "content[].type === 'image'",
extractFromSource: extractImageFromToolResult
},
scan: {
trigger: "on-tool-result",
scope: "current-tool-result"
},
harvesting: {
defaultExtractor: extractImageFromToolResult
// No tool-specific extractors - all tools get uniform treatment
},
actions: {
generatePreview: true,
// ALWAYS generate normalized previews
visionPromotion: {
metadata: true,
// Labels (i1, i2) ALWAYS included
pixel: true
// Base64 pixels in rolling window
}
},
injectMdInAgentResponse: {
// Default behavior: PREVIEW_IN_CHAT=false -> inject markdown into agent turn
// PREVIEW_IN_CHAT=true -> do nothing; rely on standard tool-result rendering
format: "markdown-image",
labelGenerator: (item, i) => `Image i${item.index ?? i + 1}`
},
preview: {
generate: true,
namingPattern: "preview-{basename}.jpg",
format: "jpeg",
mimeType: "image/jpeg",
// Sum constraint: w + h ≤ 1536 (e.g., 768x768, 1024x512)
maxSum: 1536,
quality: 80,
outputDir: "."
},
toggles: {
toggles: [
{
name: "PREVIEW_IN_CHAT",
affects: "format",
transform: (previewInChat, defaultFormat) => previewInChat ? "none" : defaultFormat
}
]
}
};
var IMAGE_CONFIG = {
mediaType: "image",
allow: "all",
defaultExtractor: extractImageFromToolResult,
defaultActions: IMAGE_FULL_CONFIG.actions
// No tool-specific extractors
};
// src/services/toolParams/types.ts
function resolveActions(config, toggles) {
const actions = { ...config.actions };
for (const dep of config.toggles.toggles) {
if (dep.affects !== "generatePreview") continue;
const toggleValue = toggles[dep.name] ?? false;
actions.generatePreview = dep.transform(
toggleValue,
actions.generatePreview
);
}
return actions;
}
function resolveInjection(config, toggles) {
const injection = { ...config.injectMdInAgentResponse };
for (const dep of config.toggles.toggles) {
if (dep.affects !== "format") continue;
const toggleValue = toggles[dep.name] ?? false;
injection.format = dep.transform(toggleValue, injection.format);
}
return injection;
}
// src/services/toolParams/index.ts
var MEDIA_TYPE_CONFIGS = {
attachment: ATTACHMENT_CONFIG,
variant: VARIANT_CONFIG,
picture: PICTURE_CONFIG,
image: IMAGE_CONFIG
};
var MEDIA_TYPE_FULL_CONFIGS = {
attachment: ATTACHMENT_FULL_CONFIG,
variant: VARIANT_FULL_CONFIG,
picture: PICTURE_FULL_CONFIG,
image: IMAGE_FULL_CONFIG
};
function getToolExtractor(pluginId, toolName) {
const toolKey = `${pluginId}/${toolName}`;
const pictureConfig = MEDIA_TYPE_CONFIGS.picture;
if (pictureConfig?.allow === "only" && pictureConfig.tools[toolKey]) {
return {
mediaType: "picture",
extractor: pictureConfig.tools[toolKey]
};
}
const selfPlugin2 = getSelfPluginIdentifier();
if (selfPlugin2 && pluginId === selfPlugin2) {
const variantConfig = MEDIA_TYPE_CONFIGS.variant;
if (variantConfig?.allow === "all") {
if (variantConfig.tools?.[toolKey]) {
return {
mediaType: "variant",
extractor: variantConfig.tools[toolKey]
};
}
return {
mediaType: "variant",
extractor: {
extractCandidates: variantConfig.defaultExtractor,
actions: variantConfig.defaultActions
}
};
}
}
const imageConfig = MEDIA_TYPE_CONFIGS.image;
if (imageConfig?.allow === "all") {
if (imageConfig.tools?.[toolKey]) {
return {
mediaType: "image",
extractor: imageConfig.tools[toolKey]
};
}
return {
mediaType: "image",
extractor: {
extractCandidates: imageConfig.defaultExtractor,
actions: imageConfig.defaultActions
}
};
}
return void 0;
}
function isPictureToolAllowlisted(pluginId, toolName) {
const toolKey = `${pluginId}/${toolName}`;
return getAllowlistedPictureTools().includes(toolKey);
}
function normalizeToolPluginId(pluginId, toolName) {
const p = typeof pluginId === "string" ? pluginId.trim() : "";
if (p) return p;
const tn = String(toolName || "").trim().toLowerCase();
if (tn === "brave_image_search") {
return "mcp/brave-search-mcp";
}
if (tn === "index_image") {
return "ceveyne/draw-things-index";
}
return void 0;
}
function getMediaTypeForToolCall(pluginId, toolName) {
const result = getToolExtractor(pluginId, toolName);
return result?.mediaType;
}
function getAllMediaTypeConfigs() {
return { ...MEDIA_TYPE_CONFIGS };
}
function getMediaTypeConfig(kind) {
return MEDIA_TYPE_CONFIGS[kind];
}
function getMediaTypeFullConfig(kind) {
return MEDIA_TYPE_FULL_CONFIGS[kind];
}
function getAllMediaTypeFullConfigs() {
return { ...MEDIA_TYPE_FULL_CONFIGS };
}
// src/services/mediaScanner/scanner.ts
var VARIANT_FILENAME_RE = /generated-image-[A-Za-z0-9T-]+Z-v\d+\.png/gi;
function buildConversationWideRequestMetadata(messages, debug) {
const metaByKey = /* @__PURE__ */ new Map();
let toolRequestCount = 0;
const remember = (key, meta) => {
const k = typeof key === "string" || typeof key === "number" ? String(key) : "";
if (!k) return;
const prev = metaByKey.get(k) ?? {};
metaByKey.set(k, {
pluginId: meta.pluginId ?? prev.pluginId,
toolName: meta.toolName ?? prev.toolName
});
};
if (debug) {
console.info(
`[MediaScanner] buildConversationWideRequestMetadata: scanning ${messages.length} messages`
);
}
for (const msg of messages) {
const content = msg.content;
if (!content || typeof content !== "object") continue;
const obj = content;
if (obj.type === "contentBlock" && Array.isArray(obj.content)) {
for (const item of obj.content) {
if (!item || typeof item !== "object") continue;
const bo = item;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case1): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
if (Array.isArray(obj.content)) {
for (const item of obj.content) {
if (!item || typeof item !== "object") continue;
const it = item;
if (it.type === "contentBlock" && Array.isArray(it.content)) {
for (const bi of it.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case2): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
}
if (Array.isArray(obj.steps)) {
for (const step of obj.steps) {
if (!step || typeof step !== "object") continue;
const st = step;
if (st.type === "contentBlock" && Array.isArray(st.content)) {
for (const bi of st.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case3-steps): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
}
}
if (debug) {
console.info(
`[MediaScanner] buildConversationWideRequestMetadata: found ${toolRequestCount} toolCallRequests, ${metaByKey.size} unique keys`
);
}
return metaByKey;
}
function extractVariantFilenames(content) {
const text = typeof content === "string" ? content : JSON.stringify(content);
const matches = text.match(VARIANT_FILENAME_RE) ?? [];
return [...new Set(matches)];
}
function extractImageFilenames(content) {
const result = [];
const text = typeof content === "string" ? content : JSON.stringify(content);
const base64Re = /data:image\/[a-z]+;base64,[A-Za-z0-9+/=]+/g;
const base64Matches = text.match(base64Re) ?? [];
result.push(...base64Matches);
const filenameRe = /\b([a-zA-Z0-9_-]+\.(png|jpg|jpeg|webp|gif))\b/gi;
const filenameMatches = text.match(filenameRe) ?? [];
for (const match of filenameMatches) {
if (VARIANT_FILENAME_RE.test(match)) {
VARIANT_FILENAME_RE.lastIndex = 0;
continue;
}
if (match.includes("://")) continue;
result.push(match);
}
return [...new Set(result)];
}
async function scanMedia(chatWd, mediaType, options) {
const { scope, debug = false } = options;
if (!chatWd) {
return { candidates: [] };
}
const conv = await readConversation(chatWd);
if (!conv) {
if (debug) {
console.info(`[MediaScanner] No conversation.json found for ${chatWd}`);
}
return { candidates: [] };
}
if (debug && (mediaType === "picture" || mediaType === "image")) {
try {
const arr = findMessagesArray(conv.json);
const arrLen = Array.isArray(arr) ? arr.length : 0;
console.info(
`[MediaScanner] SSOT messages array present=${!!arr} len=${arrLen}`
);
let contentBlockCount = 0;
let toolCallRequestCount = 0;
let toolCallResultCount = 0;
let braveHits = 0;
const stack = Array.isArray(arr) ? [...arr] : [conv.json];
const maxNodes = 5e4;
let nodes = 0;
while (stack.length) {
const v = stack.pop();
nodes++;
if (nodes > maxNodes) break;
if (!v) continue;
if (typeof v === "string") {
if (v.includes("brave_image_search")) {
braveHits++;
}
continue;
}
if (typeof v !== "object") continue;
if (Array.isArray(v)) {
for (const it of v) stack.push(it);
continue;
}
const o = v;
const t = o.type;
if (t === "contentBlock") contentBlockCount++;
if (t === "toolCallRequest" || t === "toolCallRequest") {
toolCallRequestCount++;
if (o.name === "brave_image_search") {
braveHits++;
}
}
if (t === "toolCallResult" || t === "tool_result" || t === "tool-result") {
toolCallResultCount++;
if (o.name === "brave_image_search") {
braveHits++;
}
}
for (const k of Object.keys(o)) {
stack.push(o[k]);
}
}
console.info(
`[MediaScanner] SSOT scan stats: contentBlock=${contentBlockCount} toolCallRequest=${toolCallRequestCount} toolCallResult=${toolCallResultCount} braveHits=${braveHits} nodes=${Math.min(
nodes,
maxNodes
)}${nodes > maxNodes ? "+" : ""}`
);
} catch (e) {
console.info(
`[MediaScanner] SSOT debug scan failed: ${String(
e?.message ?? e
)}`
);
}
}
const messages = parseMessages(conv.json);
if (debug) {
console.info(
`[MediaScanner] Parsed ${messages.length} messages from ${conv.path}`
);
if (mediaType === "picture" || mediaType === "image") {
const byRole = /* @__PURE__ */ new Map();
for (const m of messages) {
const r = String(m.role || "unknown");
byRole.set(r, (byRole.get(r) ?? 0) + 1);
}
const roleSummary = Array.from(byRole.entries()).sort((a, b) => b[1] - a[1]).map(([r, n]) => `${r}:${n}`).join(" ");
console.info(`[MediaScanner] Role distribution: ${roleSummary}`);
}
}
const requestMetaByKey = buildConversationWideRequestMetadata(
messages,
debug
);
if (debug && requestMetaByKey.size > 0) {
console.info(
`[MediaScanner] Collected ${requestMetaByKey.size} request metadata entries`
);
}
const candidates = [];
const orderedMessages = scope === "last" ? [...messages].reverse() : messages;
for (const msg of orderedMessages) {
let foundInThisMessage = [];
if (mediaType === "attachment") {
foundInThisMessage = scanAttachmentsInMessage(msg, conv.json, debug);
} else {
foundInThisMessage = scanToolResultsInMessage(
msg,
mediaType,
requestMetaByKey,
debug
);
}
if (foundInThisMessage.length > 0) {
candidates.push(...foundInThisMessage);
if (scope === "last") {
break;
}
}
}
if (mediaType === "attachment" && scope === "all") {
const pending = extractPendingAttachments(conv.json);
const toAppend = [];
for (const p of pending) {
const exists = candidates.some((c) => c.identifier === p);
if (exists) continue;
toAppend.push({
kind: "attachment",
identifier: p,
turnId: 0
// Pending = before any turn
});
}
if (toAppend.length) {
candidates.push(...toAppend);
}
}
if (debug) {
console.info(
`[MediaScanner] FINAL: Found ${candidates.length} ${mediaType} candidates (scope: ${scope})`
);
if (candidates.length > 0) {
for (const c of candidates.slice(0, 3)) {
console.info(
`[MediaScanner] candidate: id=${c.identifier?.slice(
0,
50
)} pluginId=${c.pluginId} tool=${c.sourceTool}`
);
}
}
}
return {
candidates,
conversationPath: conv.path
};
}
function scanAttachmentsInMessage(msg, conversationJson, debug) {
if (msg.role !== "user") return [];
const attachments = extractUserAttachments(msg);
return attachments.map((absPath) => ({
kind: "attachment",
identifier: absPath,
turnId: msg.turnId
}));
}
function scanToolResultsInMessage(msg, mediaType, requestMetaByKey, debug) {
if (msg.role !== "assistant" && msg.role !== "tool") return [];
const toolCalls = extractToolCalls(msg);
const candidates = [];
if (debug && (mediaType === "picture" || mediaType === "image") && toolCalls.length) {
const names = toolCalls.map((t) => String(t.toolName || "")).filter((s) => s.trim().length > 0);
console.info(
`[MediaScanner] turnId=${msg.turnId} extracted toolCalls=${toolCalls.length}${names.length ? ` tools=${names.slice(0, 5).join(", ")}${names.length > 5 ? "\u2026" : ""}` : ""}`
);
}
const selfPlugin2 = getSelfPluginIdentifier();
for (const tc of toolCalls) {
let effectivePluginId = tc.pluginId;
if (!effectivePluginId && tc.toolCallId) {
const meta = requestMetaByKey.get(tc.toolCallId);
if (meta?.pluginId) effectivePluginId = meta.pluginId;
}
const isOurPlugin = selfPlugin2 && effectivePluginId === selfPlugin2;
const pluginIdNormalized = normalizeToolPluginId(
effectivePluginId,
tc.toolName
);
if (debug && (mediaType === "picture" || mediaType === "image")) {
console.info(
`[MediaScanner] ${mediaType} check: toolName="${tc.toolName}" pluginId="${tc.pluginId ?? "(none)"}" effectivePluginId="${effectivePluginId ?? "(none)"}" normalized="${pluginIdNormalized ?? "(none)"}" isOurPlugin=${isOurPlugin}`
);
}
if (mediaType === "variant") {
if (!isOurPlugin) continue;
const filenames = extractVariantFilenames(tc.content);
for (const filename of filenames) {
candidates.push({
kind: "variant",
identifier: filename,
turnId: tc.turnId,
toolCallId: tc.toolCallId,
sourceTool: tc.toolName,
pluginId: effectivePluginId
});
}
} else if (mediaType === "picture") {
if (!pluginIdNormalized) continue;
const lookup = getToolExtractor(pluginIdNormalized, tc.toolName);
if (!lookup || lookup.mediaType !== "picture") continue;
const extracted = lookup.extractor.extractCandidates(tc.content);
if (debug) {
console.info(
`[MediaScanner] picture toolCall: ${pluginIdNormalized}/${tc.toolName} candidates=${extracted.length}`
);
}
for (const c of extracted) {
const identifier = typeof c.filename === "string" && c.filename.trim() ? c.filename.trim() : typeof c.url === "string" && c.url.trim() ? c.url.trim() : "";
if (!identifier) continue;
candidates.push({
kind: "picture",
identifier,
turnId: tc.turnId,
toolCallId: tc.toolCallId,
sourceTool: tc.toolName,
pluginId: pluginIdNormalized,
title: c.title,
pageUrl: typeof c.sourceUrl === "string" ? c.sourceUrl : void 0,
width: typeof c.width === "number" ? c.width : void 0,
height: typeof c.height === "number" ? c.height : void 0,
rawContent: tc.content
});
}
} else if (mediaType === "image") {
if (isOurPlugin) continue;
if (!pluginIdNormalized) continue;
const lookup = getToolExtractor(pluginIdNormalized, tc.toolName);
if (lookup && lookup.mediaType !== "image") {
if (debug) {
console.info(
`[MediaScanner] image skip: ${pluginIdNormalized}/${tc.toolName} is registered as ${lookup.mediaType}`
);
}
continue;
}
if (lookup && lookup.mediaType === "image") {
const extracted = lookup.extractor.extractCandidates(tc.content);
if (debug) {
console.info(
`[MediaScanner] image toolCall: ${pluginIdNormalized}/${tc.toolName} candidates=${extracted.length}`
);
}
for (const c of extracted) {
const filename = c.filename ?? "";
if (!filename) continue;
candidates.push({
kind: "image",
identifier: filename,
turnId: tc.turnId,
toolCallId: tc.toolCallId,
sourceTool: tc.toolName,
pluginId: pluginIdNormalized,
previewIsOriginal: c.previewIsOriginal
});
}
} else {
const filenames = extractImageFilenames(tc.content);
for (const filename of filenames) {
candidates.push({
kind: "image",
identifier: filename,
turnId: tc.turnId,
toolCallId: tc.toolCallId,
sourceTool: tc.toolName,
pluginId: effectivePluginId
});
}
}
}
}
return candidates;
}
async function findAllMedia(chatWd, mediaType, debug = false) {
return scanMedia(chatWd, mediaType, { scope: "all", debug });
}
async function findLastMedia(chatWd, mediaType, debug = false) {
return scanMedia(chatWd, mediaType, { scope: "last", debug });
}
async function findAllMediaAllTypes(chatWd, debug = false) {
const [attachments, variants, pictures, images] = await Promise.all([
findAllMedia(chatWd, "attachment", debug),
findAllMedia(chatWd, "variant", debug),
findAllMedia(chatWd, "picture", debug),
findAllMedia(chatWd, "image", debug)
]);
return {
attachment: attachments.candidates,
variant: variants.candidates,
picture: pictures.candidates,
image: images.candidates
};
}
// src/services/mediaScanner/legacyAdapters.ts
async function findAllAttachmentsLegacy(chatWd, debug) {
if (!chatWd) return { found: [], turnIdByAbs: {} };
const result = await findAllMedia(chatWd, "attachment", debug);
const found = [];
const turnIdByAbs = {};
for (const c of result.candidates) {
const abs = c.identifier;
if (!found.includes(abs)) {
found.push(abs);
}
if (turnIdByAbs[abs] === void 0) {
turnIdByAbs[abs] = c.turnId;
}
}
return { found, turnIdByAbs };
}
async function findLastAttachmentLegacy(chatWd, debug) {
if (!chatWd) return [];
const result = await findLastMedia(chatWd, "attachment", debug);
const found = [];
for (const c of result.candidates) {
if (!found.includes(c.identifier)) {
found.push(c.identifier);
}
}
return found;
}
async function findAllVariantsLegacy(chatWd, debug) {
if (!chatWd) return { found: [], turnIdByBasename: {} };
const result = await findAllMedia(chatWd, "variant", debug);
const found = [];
const turnIdByBasename = {};
for (const c of result.candidates) {
const basename3 = c.identifier;
if (!found.includes(basename3)) {
found.push(basename3);
}
turnIdByBasename[basename3] = c.turnId;
}
return { found, turnIdByBasename };
}
async function findAllPictures(chatWd, debug) {
if (!chatWd) return { candidates: [] };
const result = await findAllMedia(chatWd, "picture", debug);
return { candidates: result.candidates };
}
async function findLastPictures(chatWd, debug) {
if (!chatWd) return { candidates: [] };
const result = await findLastMedia(chatWd, "picture", debug);
return { candidates: result.candidates };
}
async function findAllImages(chatWd, debug) {
if (!chatWd) return { candidates: [] };
const result = await findAllMedia(chatWd, "image", debug);
return { candidates: result.candidates };
}
async function findLastImages(chatWd, debug) {
if (!chatWd) return { candidates: [] };
const result = await findLastMedia(chatWd, "image", debug);
return { candidates: result.candidates };
}
// src/tools/review_image.ts
import { tool } from "@lmstudio/sdk";
import { z as z2 } from "zod";
import path18 from "path";
import fs15 from "fs";
var FlexibleTargetsList = z2.union([
// String form: split by comma/space
z2.string().transform(
(s) => s.split(/[\s,]+/).map((x) => x.trim()).filter((x) => x.length > 0)
),
// Array form: pass through
z2.array(z2.string())
]).refine((arr) => arr.length >= 1, "targets must contain at least one notation").refine((arr) => arr.length <= 32, "targets must contain at most 32 notations");
var ReviewImageParamsShape = {
targets: FlexibleTargetsList
};
function normalizeTargetNotationToken(raw) {
let s = String(raw ?? "").trim();
if (!s) return "";
s = s.replace(/^[\[{(]+/, "").replace(/[\]})]+$/, "");
s = s.replace(/^['"`]+|['"`]+$/g, "");
s = s.replace(/[;:.!?]+$/, "");
s = s.trim().toLowerCase();
if (/^[avip]$/i.test(s)) return `${s}1`;
return s;
}
function normalizeTargetsArg(rawTargets) {
const fromArray = (arr) => arr.map((x) => typeof x === "string" ? x : String(x ?? "")).map((x) => x.trim()).filter((x) => x.length > 0);
if (Array.isArray(rawTargets)) {
return fromArray(rawTargets);
}
if (typeof rawTargets === "string") {
const t = rawTargets.trim();
if (!t) return [];
if (t.startsWith("[") && t.endsWith("]")) {
try {
const parsed = JSON.parse(t);
if (Array.isArray(parsed)) {
return fromArray(parsed);
}
} catch {
}
}
return t.split(/[\s,]+/).map((x) => x.trim()).filter((x) => x.length > 0);
}
if (rawTargets == null) return [];
return [String(rawTargets)];
}
function parseTargets(targets) {
const parsed = { a: [], v: [], i: [], p: [] };
const invalid = [];
for (const raw of targets) {
const s = normalizeTargetNotationToken(raw);
const m = /^([avip])(\d+)$/i.exec(s);
if (!m) {
invalid.push(String(raw));
continue;
}
const kind = m[1].toLowerCase();
const n = parseInt(m[2], 10);
if (!Number.isFinite(n) || n <= 0) {
invalid.push(String(raw));
continue;
}
parsed[kind].push(n);
}
Object.keys(parsed).forEach((k) => {
parsed[k] = Array.from(new Set(parsed[k])).sort((a, b) => a - b);
});
return { parsed, invalid };
}
function normalizeAliasRef(input) {
const raw = String(input ?? "");
let s = raw.trim();
s = s.replace(/[)\],.;]+$/g, "");
s = s.replace(/^"(.+)"$/g, "$1");
s = s.replace(/^'(.+)'$/g, "$1");
let url;
try {
if (/^https?:\/\//i.test(s)) url = new URL(s);
} catch {
url = void 0;
}
const basename3 = (() => {
try {
if (url) return path18.posix.basename(url.pathname || "") || void 0;
return path18.basename(s) || void 0;
} catch {
return void 0;
}
})();
return { raw, trimmed: s, basename: basename3, url };
}
function resolveAliasTargets(state, refs) {
const resolved = [];
const unresolved = [];
const pushUnique = (arr, v) => {
const s = String(v || "").trim();
if (!s) return;
if (!arr.includes(s)) arr.push(s);
};
const matchByExactOrBasename = (candidate, basename3, field) => {
const v = typeof field === "string" ? field.trim() : "";
if (!v) return false;
if (v === candidate) return true;
if (!basename3) return false;
try {
return path18.basename(v) === basename3;
} catch {
return false;
}
};
for (const r of refs) {
const n = normalizeAliasRef(r);
const s = n.trimmed;
if (!s) {
unresolved.push(String(r));
continue;
}
const candidates = /* @__PURE__ */ new Set();
for (const rec of state.pictures || []) {
if (!rec || typeof rec.p !== "number") continue;
const pVal = rec.p;
if (!Number.isFinite(pVal) || pVal <= 0) continue;
if (matchByExactOrBasename(s, n.basename, rec.sourceUrl))
candidates.add(`p${pVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.preview))
candidates.add(`p${pVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.filename))
candidates.add(`p${pVal}`);
}
for (const rec of state.images || []) {
if (!rec || typeof rec.i !== "number") continue;
const iVal = rec.i;
if (!Number.isFinite(iVal) || iVal <= 0) continue;
if (matchByExactOrBasename(s, n.basename, rec.preview))
candidates.add(`i${iVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.filename))
candidates.add(`i${iVal}`);
}
for (const rec of state.variants || []) {
if (!rec || typeof rec.v !== "number") continue;
const vVal = rec.v;
if (!Number.isFinite(vVal) || vVal <= 0) continue;
if (matchByExactOrBasename(s, n.basename, rec.preview))
candidates.add(`v${vVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.filename))
candidates.add(`v${vVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.sourceUrl))
candidates.add(`v${vVal}`);
}
for (const rec of state.attachments || []) {
if (!rec || typeof rec.a !== "number") continue;
const aVal = rec.a;
if (!Number.isFinite(aVal) || aVal <= 0) continue;
if (matchByExactOrBasename(s, n.basename, rec.originAbs))
candidates.add(`a${aVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.preview))
candidates.add(`a${aVal}`);
else if (matchByExactOrBasename(s, n.basename, rec.filename))
candidates.add(`a${aVal}`);
}
if (candidates.size === 1) {
pushUnique(resolved, Array.from(candidates)[0]);
continue;
}
unresolved.push(String(r));
}
return { resolved, unresolved };
}
function getAvailable(state) {
const availableA = (state.attachments || []).map((x) => typeof x?.a === "number" ? x.a : void 0).filter((x) => typeof x === "number" && x > 0).sort((a, b) => a - b);
const availableV = (state.variants || []).map((x) => typeof x?.v === "number" ? x.v : void 0).filter((x) => typeof x === "number" && x > 0).sort((a, b) => a - b);
const availableI = (state.images || []).map((x) => typeof x?.i === "number" ? x.i : void 0).filter((x) => typeof x === "number" && x > 0).sort((a, b) => a - b);
const availableP = (state.pictures || []).map((x) => typeof x?.p === "number" ? x.p : void 0).filter((x) => typeof x === "number" && x > 0).sort((a, b) => a - b);
return { availableA, availableV, availableI, availableP };
}
async function ensurePreviewExists(chatWd, previewRel) {
const pAbs = path18.join(chatWd, previewRel);
await fs15.promises.access(pAbs, fs15.constants.F_OK);
}
function appendReviewToPluginLog(line) {
try {
const logsDir = getLogsDir();
if (!fs15.existsSync(logsDir)) fs15.mkdirSync(logsDir, { recursive: true });
fs15.appendFileSync(
path18.join(logsDir, "generate-image-plugin.log"),
`${localTimestamp2()} - ${line}
`
);
} catch {
}
}
function createReviewImageTool(ctl) {
return tool({
name: "review_image",
description: `Request a visual presentation of one or more media items: attachments, generated variants, or indexed pictures.
There are three distinct media classes, each with different visibility behaviour:
- Attachments (aN): Images provided by the user. Displayed automatically on first appearance. Use review_image to re-examine them \u2014 for a closer look, re-evaluation, or comparison.
- Variants (vN): Images or video previews produced by generate_image. Also displayed automatically once. Use review_image to bring them back into view for inspection or side-by-side comparison.
- Pictures (pN): A separate class \u2014 typically search results returned by index_image, often in larger quantities. Pictures are NEVER shown automatically and are only made visible on explicit request via this tool.
Since each attachment and variant renders only once automatically, review_image is also the reliable way to place any selection of results side-by-side for direct visual comparison.
Parameters:
- targets: One or more media references (array or single string).
- 'aN' \u2014 Attachment N (e.g. 'a1', 'a2')
- 'vN' \u2014 Generated variant N (e.g. 'v1', 'v3')
- 'pN' \u2014 Picture / index result N (e.g. 'p1', 'p2')
- Shorthand: 'a'\u2192'a1', 'v'\u2192'v1', 'p'\u2192'p1'
Examples:
- ["v1", "v2", "v3"] \u2014 Compare three generated variants side-by-side
- ["p1", "p2", "p3"] \u2014 Inspect the top results from an index_image search
- ["a1", "v1"] \u2014 Compare the original attachment with a generated result
- ["v1"] \u2014 Re-examine a single variant for closer review
${formatToolMetaBlock()}`,
parameters: ReviewImageParamsShape,
implementation: async (args, ctx) => {
try {
try {
const workingDir2 = ctl.getWorkingDirectory();
if (typeof workingDir2 === "string" && workingDir2.trim().length > 0) {
const chatId2 = path18.basename(workingDir2);
if (/^\d+$/.test(chatId2)) {
setActiveChatContext({
chatId: chatId2,
workingDir: workingDir2,
requestId: `tool-${Date.now()}`
});
}
}
} catch {
}
const previewInChat = String(process.env.PREVIEW_IN_CHAT || "").toLowerCase() === "true";
if (previewInChat) {
const msg = 'review_image can\u2019t be used while "Simple Previews in Chat" is enabled. In that mode the plugin does not keep the local preview/index files required for re-view. Turn OFF "Simple Previews in Chat" in Global Plugin Settings, then call review_image again.';
try {
ctx.status(
"review_image unavailable: Simple Previews in Chat is enabled"
);
} catch {
}
return msg;
}
const strict = args?.strict !== void 0 ? !!args.strict : true;
const targets = normalizeTargetsArg(args?.targets);
const { parsed, invalid: invalidRaw } = parseTargets(targets);
const workingDir = ctl.getWorkingDirectory();
if (typeof workingDir !== "string" || !workingDir.trim()) {
return "review_image failed: working directory not available.";
}
const chatWd = workingDir;
const chatId = (() => {
try {
return path18.basename(chatWd);
} catch {
return "(unknown)";
}
})();
const state = await readState(chatWd);
const { resolved: resolvedAliases, unresolved: unresolvedAliases } = invalidRaw.length > 0 ? resolveAliasTargets(state, invalidRaw) : { resolved: [], unresolved: [] };
if (resolvedAliases.length > 0) {
const { parsed: parsed2, invalid: stillInvalid } = parseTargets(
resolvedAliases
);
parsed.a.push(...parsed2.a);
parsed.v.push(...parsed2.v);
parsed.i.push(...parsed2.i);
parsed.p.push(...parsed2.p);
unresolvedAliases.push(...stillInvalid);
}
Object.keys(parsed).forEach((k) => {
parsed[k] = Array.from(new Set(parsed[k])).sort((a, b) => a - b);
});
if (unresolvedAliases.length > 0 && strict) {
return `review_image failed: invalid targets: ${unresolvedAliases.map((s) => JSON.stringify(s)).join(", ")}`;
}
const { availableA, availableV, availableI, availableP } = getAvailable(state);
const missingNotations = /* @__PURE__ */ new Set();
const missingDetails = [];
for (const n of parsed.a) {
const rec = (state.attachments || []).find((x) => x?.a === n);
if (!rec) {
missingNotations.add(`a${n}`);
missingDetails.push(`a${n}`);
continue;
}
const previewRel = typeof rec.preview === "string" ? String(rec.preview) : "";
if (!previewRel.trim()) {
missingNotations.add(`a${n}`);
missingDetails.push(`a${n} (missing preview)`);
continue;
}
try {
await ensurePreviewExists(chatWd, previewRel);
} catch {
missingNotations.add(`a${n}`);
missingDetails.push(`a${n} (preview file missing)`);
}
}
for (const n of parsed.v) {
const rec = (state.variants || []).find((x) => x?.v === n);
if (!rec) {
missingNotations.add(`v${n}`);
missingDetails.push(`v${n}`);
continue;
}
const previewRel = typeof rec.preview === "string" ? String(rec.preview) : "";
if (!previewRel.trim()) {
missingNotations.add(`v${n}`);
missingDetails.push(`v${n} (missing preview)`);
continue;
}
try {
await ensurePreviewExists(chatWd, previewRel);
} catch {
missingNotations.add(`v${n}`);
missingDetails.push(`v${n} (preview file missing)`);
}
}
for (const n of parsed.i) {
const rec = (state.images || []).find((x) => x?.i === n);
if (!rec) {
missingNotations.add(`i${n}`);
missingDetails.push(`i${n}`);
continue;
}
const previewRel = typeof rec.preview === "string" ? String(rec.preview) : "";
if (!previewRel.trim()) {
missingNotations.add(`i${n}`);
missingDetails.push(`i${n} (missing preview)`);
continue;
}
try {
await ensurePreviewExists(chatWd, previewRel);
} catch {
missingNotations.add(`i${n}`);
missingDetails.push(`i${n} (preview file missing)`);
}
}
for (const n of parsed.p) {
const rec = (state.pictures || []).find((x) => x?.p === n);
if (!rec) {
missingNotations.add(`p${n}`);
missingDetails.push(`p${n}`);
continue;
}
const previewRel = typeof rec.preview === "string" ? String(rec.preview) : "";
if (!previewRel.trim()) {
missingNotations.add(`p${n}`);
missingDetails.push(`p${n} (missing preview)`);
continue;
}
try {
await ensurePreviewExists(chatWd, previewRel);
} catch {
missingNotations.add(`p${n}`);
missingDetails.push(`p${n} (preview file missing)`);
}
}
if (missingDetails.length > 0 && strict) {
const hint = `Available: a=[${availableA.map((x) => `a${x}`).join(", ") || "(none)"}] v=[${availableV.map((x) => `v${x}`).join(", ") || "(none)"}] i=[${availableI.map((x) => `i${x}`).join(", ") || "(none)"}] p=[${availableP.map((x) => `p${x}`).join(", ") || "(none)"}]`;
return `review_image failed: unknown/invalid targets: ${missingDetails.join(", ")}. ${hint}`;
}
if (!strict && missingNotations.size > 0) {
parsed.a = parsed.a.filter((n) => !missingNotations.has(`a${n}`));
parsed.v = parsed.v.filter((n) => !missingNotations.has(`v${n}`));
parsed.i = parsed.i.filter((n) => !missingNotations.has(`i${n}`));
parsed.p = parsed.p.filter((n) => !missingNotations.has(`p${n}`));
}
const requestedByToolCallId = (() => {
try {
const anyCtx = ctx;
const v = anyCtx?.toolCallId ?? anyCtx?.tool_call_id ?? anyCtx?.id ?? anyCtx?.callId;
return typeof v === "string" && v.trim() ? v.trim() : void 0;
} catch {
return void 0;
}
})();
state.pendingReviewPromotion = {
requestedAt: localTimestamp2(),
requestedByToolCallId,
ttlMs: 5 * 60 * 1e3,
targets: {
a: parsed.a.length ? parsed.a : void 0,
v: parsed.v.length ? parsed.v : void 0,
i: parsed.i.length ? parsed.i : void 0,
p: parsed.p.length ? parsed.p : void 0
}
};
await writeStateAtomic(chatWd, state);
const scheduled = [];
scheduled.push(...parsed.a.map((n) => `a${n}`));
scheduled.push(...parsed.v.map((n) => `v${n}`));
scheduled.push(...parsed.i.map((n) => `i${n}`));
scheduled.push(...parsed.p.map((n) => `p${n}`));
try {
appendReviewToPluginLog(
`VIP review scheduled: chatId=${chatId} targets=[${scheduled.join(",")}]` + (requestedByToolCallId ? ` toolCallId=${requestedByToolCallId}` : "")
);
} catch {
}
try {
ctx.status(`Scheduled re-view for: ${scheduled.join(", ")}`);
} catch {
}
return `Scheduled re-view for: ${scheduled.join(", ")}`;
} catch (e) {
return `review_image failed: ${String(e?.message || e)}`;
}
}
});
}
export {
ATTACHMENT_CONFIG,
ATTACHMENT_FULL_CONFIG,
CompressionMethod,
Control,
ControlInputType,
ControlMode,
DEFAULT_RECONCILE_OPTIONS,
FASTVLM_DEFAULT_ENDPOINT,
GenerateToolParamsSchema,
GenerateToolParamsSchemaBase,
GenerateToolParamsSchemaMinimalStrict,
GenerateToolParamsShapeMinimal,
GenerationConfiguration,
IMAGE_CONFIG,
IMAGE_FULL_CONFIG,
IMAGE_MODEL_CAPABILITY_MAP,
INJECTION_END,
INJECTION_START,
LoRA,
LoRAMode,
MEDIA_HARVESTING_CONFIG,
MEDIA_SOURCE_REGISTRY,
MLX_VISION_ANALYZE_IMAGE_PROMPT,
MLX_VISION_INDEX_IMAGE_PROMPT,
MODEL_PRESET_TO_CAPABILITY_KEY,
PICTURE_CONFIG,
PICTURE_FULL_CONFIG,
SamplerType,
SeedMode,
VARIANT_CONFIG,
VARIANT_FULL_CONFIG,
VISION_WINDOW_SIZES,
appendImages,
appendMediaItems,
appendPictures,
appendPluginLog,
appendVariants,
buildAuditLogger,
buildPromotionItems,
checkDrawThingsGrpcAssets,
checkDrawThingsGrpcFilesExist,
checkModeSupport,
checkModeSupportWithCustom,
checkVisionPrimerStatus,
clampNumber,
clearInjections,
configSchematics,
createReviewImageTool,
deepParseMaybeJson,
defaultParams,
defaultParamsImg2Img as defaultParamsEdit,
defaultParamsImg2Img2 as defaultParamsImg2Img,
detectCapabilities,
detectImageModelCapabilities,
detectProjectImageType,
drawthingsLimits as drawthingsEditLimits,
drawthingsLimits,
encodeJpegPreview,
encodeJpegPreviewFromBuffer,
engineConnectionDefaults,
ensureAgentModelLoaded,
ensureLogsDir,
extractPendingAttachments,
extractToolCalls,
extractToolResultImageCandidates,
extractUserAttachments,
fileInWd,
findAllAttachmentsLegacy,
findAllImages,
findAllMedia,
findAllMediaAllTypes,
findAllPictures,
findAllVariantsLegacy,
findConversationPath,
findLMStudioHome,
findLastAttachmentLegacy,
findLastImages,
findLastMedia,
findLastPictures,
findLmsCli,
findMessagesArray,
fluxOverlays,
formatToolMetaBlock,
formatZodError,
generatePreview,
generatePreviewFromBuffer,
generateRuntimeDefaults,
getActiveChatContext,
getAllMediaTypeConfigs,
getAllMediaTypeFullConfigs,
getAllowlistedPictureTools,
getAttachmentWindowSize,
getAudioSampleRateForModel,
getCapabilityKeyForPreset,
getDefaultFpsForModel,
getEngineConnectionDefaults,
getEverythingElseWindowSize,
getHealthyServerBaseUrl,
getInjectionCount,
getLMStudioFileMetadata,
getLMStudioUserFilePath,
getLMStudioWorkingDir,
getLogsDir,
getMediaTypeConfig,
getMediaTypeForTool,
getMediaTypeForToolCall,
getMediaTypeFullConfig,
getMessageRole,
getOriginalFileName,
getPluginMeta,
getProjectRoot,
getPromotableAttachmentNs,
getPromotableEverythingElse,
getPromotableImageIs,
getPromotableVariantVs,
getSelfPluginIdentifier,
getSize,
getStateArrayKey,
getThumbnailFromProject,
getToolExtractor,
getWindowSizeForKind,
globalConfigSchematics,
guessExtForOriginal,
harvestToolResultImages,
hasInjections,
hasTrackedInjections,
importAttachmentBatch,
isAllowedOriginalExt,
isAllowlistedSsotToolCall,
isHttpServerHealthy,
isModelInstalled,
isModelLoaded,
isPictureToolAllowlisted,
isProjectUri,
isToolAllowlistedForMediaType,
loadVisionPrimerModel,
localTimestamp2 as localTimestamp,
materializeToolResultImageToFiles,
normalizeQualityToInt,
normalizeToolPluginId,
parseMessages,
parseProjectUri,
parseToolResultPayload,
preprocess,
previewDefaults,
previewFilenameFrom,
primeVisionCapability,
qwenImageOverlays,
readConversation,
readLastAttachmentRef,
readState,
reconcileMedia,
recordGeneratedVariants,
referencedFiles,
rehydrateLastAttachment,
renderPictureMarkdown,
resizeAndEncode,
resizeAndEncodeByHeight,
resizeAndEncodeBySum,
resizeCoverToPng,
resizeInsideToPng,
resolveActions,
resolveActiveLMStudioChatId,
resolveImg2ImgSourceLMStudio,
resolveInjection,
resolveMessageVersion,
resolveProjectUri,
scanMedia,
selectAutoModel,
setActiveChatContext,
shouldPromoteAttachments,
shouldPromoteEverythingElseIdempotent,
shouldPromoteVariantsIdempotent,
shouldUseFilesApiForModel,
stripInjections,
toHttpOriginalUrl,
toHttpPreviewUrl,
toOpenAIPromptParts,
toPng,
trackInjection,
tsForFilename,
unloadAgentModel,
unloadVisionPrimer,
updateLastUsedModelForAgentModel,
upsertAttachment,
validateImageGenerationParams,
wrapInjection,
writeStateAtomic,
zImageOverlays
};