From 488fd3d5b2a9b210ad8be7c097fed21cad8c6ac1 Mon Sep 17 00:00:00 2001 From: longcoding Date: Thu, 23 Jul 2026 17:29:19 +0800 Subject: [PATCH] fix(windows): configure stdio as utf-8 --- src/kimi_cli/__main__.py | 26 +++++++++++ tests/core/test_windows_stdio.py | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 tests/core/test_windows_stdio.py diff --git a/src/kimi_cli/__main__.py b/src/kimi_cli/__main__.py index 2d8aded024..ec55db0968 100644 --- a/src/kimi_cli/__main__.py +++ b/src/kimi_cli/__main__.py @@ -5,11 +5,37 @@ from pathlib import Path +def _ensure_utf8_stdio() -> None: + """Use UTF-8 for the standard Windows text streams when they support it. + + Git Bash may expose the Windows legacy code page (for example, cp936) to + Python. The interactive UI contains characters outside those code pages, + so Rich can otherwise fail while rendering the welcome panel. Redirected + and non-standard streams are deliberately left alone. + """ + if sys.platform != "win32": + return + + for stream_name in ("stdout", "stderr"): + stream = getattr(sys, stream_name) + reconfigure = getattr(stream, "reconfigure", None) + if not callable(reconfigure): + continue + try: + reconfigure(encoding="utf-8", errors="replace") + except (OSError, TypeError, ValueError): + # Some embedders expose text-like streams with a partial or locked + # reconfigure implementation. Preserve those streams as-is. + continue + + def _prog_name() -> str: return Path(sys.argv[0]).name or "kimi" def main(argv: Sequence[str] | None = None) -> int | str | None: + _ensure_utf8_stdio() + from kimi_cli.telemetry.crash import install_crash_handlers, set_phase from kimi_cli.utils.proxy import normalize_proxy_env diff --git a/tests/core/test_windows_stdio.py b/tests/core/test_windows_stdio.py new file mode 100644 index 0000000000..3e6a560291 --- /dev/null +++ b/tests/core/test_windows_stdio.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import io +import sys + + +class _EncodinglessStream: + encoding = None + + def __init__(self) -> None: + self.value = "" + + def write(self, value: str) -> int: + self.value += value + return len(value) + + def flush(self) -> None: + pass + + +def test_windows_stdio_allows_welcome_rendering_with_cp936_streams(monkeypatch) -> None: + from kimi_cli.__main__ import _ensure_utf8_stdio + + stdout_buffer = io.BytesIO() + stderr_buffer = io.BytesIO() + stdout = io.TextIOWrapper(stdout_buffer, encoding="cp936", errors="strict") + stderr = io.TextIOWrapper(stderr_buffer, encoding="cp936", errors="strict") + monkeypatch.setattr(sys, "platform", "win32") + monkeypatch.setattr(sys, "stdout", stdout) + monkeypatch.setattr(sys, "stderr", stderr) + + _ensure_utf8_stdio() + + import kimi_cli.ui.shell as shell_ui + from kimi_cli.ui.shell import _print_welcome_info + from kimi_cli.ui.shell.console import _KimiConsole + + monkeypatch.setattr(shell_ui, "console", _KimiConsole(file=sys.stdout, highlight=False)) + _print_welcome_info("Kimi Code CLI", []) + sys.stdout.flush() + + assert sys.stdout.encoding == "utf-8" + assert "Welcome to Kimi Code CLI!" in stdout_buffer.getvalue().decode("utf-8") + + +def test_windows_stdio_preserves_redirected_and_encodingless_streams(monkeypatch) -> None: + from kimi_cli.__main__ import _ensure_utf8_stdio + + redirected = io.StringIO() + encodingless = _EncodinglessStream() + monkeypatch.setattr(sys, "platform", "win32") + monkeypatch.setattr(sys, "stdout", redirected) + monkeypatch.setattr(sys, "stderr", encodingless) + + _ensure_utf8_stdio() + + assert sys.stdout is redirected + assert sys.stderr is encodingless + print("redirected") + print("encodingless", file=sys.stderr) + assert redirected.getvalue() == "redirected\n" + assert encodingless.value == "encodingless\n" + + +def test_non_windows_stdio_is_unchanged(monkeypatch) -> None: + from kimi_cli.__main__ import _ensure_utf8_stdio + + stream = io.TextIOWrapper(io.BytesIO(), encoding="cp936", errors="strict") + monkeypatch.setattr(sys, "platform", "linux") + monkeypatch.setattr(sys, "stdout", stream) + + _ensure_utf8_stdio() + + assert sys.stdout is stream + assert sys.stdout.encoding == "cp936"