| 124 | } |
| 125 | |
| 126 | func (p *Copilot) CreateInterceptor(_ http.ResponseWriter, r *http.Request, tracer trace.Tracer) (_ intercept.Interceptor, outErr error) { |
| 127 | _, span := tracer.Start(r.Context(), "Intercept.CreateInterceptor") |
| 128 | defer tracing.EndSpanErr(span, &outErr) |
| 129 | |
| 130 | // Extract the per-user Copilot key from the Authorization header. |
| 131 | key := utils.ExtractBearerToken(r.Header.Get("Authorization")) |
| 132 | if key == "" { |
| 133 | span.SetStatus(codes.Error, "missing authorization") |
| 134 | return nil, xerrors.New("missing Copilot authorization: Authorization header not found or invalid") |
| 135 | } |
| 136 | |
| 137 | id := uuid.New() |
| 138 | |
| 139 | // Build config for the interceptor using the per-request key. |
| 140 | // Copilot's API is OpenAI-compatible, so it uses the OpenAI interceptors |
| 141 | // that require a config.OpenAI. |
| 142 | cfg := config.OpenAI{ |
| 143 | BaseURL: p.cfg.BaseURL, |
| 144 | Key: key, |
| 145 | APIDumpDir: p.cfg.APIDumpDir, |
| 146 | CircuitBreaker: p.cfg.CircuitBreaker, |
| 147 | ExtraHeaders: extractCopilotHeaders(r), |
| 148 | } |
| 149 | |
| 150 | cred := intercept.NewCredentialInfo(intercept.CredentialKindBYOK, key) |
| 151 | |
| 152 | var interceptor intercept.Interceptor |
| 153 | |
| 154 | path := strings.TrimPrefix(r.URL.Path, p.RoutePrefix()) |
| 155 | switch path { |
| 156 | case routeCopilotChatCompletions: |
| 157 | var req chatcompletions.ChatCompletionNewParamsWrapper |
| 158 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| 159 | return nil, xerrors.Errorf("unmarshal chat completions request body: %w", err) |
| 160 | } |
| 161 | |
| 162 | if req.Stream { |
| 163 | interceptor = chatcompletions.NewStreamingInterceptor(id, &req, p.Name(), cfg, r.Header, p.AuthHeader(), tracer, cred) |
| 164 | } else { |
| 165 | interceptor = chatcompletions.NewBlockingInterceptor(id, &req, p.Name(), cfg, r.Header, p.AuthHeader(), tracer, cred) |
| 166 | } |
| 167 | |
| 168 | case routeCopilotResponses: |
| 169 | payload, err := io.ReadAll(r.Body) |
| 170 | if err != nil { |
| 171 | return nil, xerrors.Errorf("read body: %w", err) |
| 172 | } |
| 173 | reqPayload, err := responses.NewRequestPayload(payload) |
| 174 | if err != nil { |
| 175 | return nil, xerrors.Errorf("unmarshal request body: %w", err) |
| 176 | } |
| 177 | |
| 178 | if reqPayload.Stream() { |
| 179 | interceptor = responses.NewStreamingInterceptor(id, reqPayload, p.Name(), cfg, r.Header, p.AuthHeader(), tracer, cred) |
| 180 | } else { |
| 181 | interceptor = responses.NewBlockingInterceptor(id, reqPayload, p.Name(), cfg, r.Header, p.AuthHeader(), tracer, cred) |
| 182 | } |
| 183 | |