hud-python 0.4.57__py3-none-any.whl → 0.4.58__py3-none-any.whl
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.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/cli/__init__.py +20 -7
- hud/cli/dev.py +111 -1
- hud/cli/eval.py +2 -2
- hud/cli/flows/dev.py +5 -3
- hud/cli/init.py +14 -18
- hud/cli/push.py +2 -2
- hud/cli/rl/__init__.py +1 -1
- hud/cli/rl/celebrate.py +1 -1
- hud/cli/rl/remote_runner.py +3 -3
- hud/clients/base.py +1 -1
- hud/clients/fastmcp.py +1 -1
- hud/otel/config.py +1 -1
- hud/otel/context.py +2 -2
- hud/server/server.py +283 -36
- hud/shared/hints.py +3 -3
- hud/telemetry/job.py +2 -2
- hud/tools/playwright.py +8 -1
- hud/types.py +1 -1
- hud/utils/tests/test_version.py +1 -1
- hud/version.py +1 -1
- {hud_python-0.4.57.dist-info → hud_python-0.4.58.dist-info}/METADATA +1 -1
- {hud_python-0.4.57.dist-info → hud_python-0.4.58.dist-info}/RECORD +25 -25
- {hud_python-0.4.57.dist-info → hud_python-0.4.58.dist-info}/WHEEL +0 -0
- {hud_python-0.4.57.dist-info → hud_python-0.4.58.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.57.dist-info → hud_python-0.4.58.dist-info}/licenses/LICENSE +0 -0
hud/cli/__init__.py
CHANGED
|
@@ -253,10 +253,23 @@ def debug(
|
|
|
253
253
|
else:
|
|
254
254
|
# Assume it's an image name
|
|
255
255
|
image = first_param
|
|
256
|
-
from .utils.docker import
|
|
256
|
+
from .utils.docker import create_docker_run_command
|
|
257
|
+
|
|
258
|
+
# For image mode, check if there's a .env file in current directory
|
|
259
|
+
# and use it if available (similar to hud dev behavior)
|
|
260
|
+
cwd = Path.cwd()
|
|
261
|
+
if (cwd / ".env").exists():
|
|
262
|
+
# Use create_docker_run_command to load .env from current directory
|
|
263
|
+
command = create_docker_run_command(
|
|
264
|
+
image,
|
|
265
|
+
docker_args=docker_args,
|
|
266
|
+
env_dir=cwd, # Load .env from current directory
|
|
267
|
+
)
|
|
268
|
+
else:
|
|
269
|
+
# No .env file, use basic command without env loading
|
|
270
|
+
from .utils.docker import build_run_command
|
|
257
271
|
|
|
258
|
-
|
|
259
|
-
command = build_run_command(image, docker_args)
|
|
272
|
+
command = build_run_command(image, docker_args)
|
|
260
273
|
else:
|
|
261
274
|
console.print(
|
|
262
275
|
"[red]Error: Must specify a directory, Docker image, --config, or --cursor[/red]"
|
|
@@ -741,14 +754,14 @@ def remove(
|
|
|
741
754
|
|
|
742
755
|
@app.command()
|
|
743
756
|
def init(
|
|
744
|
-
name: str = typer.Argument(None, help="Environment name (default:
|
|
757
|
+
name: str = typer.Argument(None, help="Environment name (default: chosen preset name)"),
|
|
745
758
|
preset: str | None = typer.Option(
|
|
746
759
|
None,
|
|
747
760
|
"--preset",
|
|
748
761
|
"-p",
|
|
749
762
|
help="Preset to use: blank, deep-research, browser, rubrics. If omitted, you'll choose interactively.", # noqa: E501
|
|
750
763
|
),
|
|
751
|
-
directory: str = typer.Option(".", "--dir", "-d", help="
|
|
764
|
+
directory: str = typer.Option(".", "--dir", "-d", help="Parent directory for the environment"),
|
|
752
765
|
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
|
|
753
766
|
) -> None:
|
|
754
767
|
"""🚀 Initialize a new HUD environment with minimal boilerplate.
|
|
@@ -760,8 +773,8 @@ def init(
|
|
|
760
773
|
- Required setup/evaluate tools
|
|
761
774
|
|
|
762
775
|
Examples:
|
|
763
|
-
hud init #
|
|
764
|
-
hud init my-env # Create
|
|
776
|
+
hud init # Choose preset interactively, create ./preset-name/
|
|
777
|
+
hud init my-env # Create new directory ./my-env/
|
|
765
778
|
hud init my-env --dir /tmp # Create in /tmp/my-env/
|
|
766
779
|
"""
|
|
767
780
|
create_environment(name, directory, force, preset)
|
hud/cli/dev.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import contextlib
|
|
6
7
|
import importlib
|
|
7
8
|
import importlib.util
|
|
8
9
|
import logging
|
|
@@ -13,6 +14,8 @@ import threading
|
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
17
|
+
import typer
|
|
18
|
+
|
|
16
19
|
from hud.utils.hud_console import HUDConsole
|
|
17
20
|
|
|
18
21
|
hud_console = HUDConsole()
|
|
@@ -26,6 +29,7 @@ def show_dev_server_info(
|
|
|
26
29
|
interactive: bool,
|
|
27
30
|
env_dir: Path | None = None,
|
|
28
31
|
new: bool = False,
|
|
32
|
+
docker_mode: bool = False,
|
|
29
33
|
) -> str:
|
|
30
34
|
"""Show consistent server info for both Python and Docker modes.
|
|
31
35
|
|
|
@@ -54,7 +58,15 @@ def show_dev_server_info(
|
|
|
54
58
|
if transport == "http":
|
|
55
59
|
hud_console.section_title("Quick Links")
|
|
56
60
|
hud_console.info(f"{hud_console.sym.ITEM} Docs: http://localhost:{port}/docs")
|
|
57
|
-
hud_console.info(f"{hud_console.sym.ITEM} Cursor:
|
|
61
|
+
hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
|
|
62
|
+
# Display the Cursor link on its own line to prevent wrapping
|
|
63
|
+
hud_console.link(cursor_deeplink)
|
|
64
|
+
|
|
65
|
+
# Show eval endpoint if in Docker mode
|
|
66
|
+
if docker_mode:
|
|
67
|
+
hud_console.info(
|
|
68
|
+
f"{hud_console.sym.ITEM} Eval API: http://localhost:{port}/eval (POST)"
|
|
69
|
+
)
|
|
58
70
|
|
|
59
71
|
# Check for VNC (browser environment)
|
|
60
72
|
if env_dir and (env_dir / "environment" / "server.py").exists():
|
|
@@ -510,6 +522,9 @@ def run_docker_dev_server(
|
|
|
510
522
|
new: bool = False,
|
|
511
523
|
) -> None:
|
|
512
524
|
"""Run MCP server in Docker with volume mounts, expose via local HTTP proxy."""
|
|
525
|
+
import atexit
|
|
526
|
+
import signal
|
|
527
|
+
|
|
513
528
|
import typer
|
|
514
529
|
import yaml
|
|
515
530
|
|
|
@@ -522,6 +537,69 @@ def run_docker_dev_server(
|
|
|
522
537
|
|
|
523
538
|
cwd = Path.cwd()
|
|
524
539
|
|
|
540
|
+
# Container name will be set later and used for cleanup
|
|
541
|
+
container_name: str | None = None
|
|
542
|
+
cleanup_done = False
|
|
543
|
+
|
|
544
|
+
def cleanup_container() -> None:
|
|
545
|
+
"""Clean up Docker container on exit."""
|
|
546
|
+
nonlocal cleanup_done
|
|
547
|
+
if cleanup_done or not container_name:
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
cleanup_done = True
|
|
551
|
+
hud_console.debug(f"Cleaning up container: {container_name}")
|
|
552
|
+
|
|
553
|
+
# Check if container is still running
|
|
554
|
+
try:
|
|
555
|
+
result = subprocess.run( # noqa: S603
|
|
556
|
+
["docker", "ps", "-q", "-f", f"name={container_name}"], # noqa: S607
|
|
557
|
+
stdout=subprocess.PIPE,
|
|
558
|
+
stderr=subprocess.DEVNULL,
|
|
559
|
+
text=True,
|
|
560
|
+
timeout=5,
|
|
561
|
+
)
|
|
562
|
+
if not result.stdout.strip():
|
|
563
|
+
# Container is not running, just try to remove it
|
|
564
|
+
subprocess.run( # noqa: S603
|
|
565
|
+
["docker", "rm", "-f", container_name], # noqa: S607
|
|
566
|
+
stdout=subprocess.DEVNULL,
|
|
567
|
+
stderr=subprocess.DEVNULL,
|
|
568
|
+
timeout=5,
|
|
569
|
+
)
|
|
570
|
+
return
|
|
571
|
+
except Exception: # noqa: S110
|
|
572
|
+
pass
|
|
573
|
+
|
|
574
|
+
try:
|
|
575
|
+
# First try to stop gracefully
|
|
576
|
+
subprocess.run( # noqa: S603
|
|
577
|
+
["docker", "stop", container_name], # noqa: S607
|
|
578
|
+
stdout=subprocess.DEVNULL,
|
|
579
|
+
stderr=subprocess.DEVNULL,
|
|
580
|
+
timeout=10,
|
|
581
|
+
)
|
|
582
|
+
hud_console.debug(f"Container {container_name} stopped successfully")
|
|
583
|
+
except subprocess.TimeoutExpired:
|
|
584
|
+
# Force kill if stop times out
|
|
585
|
+
hud_console.debug(f"Container {container_name} stop timeout, forcing kill")
|
|
586
|
+
with contextlib.suppress(Exception):
|
|
587
|
+
subprocess.run( # noqa: S603
|
|
588
|
+
["docker", "kill", container_name], # noqa: S607
|
|
589
|
+
stdout=subprocess.DEVNULL,
|
|
590
|
+
stderr=subprocess.DEVNULL,
|
|
591
|
+
timeout=5,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Set up signal handlers for cleanup
|
|
595
|
+
def signal_handler(signum: int, frame: Any) -> None:
|
|
596
|
+
cleanup_container()
|
|
597
|
+
sys.exit(0)
|
|
598
|
+
|
|
599
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
600
|
+
if sys.platform != "win32":
|
|
601
|
+
signal.signal(signal.SIGHUP, signal_handler)
|
|
602
|
+
|
|
525
603
|
# Find environment directory (current or parent with hud.lock.yaml)
|
|
526
604
|
env_dir = cwd
|
|
527
605
|
lock_path = env_dir / "hud.lock.yaml"
|
|
@@ -562,10 +640,14 @@ def run_docker_dev_server(
|
|
|
562
640
|
base_name = image_name.replace(":", "-").replace("/", "-")
|
|
563
641
|
container_name = f"{base_name}-dev-{pid}"
|
|
564
642
|
|
|
643
|
+
# Register cleanup function with atexit
|
|
644
|
+
atexit.register(cleanup_container)
|
|
645
|
+
|
|
565
646
|
# Build docker run command with volume mounts and folder-mode envs
|
|
566
647
|
from .utils.docker import create_docker_run_command
|
|
567
648
|
|
|
568
649
|
base_args = [
|
|
650
|
+
"--rm", # Automatically remove container when it stops
|
|
569
651
|
"--name",
|
|
570
652
|
container_name,
|
|
571
653
|
"-v",
|
|
@@ -643,6 +725,7 @@ def run_docker_dev_server(
|
|
|
643
725
|
interactive=interactive,
|
|
644
726
|
env_dir=env_dir,
|
|
645
727
|
new=new,
|
|
728
|
+
docker_mode=True,
|
|
646
729
|
)
|
|
647
730
|
hud_console.dim_info(
|
|
648
731
|
"",
|
|
@@ -679,6 +762,11 @@ def run_docker_dev_server(
|
|
|
679
762
|
os.environ["_HUD_DEV_DOCKER_CONTAINER"] = container_name
|
|
680
763
|
hud_console.debug(f"Docker container: {container_name}")
|
|
681
764
|
|
|
765
|
+
# Store the docker mcp_config for the eval endpoint
|
|
766
|
+
import json
|
|
767
|
+
|
|
768
|
+
os.environ["_HUD_DEV_DOCKER_MCP_CONFIG"] = json.dumps(mcp_config)
|
|
769
|
+
|
|
682
770
|
# Create FastMCP proxy using the ProxyClient
|
|
683
771
|
fastmcp_proxy = FastMCP.as_proxy(proxy_client)
|
|
684
772
|
|
|
@@ -713,7 +801,15 @@ def run_docker_dev_server(
|
|
|
713
801
|
asyncio.run(run_proxy())
|
|
714
802
|
except KeyboardInterrupt:
|
|
715
803
|
hud_console.info("\n\nStopping...")
|
|
804
|
+
cleanup_container()
|
|
716
805
|
raise typer.Exit(0) from None
|
|
806
|
+
except Exception:
|
|
807
|
+
# Ensure cleanup happens on any exception
|
|
808
|
+
cleanup_container()
|
|
809
|
+
raise
|
|
810
|
+
finally:
|
|
811
|
+
# Final cleanup attempt
|
|
812
|
+
cleanup_container()
|
|
717
813
|
|
|
718
814
|
|
|
719
815
|
def run_mcp_dev_server(
|
|
@@ -732,6 +828,20 @@ def run_mcp_dev_server(
|
|
|
732
828
|
docker_args = docker_args or []
|
|
733
829
|
cwd = Path.cwd()
|
|
734
830
|
|
|
831
|
+
# Find an available port if not using stdio transport
|
|
832
|
+
if not stdio:
|
|
833
|
+
from hud.cli.utils.logging import find_free_port
|
|
834
|
+
|
|
835
|
+
actual_port = find_free_port(port)
|
|
836
|
+
if actual_port is None:
|
|
837
|
+
hud_console.error(f"No available ports found starting from {port}")
|
|
838
|
+
raise typer.Exit(1)
|
|
839
|
+
|
|
840
|
+
if actual_port != port:
|
|
841
|
+
hud_console.info(f"Port {port} is in use, using port {actual_port} instead")
|
|
842
|
+
|
|
843
|
+
port = actual_port
|
|
844
|
+
|
|
735
845
|
# Auto-detect Docker mode if Dockerfile present and no module specified
|
|
736
846
|
if not docker and module is None and should_use_docker_mode(cwd):
|
|
737
847
|
hud_console.note("Detected Dockerfile - using Docker mode with volume mounts")
|
hud/cli/eval.py
CHANGED
|
@@ -53,7 +53,7 @@ def get_available_models() -> list[dict[str, str | None]]:
|
|
|
53
53
|
try:
|
|
54
54
|
from hud.cli.rl import rl_api
|
|
55
55
|
|
|
56
|
-
hud_console.info("Fetching your models from https://hud.
|
|
56
|
+
hud_console.info("Fetching your models from https://hud.ai/models")
|
|
57
57
|
models = rl_api.list_models()
|
|
58
58
|
|
|
59
59
|
# Filter for ready models only and sort by recency
|
|
@@ -771,7 +771,7 @@ def eval_command(
|
|
|
771
771
|
# Check for HUD_API_KEY if using HUD services
|
|
772
772
|
if not settings.api_key:
|
|
773
773
|
hud_console.warning("HUD_API_KEY not set. Some features may be limited.")
|
|
774
|
-
hud_console.info("Get your API key at: https://hud.
|
|
774
|
+
hud_console.info("Get your API key at: https://hud.ai")
|
|
775
775
|
hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
|
|
776
776
|
|
|
777
777
|
# Parse allowed tools
|
hud/cli/flows/dev.py
CHANGED
|
@@ -47,11 +47,11 @@ async def create_dynamic_trace(
|
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
49
|
resp = await make_request("POST", url=url, json=payload, api_key=api_key)
|
|
50
|
-
# New API returns an id; construct the URL as https://hud.
|
|
50
|
+
# New API returns an id; construct the URL as https://hud.ai/trace/{id}
|
|
51
51
|
trace_id = resp.get("id")
|
|
52
52
|
|
|
53
53
|
if isinstance(trace_id, str) and trace_id:
|
|
54
|
-
return trace_id, f"https://hud.
|
|
54
|
+
return trace_id, f"https://hud.ai/trace/{trace_id}"
|
|
55
55
|
return None, None
|
|
56
56
|
except Exception as e:
|
|
57
57
|
# Do not interrupt dev flow
|
|
@@ -114,7 +114,9 @@ def show_dev_ui(
|
|
|
114
114
|
label = "Base image" if is_docker else "Server"
|
|
115
115
|
hud_console.info("")
|
|
116
116
|
hud_console.info(f"{hud_console.sym.ITEM} {label}: {server_name}")
|
|
117
|
-
hud_console.info(f"{hud_console.sym.ITEM} Cursor:
|
|
117
|
+
hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
|
|
118
|
+
# Display the Cursor link on its own line to prevent wrapping
|
|
119
|
+
hud_console.link(cursor_deeplink)
|
|
118
120
|
hud_console.info("")
|
|
119
121
|
hud_console.info(f"{hud_console.sym.SUCCESS} Hot-reload enabled")
|
|
120
122
|
if is_docker:
|
hud/cli/init.py
CHANGED
|
@@ -182,17 +182,17 @@ def create_environment(
|
|
|
182
182
|
|
|
183
183
|
hud_console = HUDConsole()
|
|
184
184
|
|
|
185
|
-
# Determine environment name/target directory
|
|
186
|
-
if name is None:
|
|
187
|
-
current_dir = Path.cwd()
|
|
188
|
-
name = current_dir.name
|
|
189
|
-
target_dir = current_dir
|
|
190
|
-
hud_console.info(f"Using current directory name: {name}")
|
|
191
|
-
else:
|
|
192
|
-
target_dir = Path(directory) / name
|
|
193
|
-
|
|
194
185
|
# Choose preset
|
|
195
186
|
preset_normalized = (preset or "").strip().lower() if preset else _prompt_for_preset()
|
|
187
|
+
|
|
188
|
+
# If no name is provided, use the preset name as the environment name
|
|
189
|
+
if name is None:
|
|
190
|
+
name = preset_normalized
|
|
191
|
+
hud_console.info(f"Using preset name as environment name: {name}")
|
|
192
|
+
|
|
193
|
+
# Always create a new directory based on the name
|
|
194
|
+
target_dir = Path.cwd() / name if directory == "." else Path(directory) / name
|
|
195
|
+
|
|
196
196
|
if preset_normalized not in PRESET_MAP:
|
|
197
197
|
hud_console.warning(
|
|
198
198
|
f"Unknown preset '{preset_normalized}', defaulting to 'blank' "
|
|
@@ -263,14 +263,10 @@ def create_environment(
|
|
|
263
263
|
hud_console.status_item(entry, "added")
|
|
264
264
|
|
|
265
265
|
hud_console.section_title("Next steps")
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
hud_console.command_example(f"cd {target_dir}")
|
|
272
|
-
hud_console.info("\n2. Start development server (with MCP inspector):")
|
|
273
|
-
hud_console.command_example("hud dev --inspector")
|
|
274
|
-
|
|
266
|
+
# Since we now almost always create a new directory, show cd command
|
|
267
|
+
hud_console.info("1. Enter the directory:")
|
|
268
|
+
hud_console.command_example(f"cd {target_dir.name}")
|
|
269
|
+
hud_console.info("\n2. Start development server (with MCP inspector):")
|
|
270
|
+
hud_console.command_example("hud dev --inspector")
|
|
275
271
|
hud_console.info("\n3. Review the README in this preset for specific instructions.")
|
|
276
272
|
hud_console.info("\n4. Customize as needed.")
|
hud/cli/push.py
CHANGED
|
@@ -152,7 +152,7 @@ def push_environment(
|
|
|
152
152
|
hud_console.error("No HUD API key found")
|
|
153
153
|
hud_console.warning("A HUD API key is required to push environments.")
|
|
154
154
|
hud_console.info("\nTo get started:")
|
|
155
|
-
hud_console.info("1. Get your API key at: https://hud.
|
|
155
|
+
hud_console.info("1. Get your API key at: https://hud.ai/settings")
|
|
156
156
|
hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
|
|
157
157
|
hud_console.command_example("hud push", "Try again")
|
|
158
158
|
hud_console.info("")
|
|
@@ -440,7 +440,7 @@ def push_environment(
|
|
|
440
440
|
elif response.status_code == 401:
|
|
441
441
|
hud_console.error("Authentication failed")
|
|
442
442
|
hud_console.info("Check your HUD_API_KEY is valid")
|
|
443
|
-
hud_console.info("Get a new key at: https://hud.
|
|
443
|
+
hud_console.info("Get a new key at: https://hud.ai/settings")
|
|
444
444
|
hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
|
|
445
445
|
elif response.status_code == 403:
|
|
446
446
|
hud_console.error("Permission denied")
|
hud/cli/rl/__init__.py
CHANGED
|
@@ -25,7 +25,7 @@ def rl_command(
|
|
|
25
25
|
),
|
|
26
26
|
model: str | None = typer.Argument(
|
|
27
27
|
None,
|
|
28
|
-
help="Model to train from https://hud.
|
|
28
|
+
help="Model to train from https://hud.ai/models (default: interactive selection)",
|
|
29
29
|
),
|
|
30
30
|
config_file: Path | None = typer.Option( # noqa: B008
|
|
31
31
|
None,
|
hud/cli/rl/celebrate.py
CHANGED
|
@@ -133,7 +133,7 @@ def show_confetti(console: Console, seconds: float = 2.5) -> None:
|
|
|
133
133
|
"""
|
|
134
134
|
# Show celebratory message first
|
|
135
135
|
console.print(
|
|
136
|
-
"[bold green]🎉 Starting training! See your model on https://hud.
|
|
136
|
+
"[bold green]🎉 Starting training! See your model on https://hud.ai/models[/bold green]"
|
|
137
137
|
)
|
|
138
138
|
time.sleep(0.3) # Brief pause to see the message
|
|
139
139
|
|
hud/cli/rl/remote_runner.py
CHANGED
|
@@ -55,7 +55,7 @@ def ensure_vllm_deployed(
|
|
|
55
55
|
hud_console.info("Waiting for vLLM server to be ready...")
|
|
56
56
|
start_time = time.time()
|
|
57
57
|
with hud_console.progress() as progress:
|
|
58
|
-
progress.update("Checking deployment status (see live status on https://hud.
|
|
58
|
+
progress.update("Checking deployment status (see live status on https://hud.ai/models)")
|
|
59
59
|
while True:
|
|
60
60
|
if time.time() - start_time > timeout:
|
|
61
61
|
hud_console.error("Timeout waiting for vLLM deployment")
|
|
@@ -139,7 +139,7 @@ def run_remote_training(
|
|
|
139
139
|
hud_console.section_title("Model Selection")
|
|
140
140
|
|
|
141
141
|
# Fetch existing models
|
|
142
|
-
hud_console.info("Fetching your models from https://hud.
|
|
142
|
+
hud_console.info("Fetching your models from https://hud.ai/models")
|
|
143
143
|
|
|
144
144
|
try:
|
|
145
145
|
models = rl_api.list_models()
|
|
@@ -312,7 +312,7 @@ def run_remote_training(
|
|
|
312
312
|
# gpu_table.add_column("Price/hr", style="yellow")
|
|
313
313
|
|
|
314
314
|
# for gpu, info in GPU_PRICING.items():
|
|
315
|
-
# gpu_table.add_row(gpu, info["memory"], "see pricing on hud.
|
|
315
|
+
# gpu_table.add_row(gpu, info["memory"], "see pricing on hud.ai")
|
|
316
316
|
|
|
317
317
|
# console.print(gpu_table)
|
|
318
318
|
|
hud/clients/base.py
CHANGED
|
@@ -140,7 +140,7 @@ class BaseHUDClient(AgentMCPClient):
|
|
|
140
140
|
raise HudAuthenticationError(
|
|
141
141
|
f'Sending authorization "{headers.get("Authorization", "")}", which may'
|
|
142
142
|
" be incomplete. Ensure HUD_API_KEY environment variable is set or send it"
|
|
143
|
-
" as a header. You can get an API key at https://hud.
|
|
143
|
+
" as a header. You can get an API key at https://hud.ai"
|
|
144
144
|
)
|
|
145
145
|
# Subclasses implement connection
|
|
146
146
|
await self._connect(self._mcp_config)
|
hud/clients/fastmcp.py
CHANGED
|
@@ -95,7 +95,7 @@ class FastMCPHUDClient(BaseHUDClient):
|
|
|
95
95
|
raise RuntimeError(
|
|
96
96
|
"Authentication failed for HUD API. "
|
|
97
97
|
"Please ensure your HUD_API_KEY environment variable is set correctly." # noqa: E501
|
|
98
|
-
"You can get an API key at https://hud.
|
|
98
|
+
"You can get an API key at https://hud.ai"
|
|
99
99
|
) from e
|
|
100
100
|
# Generic 401 error
|
|
101
101
|
raise RuntimeError(
|
hud/otel/config.py
CHANGED
|
@@ -113,7 +113,7 @@ def configure_telemetry(
|
|
|
113
113
|
# Error if no exporters are configured
|
|
114
114
|
raise ValueError(
|
|
115
115
|
"No telemetry backend configured. Either:\n"
|
|
116
|
-
"1. Set HUD_API_KEY environment variable for HUD telemetry (https://hud.
|
|
116
|
+
"1. Set HUD_API_KEY environment variable for HUD telemetry (https://hud.ai)\n"
|
|
117
117
|
"2. Use enable_otlp=True with configure_telemetry() for alternative backends (e.g., Jaeger)\n" # noqa: E501
|
|
118
118
|
)
|
|
119
119
|
elif not settings.telemetry_enabled:
|
hud/otel/context.py
CHANGED
|
@@ -408,7 +408,7 @@ def _print_trace_url(task_run_id: str) -> None:
|
|
|
408
408
|
if not (settings.telemetry_enabled and settings.api_key):
|
|
409
409
|
return
|
|
410
410
|
|
|
411
|
-
url = f"https://hud.
|
|
411
|
+
url = f"https://hud.ai/trace/{task_run_id}"
|
|
412
412
|
header = "🚀 See your agent live at:"
|
|
413
413
|
|
|
414
414
|
# ANSI color codes
|
|
@@ -447,7 +447,7 @@ def _print_trace_complete_url(task_run_id: str, error_occurred: bool = False) ->
|
|
|
447
447
|
if not (settings.telemetry_enabled and settings.api_key):
|
|
448
448
|
return
|
|
449
449
|
|
|
450
|
-
url = f"https://hud.
|
|
450
|
+
url = f"https://hud.ai/trace/{task_run_id}"
|
|
451
451
|
|
|
452
452
|
# ANSI color codes
|
|
453
453
|
GREEN = "\033[92m"
|
hud/server/server.py
CHANGED
|
@@ -16,7 +16,9 @@ from fastmcp.server.server import FastMCP, Transport
|
|
|
16
16
|
from starlette.requests import Request
|
|
17
17
|
from starlette.responses import JSONResponse, Response
|
|
18
18
|
|
|
19
|
+
from hud.cli.eval import run_full_dataset
|
|
19
20
|
from hud.server.low_level import LowLevelServerWithInit
|
|
21
|
+
from hud.types import Task
|
|
20
22
|
|
|
21
23
|
if TYPE_CHECKING:
|
|
22
24
|
from collections.abc import AsyncGenerator, Callable
|
|
@@ -486,6 +488,84 @@ class MCPServer(FastMCP):
|
|
|
486
488
|
self._prompt_manager._prompts[new_key] = prompt
|
|
487
489
|
# await self.import_server(hidden_router, prefix=None, **kwargs)
|
|
488
490
|
|
|
491
|
+
def _get_docker_logs(
|
|
492
|
+
self,
|
|
493
|
+
tail: int = 100,
|
|
494
|
+
since: str | None = None,
|
|
495
|
+
until: str | None = None,
|
|
496
|
+
timestamps: bool = False,
|
|
497
|
+
) -> dict[str, Any]:
|
|
498
|
+
"""Helper function to get Docker container logs.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
tail: Number of lines to show from the end of the logs
|
|
502
|
+
since: Show logs since timestamp or relative time
|
|
503
|
+
until: Show logs before a timestamp or relative time
|
|
504
|
+
timestamps: Show timestamps in log output
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
Dictionary with logs data or error information
|
|
508
|
+
"""
|
|
509
|
+
import subprocess
|
|
510
|
+
|
|
511
|
+
container_name = os.environ.get("_HUD_DEV_DOCKER_CONTAINER")
|
|
512
|
+
if not container_name:
|
|
513
|
+
return {"items": [], "container_name": None, "error": "No container name found"}
|
|
514
|
+
|
|
515
|
+
# Build docker logs command
|
|
516
|
+
cmd = ["docker", "logs", "--tail", str(tail)]
|
|
517
|
+
|
|
518
|
+
if since:
|
|
519
|
+
cmd.extend(["--since", since])
|
|
520
|
+
if until:
|
|
521
|
+
cmd.extend(["--until", until])
|
|
522
|
+
if timestamps:
|
|
523
|
+
cmd.append("--timestamps")
|
|
524
|
+
|
|
525
|
+
cmd.append(container_name)
|
|
526
|
+
|
|
527
|
+
try:
|
|
528
|
+
# Run docker logs to get output
|
|
529
|
+
result = subprocess.run( # noqa: S603
|
|
530
|
+
cmd,
|
|
531
|
+
stdout=subprocess.PIPE,
|
|
532
|
+
stderr=subprocess.STDOUT,
|
|
533
|
+
text=True,
|
|
534
|
+
encoding="utf-8",
|
|
535
|
+
errors="replace",
|
|
536
|
+
timeout=5,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
# Parse logs into items
|
|
540
|
+
items = []
|
|
541
|
+
lines = result.stdout.strip().split("\n") if result.stdout else []
|
|
542
|
+
|
|
543
|
+
for i, line in enumerate(lines):
|
|
544
|
+
if line.strip():
|
|
545
|
+
items.append(
|
|
546
|
+
{
|
|
547
|
+
"id": i,
|
|
548
|
+
"stream": "mixed",
|
|
549
|
+
"log": line,
|
|
550
|
+
"container_name": container_name,
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
"items": items,
|
|
556
|
+
"container_name": container_name,
|
|
557
|
+
"total_lines": len(items),
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
except subprocess.TimeoutExpired:
|
|
561
|
+
return {"error": "Docker logs timeout", "container_name": container_name, "items": []}
|
|
562
|
+
except Exception as e:
|
|
563
|
+
return {
|
|
564
|
+
"error": f"Failed to get logs: {e!s}",
|
|
565
|
+
"container_name": container_name,
|
|
566
|
+
"items": [],
|
|
567
|
+
}
|
|
568
|
+
|
|
489
569
|
def _register_hud_helpers(self) -> None:
|
|
490
570
|
"""Register development helper endpoints.
|
|
491
571
|
|
|
@@ -494,6 +574,7 @@ class MCPServer(FastMCP):
|
|
|
494
574
|
- POST /api/tools/{name} - REST wrappers for MCP tools
|
|
495
575
|
- GET /openapi.json - OpenAPI spec for REST endpoints
|
|
496
576
|
- GET /logs - Development log endpoint (when provided by dev runtime)
|
|
577
|
+
- hud-logs tool - MCP tool for fetching logs (when in Docker mode)
|
|
497
578
|
"""
|
|
498
579
|
|
|
499
580
|
# Register REST wrapper for each tool
|
|
@@ -544,7 +625,7 @@ class MCPServer(FastMCP):
|
|
|
544
625
|
endpoint = create_tool_endpoint(tool_key)
|
|
545
626
|
self.custom_route(f"/api/tools/{tool_key}", methods=["POST"])(endpoint)
|
|
546
627
|
|
|
547
|
-
# Development
|
|
628
|
+
# Development endpoints - only if dev runtime set a provider
|
|
548
629
|
provider = os.environ.get("_HUD_DEV_LOGS_PROVIDER")
|
|
549
630
|
if provider == "enabled":
|
|
550
631
|
|
|
@@ -556,50 +637,182 @@ class MCPServer(FastMCP):
|
|
|
556
637
|
- limit: max number of lines to return (default 100)
|
|
557
638
|
- tail: number of lines from end to return (default 100)
|
|
558
639
|
"""
|
|
559
|
-
import subprocess
|
|
560
|
-
|
|
561
|
-
# Get container name from environment
|
|
562
|
-
container_name = os.environ.get("_HUD_DEV_DOCKER_CONTAINER")
|
|
563
|
-
if not container_name:
|
|
564
|
-
return JSONResponse({"items": [], "next": None})
|
|
565
|
-
|
|
566
640
|
# Get query params
|
|
567
641
|
params = request.query_params
|
|
568
|
-
tail = params.get("tail", "100")
|
|
642
|
+
tail = int(params.get("tail", "100"))
|
|
643
|
+
|
|
644
|
+
# Use helper function to get logs
|
|
645
|
+
result = self._get_docker_logs(tail=tail)
|
|
646
|
+
|
|
647
|
+
# Add 'next' field for compatibility with existing API
|
|
648
|
+
if "error" in result:
|
|
649
|
+
return JSONResponse(result, status_code=500)
|
|
650
|
+
else:
|
|
651
|
+
items = result.get("items", [])
|
|
652
|
+
return JSONResponse(
|
|
653
|
+
{
|
|
654
|
+
"items": items,
|
|
655
|
+
"next": len(items) - 1 if items else None,
|
|
656
|
+
}
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# Import existing types from the codebase
|
|
660
|
+
from pydantic import BaseModel
|
|
661
|
+
|
|
662
|
+
from hud.types import AgentType
|
|
663
|
+
|
|
664
|
+
class EvalRequest(BaseModel):
|
|
665
|
+
"""Request model for /eval endpoint."""
|
|
666
|
+
|
|
667
|
+
tasks: list[dict[str, Any]] = []
|
|
668
|
+
agent: str = "claude"
|
|
669
|
+
model: str | None = None
|
|
670
|
+
max_steps: int = 10
|
|
671
|
+
verbose: bool = False
|
|
672
|
+
group_size: int = 1
|
|
673
|
+
name: str | None = None
|
|
674
|
+
|
|
675
|
+
@self.custom_route("/eval", methods=["POST"])
|
|
676
|
+
async def run_eval(request: Request) -> Response:
|
|
677
|
+
"""Run evaluation on tasks using the current Docker environment."""
|
|
678
|
+
import asyncio
|
|
679
|
+
import json
|
|
569
680
|
|
|
570
681
|
try:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
682
|
+
body = await request.body()
|
|
683
|
+
data = json.loads(body)
|
|
684
|
+
|
|
685
|
+
# Validate request using Pydantic model
|
|
686
|
+
try:
|
|
687
|
+
eval_request = EvalRequest(**data)
|
|
688
|
+
except Exception as e:
|
|
689
|
+
return JSONResponse({"error": f"Invalid request: {e!s}"}, status_code=400)
|
|
690
|
+
|
|
691
|
+
# Get the Docker MCP config from environment
|
|
692
|
+
docker_mcp_config = os.environ.get("_HUD_DEV_DOCKER_MCP_CONFIG")
|
|
693
|
+
if not docker_mcp_config:
|
|
694
|
+
return JSONResponse(
|
|
695
|
+
{"error": "Docker MCP config not available"}, status_code=500
|
|
696
|
+
)
|
|
581
697
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
698
|
+
docker_config = json.loads(docker_mcp_config)
|
|
699
|
+
|
|
700
|
+
# Simplify Docker config for evaluation
|
|
701
|
+
if "docker" in docker_config and "args" in docker_config["docker"]:
|
|
702
|
+
original_args = docker_config["docker"]["args"]
|
|
703
|
+
filtered_args = []
|
|
704
|
+
i = 0
|
|
705
|
+
|
|
706
|
+
while i < len(original_args):
|
|
707
|
+
arg = original_args[i]
|
|
708
|
+
|
|
709
|
+
# Skip volume mounts and their values
|
|
710
|
+
if arg in ["-v", "--volume"]:
|
|
711
|
+
i += 2 # Skip the flag and its value
|
|
712
|
+
continue
|
|
713
|
+
|
|
714
|
+
# Skip combined volume mount args
|
|
715
|
+
if arg.startswith(("-v", "--volume=")):
|
|
716
|
+
i += 1
|
|
717
|
+
continue
|
|
718
|
+
|
|
719
|
+
# Skip explicit container name to avoid collisions
|
|
720
|
+
if arg == "--name" and i + 1 < len(original_args):
|
|
721
|
+
i += 2 # Skip the --name and its value
|
|
722
|
+
continue
|
|
723
|
+
|
|
724
|
+
# Skip dev-specific environment variables
|
|
725
|
+
if arg == "-e" and i + 1 < len(original_args):
|
|
726
|
+
next_arg = original_args[i + 1]
|
|
727
|
+
if next_arg in [
|
|
728
|
+
"PYTHONPATH=/app",
|
|
729
|
+
"HUD_DEV=1",
|
|
730
|
+
"PYTHONUNBUFFERED=1",
|
|
731
|
+
]:
|
|
732
|
+
i += 2 # Skip the -e and its value
|
|
733
|
+
continue
|
|
734
|
+
|
|
735
|
+
filtered_args.append(arg)
|
|
736
|
+
i += 1
|
|
737
|
+
|
|
738
|
+
# Update the docker args with filtered version
|
|
739
|
+
docker_config["docker"]["args"] = filtered_args
|
|
740
|
+
|
|
741
|
+
try:
|
|
742
|
+
agent_type = AgentType(eval_request.agent.lower())
|
|
743
|
+
except ValueError:
|
|
744
|
+
valid_agents = [
|
|
745
|
+
a.value for a in AgentType if a != AgentType.INTEGRATION_TEST
|
|
746
|
+
]
|
|
747
|
+
return JSONResponse(
|
|
748
|
+
{
|
|
749
|
+
"error": f"Invalid agent type: {eval_request.agent}",
|
|
750
|
+
"valid_agents": valid_agents,
|
|
751
|
+
},
|
|
752
|
+
status_code=400,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
# Add MCP config to each task and validate basic structure
|
|
756
|
+
tasks = []
|
|
757
|
+
for task_data in eval_request.tasks:
|
|
758
|
+
task_data["mcp_config"] = docker_config
|
|
759
|
+
tasks.append(Task.model_validate(task_data).model_dump())
|
|
760
|
+
|
|
761
|
+
# Save tasks to temporary file
|
|
762
|
+
import tempfile
|
|
763
|
+
|
|
764
|
+
with tempfile.NamedTemporaryFile(
|
|
765
|
+
mode="w", prefix="hud-eval-", suffix=".json", delete=False
|
|
766
|
+
) as f:
|
|
767
|
+
json.dump(tasks, f)
|
|
768
|
+
task_file = f.name
|
|
769
|
+
|
|
770
|
+
# Fire and forget - launch evaluation in background
|
|
771
|
+
async def run_eval_background() -> None:
|
|
772
|
+
try:
|
|
773
|
+
await run_full_dataset(
|
|
774
|
+
task_file,
|
|
775
|
+
agent_type=agent_type,
|
|
776
|
+
model=eval_request.model,
|
|
777
|
+
max_steps=eval_request.max_steps,
|
|
778
|
+
verbose=eval_request.verbose,
|
|
779
|
+
group_size=eval_request.group_size,
|
|
595
780
|
)
|
|
781
|
+
except Exception as e:
|
|
782
|
+
raise e
|
|
783
|
+
finally:
|
|
784
|
+
# Clean up temp file
|
|
785
|
+
import os
|
|
786
|
+
|
|
787
|
+
if os.path.exists(task_file):
|
|
788
|
+
os.unlink(task_file)
|
|
789
|
+
|
|
790
|
+
# Start the evaluation in the background (fire and forget)
|
|
791
|
+
asyncio.create_task(run_eval_background()) # noqa: RUF006
|
|
792
|
+
|
|
793
|
+
# Return immediately
|
|
794
|
+
response_data = {
|
|
795
|
+
"status": "started",
|
|
796
|
+
"message": f"Evaluation launched with {len(tasks)} task(s)",
|
|
797
|
+
"agent": eval_request.agent,
|
|
798
|
+
"model": eval_request.model,
|
|
799
|
+
"max_steps": eval_request.max_steps,
|
|
800
|
+
"verbose": eval_request.verbose,
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
# Include group_size if > 1
|
|
804
|
+
if eval_request.group_size > 1:
|
|
805
|
+
response_data["group_size"] = eval_request.group_size
|
|
806
|
+
response_data["total_episodes"] = len(tasks) * eval_request.group_size
|
|
596
807
|
|
|
597
|
-
return JSONResponse(
|
|
808
|
+
return JSONResponse(response_data)
|
|
598
809
|
|
|
599
|
-
except
|
|
600
|
-
return JSONResponse({"error": "
|
|
810
|
+
except json.JSONDecodeError:
|
|
811
|
+
return JSONResponse({"error": "Invalid JSON in request body"}, status_code=400)
|
|
601
812
|
except Exception as e:
|
|
602
|
-
return JSONResponse(
|
|
813
|
+
return JSONResponse(
|
|
814
|
+
{"error": f"Failed to run evaluation: {e!s}"}, status_code=500
|
|
815
|
+
)
|
|
603
816
|
|
|
604
817
|
@self.custom_route("/openapi.json", methods=["GET"])
|
|
605
818
|
async def openapi_spec(request: Request) -> Response:
|
|
@@ -656,6 +869,40 @@ class MCPServer(FastMCP):
|
|
|
656
869
|
|
|
657
870
|
return JSONResponse(spec)
|
|
658
871
|
|
|
872
|
+
# Register hud-logs tool when in Docker dev mode
|
|
873
|
+
container_name = os.environ.get("_HUD_DEV_DOCKER_CONTAINER")
|
|
874
|
+
if container_name:
|
|
875
|
+
|
|
876
|
+
@self.tool("hud-logs")
|
|
877
|
+
async def get_docker_logs(
|
|
878
|
+
tail: int = 100,
|
|
879
|
+
since: str | None = None,
|
|
880
|
+
until: str | None = None,
|
|
881
|
+
timestamps: bool = False,
|
|
882
|
+
) -> dict[str, Any]:
|
|
883
|
+
"""Get logs from the Docker container running the HUD environment.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
tail: Number of lines to show from the end of the logs (default: 100)
|
|
887
|
+
since: Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (42m)
|
|
888
|
+
until: Show logs before timestamp (e.g. 2013-01-02T13:23:37Z) or relative (42m)
|
|
889
|
+
timestamps: Show timestamps in log output
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
Dictionary with:
|
|
893
|
+
- items: List of log entries
|
|
894
|
+
- container_name: Name of the container
|
|
895
|
+
- total_lines: Total number of log lines returned
|
|
896
|
+
- error: Error message if logs could not be retrieved
|
|
897
|
+
"""
|
|
898
|
+
# Use helper function to get logs
|
|
899
|
+
return self._get_docker_logs(
|
|
900
|
+
tail=tail,
|
|
901
|
+
since=since,
|
|
902
|
+
until=until,
|
|
903
|
+
timestamps=timestamps,
|
|
904
|
+
)
|
|
905
|
+
|
|
659
906
|
@self.custom_route("/docs", methods=["GET"])
|
|
660
907
|
async def docs_page(request: Request) -> Response:
|
|
661
908
|
"""Interactive documentation page."""
|
hud/shared/hints.py
CHANGED
|
@@ -38,7 +38,7 @@ HUD_API_KEY_MISSING = Hint(
|
|
|
38
38
|
message="Missing or invalid HUD_API_KEY.",
|
|
39
39
|
tips=[
|
|
40
40
|
"Set HUD_API_KEY in your environment or run: hud set HUD_API_KEY=your-key-here",
|
|
41
|
-
"Get a key at https://hud.
|
|
41
|
+
"Get a key at https://hud.ai",
|
|
42
42
|
"Check for whitespace or truncation",
|
|
43
43
|
],
|
|
44
44
|
docs_url=None,
|
|
@@ -68,7 +68,7 @@ PRO_PLAN_REQUIRED = Hint(
|
|
|
68
68
|
tips=[
|
|
69
69
|
"Upgrade your plan to continue",
|
|
70
70
|
],
|
|
71
|
-
docs_url="https://hud.
|
|
71
|
+
docs_url="https://hud.ai/project/billing",
|
|
72
72
|
command_examples=None,
|
|
73
73
|
code="PRO_PLAN_REQUIRED",
|
|
74
74
|
context=["billing", "plan"],
|
|
@@ -80,7 +80,7 @@ CREDITS_EXHAUSTED = Hint(
|
|
|
80
80
|
tips=[
|
|
81
81
|
"Top up credits or upgrade your plan",
|
|
82
82
|
],
|
|
83
|
-
docs_url="https://hud.
|
|
83
|
+
docs_url="https://hud.ai/project/billing",
|
|
84
84
|
command_examples=None,
|
|
85
85
|
code="CREDITS_EXHAUSTED",
|
|
86
86
|
context=["billing", "credits"],
|
hud/telemetry/job.py
CHANGED
|
@@ -170,7 +170,7 @@ def _print_job_url(job_id: str, job_name: str) -> None:
|
|
|
170
170
|
if not (settings.telemetry_enabled and settings.api_key):
|
|
171
171
|
return
|
|
172
172
|
|
|
173
|
-
url = f"https://hud.
|
|
173
|
+
url = f"https://hud.ai/jobs/{job_id}"
|
|
174
174
|
header = f"🚀 Job '{job_name}' started:"
|
|
175
175
|
|
|
176
176
|
# ANSI color codes
|
|
@@ -209,7 +209,7 @@ def _print_job_complete_url(job_id: str, job_name: str, error_occurred: bool = F
|
|
|
209
209
|
if not (settings.telemetry_enabled and settings.api_key):
|
|
210
210
|
return
|
|
211
211
|
|
|
212
|
-
url = f"https://hud.
|
|
212
|
+
url = f"https://hud.ai/jobs/{job_id}"
|
|
213
213
|
|
|
214
214
|
# ANSI color codes
|
|
215
215
|
GREEN = "\033[92m"
|
hud/tools/playwright.py
CHANGED
|
@@ -225,7 +225,14 @@ class PlaywrightTool(BaseTool):
|
|
|
225
225
|
if self._browser_context is None:
|
|
226
226
|
raise RuntimeError("Browser context failed to initialize")
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
# Reuse existing page if available (for CDP connections), otherwise create new one
|
|
229
|
+
pages = self._browser_context.pages
|
|
230
|
+
if pages:
|
|
231
|
+
self.page = pages[0]
|
|
232
|
+
logger.info("Reusing existing browser page")
|
|
233
|
+
else:
|
|
234
|
+
self.page = await self._browser_context.new_page()
|
|
235
|
+
logger.info("Created new browser page")
|
|
229
236
|
logger.info("Playwright browser launched successfully")
|
|
230
237
|
|
|
231
238
|
async def navigate(
|
hud/types.py
CHANGED
|
@@ -230,7 +230,7 @@ class AgentResponse(BaseModel):
|
|
|
230
230
|
tool_calls: list[MCPToolCall] = Field(default_factory=list)
|
|
231
231
|
done: bool = Field(default=False)
|
|
232
232
|
|
|
233
|
-
# --- TELEMETRY [hud.
|
|
233
|
+
# --- TELEMETRY [hud.ai] ---
|
|
234
234
|
# Responses
|
|
235
235
|
content: str | None = Field(default=None)
|
|
236
236
|
reasoning: str | None = Field(default=None)
|
hud/utils/tests/test_version.py
CHANGED
hud/version.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
hud/__init__.py,sha256=0LQ9PyuU6yZx7Fxu8YJXKC1i3TTHjg3UrInThh759QE,653
|
|
2
2
|
hud/__main__.py,sha256=YR8Dq8OhINOsVfQ55PmRXXg4fEK84Rt_-rMtJ5rvhWo,145
|
|
3
3
|
hud/settings.py,sha256=9SPEZcsZGqplw_LuV8RvJisDRdlsbQgxBqG8ifmjGNc,4117
|
|
4
|
-
hud/types.py,sha256=
|
|
5
|
-
hud/version.py,sha256=
|
|
4
|
+
hud/types.py,sha256=XA15zkH25Kt8IQAjT3xfEPew6oCKsnkERKrrKrYmYbM,11608
|
|
5
|
+
hud/version.py,sha256=7lqYv2fdoclIKiUgO55ISA4NzOcYp9DBF4rdJcsoHYk,105
|
|
6
6
|
hud/agents/__init__.py,sha256=UoIkljWdbq4bM0LD-mSaw6w826EqdEjOk7r6glNYwYQ,286
|
|
7
7
|
hud/agents/base.py,sha256=nRRnK_kA_PZDT0fepMMTz3QDTHj0jpqvbTqdgRPKSlg,32514
|
|
8
8
|
hud/agents/claude.py,sha256=Lf7p_h5H4sd2w16ZNc2ru3xuxNP7IDeFayrcdePOLSE,16030
|
|
@@ -22,32 +22,32 @@ hud/agents/tests/test_claude.py,sha256=YsI2TV87Sex2BJsLaO2l99WEL3PL3OhQDlTwUHn04
|
|
|
22
22
|
hud/agents/tests/test_client.py,sha256=uikgh6yhjPPX2RBU4XJQMz1mNox9uXjuwsP8t93id18,13337
|
|
23
23
|
hud/agents/tests/test_grounded_openai_agent.py,sha256=VK8lUvHIjWicMX00VKPE-FZyjiJqTEhb80MuRRa9fVc,5437
|
|
24
24
|
hud/agents/tests/test_openai.py,sha256=hPkL_ANbkrB_HUwvd4wBONuWomCarhLJQcFRpmwSbEk,8016
|
|
25
|
-
hud/cli/__init__.py,sha256=
|
|
25
|
+
hud/cli/__init__.py,sha256=sb0EyBBGEBKbfu2Ph3Wns42plYmA1Hejne5lLZ1hn4A,43747
|
|
26
26
|
hud/cli/__main__.py,sha256=fDH7XITyuDITwSDIVwRso06aouADO0CzTHKqp5TOwJE,143
|
|
27
27
|
hud/cli/analyze.py,sha256=4u5oYfJMquOjT9PzzRTYVcTZDxDi0ilNP_g532_hpOU,14716
|
|
28
28
|
hud/cli/build.py,sha256=EV6PsJ08p3mZZkfeUFjZ687ithxKGlUZ66yAF9lvtaI,28780
|
|
29
29
|
hud/cli/clone.py,sha256=AwVDIuhr8mHb1oT2Af2HrD25SiTdwATpE6zd93vzLgA,6099
|
|
30
30
|
hud/cli/debug.py,sha256=jtFW8J5F_3rhq1Hf1_SkJ7aLS3wjnyIs_LsC8k5cnzc,14200
|
|
31
|
-
hud/cli/dev.py,sha256=
|
|
32
|
-
hud/cli/eval.py,sha256=
|
|
31
|
+
hud/cli/dev.py,sha256=Cg9BLUs4mcMR6UQw43vW68Tx548BPpzlawlC8NgR2Fk,30018
|
|
32
|
+
hud/cli/eval.py,sha256=8LC8kS4RNuvgEGGf_sP7lz0LOgD8mVYOtEEQMXN59Bw,29024
|
|
33
33
|
hud/cli/get.py,sha256=sksKrdzBGZa7ZuSoQkc0haj-CvOGVSSikoVXeaUd3N4,6274
|
|
34
|
-
hud/cli/init.py,sha256=
|
|
34
|
+
hud/cli/init.py,sha256=bkpby7vBDqRH3GUDunNA5PIkbQ-Jnpas5pEeN1ltK7k,9976
|
|
35
35
|
hud/cli/list_func.py,sha256=EVi2Vc3Lb3glBNJxFx4MPnZknZ4xmuJz1OFg_dc8a_E,7177
|
|
36
36
|
hud/cli/pull.py,sha256=XGEZ8n60tbzLQP_8d9h7XYmzyCW0e2-Rkr3_tLG7jvw,12449
|
|
37
|
-
hud/cli/push.py,sha256=
|
|
37
|
+
hud/cli/push.py,sha256=y-1xYup2M396wlJ15d_N2jqthofmlWWiqCbtdYfZyGc,19488
|
|
38
38
|
hud/cli/remove.py,sha256=8vGQyXDqgtjz85_vtusoIG8zurH4RHz6z8UMevQRYM4,6861
|
|
39
39
|
hud/cli/flows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
hud/cli/flows/dev.py,sha256=
|
|
40
|
+
hud/cli/flows/dev.py,sha256=aLF_hzra0MIin2_2jWi3--yIZdHm5wi2o2PoQlpNm6M,4846
|
|
41
41
|
hud/cli/flows/tasks.py,sha256=SAQzS53g6vBPMvuEaMRsmkYOJMwvy28RQ-miVmVnP_8,18028
|
|
42
|
-
hud/cli/rl/__init__.py,sha256=
|
|
43
|
-
hud/cli/rl/celebrate.py,sha256=
|
|
42
|
+
hud/cli/rl/__init__.py,sha256=Tn_dYNrpk0UlKiPx0VZjGlGWlRUYxGj5PThp5PqTWbc,5376
|
|
43
|
+
hud/cli/rl/celebrate.py,sha256=vFex4RtHTgbhdsRpF4qS2vo4gd_W6LRd0ksO1EIv6J4,5947
|
|
44
44
|
hud/cli/rl/config.py,sha256=A-4WWwAS68GRKx1cP_DJ-NZD_96cFNnGwx0P3pQT1ps,3271
|
|
45
45
|
hud/cli/rl/display.py,sha256=hqJVGmO9csYinladhZwjF-GMvppYWngxDHajTyIJ_gM,5214
|
|
46
46
|
hud/cli/rl/gpu.py,sha256=peXS-NdUF5RyuSs0aZoCzGLboneBUpCy8f9f99WMrG0,2009
|
|
47
47
|
hud/cli/rl/gpu_utils.py,sha256=0nFRrmJZzLOHh_0bjMhIsBj94PAuu95vwxLd_sa4Q5g,11202
|
|
48
48
|
hud/cli/rl/local_runner.py,sha256=_4_Bs8UXPz5Is6PyVg_DnrG0zL_Mr-6DJ5_6jC9o0qA,23199
|
|
49
49
|
hud/cli/rl/presets.py,sha256=DzOO82xL5QyzdVtlX-Do1CODMvDz9ILMPapjU92jcZg,3051
|
|
50
|
-
hud/cli/rl/remote_runner.py,sha256=
|
|
50
|
+
hud/cli/rl/remote_runner.py,sha256=R8XXVrpO4l87nA8hlCcURmPfGzrszNLPuoi7-ZKPrhs,17837
|
|
51
51
|
hud/cli/rl/rl_api.py,sha256=fvRMWQXhTSLM2zQaWWDas_u47RreH8erLgtXRKnQGeA,4350
|
|
52
52
|
hud/cli/rl/viewer.py,sha256=ExQs1IX3T8x_9aBzc4JojZ779jmFvFTh7EjOYIHzYsU,4441
|
|
53
53
|
hud/cli/rl/vllm.py,sha256=xTRvex9mmdatTnNaqwZFDBwUsX70bJP5pAGTpbWjmvM,6153
|
|
@@ -113,8 +113,8 @@ hud/cli/utils/tests/test_source_hash.py,sha256=GGFklT9xrVPMEpPwgiUzr94An66R-nhNn
|
|
|
113
113
|
hud/cli/utils/tests/test_tasks.py,sha256=FT4smFGUHaJpZGIqj-yevBH8l9ARCu03RoedGxiLc2M,2624
|
|
114
114
|
hud/clients/README.md,sha256=XNE3mch95ozDgVqfwCGcrhlHY9CwT1GKfNANNboowto,3826
|
|
115
115
|
hud/clients/__init__.py,sha256=N5M_gZv4nP7dLRwpAiaqqaxyaLieGW6397FszeG7JGw,364
|
|
116
|
-
hud/clients/base.py,sha256=
|
|
117
|
-
hud/clients/fastmcp.py,sha256=
|
|
116
|
+
hud/clients/base.py,sha256=3U3AVLxKdtH6fw2B4V_CXxLv-J93jzlGsdc3ArxDq2I,15274
|
|
117
|
+
hud/clients/fastmcp.py,sha256=7yNk9SqOw-iBKPFJlt3hLAhR2Cl_QwVEln4OwXRd1Ww,9209
|
|
118
118
|
hud/clients/mcp_use.py,sha256=Zq_NBFPAMb81qoQa_adCbdTm-yUFjDFCMFG7m8tOlr4,14781
|
|
119
119
|
hud/clients/tests/__init__.py,sha256=sKOtJFFa4mDIXh1U6O8ZUHjigE8CiRMQ2PzJTIBZuVE,33
|
|
120
120
|
hud/clients/tests/test_client_integration.py,sha256=kohU6jfCNfwSnAushHeB1_CmDlRfQc7VBL0GEdJYSeI,4198
|
|
@@ -141,8 +141,8 @@ hud/native/tests/test_comparator.py,sha256=pDch3r3xDi2o5YXF_bkoLfIdHcCjse3foAaqy
|
|
|
141
141
|
hud/native/tests/test_native_init.py,sha256=Z-2dinbQYEkrbCcfBrBOLGdpXtWWOtkfPzp7ZKri68Y,2839
|
|
142
142
|
hud/otel/__init__.py,sha256=ii17ayoWiS5vAhA7UAmZ8TkmP52gs2pWyHsD46-uYbE,1003
|
|
143
143
|
hud/otel/collector.py,sha256=jLZymZ8r7xt2VDuWexfbnT7PY1-0aiyLMgjBy8KDY1M,4497
|
|
144
|
-
hud/otel/config.py,sha256=
|
|
145
|
-
hud/otel/context.py,sha256=
|
|
144
|
+
hud/otel/config.py,sha256=eHim5S4QdtrLKq53hJSiq02ezaYevN7Js2QbZyjHdVo,7037
|
|
145
|
+
hud/otel/context.py,sha256=Lic5T4PFdyeM9xfQ1yqzRoK3jhmT4CsRR78mMmRP8co,20403
|
|
146
146
|
hud/otel/exporters.py,sha256=k0yfUppMbcJ3IfNH3cc_u1hdR54qnnkT7lQjxO3I06g,21470
|
|
147
147
|
hud/otel/instrumentation.py,sha256=fsFG9W89RdewFDxWKN9Ft4GUb7WbIKpfucTc16WxaZU,5093
|
|
148
148
|
hud/otel/processors.py,sha256=-gGRbwifplcExDQBLfx_9tqWreDImULJNcENgO9q7VU,4700
|
|
@@ -170,7 +170,7 @@ hud/server/__init__.py,sha256=ZTxwhR7tMtSE14i1sONTz5UaMXURW1AYoFZMbWGBviU,134
|
|
|
170
170
|
hud/server/context.py,sha256=6bCdSzv1FGyItu9472HbbYef279H7QuMGJDR8EtYg5Y,3210
|
|
171
171
|
hud/server/low_level.py,sha256=XYs2pOJ9kN4OcJ6ahDmXM5mWkzq5wJLpKFInUYrWEok,4701
|
|
172
172
|
hud/server/router.py,sha256=T9hLe08-W8SGvO14RfwY3j2zfcvB3lKWS5sR9h_4awM,5343
|
|
173
|
-
hud/server/server.py,sha256=
|
|
173
|
+
hud/server/server.py,sha256=G_tnVuEBhC5gDNPfXChsPimo8L8ArMjQ9v3c1CSg848,40863
|
|
174
174
|
hud/server/helper/__init__.py,sha256=ZxO8VP3RZEBBp-q65VixuhzQgqEPSVzW0hEY9J9QqDA,116
|
|
175
175
|
hud/server/tests/__init__.py,sha256=eEYYkxX5Hz9woXVOBJ2H2_CQoEih0vH6nRt3sH2Z8v8,49
|
|
176
176
|
hud/server/tests/test_add_tool.py,sha256=9Y59LJpow3BQ31Jg7fowhV7nAeyqude9Tap9tEs_vBE,1863
|
|
@@ -183,7 +183,7 @@ hud/server/tests/test_server_extra.py,sha256=blIO0ne4Rq7R40y1-O-P8ANeRHnv2iKFWqS
|
|
|
183
183
|
hud/server/tests/test_sigterm_runner.py,sha256=HTM_0DAxA2exGYj7LK4udxMGXHZhY9LDZaKkHhQMu_Y,2610
|
|
184
184
|
hud/shared/__init__.py,sha256=IPxPCqtPLguryN-nBq78Sakypw2bRiE2iHv3SXG8YRk,139
|
|
185
185
|
hud/shared/exceptions.py,sha256=9IIqwlGuaNmcshEiFnph1jceZpWKpCnWgoH2OH-NL7I,13260
|
|
186
|
-
hud/shared/hints.py,sha256=
|
|
186
|
+
hud/shared/hints.py,sha256=aH9iIhGvC3Uo56z0H5H68Mh6HMMBMBqQyK8rvjv1Hc0,5717
|
|
187
187
|
hud/shared/requests.py,sha256=FbvMU_dKSWu7OzNBKGAXlq-Q34zL1cULhEdGFa6rJyQ,9993
|
|
188
188
|
hud/shared/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
189
189
|
hud/shared/tests/test_exceptions.py,sha256=66k9Z1zQmYK4yPlG2NC7zggabpCQMFpGqRG3_lOWM8U,16376
|
|
@@ -192,7 +192,7 @@ hud/shared/tests/test_requests.py,sha256=nKFcSN1sjrOouVU2xik9lE5Wxapy3EWsO8iIXrM
|
|
|
192
192
|
hud/telemetry/__init__.py,sha256=oYJ9fiaiwUGzfMH0qBixX74uSpAR_hyhl8Y-REjmNak,1396
|
|
193
193
|
hud/telemetry/async_context.py,sha256=H3MBt1vi_ogxUB0U0zSkeO8EqjcU5Iv_04LVQWtTO-E,11550
|
|
194
194
|
hud/telemetry/instrument.py,sha256=m3u6YK02PTk39Jr4L3se7l-cYyKx0maCaqf5Z5JqWNA,14096
|
|
195
|
-
hud/telemetry/job.py,sha256=
|
|
195
|
+
hud/telemetry/job.py,sha256=fhIWvsGdp1RbwINVPRUC47i4zQj4xGfe3pjft4mu8No,13278
|
|
196
196
|
hud/telemetry/replay.py,sha256=YW17s314s5Wy6Rl8MXHqg1FU8EF9_XcHBMJI0rrkyS4,2306
|
|
197
197
|
hud/telemetry/trace.py,sha256=Q8ypSKKjjMOV6BdNH65iMnya8ifd2dcOqA9-38Td15Y,5254
|
|
198
198
|
hud/telemetry/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -205,7 +205,7 @@ hud/tools/__init__.py,sha256=i6lE0GxYcPnlLLd-55ryCCHo7o9anC4RfqkuYFXvzMQ,1009
|
|
|
205
205
|
hud/tools/base.py,sha256=IxpnEHfxUtgz1k_w-09l65KKpDV3tQu4WdF3nZDsdwU,17854
|
|
206
206
|
hud/tools/bash.py,sha256=1jl7cpB1ApGXn7Hy8zghJ2fXugEol6UeN0aYUSiM2EQ,5189
|
|
207
207
|
hud/tools/edit.py,sha256=NYQL3coPIaG_-TP6DOpsVWFg1xcaMZwM5LtFye9WgNE,12644
|
|
208
|
-
hud/tools/playwright.py,sha256=
|
|
208
|
+
hud/tools/playwright.py,sha256=7ggBkylR71taQtrPr5UNwdxtQrHNXMFzKcHW8qDOgzA,15330
|
|
209
209
|
hud/tools/response.py,sha256=t6Oc8NM4u951A1XMCBaIkFyu3VNEQ8dcWURyTygfZmA,2228
|
|
210
210
|
hud/tools/submit.py,sha256=hJG2G3Oex4fz_3CsAUVhOhAA56UvDMhquB29xCT-C3M,1973
|
|
211
211
|
hud/tools/types.py,sha256=g-CWnUUDSxxIfUy54S1bpY1nfTzdYO1R_nPKYReABjQ,2734
|
|
@@ -266,10 +266,10 @@ hud/utils/tests/test_progress.py,sha256=QSF7Kpi03Ff_l3mAeqW9qs1nhK50j9vBiSobZq7T
|
|
|
266
266
|
hud/utils/tests/test_tasks.py,sha256=Rbbm51vZxygyWlhjunFq4IfFPefVB3qevM9_CZSt5w4,5774
|
|
267
267
|
hud/utils/tests/test_telemetry.py,sha256=5jl7bEx8C8b-FfFUko5pf4UY-mPOR-9HaeL98dGtVHM,2781
|
|
268
268
|
hud/utils/tests/test_tool_shorthand.py,sha256=1p3j3D0G93OXHqnUXbvTs3G4A8awrPvwhPpLi6YPeOM,5458
|
|
269
|
-
hud/utils/tests/test_version.py,sha256=
|
|
269
|
+
hud/utils/tests/test_version.py,sha256=3eFNjP_ugwRDZqUNNdWr6SX43UcvMw4739huirlrQ1o,160
|
|
270
270
|
hud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
271
|
-
hud_python-0.4.
|
|
272
|
-
hud_python-0.4.
|
|
273
|
-
hud_python-0.4.
|
|
274
|
-
hud_python-0.4.
|
|
275
|
-
hud_python-0.4.
|
|
271
|
+
hud_python-0.4.58.dist-info/METADATA,sha256=XasQ6ALt1lQPCHGOmu7h_bgDmPc3TghvlPP7O9N7Jhs,22338
|
|
272
|
+
hud_python-0.4.58.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
273
|
+
hud_python-0.4.58.dist-info/entry_points.txt,sha256=jJbodNFg1m0-CDofe5AHvB4zKBq7sSdP97-ohaQ3ae4,63
|
|
274
|
+
hud_python-0.4.58.dist-info/licenses/LICENSE,sha256=yIzBheVUf86FC1bztAcr7RYWWNxyd3B-UJQ3uddg1HA,1078
|
|
275
|
+
hud_python-0.4.58.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|