返回 DeepSeek-Reasonix
dto_ui.go
根目录 / internal / extension / protocol / dto_ui.go
1 package protocol
2
3 import (
4 "encoding/json"
5 "fmt"
6 "reflect"
7 )
8
9 // UI DTOs: extension-contributed actions, host-rendered surfaces, and
10 // blocking prompts.
11 //
12 // The extension UI surface is structured-only: payloads are the typed
13 // documents below and nothing else. HTML, CSS, JavaScript, and URLs are
14 // never accepted or rendered — hosts map these structures onto their own
15 // native widgets (TUI, desktop, ACP client, or headless sink).
16
17 // UIActionDecl declares one invocable action in the handshake.
18 type UIActionDecl struct {
19 ActionID string `json:"actionId" validate:"nonempty"`
20 Label string `json:"label,omitempty"`
21 }
22
23 // UIActionParams invokes one declared action. Generation pins the session
24 // generation the action applies to; a stale generation is rejected host-side.
25 type UIActionParams struct {
26 ActionID string `json:"actionId" validate:"nonempty"`
27 SessionID string `json:"sessionId" validate:"nonempty"`
28 Generation uint64 `json:"generation"`
29 Args map[string]string `json:"args,omitempty"`
30 }
31
32 // UIActionResult reports whether the extension accepted the invocation.
33 type UIActionResult struct {
34 Accepted bool `json:"accepted"`
35 Message string `json:"message,omitempty"`
36 }
37
38 // UISubmitParams delivers the values of a previously published form surface
39 // back to the extension.
40 type UISubmitParams struct {
41 SurfaceID string `json:"surfaceId" validate:"nonempty"`
42 SessionID string `json:"sessionId" validate:"nonempty"`
43 Generation uint64 `json:"generation"`
44 Values map[string]any `json:"values"`
45 }
46
47 // UISubmitResult acknowledges the submission.
48 type UISubmitResult struct {
49 Accepted bool `json:"accepted"`
50 }
51
52 // UIPublishParams publishes or replaces one structured surface on the host.
53 // Payload must decode to the UISurfaceKind's payload type below.
54 type UIPublishParams struct {
55 SurfaceID string `json:"surfaceId" validate:"nonempty"`
56 SessionID string `json:"sessionId" validate:"nonempty"`
57 Generation uint64 `json:"generation"`
58 Kind UISurfaceKind `json:"kind"`
59 Payload json.RawMessage `json:"payload"`
60 }
61
62 // UIPublishResult acknowledges the publish.
63 type UIPublishResult struct {
64 Accepted bool `json:"accepted"`
65 }
66
67 // UIRequestParams asks the host to block on one structured prompt. Payload
68 // must decode to the UIRequestKind's payload shape (a UIFormPayload-shaped
69 // document for input/select/multiselect).
70 type UIRequestParams struct {
71 SurfaceID string `json:"surfaceId" validate:"nonempty"`
72 SessionID string `json:"sessionId" validate:"nonempty"`
73 Generation uint64 `json:"generation"`
74 Kind UIRequestKind `json:"kind"`
75 Payload json.RawMessage `json:"payload"`
76 }
77
78 // UIRequestResult carries the user's answer; Cancelled distinguishes
79 // dismissal from an empty value set.
80 type UIRequestResult struct {
81 Cancelled bool `json:"cancelled"`
82 Values map[string]any `json:"values,omitempty"`
83 }
84
85 // UIStatusPayload is a one-line status contribution.
86 type UIStatusPayload struct {
87 Label string `json:"label" validate:"nonempty"`
88 Detail string `json:"detail,omitempty"`
89 Severity UISeverity `json:"severity,omitempty"`
90 Progress *float64 `json:"progress,omitempty"`
91 }
92
93 // UIKeyValue is one labelled value row in a card.
94 type UIKeyValue struct {
95 Key string `json:"key" validate:"nonempty"`
96 Value string `json:"value"`
97 }
98
99 // UIActionRef renders a button that invokes a declared action.
100 type UIActionRef struct {
101 ActionID string `json:"actionId" validate:"nonempty"`
102 Label string `json:"label" validate:"nonempty"`
103 }
104
105 // UICardPayload is a rich read-only surface: Markdown body, key/value rows,
106 // optional progress, and action buttons.
107 type UICardPayload struct {
108 Title string `json:"title,omitempty"`
109 Markdown string `json:"markdown,omitempty"`
110 Text string `json:"text,omitempty"`
111 Fields []UIKeyValue `json:"fields,omitempty"`
112 Progress *float64 `json:"progress,omitempty"`
113 Actions []UIActionRef `json:"actions,omitempty"`
114 }
115
116 // UIFormField is one input row of a form surface.
117 type UIFormField struct {
118 Key string `json:"key" validate:"nonempty"`
119 Label string `json:"label,omitempty"`
120 Kind UIFieldKind `json:"kind"`
121 Options []string `json:"options,omitempty"`
122 Default any `json:"default,omitempty"`
123 Required bool `json:"required,omitempty"`
124 }
125
126 // UIFormPayload is an editable surface; submissions return through
127 // extension/ui/submit.
128 type UIFormPayload struct {
129 Title string `json:"title,omitempty"`
130 Message string `json:"message,omitempty"`
131 Fields []UIFormField `json:"fields"`
132 }
133
134 // UINotificationPayload is a transient toast-style message.
135 type UINotificationPayload struct {
136 Title string `json:"title" validate:"nonempty"`
137 Body string `json:"body,omitempty"`
138 Severity UISeverity `json:"severity,omitempty"`
139 }
140
141 // DecodeUIPublishPayload strict-decodes the payload document of one
142 // host/ui/publish call into the kind's payload struct (UIStatusPayload,
143 // UICardPayload, UIFormPayload, or UINotificationPayload). It applies the
144 // same strictness as the method DTO decoders: unknown fields, enum values,
145 // and required-field violations are rejected.
146 func DecodeUIPublishPayload(kind UISurfaceKind, raw json.RawMessage) (any, error) {
147 var typ reflect.Type
148 switch kind {
149 case UISurfaceStatus:
150 typ = reflect.TypeOf(UIStatusPayload{})
151 case UISurfaceCard:
152 typ = reflect.TypeOf(UICardPayload{})
153 case UISurfaceForm:
154 typ = reflect.TypeOf(UIFormPayload{})
155 case UISurfaceNotification:
156 typ = reflect.TypeOf(UINotificationPayload{})
157 default:
158 return nil, fmt.Errorf("protocol: unknown UI surface kind %q", kind)
159 }
160 return decodeAndValidate(raw, typ)
161 }
162
163 // DecodeUIRequestPayload strict-decodes the payload document of one
164 // host/ui/request call. Every request kind carries a UIFormPayload-shaped
165 // document: a confirm is a form with one confirm field (or a bare message),
166 // input/select/multiselect compose the matching field kinds.
167 func DecodeUIRequestPayload(kind UIRequestKind, raw json.RawMessage) (any, error) {
168 switch kind {
169 case UIRequestConfirm, UIRequestInput, UIRequestSelect, UIRequestMultiselect:
170 return decodeAndValidate(raw, reflect.TypeOf(UIFormPayload{}))
171 default:
172 return nil, fmt.Errorf("protocol: unknown UI request kind %q", kind)
173 }
174 }
175
175 lines GO