mirror of
https://github.com/langgenius/dify-plugin-daemon.git
synced 2026-07-22 17:56:00 -04:00
2bb1b2f521
- Introduced a new `pull` command that retrieves all available tools from the Dify platform and saves them to the `.dify_cli.json` configuration file. - Implemented error handling for API requests and configuration saving to enhance reliability. - Updated the command initialization to include the new `pull` command in the CLI structure. - Enhanced the `EnvConfig` struct to include a `FilesURL` field for better tool management. This update improves the CLI's functionality by allowing users to easily pull and manage tools from the Dify platform.
244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
package tool
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/langgenius/dify-plugin-daemon/cmd/dify_cli/types"
|
|
)
|
|
|
|
type HandlerFunc func(chunk *types.DifyToolResponseChunk, w io.Writer) error
|
|
|
|
type Registry struct {
|
|
handlers map[types.DifyToolResponseChunkType]HandlerFunc
|
|
}
|
|
|
|
func NewRegistry() *Registry {
|
|
r := &Registry{handlers: make(map[types.DifyToolResponseChunkType]HandlerFunc)}
|
|
r.Register(types.ToolResponseChunkTypeText, handleText)
|
|
r.Register(types.ToolResponseChunkTypeJson, handleJSON)
|
|
r.Register(types.ToolResponseChunkTypeLink, handleLink)
|
|
r.Register(types.ToolResponseChunkTypeImage, handleImage)
|
|
r.Register(types.ToolResponseChunkTypeImageLink, handleImageLink)
|
|
r.Register(types.ToolResponseChunkTypeFile, handleFile)
|
|
r.Register(types.ToolResponseChunkTypeBlob, handleBlob)
|
|
r.Register(types.ToolResponseChunkTypeBlobChunk, handleBlobChunk)
|
|
r.Register(types.ToolResponseChunkTypeBinaryLink, handleBinaryLink)
|
|
r.Register(types.ToolResponseChunkTypeVariable, handleVariable)
|
|
r.Register(types.ToolResponseChunkTypeLog, handleLog)
|
|
r.Register(types.ToolResponseChunkTypeRetrieverResources, handleRetrieverResources)
|
|
return r
|
|
}
|
|
|
|
func (r *Registry) Register(t types.DifyToolResponseChunkType, h HandlerFunc) {
|
|
r.handlers[t] = h
|
|
}
|
|
|
|
func (r *Registry) Dispatch(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
if h, ok := r.handlers[chunk.Type]; ok {
|
|
return h(chunk, w)
|
|
}
|
|
data, _ := json.Marshal(chunk)
|
|
fmt.Fprintf(w, "[%s] %s\n", chunk.Type, string(data))
|
|
return nil
|
|
}
|
|
|
|
var globalRegistry = NewRegistry()
|
|
|
|
func Dispatch(chunk *types.DifyToolResponseChunk) error {
|
|
return globalRegistry.Dispatch(chunk, os.Stdout)
|
|
}
|
|
|
|
// Handlers
|
|
|
|
func handleText(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
if text, ok := chunk.Message["text"]; ok {
|
|
fmt.Fprintf(w, "%v", text)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleJSON(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
data, err := json.MarshalIndent(chunk.Message, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "%s\n", string(data))
|
|
return nil
|
|
}
|
|
|
|
func handleLink(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
if text, ok := chunk.Message["text"]; ok {
|
|
fmt.Fprintf(w, "[link] %v\n", text)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleImage(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
if url, ok := chunk.Message["url"]; ok {
|
|
fmt.Fprintf(w, "[image] %v\n", url)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleImageLink(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
text, ok := chunk.Message["text"].(string)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if isToolFileURL(text) {
|
|
return downloadAndPrint(text, "image", w)
|
|
}
|
|
fmt.Fprintf(w, "[image_link] %s\n", text)
|
|
return nil
|
|
}
|
|
|
|
func handleFile(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
text, ok := chunk.Message["text"].(string)
|
|
if !ok {
|
|
if url, ok := chunk.Message["url"].(string); ok {
|
|
text = url
|
|
}
|
|
}
|
|
if text != "" && isToolFileURL(text) {
|
|
return downloadAndPrint(text, "file", w)
|
|
}
|
|
fmt.Fprintf(w, "[file] %s\n", text)
|
|
return nil
|
|
}
|
|
|
|
func handleBlob(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
mimeType, _ := chunk.Meta["mime_type"]
|
|
fmt.Fprintf(w, "[blob] mime_type=%v\n", mimeType)
|
|
return nil
|
|
}
|
|
|
|
func handleBlobChunk(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
if data, ok := chunk.Message["data"]; ok {
|
|
fmt.Fprintf(w, "%v", data)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleBinaryLink(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
text, ok := chunk.Message["text"].(string)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if isToolFileURL(text) {
|
|
return downloadAndPrint(text, "binary", w)
|
|
}
|
|
fmt.Fprintf(w, "[binary_link] %s\n", text)
|
|
return nil
|
|
}
|
|
|
|
func handleVariable(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
name := chunk.Message["variable_name"]
|
|
value := chunk.Message["variable_value"]
|
|
if chunk.Message["stream"] == true {
|
|
fmt.Fprintf(w, "[variable:stream] %v = %v\n", name, value)
|
|
} else {
|
|
fmt.Fprintf(w, "[variable] %v = %v\n", name, value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleLog(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
label := chunk.Message["label"]
|
|
status := chunk.Message["status"]
|
|
id := chunk.Message["id"]
|
|
fmt.Fprintf(w, "[log] id=%v label=%v status=%v\n", id, label, status)
|
|
|
|
if data, ok := chunk.Message["data"]; ok && data != nil {
|
|
if dataJSON, err := json.MarshalIndent(data, " ", " "); err == nil {
|
|
fmt.Fprintf(w, " data: %s\n", string(dataJSON))
|
|
}
|
|
}
|
|
if errMsg, ok := chunk.Message["error"]; ok && errMsg != nil {
|
|
fmt.Fprintf(w, " error: %v\n", errMsg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleRetrieverResources(chunk *types.DifyToolResponseChunk, w io.Writer) error {
|
|
context := chunk.Message["context"]
|
|
resources := chunk.Message["retriever_resources"]
|
|
fmt.Fprintf(w, "[retriever_resources]\n")
|
|
fmt.Fprintf(w, " context: %v\n", context)
|
|
|
|
if resources != nil {
|
|
if resourcesJSON, err := json.MarshalIndent(resources, " ", " "); err == nil {
|
|
fmt.Fprintf(w, " resources: %s\n", string(resourcesJSON))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Download helpers
|
|
|
|
var filesBaseURL string
|
|
|
|
func SetFilesURL(url string) { filesBaseURL = strings.TrimSuffix(url, "/") }
|
|
|
|
func isToolFileURL(url string) bool {
|
|
return strings.Contains(url, "/files/tools/")
|
|
}
|
|
|
|
func downloadAndPrint(fileURL string, fileType string, w io.Writer) error {
|
|
url := fileURL
|
|
if !strings.HasPrefix(fileURL, "http://") && !strings.HasPrefix(fileURL, "https://") {
|
|
if filesBaseURL == "" {
|
|
fmt.Fprintf(w, "[%s] %s (download skipped: files_url not configured)\n", fileType, fileURL)
|
|
return nil
|
|
}
|
|
url = filesBaseURL + fileURL
|
|
}
|
|
|
|
client := &http.Client{Timeout: 5 * time.Minute}
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
fmt.Fprintf(w, "[%s] %s (download failed: %v)\n", fileType, fileURL, err)
|
|
return nil
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
fmt.Fprintf(w, "[%s] %s (download failed: status %d)\n", fileType, fileURL, resp.StatusCode)
|
|
return nil
|
|
}
|
|
|
|
urlPath := fileURL
|
|
if idx := strings.Index(urlPath, "?"); idx != -1 {
|
|
urlPath = urlPath[:idx]
|
|
}
|
|
filename := filepath.Base(urlPath)
|
|
|
|
if err := os.MkdirAll("downloads", 0755); err != nil {
|
|
fmt.Fprintf(w, "[%s] %s (failed to create dir: %v)\n", fileType, fileURL, err)
|
|
return nil
|
|
}
|
|
|
|
localPath := filepath.Join("downloads", filename)
|
|
file, err := os.Create(localPath)
|
|
if err != nil {
|
|
fmt.Fprintf(w, "[%s] %s (failed to create file: %v)\n", fileType, fileURL, err)
|
|
return nil
|
|
}
|
|
defer file.Close()
|
|
|
|
if _, err = io.Copy(file, resp.Body); err != nil {
|
|
fmt.Fprintf(w, "[%s] %s (failed to write: %v)\n", fileType, fileURL, err)
|
|
return nil
|
|
}
|
|
|
|
absPath, _ := filepath.Abs(localPath)
|
|
fmt.Fprintf(w, "[%s] downloaded to: %s\n", fileType, absPath)
|
|
return nil
|
|
}
|