mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-25 18:36:59 +08:00
### What problem does this PR solve? issue #13714 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
178 lines
5.4 KiB
Go
178 lines
5.4 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package source
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// SkillSource is the interface for skill sources
|
|
type SkillSource interface {
|
|
// SourceID returns the source identifier (local, github, clawhub, skillssh)
|
|
SourceID() string
|
|
|
|
// Fetch downloads and returns the skill bundle
|
|
Fetch(identifier string) (*SkillBundle, error)
|
|
|
|
// Inspect retrieves metadata without downloading full content
|
|
Inspect(identifier string) (*SkillMetadata, error)
|
|
|
|
// TrustLevel returns the trust level for this source (builtin/trusted/community)
|
|
TrustLevel(identifier string) string
|
|
}
|
|
|
|
// SourceResolver resolves source references to appropriate adapters
|
|
type SourceResolver struct {
|
|
sources map[string]SkillSource
|
|
}
|
|
|
|
// NewSourceResolver creates a new source resolver
|
|
func NewSourceResolver(client HTTPClientInterface) *SourceResolver {
|
|
return &SourceResolver{
|
|
sources: map[string]SkillSource{
|
|
"local": NewLocalSource(),
|
|
"github": NewGitHubSource(client),
|
|
"clawhub": NewClawHubSource(client),
|
|
"skillssh": NewSkillsShSource(client),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Resolve parses a source reference and returns the appropriate source adapter
|
|
// Supported formats:
|
|
// - ./path, /absolute/path -> local
|
|
// - github.com/owner/repo/path -> github
|
|
// - clawhub://owner/skill-name, clawhub.ai/owner/skill-name -> clawhub
|
|
// - skill://skill-name, skills.sh/skill/name -> skillssh
|
|
func (r *SourceResolver) Resolve(ref string) (SkillSource, string, error) {
|
|
ref = strings.TrimSpace(ref)
|
|
if ref == "" {
|
|
return nil, "", fmt.Errorf("empty source reference")
|
|
}
|
|
|
|
// Check for URI schemes
|
|
if strings.HasPrefix(ref, "clawhub://") {
|
|
identifier := strings.TrimPrefix(ref, "clawhub://")
|
|
return r.sources["clawhub"], identifier, nil
|
|
}
|
|
if strings.HasPrefix(ref, "skill://") {
|
|
identifier := strings.TrimPrefix(ref, "skill://")
|
|
return r.sources["skillssh"], identifier, nil
|
|
}
|
|
|
|
// Check for local path (starts with ./ or / or ~)
|
|
if strings.HasPrefix(ref, "./") || strings.HasPrefix(ref, "/") || strings.HasPrefix(ref, "~/") {
|
|
// Expand ~ to home directory
|
|
if strings.HasPrefix(ref, "~/") {
|
|
home, err := getHomeDir()
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("cannot resolve home directory: %w", err)
|
|
}
|
|
ref = filepath.Join(home, ref[2:])
|
|
}
|
|
return r.sources["local"], ref, nil
|
|
}
|
|
|
|
// Check for github.com domain
|
|
if strings.HasPrefix(ref, "github.com/") || strings.HasPrefix(ref, "https://github.com/") {
|
|
identifier := strings.TrimPrefix(ref, "https://")
|
|
return r.sources["github"], identifier, nil
|
|
}
|
|
|
|
// Check for clawhub.ai domain
|
|
if strings.HasPrefix(ref, "clawhub.ai/") || strings.HasPrefix(ref, "https://clawhub.ai/") {
|
|
identifier := strings.TrimPrefix(ref, "https://")
|
|
identifier = strings.TrimPrefix(identifier, "clawhub.ai/")
|
|
return r.sources["clawhub"], identifier, nil
|
|
}
|
|
|
|
// Check for skills.sh domain
|
|
if strings.HasPrefix(ref, "skills.sh/") || strings.HasPrefix(ref, "https://skills.sh/") {
|
|
identifier := strings.TrimPrefix(ref, "https://")
|
|
identifier = strings.TrimPrefix(identifier, "skills.sh/")
|
|
return r.sources["skillssh"], identifier, nil
|
|
}
|
|
|
|
// Default: treat as local path if it exists, otherwise error
|
|
return r.sources["local"], ref, nil
|
|
}
|
|
|
|
// getHomeDir returns the user's home directory
|
|
func getHomeDir() (string, error) {
|
|
home := os.Getenv("HOME")
|
|
if home == "" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
if home == "" {
|
|
return "", fmt.Errorf("cannot determine home directory")
|
|
}
|
|
return home, nil
|
|
}
|
|
|
|
// parseGitHubURL parses a GitHub URL and returns owner, repo, and path
|
|
func parseGitHubURL(urlStr string) (owner, repo, path string, err error) {
|
|
// Remove protocol prefix if present
|
|
urlStr = strings.TrimPrefix(urlStr, "https://")
|
|
urlStr = strings.TrimPrefix(urlStr, "http://")
|
|
|
|
// Remove github.com/ prefix
|
|
urlStr = strings.TrimPrefix(urlStr, "github.com/")
|
|
|
|
parts := strings.Split(urlStr, "/")
|
|
if len(parts) < 2 {
|
|
return "", "", "", fmt.Errorf("invalid GitHub URL format")
|
|
}
|
|
|
|
owner = parts[0]
|
|
repo = parts[1]
|
|
if len(parts) > 2 {
|
|
path = strings.Join(parts[2:], "/")
|
|
}
|
|
|
|
return owner, repo, path, nil
|
|
}
|
|
|
|
// extractSkillNameFromPath extracts the skill name from a path
|
|
func extractSkillNameFromPath(path string) string {
|
|
base := filepath.Base(path)
|
|
// Remove common suffixes
|
|
base = strings.TrimSuffix(base, ".git")
|
|
return base
|
|
}
|
|
|
|
// isTrustedGitHubRepo checks if a GitHub repo is trusted
|
|
func isTrustedGitHubRepo(owner, repo string) bool {
|
|
fullName := owner + "/" + repo
|
|
trusted := map[string]bool{
|
|
"openai/skills": true,
|
|
"anthropics/skills": true,
|
|
"microsoft/skills": true,
|
|
"google/skills": true,
|
|
}
|
|
return trusted[fullName]
|
|
}
|
|
|
|
// Helper to check if URL is valid
|
|
func isValidURL(str string) bool {
|
|
u, err := url.Parse(str)
|
|
return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
|
|
}
|