From e52456ba9d8b29e3ef9234c9b0286623753767dd Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Mon, 19 Jan 2026 21:47:57 +0000 Subject: [PATCH] feat: add timeout handling for synchronous agent calls- Introduce `_run_sync_with_timeout` to manage execution time limits for agent prompts.- Update `_run_with_retries` to use the new timeout function, improving error handling for timeouts and rate limits. --- scripts/component_migration_orchestrator.py | 33 ++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/scripts/component_migration_orchestrator.py b/scripts/component_migration_orchestrator.py index 7245265..6897637 100644 --- a/scripts/component_migration_orchestrator.py +++ b/scripts/component_migration_orchestrator.py @@ -15,7 +15,7 @@ import difflib import time import random import re -from concurrent.futures import ThreadPoolExecutor, as_completed +from concurrent.futures import ThreadPoolExecutor, as_completed, TimeoutError as FuturesTimeoutError from dataclasses import dataclass from pathlib import Path from typing import Any, Dict, Iterable, List, Optional, Tuple @@ -31,6 +31,7 @@ except Exception as exc: # pragma: no cover - runtime dependency check ROOT = Path(__file__).resolve().parents[1] DEFAULT_MODEL = os.getenv("CODEX_MODEL") or os.getenv("OPENAI_MODEL") or "gpt-4o-mini" API_CALL_DELAY_SECONDS = 2.0 +API_CALL_TIMEOUT_SECONDS = 120.0 COMPONENT_DIRS = [ ROOT / "src" / "components" / "atoms", ROOT / "src" / "components" / "molecules", @@ -484,6 +485,12 @@ def _is_rate_limited(exc: Exception) -> bool: return "rate limit" in message or "too many requests" in message or "429" in message +def _run_sync_with_timeout(agent: Agent, prompt: str, timeout: float) -> Any: + with ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(Runner.run_sync, agent, prompt) + return future.result(timeout=timeout) + + def _run_with_retries(agent: Agent, prompt: str, label: str) -> Any: max_retries = 5 max_attempts = max_retries + 1 @@ -501,14 +508,17 @@ def _run_with_retries(agent: Agent, prompt: str, label: str) -> Any: file=sys.stderr, ) time.sleep(API_CALL_DELAY_SECONDS) - result = Runner.run_sync(agent, prompt) + result = _run_sync_with_timeout( + agent, prompt, API_CALL_TIMEOUT_SECONDS + ) print( f"[info] {label} attempt {attempt}/{max_attempts} completed", file=sys.stderr, ) return result except Exception as exc: - if not _is_rate_limited(exc): + is_timeout = isinstance(exc, (FuturesTimeoutError, TimeoutError)) + if not _is_rate_limited(exc) and not is_timeout: print( f"[error] {label} attempt {attempt} failed: {exc}", file=sys.stderr, @@ -518,10 +528,19 @@ def _run_with_retries(agent: Agent, prompt: str, label: str) -> Any: raise delay = min(max_delay, base_delay * (2 ** (attempt - 1))) delay += random.uniform(0, delay * 0.2) - print( - f"[warn] rate limited {label}; retry {attempt}/{max_retries} in {delay:.1f}s", - file=sys.stderr, - ) + if is_timeout: + print( + ( + f"[warn] timeout {label} after {API_CALL_TIMEOUT_SECONDS:.0f}s; " + f"retry {attempt}/{max_retries} in {delay:.1f}s" + ), + file=sys.stderr, + ) + else: + print( + f"[warn] rate limited {label}; retry {attempt}/{max_retries} in {delay:.1f}s", + file=sys.stderr, + ) time.sleep(delay)