Project Files
src / tools / mask.ts
import { tool, type Tool, type ToolsProviderController, type ToolCallContext } from "@lmstudio/sdk";
import { z } from "zod";
import { CropToolParamsShape, formatToolMetaBlock, setActiveChatContext, syncAttachmentsToState } from "../core-bundle.mjs";
import path from "path";
export function createMaskTool(ctl: ToolsProviderController): Tool {
return tool({
name: "mask",
description: `Define a region of interest on an image by drawing a CYAN bounding box.
NOTE — If you have access to 'detect_object': Use detectLabel to place the mask directly on a detected object's bounding box.
- detectLabel: label text to match (e.g. 'cat', 'a person') — case-insensitive substring match.
- detectIndex: zero-based index when multiple matches exist (default: 0).
- frameAdjust: expand (+) or shrink (-) the box. Number = % of bbox dimensions (preserves AR); string with 'px' suffix = absolute pixel margin. E.g. 5, -3, '20px'.
canvas may be the original source (e.g. 'a1') or the annotated detect_object result (e.g. 'i3').
Example: canvas='i3', detectLabel='cat', frameAdjust=5
IMPORTANT — Without detect_object (iterative procedure, do NOT set all sides at once):
1. Set cropLeft only → inspect result.
2. Keep cropLeft, set cropRight only → inspect.
3. Keep both, set cropTop only → inspect.
4. Set imageFormat to lock the aspect ratio (derives cropBottom automatically).
At tight crops, 1–3% per step is enough. Symmetric values do NOT guarantee a centred subject — judge by visual gap, not numbers.
Parameters:
- canvas: Source image. Notation: 'a1', 'v2', 'p1', 'i3'.
- cropLeft / cropRight / cropTop / cropBottom: Amount to remove per edge. Default unit: % (0–99). Optionally append 'px' for absolute pixels (e.g. '120px' or '10 %').
- imageFormat: Target aspect ratio ('square'=1:1, 'landscape'=4:3, 'portrait'=3:4, '16:9').
Only active when explicitly set. Ignored when all 4 sides are given.
Per axis: neither side → centred; one side → anchored, opposite defaults to 0; both → locked.
The result is saved as an iN image with cropLeft/cropRight/cropTop/cropBottom metadata.
Pass it to 'inpaint' or 'outpaint' as canvas to use the marked region as the mask.
Returns: The annotated image saved to the chat working directory as a new iN image.
${formatToolMetaBlock()}`,
parameters: CropToolParamsShape as Record<string, z.ZodTypeAny>,
implementation: async (args: any, ctx: ToolCallContext) => {
try {
try {
const workingDir = ctl.getWorkingDirectory();
if (typeof workingDir === "string" && workingDir.trim().length > 0) {
const chatId = path.basename(workingDir);
if (/^\d+$/.test(chatId)) {
setActiveChatContext({ chatId, workingDir, requestId: `tool-${Date.now()}` });
}
}
} catch {}
try {
const workingDir = ctl.getWorkingDirectory();
if (typeof workingDir === "string" && workingDir.trim().length > 0) {
await syncAttachmentsToState(workingDir, false, Number.MAX_SAFE_INTEGER);
}
} catch {}
const mod = await import("../core/tools.js");
const resp = await mod.handleMask(args);
if (resp && Array.isArray(resp.content)) return resp.content;
return typeof resp === "string" ? resp : JSON.stringify(resp);
} catch (e) {
return `mask failed: ${String((e as any)?.message || e)}`;
}
},
});
}