filegenerator

pull/20511/head
chenyangzhao 12 months ago
parent 92528360f9
commit eda3e7d77d

@ -0,0 +1,179 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# Vscode
.vscode/
# Git
.git/
.gitignore
.github/
# Mac
.DS_Store
# Windows
Thumbs.db

@ -0,0 +1,3 @@
INSTALL_METHOD=remote
REMOTE_INSTALL_URL=debug.dify.ai:5003
REMOTE_INSTALL_KEY=********-****-****-****-************

@ -0,0 +1,109 @@
name: Plugin Publish Workflow
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download CLI tool
run: |
mkdir -p $RUNNER_TEMP/bin
cd $RUNNER_TEMP/bin
wget https://github.com/langgenius/dify-plugin-daemon/releases/download/0.0.6/dify-plugin-linux-amd64
chmod +x dify-plugin-linux-amd64
echo "CLI tool location:"
pwd
ls -la dify-plugin-linux-amd64
- name: Get basic info from manifest
id: get_basic_info
run: |
PLUGIN_NAME=$(grep "^name:" manifest.yaml | cut -d' ' -f2)
echo "Plugin name: $PLUGIN_NAME"
echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT
VERSION=$(grep "^version:" manifest.yaml | cut -d' ' -f2)
echo "Plugin version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
# If the author's name is not your github username, you can change the author here
AUTHOR=$(grep "^author:" manifest.yaml | cut -d' ' -f2)
echo "Plugin author: $AUTHOR"
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
- name: Package Plugin
id: package
run: |
cd $GITHUB_WORKSPACE
PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg"
$RUNNER_TEMP/bin/dify-plugin-linux-amd64 plugin package . -o "$PACKAGE_NAME"
echo "Package result:"
ls -la "$PACKAGE_NAME"
echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT
echo "\nFull file path:"
pwd
echo "\nDirectory structure:"
tree || ls -R
- name: Checkout target repo
uses: actions/checkout@v3
with:
repository: ${{steps.get_basic_info.outputs.author}}/dify-plugins
path: dify-plugins
token: ${{ secrets.PLUGIN_ACTION }}
fetch-depth: 1
persist-credentials: true
- name: Prepare and create PR
run: |
PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg"
mkdir -p dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}
mv "$PACKAGE_NAME" dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}/
cd dify-plugins
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git fetch origin main
git checkout main
git pull origin main
BRANCH_NAME="bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{ steps.get_basic_info.outputs.version }}"
git checkout -b "$BRANCH_NAME"
git add .
git commit -m "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}"
git push -u origin "$BRANCH_NAME" --force
git branch -a
echo "Waiting for branch to sync..."
sleep 10 # Wait 10 seconds for branch sync
- name: Create PR via GitHub API
env:
# How to config the token:
# 1. Profile -> Settings -> Developer settings -> Personal access tokens -> Generate new token (with repo scope) -> Copy the token
# 2. Go to the target repository -> Settings -> Secrets and variables -> Actions -> New repository secret -> Add the token as PLUGIN_ACTION
GH_TOKEN: ${{ secrets.PLUGIN_ACTION }}
run: |
gh pr create \
--repo langgenius/dify-plugins \
--head "${{ steps.get_basic_info.outputs.author }}:${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}" \
--base main \
--title "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}" \
--body "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin package to version ${{ steps.get_basic_info.outputs.version }}
Changes:
- Updated plugin package file" || echo "PR already exists or creation skipped." # Handle cases where PR already exists

@ -0,0 +1,176 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# Vscode
.vscode/
# macOS
.DS_Store
.AppleDouble
.LSOverride

