返回 DeepSeek-Reasonix
types.go
1 package installsource
2
3 import (
4 "encoding/json"
5
6 "reasonix/internal/config"
7 "reasonix/internal/pluginpkg"
8 )
9
10 // request mirrors the public schema. Fields stay exported because Execute
11 // unmarshals directly into this struct; the struct comment is the source of
12 // truth for the field semantics surfaced to the model.
13 type request struct {
14 Op string `json:"op"` // "install" (default) | "uninstall"
15 Source string `json:"source"` // required for install; ignored for uninstall
16 Kind string `json:"kind"` // auto|skill|mcp|plugin (install only)
17 Apply bool `json:"apply"` // install only: actually write/connect
18 Scope string `json:"scope"` // project|global
19 Mode string `json:"mode"` // auto|copy|link|register (skill install)
20 Name string `json:"name"` // override the discovered name
21 Transport string `json:"transport"` // auto|stdio|http|sse
22 Command string `json:"command"`
23 Args []string `json:"args"`
24 Env map[string]string `json:"env"`
25 Headers map[string]string `json:"headers"`
26 Tier string `json:"tier"`
27 Replace bool `json:"replace"` // overwrite existing entries
28 Strict *bool `json:"strict"` // nil -> true; require skill frontmatter
29 // PlanID is echoed back on a confirm-apply call so the host can refuse
30 // to apply a plan that does not match the one it approved.
31 PlanID string `json:"planId"`
32
33 scopeExplicit bool
34 }
35
36 // response is the JSON shape returned to the model. Status is one of
37 // "planned" (apply=false, no writes), "done" (all actions succeeded),
38 // "partial" (some actions succeeded), "failed" (no actions succeeded), or
39 // "blocked" (no plan could be produced). The `Kind` field is preserved for
40 // back-compat with callers that only handle a single capability; `Kinds`
41 // reports per-kind counts.
42 type response struct {
43 OK bool `json:"ok"`
44 Status string `json:"status"`
45 Op string `json:"op"`
46 Applied bool `json:"applied"`
47 Source string `json:"source"`
48 Name string `json:"name,omitempty"`
49 Kind string `json:"kind"` // legacy single kind, "" when no plan
50 Kinds kindTally `json:"kinds,omitempty"` // counts per kind
51 Scope string `json:"scope"`
52 Mode string `json:"mode"`
53 PlanID string `json:"planId,omitempty"`
54 Actions []action `json:"actions"`
55 Warnings []string `json:"warnings,omitempty"`
56 Error string `json:"error,omitempty"`
57 Next string `json:"next,omitempty"`
58 }
59
60 // kindTally reports per-kind counts. It is a struct (not a map) so the JSON
61 // shape stays stable: missing fields read as zero, and we can add new kinds
62 // without breaking old clients.
63 type kindTally struct {
64 Skill int `json:"skill"`
65 MCP int `json:"mcp"`
66 Plugin int `json:"plugin"`
67 }
68
69 // action is the per-install-step DTO. The Kind/Action pair drives the apply
70 // dispatcher; RiskLevel/RiskReasons help the calling skill decide whether to
71 // ask the user before apply=true.
72 type action struct {
73 Kind string `json:"kind"` // "skill" | "mcp" | "plugin"
74 Action string `json:"action"` // copy_skill|link_skill|register_skill_root|install_mcp_server|remove_skill|remove_skill_root|remove_mcp_server
75 Status string `json:"status"` // planned|done|failed
76 RiskLevel RiskLevel `json:"riskLevel"` // low|medium|high
77 RiskReasons []string `json:"riskReasons,omitempty"`
78 Name string `json:"name,omitempty"`
79 Source string `json:"source,omitempty"`
80 Target string `json:"target,omitempty"`
81 ConfigPath string `json:"configPath,omitempty"`
82 Scope string `json:"scope,omitempty"`
83 Mode string `json:"mode,omitempty"`
84 Transport string `json:"transport,omitempty"`
85 URL string `json:"url,omitempty"`
86 Command string `json:"command,omitempty"`
87 Args []string `json:"args,omitempty"`
88 Env map[string]string `json:"env,omitempty"`
89 Headers map[string]string `json:"headers,omitempty"`
90 Skills []string `json:"skills,omitempty"`
91 SkillCount int `json:"skillCount,omitempty"`
92 Agents []string `json:"agents,omitempty"`
93 AgentCount int `json:"agentCount,omitempty"`
94 Commands []string `json:"commands,omitempty"`
95 CommandCount int `json:"commandCount,omitempty"`
96 Commit string `json:"commit,omitempty"` // resolved git snapshot the plan describes; apply pins to it
97 Layout string `json:"layout,omitempty"` // canonical_dir|flat_compat|registered_root
98 InstallRoot string `json:"installRoot,omitempty"` // skills root or registered custom root
99 CanonicalPath string `json:"canonicalPath,omitempty"` // <skill-name>/SKILL.md when applicable
100 Discoverable bool `json:"discoverable,omitempty"` // Store.Read can load it after apply/register
101 Indexed bool `json:"indexed,omitempty"` // Store.List includes it for the skills index
102 ToolCount int `json:"toolCount,omitempty"`
103 Compatibility string `json:"compatibility,omitempty"`
104 MappedCapabilities []string `json:"mappedCapabilities,omitempty"`
105 SkippedCapabilities []pluginpkg.CompatibilityIssue `json:"skippedCapabilities,omitempty"`
106 HookCount int `json:"hookCount,omitempty"`
107 ManifestKind string `json:"manifestKind,omitempty"`
108 Version string `json:"version,omitempty"`
109 PromptCount int `json:"promptCount,omitempty"`
110 ThemeCount int `json:"themeCount,omitempty"`
111 // Runtime carries a plugin package's declared runtime process so
112 // frontends can render the full-trust implications before approval.
113 // Purely additive: older consumers ignore it.
114 Runtime *RuntimePlanInfo `json:"runtime,omitempty"`
115 Warnings []string `json:"warnings,omitempty"`
116 Error string `json:"error,omitempty"`
117 Next string `json:"next,omitempty"`
118
119 // Internal state used by apply. Stripped by publicActions before
120 // serializing to JSON.
121 entry config.PluginEntry
122 skill skillCandidate
123 disconnect func() // optional MCP rollback; nil when not connected
124 // preparedRoot lets a multi-plugin marketplace apply reuse the exact clone
125 // that produced its approved plan instead of cloning the same repository
126 // once per plugin. cleanup is attached to one action and runs after all
127 // actions finish.
128 preparedRoot string
129 cleanup func()
130 }
131
132 // RuntimePlanInfo describes a plugin package's declared runtime process in
133 // the install plan. A runtime executes inside Reasonix with the user's full
134 // trust — it can read the session and environment, bypass permissions, and
135 // operate the machine directly — so FullTrust is always true when present
136 // and the plan action also carries RiskHigh with a FULL TRUST reason.
137 type RuntimePlanInfo struct {
138 Command string `json:"command"`
139 Args []string `json:"args,omitempty"`
140 Intercepts []string `json:"intercepts,omitempty"`
141 Replaces []string `json:"replaces,omitempty"`
142 Capabilities []string `json:"capabilities,omitempty"`
143 FullTrust bool `json:"fullTrust"`
144 }
145
146 func actionPlanKey(a action) string {
147 return a.Kind + ":" + a.Name + ":" + a.Action + ":" + a.Source + ":" + a.Target + ":" + a.ConfigPath
148 }
149
150 // skillCandidate is a parsed skill file/directory ready to install. The
151 // caller decides whether to copy, link, or register the source path.
152 type skillCandidate struct {
153 Name string
154 Description string
155 SourcePath string
156 RootPath string
157 IsDir bool
158 Content string
159 }
160
161 // summarizeKind returns a one-word kind label for callers that only want the
162 // dominant capability. "mixed" is returned when the plan contains both
163 // skills and MCP servers; counts live in response.Kinds.
164 func summarizeKind(actions []action) string {
165 seen := map[string]bool{}
166 for _, a := range actions {
167 seen[a.Kind] = true
168 }
169 if len(seen) > 1 {
170 return "mixed"
171 }
172 for k := range seen {
173 return k
174 }
175 return ""
176 }
177
178 // publicActions strips unexported state from each action so the JSON
179 // response does not leak internal config types.
180 func publicActions(in []action) []action {
181 out := make([]action, len(in))
182 for i := range in {
183 out[i] = in[i]
184 out[i].entry = config.PluginEntry{}
185 out[i].skill = skillCandidate{}
186 out[i].preparedRoot = ""
187 out[i].cleanup = nil
188 }
189 return out
190 }
191
192 func marshalJSON(v any) string {
193 b, _ := json.Marshal(v)
194 return string(b)
195 }
196
196 lines GO