| 116 | } |
| 117 | |
| 118 | func (p *OpenAI) CreateInterceptor(_ http.ResponseWriter, r *http.Request, tracer trace.Tracer) (_ intercept.Interceptor, outErr error) { |
| 119 | id := uuid.New() |
| 120 | |
| 121 | _, span := tracer.Start(r.Context(), "Intercept.CreateInterceptor") |
| 122 | defer tracing.EndSpanErr(span, &outErr) |
| 123 | |
| 124 | var interceptor intercept.Interceptor |
| 125 | |
| 126 | cfg := p.cfg |
| 127 | // At this point the request contains only LLM provider headers. |
| 128 | // Any Coder-specific authentication has already been stripped. |
| 129 | // |
| 130 | // In centralized mode Authorization is absent, so cfg keeps the |
| 131 | // KeyPool from provider construction and the failover loop walks |
| 132 | // it. |
| 133 | // |
| 134 | // In BYOK mode the user's credential is in Authorization, |
| 135 | // populate cfg.Key and clear cfg.KeyPool so failover is disabled. |
| 136 | // |
| 137 | // TODO(ssncferreira): consolidate auth field handling per |
| 138 | // https://github.com/coder/aibridge/issues/266. |
| 139 | credKind := intercept.CredentialKindCentralized |
| 140 | var credSecret string |
| 141 | if token := utils.ExtractBearerToken(r.Header.Get("Authorization")); token != "" { |
| 142 | cfg.Key = token |
| 143 | cfg.KeyPool = nil |
| 144 | credKind = intercept.CredentialKindBYOK |
| 145 | credSecret = token |
| 146 | } |
| 147 | // Centralized leaves credSecret empty: the hint is set by the |
| 148 | // failover loop on each key attempt and persisted at |
| 149 | // end-of-interception. |
| 150 | cred := intercept.NewCredentialInfo(credKind, credSecret) |
| 151 | |
| 152 | path := strings.TrimPrefix(r.URL.Path, p.RoutePrefix()) |
| 153 | switch path { |
| 154 | case routeChatCompletions: |
| 155 | var req chatcompletions.ChatCompletionNewParamsWrapper |
| 156 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| 157 | return nil, xerrors.Errorf("unmarshal request body: %w", err) |
| 158 | } |
| 159 | |
| 160 | if req.Stream { |
| 161 | interceptor = chatcompletions.NewStreamingInterceptor(id, &req, p.Name(), cfg, r.Header, p.AuthHeader(), tracer, cred) |
| 162 | } else { |
| 163 | interceptor = chatcompletions.NewBlockingInterceptor(id, &req, p.Name(), cfg, r.Header, p.AuthHeader(), tracer, cred) |
| 164 | } |
| 165 | |
| 166 | case routeResponses: |
| 167 | payload, err := io.ReadAll(r.Body) |
| 168 | if err != nil { |
| 169 | return nil, xerrors.Errorf("read body: %w", err) |
| 170 | } |
| 171 | reqPayload, err := responses.NewRequestPayload(payload) |
| 172 | if err != nil { |
| 173 | return nil, xerrors.Errorf("unmarshal request body: %w", err) |
| 174 | } |
| 175 | if reqPayload.Stream() { |