返回 DeepSeek-Reasonix
profile.go
根目录 / internal / plugin / profile.go
1 package plugin
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "fmt"
8 )
9
10 // HostProfile is the semantic identity of a frontend's MCP client surface:
11 // which optional client capabilities every server connection declares. It is
12 // fixed when the Host is created and never changes for the Host's lifetime —
13 // capabilities are negotiated per connection at initialize time, so a live
14 // downgrade would require tearing every session down. Cache identity and the
15 // capability matrix both derive from the profile, never from SDK versions,
16 // wall-clock time, or negotiated results.
17 type HostProfile string
18
19 const (
20 // HostProfileCore is the headless surface: bots and print-mode CLI. It
21 // declares no optional interaction capabilities, matching the legacy
22 // client byte-for-byte, so it keeps the v2 cache contract.
23 HostProfileCore HostProfile = "core-v1"
24 // HostProfileInteractive adds form and URL elicitation for frontends with
25 // a human on the other end: the chat TUI and serve.
26 HostProfileInteractive HostProfile = "interactive-v1"
27 // HostProfileDesktopApps is the Desktop surface: elicitation plus the
28 // stable MCP Apps 2026-01-26 ui extension.
29 HostProfileDesktopApps HostProfile = "desktop-apps-2026-01-26-v1"
30 )
31
32 // AppsUIExtensionID is the client extension identifier for MCP Apps
33 // (ext-apps 2026-01-26). Declaring it tells servers this host can render
34 // text/html;profile=mcp-app resources inline.
35 const AppsUIExtensionID = "io.modelcontextprotocol/ui"
36
37 // AppsMimeType is the single MIME type the Desktop host accepts for app
38 // resources, matching the stable Apps specification.
39 const AppsMimeType = "text/html;profile=mcp-app"
40
41 // ProfileCapabilities is the set of optional client capabilities a profile
42 // declares. The JSON encoding is the profile's cache identity: two profiles
43 // that declare identical capabilities must share one cache identity, and any
44 // capability change must change the identity so stale tool catalogs written
45 // under the old negotiation can never be read under the new one.
46 type ProfileCapabilities struct {
47 ElicitationForms bool `json:"elicitationForms,omitempty"`
48 ElicitationURL bool `json:"elicitationURL,omitempty"`
49 AppsUI bool `json:"appsUI,omitempty"`
50 }
51
52 // HostProfileForInteractive maps a human-in-the-loop flag onto the profile:
53 // interactive-v1 when a human can answer prompts, core-v1 otherwise.
54 func HostProfileForInteractive(interactive bool) HostProfile {
55 if interactive {
56 return HostProfileInteractive
57 }
58 return HostProfileCore
59 }
60
61 // String returns the wire form of the profile identifier.
62 func (p HostProfile) String() string { return string(p.Normalize()) }
63
64 // Capabilities returns what the profile declares to every server.
65 func (p HostProfile) Capabilities() ProfileCapabilities {
66 switch p {
67 case HostProfileInteractive:
68 return ProfileCapabilities{ElicitationForms: true, ElicitationURL: true}
69 case HostProfileDesktopApps:
70 return ProfileCapabilities{ElicitationForms: true, ElicitationURL: true, AppsUI: true}
71 default:
72 return ProfileCapabilities{}
73 }
74 }
75
76 // Normalize maps an unknown or empty profile onto core-v1 so a config typo can
77 // never silently widen the declared surface.
78 func (p HostProfile) Normalize() HostProfile {
79 switch p {
80 case HostProfileInteractive, HostProfileDesktopApps:
81 return p
82 default:
83 return HostProfileCore
84 }
85 }
86
87 // UsesEnhancedCache reports whether the profile declares anything beyond the
88 // legacy client surface. Such profiles cannot reuse the v2 cache: a server may
89 // return a different tools/list once it sees elicitation or ui extensions, so
90 // they read and write an isolated v3 cache instead.
91 func (p HostProfile) UsesEnhancedCache() bool {
92 return p.Normalize() != HostProfileCore
93 }
94
95 // ProfileCacheHash is a stable, short digest of the declared capabilities.
96 // It appears in the enhanced cache filename (<slug>.host-<hash>.json) so
97 // caches written under different profiles never collide.
98 func (p HostProfile) ProfileCacheHash() string {
99 caps := p.Capabilities()
100 b, err := json.Marshal(caps)
101 if err != nil {
102 // ProfileCapabilities is three booleans; marshal cannot fail.
103 panic(fmt.Sprintf("plugin: marshal profile capabilities: %v", err))
104 }
105 sum := sha256.Sum256(b)
106 return hex.EncodeToString(sum[:6])
107 }
108
109 // HostProfileOrder is the display order of the capability matrix layers.
110 var HostProfileOrder = []HostProfile{HostProfileCore, HostProfileInteractive, HostProfileDesktopApps}
111
112 // ProfileDisplayNames maps profiles to human labels for status surfaces.
113 func (p HostProfile) DisplayName() string {
114 switch p {
115 case HostProfileInteractive:
116 return "Interactive Host"
117 case HostProfileDesktopApps:
118 return "Apps Host"
119 default:
120 return "Core Host"
121 }
122 }
123
123 lines GO