返回 DeepSeek-Reasonix
providerconv.go
根目录 / internal / extension / providerconv / providerconv.go
1 // Package providerconv holds the host-side conversions between
2 // internal/provider values and the public Extension Protocol v2 wire DTOs.
3 // The protocol package deliberately does not import internal/provider, so
4 // every host surface that moves provider data across the extension boundary —
5 // the agent's intercept wiring (stage 6b2) and the sidecar provider adapter
6 // (stage 7) — shares these helpers. Wire DTOs never carry credentials:
7 // ProviderError messages must be redacted by the producer before they cross,
8 // and the host defensively redacts them again at the trust boundary.
9 package providerconv
10
11 import (
12 "errors"
13
14 "reasonix/internal/extension/protocol"
15 "reasonix/internal/provider"
16 "reasonix/internal/secrets"
17 )
18
19 // MessagesToProtocol copies provider messages into their public wire form,
20 // dropping the local-only UI metadata fields that never belong on the wire.
21 func MessagesToProtocol(msgs []provider.Message) []protocol.ProviderMessage {
22 out := make([]protocol.ProviderMessage, 0, len(msgs))
23 for _, m := range msgs {
24 out = append(out, protocol.ProviderMessage{
25 Role: protocol.ProviderRole(m.Role),
26 Content: m.Content,
27 Images: m.Images,
28 ReasoningContent: m.ReasoningContent,
29 ReasoningSignature: m.ReasoningSignature,
30 ToolCalls: ToolCallsToProtocol(m.ToolCalls),
31 ToolCallID: m.ToolCallID,
32 Name: m.Name,
33 })
34 }
35 return out
36 }
37
38 // MessagesFromProtocol copies wire messages back into provider messages.
39 func MessagesFromProtocol(msgs []protocol.ProviderMessage) []provider.Message {
40 out := make([]provider.Message, 0, len(msgs))
41 for _, m := range msgs {
42 out = append(out, provider.Message{
43 Role: provider.Role(m.Role),
44 Content: m.Content,
45 Images: m.Images,
46 ReasoningContent: m.ReasoningContent,
47 ReasoningSignature: m.ReasoningSignature,
48 ToolCalls: ToolCallsFromProtocol(m.ToolCalls),
49 ToolCallID: m.ToolCallID,
50 Name: m.Name,
51 })
52 }
53 return out
54 }
55
56 // ToolCallsToProtocol copies tool calls into their provider-visible wire form.
57 func ToolCallsToProtocol(calls []provider.ToolCall) []protocol.ProviderToolCall {
58 if len(calls) == 0 {
59 return nil
60 }
61 out := make([]protocol.ProviderToolCall, 0, len(calls))
62 for _, c := range calls {
63 out = append(out, protocol.ProviderToolCall{
64 ID: c.ID,
65 Name: c.Name,
66 Arguments: c.Arguments,
67 ThoughtSignature: c.ThoughtSignature,
68 })
69 }
70 return out
71 }
72
73 // ToolCallsFromProtocol copies wire tool calls back into provider tool calls.
74 func ToolCallsFromProtocol(calls []protocol.ProviderToolCall) []provider.ToolCall {
75 if len(calls) == 0 {
76 return nil
77 }
78 out := make([]provider.ToolCall, 0, len(calls))
79 for _, c := range calls {
80 out = append(out, provider.ToolCall{
81 ID: c.ID,
82 Name: c.Name,
83 Arguments: c.Arguments,
84 ThoughtSignature: c.ThoughtSignature,
85 })
86 }
87 return out
88 }
89
90 // RequestToProtocol copies a completion request into the credential-free wire
91 // form the host sends to an extension-hosted provider.
92 func RequestToProtocol(req provider.Request) protocol.ProviderRequest {
93 tools := make([]protocol.ProviderToolSchema, 0, len(req.Tools))
94 for _, s := range req.Tools {
95 tools = append(tools, protocol.ProviderToolSchema{
96 Name: s.Name,
97 Description: s.Description,
98 Parameters: s.Parameters,
99 })
100 }
101 var responseFormat *protocol.ProviderResponseFormat
102 if req.ResponseFormat != nil {
103 responseFormat = &protocol.ProviderResponseFormat{Type: req.ResponseFormat.Type}
104 }
105 return protocol.ProviderRequest{
106 Messages: MessagesToProtocol(req.Messages),
107 Tools: tools,
108 Temperature: req.Temperature,
109 MaxTokens: req.MaxTokens,
110 ResponseFormat: responseFormat,
111 }
112 }
113
114 // RequestFromProtocol copies a wire request back into a provider request.
115 func RequestFromProtocol(req protocol.ProviderRequest) provider.Request {
116 tools := make([]provider.ToolSchema, 0, len(req.Tools))
117 for _, s := range req.Tools {
118 tools = append(tools, provider.ToolSchema{
119 Name: s.Name,
120 Description: s.Description,
121 Parameters: s.Parameters,
122 })
123 }
124 var responseFormat *provider.ResponseFormat
125 if req.ResponseFormat != nil {
126 responseFormat = &provider.ResponseFormat{Type: req.ResponseFormat.Type}
127 }
128 return provider.Request{
129 Messages: MessagesFromProtocol(req.Messages),
130 Tools: tools,
131 Temperature: req.Temperature,
132 MaxTokens: req.MaxTokens,
133 ResponseFormat: responseFormat,
134 }
135 }
136
137 // UsageToProtocol copies token accounting into its wire form.
138 func UsageToProtocol(u *provider.Usage) *protocol.ProviderUsage {
139 if u == nil {
140 return nil
141 }
142 return &protocol.ProviderUsage{
143 PromptTokens: u.PromptTokens,
144 CompletionTokens: u.CompletionTokens,
145 TotalTokens: u.TotalTokens,
146 CacheHitTokens: u.CacheHitTokens,
147 CacheMissTokens: u.CacheMissTokens,
148 ReasoningTokens: u.ReasoningTokens,
149 FinishReason: u.FinishReason,
150 }
151 }
152
153 // UsageFromProtocol copies wire token accounting back into a provider Usage.
154 func UsageFromProtocol(u *protocol.ProviderUsage) *provider.Usage {
155 if u == nil {
156 return nil
157 }
158 return &provider.Usage{
159 PromptTokens: u.PromptTokens,
160 CompletionTokens: u.CompletionTokens,
161 TotalTokens: u.TotalTokens,
162 CacheHitTokens: u.CacheHitTokens,
163 CacheMissTokens: u.CacheMissTokens,
164 ReasoningTokens: u.ReasoningTokens,
165 FinishReason: u.FinishReason,
166 }
167 }
168
169 // DescriptorFromProtocol copies an extension's provider catalog entry into the
170 // host's descriptor form. The DTO mirrors provider.Descriptor field-for-field.
171 func DescriptorFromProtocol(d protocol.ProviderDescriptor) provider.Descriptor {
172 return provider.Descriptor{
173 Ref: d.Ref,
174 DisplayName: d.DisplayName,
175 Model: d.Model,
176 ContextWindow: d.ContextWindow,
177 PricingCurrency: d.PricingCurrency,
178 CacheHitPerMillion: d.CacheHitPerMillion,
179 InputPerMillion: d.InputPerMillion,
180 OutputPerMillion: d.OutputPerMillion,
181 Vision: d.Vision,
182 InputModalities: stringModalities(d.InputModalities),
183 Tools: d.Tools,
184 Reasoning: d.Reasoning,
185 Efforts: append([]string(nil), d.Efforts...),
186 DefaultEffort: d.DefaultEffort,
187 ToolCallReasoning: d.ToolCallReasoning,
188 ReasoningRoundTrip: d.ReasoningRoundTrip,
189 WarnOnMissingToolCallReasoning: d.WarnOnMissingToolCallReasoning,
190 }
191 }
192
193 func stringModalities(values []string) []provider.ModelModality {
194 if values == nil {
195 return nil
196 }
197 out := make([]provider.ModelModality, 0, len(values))
198 for _, value := range values {
199 out = append(out, provider.ModelModality(value))
200 }
201 return out
202 }
203
204 // ChunkFromProtocol converts one inbound extension stream chunk. The protocol
205 // requires extensions to redact provider errors before sending them, but the
206 // host treats the sidecar as untrusted and defensively redacts again. A
207 // provider_interrupted code maps to StreamInterruptedError so the agent's
208 // interruption recovery applies.
209 func ChunkFromProtocol(chunk protocol.ProviderChunk) provider.Chunk {
210 converted := provider.Chunk{
211 Type: chunkTypeFromProtocol(chunk.Type),
212 Text: chunk.Text,
213 Signature: chunk.Signature,
214 ArgChars: chunk.ArgChars,
215 }
216 if chunk.ToolCall != nil {
217 converted.ToolCall = &provider.ToolCall{
218 ID: chunk.ToolCall.ID,
219 Name: chunk.ToolCall.Name,
220 Arguments: chunk.ToolCall.Arguments,
221 ThoughtSignature: chunk.ToolCall.ThoughtSignature,
222 }
223 }
224 if chunk.Usage != nil {
225 converted.Usage = UsageFromProtocol(chunk.Usage)
226 }
227 if chunk.Error != nil {
228 err := errors.New(secrets.RedactCredentials(chunk.Error.Message))
229 if chunk.Error.Code == protocol.ProviderInterrupted {
230 err = &provider.StreamInterruptedError{Err: err}
231 }
232 converted.Err = err
233 }
234 return converted
235 }
236
237 func chunkTypeFromProtocol(kind protocol.ProviderChunkType) provider.ChunkType {
238 switch kind {
239 case protocol.ChunkText:
240 return provider.ChunkText
241 case protocol.ChunkReasoning:
242 return provider.ChunkReasoning
243 case protocol.ChunkToolCallStart:
244 return provider.ChunkToolCallStart
245 case protocol.ChunkToolCallDelta:
246 return provider.ChunkToolCallArgsDelta
247 case protocol.ChunkToolCall:
248 return provider.ChunkToolCall
249 case protocol.ChunkUsage:
250 return provider.ChunkUsage
251 case protocol.ChunkDone:
252 return provider.ChunkDone
253 case protocol.ChunkError:
254 return provider.ChunkError
255 default:
256 return provider.ChunkError
257 }
258 }
259
259 lines GO