MCPcopy Create free account
hub / github.com/coder/aibridge / ProcessRequest

Method ProcessRequest

intercept/chatcompletions/streaming.go:83–348  ·  view source on GitHub ↗

ProcessRequest handles a request to /v1/chat/completions. See https://platform.openai.com/docs/api-reference/chat-streaming/streaming. It will inject any tools which have been provided by the [mcp.ServerProxier]. When a response from the server includes an event indicating that a tool must be invo

(w http.ResponseWriter, r *http.Request)

Source from the content-addressed store, hash-verified

81// results relayed to the SERVER. The response from the server will be handled synchronously, and this loop
82// can continue until all injected tool invocations are completed and the response is relayed to the client.
83func (i *StreamingInterception) ProcessRequest(w http.ResponseWriter, r *http.Request) (outErr error) {
84 if i.req == nil {
85 return xerrors.New("developer error: req is nil")
86 }
87
88 ctx, span := i.tracer.Start(r.Context(), "Intercept.ProcessRequest", trace.WithAttributes(tracing.InterceptionAttributesFromContext(r.Context())...))
89 defer tracing.EndSpanErr(span, &outErr)
90
91 // Include token usage.
92 i.req.StreamOptions.IncludeUsage = openai.Bool(true)
93
94 i.injectTools()
95
96 // Allow us to interrupt watch via cancel.
97 ctx, cancel := context.WithCancel(ctx)
98 defer cancel()
99 r = r.WithContext(ctx) // Rewire context for SSE cancellation.
100
101 svc := i.newCompletionsService()
102 logger := i.logger.With(slog.F("model", i.req.Model))
103
104 streamCtx, streamCancel := context.WithCancelCause(ctx)
105 defer streamCancel(xerrors.New("deferred"))
106
107 // events will either terminate when shutdown after interaction with upstream completes, or when streamCtx is done.
108 events := eventstream.NewEventStream(streamCtx, logger.Named("sse-sender"), nil)
109 go events.Start(w, r)
110 defer func() {
111 _ = events.Shutdown(streamCtx) // Catch-all in case it doesn't get shutdown after stream completes.
112 }()
113
114 // Force responses to only have one choice.
115 // It's unnecessary to generate multiple responses, and would complicate our stream processing logic if
116 // multiple choices were returned.
117 i.req.N = openai.Int(1)
118
119 prompt, err := i.req.lastUserPrompt()
120 if err != nil {
121 logger.Warn(ctx, "failed to retrieve last user prompt", slog.Error(err))
122 }
123
124 var (
125 stream *ssestream.Stream[openai.ChatCompletionChunk]
126 lastErr error
127 interceptionErr error
128 )
129 for {
130 // TODO add outer loop span (https://github.com/coder/aibridge/issues/67)
131 var opts []option.RequestOption
132
133 // TODO(ssncferreira): inject actor headers directly in the client-header
134 // middleware instead of using SDK options.
135 if actor := aibcontext.ActorFromContext(r.Context()); actor != nil && i.cfg.SendActorHeaders {
136 opts = append(opts, intercept.ActorHeadersAsOpenAIOpts(actor)...)
137 }
138
139 // We take control of request body here and pass it to the SDK as a raw byte slice.
140 // This is because the SDK's serialization applies hidden request options that result in

Callers

nothing calls this directly

Calls 15

StartMethod · 0.95
ShutdownMethod · 0.95
newStreamMethod · 0.95
marshalChunkMethod · 0.95
SendMethod · 0.95
getInjectedToolByNameMethod · 0.95
InitiateStreamMethod · 0.95
IsStreamingMethod · 0.95
marshalErrMethod · 0.95
SendRawMethod · 0.95
encodeForStreamMethod · 0.95

Tested by

no test coverage detected