agently-devtools 0.0.1a0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agently_devtools-0.0.1a0/PKG-INFO +83 -0
- agently_devtools-0.0.1a0/README.md +66 -0
- agently_devtools-0.0.1a0/agently_devtools/__init__.py +21 -0
- agently_devtools-0.0.1a0/agently_devtools/_agently_compat.py +55 -0
- agently_devtools-0.0.1a0/agently_devtools/app.py +197 -0
- agently_devtools-0.0.1a0/agently_devtools/bridge.c +17233 -0
- agently_devtools-0.0.1a0/agently_devtools/bridge.py +194 -0
- agently_devtools-0.0.1a0/agently_devtools/cli.py +49 -0
- agently_devtools-0.0.1a0/agently_devtools/evaluations.py +850 -0
- agently_devtools-0.0.1a0/agently_devtools/integrations/__init__.py +1 -0
- agently_devtools-0.0.1a0/agently_devtools/integrations/observation.c +64439 -0
- agently_devtools-0.0.1a0/agently_devtools/integrations/observation.py +1625 -0
- agently_devtools-0.0.1a0/agently_devtools/logs/LogService.py +402 -0
- agently_devtools-0.0.1a0/agently_devtools/logs/Query.py +37 -0
- agently_devtools-0.0.1a0/agently_devtools/logs/__init__.py +2 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/ObservationBackend.c +11553 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/ObservationBackend.py +29 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/ObservationService.c +27679 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/ObservationService.py +475 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/Query.c +5741 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/Query.py +61 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/RunStore.c +21197 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/RunStore.py +324 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/StoreBackend.py +119 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/__init__.py +19 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/backends/JSONLBackend.c +12351 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/backends/JSONLBackend.py +44 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/backends/RemoteBackend.c +12741 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/backends/RemoteBackend.py +79 -0
- agently_devtools-0.0.1a0/agently_devtools/observation/backends/__init__.py +17 -0
- agently_devtools-0.0.1a0/agently_devtools/rust_core.py +39 -0
- agently_devtools-0.0.1a0/agently_devtools/static/.gitkeep +1 -0
- agently_devtools-0.0.1a0/agently_devtools/static/assets/index-Dnpkc26K.js +765 -0
- agently_devtools-0.0.1a0/agently_devtools/static/assets/index-ar1q8gZ0.css +1 -0
- agently_devtools-0.0.1a0/agently_devtools/static/index.html +13 -0
- agently_devtools-0.0.1a0/agently_devtools/ui.py +539 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/PKG-INFO +83 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/SOURCES.txt +51 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/dependency_links.txt +1 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/entry_points.txt +2 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/not-zip-safe +1 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/requires.txt +10 -0
- agently_devtools-0.0.1a0/agently_devtools.egg-info/top_level.txt +1 -0
- agently_devtools-0.0.1a0/pyproject.toml +40 -0
- agently_devtools-0.0.1a0/setup.cfg +4 -0
- agently_devtools-0.0.1a0/setup.py +85 -0
- agently_devtools-0.0.1a0/tests/test_cli.py +25 -0
- agently_devtools-0.0.1a0/tests/test_evaluations.py +335 -0
- agently_devtools-0.0.1a0/tests/test_log_service.py +237 -0
- agently_devtools-0.0.1a0/tests/test_observation_integration.py +533 -0
- agently_devtools-0.0.1a0/tests/test_observation_store_backend.py +49 -0
- agently_devtools-0.0.1a0/tests/test_playground.py +396 -0
- agently_devtools-0.0.1a0/tests/test_runtime_observation_service.py +497 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: agently-devtools
|
|
3
|
+
Version: 0.0.1a0
|
|
4
|
+
Summary: Observation and developer tooling companion package for Agently
|
|
5
|
+
Author-email: Agently Team <developer@agently.tech>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: agently<4.1.0,>=4.0.9
|
|
9
|
+
Requires-Dist: fastapi<1.0,>=0.104
|
|
10
|
+
Requires-Dist: httpx<0.29.0,>=0.28.1
|
|
11
|
+
Requires-Dist: uvicorn<1.0,>=0.30
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: anyio<5,>=4; extra == "dev"
|
|
14
|
+
Requires-Dist: build<2.0,>=1.2; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest<9.0.0,>=8.4.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-asyncio<2.0.0,>=1.0.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# agently-devtools
|
|
19
|
+
|
|
20
|
+
`agently-devtools` is the local observation, evaluation, and playground companion package for `agently`.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install -U agently agently-devtools
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Python `>=3.10`
|
|
29
|
+
|
|
30
|
+
## Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
agently-devtools start
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Default local address:
|
|
37
|
+
|
|
38
|
+
- Console: `http://127.0.0.1:15596/`
|
|
39
|
+
- Ingest: `http://127.0.0.1:15596/observation/ingest`
|
|
40
|
+
|
|
41
|
+
## Observation Bridge
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import os
|
|
45
|
+
|
|
46
|
+
from agently import Agently
|
|
47
|
+
from agently_devtools import ObservationBridge
|
|
48
|
+
|
|
49
|
+
bridge = ObservationBridge(
|
|
50
|
+
os.environ["AGENTLY_DEVTOOLS_INGEST_URL"],
|
|
51
|
+
app_id="your_app_id",
|
|
52
|
+
group_id="your_group_id",
|
|
53
|
+
)
|
|
54
|
+
bridge.register(Agently.event_center)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
TriggerFlow executions automatically publish their flow definition, so the execution graph can show the full static structure, including branches that have not been hit yet.
|
|
58
|
+
|
|
59
|
+
## Scenario Evaluations
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import os
|
|
63
|
+
|
|
64
|
+
from agently_devtools import EvaluationBridge, EvaluationCase, EvaluationRunner
|
|
65
|
+
|
|
66
|
+
bridge = EvaluationBridge(
|
|
67
|
+
base_url=os.environ["AGENTLY_DEVTOOLS_BASE_URL"],
|
|
68
|
+
app_id="your_app_id",
|
|
69
|
+
group_id="your_group_id",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
binding = bridge.bind_agent(
|
|
73
|
+
agent,
|
|
74
|
+
suite_id="your_suite_id",
|
|
75
|
+
target_name="your_target_name",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
report = EvaluationRunner(bridge=bridge).run(
|
|
79
|
+
binding,
|
|
80
|
+
cases=[EvaluationCase(case_id="case-1", input="your scenario")],
|
|
81
|
+
rounds=3,
|
|
82
|
+
)
|
|
83
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# agently-devtools
|
|
2
|
+
|
|
3
|
+
`agently-devtools` is the local observation, evaluation, and playground companion package for `agently`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -U agently agently-devtools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Python `>=3.10`
|
|
12
|
+
|
|
13
|
+
## Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
agently-devtools start
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Default local address:
|
|
20
|
+
|
|
21
|
+
- Console: `http://127.0.0.1:15596/`
|
|
22
|
+
- Ingest: `http://127.0.0.1:15596/observation/ingest`
|
|
23
|
+
|
|
24
|
+
## Observation Bridge
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
from agently import Agently
|
|
30
|
+
from agently_devtools import ObservationBridge
|
|
31
|
+
|
|
32
|
+
bridge = ObservationBridge(
|
|
33
|
+
os.environ["AGENTLY_DEVTOOLS_INGEST_URL"],
|
|
34
|
+
app_id="your_app_id",
|
|
35
|
+
group_id="your_group_id",
|
|
36
|
+
)
|
|
37
|
+
bridge.register(Agently.event_center)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
TriggerFlow executions automatically publish their flow definition, so the execution graph can show the full static structure, including branches that have not been hit yet.
|
|
41
|
+
|
|
42
|
+
## Scenario Evaluations
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import os
|
|
46
|
+
|
|
47
|
+
from agently_devtools import EvaluationBridge, EvaluationCase, EvaluationRunner
|
|
48
|
+
|
|
49
|
+
bridge = EvaluationBridge(
|
|
50
|
+
base_url=os.environ["AGENTLY_DEVTOOLS_BASE_URL"],
|
|
51
|
+
app_id="your_app_id",
|
|
52
|
+
group_id="your_group_id",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
binding = bridge.bind_agent(
|
|
56
|
+
agent,
|
|
57
|
+
suite_id="your_suite_id",
|
|
58
|
+
target_name="your_target_name",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
report = EvaluationRunner(bridge=bridge).run(
|
|
62
|
+
binding,
|
|
63
|
+
cases=[EvaluationCase(case_id="case-1", input="your scenario")],
|
|
64
|
+
rounds=3,
|
|
65
|
+
)
|
|
66
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .app import DEFAULT_LOCAL_API_KEY, LocalObservationBinding, create_local_observation_app
|
|
2
|
+
from .bridge import ObservationBridge
|
|
3
|
+
from .evaluations import (
|
|
4
|
+
EvaluationBinding,
|
|
5
|
+
EvaluationBridge,
|
|
6
|
+
EvaluationCase,
|
|
7
|
+
EvaluationCheckResult,
|
|
8
|
+
EvaluationReport,
|
|
9
|
+
EvaluationRoundRecord,
|
|
10
|
+
EvaluationRule,
|
|
11
|
+
EvaluationRunner,
|
|
12
|
+
EvaluationVariant,
|
|
13
|
+
)
|
|
14
|
+
from .logs import LogEventQuery, RuntimeLogService, RuntimeLogStats
|
|
15
|
+
from .observation import ObservationEventQuery, ObservationRunQuery, ObservationSubscriptionFilter, RunTreeNode
|
|
16
|
+
from .observation import (
|
|
17
|
+
RunRecord,
|
|
18
|
+
RuntimeObservationBackend,
|
|
19
|
+
RuntimeObservationService,
|
|
20
|
+
RuntimeObservationSubscription,
|
|
21
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
class RunContext:
|
|
7
|
+
run_id: str
|
|
8
|
+
root_run_id: str | None
|
|
9
|
+
parent_run_id: str | None
|
|
10
|
+
run_kind: str
|
|
11
|
+
agent_id: str | None
|
|
12
|
+
agent_name: str | None
|
|
13
|
+
session_id: str | None
|
|
14
|
+
response_id: str | None
|
|
15
|
+
execution_id: str | None
|
|
16
|
+
meta: dict[str, Any]
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def create(cls, *args: Any, **kwargs: Any) -> "RunContext": ...
|
|
20
|
+
|
|
21
|
+
def create_child(self, *args: Any, **kwargs: Any) -> "RunContext": ...
|
|
22
|
+
|
|
23
|
+
def model_copy(self, *args: Any, **kwargs: Any) -> "RunContext": ...
|
|
24
|
+
|
|
25
|
+
def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]: ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RuntimeEvent:
|
|
29
|
+
event_id: str
|
|
30
|
+
event_type: str
|
|
31
|
+
source: str
|
|
32
|
+
level: str
|
|
33
|
+
message: str | None
|
|
34
|
+
payload: Any
|
|
35
|
+
error: Any
|
|
36
|
+
run: RunContext | None
|
|
37
|
+
meta: dict[str, Any]
|
|
38
|
+
timestamp: int
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
|
41
|
+
|
|
42
|
+
def model_copy(self, *args: Any, **kwargs: Any) -> "RuntimeEvent": ...
|
|
43
|
+
|
|
44
|
+
def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]: ...
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def model_validate(cls, *args: Any, **kwargs: Any) -> "RuntimeEvent": ...
|
|
48
|
+
|
|
49
|
+
else:
|
|
50
|
+
try:
|
|
51
|
+
from agently.types.data import RunContext, RuntimeEvent
|
|
52
|
+
except ImportError: # Compatibility for published agently wheel variants.
|
|
53
|
+
from agently.types.data.event import RunContext, RuntimeEvent
|
|
54
|
+
|
|
55
|
+
__all__ = ["RunContext", "RuntimeEvent"]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
from agently import Agently
|
|
8
|
+
|
|
9
|
+
from ._agently_compat import RunContext
|
|
10
|
+
from .bridge import _create_observation_service_hooker
|
|
11
|
+
from .integrations import ObservationFastAPI
|
|
12
|
+
from .observation import RuntimeObservationService
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from agently.core import EventCenter
|
|
16
|
+
from agently.types.plugins import EventHooker
|
|
17
|
+
|
|
18
|
+
DEFAULT_LOCAL_API_KEY = ""
|
|
19
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
20
|
+
DEFAULT_PORT = 15596
|
|
21
|
+
DEFAULT_ROUTE_PREFIX = "/observation"
|
|
22
|
+
DEFAULT_LOG_ROOT = Path.home() / ".agently-devtools" / "logs"
|
|
23
|
+
_LOCAL_SERVER_LOCK = threading.Lock()
|
|
24
|
+
_LOCAL_SERVER_THREADS: dict[tuple[str, int, str], threading.Thread] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _ensure_local_auth(api_key: str | None):
|
|
28
|
+
if Agently.settings.get("observation.buffer_limit", None) is None:
|
|
29
|
+
Agently.set_settings("observation.buffer_limit", 1200)
|
|
30
|
+
if Agently.settings.get("observation.channel_buffer_limit", None) is None:
|
|
31
|
+
Agently.set_settings("observation.channel_buffer_limit", 160)
|
|
32
|
+
if Agently.settings.get("logs.enabled", None) is None:
|
|
33
|
+
Agently.set_settings("logs.enabled", True)
|
|
34
|
+
if Agently.settings.get("logs.storage.path", None) is None:
|
|
35
|
+
Agently.set_settings("logs.storage.path", str(DEFAULT_LOG_ROOT))
|
|
36
|
+
if Agently.settings.get("logs.storage.segment_event_limit", None) is None:
|
|
37
|
+
Agently.set_settings("logs.storage.segment_event_limit", 1000)
|
|
38
|
+
if isinstance(api_key, str) and api_key:
|
|
39
|
+
cast(Any, Agently).set_api_key(api_key)
|
|
40
|
+
Agently.set_settings("observation.api.auth.api_keys", [api_key])
|
|
41
|
+
else:
|
|
42
|
+
Agently.set_settings("observation.api.auth.api_keys", [])
|
|
43
|
+
Agently.set_settings("observation.api.auth.allow_query_api_key", True)
|
|
44
|
+
return Agently.settings
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def emit_startup_run(
|
|
48
|
+
*,
|
|
49
|
+
source: str = "AgentlyDevtoolsCLI",
|
|
50
|
+
message: str = "Observation service is ready.",
|
|
51
|
+
):
|
|
52
|
+
run = RunContext.create(
|
|
53
|
+
run_kind="request",
|
|
54
|
+
agent_name="agently-devtools",
|
|
55
|
+
meta={"component": "devtools"},
|
|
56
|
+
)
|
|
57
|
+
emitter = cast(Any, Agently.event_center).create_emitter(source)
|
|
58
|
+
await emitter.async_emit(
|
|
59
|
+
"observation.service_started",
|
|
60
|
+
message=message,
|
|
61
|
+
run=run,
|
|
62
|
+
payload={"component": "observation"},
|
|
63
|
+
)
|
|
64
|
+
await emitter.async_emit(
|
|
65
|
+
"observation.service_ready",
|
|
66
|
+
message="Observation console is listening for remote runtime uploads.",
|
|
67
|
+
run=run,
|
|
68
|
+
payload={"component": "observation"},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LocalObservationBinding:
|
|
73
|
+
def __init__(self, observation_service: RuntimeObservationService, hooker: type["EventHooker"]):
|
|
74
|
+
self.observation_service = observation_service
|
|
75
|
+
self._hooker = hooker
|
|
76
|
+
|
|
77
|
+
def unregister(self, event_center: "EventCenter"):
|
|
78
|
+
event_center.unregister_hooker_plugin(self._hooker)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def hooker(self):
|
|
83
|
+
return self._hooker
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def create_local_observation_app(
|
|
87
|
+
*,
|
|
88
|
+
api_key: str | None = DEFAULT_LOCAL_API_KEY,
|
|
89
|
+
route_prefix: str = DEFAULT_ROUTE_PREFIX,
|
|
90
|
+
title: str = "Agently DevTools",
|
|
91
|
+
fastapi_kwargs: dict[str, Any] | None = None,
|
|
92
|
+
register_event_sink: bool = True,
|
|
93
|
+
):
|
|
94
|
+
settings = _ensure_local_auth(api_key)
|
|
95
|
+
observation_service = RuntimeObservationService(settings)
|
|
96
|
+
hooker = _create_observation_service_hooker(observation_service, name="LocalObservationSinkHooker")
|
|
97
|
+
if register_event_sink:
|
|
98
|
+
Agently.event_center.register_hooker_plugin(hooker)
|
|
99
|
+
binding = LocalObservationBinding(observation_service, hooker)
|
|
100
|
+
kwargs = dict(fastapi_kwargs) if fastapi_kwargs is not None else {}
|
|
101
|
+
app = ObservationFastAPI(
|
|
102
|
+
observation_service=observation_service,
|
|
103
|
+
settings=settings,
|
|
104
|
+
api_keys=[api_key] if isinstance(api_key, str) and api_key else None,
|
|
105
|
+
route_prefix=route_prefix,
|
|
106
|
+
title=title,
|
|
107
|
+
**kwargs,
|
|
108
|
+
)
|
|
109
|
+
return app, binding
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_local_base_url(*, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT):
|
|
113
|
+
return f"http://{host}:{port}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build_observation_ingest_endpoint(*, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT, route_prefix: str = DEFAULT_ROUTE_PREFIX):
|
|
117
|
+
return f"{build_local_base_url(host=host, port=port)}{route_prefix}/ingest"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _is_local_base_url(base_url: str):
|
|
121
|
+
parsed = urlparse(base_url)
|
|
122
|
+
return parsed.scheme in {"http", "https"} and parsed.hostname in {"127.0.0.1", "localhost"}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _probe_local_listener(base_url: str, *, route_prefix: str = DEFAULT_ROUTE_PREFIX, api_key: str | None = None, timeout: float = 0.35):
|
|
126
|
+
try:
|
|
127
|
+
import httpx
|
|
128
|
+
|
|
129
|
+
headers = {"Authorization": f"Bearer {api_key}"} if api_key else None
|
|
130
|
+
response = httpx.get(f"{base_url}{route_prefix}/runs", headers=headers, timeout=timeout)
|
|
131
|
+
return response.status_code < 500
|
|
132
|
+
except Exception:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _serve_local_listener(*, host: str, port: int, api_key: str | None, route_prefix: str):
|
|
137
|
+
import uvicorn
|
|
138
|
+
|
|
139
|
+
app, _binding = create_local_observation_app(
|
|
140
|
+
api_key=api_key,
|
|
141
|
+
route_prefix=route_prefix,
|
|
142
|
+
register_event_sink=False,
|
|
143
|
+
)
|
|
144
|
+
config = uvicorn.Config(
|
|
145
|
+
app,
|
|
146
|
+
host=host,
|
|
147
|
+
port=port,
|
|
148
|
+
log_level="warning",
|
|
149
|
+
access_log=False,
|
|
150
|
+
)
|
|
151
|
+
server = uvicorn.Server(config)
|
|
152
|
+
cast(Any, server).install_signal_handlers = lambda: None
|
|
153
|
+
server.run()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def ensure_local_devtools_listener(
|
|
157
|
+
base_url: str,
|
|
158
|
+
*,
|
|
159
|
+
api_key: str | None = None,
|
|
160
|
+
route_prefix: str = DEFAULT_ROUTE_PREFIX,
|
|
161
|
+
wait_timeout: float = 5.0,
|
|
162
|
+
):
|
|
163
|
+
if not _is_local_base_url(base_url):
|
|
164
|
+
return False
|
|
165
|
+
if _probe_local_listener(base_url, route_prefix=route_prefix, api_key=api_key):
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
parsed = urlparse(base_url)
|
|
169
|
+
host = parsed.hostname or DEFAULT_HOST
|
|
170
|
+
port = parsed.port or DEFAULT_PORT
|
|
171
|
+
server_key = (host, port, route_prefix)
|
|
172
|
+
|
|
173
|
+
with _LOCAL_SERVER_LOCK:
|
|
174
|
+
if _probe_local_listener(base_url, route_prefix=route_prefix, api_key=api_key):
|
|
175
|
+
return False
|
|
176
|
+
thread = _LOCAL_SERVER_THREADS.get(server_key)
|
|
177
|
+
if thread is None or not thread.is_alive():
|
|
178
|
+
thread = threading.Thread(
|
|
179
|
+
target=_serve_local_listener,
|
|
180
|
+
kwargs={
|
|
181
|
+
"host": host,
|
|
182
|
+
"port": port,
|
|
183
|
+
"api_key": api_key,
|
|
184
|
+
"route_prefix": route_prefix,
|
|
185
|
+
},
|
|
186
|
+
name=f"AgentlyDevToolsListener-{host}:{port}",
|
|
187
|
+
daemon=True,
|
|
188
|
+
)
|
|
189
|
+
thread.start()
|
|
190
|
+
_LOCAL_SERVER_THREADS[server_key] = thread
|
|
191
|
+
|
|
192
|
+
deadline = time.time() + wait_timeout
|
|
193
|
+
while time.time() < deadline:
|
|
194
|
+
if _probe_local_listener(base_url, route_prefix=route_prefix, api_key=api_key):
|
|
195
|
+
return True
|
|
196
|
+
time.sleep(0.1)
|
|
197
|
+
raise RuntimeError(f"Timed out while starting local Agently DevTools listener at {base_url}.")
|