ProcessRequest handles a request to /v1/messages. This API has a state-machine behind it, which is described in https://docs.claude.com/en/docs/build-with-claude/streaming#event-types. Each stream uses the following event flow: - `message_start`: contains a Message object with empty content. - A se
(w http.ResponseWriter, r *http.Request)
| 92 | // results relayed to the SERVER. The response from the server will be handled synchronously, and this loop |
| 93 | // can continue until all injected tool invocations are completed and the response is relayed to the client. |
| 94 | func (i *StreamingInterception) ProcessRequest(w http.ResponseWriter, r *http.Request) (outErr error) { |
| 95 | if len(i.reqPayload) == 0 { |
| 96 | return xerrors.New("developer error: request payload is empty") |
| 97 | } |
| 98 | |
| 99 | ctx, span := i.tracer.Start(r.Context(), "Intercept.ProcessRequest", trace.WithAttributes(tracing.InterceptionAttributesFromContext(r.Context())...)) |
| 100 | defer tracing.EndSpanErr(span, &outErr) |
| 101 | |
| 102 | // Allow us to interrupt watch via cancel. |
| 103 | ctx, cancel := context.WithCancel(ctx) |
| 104 | defer cancel() |
| 105 | r = r.WithContext(ctx) // Rewire context for SSE cancellation. |
| 106 | |
| 107 | logger := i.logger.With(slog.F("model", i.Model())) |
| 108 | |
| 109 | var ( |
| 110 | prompt string |
| 111 | promptFound bool |
| 112 | err error |
| 113 | ) |
| 114 | |
| 115 | prompt, promptFound, err = i.reqPayload.lastUserPrompt() |
| 116 | if err != nil { |
| 117 | logger.Warn(ctx, "failed to determine last user prompt", slog.Error(err)) |
| 118 | } |
| 119 | |
| 120 | // Claude Code uses a "small/fast model" for certain tasks. |
| 121 | if !i.isSmallFastModel() { |
| 122 | // Only inject tools into "actual" request. |
| 123 | i.injectTools() |
| 124 | } |
| 125 | |
| 126 | streamCtx, streamCancel := context.WithCancelCause(ctx) |
| 127 | defer streamCancel(xerrors.New("deferred")) |
| 128 | |
| 129 | // TODO(ssncferreira): inject actor headers directly in the client-header |
| 130 | // middleware instead of using SDK options. |
| 131 | var opts []option.RequestOption |
| 132 | if actor := aibcontext.ActorFromContext(ctx); actor != nil && i.cfg.SendActorHeaders { |
| 133 | opts = append(opts, intercept.ActorHeadersAsAnthropicOpts(actor)...) |
| 134 | } |
| 135 | |
| 136 | svc, err := i.newMessagesService(streamCtx, opts...) |
| 137 | if err != nil { |
| 138 | err = xerrors.Errorf("create anthropic client: %w", err) |
| 139 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 140 | return err |
| 141 | } |
| 142 | |
| 143 | // events will either terminate when shutdown after interaction with upstream completes, or when streamCtx is done. |
| 144 | events := eventstream.NewEventStream(streamCtx, logger.Named("sse-sender"), i.pingPayload(), quartz.NewReal()) |
| 145 | go events.Start(w, r) |
| 146 | defer func() { |
| 147 | _ = events.Shutdown(streamCtx) // Catch-all in case it doesn't get shutdown after stream completes. |
| 148 | }() |
| 149 | |
| 150 | // Accumulate usage across the entire streaming interaction (including tool reinvocations). |
| 151 | var cumulativeUsage anthropic.Usage |
nothing calls this directly
no test coverage detected