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 } } }