CompleteStreaming implements RuntimeService
(req *runtimev1.CompleteStreamingRequest, stream runtimev1.RuntimeService_CompleteStreamingServer)
| 319 | |
| 320 | // CompleteStreaming implements RuntimeService |
| 321 | func (s *Server) CompleteStreaming(req *runtimev1.CompleteStreamingRequest, stream runtimev1.RuntimeService_CompleteStreamingServer) (resErr error) { |
| 322 | // Access check |
| 323 | claims := auth.GetClaims(stream.Context(), req.InstanceId) |
| 324 | if !claims.Can(runtime.UseAI) { |
| 325 | return ErrForbidden |
| 326 | } |
| 327 | |
| 328 | // Apply configured timeout for AI completions |
| 329 | cfg, err := s.runtime.InstanceConfig(stream.Context(), req.InstanceId) |
| 330 | if err != nil { |
| 331 | return fmt.Errorf("failed to load instance config: %w", err) |
| 332 | } |
| 333 | ctx, cancel := context.WithTimeout(stream.Context(), time.Duration(cfg.AICompletionTimeoutSeconds)*time.Second) |
| 334 | defer cancel() |
| 335 | |
| 336 | // Validate request - either prompt or feedback context must be provided |
| 337 | if req.Prompt == "" && req.FeedbackAgentContext == nil { |
| 338 | return status.Error(codes.InvalidArgument, "prompt or feedback_agent_context must be provided") |
| 339 | } |
| 340 | |
| 341 | // Setup user agent |
| 342 | version := s.runtime.Version().Number |
| 343 | if version == "" { |
| 344 | version = "unknown" |
| 345 | } |
| 346 | userAgent := fmt.Sprintf("rill/%s", version) |
| 347 | |
| 348 | // Open the AI session |
| 349 | session, err := s.ai.Session(ctx, &ai.SessionOptions{ |
| 350 | InstanceID: req.InstanceId, |
| 351 | SessionID: req.ConversationId, |
| 352 | Claims: claims, |
| 353 | UserAgent: userAgent, |
| 354 | }) |
| 355 | if err != nil { |
| 356 | return err |
| 357 | } |
| 358 | defer func() { |
| 359 | err := session.Flush(ctx) |
| 360 | if err != nil { |
| 361 | resErr = errors.Join(resErr, err) |
| 362 | } |
| 363 | }() |
| 364 | |
| 365 | // Open subscription for session messages and stream them to the client in the background |
| 366 | subCh := session.Subscribe() |
| 367 | defer session.Unsubscribe(subCh) |
| 368 | go func() { |
| 369 | // Handle panics (it's a separate goroutine so the middleware won't catch panics) |
| 370 | defer func() { |
| 371 | if r := recover(); r != nil { |
| 372 | s.logger.Error("panic in CompleteStreaming subscription goroutine", zap.Any("recover", r), zap.Stack("stack")) |
| 373 | } |
| 374 | }() |
| 375 | |
| 376 | // Read messages until the context is done or the tool call finished. |
| 377 | for { |
| 378 | select { |
no test coverage detected