@ -0,0 +1,148 @@
## User Guide of how to develop a Dify Plugin
Hi there, looks like you have already created a Plugin, now let's get you started with the development!
### Choose a Plugin type you want to develop
Before start, you need some basic knowledge about the Plugin types, Plugin supports to extend the following abilities in Dify:
- **Tool**: Tool Providers like Google Search, Stable Diffusion, etc. it can be used to perform a specific task.
- **Model**: Model Providers like OpenAI, Anthropic, etc. you can use their models to enhance the AI capabilities.
- **Endpoint**: Like Service API in Dify and Ingress in Kubernetes, you can extend a http service as an endpoint and control its logics using your own code.
Based on the ability you want to extend, we have divided the Plugin into three types: **Tool**, **Model**, and **Extension**.
- **Tool**: It's a tool provider, but not only limited to tools, you can implement an endpoint there, for example, you need both `Sending Message` and `Receiving Message` if you are building a Discord Bot, **Tool** and **Endpoint** are both required.
- **Model**: Just a model provider, extending others is not allowed.
- **Extension**: Other times, you may only need a simple http service to extend the functionalities, **Extension** is the right choice for you.
I believe you have chosen the right type for your Plugin while creating it, if not, you can change it later by modifying the `manifest.yaml` file.
### Manifest
Now you can edit the `manifest.yaml` file to describe your Plugin, here is the basic structure of it:
- version(version, required)Plugin's version
- type(type, required)Plugin's type, currently only supports `plugin`, future support `bundle`
- author(string, required)Author, it's the organization name in Marketplace and should also equals to the owner of the repository
- label(label, required)Multi-language name
- created_at(RFC3339, required)Creation time, Marketplace requires that the creation time must be less than the current time
- icon(asset, required)Icon path
- resource (object)Resources to be applied
- memory (int64)Maximum memory usage, mainly related to resource application on SaaS for serverless, unit bytes
- permission(object)Permission application
- tool(object)Reverse call tool permission
- enabled (bool)
- model(object)Reverse call model permission
- enabled(bool)
- llm(bool)
- text_embedding(bool)
- rerank(bool)
- tts(bool)
- speech2text(bool)
- moderation(bool)
- node(object)Reverse call node permission
- enabled(bool)
- endpoint(object)Allow to register endpoint permission
- enabled(bool)
- app(object)Reverse call app permission
- enabled(bool)
- storage(object)Apply for persistent storage permission
- enabled(bool)
- size(int64)Maximum allowed persistent memory, unit bytes
- plugins(object, required)Plugin extension specific ability yaml file list, absolute path in the plugin package, if you need to extend the model, you need to define a file like openai.yaml, and fill in the path here, and the file on the path must exist, otherwise the packaging will fail.
- Format
- tools(list[string]): Extended tool suppliers, as for the detailed format, please refer to [Tool Guide](https://docs.dify.ai/plugins/schema-definition/tool)
- models(list[string])Extended model suppliers, as for the detailed format, please refer to [Model Guide](https://docs.dify.ai/plugins/schema-definition/model)
- endpoints(list[string])Extended Endpoints suppliers, as for the detailed format, please refer to [Endpoint Guide](https://docs.dify.ai/plugins/schema-definition/endpoint)
- Restrictions
- Not allowed to extend both tools and models
- Not allowed to have no extension
- Not allowed to extend both models and endpoints
- Currently only supports up to one supplier of each type of extension
- meta(object)
- version(version, required)manifest format version, initial version 0.0.1
- arch(list[string], required)Supported architectures, currently only supports amd64 arm64
- runner(object, required)Runtime configuration
- language(string)Currently only supports python
- version(string)Language version, currently only supports 3.12
- entrypoint(string)Program entry, in python it should be main
### Install Dependencies
- First of all, you need a Python 3.11+ environment, as our SDK requires that.
- Then, install the dependencies:
```bash
pip install -r requirements.txt
```
- If you want to add more dependencies, you can add them to the `requirements.txt` file, once you have set the runner to python in the `manifest.yaml` file, `requirements.txt` will be automatically generated and used for packaging and deployment.
### Implement the Plugin
Now you can start to implement your Plugin, by following these examples, you can quickly understand how to implement your own Plugin:
- [OpenAI](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/openai): best practice for model provider
- [Google Search](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/google): a simple example for tool provider
- [Neko](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko): a funny example for endpoint group
### Test and Debug the Plugin
You may already noticed that a `.env.example` file in the root directory of your Plugin, just copy it to `.env` and fill in the corresponding values, there are some environment variables you need to set if you want to debug your Plugin locally.
- `INSTALL_METHOD`: Set this to `remote`, your plugin will connect to a Dify instance through the network.
- `REMOTE_INSTALL_URL`: The URL of debugging host and port of plugin-daemon service from your Dify instance, eg. `debug.dify.ai:5003`. Either the [Dify SaaS](https://debug.dify.ai) or [self-hosted Dify instance](https://docs.dify.ai/en/getting-started/install-self-hosted/readme) can be used.
- `REMOTE_INSTALL_KEY`: You should get your debugging key from the Dify instance you used, at the right top of the plugin management page, you can see a button with a `debug` icon, click it and you will get the key.
Run the following command to start your Plugin:
```bash
python -m main
```
Refresh the page of your Dify instance, you should be able to see your Plugin in the list now, but it will be marked as `debugging`, you can use it normally, but not recommended for production.
### Publish and Update the Plugin
To streamline your plugin update workflow, you can configure GitHub Actions to automatically create PRs to the Dify plugin repository whenever you create a release.
##### Prerequisites
- Your plugin source repository
- A fork of the dify-plugins repository
- Proper plugin directory structure in your fork
#### Configure GitHub Action
1. Create a Personal Access Token with write permissions to your forked repository
2. Add it as a secret named `PLUGIN_ACTION` in your source repository settings
3. Create a workflow file at `.github/workflows/plugin-publish.yml`
#### Usage
1. Update your code and the version in your `manifest.yaml`
2. Create a release in your source repository
3. The action automatically packages your plugin and creates a PR to your forked repository
#### Benefits
- Eliminates manual packaging and PR creation steps
- Ensures consistency in your release process
- Saves time during frequent updates
---
For detailed setup instructions and example configuration, visit: [GitHub Actions Workflow Documentation](https://docs.dify.ai/plugins/publish-plugins/plugin-auto-publish-pr)
### Package the Plugin
After all, just package your Plugin by running the following command:
```bash
dify-plugin plugin package ./ROOT_DIRECTORY_OF_YOUR_PLUGIN
```
you will get a `plugin.difypkg` file, that's all, you can submit it to the Marketplace now, look forward to your Plugin being listed!
## User Privacy Policy
Please fill in the privacy policy of the plugin if you want to make it published on the Marketplace, refer to [PRIVACY.md](PRIVACY.md) for more details.

@ -0,0 +1,3 @@
## Privacy
!!! Please fill in the privacy policy of the plugin.

@ -0,0 +1,10 @@
## filegenerator
**Author:** randomradio
**Version:** 0.0.1
**Type:** tool
### Description

@ -0,0 +1,6 @@
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<path d="M20 20 V80 M20 20 H60 Q80 20 80 40 T60 60 H20"
fill="none"
stroke="black"
stroke-width="5"/>
</svg>

After

Width:  |  Height:  |  Size: 203 B

@ -0,0 +1,6 @@
from dify_plugin import Plugin, DifyPluginEnv
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
if __name__ == '__main__':
plugin.run()

@ -0,0 +1,48 @@
version: 0.0.1
type: plugin
author: randomradio
name: filegenerator
label:
en_US: filegenerator
ja_JP: filegenerator
zh_Hans: filegenerator
pt_BR: filegenerator
description:
en_US: generate file with extension and content
ja_JP: generate file with extension and content
zh_Hans: generate file with extension and content
pt_BR: generate file with extension and content
icon: icon.svg
resource:
memory: 268435456
permission:
tool:
enabled: true
model:
enabled: false
llm: false
text_embedding: false
rerank: false
tts: false
speech2text: false
moderation: false
node:
enabled: false
app:
enabled: false
plugins:
tools:
- provider/filegenerator.yaml
meta:
version: 0.0.1
arch:
- amd64
- arm64
runner:
language: python
version: "3.12"
entrypoint: main
minimum_dify_version: null
created_at: 2025-05-30T14:51:13.938188+08:00
privacy: PRIVACY.md
verified: false

@ -0,0 +1,15 @@
from typing import Any
from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
class FilegeneratorProvider(ToolProvider):
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
try:
"""
IMPLEMENT YOUR VALIDATION HERE
"""
return None
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))

@ -0,0 +1,17 @@
identity:
author: randomradio
name: filegenerator
label:
en_US: filegenerator
zh_Hans: filegenerator
pt_BR: filegenerator
description:
en_US: generate file with extension and content
zh_Hans: generate file with extension and content
pt_BR: generate file with extension and content
icon: icon.svg
tools:
- tools/filegenerator.yaml
extra:
python:
source: provider/filegenerator.py

@ -0,0 +1,2 @@
dify_plugin>=0.2.0,<0.3.0
filegenerator

@ -0,0 +1,68 @@
from collections.abc import Generator
from typing import Any
import re
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
import mimetypes
class FilegeneratorTool(Tool):
"""Generate a downloadable text file based on user input."""
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
# 1. Retrieve parameters
content = tool_parameters.get("content")
title = tool_parameters.get("title")
ext = tool_parameters.get("ext", ".txt")
# Normalize extension
if not ext.startswith("."):
ext = "." + ext
# 2. Validate required parameters
if not content:
yield self.create_text_message("Error: content is required.")
return
if not title:
yield self.create_text_message("Error: title is required.")
return
# Sanitize title to create a safe filename (remove/replace problematic characters)
sanitized_title = re.sub(r"[^\w\-. ]+", "_", title).strip()
if not sanitized_title:
sanitized_title = "file"
filename = f"{sanitized_title}{ext}"
# Guess mime type, default to text/plain
mime_type, _ = mimetypes.guess_type(filename)
if mime_type is None:
mime_type = "text/plain"
try:
file_bytes = content.encode("utf-8")
# 3. Return file as blob message (Dify will handle storage & provide download URL)
yield self.create_blob_message(
blob=file_bytes,
meta={
"filename": filename,
"mime_type": mime_type,
},
)
# 4. Provide workflow variables / JSON
yield self.create_variable_message("download_filename", filename)
yield self.create_variable_message("download_url", "{{files[0].url}}")
yield self.create_json_message({
"download_url": "{{download_url}}", # Will be replaced by Dify
"filename": filename,
})
except Exception as e:
yield self.create_text_message(f"Error generating file: {str(e)}")

@ -0,0 +1,55 @@
identity:
name: filegenerator
author: randomradio
label:
en_US: filegenerator
zh_Hans: filegenerator
pt_BR: filegenerator
description:
human:
en_US: Generate a TXT (or custom extension) file for download with given content and title.
zh_Hans: 根据输入内容和标题生成可下载的 TXT 或指定扩展名文件。
llm: Generate a downloadable text file with given content, title and optional extension.
parameters:
- name: content
type: string
required: true
label:
en_US: File Content
zh_Hans: 文件内容
human_description:
en_US: Body content of the file to generate.
zh_Hans: 要生成的文件正文内容。
llm_description: The file body content.
form: llm
- name: title
type: string
required: true
label:
en_US: File Title
zh_Hans: 文件标题
human_description:
en_US: File name (without extension).
zh_Hans: 文件名称(不含扩展名)。
llm_description: The name of the file, not including extension.
form: llm
- name: ext
type: string
required: false
label:
en_US: File Extension
zh_Hans: 文件扩展名
human_description:
en_US: File extension (default is .txt, e.g. .md/.csv).
zh_Hans: 文件扩展名(默认为 .txt可自定义如 .md/.csv
llm_description: File extension to use, include the leading dot, e.g. ".txt" or ".md". Optional.
form: llm
extra:
python:
source: tools/filegenerator.py
output_schema:
type: object
properties:
download_url:
type: string
description: URL to download the generated file.
Loading…
Cancel
Save