From 25c7b1baa7bbb72113552c40cc16b34d0e9f30f0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:05:11 -0700 Subject: [PATCH] fix: handle httpx.Timeout object in CopilotACPClient (#11058) run_agent.py passes httpx.Timeout(connect=30, read=120, write=1800, pool=30) as the timeout kwarg on the streaming path. The OpenAI SDK handles this natively, but CopilotACPClient._create_chat_completion() called float(timeout or default), which raises TypeError because httpx.Timeout doesn't implement __float__. Normalize the timeout before passing to _run_prompt: plain floats/ints pass through, httpx.Timeout objects get their largest component extracted (write=1800s is the correct wall-clock budget for the ACP subprocess), and None falls back to the 900s default. --- agent/copilot_acp_client.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/agent/copilot_acp_client.py b/agent/copilot_acp_client.py index 235fd9a1a..031c58d70 100644 --- a/agent/copilot_acp_client.py +++ b/agent/copilot_acp_client.py @@ -313,9 +313,25 @@ class CopilotACPClient: tools=tools, tool_choice=tool_choice, ) + # Normalise timeout: run_agent.py may pass an httpx.Timeout object + # (used natively by the OpenAI SDK) rather than a plain float. + if timeout is None: + _effective_timeout = _DEFAULT_TIMEOUT_SECONDS + elif isinstance(timeout, (int, float)): + _effective_timeout = float(timeout) + else: + # httpx.Timeout or similar — pick the largest component so the + # subprocess has enough wall-clock time for the full response. + _candidates = [ + getattr(timeout, attr, None) + for attr in ("read", "write", "connect", "pool", "timeout") + ] + _numeric = [float(v) for v in _candidates if isinstance(v, (int, float))] + _effective_timeout = max(_numeric) if _numeric else _DEFAULT_TIMEOUT_SECONDS + response_text, reasoning_text = self._run_prompt( prompt_text, - timeout_seconds=float(timeout or _DEFAULT_TIMEOUT_SECONDS), + timeout_seconds=_effective_timeout, ) tool_calls, cleaned_text = _extract_tool_calls_from_text(response_text)