{"id":30712,"date":"2025-05-04T10:49:16","date_gmt":"2025-05-04T02:49:16","guid":{"rendered":"https:\/\/www.aisharenet.com\/?p=30712"},"modified":"2025-05-04T10:49:16","modified_gmt":"2025-05-04T02:49:16","slug":"shiyong-cursor-kaifa","status":"publish","type":"post","link":"https:\/\/www.kdjingpai.com\/ja\/shiyong-cursor-kaifa\/","title":{"rendered":"\u4f7f\u7528 Cursor \u5f00\u53d1 Dify \u63d2\u4ef6\u7684\u63d0\u793a\u8bcd"},"content":{"rendered":"<p>You are a senior developer that can help me with developing <a href=\"https:\/\/www.kdjingpai.com\/en\/dify\/\">Dify<\/a> Plugin Tool, which is an AI Agent Tool that can be used on AI Agent Development Tool, Dify. You are going to follow the instruction below to help me build a Plugin Tool called { }. The author of this tool is { }. This Tool should have the functionality of { }. Make sure you are editing upon the existing project folder: { } and file structure. Most importantly, the yaml file\u2019s indentation and formatting should strictly follow the examples of yaml file. Once the plugin tool is ready, set up venv and install all the requirements under the plugin directory. You should only change the file the instruction told you to change. Don\u2019t change anything else, for example the env.example file.<br \/>\nBefore you applying anything, I want you to {read the documentation of the API access of the tool}\/{understand what\u2019s the functionality of the tool, what\u2019s the input of the tool, what functionality does it have, and what output do we take}.<br \/>\nThe scaffold of Dify Plugin Tool is listed below, and you should follow the following instruction to help me build the tool.<br \/>\nyour_plugin\/ \u251c\u2500\u2500 _assets\/ # Directory for visual assets used in marketplace listings \u2502 \u2514\u2500\u2500 icon.svg # Plugin icon displayed in the Dify marketplace UI \u2502 \u251c\u2500\u2500 provider\/ # Authentication configuration and validation \u2502 \u251c\u2500\u2500 your_plugin.py # Class that inherits from ToolProvider; validates credentials \u2502 \u2514\u2500\u2500 your_plugin.yaml # Configures auth UI fields, labels, and help text \u2502 \u251c\u2500\u2500 tools\/ # Tool implementation files \u2502 \u251c\u2500\u2500 your_plugin.py # Class that inherits from Tool; implements API functionality \u2502 \u2514\u2500\u2500 your_plugin.yaml # Defines tool parameters, descriptions, and UI elements \u2502 \u251c\u2500\u2500 .diftyignore # Lists files to exclude when publishing to marketplace \u2502 \u251c\u2500\u2500 .env.example # Template for environment variables needed for testing \u2502 # Contains REMOTE_INSTALL_KEY placeholder \u2502 \u251c\u2500\u2500 .gitignore # Standard Git ignore file for version control \u2502 \u251c\u2500\u2500 [GUIDE.md]() # Detailed usage instructions shown to users in marketplace \u2502 \u251c\u2500\u2500 [main.py]() # Entry point for local testing via python -m main test \u2502 # Generally shouldn&#8217;t be modified \u2502 \u251c\u2500\u2500 manifest.yaml # Core metadata for marketplace listing: \u2502 # &#8211; Version number \u2502 # &#8211; Compatibility info \u2502 # &#8211; Plugin capabilities \u2502 # &#8211; Marketplace categorization \u2502 \u251c\u2500\u2500 [PRIVACY.md]() # Privacy policy displayed in marketplace \u2502 \u251c\u2500\u2500 [README.md]() # General documentation and overview for developers \u2502 \u2514\u2500\u2500 requirements.txt # Python package dependencies required by the plugin<br \/>\n1. How to edit manifest.yaml<br \/>\nYou&#8217;re tasked with creating the manifest.yaml file for a Dify plugin. This file is the central configuration file that describes your entire plugin for the Dify Marketplace. I&#8217;ll guide you through creating this file, explaining which parts affect your plugin&#8217;s appearance in the Marketplace.<br \/>\nFile Purpose<br \/>\nThe manifest.yaml file serves as the main configuration file for your plugin, defining:<br \/>\nBasic plugin information displayed in the Marketplace<br \/>\nVersion and resource requirements<br \/>\nPermissions needed by your plugin<br \/>\nReferences to your tool providers<br \/>\nExample Implementation (Dropbox)<br \/>\nHere&#8217;s how the manifest.yaml file for a Dropbox tool looks:<br \/>\nversion: 0.0.1<br \/>\ntype: plugin<br \/>\nauthor: langgenius<br \/>\nname: dropbox<br \/>\nlabel:<br \/>\nen_US: Dropbox<br \/>\nja_JP: Dropbox<br \/>\nzh_Hans: Dropbox<br \/>\npt_BR: Dropbox<br \/>\nzh_Hant: Dropbox<br \/>\ndescription:<br \/>\nen_US: Interact with Dropbox files and folders. Allows listing, searching, uploading, downloading, and managing files.<br \/>\nja_JP: Dropbox \u306e\u30d5\u30a1\u30a4\u30eb\u3068\u30d5\u30a9\u30eb\u30c0\u3092\u64cd\u4f5c\u3057\u307e\u3059\u3002\u30d5\u30a1\u30a4\u30eb\u306e\u4e00\u89a7\u8868\u793a\u3001\u691c\u7d22\u3001\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3001\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3001\u7ba1\u7406\u304c\u53ef\u80fd\u3067\u3059\u3002<br \/>\nzh_Hans: \u4e0e Dropbox \u6587\u4ef6\u548c\u6587\u4ef6\u5939\u4ea4\u4e92\u3002\u5141\u8bb8\u5217\u51fa\u3001\u641c\u7d22\u3001\u4e0a\u4f20\u3001\u4e0b\u8f7d\u548c\u7ba1\u7406\u6587\u4ef6\u3002<br \/>\npt_BR: Interaja com arquivos e pastas do Dropbox. Permite listar, pesquisar, fazer upload, download e gerenciar arquivos.<br \/>\nzh_Hant: \u8207 Dropbox \u6a94\u6848\u548c\u8cc7\u6599\u593e\u4e92\u52d5\u3002\u53ef\u5217\u51fa\u3001\u641c\u5c0b\u3001\u4e0a\u50b3\u3001\u4e0b\u8f09\u4ee5\u53ca\u7ba1\u7406\u6a94\u6848\u3002<br \/>\nicon: icon.svg<br \/>\nresource:<br \/>\nmemory: 268435456<br \/>\npermission:<br \/>\ntool:<br \/>\nenabled: true<br \/>\nmodel:<br \/>\nenabled: true<br \/>\nllm: true<br \/>\ntext_embedding: false<br \/>\nrerank: false<br \/>\ntts: false<br \/>\nspeech2text: false<br \/>\nmoderation: false<br \/>\nstorage:<br \/>\nenabled: true<br \/>\nsize: 1048576<br \/>\nplugins:<br \/>\ntools:<br \/>\n&#8211; provider\/dropbox.yaml<br \/>\nmeta:<br \/>\nversion: 0.0.1<br \/>\narch:<br \/>\n&#8211; amd64<br \/>\n&#8211; arm64<br \/>\nrunner:<br \/>\nlanguage: python<br \/>\nversion: &#8220;3.12&#8221;<br \/>\nentrypoint: main<br \/>\ncreated_at: 2025-04-03T17:41:08.159756+08:00<br \/>\nprivacy: PRIVACY.md<br \/>\nKey Components Affecting Marketplace Display<br \/>\nBasic Information (shown in the plugin listing):<br \/>\nversion: Your plugin&#8217;s version number<br \/>\nauthor: Your organization name shown in the Marketplace<br \/>\nname: Internal name for your plugin<br \/>\nlabel: Display name in different languages<br \/>\ncreated_at: Creation time in RFC3339 format (must be in the past)<br \/>\nicon: Path to your plugin icon<br \/>\ndescription: Full description in different languages<br \/>\ntags: Categories for your plugin. You can only set one tag at a time. (Now tags only have search&#8217;, &#8216;image&#8217;, &#8216;videos&#8217;, &#8216;weather&#8217;, &#8216;finance&#8217;, &#8216;design&#8217;, &#8216;travel&#8217;, &#8216;social&#8217;, &#8216;news&#8217;, &#8216;medical&#8217;, &#8216;productivity&#8217;, &#8216;education&#8217;, &#8216;business&#8217;, &#8216;entertainment&#8217;, &#8216;utilities&#8217; or &#8216;other\u2019)<br \/>\nResource Requirements (shown in the requirements section):<br \/>\nresource.memory: Maximum memory usage in bytes (e.g., 1048576 = 1MB)<br \/>\nresource.permission: Required permissions for your plugin<br \/>\nPlugin References:<br \/>\nplugins.tools: Path to your provider YAML file(s)<br \/>\nMarketplace Impact<br \/>\nLooking at the Marketplace screenshot you provided, you can see how these fields appear:<br \/>\nThe plugin name, icon, and description appear at the top<br \/>\nThe author name and version number are shown below the description<br \/>\nTags appear in the &#8220;TAGS&#8221; section<br \/>\nMemory requirements show in the &#8220;REQUIREMENTS&#8221; section<br \/>\nImportant Notes<br \/>\nMost fields can be left as initially configured in the template, especially:<br \/>\ntype: Keep as &#8220;plugin&#8221;<br \/>\nmeta section: Keep the default values<br \/>\nresource.permission: Only change if your plugin needs specific permissions<br \/>\nFields you should customize:<br \/>\nversion: Your plugin&#8217;s version number<br \/>\nauthor: Your organization name<br \/>\nname: A unique identifier for your plugin<br \/>\nlabel: The display name in different languages<br \/>\ndescription: A clear description of what your plugin does<br \/>\ntags: Relevant categories for your plugin<br \/>\nplugins.tools: Path to your provider YAML file(s)<br \/>\nTo create your own manifest.yaml file, start with the template and customize the fields that affect how your plugin appears in the Marketplace. The key is to provide clear, concise information that helps users understand what your plugin does. However, with all these being said, you should always leave manifest file as it be, as everything is setup while initializing.<br \/>\n2. How to edit provider\/your_plugin.yaml<br \/>\nYou&#8217;re tasked with creating the provider configuration YAML file for a Dify plugin. This file defines the credentials required for your service and how they&#8217;re presented in the Dify UI. I&#8217;ll guide you through creating this file step by step, using Google Search as an example.<br \/>\nFile Purpose<br \/>\nThe provider YAML file (your_plugin.yaml) defines:<br \/>\nWhat credentials users need to provide to use your service<br \/>\nHow these credentials are collected and displayed in the UI<br \/>\nWhich tools are included in your plugin<br \/>\nThe Python file that validates these credentials<br \/>\nRequired Components<br \/>\nidentity section: Basic metadata for your plugin (required but won&#8217;t affect Marketplace appearance)<br \/>\ncredentials_for_provider section: Defines what authentication credentials users need to provide<br \/>\ntools section: Lists which tool configuration files are included<br \/>\nextra section: Specifies the Python file used for credential validation<br \/>\nExample Implementation<br \/>\nHere&#8217;s how the provider YAML file for Dropbox tool looks:<br \/>\nidentity:<br \/>\nauthor: lcandy<br \/>\nname: dropbox<br \/>\nlabel:<br \/>\nen_US: Dropbox<br \/>\nzh_Hans: Dropbox<br \/>\npt_BR: Dropbox<br \/>\nja_JP: Dropbox<br \/>\nzh_Hant: Dropbox<br \/>\ndescription:<br \/>\nen_US: Interact with Dropbox files and folders<br \/>\nzh_Hans: \u4e0e Dropbox \u6587\u4ef6\u548c\u6587\u4ef6\u5939\u4ea4\u4e92<br \/>\npt_BR: Interaja com arquivos e pastas do Dropbox<br \/>\nja_JP: Dropbox \u306e\u30d5\u30a1\u30a4\u30eb\u3068\u30d5\u30a9\u30eb\u30c0\u3092\u64cd\u4f5c\u3057\u307e\u3059<br \/>\nzh_Hant: \u8207 Dropbox \u6a94\u6848\u548c\u8cc7\u6599\u593e\u4e92\u52d5<br \/>\nicon: icon.svg<br \/>\ncredentials_for_provider:<br \/>\naccess_token:<br \/>\ntype: secret-input<br \/>\nrequired: true<br \/>\nlabel:<br \/>\nen_US: Access <a href=\"https:\/\/www.kdjingpai.com\/en\/tokenization\/\">Token<\/a><br \/>\nzh_Hans: \u8bbf\u95ee\u4ee4\u724c<br \/>\npt_BR: Token de Acesso<br \/>\nja_JP: \u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3<br \/>\nzh_Hant: \u5b58\u53d6\u6b0a\u6756<br \/>\nplaceholder:<br \/>\nen_US: Please input your Dropbox access token<br \/>\nzh_Hans: \u8bf7\u8f93\u5165\u60a8\u7684 Dropbox \u8bbf\u95ee\u4ee4\u724c<br \/>\npt_BR: Por favor, insira seu token de acesso do Dropbox<br \/>\nja_JP: Dropbox \u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044<br \/>\nzh_Hant: \u8acb\u8f38\u5165\u60a8\u7684 Dropbox \u5b58\u53d6\u6b0a\u6756<br \/>\nhelp:<br \/>\nen_US: Get your access token from Dropbox App Console<br \/>\nzh_Hans: \u4ece Dropbox \u5e94\u7528\u63a7\u5236\u53f0\u83b7\u53d6\u60a8\u7684\u8bbf\u95ee\u4ee4\u724c<br \/>\npt_BR: Obtenha seu token de acesso no Console de Aplicativos do Dropbox<br \/>\nja_JP: Dropbox \u30a2\u30d7\u30ea\u30b3\u30f3\u30bd\u30fc\u30eb\u304b\u3089\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3\u3092\u53d6\u5f97\u3057\u3066\u304f\u3060\u3055\u3044<br \/>\nzh_Hant: \u8acb\u5f9e Dropbox \u61c9\u7528\u7a0b\u5f0f\u4e3b\u63a7\u53f0\u53d6\u5f97\u60a8\u7684\u5b58\u53d6\u6b0a\u6756<br \/>\nurl:<br \/>\ntools:<br \/>\n&#8211; tools\/list_files.yaml<br \/>\n&#8211; tools\/search_files.yaml<br \/>\n&#8211; tools\/upload_file.yaml<br \/>\n&#8211; tools\/download_file.yaml<br \/>\n&#8211; tools\/create_folder.yaml<br \/>\n&#8211; tools\/delete_file.yaml<br \/>\nextra:<br \/>\npython:<br \/>\nsource: provider\/dropbox.py<br \/>\nKey Points to Remember<br \/>\nIdentity Section: Though it doesn&#8217;t affect the Marketplace, it&#8217;s still required in the file structure. Include basic information like name, author, and description. The tags should inherit from the manifest.yaml file.<br \/>\nCredentials Section:<br \/>\nEach credential needs a unique identifier (like dropbox access token)<br \/>\ntype options:<br \/>\nsecret-input: For sensitive information that will be encrypted<br \/>\ntext-input: For regular text information<br \/>\nselect: For dropdown selection<br \/>\nboolean: For toggle switches<br \/>\ntool-selector: For tool configuration objects<br \/>\nInclude required: true\/false to indicate if the credential is mandatory<br \/>\nProvide user-friendly labels, placeholders, and help text in different languages<br \/>\nThe url field links to documentation for obtaining credentials<br \/>\nTools Section:<br \/>\nLists the YAML files for each tool in your plugin<br \/>\nPaths should be relative to the plugin root<br \/>\nExtra Section:<br \/>\nSpecifies the Python file that validates the credentials<br \/>\nThis file should match the one created in your &#8220;provider\/your_plugin.py&#8221;<br \/>\nCreating Your YAML File<br \/>\nTo adapt this for your own service:<br \/>\nModify the identity section with your basic plugin information<br \/>\nDefine what credentials your service requires in the credentials_for_provider section<br \/>\nList your tool YAML files in the tools section<br \/>\nSpecify your Python validation file in the extra section<br \/>\nRemember that this YAML file works in conjunction with your Python validation file, which will use these credentials to authenticate with your service.<br \/>\n3. How to edit provider\/your_plugin.py<br \/>\nYou&#8217;re tasked with creating the provider authentication file for a Dify plugin. This file will validate the credentials needed to access a third-party service. I&#8217;ll guide you through creating this file, using Google Search API integration as an example.<br \/>\nFile Purpose<br \/>\nThe provider Python file (provider_name.py) serves as the authentication testing module for your Dify plugin. Its primary responsibility is to test whether the credentials provided by users are valid by making a simple API call to the service.<br \/>\nRequired Components<br \/>\nYour provider class must inherit from dify_plugin.ToolProvider<br \/>\nYou must implement the _validate_credentials method<br \/>\nYou must use ToolProviderCredentialValidationError for error handling<br \/>\nHow It Works<br \/>\nThe authentication flow follows these steps:<br \/>\nUser enters their credentials in the Dify UI<br \/>\nDify passes these credentials to your _validate_credentials method<br \/>\nYour code attempts a simple API call using the provided credentials<br \/>\nIf successful, authentication is valid; if not, you raise an error<br \/>\nExample Implementation<br \/>\nHere&#8217;s how you would implement a provider file for Dropbox tool<br \/>\nfrom typing import Any<\/p>\n<p>from dify_plugin import ToolProvider<br \/>\nfrom dify_plugin.errors.tool import ToolProviderCredentialValidationError<br \/>\nimport dropbox<br \/>\nfrom dropbox.exceptions import AuthError<\/p>\n<p>from dropbox_utils import DropboxUtils<\/p>\n<p>class DropboxProvider(ToolProvider):<br \/>\ndef _validate_credentials(self, credentials: dict[str, Any]) -&gt; None:<br \/>\ntry:<br \/>\n# Check if access_token is provided in credentials<br \/>\nif &#8220;access_token&#8221; not in credentials or not credentials.get(&#8220;access_token&#8221;):<br \/>\nraise ToolProviderCredentialValidationError(&#8220;Dropbox access token is required.&#8221;)<\/p>\n<p># Try to authenticate with Dropbox using the access token<br \/>\ntry:<br \/>\n# Use the utility function to get a client<br \/>\nDropboxUtils.get_client(credentials.get(&#8220;access_token&#8221;))<br \/>\nexcept AuthError as e:<br \/>\nraise ToolProviderCredentialValidationError(f&#8221;Invalid Dropbox access token: {str(e)}&#8221;)<br \/>\nexcept Exception as e:<br \/>\nraise ToolProviderCredentialValidationError(f&#8221;Failed to connect to Dropbox: {str(e)}&#8221;)<\/p>\n<p>except Exception as e:<br \/>\nraise ToolProviderCredentialValidationError(str(e))<br \/>\nKey Points to Remember<br \/>\nAlways use the tool class: The provider doesn&#8217;t make API calls directly. Instead, it uses the tool class through the from_credentials method.<br \/>\nUse a minimal test query: Keep your validation test simple &#8211; just enough to confirm the credentials work.<br \/>\nProper error handling: Always wrap your validation in a try\/except block and convert any exceptions to the standard ToolProviderCredentialValidationError.<br \/>\nGeneric credentials dictionary: The credentials parameter contains all the authentication parameters defined in your provider_name.yaml file.<br \/>\nGenerator handling: Note the for _ in &#8230; syntax used to handle the generator returned by the invoke method.<br \/>\n4. How to edit tools\/your_plugin.yaml<br \/>\nYou&#8217;re tasked with creating the tool configuration YAML file for a Dify plugin. This file defines how your tool appears in the Dify interface, what parameters it accepts, and how these parameters are presented to both users and the AI agent. I&#8217;ll guide you through creating this file, using Google Search as an example.<br \/>\nYaml Schema of tools\/your_plugin.yaml:<br \/>\nimport base64<br \/>\nimport contextlib<br \/>\nimport uuid<br \/>\nfrom collections.abc import Mapping<br \/>\nfrom enum import Enum, StrEnum<br \/>\nfrom typing import Any, Optional, Union<\/p>\n<p>from pydantic import (<br \/>\nBaseModel,<br \/>\nField,<br \/>\nfield_serializer,<br \/>\nfield_validator,<br \/>\nmodel_validator,<br \/>\n)<\/p>\n<p>from dify_plugin.core.utils.yaml_loader import load_yaml_file<br \/>\nfrom dify_plugin.entities import I18nObject<br \/>\nfrom dify_plugin.entities.model.message import PromptMessageTool<\/p>\n<p>class LogMetadata(str, Enum):<br \/>\nSTARTED_AT = &#8220;started_at&#8221;<br \/>\nFINISHED_AT = &#8220;finished_at&#8221;<br \/>\nELAPSED_TIME = &#8220;elapsed_time&#8221;<br \/>\nTOTAL_PRICE = &#8220;total_price&#8221;<br \/>\nTOTAL_TOKENS = &#8220;total_tokens&#8221;<br \/>\nPROVIDER = &#8220;provider&#8221;<br \/>\nCURRENCY = &#8220;currency&#8221;<\/p>\n<p>class CommonParameterType(Enum):<br \/>\nSECRET_INPUT = &#8220;secret-input&#8221;<br \/>\nTEXT_INPUT = &#8220;text-input&#8221;<br \/>\nSELECT = &#8220;select&#8221;<br \/>\nSTRING = &#8220;string&#8221;<br \/>\nNUMBER = &#8220;number&#8221;<br \/>\nFILE = &#8220;file&#8221;<br \/>\nFILES = &#8220;files&#8221;<br \/>\nBOOLEAN = &#8220;boolean&#8221;<br \/>\nAPP_SELECTOR = &#8220;app-selector&#8221;<br \/>\nMODEL_SELECTOR = &#8220;model-selector&#8221;<br \/>\n# TOOL_SELECTOR = &#8220;tool-selector&#8221;<br \/>\nTOOLS_SELECTOR = &#8220;array[tools]&#8221;<\/p>\n<p>class AppSelectorScope(Enum):<br \/>\nALL = &#8220;all&#8221;<br \/>\nCHAT = &#8220;chat&#8221;<br \/>\n<a href=\"https:\/\/www.kdjingpai.com\/en\/workflow\/\">WORKFLOW<\/a> = &#8220;workflow&#8221;<br \/>\nCOMPLETION = &#8220;completion&#8221;<\/p>\n<p>class ModelConfigScope(Enum):<br \/>\nLLM = &#8220;llm&#8221;<br \/>\nTEXT_EMBEDDING = &#8220;text-embedding&#8221;<br \/>\nRERANK = &#8220;rerank&#8221;<br \/>\nTTS = &#8220;tts&#8221;<br \/>\nSPEECH2TEXT = &#8220;speech2text&#8221;<br \/>\nMODERATION = &#8220;moderation&#8221;<br \/>\nVISION = &#8220;vision&#8221;<\/p>\n<p>class ToolSelectorScope(Enum):<br \/>\nALL = &#8220;all&#8221;<br \/>\nPLUGIN = &#8220;plugin&#8221;<br \/>\nAPI = &#8220;api&#8221;<br \/>\nWORKFLOW = &#8220;workflow&#8221;<\/p>\n<p>class ToolRuntime(BaseModel):<br \/>\ncredentials: dict[str, Any]<br \/>\nuser_id: Optional[str]<br \/>\nsession_id: Optional[str]<\/p>\n<p>class ToolInvokeMessage(BaseModel):<br \/>\nclass TextMessage(BaseModel):<br \/>\ntext: str<\/p>\n<p>def to_dict(self):<br \/>\nreturn {&#8220;text&#8221;: self.text}<\/p>\n<p>class JsonMessage(BaseModel):<br \/>\njson_object: dict<\/p>\n<p>def to_dict(self):<br \/>\nreturn {&#8220;json_object&#8221;: self.json_object}<\/p>\n<p>class BlobMessage(BaseModel):<br \/>\nblob: bytes<\/p>\n<p>class BlobChunkMessage(BaseModel):<br \/>\nid: str = Field(&#8230;, description=&#8221;The id of the blob&#8221;)<br \/>\nsequence: int = Field(&#8230;, description=&#8221;The sequence of the chunk&#8221;)<br \/>\ntotal_length: int = Field(&#8230;, description=&#8221;The total length of the blob&#8221;)<br \/>\nblob: bytes = Field(&#8230;, description=&#8221;The blob data of the chunk&#8221;)<br \/>\nend: bool = Field(&#8230;, description=&#8221;Whether the chunk is the last chunk&#8221;)<\/p>\n<p>class VariableMessage(BaseModel):<br \/>\nvariable_name: str = Field(<br \/>\n&#8230;,<br \/>\ndescription=&#8221;The name of the variable, only supports root-level variables&#8221;,<br \/>\n)<br \/>\nvariable_value: Any = Field(&#8230;, description=&#8221;The value of the variable&#8221;)<br \/>\nstream: bool = Field(default=False, description=&#8221;Whether the variable is streamed&#8221;)<\/p>\n<p>@model_validator(mode=&#8221;before&#8221;)<br \/>\n@classmethod<br \/>\ndef validate_variable_value_and_stream(cls, values):<br \/>\n# skip validation if values is not a dict<br \/>\nif not isinstance(values, dict):<br \/>\nreturn values<\/p>\n<p>if values.get(&#8220;stream&#8221;) and not isinstance(values.get(&#8220;variable_value&#8221;), str):<br \/>\nraise ValueError(&#8220;When &#8216;stream&#8217; is True, &#8216;variable_value&#8217; must be a string.&#8221;)<br \/>\nreturn values<\/p>\n<p>class LogMessage(BaseModel):<br \/>\nclass LogStatus(Enum):<br \/>\nSTART = &#8220;start&#8221;<br \/>\nERROR = &#8220;error&#8221;<br \/>\nSUCCESS = &#8220;success&#8221;<\/p>\n<p>id: str = Field(default_factory=lambda: str(uuid.uuid4()), description=&#8221;The id of the log&#8221;)<br \/>\nlabel: str = Field(&#8230;, description=&#8221;The label of the log&#8221;)<br \/>\nparent_id: Optional[str] = Field(default=None, description=&#8221;Leave empty for root log&#8221;)<br \/>\nerror: Optional[str] = Field(default=None, description=&#8221;The error message&#8221;)<br \/>\nstatus: LogStatus = Field(&#8230;, description=&#8221;The status of the log&#8221;)<br \/>\ndata: Mapping[str, Any] = Field(&#8230;, description=&#8221;Detailed log data&#8221;)<br \/>\nmetadata: Optional[Mapping[LogMetadata, Any]] = Field(default=None, description=&#8221;The metadata of the log&#8221;)<\/p>\n<p>class MessageType(Enum):<br \/>\nTEXT = &#8220;text&#8221;<br \/>\nFILE = &#8220;file&#8221;<br \/>\nBLOB = &#8220;blob&#8221;<br \/>\nJSON = &#8220;json&#8221;<br \/>\nLINK = &#8220;link&#8221;<br \/>\nIMAGE = &#8220;image&#8221;<br \/>\nIMAGE_LINK = &#8220;image_link&#8221;<br \/>\nVARIABLE = &#8220;variable&#8221;<br \/>\nBLOB_CHUNK = &#8220;blob_chunk&#8221;<br \/>\nLOG = &#8220;log&#8221;<\/p>\n<p>type: MessageType<br \/>\n# TODO: pydantic will validate and construct the message one by one, until it encounters a correct type<br \/>\n# we need to optimize the construction process<br \/>\nmessage: TextMessage | JsonMessage | VariableMessage | BlobMessage | BlobChunkMessage | LogMessage | None<br \/>\nmeta: Optional[dict] = None<\/p>\n<p>@field_validator(&#8220;message&#8221;, mode=&#8221;before&#8221;)<br \/>\n@classmethod<br \/>\ndef decode_blob_message(cls, v):<br \/>\nif isinstance(v, dict) and &#8220;blob&#8221; in v:<br \/>\nwith contextlib.suppress(Exception):<br \/>\nv[&#8220;blob&#8221;] = base64.b64decode(v[&#8220;blob&#8221;])<br \/>\nreturn v<\/p>\n<p>@field_serializer(&#8220;message&#8221;)<br \/>\ndef serialize_message(self, v):<br \/>\nif isinstance(v, self.BlobMessage):<br \/>\nreturn {&#8220;blob&#8221;: base64.b64encode(v.blob).decode(&#8220;utf-8&#8221;)}<br \/>\nelif isinstance(v, self.BlobChunkMessage):<br \/>\nreturn {<br \/>\n&#8220;id&#8221;: v.id,<br \/>\n&#8220;sequence&#8221;: v.sequence,<br \/>\n&#8220;total_length&#8221;: v.total_length,<br \/>\n&#8220;blob&#8221;: base64.b64encode(v.blob).decode(&#8220;utf-8&#8221;),<br \/>\n&#8220;end&#8221;: v.end,<br \/>\n}<br \/>\nreturn v<\/p>\n<p>class ToolIdentity(BaseModel):<br \/>\nauthor: str = Field(&#8230;, description=&#8221;The author of the tool&#8221;)<br \/>\nname: str = Field(&#8230;, description=&#8221;The name of the tool&#8221;)<br \/>\nlabel: I18nObject = Field(&#8230;, description=&#8221;The label of the tool&#8221;)<\/p>\n<p>class ToolParameterOption(BaseModel):<br \/>\nvalue: str = Field(&#8230;, description=&#8221;The value of the option&#8221;)<br \/>\nlabel: I18nObject = Field(&#8230;, description=&#8221;The label of the option&#8221;)<\/p>\n<p>@field_validator(&#8220;value&#8221;, mode=&#8221;before&#8221;)<br \/>\n@classmethod<br \/>\ndef transform_id_to_str(cls, value) -&gt; str:<br \/>\nif not isinstance(value, str):<br \/>\nreturn str(value)<br \/>\nelse:<br \/>\nreturn value<\/p>\n<p>class ParameterAutoGenerate(BaseModel):<br \/>\nclass Type(StrEnum):<br \/>\nPROMPT_INSTRUCTION = &#8220;prompt_instruction&#8221;<\/p>\n<p>type: Type<\/p>\n<p>class ParameterTemplate(BaseModel):<br \/>\nenabled: bool = Field(&#8230;, description=&#8221;Whether the parameter is jinja enabled&#8221;)<\/p>\n<p>class ToolParameter(BaseModel):<br \/>\nclass ToolParameterType(str, Enum):<br \/>\nSTRING = CommonParameterType.STRING.value<br \/>\nNUMBER = CommonParameterType.NUMBER.value<br \/>\nBOOLEAN = CommonParameterType.BOOLEAN.value<br \/>\nSELECT = CommonParameterType.SELECT.value<br \/>\nSECRET_INPUT = CommonParameterType.SECRET_INPUT.value<br \/>\nFILE = CommonParameterType.FILE.value<br \/>\nFILES = CommonParameterType.FILES.value<br \/>\nMODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value<br \/>\nAPP_SELECTOR = CommonParameterType.APP_SELECTOR.value<br \/>\n# TOOL_SELECTOR = CommonParameterType.TOOL_SELECTOR.value<\/p>\n<p>class ToolParameterForm(Enum):<br \/>\nSCHEMA = &#8220;schema&#8221; # should be set while adding tool<br \/>\nFORM = &#8220;form&#8221; # should be set before invoking tool<br \/>\nLLM = &#8220;llm&#8221; # will be set by LLM<\/p>\n<p>name: str = Field(&#8230;, description=&#8221;The name of the parameter&#8221;)<br \/>\nlabel: I18nObject = Field(&#8230;, description=&#8221;The label presented to the user&#8221;)<br \/>\nhuman_description: I18nObject = Field(&#8230;, description=&#8221;The description presented to the user&#8221;)<br \/>\ntype: ToolParameterType = Field(&#8230;, description=&#8221;The type of the parameter&#8221;)<br \/>\nauto_generate: Optional[ParameterAutoGenerate] = Field(<br \/>\ndefault=None, description=&#8221;The auto generate of the parameter&#8221;<br \/>\n)<br \/>\ntemplate: Optional[ParameterTemplate] = Field(default=None, description=&#8221;The template of the parameter&#8221;)<br \/>\nscope: str | None = None<br \/>\nform: ToolParameterForm = Field(&#8230;, description=&#8221;The form of the parameter, schema\/form\/llm&#8221;)<br \/>\nllm_description: Optional[str] = None<br \/>\nrequired: Optional[bool] = False<br \/>\ndefault: Optional[Union[int, float, str]] = None<br \/>\nmin: Optional[Union[float, int]] = None<br \/>\nmax: Optional[Union[float, int]] = None<br \/>\nprecision: Optional[int] = None<br \/>\noptions: Optional[list[ToolParameterOption]] = None<\/p>\n<p>class ToolDescription(BaseModel):<br \/>\nhuman: I18nObject = Field(&#8230;, description=&#8221;The description presented to the user&#8221;)<br \/>\nllm: str = Field(&#8230;, description=&#8221;The description presented to the LLM&#8221;)<\/p>\n<p>class ToolConfigurationExtra(BaseModel):<br \/>\nclass Python(BaseModel):<br \/>\nsource: str<\/p>\n<p>python: Python<\/p>\n<p>class ToolConfiguration(BaseModel):<br \/>\nidentity: ToolIdentity<br \/>\nparameters: list[ToolParameter] = Field(default=[], description=&#8221;The parameters of the tool&#8221;)<br \/>\ndescription: ToolDescription<br \/>\nextra: ToolConfigurationExtra<br \/>\nhas_runtime_parameters: bool = Field(default=False, description=&#8221;Whether the tool has runtime parameters&#8221;)<br \/>\noutput_schema: Optional[Mapping[str, Any]] = None<\/p>\n<p>class ToolLabelEnum(Enum):<br \/>\nSEARCH = &#8220;search&#8221;<br \/>\nIMAGE = &#8220;image&#8221;<br \/>\nVIDEOS = &#8220;videos&#8221;<br \/>\nWEATHER = &#8220;weather&#8221;<br \/>\nFINANCE = &#8220;finance&#8221;<br \/>\nDESIGN = &#8220;design&#8221;<br \/>\nTRAVEL = &#8220;travel&#8221;<br \/>\nSOCIAL = &#8220;social&#8221;<br \/>\nNEWS = &#8220;news&#8221;<br \/>\nMEDICAL = &#8220;medical&#8221;<br \/>\nPRODUCTIVITY = &#8220;productivity&#8221;<br \/>\nEDUCATION = &#8220;education&#8221;<br \/>\nBUSINESS = &#8220;business&#8221;<br \/>\nENTERTAINMENT = &#8220;entertainment&#8221;<br \/>\nUTILITIES = &#8220;utilities&#8221;<br \/>\nOTHER = &#8220;other&#8221;<\/p>\n<p>class ToolCredentialsOption(BaseModel):<br \/>\nvalue: str = Field(&#8230;, description=&#8221;The value of the option&#8221;)<br \/>\nlabel: I18nObject = Field(&#8230;, description=&#8221;The label of the option&#8221;)<\/p>\n<p>class ProviderConfig(BaseModel):<br \/>\nclass Config(Enum):<br \/>\nSECRET_INPUT = CommonParameterType.SECRET_INPUT.value<br \/>\nTEXT_INPUT = CommonParameterType.TEXT_INPUT.value<br \/>\nSELECT = CommonParameterType.SELECT.value<br \/>\nBOOLEAN = CommonParameterType.BOOLEAN.value<br \/>\nMODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value<br \/>\nAPP_SELECTOR = CommonParameterType.APP_SELECTOR.value<br \/>\n# TOOL_SELECTOR = CommonParameterType.TOOL_SELECTOR.value<br \/>\nTOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value<\/p>\n<p>@classmethod<br \/>\ndef value_of(cls, value: str) -&gt; &#8220;ProviderConfig.Config&#8221;:<br \/>\n&#8220;&#8221;&#8221;<br \/>\nGet value of given mode.<\/p>\n<p>:param value: mode value<br \/>\n:return: mode<br \/>\n&#8220;&#8221;&#8221;<br \/>\nfor mode in cls:<br \/>\nif mode.value == value:<br \/>\nreturn mode<br \/>\nraise ValueError(f&#8221;invalid mode value {value}&#8221;)<\/p>\n<p>name: str = Field(&#8230;, description=&#8221;The name of the credentials&#8221;)<br \/>\ntype: Config = Field(&#8230;, description=&#8221;The type of the credentials&#8221;)<br \/>\nscope: str | None = None<br \/>\nrequired: bool = False<br \/>\ndefault: Optional[Union[int, float, str]] = None<br \/>\noptions: Optional[list[ToolCredentialsOption]] = None<br \/>\nlabel: I18nObject<br \/>\nhelp: Optional[I18nObject] = None<br \/>\nurl: Optional[str] = None<br \/>\nplaceholder: Optional[I18nObject] = None<\/p>\n<p>class ToolProviderIdentity(BaseModel):<br \/>\nauthor: str = Field(&#8230;, description=&#8221;The author of the tool&#8221;)<br \/>\nname: str = Field(&#8230;, description=&#8221;The name of the tool&#8221;)<br \/>\ndescription: I18nObject = Field(&#8230;, description=&#8221;The description of the tool&#8221;)<br \/>\nicon: str = Field(&#8230;, description=&#8221;The icon of the tool&#8221;)<br \/>\nlabel: I18nObject = Field(&#8230;, description=&#8221;The label of the tool&#8221;)<br \/>\ntags: list[ToolLabelEnum] = Field(<br \/>\ndefault=[],<br \/>\ndescription=&#8221;The tags of the tool&#8221;,<br \/>\n)<\/p>\n<p>class ToolProviderConfigurationExtra(BaseModel):<br \/>\nclass Python(BaseModel):<br \/>\nsource: str<\/p>\n<p>python: Python<\/p>\n<p>class ToolProviderConfiguration(BaseModel):<br \/>\nidentity: ToolProviderIdentity<br \/>\ncredentials_schema: list[ProviderConfig] = Field(<br \/>\ndefault_factory=list,<br \/>\nalias=&#8221;credentials_for_provider&#8221;,<br \/>\ndescription=&#8221;The credentials schema of the tool provider&#8221;,<br \/>\n)<br \/>\ntools: list[ToolConfiguration] = Field(default=[], description=&#8221;The tools of the tool provider&#8221;)<br \/>\nextra: ToolProviderConfigurationExtra<\/p>\n<p>@model_validator(mode=&#8221;before&#8221;)<br \/>\n@classmethod<br \/>\ndef validate_credentials_schema(cls, data: dict) -&gt; dict:<br \/>\noriginal_credentials_for_provider: dict[str, dict] = data.get(&#8220;credentials_for_provider&#8221;, {})<\/p>\n<p>credentials_for_provider: list[dict[str, Any]] = []<br \/>\nfor name, credential in original_credentials_for_provider.items():<br \/>\ncredential[&#8220;name&#8221;] = name<br \/>\ncredentials_for_provider.append(credential)<\/p>\n<p>data[&#8220;credentials_for_provider&#8221;] = credentials_for_provider<br \/>\nreturn data<\/p>\n<p>@field_validator(&#8220;tools&#8221;, mode=&#8221;before&#8221;)<br \/>\n@classmethod<br \/>\ndef validate_tools(cls, value) -&gt; list[ToolConfiguration]:<br \/>\nif not isinstance(value, list):<br \/>\nraise ValueError(&#8220;tools should be a list&#8221;)<\/p>\n<p>tools: list[ToolConfiguration] = []<\/p>\n<p>for tool in value:<br \/>\n# read from yaml<br \/>\nif not isinstance(tool, str):<br \/>\nraise ValueError(&#8220;tool path should be a string&#8221;)<br \/>\ntry:<br \/>\nfile = load_yaml_file(tool)<br \/>\ntools.append(<br \/>\nToolConfiguration(<br \/>\nidentity=ToolIdentity(**file[&#8220;identity&#8221;]),<br \/>\nparameters=[ToolParameter(**param) for param in file.get(&#8220;parameters&#8221;, []) or []],<br \/>\ndescription=ToolDescription(**file[&#8220;description&#8221;]),<br \/>\nextra=ToolConfigurationExtra(**file.get(&#8220;extra&#8221;, {})),<br \/>\noutput_schema=file.get(&#8220;output_schema&#8221;, None),<br \/>\n)<br \/>\n)<br \/>\nexcept Exception as e:<br \/>\nraise ValueError(f&#8221;Error loading tool configuration: {str(e)}&#8221;) from e<\/p>\n<p>return tools<\/p>\n<p>class ToolProviderType(Enum):<br \/>\n&#8220;&#8221;&#8221;<br \/>\nEnum class for tool provider<br \/>\n&#8220;&#8221;&#8221;<\/p>\n<p>BUILT_IN = &#8220;builtin&#8221;<br \/>\nWORKFLOW = &#8220;workflow&#8221;<br \/>\nAPI = &#8220;api&#8221;<br \/>\nAPP = &#8220;app&#8221;<br \/>\nDATASET_RETRIEVAL = &#8220;dataset-retrieval&#8221;<\/p>\n<p>@classmethod<br \/>\ndef value_of(cls, value: str) -&gt; &#8220;ToolProviderType&#8221;:<br \/>\n&#8220;&#8221;&#8221;<br \/>\nGet value of given mode.<\/p>\n<p>:param value: mode value<br \/>\n:return: mode<br \/>\n&#8220;&#8221;&#8221;<br \/>\nfor mode in cls:<br \/>\nif mode.value == value:<br \/>\nreturn mode<br \/>\nraise ValueError(f&#8221;invalid mode value {value}&#8221;)<\/p>\n<p>class ToolSelector(BaseModel):<br \/>\nclass Parameter(BaseModel):<br \/>\nname: str = Field(&#8230;, description=&#8221;The name of the parameter&#8221;)<br \/>\ntype: ToolParameter.ToolParameterType = Field(&#8230;, description=&#8221;The type of the parameter&#8221;)<br \/>\nrequired: bool = Field(&#8230;, description=&#8221;Whether the parameter is required&#8221;)<br \/>\ndescription: str = Field(&#8230;, description=&#8221;The description of the parameter&#8221;)<br \/>\ndefault: Optional[Union[int, float, str]] = None<br \/>\noptions: Optional[list[ToolParameterOption]] = None<\/p>\n<p>provider_id: str = Field(&#8230;, description=&#8221;The id of the provider&#8221;)<br \/>\ntool_name: str = Field(&#8230;, description=&#8221;The name of the tool&#8221;)<br \/>\ntool_description: str = Field(&#8230;, description=&#8221;The description of the tool&#8221;)<br \/>\ntool_configuration: Mapping[str, Any] = Field(&#8230;, description=&#8221;Configuration, type form&#8221;)<br \/>\ntool_parameters: Mapping[str, Parameter] = Field(&#8230;, description=&#8221;Parameters, type llm&#8221;)<\/p>\n<p>def to_prompt_message(self) -&gt; PromptMessageTool:<br \/>\n&#8220;&#8221;&#8221;<br \/>\nConvert tool selector to prompt message tool, based on openai <a href=\"https:\/\/www.kdjingpai.com\/en\/hanshudiaoyongfunct\/\">function calling<\/a> schema.<br \/>\n&#8220;&#8221;&#8221;<br \/>\ntool = PromptMessageTool(<br \/>\nname=self.tool_name,<br \/>\ndescription=self.tool_description,<br \/>\nparameters={<br \/>\n&#8220;type&#8221;: &#8220;object&#8221;,<br \/>\n&#8220;properties&#8221;: {},<br \/>\n&#8220;required&#8221;: [],<br \/>\n},<br \/>\n)<\/p>\n<p>for name, parameter in self.tool_parameters.items():<br \/>\ntool.parameters[name] = {<br \/>\n&#8220;type&#8221;: parameter.type.value,<br \/>\n&#8220;description&#8221;: parameter.description,<br \/>\n}<\/p>\n<p>if parameter.required:<br \/>\ntool.parameters[&#8220;required&#8221;].append(name)<\/p>\n<p>if parameter.options:<br \/>\ntool.parameters[name][&#8220;enum&#8221;] = [option.value for option in parameter.options]<\/p>\n<p>return tool<br \/>\nFile Purpose<br \/>\nThe tool YAML file (your_plugin.yaml) defines:<br \/>\nBasic identity information about your tool<br \/>\nDescriptions for both humans and the AI agent<br \/>\nParameters that your tool accepts<br \/>\nHow these parameters are presented and collected<br \/>\nExample Implementation<br \/>\nHere&#8217;s how the tool YAML files for Dropbox looks:<br \/>\nceate_folder.yaml:<br \/>\nidentity:<br \/>\nname: create_folder<br \/>\nauthor: lcandy<br \/>\nlabel:<br \/>\nen_US: Create Folder<br \/>\nzh_Hans: \u521b\u5efa\u6587\u4ef6\u5939<br \/>\npt_BR: Criar Pasta<br \/>\nja_JP: \u30d5\u30a9\u30eb\u30c0\u4f5c\u6210<br \/>\nzh_Hant: \u5efa\u7acb\u8cc7\u6599\u593e<br \/>\ndescription:<br \/>\nhuman:<br \/>\nen_US: Create a new folder in Dropbox<br \/>\nzh_Hans: \u5728 Dropbox \u4e2d\u521b\u5efa\u65b0\u6587\u4ef6\u5939<br \/>\npt_BR: Criar uma nova pasta no Dropbox<br \/>\nja_JP: Dropbox \u306b\u65b0\u3057\u3044\u30d5\u30a9\u30eb\u30c0\u3092\u4f5c\u6210\u3057\u307e\u3059<br \/>\nzh_Hant: \u5728 Dropbox \u4e2d\u5efa\u7acb\u65b0\u8cc7\u6599\u593e<br \/>\nllm: Creates a new folder at the specified path in Dropbox. Returns information about the created folder including path and ID.<br \/>\nparameters:<br \/>\n&#8211; name: folder_path<br \/>\ntype: string<br \/>\nrequired: true<br \/>\nlabel:<br \/>\nen_US: Folder Path<br \/>\nzh_Hans: \u6587\u4ef6\u5939\u8def\u5f84<br \/>\npt_BR: Caminho da Pasta<br \/>\nja_JP: \u30d5\u30a9\u30eb\u30c0\u30d1\u30b9<br \/>\nzh_Hant: \u8cc7\u6599\u593e\u8def\u5f91<br \/>\nhuman_description:<br \/>\nen_US: The path where the folder will be created in Dropbox<br \/>\nzh_Hans: \u6587\u4ef6\u5939\u5728 Dropbox \u4e2d\u7684\u521b\u5efa\u8def\u5f84<br \/>\npt_BR: O caminho onde a pasta ser\u00e1 criada no Dropbox<br \/>\nja_JP: Dropbox \u3067\u30d5\u30a9\u30eb\u30c0\u3092\u4f5c\u6210\u3059\u308b\u30d1\u30b9<br \/>\nzh_Hant: \u6b32\u5728 Dropbox \u4e2d\u5efa\u7acb\u8cc7\u6599\u593e\u7684\u8def\u5f91<br \/>\nllm_description: The path where the folder will be created in Dropbox. Should be specified as a complete path, like &#8216;\/Documents\/Projects&#8217; or &#8216;\/Photos\/Vacation2023&#8217;. Paths are case-sensitive and must start with a forward slash.<br \/>\nform: llm<br \/>\nextra:<br \/>\npython:<br \/>\nsource: tools\/create_folder.py<br \/>\ndelete_file.yaml:<br \/>\nidentity:<br \/>\nname: delete_file<br \/>\nauthor: lcandy<br \/>\nlabel:<br \/>\nen_US: Delete File\/Folder<br \/>\nzh_Hans: \u5220\u9664\u6587\u4ef6\/\u6587\u4ef6\u5939<br \/>\npt_BR: Excluir Arquivo\/Pasta<br \/>\nja_JP: \u30d5\u30a1\u30a4\u30eb\/\u30d5\u30a9\u30eb\u30c0\u524a\u9664<br \/>\nzh_Hant: \u522a\u9664\u6a94\u6848\/\u8cc7\u6599\u593e<br \/>\ndescription:<br \/>\nhuman:<br \/>\nen_US: Delete a file or folder from Dropbox<br \/>\nzh_Hans: \u4ece Dropbox \u5220\u9664\u6587\u4ef6\u6216\u6587\u4ef6\u5939<br \/>\npt_BR: Excluir um arquivo ou pasta do Dropbox<br \/>\nja_JP: Dropbox \u304b\u3089\u30d5\u30a1\u30a4\u30eb\u3084\u30d5\u30a9\u30eb\u30c0\u3092\u524a\u9664\u3057\u307e\u3059<br \/>\nzh_Hant: \u5f9e Dropbox \u522a\u9664\u6a94\u6848\u6216\u8cc7\u6599\u593e<br \/>\nllm: Permanently deletes a file or folder from Dropbox at the specified path. Returns confirmation information about the deleted item.<br \/>\nparameters:<br \/>\n&#8211; name: file_path<br \/>\ntype: string<br \/>\nrequired: true<br \/>\nlabel:<br \/>\nen_US: File\/Folder Path<br \/>\nzh_Hans: \u6587\u4ef6\/\u6587\u4ef6\u5939\u8def\u5f84<br \/>\npt_BR: Caminho do Arquivo\/Pasta<br \/>\nja_JP: \u30d5\u30a1\u30a4\u30eb\/\u30d5\u30a9\u30eb\u30c0\u30d1\u30b9<br \/>\nzh_Hant: \u6a94\u6848\/\u8cc7\u6599\u593e\u8def\u5f91<br \/>\nhuman_description:<br \/>\nen_US: The path of the file or folder to delete from Dropbox<br \/>\nzh_Hans: \u8981\u4ece Dropbox \u5220\u9664\u7684\u6587\u4ef6\u6216\u6587\u4ef6\u5939\u7684\u8def\u5f84<br \/>\npt_BR: O caminho do arquivo ou pasta para excluir do Dropbox<br \/>\nja_JP: Dropbox \u304b\u3089\u524a\u9664\u3059\u308b\u30d5\u30a1\u30a4\u30eb\u3084\u30d5\u30a9\u30eb\u30c0\u306e\u30d1\u30b9<br \/>\nzh_Hant: \u6b32\u5f9e Dropbox \u522a\u9664\u7684\u6a94\u6848\u6216\u8cc7\u6599\u593e\u8def\u5f91<br \/>\nllm_description: The path of the file or folder to delete from Dropbox. Should be specified as a complete path, like &#8216;\/Documents\/report.txt&#8217; or &#8216;\/Photos\/Vacation2023&#8217;. Paths are case-sensitive and must start with a forward slash. WARNING &#8211; This is a permanent deletion.<br \/>\nform: llm<br \/>\nextra:<br \/>\npython:<br \/>\nsource: tools\/delete_file.py<br \/>\ndownload_file.py:<br \/>\nidentity:<br \/>\nname: download_file<br \/>\nauthor: lcandy<br \/>\nlabel:<br \/>\nen_US: Download File<br \/>\nzh_Hans: \u4e0b\u8f7d\u6587\u4ef6<br \/>\npt_BR: Baixar Arquivo<br \/>\nja_JP: \u30d5\u30a1\u30a4\u30eb\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9<br \/>\nzh_Hant: \u4e0b\u8f09\u6a94\u6848<br \/>\ndescription:<br \/>\nhuman:<br \/>\nen_US: Download a file from Dropbox<br \/>\nzh_Hans: \u4ece Dropbox \u4e0b\u8f7d\u6587\u4ef6<br \/>\npt_BR: Baixar um arquivo do Dropbox<br \/>\nja_JP: Dropbox \u304b\u3089\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u307e\u3059<br \/>\nzh_Hant: \u5f9e Dropbox \u4e0b\u8f09\u6a94\u6848<br \/>\nllm: Downloads a file from Dropbox at the specified path. Returns file metadata and optionally the file content (as base64 for binary files or text for text files).<br \/>\nparameters:<br \/>\n&#8211; name: file_path<br \/>\ntype: string<br \/>\nrequired: true<br \/>\nlabel:<br \/>\nen_US: File Path<br \/>\nzh_Hans: \u6587\u4ef6\u8def\u5f84<br \/>\npt_BR: Caminho do Arquivo<br \/>\nja_JP: \u30d5\u30a1\u30a4\u30eb\u30d1\u30b9<br \/>\nzh_Hant: \u6a94\u6848\u8def\u5f91<br \/>\nhuman_description:<br \/>\nen_US: The path of the file to download from Dropbox<br \/>\nzh_Hans: \u8981\u4ece Dropbox \u4e0b\u8f7d\u7684\u6587\u4ef6\u8def\u5f84<br \/>\npt_BR: O caminho do arquivo para baixar do Dropbox<br \/>\nja_JP: Dropbox \u304b\u3089\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb\u306e\u30d1\u30b9<br \/>\nzh_Hant: \u6b32\u5f9e Dropbox \u4e0b\u8f09\u7684\u6a94\u6848\u8def\u5f91<br \/>\nllm_description: The path of the file to download from Dropbox. Should include the complete path with filename and extension, like &#8216;\/Documents\/report.txt&#8217;. Paths are case-sensitive and must start with a forward slash.<br \/>\nform: llm<br \/>\n&#8211; name: include_content<br \/>\ntype: boolean<br \/>\nrequired: false<br \/>\ndefault: false<br \/>\nlabel:<br \/>\nen_US: Include Content<br \/>\nzh_Hans: \u5305\u542b\u5185\u5bb9<br \/>\npt_BR: Incluir Conte\u00fado<br \/>\nja_JP: \u5185\u5bb9\u3092\u542b\u3081\u308b<br \/>\nzh_Hant: \u5305\u542b\u5167\u5bb9<br \/>\nhuman_description:<br \/>\nen_US: Whether to include the file content in the response<br \/>\nzh_Hans: \u662f\u5426\u5728\u54cd\u5e94\u4e2d\u5305\u542b\u6587\u4ef6\u5185\u5bb9<br \/>\npt_BR: Se deve incluir o conte\u00fado do arquivo na resposta<br \/>\nja_JP: \u30ec\u30b9\u30dd\u30f3\u30b9\u306b\u30d5\u30a1\u30a4\u30eb\u306e\u5185\u5bb9\u3092\u542b\u3081\u308b\u304b\u3069\u3046\u304b<br \/>\nzh_Hant: \u662f\u5426\u5728\u56de\u61c9\u4e2d\u5305\u542b\u6a94\u6848\u5167\u5bb9<br \/>\nllm_description: Set to true to include the file content in the response. For small text files, the content will be provided as text. For binary files, the content will be provided as base64-encoded string. Default is false.<br \/>\nform: llm<br \/>\nextra:<br \/>\npython:<br \/>\nsource: tools\/download_file.py<\/p>\n<p>dropbox.yaml:<br \/>\nidentity:<br \/>\nname: dropbox<br \/>\nauthor: lcandy<br \/>\nlabel:<br \/>\nen_US: Dropbox<br \/>\nzh_Hans: Dropbox<br \/>\npt_BR: Dropbox<br \/>\nja_JP: Dropbox<br \/>\nzh_Hant: Dropbox<br \/>\ndescription:<br \/>\nhuman:<br \/>\nen_US: Interact with Dropbox<br \/>\nzh_Hans: \u4e0e Dropbox \u4ea4\u4e92<br \/>\npt_BR: Interagir com o Dropbox<br \/>\nja_JP: Dropbox \u3068\u9023\u643a\u3059\u308b<br \/>\nzh_Hant: \u8207 Dropbox \u4e92\u52d5<br \/>\nllm: Provides access to Dropbox services, allowing you to interact with files and folders in a Dropbox account.<br \/>\nparameters:<br \/>\n&#8211; name: query<br \/>\ntype: string<br \/>\nrequired: true<br \/>\nlabel:<br \/>\nen_US: Query string<br \/>\nzh_Hans: \u67e5\u8be2\u8bed\u53e5<br \/>\npt_BR: Termo de consulta<br \/>\nja_JP: \u30af\u30a8\u30ea\u6587\u5b57\u5217<br \/>\nzh_Hant: \u67e5\u8a62\u8a9e\u53e5<br \/>\nhuman_description:<br \/>\nen_US: Enter your Dropbox operation query<br \/>\nzh_Hans: \u8f93\u5165\u60a8\u7684 Dropbox \u64cd\u4f5c\u67e5\u8be2<br \/>\npt_BR: Digite sua consulta de opera\u00e7\u00e3o do Dropbox<br \/>\nja_JP: Dropbox \u64cd\u4f5c\u30af\u30a8\u30ea\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044<br \/>\nzh_Hant: \u8acb\u8f38\u5165\u60a8\u8981\u57f7\u884c\u7684 Dropbox \u64cd\u4f5c\u6307\u4ee4<br \/>\nllm_description: The query describing the Dropbox operation you want to perform.<br \/>\nform: llm<br \/>\nextra:<br \/>\npython:<br \/>\nsource: tools\/dropbox.py<br \/>\nKey Components<br \/>\nIdentity Section:<br \/>\nname: Internal name for your tool (should match your file naming)<br \/>\nauthor: Who created the tool<br \/>\nlabel: Display name in different languages<br \/>\nDescription Section:<br \/>\nhuman: Description shown to human users in different languages<br \/>\nllm: Description provided to the AI agent to understand what your tool does and how to use it<br \/>\nParameters Section:<br \/>\nList of parameters your tool accepts, each with:<br \/>\nname: Parameter identifier (used in your Python code)<br \/>\ntype: Data type (string, number, boolean, etc.)<br \/>\nrequired: Whether this parameter is mandatory<br \/>\nlabel: User-friendly name in different languages<br \/>\nhuman_description: Explanation for human users in different languages<br \/>\nllm_description: Explanation for the AI agent to understand this parameter<br \/>\nform: How the parameter is collected<br \/>\nllm: AI agent extracts from user queries<br \/>\nworkflow: User must provide as a variable in the UI<br \/>\nOptional: default: Default value for this parameter<br \/>\nExtra Section:<br \/>\npython.source: Path to your tool&#8217;s Python implementation file<br \/>\nImportant Notes<br \/>\nFile Seperation:<br \/>\nImportant!!!!: If your tool has different functionality, like read and write a email, or read or update a database, you should seperate the yaml file into more than one. The principle is each yaml and code file are exclusively for each type of tool execution. The file itself should only extract parameter that the tool\u2019s functionality will use. For example, for to read and update a database, you should use two yaml file: read_database.yaml and update_database.yaml seperately.<br \/>\nLLM Descriptions:<br \/>\nThe llm description for both the tool and parameters is crucial &#8211; it tells the AI agent how to use your tool<br \/>\nBe clear about what parameters are needed and what information your tool will return<br \/>\nThis helps the AI agent decide when to use your tool and how to extract parameters from user queries<br \/>\nParameter Configuration:<br \/>\nFor each parameter, specify whether it&#8217;s required<br \/>\nChoose the appropriate data type<br \/>\nSet the form to llm if you want the AI to extract it from user queries<br \/>\nSet the form to workflow if you want users to provide it directly<br \/>\nLocalization:<br \/>\nProvide translations for labels and descriptions in multiple languages as needed<br \/>\nAt minimum, include English (en_US)<br \/>\nTo create your own tool YAML file, adapt this structure for your specific tool, clearly defining what parameters it needs and how they should be presented to both humans and the AI agent.<br \/>\n5. How to edit tools\/your_plugin.py<br \/>\nYou&#8217;re tasked with creating the tool implementation file for a Dify plugin. This file contains the actual logic for your tool that makes API requests and processes the results. I&#8217;ll guide you through creating this file, using Google Search as an example.<br \/>\nFile Purpose<br \/>\nThe tool Python file (your_plugin.py) is responsible for:<br \/>\nMaking API requests to your service<br \/>\nProcessing the responses<br \/>\nReturning the results in a format usable by Dify<br \/>\nRequired Components<br \/>\nYour tool class must inherit from dify_plugin.Tool<br \/>\nYou must implement the _invoke method that returns a generator<br \/>\nYou must include these essential imports:<br \/>\nfrom collections.abc import Generator<br \/>\nfrom typing import Any<br \/>\nfrom dify_plugin import Tool<br \/>\nfrom dify_plugin.entities.tool import ToolInvokeMessage<br \/>\nExample Implementation<br \/>\nHere&#8217;s how the tool implementation for Dropbox tool looks:<br \/>\nceate_folder.yaml:<br \/>\nfrom collections.abc import Generator<br \/>\nfrom typing import Any<\/p>\n<p>from dify_plugin import Tool<br \/>\nfrom dify_plugin.entities.tool import ToolInvokeMessage<br \/>\nfrom dropbox.exceptions import ApiError, AuthError<\/p>\n<p>from dropbox_utils import DropboxUtils<\/p>\n<p>class CreateFolderTool(Tool):<br \/>\ndef _invoke(self, tool_parameters: dict[str, Any]) -&gt; Generator[ToolInvokeMessage, None, None]:<br \/>\n&#8220;&#8221;&#8221;<br \/>\nCreate a folder in Dropbox<br \/>\n&#8220;&#8221;&#8221;<br \/>\n# Get parameters<br \/>\nfolder_path = tool_parameters.get(&#8220;folder_path&#8221;, &#8220;&#8221;)<\/p>\n<p># Validate parameters<br \/>\nif not folder_path:<br \/>\nyield self.create_text_message(&#8220;Folder path in Dropbox is required.&#8221;)<br \/>\nreturn<\/p>\n<p># Make sure folder path starts with \/<br \/>\nif not folder_path.startswith(&#8220;\/&#8221;):<br \/>\nfolder_path = &#8220;\/&#8221; + folder_path<\/p>\n<p>try:<br \/>\n# Get access token from credentials<br \/>\naccess_token = self.runtime.credentials.get(&#8220;access_token&#8221;)<br \/>\nif not access_token:<br \/>\nyield self.create_text_message(&#8220;Dropbox access token is required.&#8221;)<br \/>\nreturn<\/p>\n<p># Get Dropbox client<br \/>\ntry:<br \/>\ndbx = DropboxUtils.get_client(access_token)<br \/>\nexcept AuthError as e:<br \/>\nyield self.create_text_message(f&#8221;Authentication failed: {str(e)}&#8221;)<br \/>\nreturn<br \/>\nexcept Exception as e:<br \/>\nyield self.create_text_message(f&#8221;Failed to connect to Dropbox: {str(e)}&#8221;)<br \/>\nreturn<\/p>\n<p># Create the folder<br \/>\ntry:<br \/>\nresult = DropboxUtils.create_folder(dbx, folder_path)<\/p>\n<p># Create response<br \/>\nsummary = f&#8221;Folder &#8216;{result[&#8216;name&#8217;]}&#8217; created successfully at &#8216;{result[&#8216;path&#8217;]}'&#8221;<br \/>\nyield self.create_text_message(summary)<br \/>\nyield self.create_json_message(result)<\/p>\n<p>except ApiError as e:<br \/>\nif &#8220;path\/conflict&#8221; in str(e):<br \/>\nyield self.create_text_message(f&#8221;A folder already exists at &#8216;{folder_path}'&#8221;)<br \/>\nelse:<br \/>\nyield self.create_text_message(f&#8221;Error creating folder: {str(e)}&#8221;)<br \/>\nreturn<\/p>\n<p>except Exception as e:<br \/>\nyield self.create_text_message(f&#8221;Error: {str(e)}&#8221;)<br \/>\nreturn<br \/>\ndelete_file.py<br \/>\nfrom collections.abc import Generator<br \/>\nfrom typing import Any<\/p>\n<p>from dify_plugin import Tool<br \/>\nfrom dify_plugin.entities.tool import ToolInvokeMessage<br \/>\nfrom dropbox.exceptions import ApiError, AuthError<\/p>\n<p>from dropbox_utils import DropboxUtils<\/p>\n<p>class DeleteFileTool(Tool):<br \/>\ndef _invoke(self, tool_parameters: dict[str, Any]) -&gt; Generator[ToolInvokeMessage, None, None]:<br \/>\n&#8220;&#8221;&#8221;<br \/>\nDelete a file or folder from Dropbox<br \/>\n&#8220;&#8221;&#8221;<br \/>\n# Get parameters<br \/>\nfile_path = tool_parameters.get(&#8220;file_path&#8221;, &#8220;&#8221;)<\/p>\n<p># Validate parameters<br \/>\nif not file_path:<br \/>\nyield self.create_text_message(&#8220;File or folder path in Dropbox is required.&#8221;)<br \/>\nreturn<\/p>\n<p># Make sure path starts with \/<br \/>\nif not file_path.startswith(&#8220;\/&#8221;):<br \/>\nfile_path = &#8220;\/&#8221; + file_path<\/p>\n<p>try:<br \/>\n# Get access token from credentials<br \/>\naccess_token = self.runtime.credentials.get(&#8220;access_token&#8221;)<br \/>\nif not access_token:<br \/>\nyield self.create_text_message(&#8220;Dropbox access token is required.&#8221;)<br \/>\nreturn<\/p>\n<p># Get Dropbox client<br \/>\ntry:<br \/>\ndbx = DropboxUtils.get_client(access_token)<br \/>\nexcept AuthError as e:<br \/>\nyield self.create_text_message(f&#8221;Authentication failed: {str(e)}&#8221;)<br \/>\nreturn<br \/>\nexcept Exception as e:<br \/>\nyield self.create_text_message(f&#8221;Failed to connect to Dropbox: {str(e)}&#8221;)<br \/>\nreturn<\/p>\n<p># Delete the file or folder<br \/>\ntry:<br \/>\nresult = DropboxUtils.delete_file(dbx, file_path)<\/p>\n<p># Create response<br \/>\nsummary = f&#8221;&#8216;{result[&#8216;name&#8217;]}&#8217; deleted successfully&#8221;<br \/>\nyield self.create_text_message(summary)<br \/>\nyield self.create_json_message(result)<\/p>\n<p>except ApiError as e:<br \/>\nif &#8220;path\/not_found&#8221; in str(e):<br \/>\nyield self.create_text_message(f&#8221;File or folder not found at &#8216;{file_path}'&#8221;)<br \/>\nelse:<br \/>\nyield self.create_text_message(f&#8221;Error deleting file\/folder: {str(e)}&#8221;)<br \/>\nreturn<\/p>\n<p>except Exception as e:<br \/>\nyield self.create_text_message(f&#8221;Error: {str(e)}&#8221;)<br \/>\nreturn<br \/>\ndownload_file.yaml:<br \/>\nfrom collections.abc import Generator<br \/>\nimport base64<br \/>\nfrom typing import Any<\/p>\n<p>from dify_plugin import Tool<br \/>\nfrom dify_plugin.entities.tool import ToolInvokeMessage<br \/>\nfrom dropbox.exceptions import ApiError, AuthError<\/p>\n<p>from dropbox_utils import DropboxUtils<\/p>\n<p>class DownloadFileTool(Tool):<br \/>\ndef _invoke(self, tool_parameters: dict[str, Any]) -&gt; Generator[ToolInvokeMessage, None, None]:<br \/>\n&#8220;&#8221;&#8221;<br \/>\nDownload a file from Dropbox<br \/>\n&#8220;&#8221;&#8221;<br \/>\n# Get parameters<br \/>\nfile_path = tool_parameters.get(&#8220;file_path&#8221;, &#8220;&#8221;)<br \/>\ninclude_content = tool_parameters.get(&#8220;include_content&#8221;, False)<\/p>\n<p># Validate parameters<br \/>\nif not file_path:<br \/>\nyield self.create_text_message(&#8220;File path in Dropbox is required.&#8221;)<br \/>\nreturn<\/p>\n<p># Make sure file path starts with \/<br \/>\nif not file_path.startswith(&#8220;\/&#8221;):<br \/>\nfile_path = &#8220;\/&#8221; + file_path<\/p>\n<p>try:<br \/>\n# Get access token from credentials<br \/>\naccess_token = self.runtime.credentials.get(&#8220;access_token&#8221;)<br \/>\nif not access_token:<br \/>\nyield self.create_text_message(&#8220;Dropbox access token is required.&#8221;)<br \/>\nreturn<\/p>\n<p># Get Dropbox client<br \/>\ntry:<br \/>\ndbx = DropboxUtils.get_client(access_token)<br \/>\nexcept AuthError as e:<br \/>\nyield self.create_text_message(f&#8221;Authentication failed: {str(e)}&#8221;)<br \/>\nreturn<br \/>\nexcept Exception as e:<br \/>\nyield self.create_text_message(f&#8221;Failed to connect to Dropbox: {str(e)}&#8221;)<br \/>\nreturn<\/p>\n<p># Download the file<br \/>\ntry:<br \/>\nresult = DropboxUtils.download_file(dbx, file_path)<\/p>\n<p># Create response<br \/>\nresponse = {<br \/>\n&#8220;name&#8221;: result[&#8220;name&#8221;],<br \/>\n&#8220;path&#8221;: result[&#8220;path&#8221;],<br \/>\n&#8220;id&#8221;: result[&#8220;id&#8221;],<br \/>\n&#8220;size&#8221;: result[&#8220;size&#8221;],<br \/>\n&#8220;modified&#8221;: result[&#8220;modified&#8221;]<br \/>\n}<\/p>\n<p># Include content if requested<br \/>\nif include_content:<br \/>\n# Encode binary content as base64<br \/>\nresponse[&#8220;content_base64&#8221;] = base64.b64encode(result[&#8220;content&#8221;]).decode(&#8216;utf-8&#8217;)<\/p>\n<p># Try to decode as text if small enough<br \/>\nif result[&#8220;size&#8221;] &lt; 1024 * 1024: # Less than 1MB<br \/>\ntry:<br \/>\ntext_content = result[&#8220;content&#8221;].decode(&#8216;utf-8&#8217;)<br \/>\nresponse[&#8220;content_text&#8221;] = text_content<br \/>\nexcept UnicodeDecodeError:<br \/>\n# Not a text file, just include base64<br \/>\npass<\/p>\n<p>summary = f&#8221;File &#8216;{result[&#8216;name&#8217;]}&#8217; downloaded successfully&#8221;<br \/>\nyield self.create_text_message(summary)<br \/>\nyield self.create_json_message(response)<\/p>\n<p>except ApiError as e:<br \/>\nyield self.create_text_message(f&#8221;Error downloading file: {str(e)}&#8221;)<br \/>\nreturn<\/p>\n<p>except Exception as e:<br \/>\nyield self.create_text_message(f&#8221;Error: {str(e)}&#8221;)<br \/>\nreturn<br \/>\nKey Points to Remember<br \/>\nFile Seperation:<br \/>\nImportant: If your tool has different functionality, like read and write a email, or read or update a database, you should seperate the yaml file into more than one. The principle is each yaml and code file are exclusively for each type of tool execution. The file itself should only extract parameter that the tool\u2019s functionality will use. For example, for to read and update a database, you should use two yaml file: read_database.py and update_database.py seperately.<br \/>\nRequired Imports: Always include the essential imports at the top of your file.<br \/>\nClass Inheritance: Your tool class must inherit from dify_plugin.Tool<br \/>\nExtracting Parameters:<br \/>\nThe tool_parameters dictionary contains all parameters defined in your tool&#8217;s YAML file<br \/>\nAccess these parameters directly using dictionary keys, e.g., tool_parameters[&#8220;query&#8221;]<br \/>\nThese parameters are automatically extracted from user queries by the AI agent<br \/>\nEnsure you handle any required parameters and provide appropriate error handling if they&#8217;re missing<br \/>\nExample:<br \/>\nquery = tool_parameters[&#8220;query&#8221;] # Extract the query parameterlimit = tool_parameters.get(&#8220;limit&#8221;, 10) # Extract with a default value# Optional validationif not query: raise ValueError(&#8220;Query parameter cannot be empty&#8221;)<br \/>\nAccessing Credentials:<br \/>\nAccess your authentication credentials using self.runtime.credentials<br \/>\nThe keys match those defined in your provider YAML file<br \/>\nExample: self.runtime.credentials[&#8220;serpapi_api_key&#8221;]<br \/>\nResponse Processing: Create a helper method to extract only the relevant information from API responses.<br \/>\nYielding Results: You must use yield with one of the message creation methods to return data.<br \/>\nWhen implementing your own tool, make sure you correctly extract all the parameters you need from the tool_parameters dictionary, and validate them if necessary. The available parameters are defined in your tool&#8217;s YAML file and will be automatically extracted from user queries by the AI agent.<br \/>\n6. How to Create PRIVACY.md and README.md<br \/>\nYou&#8217;re tasked with creating the privacy policy and readme files for your Dify plugin. These files are written in Markdown format and serve important purposes for users and developers of your plugin. Let me guide you through creating both files.<br \/>\nPRIVACY.md<br \/>\nThe PRIVACY.md file <a href=\"https:\/\/www.kdjingpai.com\/en\/outlines\/\">outlines<\/a> your plugin&#8217;s privacy practices, including what data it collects and how that data is used. This is critical information for users concerned about their data privacy.<br \/>\nWhat to Include<br \/>\nBased on the placeholder text you shared (&#8220;!!! Please fill in the privacy policy of the plugin.&#8221;), you should include:<br \/>\nWhat data your plugin collects<br \/>\nHow this data is stored and processed<br \/>\nWhat third-party services are used (if any)<br \/>\nUser rights regarding their data<br \/>\nHow long data is retained<br \/>\nContact information for privacy concerns<br \/>\nExample Structure<br \/>\n# Privacy Policy<\/p>\n<p>## Data Collection<br \/>\n[Describe what user data your plugin collects and why]<\/p>\n<p>## Data Processing<br \/>\n[Explain how the collected data is processed]<\/p>\n<p>## Third-party Services<br \/>\n[List any third-party services used by your plugin and link to their privacy policies]<\/p>\n<p>## Data Retention<br \/>\n[Explain how long user data is stored]<\/p>\n<p>## User Rights<br \/>\n[Outline what rights users have regarding their data]<\/p>\n<p>## Contact Information<br \/>\n[Provide contact information for privacy-related inquiries]<\/p>\n<p>Last updated: [Date]<br \/>\nREADME.md<br \/>\nThe README.md file provides essential information about your plugin, including what it does, how to install it, and how to use it. This is the first document most users and developers will refer to.<br \/>\nWhat to Include<br \/>\nBased on the example you shared (Jira plugin readme), you should include:<br \/>\nPlugin name as the main heading<br \/>\nAuthor information<br \/>\nVersion information<br \/>\nType of plugin<br \/>\nDetailed description of what the plugin does<br \/>\nInstallation instructions<br \/>\nUsage examples<br \/>\nConfiguration options<br \/>\nTroubleshooting information<br \/>\nExample Structure<br \/>\n# Your Plugin Name<\/p>\n<p>**Author:** [Your name or organization]<br \/>\n**Version:** [Current version number]<br \/>\n**Type:** [Plugin type]<\/p>\n<p>## Description<br \/>\n[Provide a detailed description of what your plugin does]<\/p>\n<p>## Features<br \/>\n&#8211; [Feature 1]<br \/>\n&#8211; [Feature 2]<br \/>\n&#8211; [Feature 3]<\/p>\n<p>## Installation<br \/>\n[Provide step-by-step installation instructions]<\/p>\n<p>## Configuration<br \/>\n[Explain how to configure your plugin]<\/p>\n<p>## Usage Examples<br \/>\n[Provide examples of how to use your plugin]<\/p>\n<p>## Troubleshooting<br \/>\n[List common issues and their solutions]<\/p>\n<p>## Contributing<br \/>\n[Explain how others can contribute to your plugin]<\/p>\n<p>## License<br \/>\n[Specify the license under which your plugin is released]<br \/>\nUsing Images<br \/>\nAs you mentioned, if you want to include images in either document:<br \/>\nStore the images in the _assets folder<br \/>\nReference them in your Markdown using relative paths:<br \/>\n![Description of image](_assets\/image_name.png)<br \/>\nBoth of these files should be written in Markdown format (.md extension) and placed in the root directory of your plugin project. Make sure to keep them up-to-date as your plugin evolves.<br \/>\nRequirements.txt<br \/>\nYou should always use the latest dependencies by using ~= in your txt file, and the dify_plugin~=0.0.1b72 is a must be.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You are a senior developer that can help me with developing Dify Plugin Tool, which is an AI Agent Tool that can be used&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18],"tags":[],"class_list":["post-30712","post","type-post","status-publish","format-standard","hentry","category-prompts"],"_links":{"self":[{"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/posts\/30712","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/comments?post=30712"}],"version-history":[{"count":0,"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/posts\/30712\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/media?parent=30712"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/categories?post=30712"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kdjingpai.com\/ja\/wp-json\/wp\/v2\/tags?post=30712"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}