- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
151 lines
5.1 KiB
Python
151 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import ssl
|
|
import sys
|
|
from typing import Any, Protocol, cast
|
|
|
|
import anyio
|
|
import httpx2
|
|
|
|
from ._constants import DEFAULT_TIMEOUT, DEFAULT_CONNECTION_LIMITS
|
|
|
|
|
|
class _LegacyHttpxModule(Protocol):
|
|
Auth: type[httpx2.Auth]
|
|
Client: type[httpx2.Client]
|
|
AsyncClient: type[httpx2.AsyncClient]
|
|
URL: type[httpx2.URL]
|
|
Response: type[httpx2.Response]
|
|
Timeout: type[httpx2.Timeout]
|
|
Limits: type[httpx2.Limits]
|
|
TimeoutException: type[httpx2.TimeoutException]
|
|
RequestError: type[httpx2.RequestError]
|
|
HTTPStatusError: type[httpx2.HTTPStatusError]
|
|
StreamConsumed: type[httpx2.StreamConsumed]
|
|
RequestNotRead: type[httpx2.RequestNotRead]
|
|
|
|
|
|
def _loaded_legacy_httpx() -> _LegacyHttpxModule | None:
|
|
module = sys.modules.get("httpx")
|
|
return cast(_LegacyHttpxModule, module) if module is not None else None
|
|
|
|
|
|
def is_httpx2_sync_client(value: object) -> bool:
|
|
return isinstance(value, httpx2.Client)
|
|
|
|
|
|
def is_httpx2_async_client(value: object) -> bool:
|
|
return isinstance(value, httpx2.AsyncClient)
|
|
|
|
|
|
def is_legacy_httpx_sync_client(value: object) -> bool:
|
|
module = _loaded_legacy_httpx()
|
|
return module is not None and isinstance(value, module.Client)
|
|
|
|
|
|
def is_legacy_httpx_async_client(value: object) -> bool:
|
|
module = _loaded_legacy_httpx()
|
|
return module is not None and isinstance(value, module.AsyncClient)
|
|
|
|
|
|
def normalize_httpx_url(value: str | httpx2.URL) -> httpx2.URL:
|
|
if isinstance(value, httpx2.URL):
|
|
return value
|
|
|
|
module = _loaded_legacy_httpx()
|
|
legacy_value: object = value
|
|
if module is not None and isinstance(legacy_value, module.URL):
|
|
return httpx2.URL(str(legacy_value))
|
|
|
|
return httpx2.URL(value)
|
|
|
|
|
|
def http_response_types() -> tuple[type[httpx2.Response], ...]:
|
|
module = _loaded_legacy_httpx()
|
|
return (httpx2.Response,) if module is None else (httpx2.Response, module.Response)
|
|
|
|
|
|
def normalize_httpx_timeout(value: float | httpx2.Timeout | None) -> float | httpx2.Timeout | None:
|
|
module = _loaded_legacy_httpx()
|
|
if module is not None and isinstance(value, module.Timeout):
|
|
return httpx2.Timeout(**value.as_dict())
|
|
return value
|
|
|
|
|
|
def normalize_httpx2_timeout(value: float | httpx2.Timeout | None) -> float | httpx2.Timeout | None:
|
|
return normalize_httpx_timeout(value)
|
|
|
|
|
|
def normalize_legacy_httpx_timeout(value: float | httpx2.Timeout | None) -> float | httpx2.Timeout | None:
|
|
module = _loaded_legacy_httpx()
|
|
if module is not None and isinstance(value, httpx2.Timeout):
|
|
return module.Timeout(**value.as_dict())
|
|
return value
|
|
|
|
|
|
def normalize_httpx2_auth(value: httpx2.Auth) -> httpx2.Auth:
|
|
module = _loaded_legacy_httpx()
|
|
if module is not None and type(value) is module.Auth:
|
|
return httpx2.Auth()
|
|
return value
|
|
|
|
|
|
def normalize_legacy_httpx_auth(value: httpx2.Auth) -> httpx2.Auth:
|
|
module = _loaded_legacy_httpx()
|
|
if module is not None and type(value) is httpx2.Auth:
|
|
return module.Auth()
|
|
return value
|
|
|
|
|
|
def timeout_exceptions() -> tuple[type[httpx2.TimeoutException], ...]:
|
|
module = _loaded_legacy_httpx()
|
|
return (httpx2.TimeoutException,) if module is None else (httpx2.TimeoutException, module.TimeoutException)
|
|
|
|
|
|
def request_exceptions() -> tuple[type[Exception], ...]:
|
|
module = _loaded_legacy_httpx()
|
|
# Shim unmapped AnyIO TLS failures pending https://github.com/pydantic/httpx2/issues/854.
|
|
errors = (httpx2.RequestError, ssl.SSLError, anyio.EndOfStream)
|
|
return errors if module is None else (*errors, module.RequestError)
|
|
|
|
|
|
def status_exceptions() -> tuple[type[httpx2.HTTPStatusError], ...]:
|
|
module = _loaded_legacy_httpx()
|
|
return (httpx2.HTTPStatusError,) if module is None else (httpx2.HTTPStatusError, module.HTTPStatusError)
|
|
|
|
|
|
def stream_consumed_exceptions() -> tuple[type[httpx2.StreamConsumed], ...]:
|
|
module = _loaded_legacy_httpx()
|
|
return (httpx2.StreamConsumed,) if module is None else (httpx2.StreamConsumed, module.StreamConsumed)
|
|
|
|
|
|
def request_not_read_exceptions() -> tuple[type[httpx2.RequestNotRead], ...]:
|
|
module = _loaded_legacy_httpx()
|
|
return (httpx2.RequestNotRead,) if module is None else (httpx2.RequestNotRead, module.RequestNotRead)
|
|
|
|
|
|
def _set_httpx2_defaults(kwargs: dict[str, Any]) -> None:
|
|
kwargs["timeout"] = normalize_httpx2_timeout(kwargs.get("timeout", DEFAULT_TIMEOUT))
|
|
|
|
limits = kwargs.get("limits", DEFAULT_CONNECTION_LIMITS)
|
|
module = _loaded_legacy_httpx()
|
|
if module is not None and isinstance(limits, module.Limits):
|
|
limits = httpx2.Limits(
|
|
max_connections=limits.max_connections,
|
|
max_keepalive_connections=limits.max_keepalive_connections,
|
|
keepalive_expiry=limits.keepalive_expiry,
|
|
)
|
|
kwargs["limits"] = limits
|
|
kwargs.setdefault("follow_redirects", True)
|
|
|
|
|
|
def DefaultHttpx2Client(**kwargs: Any) -> httpx2.Client:
|
|
"""Create an HTTPX2 client with the SDK's recommended defaults."""
|
|
_set_httpx2_defaults(kwargs)
|
|
return httpx2.Client(**kwargs)
|
|
|
|
|
|
def DefaultAsyncHttpx2Client(**kwargs: Any) -> httpx2.AsyncClient:
|
|
"""Create an async HTTPX2 client with the SDK's recommended defaults."""
|
|
_set_httpx2_defaults(kwargs)
|
|
return httpx2.AsyncClient(**kwargs)
|