dist / toolsProvider.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.notificationConfigSchematics = void 0;
exports.toolsProvider = toolsProvider;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const child_process_1 = require("child_process");
/**
* Lazily load node-notifier. It is a CommonJS module; under the LM Studio
* runtime the resolved namespace exposes the notifier on `.default`, so we
* always reach `notify` via that path (see regression test #26 in the tools
* repo). The result is cached across tool calls.
*/
let cachedNotifier;
/**
* Timeout value used when "Persistent notification" is enabled. This is the
* highest practical value for node-notifier's macOS timeout; effectively keeps
* the notification on screen until the user dismisses it.
*/
const MAX_TIMEOUT = 300;
function getNotifier() {
if (!cachedNotifier) {
const mod = require("node-notifier");
cachedNotifier = (mod && mod.default) || mod;
}
return cachedNotifier;
}
/**
* Sends a notification via node-notifier's notify(). Fires and forgets — the
* LLM is not blocked waiting for user interaction with the notification.
*/
const activateLMStudio = () => {
if (process.platform === "darwin") {
(0, child_process_1.exec)('osascript -e \'tell application "LM Studio" to activate\'', (execErr) => {
if (execErr)
console.log("Failed to open LM Studio:", execErr);
});
}
};
// Register event listener for notification-area clicks (not action buttons).
// Action button clicks are handled via the notify() callback's metadata.
const notifier = getNotifier();
function sendNotification(opts) {
const notifyOptions = {
title: opts.title,
message: opts.message,
};
// if (opts.subtitle) notifyOptions.subtitle = opts.subtitle;
if (opts.sound === true)
notifyOptions.sound = true;
if (opts.timeout != null && Number.isFinite(opts.timeout))
notifyOptions.timeout = opts.timeout;
// On macOS, add an action button that brings LM Studio to the foreground.
if (process.platform === "darwin") {
notifyOptions.actions = ["Open LM Studio"];
}
console.log(notifyOptions);
// Fire-and-forget: resolve immediately so the LLM is not blocked.
// The callback handles action button clicks via metadata.activationValue;
// node-notifier's event system does not emit named events for custom actions on macOS.
notifier.notify(notifyOptions, (err, response, metadata) => {
console.log(metadata);
if (metadata?.activationValue === "Open LM Studio" || metadata?.activationType === "contentsClicked") {
activateLMStudio();
}
});
return { success: true };
}
/**
* Parameters for `send_notification`, expressed as a Zod raw shape.
*
* `tool()` wraps this object in `z.object(shape)` internally, so it must be an
* object of zod schema instances (a ZodRawShape) — never a pre-built
* `z.object(...)` and never a raw JSON Schema. Only the simplest forms are used
* here (`z.string()` / `z.boolean().optional()` / `z.array(z.string())`);
* refinements like `.min()` / `.describe()` / `.int()` were avoided because they
* crashed the SDK's zod -> json-schema extraction in earlier builds.
*/
const sendNotificationParameters = {
title: zod_1.z.string(),
message: zod_1.z.string(),
// subtitle: z.string().optional(),
sound: zod_1.z.boolean().optional(),
timeout: zod_1.z.number().optional(),
actions: zod_1.z.string().optional(),
};
/**
* User-configurable global settings exposed in the LM Studio plugin UI.
* Registered via `context.withGlobalConfigSchematics()` in index.ts and read
* inside the tool implementation through `ctl.getGlobalPluginConfig()`. These
* provide sensible defaults that the model can still override per call.
*/
exports.notificationConfigSchematics = (0, sdk_1.createConfigSchematics)()
.field("sound", "boolean", { displayName: "Play a system sound" }, false)
.field("persist", "boolean", {
displayName: "Persistent notification",
hint: "Keep the notification on screen until the user acts. When enabled, any 'timeout' value is ignored.",
}, false)
.field("timeout", "numeric", {
displayName: "Auto-dismiss timeout (seconds)",
hint: "Time before the notification disappears; ignored when 'Persistent notification' is on.",
min: 1,
max: 300,
step: 1,
int: true,
// Grey out / disable this field whenever 'persist' is enabled. The field
// stays available only while the referenced 'persist' field does NOT equal
// true (see KVConfigFieldDependency in @lmstudio/sdk).
dependencies: [
{ key: "persist", condition: { type: "notEquals", value: true } },
],
}, 10)
.build();
/**
* Tools provider that exposes the send_notification tool to LM Studio models.
*/
async function toolsProvider(ctl) {
// Read the user-configurable global defaults registered in index.ts.
const config = ctl.getGlobalPluginConfig(exports.notificationConfigSchematics);
const defaultSound = config.get("sound");
const defaultPersist = config.get("persist") === true;
const defaultTimeout = config.get("timeout");
return [
(0, sdk_1.tool)({
name: "send_notification",
description: "Send a system notification. The LLM should pass a short 'title' and 'message'.",
parameters: sendNotificationParameters,
implementation: async (args) => {
// Apply sensible defaults for optional fields not provided by the model.
const title = args.title ?? "";
const message = args.message ?? "";
// const subtitle = args.subtitle ?? "";
const sound = args.sound === true || (args.sound === undefined && defaultSound);
const timeout = (args.timeout != null && Number.isFinite(args.timeout) && args.timeout >= 1)
? args.timeout
: defaultTimeout;
const actions = args.actions ?? "";
return sendNotification({
title,
message,
// subtitle: subtitle || undefined,
sound: sound === true ? true : undefined,
icon: "/Users/brot/Sites/dev/llm/lms-notification/assets/lm-studio-logo.png",
timeout: defaultPersist ? MAX_TIMEOUT : (timeout != null && Number.isFinite(timeout) ? timeout : undefined),
actions: actions || undefined,
});
},
}),
];
}
//# sourceMappingURL=toolsProvider.js.mapdist / toolsProvider.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.notificationConfigSchematics = void 0;
exports.toolsProvider = toolsProvider;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const child_process_1 = require("child_process");
/**
* Lazily load node-notifier. It is a CommonJS module; under the LM Studio
* runtime the resolved namespace exposes the notifier on `.default`, so we
* always reach `notify` via that path (see regression test #26 in the tools
* repo). The result is cached across tool calls.
*/
let cachedNotifier;
/**
* Timeout value used when "Persistent notification" is enabled. This is the
* highest practical value for node-notifier's macOS timeout; effectively keeps
* the notification on screen until the user dismisses it.
*/
const MAX_TIMEOUT = 300;
function getNotifier() {
if (!cachedNotifier) {
const mod = require("node-notifier");
cachedNotifier = (mod && mod.default) || mod;
}
return cachedNotifier;
}
/**
* Sends a notification via node-notifier's notify(). Fires and forgets — the
* LLM is not blocked waiting for user interaction with the notification.
*/
const activateLMStudio = () => {
if (process.platform === "darwin") {
(0, child_process_1.exec)('osascript -e \'tell application "LM Studio" to activate\'', (execErr) => {
if (execErr)
console.log("Failed to open LM Studio:", execErr);
});
}
};
// Register event listener for notification-area clicks (not action buttons).
// Action button clicks are handled via the notify() callback's metadata.
const notifier = getNotifier();
function sendNotification(opts) {
const notifyOptions = {
title: opts.title,
message: opts.message,
};
// if (opts.subtitle) notifyOptions.subtitle = opts.subtitle;
if (opts.sound === true)
notifyOptions.sound = true;
if (opts.timeout != null && Number.isFinite(opts.timeout))
notifyOptions.timeout = opts.timeout;
// On macOS, add an action button that brings LM Studio to the foreground.
if (process.platform === "darwin") {
notifyOptions.actions = ["Open LM Studio"];
}
console.log(notifyOptions);
// Fire-and-forget: resolve immediately so the LLM is not blocked.
// The callback handles action button clicks via metadata.activationValue;
// node-notifier's event system does not emit named events for custom actions on macOS.
notifier.notify(notifyOptions, (err, response, metadata) => {
console.log(metadata);
if (metadata?.activationValue === "Open LM Studio" || metadata?.activationType === "contentsClicked") {
activateLMStudio();
}
});
return { success: true };
}
/**
* Parameters for `send_notification`, expressed as a Zod raw shape.
*
* `tool()` wraps this object in `z.object(shape)` internally, so it must be an
* object of zod schema instances (a ZodRawShape) — never a pre-built
* `z.object(...)` and never a raw JSON Schema. Only the simplest forms are used
* here (`z.string()` / `z.boolean().optional()` / `z.array(z.string())`);
* refinements like `.min()` / `.describe()` / `.int()` were avoided because they
* crashed the SDK's zod -> json-schema extraction in earlier builds.
*/
const sendNotificationParameters = {
title: zod_1.z.string(),
message: zod_1.z.string(),
// subtitle: z.string().optional(),
sound: zod_1.z.boolean().optional(),
timeout: zod_1.z.number().optional(),
actions: zod_1.z.string().optional(),
};
/**
* User-configurable global settings exposed in the LM Studio plugin UI.
* Registered via `context.withGlobalConfigSchematics()` in index.ts and read
* inside the tool implementation through `ctl.getGlobalPluginConfig()`. These
* provide sensible defaults that the model can still override per call.
*/
exports.notificationConfigSchematics = (0, sdk_1.createConfigSchematics)()
.field("sound", "boolean", { displayName: "Play a system sound" }, false)
.field("persist", "boolean", {
displayName: "Persistent notification",
hint: "Keep the notification on screen until the user acts. When enabled, any 'timeout' value is ignored.",
}, false)
.field("timeout", "numeric", {
displayName: "Auto-dismiss timeout (seconds)",
hint: "Time before the notification disappears; ignored when 'Persistent notification' is on.",
min: 1,
max: 300,
step: 1,
int: true,
// Grey out / disable this field whenever 'persist' is enabled. The field
// stays available only while the referenced 'persist' field does NOT equal
// true (see KVConfigFieldDependency in @lmstudio/sdk).
dependencies: [
{ key: "persist", condition: { type: "notEquals", value: true } },
],
}, 10)
.build();
/**
* Tools provider that exposes the send_notification tool to LM Studio models.
*/
async function toolsProvider(ctl) {
// Read the user-configurable global defaults registered in index.ts.
const config = ctl.getGlobalPluginConfig(exports.notificationConfigSchematics);
const defaultSound = config.get("sound");
const defaultPersist = config.get("persist") === true;
const defaultTimeout = config.get("timeout");
return [
(0, sdk_1.tool)({
name: "send_notification",
description: "Send a system notification. The LLM should pass a short 'title' and 'message'.",
parameters: sendNotificationParameters,
implementation: async (args) => {
// Apply sensible defaults for optional fields not provided by the model.
const title = args.title ?? "";
const message = args.message ?? "";
// const subtitle = args.subtitle ?? "";
const sound = args.sound === true || (args.sound === undefined && defaultSound);
const timeout = (args.timeout != null && Number.isFinite(args.timeout) && args.timeout >= 1)
? args.timeout
: defaultTimeout;
const actions = args.actions ?? "";
return sendNotification({
title,
message,
// subtitle: subtitle || undefined,
sound: sound === true ? true : undefined,
icon: "/Users/brot/Sites/dev/llm/lms-notification/assets/lm-studio-logo.png",
timeout: defaultPersist ? MAX_TIMEOUT : (timeout != null && Number.isFinite(timeout) ? timeout : undefined),
actions: actions || undefined,
});
},
}),
];
}
//# sourceMappingURL=toolsProvider.js.map