CompleteStreamingHandler is a HTTP handler that wraps CompleteStreaming and maps it to SSE. This is required as vanguard doesn't currently map streaming RPCs to SSE, so we register this handler manually override the behavior
(w http.ResponseWriter, req *http.Request)
| 454 | // CompleteStreamingHandler is a HTTP handler that wraps CompleteStreaming and maps it to SSE. |
| 455 | // This is required as vanguard doesn't currently map streaming RPCs to SSE, so we register this handler manually override the behavior |
| 456 | func (s *Server) CompleteStreamingHandler(w http.ResponseWriter, req *http.Request) { |
| 457 | // Observability |
| 458 | ctx := req.Context() |
| 459 | instanceID := req.PathValue("instance_id") |
| 460 | observability.AddRequestAttributes(ctx, |
| 461 | attribute.String("args.instance_id", instanceID), |
| 462 | ) |
| 463 | |
| 464 | // Access check |
| 465 | if !auth.GetClaims(ctx, instanceID).Can(runtime.UseAI) { |
| 466 | http.Error(w, "action not allowed", http.StatusUnauthorized) |
| 467 | return |
| 468 | } |
| 469 | |
| 470 | // Apply configured timeout for AI chat |
| 471 | cfg, err := s.runtime.InstanceConfig(ctx, instanceID) |
| 472 | if err != nil { |
| 473 | http.Error(w, fmt.Sprintf("failed to load instance config: %v", err), http.StatusBadRequest) |
| 474 | return |
| 475 | } |
| 476 | ctx, cancel := context.WithTimeout(ctx, time.Duration(cfg.AICompletionTimeoutSeconds)*time.Second) |
| 477 | defer cancel() |
| 478 | req = req.WithContext(ctx) |
| 479 | |
| 480 | // Build request. Note we try to support both GET and POST. |
| 481 | completeReq := &runtimev1.CompleteStreamingRequest{} |
| 482 | if req.Method == http.MethodGet { |
| 483 | // Parse from query parameters |
| 484 | completeReq.ConversationId = req.URL.Query().Get("conversationId") |
| 485 | completeReq.Prompt = req.URL.Query().Get("prompt") |
| 486 | } else { |
| 487 | // Parse from JSON body |
| 488 | body, err := io.ReadAll(req.Body) |
| 489 | if err != nil { |
| 490 | http.Error(w, "failed to read request body", http.StatusBadRequest) |
| 491 | return |
| 492 | } |
| 493 | if err := protojson.Unmarshal(body, completeReq); err != nil { |
| 494 | http.Error(w, "failed to parse request body", http.StatusBadRequest) |
| 495 | return |
| 496 | } |
| 497 | } |
| 498 | completeReq.InstanceId = instanceID // Set instance ID from path |
| 499 | |
| 500 | // Start goroutine that calls CompleteStreaming and publishes responses to a channel |
| 501 | events := make(chan *sseEvent) |
| 502 | go func() { |
| 503 | // Handle panics (it's a separate goroutine so the middleware won't catch panics) |
| 504 | defer func() { |
| 505 | if r := recover(); r != nil { |
| 506 | s.logger.Error("panic in CompleteStreamingHandler subscription goroutine", zap.Any("recover", r), zap.Stack("stack")) |
| 507 | } |
| 508 | }() |
| 509 | |
| 510 | // We must close the events channel when done to make sure the SSE handler returns |
| 511 | defer close(events) |
| 512 | |
| 513 | // Create the shim that implements RuntimeService_CompleteStreamingServer |
nothing calls this directly
no test coverage detected