src / tools / runProcessTool.ts
src / tools / runProcessTool.ts
import type { ToolsProviderController } from "@lmstudio/sdk";
import path from "path";
import {
createBackgroundRequestId,
createStatusProgressCallback,
runInBackgroundJob,
setActiveChatContext,
syncAttachmentsToState,
} from "../core-bundle.mjs";
export type BackgroundCapableTool = "zoom-in" | "inpaint" | "outpaint";
const HANDLER_NAME = {
"zoom-in": "handleZoomIn",
inpaint: "handleInpaint",
outpaint: "handleOutpaint",
} as const;
/**
* Shared implementation of zoom-in / inpaint / outpaint: refreshes the chat context, syncs
* attachments, then runs the core handler either in the foreground (default) or — with
* `runInBackground: true` — as a background job that returns a requestId immediately
* (results come from request_progress).
*/
export async function runProcessTool(
toolName: BackgroundCapableTool,
ctl: ToolsProviderController,
args: any,
ctx: any
): Promise<unknown> {
const { runInBackground, ...handlerArgs } = args ?? {};
const workingDir = ctl.getWorkingDirectory();
const hasWorkingDir = typeof workingDir === "string" && workingDir.trim().length > 0;
// Refresh on every call: the context TTL can expire while the model is thinking or Draw Things renders.
try {
if (hasWorkingDir) {
const chatId = path.basename(workingDir);
if (/^\d+$/.test(chatId)) {
setActiveChatContext({ chatId, workingDir, requestId: `tool-${Date.now()}` });
}
}
} catch {}
try {
if (hasWorkingDir) await syncAttachmentsToState(workingDir, false, Number.MAX_SAFE_INTEGER);
} catch {}
const mod: any = await import("../core/tools.js");
const handler = mod[HANDLER_NAME[toolName]];
if (typeof handler !== "function") return `${toolName} core handler not available.`;
if (runInBackground === true) {
if (!hasWorkingDir) {
return `${toolName} failed: runInBackground requires an active chat working directory.`;
}
const requestId = createBackgroundRequestId(path.basename(workingDir));
// Awaited: guarantees the record exists on disk before this tool call returns, so an
// immediate request_progress call in the same turn can never race ahead of it.
await runInBackgroundJob({
chatWd: workingDir,
requestId,
tool: toolName,
run: (bgOnProgress: any) => handler(handlerArgs, bgOnProgress, { requestIdOverride: requestId }),
});
return (
`${toolName} started in the background (requestId: ${requestId}). ` +
`Use request_progress with this requestId to check status or retrieve the result once it's done. ` +
`Do not call request_progress right away — wait for the user's next message or do other work first.`
);
}
const resp = await handler(handlerArgs, createStatusProgressCallback(ctx));
if (resp && Array.isArray(resp.content)) return resp.content;
return typeof resp === "string" ? resp : JSON.stringify(resp);
}
import type { ToolsProviderController } from "@lmstudio/sdk";
import path from "path";
import {
createBackgroundRequestId,
createStatusProgressCallback,
runInBackgroundJob,
setActiveChatContext,
syncAttachmentsToState,
} from "../core-bundle.mjs";
export type BackgroundCapableTool = "zoom-in" | "inpaint" | "outpaint";
const HANDLER_NAME = {
"zoom-in": "handleZoomIn",
inpaint: "handleInpaint",
outpaint: "handleOutpaint",
} as const;
/**
* Shared implementation of zoom-in / inpaint / outpaint: refreshes the chat context, syncs
* attachments, then runs the core handler either in the foreground (default) or — with
* `runInBackground: true` — as a background job that returns a requestId immediately
* (results come from request_progress).
*/
export async function runProcessTool(
toolName: BackgroundCapableTool,
ctl: ToolsProviderController,
args: any,
ctx: any
): Promise<unknown> {
const { runInBackground, ...handlerArgs } = args ?? {};
const workingDir = ctl.getWorkingDirectory();
const hasWorkingDir = typeof workingDir === "string" && workingDir.trim().length > 0;
// Refresh on every call: the context TTL can expire while the model is thinking or Draw Things renders.
try {
if (hasWorkingDir) {
const chatId = path.basename(workingDir);
if (/^\d+$/.test(chatId)) {
setActiveChatContext({ chatId, workingDir, requestId: `tool-${Date.now()}` });
}
}
} catch {}
try {
if (hasWorkingDir) await syncAttachmentsToState(workingDir, false, Number.MAX_SAFE_INTEGER);
} catch {}
const mod: any = await import("../core/tools.js");
const handler = mod[HANDLER_NAME[toolName]];
if (typeof handler !== "function") return `${toolName} core handler not available.`;
if (runInBackground === true) {
if (!hasWorkingDir) {
return `${toolName} failed: runInBackground requires an active chat working directory.`;
}
const requestId = createBackgroundRequestId(path.basename(workingDir));
// Awaited: guarantees the record exists on disk before this tool call returns, so an
// immediate request_progress call in the same turn can never race ahead of it.
await runInBackgroundJob({
chatWd: workingDir,
requestId,
tool: toolName,
run: (bgOnProgress: any) => handler(handlerArgs, bgOnProgress, { requestIdOverride: requestId }),
});
return (
`${toolName} started in the background (requestId: ${requestId}). ` +
`Use request_progress with this requestId to check status or retrieve the result once it's done. ` +
`Do not call request_progress right away — wait for the user's next message or do other work first.`
);
}
const resp = await handler(handlerArgs, createStatusProgressCallback(ctx));
if (resp && Array.isArray(resp.content)) return resp.content;
return typeof resp === "string" ? resp : JSON.stringify(resp);
}