| 578 | timeOut: 5000, | 641 | timeOut: 5000, |
| 579 | }); | 642 | }); |
| 580 | } | 643 | } |
| | 644 | |
| | 645 | static initToolSlashCommands() { |
| | 646 | const toolsEnumProvider = () => ToolManager.tools.map(tool => { |
| | 647 | const toolOpenAI = tool.toFunctionOpenAI(); |
| | 648 | return new SlashCommandEnumValue(toolOpenAI.function.name, toolOpenAI.function.description, enumTypes.enum, enumIcons.closure); |
| | 649 | }); |
| | 650 | |
| | 651 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| | 652 | name: 'tools-list', |
| | 653 | aliases: ['tool-list'], |
| | 654 | helpString: 'Gets a list of all registered tools in the OpenAI function JSON format. Use the <code>return</code> argument to specify the return value type.', |
| | 655 | returns: 'A list of all registered tools.', |
| | 656 | namedArgumentList: [ |
| | 657 | SlashCommandNamedArgument.fromProps({ |
| | 658 | name: 'return', |
| | 659 | description: 'The way how you want the return value to be provided', |
| | 660 | typeList: [ARGUMENT_TYPE.STRING], |
| | 661 | defaultValue: 'none', |
| | 662 | enumList: slashCommandReturnHelper.enumList({ allowObject: true }), |
| | 663 | forceEnum: true, |
| | 664 | }), |
| | 665 | ], |
| | 666 | callback: async (args) => { |
| | 667 | /** @type {any} */ |
| | 668 | const returnType = String(args?.return ?? 'popup-html').trim().toLowerCase(); |
| | 669 | const objectToStringFunc = (tool) => tool.toString(); |
| | 670 | const tools = ToolManager.tools.map(tool => tool.toFunctionOpenAI()); |
| | 671 | return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', tools ?? [], { objectToStringFunc }); |
| | 672 | }, |
| | 673 | })); |
| | 674 | |
| | 675 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| | 676 | name: 'tools-invoke', |
| | 677 | aliases: ['tool-invoke'], |
| | 678 | helpString: 'Invokes a registered tool by name. The <code>parameters</code> argument MUST be a JSON-serialized object.', |
| | 679 | namedArgumentList: [ |
| | 680 | SlashCommandNamedArgument.fromProps({ |
| | 681 | name: 'parameters', |
| | 682 | description: 'The parameters to pass to the tool.', |
| | 683 | typeList: [ARGUMENT_TYPE.DICTIONARY], |
| | 684 | isRequired: true, |
| | 685 | acceptsMultiple: false, |
| | 686 | }), |
| | 687 | ], |
| | 688 | unnamedArgumentList: [ |
| | 689 | SlashCommandArgument.fromProps({ |
| | 690 | description: 'The name of the tool to invoke.', |
| | 691 | typeList: [ARGUMENT_TYPE.STRING], |
| | 692 | isRequired: true, |
| | 693 | acceptsMultiple: false, |
| | 694 | forceEnum: true, |
| | 695 | enumProvider: toolsEnumProvider, |
| | 696 | }), |
| | 697 | ], |
| | 698 | callback: async (args, name) => { |
| | 699 | const { parameters } = args; |
| | 700 | |
| | 701 | const result = await ToolManager.invokeFunctionTool(String(name), parameters); |
| | 702 | if (result instanceof Error) { |
| | 703 | throw result; |
| | 704 | } |
| | 705 | |
| | 706 | return result; |
| | 707 | }, |
| | 708 | })); |
| | 709 | |
| | 710 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| | 711 | name: 'tools-register', |
| | 712 | aliases: ['tool-register'], |
| | 713 | helpString: `<div>Registers a new tool with the tool registry.</div> |
| | 714 | <ul> |
| | 715 | <li>The <code>parameters</code> argument MUST be a JSON-serialized object with a valid JSON schema.</li> |
| | 716 | <li>The unnamed argument MUST be a closure that accepts the function parameters as local script variables.</li> |
| | 717 | </ul> |
| | 718 | <div>See <a target="_blank" href="https://json-schema.org/learn/">json-schema.org</a> and <a target="_blank" href="https://platform.openai.com/docs/guides/function-calling">OpenAI Function Calling</a> for more information.</div> |
| | 719 | <div>Example:</div> |
| | 720 | <pre><code>/let key=echoSchema |
| | 721 | { |
| | 722 | "$schema": "http://json-schema.org/draft-04/schema#", |
| | 723 | "type": "object", |
| | 724 | "properties": { |
| | 725 | "message": { |
| | 726 | "type": "string", |
| | 727 | "description": "The message to echo." |
| | 728 | } |
| | 729 | }, |
| | 730 | "required": [ |
| | 731 | "message" |
| | 732 | ] |
| | 733 | } |
| | 734 | || |
| | 735 | /tools-register name=Echo description="Echoes a message. Call when the user is asking to repeat something" parameters={{var::echoSchema}} {: /echo {{var::arg.message}} :}</code></pre>`, |
| | 736 | namedArgumentList: [ |
| | 737 | SlashCommandNamedArgument.fromProps({ |
| | 738 | name: 'name', |
| | 739 | description: 'The name of the tool.', |
| | 740 | typeList: [ARGUMENT_TYPE.STRING], |
| | 741 | isRequired: true, |
| | 742 | acceptsMultiple: false, |
| | 743 | }), |
| | 744 | SlashCommandNamedArgument.fromProps({ |
| | 745 | name: 'description', |
| | 746 | description: 'A description of what the tool does.', |
| | 747 | typeList: [ARGUMENT_TYPE.STRING], |
| | 748 | isRequired: true, |
| | 749 | acceptsMultiple: false, |
| | 750 | }), |
| | 751 | SlashCommandNamedArgument.fromProps({ |
| | 752 | name: 'parameters', |
| | 753 | description: 'The parameters for the tool.', |
| | 754 | typeList: [ARGUMENT_TYPE.DICTIONARY], |
| | 755 | isRequired: true, |
| | 756 | acceptsMultiple: false, |
| | 757 | }), |
| | 758 | SlashCommandNamedArgument.fromProps({ |
| | 759 | name: 'displayName', |
| | 760 | description: 'The display name of the tool.', |
| | 761 | typeList: [ARGUMENT_TYPE.STRING], |
| | 762 | isRequired: false, |
| | 763 | acceptsMultiple: false, |
| | 764 | }), |
| | 765 | SlashCommandNamedArgument.fromProps({ |
| | 766 | name: 'formatMessage', |
| | 767 | description: 'The closure to be executed to format the tool call message. Must return a string.', |
| | 768 | typeList: [ARGUMENT_TYPE.CLOSURE], |
| | 769 | isRequired: true, |
| | 770 | acceptsMultiple: false, |
| | 771 | }), |
| | 772 | ], |
| | 773 | unnamedArgumentList: [ |
| | 774 | SlashCommandArgument.fromProps({ |
| | 775 | description: 'The closure to be executed when the tool is invoked.', |
| | 776 | typeList: [ARGUMENT_TYPE.CLOSURE], |
| | 777 | isRequired: true, |
| | 778 | acceptsMultiple: false, |
| | 779 | }), |
| | 780 | ], |
| | 781 | callback: async (args, action) => { |
| | 782 | /** |
| | 783 | * Converts a slash command closure to a function. |
| | 784 | * @param {SlashCommandClosure} action Closure to convert to a function |
| | 785 | * @returns {function} Function that executes the closure |
| | 786 | */ |
| | 787 | function closureToFunction(action) { |
| | 788 | return async (args) => { |
| | 789 | const localClosure = action.getCopy(); |
| | 790 | localClosure.onProgress = () => { }; |
| | 791 | const scope = localClosure.scope; |
| | 792 | if (typeof args === 'object' && args !== null) { |
| | 793 | assignNestedVariables(scope, args, 'arg'); |
| | 794 | } else if (typeof args !== 'undefined') { |
| | 795 | scope.letVariable('arg', args); |
| | 796 | } |
| | 797 | const result = await localClosure.execute(); |
| | 798 | return result.pipe; |
| | 799 | }; |
| | 800 | } |
| | 801 | |
| | 802 | const { name, displayName, description, parameters, formatMessage } = args; |
| | 803 | |
| | 804 | if (!(action instanceof SlashCommandClosure)) { |
| | 805 | throw new Error('The unnamed argument must be a closure.'); |
| | 806 | } |
| | 807 | if (typeof name !== 'string' || !name) { |
| | 808 | throw new Error('The "name" argument must be a non-empty string.'); |
| | 809 | } |
| | 810 | if (typeof description !== 'string' || !description) { |
| | 811 | throw new Error('The "description" argument must be a non-empty string.'); |
| | 812 | } |
| | 813 | if (typeof parameters !== 'string' || !isJson(parameters)) { |
| | 814 | throw new Error('The "parameters" argument must be a JSON-serialized object.'); |
| | 815 | } |
| | 816 | if (displayName && typeof displayName !== 'string') { |
| | 817 | throw new Error('The "displayName" argument must be a string.'); |
| | 818 | } |
| | 819 | if (formatMessage && !(formatMessage instanceof SlashCommandClosure)) { |
| | 820 | throw new Error('The "formatMessage" argument must be a closure.'); |
| | 821 | } |
| | 822 | |
| | 823 | const actionFunc = closureToFunction(action); |
| | 824 | const formatMessageFunc = formatMessage instanceof SlashCommandClosure ? closureToFunction(formatMessage) : null; |
| | 825 | |
| | 826 | ToolManager.registerFunctionTool({ |
| | 827 | name: String(name ?? ''), |
| | 828 | displayName: String(displayName ?? ''), |
| | 829 | description: String(description ?? ''), |
| | 830 | parameters: JSON.parse(parameters ?? '{}'), |
| | 831 | action: actionFunc, |
| | 832 | formatMessage: formatMessageFunc, |
| | 833 | }); |
| | 834 | |
| | 835 | return ''; |
| | 836 | }, |
| | 837 | })); |
| | 838 | |
| | 839 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| | 840 | name: 'tools-unregister', |
| | 841 | aliases: ['tool-unregister'], |
| | 842 | helpString: 'Unregisters a tool from the tool registry.', |
| | 843 | unnamedArgumentList: [ |
| | 844 | SlashCommandArgument.fromProps({ |
| | 845 | description: 'The name of the tool to unregister.', |
| | 846 | typeList: [ARGUMENT_TYPE.STRING], |
| | 847 | isRequired: true, |
| | 848 | acceptsMultiple: false, |
| | 849 | forceEnum: true, |
| | 850 | enumProvider: toolsEnumProvider, |
| | 851 | }), |
| | 852 | ], |
| | 853 | callback: async (name) => { |
| | 854 | if (typeof name !== 'string' || !name) { |
| | 855 | throw new Error('The unnamed argument must be a non-empty string.'); |
| | 856 | } |
| | 857 | |
| | 858 | ToolManager.unregisterFunctionTool(name); |
| | 859 | return ''; |
| | 860 | }, |
| | 861 | })); |
| | 862 | } |
| 581 | } | 863 | } |