返回 DeepSeek-Reasonix
dto_provider.go
根目录 / internal / extension / protocol / dto_provider.go
1 package protocol
2
3 import (
4 "bytes"
5 "encoding/json"
6 "strings"
7 )
8
9 // Provider DTOs: the extension-hosted provider broker. The extension holds
10 // provider credentials and runs streams; the host only ever sees these
11 // credential-free public copies. Conversion to and from internal/provider
12 // types lives host-side in a later stage; these DTOs deliberately do not
13 // import internal/provider so the public wire schema stays self-contained.
14
15 // ProviderDescriptor mirrors provider.Descriptor field-for-field as a public
16 // DTO. It never carries endpoints, credentials, headers, or env names.
17 type ProviderDescriptor struct {
18 Ref string `json:"ref" validate:"nonempty"`
19 DisplayName string `json:"displayName,omitempty"`
20 Model string `json:"model,omitempty"`
21 ContextWindow int `json:"contextWindow,omitempty" validate:"min=0"`
22 PricingCurrency string `json:"pricingCurrency,omitempty"`
23 CacheHitPerMillion float64 `json:"cacheHitPerMillion,omitempty" validate:"min=0"`
24 InputPerMillion float64 `json:"inputPerMillion,omitempty" validate:"min=0"`
25 OutputPerMillion float64 `json:"outputPerMillion,omitempty" validate:"min=0"`
26 Vision bool `json:"vision,omitempty"`
27 Tools bool `json:"tools,omitempty"`
28 Reasoning bool `json:"reasoning,omitempty"`
29 Efforts []string `json:"efforts,omitempty"`
30 DefaultEffort string `json:"defaultEffort,omitempty"`
31 ToolCallReasoning bool `json:"toolCallReasoning,omitempty"`
32 ReasoningRoundTrip bool `json:"reasoningRoundTrip,omitempty"`
33 WarnOnMissingToolCallReasoning bool `json:"warnOnMissingToolCallReasoning,omitempty"`
34 }
35
36 // PluginRefOwner extracts the plugin ID from a plugin-namespaced provider ref
37 // (plugin/<pluginID>/<rest...>) — the namespace every extension-hosted
38 // provider ref carries. Anything else — including the two-segment
39 // "plugin/<model>" shape, which stays an ordinary host ref — returns "".
40 // Host layers (boot, config validation, frontends) use it to route plugin
41 // refs away from config-backed catalogs they can never appear in.
42 func PluginRefOwner(ref string) string {
43 rest, ok := strings.CutPrefix(ref, "plugin/")
44 if !ok {
45 return ""
46 }
47 pluginID, remainder, ok := strings.Cut(rest, "/")
48 if !ok || pluginID == "" || remainder == "" {
49 return ""
50 }
51 return pluginID
52 }
53
54 // ProviderMessage is the public copy of provider.Message. It keeps the same
55 // JSON field names (snake_case) so transcripts read identically, and drops
56 // the local-only UI metadata fields that never belong on the wire.
57 type ProviderMessage struct {
58 Role ProviderRole `json:"role,omitempty"`
59 Content string `json:"content,omitempty" externalizable:"true"`
60 Images []string `json:"images,omitempty"`
61 ReasoningContent string `json:"reasoning_content,omitempty"`
62 ReasoningSignature string `json:"reasoning_signature,omitempty"`
63 ToolCalls []ProviderToolCall `json:"tool_calls,omitempty"`
64 ToolCallID string `json:"tool_call_id,omitempty"`
65 Name string `json:"name,omitempty"`
66 }
67
68 // ProviderToolCall is the public copy of provider.ToolCall: provider-visible
69 // fields only, no Reasonix-local display metadata.
70 type ProviderToolCall struct {
71 ID string `json:"id" validate:"nonempty"`
72 Name string `json:"name" validate:"nonempty"`
73 Arguments string `json:"arguments"`
74 ThoughtSignature string `json:"thought_signature,omitempty"`
75 }
76
77 // ProviderToolSchema is the public copy of provider.ToolSchema. Parameters is
78 // a JSON Schema object.
79 type ProviderToolSchema struct {
80 Name string `json:"name" validate:"nonempty"`
81 Description string `json:"description,omitempty"`
82 Parameters json.RawMessage `json:"parameters"`
83 }
84
85 // ProviderResponseFormat asks an extension-hosted provider to constrain its
86 // output shape. It is optional so ordinary requests retain their existing,
87 // cache-stable wire representation.
88 type ProviderResponseFormat struct {
89 Type string `json:"type" validate:"nonempty"`
90 }
91
92 // ProviderRequest is the credential-free completion request the host asks the
93 // extension to stream. Nil Messages/Tools arrays are invalid; empty arrays
94 // are the canonical form.
95 type ProviderRequest struct {
96 Messages []ProviderMessage `json:"messages"`
97 Tools []ProviderToolSchema `json:"tools"`
98 Temperature *float64 `json:"temperature,omitempty"`
99 MaxTokens int `json:"maxTokens" validate:"min=0"`
100 ResponseFormat *ProviderResponseFormat `json:"responseFormat,omitempty"`
101 }
102
103 // Validate enforces the deterministic wire shape.
104 func (request ProviderRequest) Validate() error {
105 if request.Messages == nil || request.Tools == nil {
106 return validationError("messages and tools must be arrays")
107 }
108 if request.MaxTokens < 0 {
109 return validationError("maxTokens must be non-negative")
110 }
111 if request.ResponseFormat != nil && strings.TrimSpace(request.ResponseFormat.Type) == "" {
112 return validationError("responseFormat.type must be non-empty")
113 }
114 for _, tool := range request.Tools {
115 parameters := bytes.TrimSpace(tool.Parameters)
116 if len(parameters) == 0 || parameters[0] != '{' || !json.Valid(parameters) {
117 return validationError("tool parameters must be a JSON object")
118 }
119 }
120 return nil
121 }
122
123 // ProviderUsage is the public copy of provider.Usage token accounting.
124 type ProviderUsage struct {
125 PromptTokens int `json:"promptTokens" validate:"min=0"`
126 CompletionTokens int `json:"completionTokens" validate:"min=0"`
127 TotalTokens int `json:"totalTokens" validate:"min=0"`
128 CacheHitTokens int `json:"cacheHitTokens" validate:"min=0"`
129 CacheMissTokens int `json:"cacheMissTokens" validate:"min=0"`
130 ReasoningTokens int `json:"reasoningTokens" validate:"min=0"`
131 FinishReason string `json:"finishReason,omitempty"`
132 }
133
134 // ProviderError is deliberately generic, like the Remote broker's: raw
135 // provider errors can contain API keys, authorization headers, endpoints, or
136 // response bodies and must never cross the extension boundary.
137 type ProviderError struct {
138 Code ProviderErrorCode `json:"code"`
139 Message string `json:"message" validate:"nonempty"`
140 }
141
142 // ProviderChunk is one chunk of an extension-hosted provider stream.
143 type ProviderChunk struct {
144 Type ProviderChunkType `json:"type"`
145 Text string `json:"text,omitempty"`
146 Signature string `json:"signature,omitempty"`
147 ToolCall *ProviderToolCall `json:"toolCall,omitempty"`
148 ArgChars int `json:"argChars,omitempty" validate:"min=0"`
149 Usage *ProviderUsage `json:"usage,omitempty"`
150 Error *ProviderError `json:"error,omitempty"`
151 }
152
153 // Validate enforces chunk invariants the tags cannot express.
154 func (chunk ProviderChunk) Validate() error {
155 if chunk.ArgChars < 0 {
156 return validationError("argChars must be non-negative")
157 }
158 if chunk.Type == ChunkError && chunk.Error == nil {
159 return validationError("error chunks require error")
160 }
161 if chunk.Type != ChunkError && chunk.Error != nil {
162 return validationError("non-error chunks forbid error")
163 }
164 if chunk.Type == ChunkUsage && chunk.Usage == nil {
165 return validationError("usage chunks require usage")
166 }
167 return nil
168 }
169
170 // ProviderCatalogParams asks for the extension's full provider catalog.
171 type ProviderCatalogParams struct{}
172
173 // ProviderCatalogResult is the extension's non-secret provider catalog.
174 type ProviderCatalogResult struct {
175 Providers []ProviderDescriptor `json:"providers"`
176 }
177
178 // StreamOpenParams opens one provider stream for a host turn. Chunks flow
179 // back as extension/provider/stream/chunk notifications numbered from
180 // SeqBase; the stream ends with exactly one stream/end notification.
181 type StreamOpenParams struct {
182 StreamID string `json:"streamId" validate:"nonempty"`
183 ProviderRef string `json:"providerRef" validate:"nonempty"`
184 Model string `json:"model,omitempty"`
185 Effort string `json:"effort,omitempty"`
186 Request ProviderRequest `json:"request"`
187 SeqBase int `json:"seqBase" validate:"min=0"`
188 }
189
190 // Validate enforces required identifiers plus the request invariants.
191 func (p StreamOpenParams) Validate() error {
192 if strings.TrimSpace(p.StreamID) == "" || strings.TrimSpace(p.ProviderRef) == "" {
193 return validationError("streamId and providerRef are required")
194 }
195 return p.Request.Validate()
196 }
197
198 // StreamOpenResult acknowledges the stream; chunks arrive as notifications.
199 type StreamOpenResult struct {
200 Accepted bool `json:"accepted"`
201 }
202
203 // StreamCancelParams cancels one in-flight provider stream.
204 type StreamCancelParams struct {
205 StreamID string `json:"streamId" validate:"nonempty"`
206 }
207
208 // StreamCancelResult acknowledges the cancel.
209 type StreamCancelResult struct {
210 Cancelled bool `json:"cancelled"`
211 }
212
213 // StreamChunkParams is one provider chunk, Extension → Host.
214 type StreamChunkParams struct {
215 StreamID string `json:"streamId" validate:"nonempty"`
216 Seq int64 `json:"seq" validate:"min=1"`
217 Chunk ProviderChunk `json:"chunk"`
218 }
219
220 // Validate enforces stream ordering preconditions and chunk invariants.
221 func (p StreamChunkParams) Validate() error {
222 if strings.TrimSpace(p.StreamID) == "" {
223 return validationError("streamId is required")
224 }
225 if p.Seq < 1 {
226 return validationError("seq must be >= 1")
227 }
228 return p.Chunk.Validate()
229 }
230
231 // StreamEndParams ends a stream, success or failure. LastSeq freezes the
232 // terminal ordering boundary: the receiver must hold chunks 1..LastSeq before
233 // completing the stream, and a missing chunk is a stream_gap error.
234 type StreamEndParams struct {
235 StreamID string `json:"streamId" validate:"nonempty"`
236 LastSeq int64 `json:"lastSeq" validate:"min=0"`
237 // Error is a redacted, non-secret failure message when the stream failed.
238 Error string `json:"error,omitempty"`
239 // Interrupted is true when the stream was cut mid-flight (transport drop
240 // or cancel), not when it finished or failed cleanly.
241 Interrupted bool `json:"interrupted,omitempty"`
242 }
243
243 lines GO