mirror of
https://github.com/langgenius/dify.git
synced 2026-07-20 09:38:32 +08:00
182 lines
4.6 KiB
Go
182 lines
4.6 KiB
Go
package agentcli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"mime"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// FileUploadResponse is the JSON output for `dify-agent file upload`.
|
|
type FileUploadResponse struct {
|
|
TransferMethod string `json:"transfer_method"`
|
|
Reference string `json:"reference"`
|
|
DownloadURL string `json:"download_url"`
|
|
}
|
|
|
|
// FileDownloadResponse is the response from a file download request.
|
|
type FileDownloadResponse struct {
|
|
Filename string `json:"filename"`
|
|
MimeType string `json:"mime_type,omitempty"`
|
|
Size int64 `json:"size"`
|
|
DownloadURL string `json:"download_url"`
|
|
}
|
|
|
|
// RunFileUpload executes the `file upload` command.
|
|
func RunFileUpload(env *Environment, path string) error {
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve path: %w", err)
|
|
}
|
|
info, err := os.Stat(absPath)
|
|
if err != nil || info.IsDir() {
|
|
return fmt.Errorf("local file not found: %s", absPath)
|
|
}
|
|
|
|
filename := filepath.Base(absPath)
|
|
mimetype := guessMIMEType(filename)
|
|
ctx := context.Background()
|
|
|
|
client, err := NewStubClient(env)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = client.Close() }()
|
|
|
|
// Step 1: Request a signed upload URL
|
|
uploadURL, err := client.CreateFileUploadURL(ctx, filename, mimetype)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 2: Upload the file to the signed URL (data-plane)
|
|
uploadBody, err := client.UploadFileToURL(uploadURL, absPath, filename, mimetype)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var uploadResult map[string]any
|
|
if err := json.Unmarshal(uploadBody, &uploadResult); err != nil {
|
|
return fmt.Errorf("parse upload result: %w", err)
|
|
}
|
|
|
|
reference, _ := uploadResult["reference"].(string)
|
|
if reference == "" {
|
|
return fmt.Errorf("signed file upload response is missing reference")
|
|
}
|
|
|
|
// Step 3: Request download URL for the uploaded file
|
|
ref := reference
|
|
dlResp, err := client.CreateFileDownloadURL(ctx, "tool_file", &ref, nil, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result := FileUploadResponse{
|
|
TransferMethod: "tool_file",
|
|
Reference: reference,
|
|
DownloadURL: dlResp.DownloadURL,
|
|
}
|
|
out, _ := json.Marshal(result)
|
|
fmt.Println(string(out))
|
|
return nil
|
|
}
|
|
|
|
// RunFileDownload executes the `file download` command.
|
|
func RunFileDownload(env *Environment, transferMethod string, referenceOrURL string, localDir string) error {
|
|
var reference *string
|
|
var url *string
|
|
if transferMethod == "remote_url" {
|
|
url = &referenceOrURL
|
|
} else {
|
|
reference = &referenceOrURL
|
|
}
|
|
|
|
ctx := context.Background()
|
|
client, err := NewStubClient(env)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = client.Close() }()
|
|
|
|
dlResp, err := client.CreateFileDownloadURL(ctx, transferMethod, reference, url, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if dlResp.DownloadURL == "" {
|
|
return fmt.Errorf("signed file download response is missing download_url")
|
|
}
|
|
if dlResp.Filename == "" {
|
|
return fmt.Errorf("signed file download response is missing filename")
|
|
}
|
|
|
|
// Download the file (data-plane)
|
|
data, err := client.DownloadFromURL(dlResp.DownloadURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Determine target directory
|
|
targetDir := localDir
|
|
if targetDir == "" {
|
|
targetDir, _ = os.Getwd()
|
|
}
|
|
if err := os.MkdirAll(targetDir, 0o755); err != nil {
|
|
return fmt.Errorf("create target directory: %w", err)
|
|
}
|
|
|
|
// Write file
|
|
sanitizedName := sanitizeFilename(dlResp.Filename)
|
|
destPath := deduplicatePath(filepath.Join(targetDir, sanitizedName))
|
|
if err := os.WriteFile(destPath, data, 0o644); err != nil {
|
|
return fmt.Errorf("write file: %w", err)
|
|
}
|
|
|
|
fmt.Println(destPath)
|
|
return nil
|
|
}
|
|
|
|
func guessMIMEType(filename string) string {
|
|
ext := filepath.Ext(filename)
|
|
if ext == "" {
|
|
return "application/octet-stream"
|
|
}
|
|
mimeType := mime.TypeByExtension(ext)
|
|
if mimeType == "" {
|
|
return "application/octet-stream"
|
|
}
|
|
// Strip parameters (e.g. "; charset=utf-8") so the MIME type matches
|
|
// what Flask/Werkzeug returns via file.mimetype during signature
|
|
// verification on the Dify API upload endpoint.
|
|
if idx := strings.Index(mimeType, ";"); idx >= 0 {
|
|
mimeType = strings.TrimSpace(mimeType[:idx])
|
|
}
|
|
return mimeType
|
|
}
|
|
|
|
func sanitizeFilename(filename string) string {
|
|
name := filepath.Base(filename)
|
|
if name == "" || name == "." || name == ".." {
|
|
return "downloaded"
|
|
}
|
|
return name
|
|
}
|
|
|
|
func deduplicatePath(path string) string {
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
return path
|
|
}
|
|
ext := filepath.Ext(path)
|
|
stem := strings.TrimSuffix(filepath.Base(path), ext)
|
|
dir := filepath.Dir(path)
|
|
for counter := 1; ; counter++ {
|
|
candidate := filepath.Join(dir, fmt.Sprintf("%s (%d)%s", stem, counter, ext))
|
|
if _, err := os.Stat(candidate); os.IsNotExist(err) {
|
|
return candidate
|
|
}
|
|
}
|
|
}
|