返回 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 v1 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 Tools: d.Tools,
183 Reasoning: d.Reasoning,
184 Efforts: append([]string(nil), d.Efforts...),
185 DefaultEffort: d.DefaultEffort,
186 ToolCallReasoning: d.ToolCallReasoning,
187 ReasoningRoundTrip: d.ReasoningRoundTrip,
188 WarnOnMissingToolCallReasoning: d.WarnOnMissingToolCallReasoning,
189 }
190 }
191
192 // ChunkFromProtocol converts one inbound extension stream chunk. The protocol
193 // requires extensions to redact provider errors before sending them, but the
194 // host treats the sidecar as untrusted and defensively redacts again. A
195 // provider_interrupted code maps to StreamInterruptedError so the agent's
196 // interruption recovery applies.
197 func ChunkFromProtocol(chunk protocol.ProviderChunk) provider.Chunk {
198 converted := provider.Chunk{
199 Type: chunkTypeFromProtocol(chunk.Type),
200 Text: chunk.Text,
201 Signature: chunk.Signature,
202 ArgChars: chunk.ArgChars,
203 }
204 if chunk.ToolCall != nil {
205 converted.ToolCall = &provider.ToolCall{
206 ID: chunk.ToolCall.ID,
207 Name: chunk.ToolCall.Name,
208 Arguments: chunk.ToolCall.Arguments,
209 ThoughtSignature: chunk.ToolCall.ThoughtSignature,
210 }
211 }
212 if chunk.Usage != nil {
213 converted.Usage = UsageFromProtocol(chunk.Usage)
214 }
215 if chunk.Error != nil {
216 err := errors.New(secrets.RedactCredentials(chunk.Error.Message))
217 if chunk.Error.Code == protocol.ProviderInterrupted {
218 err = &provider.StreamInterruptedError{Err: err}
219 }
220 converted.Err = err
221 }
222 return converted
223 }
224
225 func chunkTypeFromProtocol(kind protocol.ProviderChunkType) provider.ChunkType {
226 switch kind {
227 case protocol.ChunkText:
228 return provider.ChunkText
229 case protocol.ChunkReasoning:
230 return provider.ChunkReasoning
231 case protocol.ChunkToolCallStart:
232 return provider.ChunkToolCallStart
233 case protocol.ChunkToolCallDelta:
234 return provider.ChunkToolCallArgsDelta
235 case protocol.ChunkToolCall:
236 return provider.ChunkToolCall
237 case protocol.ChunkUsage:
238 return provider.ChunkUsage
239 case protocol.ChunkDone:
240 return provider.ChunkDone
241 case protocol.ChunkError:
242 return provider.ChunkError
243 default:
244 return provider.ChunkError
245 }
246 }
247
247 lines GO