(
response: reqwest::Response,
on_token: &Channel<String>,
cancel_token: &CancellationToken,
)
| 632 | } |
| 633 | |
| 634 | async fn stream_openai_sse( |
| 635 | response: reqwest::Response, |
| 636 | on_token: &Channel<String>, |
| 637 | cancel_token: &CancellationToken, |
| 638 | ) -> AppResult<(String, Option<TokenUsage>)> { |
| 639 | let mut stream = response.bytes_stream(); |
| 640 | let mut line_buffer = String::new(); |
| 641 | let mut output = String::new(); |
| 642 | let mut usage = UsageAccumulator::default(); |
| 643 | |
| 644 | loop { |
| 645 | tokio::select! { |
| 646 | _ = cancel_token.cancelled() => { |
| 647 | return Err(AppError::Cancelled); |
| 648 | } |
| 649 | chunk = stream.next() => { |
| 650 | match chunk { |
| 651 | Some(Ok(bytes)) => { |
| 652 | line_buffer.push_str(&String::from_utf8_lossy(&bytes)); |
| 653 | |
| 654 | while let Some(pos) = line_buffer.find('\n') { |
| 655 | let mut line = line_buffer[..pos].to_string(); |
| 656 | line_buffer.drain(..=pos); |
| 657 | if line.ends_with('\r') { |
| 658 | line.pop(); |
| 659 | } |
| 660 | |
| 661 | if parse_openai_sse_line(&line, on_token, &mut output, &mut usage)? { |
| 662 | return Ok((output, usage.finish())); |
| 663 | } |
| 664 | } |
| 665 | } |
| 666 | Some(Err(e)) => return Err(AppError::Http(e.to_string())), |
| 667 | None => break, |
| 668 | } |
| 669 | } |
| 670 | } |
| 671 | } |
| 672 | |
| 673 | if !line_buffer.is_empty() { |
| 674 | parse_openai_sse_line(&line_buffer, on_token, &mut output, &mut usage)?; |
| 675 | } |
| 676 | |
| 677 | Ok((output, usage.finish())) |
| 678 | } |
| 679 | |
| 680 | fn parse_openai_sse_line( |
| 681 | line: &str, |
no test coverage detected