Project Files
src / index.ts
import { type PluginContext } from "@lmstudio/sdk";
import fs from "node:fs";
import path from "node:path";
import { configSchematics, globalConfigSchematics } from "./config.js";
import { localTimestamp } from "./helpers/localTimestamp.js";
import { stopActiveLlamaServerEmbeddingServer } from "./llama-server-manager.js";
import { toolsProvider } from "./toolsProvider.js";
// Find Image Plugin
// - Tools: find_image for searching generation history
let lifecycleDiagnosticsInstalled = false;
let embeddingServerCleanupStarted = false;
function writeLifecycleDiagnostic(message: string) {
try {
const logsDir = path.join(process.cwd(), "logs");
fs.mkdirSync(logsDir, { recursive: true });
fs.appendFileSync(
path.join(logsDir, "find-image-plugin-lifecycle.log"),
`${localTimestamp()} pid=${process.pid} ${message}\n`
);
} catch {
// Diagnostics must never change plugin behavior.
}
}
function errorText(error: unknown): string {
if (error instanceof Error) {
return `${error.name}: ${error.message}\n${error.stack ?? ""}`.trim();
}
return String(error);
}
async function stopEmbeddingSidecars(reason: string): Promise<void> {
if (embeddingServerCleanupStarted) return;
embeddingServerCleanupStarted = true;
writeLifecycleDiagnostic(`cleanup start reason=${reason}`);
try {
await stopActiveLlamaServerEmbeddingServer();
writeLifecycleDiagnostic(`cleanup gguf stopped`);
} catch (error) {
writeLifecycleDiagnostic(`cleanup gguf skipped_or_failed ${errorText(error)}`);
}
writeLifecycleDiagnostic(`cleanup done reason=${reason}`);
}
function installLifecycleDiagnostics() {
if (lifecycleDiagnosticsInstalled) return;
lifecycleDiagnosticsInstalled = true;
writeLifecycleDiagnostic(`start cwd=${process.cwd()}`);
// Tool-level cleanup owns the normal embedding lifecycle. These handlers
// only record process diagnostics; real OS termination also attempts a
// final best-effort cleanup.
process.on("beforeExit", (code) => {
writeLifecycleDiagnostic(`beforeExit code=${code}`);
});
process.on("exit", (code) => {
writeLifecycleDiagnostic(`exit code=${code}`);
});
process.on("uncaughtException", (error) => {
writeLifecycleDiagnostic(`uncaughtException ${errorText(error)}`);
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
writeLifecycleDiagnostic(`unhandledRejection ${errorText(reason)}`);
});
process.on("disconnect", () => {
writeLifecycleDiagnostic("disconnect");
});
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
process.once(signal, async () => {
writeLifecycleDiagnostic(`signal ${signal}`);
await stopEmbeddingSidecars(`signal ${signal}`);
process.exit(128 + (signal === "SIGINT" ? 2 : signal === "SIGTERM" ? 15 : 1));
});
}
}
export async function main(context: PluginContext) {
installLifecycleDiagnostics();
context
.withConfigSchematics(configSchematics)
.withGlobalConfigSchematics(globalConfigSchematics)
.withToolsProvider(toolsProvider);
}