|
31 | 31 | BaseExecutor, |
32 | 32 | EntryKind, |
33 | 33 | ExecutionResult, |
| 34 | + StreamEvent, |
| 35 | + StreamResult, |
| 36 | + StreamChunk, |
34 | 37 | WorkspaceEntry, |
35 | 38 | wrap_last_line_interactive, |
36 | 39 | ) |
@@ -597,3 +600,56 @@ def _validate_relative_path(self, path_str: str) -> Path: |
597 | 600 | raise ValueError("File path must not be empty.") |
598 | 601 |
|
599 | 602 | return Path(*sanitized_parts) |
| 603 | + |
| 604 | + |
| 605 | +def _stream_kube_output( |
| 606 | + exec_resp: Any, |
| 607 | + deadline: float, |
| 608 | + max_output_bytes: int, |
| 609 | +) -> Generator[StreamChunk, None, tuple[int | None, bool]]: |
| 610 | + """Read stdout/stderr from a Kubernetes exec stream and yield StreamChunk events. |
| 611 | +
|
| 612 | + Returns a (exit_code, timed_out) tuple. |
| 613 | + """ |
| 614 | + stdout_bytes = 0 |
| 615 | + stderr_bytes = 0 |
| 616 | + exit_code: int | None = None |
| 617 | + timed_out = False |
| 618 | + |
| 619 | + while exec_resp.is_open(): |
| 620 | + remaining = deadline - time.time() |
| 621 | + if remaining <= 0: |
| 622 | + timed_out = True |
| 623 | + break |
| 624 | + |
| 625 | + exec_resp.update(timeout=min(remaining, 1)) |
| 626 | + |
| 627 | + if exec_resp.peek_stdout(): |
| 628 | + text: str = exec_resp.read_stdout() |
| 629 | + raw = text.encode("utf-8") |
| 630 | + if stdout_bytes < max_output_bytes: |
| 631 | + allowed = max_output_bytes - stdout_bytes |
| 632 | + if len(raw) > allowed: |
| 633 | + text = raw[:allowed].decode("utf-8", errors="ignore") |
| 634 | + if text: |
| 635 | + yield StreamChunk(stream="stdout", data=text) |
| 636 | + stdout_bytes += len(raw) |
| 637 | + |
| 638 | + if exec_resp.peek_stderr(): |
| 639 | + text = exec_resp.read_stderr() |
| 640 | + raw = text.encode("utf-8") |
| 641 | + if stderr_bytes < max_output_bytes: |
| 642 | + allowed = max_output_bytes - stderr_bytes |
| 643 | + if len(raw) > allowed: |
| 644 | + text = raw[:allowed].decode("utf-8", errors="ignore") |
| 645 | + if text: |
| 646 | + yield StreamChunk(stream="stderr", data=text) |
| 647 | + stderr_bytes += len(raw) |
| 648 | + |
| 649 | + error: str = exec_resp.read_channel(ws_client.ERROR_CHANNEL) |
| 650 | + if error: |
| 651 | + exit_code = _parse_exit_code(error) |
| 652 | + break |
| 653 | + |
| 654 | + exec_resp.close() |
| 655 | + return exit_code, timed_out |
0 commit comments