返回 DeepSeek-Reasonix
paths.go
根目录 / internal / capdiag / paths.go
1 package capdiag
2
3 import (
4 "net/url"
5 "os"
6 "path/filepath"
7 "strings"
8 )
9
10 // displayPath rewrites absolute paths for safe reports:
11 // - under workspace → <workspace>/...
12 // - under Reasonix home → <reasonix-home>/...
13 // - under OS home → ~/...
14 // - elsewhere → <external>/basename (no full external path, no username)
15 func displayPath(p, workspace, home, reasonixHome string) string {
16 p = strings.TrimSpace(p)
17 if p == "" {
18 return ""
19 }
20 // Builtin markers stay as-is.
21 if strings.HasPrefix(p, "(builtin") {
22 return p
23 }
24 clean := filepath.Clean(p)
25 if abs, err := filepath.Abs(clean); err == nil {
26 clean = abs
27 }
28 ws := workspace
29 if ws != "" {
30 if abs, err := filepath.Abs(ws); err == nil {
31 ws = abs
32 }
33 if clean == ws {
34 return "<workspace>"
35 }
36 if rel, err := filepath.Rel(ws, clean); err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
37 return "<workspace>/" + filepath.ToSlash(rel)
38 }
39 }
40 if rh := strings.TrimSpace(reasonixHome); rh != "" {
41 if abs, err := filepath.Abs(rh); err == nil {
42 rh = abs
43 }
44 rh = filepath.Clean(rh)
45 if clean == rh {
46 return "<reasonix-home>"
47 }
48 if rel, err := filepath.Rel(rh, clean); err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
49 return "<reasonix-home>/" + filepath.ToSlash(rel)
50 }
51 }
52 if home == "" {
53 if h, err := os.UserHomeDir(); err == nil {
54 home = h
55 }
56 }
57 if home != "" {
58 home = filepath.Clean(home)
59 if clean == home {
60 return "~"
61 }
62 if rel, err := filepath.Rel(home, clean); err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
63 return "~/" + filepath.ToSlash(rel)
64 }
65 }
66 // External: only show a generic marker + base name so reports stay shareable.
67 base := filepath.Base(clean)
68 if base == "" || base == "." || base == string(os.PathSeparator) {
69 return "<external>"
70 }
71 return "<external>/" + base
72 }
73
74 func sortedKeys(m map[string]string) []string {
75 if len(m) == 0 {
76 return nil
77 }
78 keys := make([]string, 0, len(m))
79 for k := range m {
80 if strings.TrimSpace(k) != "" {
81 keys = append(keys, k)
82 }
83 }
84 // Insertion sort for stability without importing sort in hot path of small maps.
85 for i := 1; i < len(keys); i++ {
86 j := i
87 for j > 0 && keys[j-1] > keys[j] {
88 keys[j-1], keys[j] = keys[j], keys[j-1]
89 j--
90 }
91 }
92 return keys
93 }
94
95 // urlHostOnly returns the host (and port) from a URL without path/query/userinfo.
96 func urlHostOnly(raw string) string {
97 raw = strings.TrimSpace(raw)
98 if raw == "" {
99 return ""
100 }
101 u, err := url.Parse(raw)
102 if err != nil || u.Host == "" {
103 return "<url>"
104 }
105 return u.Host
106 }
107
108 func transportOf(typ string) string {
109 t := strings.ToLower(strings.TrimSpace(typ))
110 switch t {
111 case "", "stdio":
112 return "stdio"
113 case "http", "streamable-http", "streamable_http":
114 return "http"
115 case "sse":
116 return "sse"
117 default:
118 return t
119 }
120 }
121
122 func isValidTransport(t string) bool {
123 switch transportOf(t) {
124 case "stdio", "http", "sse":
125 return true
126 default:
127 return false
128 }
129 }
130
130 lines GO