import { DOMPurify } from '../lib.js'; import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js'; import { chat_completion_sources, model_list, oai_settings } from './openai.js'; import { Popup } from './popup.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; import { isTrueBoolean } from './utils.js'; /** * @typedef {object} ToolInvocation * @property {string} id - A unique identifier for the tool invocation. * @property {string} displayName - The display name of the tool. * @property {string} name - The name of the tool. * @property {string} parameters - The parameters for the tool invocation. * @property {string} result - The result of the tool invocation. */ /** * @typedef {object} ToolInvocationResult * @property {ToolInvocation[]} invocations Successful tool invocations * @property {Error[]} errors Errors that occurred during tool invocation * @property {string[]} stealthCalls Names of stealth tools that were invoked */ /** * @typedef {object} ToolRegistration * @property {string} name - The name of the tool. * @property {string} displayName - The display name of the tool. * @property {string} description - A description of the tool. * @property {object} parameters - The parameters for the tool. * @property {function} action - The action to perform when the tool is invoked. * @property {function} [formatMessage] - A function to format the tool call message. * @property {function} [shouldRegister] - A function to determine if the tool should be registered. * @property {boolean} [stealth] - A tool call result will not be shown in the chat. No follow-up generation will be performed. */ /** * @typedef {object} ToolDefinitionOpenAI * @property {string} type - The type of the tool. * @property {object} function - The function definition. * @property {string} function.name - The name of the function. * @property {string} function.description - The description of the function. * @property {object} function.parameters - The parameters of the function. * @property {function} toString - A function to convert the tool to a string. */ /** * Assigns nested variables to a scope. * @param {import('./slash-commands/SlashCommandScope.js').SlashCommandScope} scope The scope to assign variables to. * @param {object} arg Object to assign variables from. * @param {string} prefix Prefix for the variable names. */ function assignNestedVariables(scope, arg, prefix) { Object.entries(arg).forEach(([key, value]) => { const newPrefix = `${prefix}.${key}`; if (typeof value === 'object' && value !== null) { if (Array.isArray(value)) { scope.letVariable(newPrefix, JSON.stringify(value)); } assignNestedVariables(scope, value, newPrefix); } else { scope.letVariable(newPrefix, value); } }); } /** * Checks if a string is a valid JSON string. * @param {string} str The string to check * @returns {boolean} If the string is a valid JSON string */ function isJson(str) { try { JSON.parse(str); return true; } catch { return false; } } /** * Tries to parse a string as JSON, returning the original string if parsing fails. * @param {string} str The string to try to parse * @returns {object|string} Parsed JSON or the original string */ function tryParse(str) { try { return JSON.parse(str); } catch { return str; } } /** * Stringifies an object if it is not already a string. * @param {any} obj The object to stringify * @returns {string} A JSON string representation of the object. */ function stringify(obj) { return typeof obj === 'string' ? obj : JSON.stringify(obj); } /** * A class that represents a tool definition. */ class ToolDefinition { /** * A unique name for the tool. * @type {string} */ #name; /** * A user-friendly display name for the tool. * @type {string} */ #displayName; /** * A description of what the tool does. * @type {string} */ #description; /** * A JSON schema for the parameters that the tool accepts. * @type {object} */ #parameters; /** * A function that will be called when the tool is executed. * @type {function} */ #action; /** * A function that will be called to format the tool call toast. * @type {function} */ #formatMessage; /** * A function that will be called to determine if the tool should be registered. * @type {function} */ #shouldRegister; /** * A tool call result will not be shown in the chat. No follow-up generation will be performed. * @type {boolean} */ #stealth; /** * Creates a new ToolDefinition. * @param {string} name A unique name for the tool. * @param {string} displayName A user-friendly display name for the tool. * @param {string} description A description of what the tool does. * @param {object} parameters A JSON schema for the parameters that the tool accepts. * @param {function} action A function that will be called when the tool is executed. * @param {function} formatMessage A function that will be called to format the tool call toast. * @param {function} shouldRegister A function that will be called to determine if the tool should be registered. * @param {boolean} stealth A tool call result will not be shown in the chat. No follow-up generation will be performed. */ constructor(name, displayName, description, parameters, action, formatMessage, shouldRegister, stealth) { this.#name = name; this.#displayName = displayName; this.#description = description; this.#parameters = parameters; this.#action = action; this.#formatMessage = formatMessage; this.#shouldRegister = shouldRegister; this.#stealth = stealth; } /** * Converts the ToolDefinition to an OpenAI API representation * @returns {ToolDefinitionOpenAI} OpenAI API representation of the tool. */ toFunctionOpenAI() { return { type: 'function', function: { name: this.#name, description: this.#description, parameters: this.#parameters, }, toString: function () { return `
${JSON.stringify(this.function.parameters, null, 2)}return argument to specify the return value type.',
            returns: 'A list of all registered tools.',
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'return',
                    description: 'The way how you want the return value to be provided',
                    typeList: [ARGUMENT_TYPE.STRING],
                    defaultValue: 'none',
                    enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
                    forceEnum: true,
                }),
            ],
            callback: async (args) => {
                /** @type {any} */
                const returnType = String(args?.return ?? 'popup-html').trim().toLowerCase();
                const objectToStringFunc = (tools) => Array.isArray(tools) ? tools.map(x => x.toString()).join('\n\n') : tools.toString();
                const tools = ToolManager.tools.map(tool => tool.toFunctionOpenAI());
                return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', tools ?? [], { objectToStringFunc });
            },
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'tools-invoke',
            aliases: ['tool-invoke'],
            helpString: 'Invokes a registered tool by name. The parameters argument MUST be a JSON-serialized object.',
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'parameters',
                    description: 'The parameters to pass to the tool.',
                    typeList: [ARGUMENT_TYPE.DICTIONARY],
                    isRequired: true,
                    acceptsMultiple: false,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'The name of the tool to invoke.',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    acceptsMultiple: false,
                    forceEnum: true,
                    enumProvider: toolsEnumProvider,
                }),
            ],
            callback: async (args, name) => {
                const { parameters } = args;
                const result = await ToolManager.invokeFunctionTool(String(name), parameters);
                if (result instanceof Error) {
                    throw result;
                }
                return result;
            },
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'tools-register',
            aliases: ['tool-register'],
            helpString: `parameters argument MUST be a JSON-serialized object with a valid JSON schema./let key=echoSchema
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "message": {
            "type": "string",
            "description": "The message to echo."
        }
    },
    "required": [
        "message"
    ]
}
||
/tools-register name=Echo description="Echoes a message. Call when the user is asking to repeat something" parameters={{var::echoSchema}} {: /echo {{var::arg.message}} :}