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