fix(kosong): add default 120s timeout to create_openai_client#2409
fix(kosong): add default 120s timeout to create_openai_client#2409wintrover wants to merge 2 commits into
Conversation
…-trip - contrib/deepseek/deepseek_proxy.py: zero-dependency proxy for DeepSeek API that injects thinking mode and stores reasoning_content for multi-turn reliability - contrib/deepseek/config.toml.example: example Kimi CLI config showing both native (OpenAILegacy) and proxy approaches - contrib/deepseek/README.md: comprehensive documentation explaining native support vs proxy approach, configuration, and related code references The OpenAILegacy provider's reasoning_key parameter already natively supports DeepSeek's reasoning_content field — no source patches needed.
When the caller does not specify a timeout, inject httpx.Timeout(120.0, connect=30.0) instead of relying on the OpenAI SDK's 600s default. Without this, openai_legacy and openai_responses providers appear "stuck" when an upstream proxy times out earlier (e.g. MiMo API proxy at ~300s) — the client silently waits another 5 minutes before the SDK timeout fires, and the user sees infinite loading. Fixes MoonshotAI#2384 (for openai_legacy providers)
| logging.basicConfig( | ||
| level=logging.INFO, | ||
| format="%(asctime)s [%(levelname)s] %(message)s", | ||
| handlers=[ | ||
| logging.FileHandler(LOG_FILE, mode="a"), | ||
| logging.StreamHandler(sys.stderr), | ||
| ], | ||
| ) |
There was a problem hiding this comment.
🔴 Logging FileHandler created at module level before log directory exists
The logging.basicConfig call at module level (line 79–86) instantiates a logging.FileHandler(LOG_FILE) for ~/.kimi-code/proxy.log. This executes at import time, before main() runs os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) at line 333. If the ~/.kimi-code/ directory does not yet exist (e.g. on first run), FileHandler.__init__ calls open(...) which raises FileNotFoundError, crashing the script before it ever reaches main().
Prompt for agents
The fix is to ensure the log directory exists before configuring the logging handlers. Move the `os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)` call from `main()` (line 333) to just before the `logging.basicConfig(...)` call at module level (around line 78). This way the directory is guaranteed to exist when the FileHandler tries to open the log file. Alternatively, move the entire logging configuration into `main()` so it runs after the directory creation.
Was this helpful? React with 👍 or 👎 to provide feedback.
| for key, value in resp.headers.items(): | ||
| k = key.lower() | ||
| if k not in ( | ||
| "transfer-encoding", | ||
| "content-encoding", | ||
| "connection", | ||
| "keep-alive", | ||
| ): | ||
| try: | ||
| self.send_header(key, value) | ||
| except Exception: | ||
| pass | ||
| # Override content-length to match our read body | ||
| self.send_header("Content-Length", str(len(body))) |
There was a problem hiding this comment.
🟡 Duplicate Content-Length headers sent in _send_upstream_response
In _send_upstream_response, the header-copying loop (lines 152–163) forwards all upstream headers except transfer-encoding, content-encoding, connection, and keep-alive — but content-length is not in the exclusion list. After the loop, line 165 adds another Content-Length header. Since BaseHTTPRequestHandler.send_header appends rather than replaces, this results in duplicate Content-Length headers in the response. If urllib decompressed the body (after stripping content-encoding), the two values will differ, which violates HTTP spec and can confuse clients.
| for key, value in resp.headers.items(): | |
| k = key.lower() | |
| if k not in ( | |
| "transfer-encoding", | |
| "content-encoding", | |
| "connection", | |
| "keep-alive", | |
| ): | |
| try: | |
| self.send_header(key, value) | |
| except Exception: | |
| pass | |
| # Override content-length to match our read body | |
| self.send_header("Content-Length", str(len(body))) | |
| for key, value in resp.headers.items(): | |
| k = key.lower() | |
| if k not in ( | |
| "transfer-encoding", | |
| "content-encoding", | |
| "content-length", | |
| "connection", | |
| "keep-alive", | |
| ): | |
| try: | |
| self.send_header(key, value) | |
| except Exception: | |
| pass | |
| # Override content-length to match our read body | |
| self.send_header("Content-Length", str(len(body))) |
Was this helpful? React with 👍 or 👎 to provide feedback.
Problem
create_openai_client()inkosong/chat_provider/openai_common.pydoes not pass atimeouttoAsyncOpenAI(). The OpenAI SDK defaults to 600s, so when an upstream proxy times out earlier (e.g. MiMo API proxy at ~300s), the client silently waits another 5+ minutes before the SDK timeout fires. Users see "infinite loading."Root Cause
The
AsyncOpenAIclient inherits the SDK default of 600s timeout. The proxy times out at ~300s, leaving the client hanging.Fix
Inject
httpx.Timeout(120.0, connect=30.0)as a default when the caller does not specify one. Callers can still override by passingtimeout=...inclient_kwargs.Trade-offs
Fixes #2384 (for openai_legacy providers)