hud-python 0.1.0b2__py3-none-any.whl → 0.1.0b3__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/__init__.py CHANGED
@@ -9,7 +9,7 @@ from hud.environment import Environment, EvalSet, Observation, TaskResult
9
9
  from hud.gym import Gym
10
10
  from hud.run import Run
11
11
 
12
- __version__ = "0.1.0b2"
12
+ __version__ = "0.1.0b3"
13
13
 
14
14
  __all__ = [
15
15
  "Environment",
@@ -3,4 +3,3 @@ from __future__ import annotations
3
3
  from .adapter import ClaudeAdapter
4
4
 
5
5
  __all__ = ["ClaudeAdapter"]
6
-
@@ -23,12 +23,13 @@ class ClickAction(CLAAction):
23
23
  selector: str | None = None
24
24
  button: Literal["left", "right", "wheel", "back", "forward"] = "left"
25
25
  pattern: list[int] | None = None # [delay_1, delay_2, ...]
26
+ hold_keys: list[CLAKey] | None = None
26
27
 
27
28
 
28
29
  # PRESS ACTION for key presses/hotkeys
29
30
  class PressAction(CLAAction):
30
31
  type: Literal["press"] = "press"
31
- keys: list[str]
32
+ keys: list[CLAKey]
32
33
 
33
34
 
34
35
  # TYPE ACTION for text typing
@@ -43,6 +44,7 @@ class ScrollAction(CLAAction):
43
44
  type: Literal["scroll"] = "scroll"
44
45
  point: Point | None = None
45
46
  scroll: Point | None = None
47
+ hold_keys: list[CLAKey] | None = None
46
48
 
47
49
 
48
50
  # MOVE ACTION for mouse movement
@@ -64,6 +66,7 @@ class DragAction(CLAAction):
64
66
  type: Literal["drag"] = "drag"
65
67
  path: list[Point]
66
68
  pattern: list[int] | None = None # [delay_1, delay_2, ...]
69
+ hold_keys: list[CLAKey] | None = None
67
70
 
68
71
 
69
72
  # SCREENSHOT ACTION
@@ -90,3 +93,201 @@ CLA = Annotated[
90
93
  ],
91
94
  Field(discriminator="type"),
92
95
  ]
96
+
97
+
98
+ CLAKey = Literal[
99
+ # Control keys
100
+ "backspace",
101
+ "tab",
102
+ "enter",
103
+ "shift",
104
+ "shiftleft",
105
+ "shiftright",
106
+ "ctrl",
107
+ "ctrlleft",
108
+ "ctrlright",
109
+ "alt",
110
+ "altleft",
111
+ "altright",
112
+ "pause",
113
+ "capslock",
114
+ "esc",
115
+ "escape",
116
+ "space",
117
+ "pageup",
118
+ "pagedown",
119
+ "end",
120
+ "home",
121
+ "left",
122
+ "up",
123
+ "right",
124
+ "down",
125
+ "select",
126
+ "print",
127
+ "execute",
128
+ "printscreen",
129
+ "prtsc",
130
+ "insert",
131
+ "delete",
132
+ "help",
133
+ "sleep",
134
+ # Special keys
135
+ "numlock",
136
+ "scrolllock",
137
+ "clear",
138
+ "separator",
139
+ "modechange",
140
+ "apps",
141
+ "browserback",
142
+ "browserfavorites",
143
+ "browserforward",
144
+ "browserhome",
145
+ "browserrefresh",
146
+ "browsersearch",
147
+ "browserstop",
148
+ "launchapp1",
149
+ "launchapp2",
150
+ "launchmail",
151
+ "launchmediaselect",
152
+ "playpause",
153
+ "stop",
154
+ "prevtrack",
155
+ "nexttrack",
156
+ "volumemute",
157
+ "volumeup",
158
+ "volumedown",
159
+ "zoom",
160
+ # Modifier keys
161
+ "win",
162
+ "winleft",
163
+ "winright",
164
+ "command",
165
+ "option",
166
+ "optionleft",
167
+ "optionright",
168
+ "fn",
169
+ # Numpad keys
170
+ "num0",
171
+ "num1",
172
+ "num2",
173
+ "num3",
174
+ "num4",
175
+ "num5",
176
+ "num6",
177
+ "num7",
178
+ "num8",
179
+ "num9",
180
+ "multiply",
181
+ "add",
182
+ "subtract",
183
+ "decimal",
184
+ "divide",
185
+ # Function keys
186
+ "f1",
187
+ "f2",
188
+ "f3",
189
+ "f4",
190
+ "f5",
191
+ "f6",
192
+ "f7",
193
+ "f8",
194
+ "f9",
195
+ "f10",
196
+ "f11",
197
+ "f12",
198
+ "f13",
199
+ "f14",
200
+ "f15",
201
+ "f16",
202
+ "f17",
203
+ "f18",
204
+ "f19",
205
+ "f20",
206
+ "f21",
207
+ "f22",
208
+ "f23",
209
+ "f24",
210
+ # Language-specific keys
211
+ "hanguel",
212
+ "hangul",
213
+ "hanja",
214
+ "kana",
215
+ "kanji",
216
+ "junja",
217
+ "convert",
218
+ "nonconvert",
219
+ "yen",
220
+ # Characters
221
+ "\t",
222
+ "\n",
223
+ "\r",
224
+ " ",
225
+ "!",
226
+ '"',
227
+ "#",
228
+ "$",
229
+ "%",
230
+ "&",
231
+ "'",
232
+ "(",
233
+ ")",
234
+ "*",
235
+ "+",
236
+ ",",
237
+ "-",
238
+ ".",
239
+ "/",
240
+ "0",
241
+ "1",
242
+ "2",
243
+ "3",
244
+ "4",
245
+ "5",
246
+ "6",
247
+ "7",
248
+ "8",
249
+ "9",
250
+ ":",
251
+ ";",
252
+ "<",
253
+ "=",
254
+ ">",
255
+ "?",
256
+ "@",
257
+ "[",
258
+ "\\",
259
+ "]",
260
+ "^",
261
+ "_",
262
+ "`",
263
+ "a",
264
+ "b",
265
+ "c",
266
+ "d",
267
+ "e",
268
+ "f",
269
+ "g",
270
+ "h",
271
+ "i",
272
+ "j",
273
+ "k",
274
+ "l",
275
+ "m",
276
+ "n",
277
+ "o",
278
+ "p",
279
+ "q",
280
+ "r",
281
+ "s",
282
+ "t",
283
+ "u",
284
+ "v",
285
+ "w",
286
+ "x",
287
+ "y",
288
+ "z",
289
+ "{",
290
+ "|",
291
+ "}",
292
+ "~",
293
+ ]
hud/client.py CHANGED
@@ -11,14 +11,14 @@ from .adapters.common import Adapter
11
11
  from .environment import EvalSet
12
12
  from .gym import Gym
13
13
  from .run import Run, RunResponse
14
- from .server import make_request, make_sync_request
14
+ from .server import make_request
15
15
  from .settings import settings
16
16
 
17
17
 
18
18
  class HUDClient:
19
19
  """
20
20
  Client for interacting with the HUD API.
21
-
21
+
22
22
  This is the main entry point for the SDK, providing methods to load gyms,
23
23
  evalsets, and create runs.
24
24
  """
@@ -26,7 +26,7 @@ class HUDClient:
26
26
  def __init__(self, api_key: str | None = None) -> None:
27
27
  """
28
28
  Initialize the HUD client with an API key.
29
-
29
+
30
30
  Args:
31
31
  api_key: API key for authentication with the HUD API
32
32
  """
@@ -36,10 +36,10 @@ class HUDClient:
36
36
  async def load_gym(self, id: str) -> Gym:
37
37
  """
38
38
  Load a gym by ID from the HUD API.
39
-
39
+
40
40
  Args:
41
41
  id: The ID of the gym to load
42
-
42
+
43
43
  Returns:
44
44
  Gym: The loaded gym object
45
45
  """
@@ -54,10 +54,10 @@ class HUDClient:
54
54
  async def load_evalset(self, id: str) -> EvalSet:
55
55
  """
56
56
  Load an evalset by ID from the HUD API.
57
-
57
+
58
58
  Args:
59
59
  id: The ID of the evalset to load
60
-
60
+
61
61
  Returns:
62
62
  EvalSet: The loaded evalset object
63
63
  """
@@ -72,7 +72,7 @@ class HUDClient:
72
72
  async def list_gyms(self) -> list[str]:
73
73
  """
74
74
  List all available gyms.
75
-
75
+
76
76
  Returns:
77
77
  list[str]: List of gym IDs
78
78
  """
@@ -85,7 +85,7 @@ class HUDClient:
85
85
  async def get_runs(self) -> list[Run]:
86
86
  """
87
87
  Get all runs associated with the API key.
88
-
88
+
89
89
  Returns:
90
90
  list[Run]: List of run objects
91
91
  """
@@ -98,11 +98,11 @@ class HUDClient:
98
98
  async def load_run(self, id: str, adapter: Adapter | None = None) -> Run | None:
99
99
  """
100
100
  Load a run by ID from the HUD API.
101
-
101
+
102
102
  Args:
103
103
  id: The ID of the run to load
104
104
  adapter: Optional adapter for action conversion
105
-
105
+
106
106
  Returns:
107
107
  Run: The loaded run object, or None if not found
108
108
  """
@@ -132,7 +132,7 @@ class HUDClient:
132
132
  )
133
133
  return None
134
134
 
135
- def create_run(
135
+ async def create_run(
136
136
  self,
137
137
  name: str,
138
138
  gym: Gym,
@@ -143,7 +143,7 @@ class HUDClient:
143
143
  ) -> Run:
144
144
  """
145
145
  Create a new run in the HUD system.
146
-
146
+
147
147
  Args:
148
148
  name: Name of the run
149
149
  gym: Gym to use for the run
@@ -151,7 +151,7 @@ class HUDClient:
151
151
  config: Optional configuration parameters
152
152
  metadata: Optional metadata for the run
153
153
  adapter: Optional adapter for action conversion
154
-
154
+
155
155
  Returns:
156
156
  Run: The created run object
157
157
  """
@@ -161,7 +161,7 @@ class HUDClient:
161
161
  metadata = {}
162
162
  if config is None:
163
163
  config = {}
164
- data = make_sync_request(
164
+ data = await make_request(
165
165
  method="POST",
166
166
  url=f"{settings.base_url}/runs",
167
167
  json={
@@ -188,6 +188,7 @@ class HUDClient:
188
188
  Display a stream in the HUD system.
189
189
  """
190
190
  from IPython.display import HTML, display
191
+
191
192
  html_content = f"""
192
193
  <div style="width: 960px; height: 540px; overflow: hidden;">
193
194
  <div style="transform: scale(0.5); transform-origin: top left;">
hud/environment.py CHANGED
@@ -15,14 +15,16 @@ if TYPE_CHECKING:
15
15
 
16
16
  logger = logging.getLogger("hud.environment")
17
17
 
18
+
18
19
  class Observation(BaseModel):
19
20
  """
20
21
  Observation from the environment.
21
-
22
+
22
23
  Attributes:
23
24
  screenshot: Base64 encoded PNG string of the screen
24
25
  text: Text observation, if available
25
26
  """
27
+
26
28
  screenshot: str | None = None # base64 string png
27
29
  text: str | None = None
28
30
 
@@ -30,18 +32,20 @@ class Observation(BaseModel):
30
32
  class TaskResult(BaseModel):
31
33
  """
32
34
  Result of a task step.
33
-
35
+
34
36
  Attributes:
35
37
  observation: The current observation
36
38
  reward: Reward value from the step
37
39
  terminated: Whether the task is complete
38
40
  info: Additional information from the environment
39
41
  """
42
+
40
43
  observation: Observation
41
44
  reward: float
42
45
  terminated: bool
43
46
  info: dict[str, Any]
44
47
 
48
+
45
49
  class EnvironmentStatus(str, enum.Enum):
46
50
  """
47
51
  Status of the environment.
@@ -52,6 +56,7 @@ class EnvironmentStatus(str, enum.Enum):
52
56
  COMPLETED: The environment is completed
53
57
  ERROR: The environment is in an error state
54
58
  """
59
+
55
60
  INITIALIZING = "initializing"
56
61
  RUNNING = "running"
57
62
  COMPLETED = "completed"
@@ -64,27 +69,30 @@ status_messages = {
64
69
  EnvironmentStatus.COMPLETED.value: "completed",
65
70
  }
66
71
 
72
+
67
73
  class Environment:
68
74
  """
69
75
  Environment interface for agent interactions.
70
-
76
+
71
77
  This class handles the environment state and interactions, including
72
78
  creating the environment, retrieving state, and executing actions.
73
79
  """
74
80
 
75
81
  def __init__(
76
82
  self,
77
- run_id: str,
78
83
  adapter: Adapter,
84
+ run_id: str,
85
+ id: str | None = None,
79
86
  config: dict[str, Any] | None = None,
80
87
  metadata: dict[str, Any] | None = None,
81
88
  ) -> None:
82
89
  """
83
90
  Initialize an environment.
84
-
91
+
85
92
  Args:
86
- run_id: ID of the run this environment belongs to
87
93
  adapter: Adapter for converting actions
94
+ run_id: ID of the run this environment belongs to
95
+ id: Optional ID of an existing environment
88
96
  config: Optional configuration parameters
89
97
  metadata: Optional metadata for the environment
90
98
  """
@@ -96,16 +104,14 @@ class Environment:
96
104
  self.config = config
97
105
  self.adapter = adapter
98
106
  self.metadata = metadata
99
- # task_run_id is created when the environment is created (create_environment)
100
- # or provided if env already exists.
101
107
  self.final_response: None | str = None
102
- self.id = None
108
+ self.id = id
103
109
  self.vnc_url = None
104
110
 
105
111
  async def create_environment(self) -> str:
106
112
  """
107
113
  Initialize the environment and return the task_run_id.
108
-
114
+
109
115
  Returns:
110
116
  str: The environment ID
111
117
  """
@@ -121,7 +127,7 @@ class Environment:
121
127
  async def get_vnc_url(self) -> str:
122
128
  """
123
129
  Get the VNC URL for the environment.
124
-
130
+
125
131
  Returns:
126
132
  str: The VNC URL for remote viewing/control
127
133
  """
@@ -136,7 +142,7 @@ class Environment:
136
142
  async def get_env_state(self) -> str:
137
143
  """
138
144
  Get the state of the environment.
139
-
145
+
140
146
  Returns:
141
147
  str: The current state (e.g., "running", "error")
142
148
  """
@@ -152,10 +158,10 @@ class Environment:
152
158
  ) -> tuple[Observation, float, bool, dict[str, Any]]:
153
159
  """
154
160
  Send action to environment and get result.
155
-
161
+
156
162
  Args:
157
163
  action: The action to take, or None for no action
158
-
164
+
159
165
  Returns:
160
166
  tuple: (observation, reward, terminated, info)
161
167
  """
@@ -181,10 +187,10 @@ class Environment:
181
187
  def translate_action(self, action: Any) -> list:
182
188
  """
183
189
  Translate action to the correct format.
184
-
190
+
185
191
  Args:
186
192
  action: The action to translate
187
-
193
+
188
194
  Returns:
189
195
  list: List of translated actions in the CLA format
190
196
  """
@@ -196,7 +202,7 @@ class Environment:
196
202
  async def evaluate(self) -> float:
197
203
  """
198
204
  Get final evaluation score.
199
-
205
+
200
206
  Returns:
201
207
  float: The evaluation score
202
208
  """
@@ -217,16 +223,14 @@ class Environment:
217
223
  api_key=settings.api_key,
218
224
  )
219
225
 
220
- async def reset(
221
- self, task_id: str, metadata: dict[str, Any] | None = None
222
- ) -> Observation:
226
+ async def reset(self, task_id: str, metadata: dict[str, Any] | None = None) -> Observation:
223
227
  """
224
228
  Reset the environment to the task.
225
-
229
+
226
230
  Args:
227
231
  task_id: ID of the task to reset to
228
232
  metadata: Optional metadata for the reset
229
-
233
+
230
234
  Returns:
231
235
  Observation: Initial observation for the task
232
236
  """
@@ -253,10 +257,11 @@ class Environment:
253
257
  break
254
258
  await asyncio.sleep(10)
255
259
 
260
+
256
261
  class EvalSet:
257
262
  """
258
263
  Evaluation set containing tasks for benchmarking.
259
-
264
+
260
265
  Attributes:
261
266
  id: Unique identifier for the evalset
262
267
  name: Human-readable name
@@ -271,7 +276,7 @@ class EvalSet:
271
276
  ) -> None:
272
277
  """
273
278
  Initialize an evaluation set.
274
-
279
+
275
280
  Args:
276
281
  id: Unique identifier
277
282
  name: Human-readable name
@@ -284,7 +289,7 @@ class EvalSet:
284
289
  async def fetch_tasks(self) -> list[str]:
285
290
  """
286
291
  Fetch all tasks in this evalset from the API.
287
-
292
+
288
293
  Returns:
289
294
  list[str]: List of task IDs
290
295
  """
hud/gym.py CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
  class Gym:
5
5
  """
6
6
  Represents a simulation environment in the HUD system.
7
-
7
+
8
8
  Attributes:
9
9
  id: Unique identifier for the gym
10
10
  name: Human-readable name of the gym
@@ -13,7 +13,7 @@ class Gym:
13
13
  def __init__(self, id: str, name: str) -> None:
14
14
  """
15
15
  Initialize a gym.
16
-
16
+
17
17
  Args:
18
18
  id: Unique identifier
19
19
  name: Human-readable name
hud/run.py CHANGED
@@ -19,21 +19,20 @@ if TYPE_CHECKING:
19
19
  class RunResponse(BaseModel):
20
20
  """
21
21
  Response model for run data from the API.
22
-
22
+
23
23
  Attributes:
24
24
  id: Unique identifier for the run
25
25
  name: Human-readable name of the run
26
26
  gym: Dictionary containing gym information
27
27
  evalset: Dictionary containing evalset information
28
- adapter: Dictionary containing adapter information
29
28
  config: Dictionary containing configuration parameters
30
29
  metadata: Dictionary containing metadata
31
30
  """
31
+
32
32
  id: str
33
33
  name: str
34
34
  gym: dict[str, Any]
35
35
  evalset: dict[str, Any]
36
- adapter: dict[str, Any]
37
36
  config: dict[str, Any]
38
37
  metadata: dict[str, Any]
39
38
 
@@ -41,7 +40,7 @@ class RunResponse(BaseModel):
41
40
  class RunAnalyticsResponse(BaseModel):
42
41
  """
43
42
  Model for Run analytics data.
44
-
43
+
45
44
  Attributes:
46
45
  id: Unique identifier for the run
47
46
  name: Human-readable name of the run
@@ -54,6 +53,7 @@ class RunAnalyticsResponse(BaseModel):
54
53
  created_at: When the run was created
55
54
  raw_data: Detailed data about tasks and environments
56
55
  """
56
+
57
57
  id: str
58
58
  name: str
59
59
  status_counts: dict[str, int] # e.g. {"completed": 5, "running": 2, "error": 1}
@@ -69,7 +69,7 @@ class RunAnalyticsResponse(BaseModel):
69
69
 
70
70
  def __str__(self) -> str:
71
71
  return self.visualize()
72
-
72
+
73
73
  def visualize(self) -> str:
74
74
  """
75
75
  Generate an ASCII bar chart visualization of run analytics.
@@ -82,9 +82,7 @@ class RunAnalyticsResponse(BaseModel):
82
82
  """
83
83
  max_width = 50
84
84
 
85
- completion_rate = (
86
- self.completion_rate if self.completion_rate is not None else 0
87
- )
85
+ completion_rate = self.completion_rate if self.completion_rate is not None else 0
88
86
 
89
87
  result = [
90
88
  f"Run: {self.name} (ID: {self.id})",
@@ -123,7 +121,7 @@ class RunAnalyticsResponse(BaseModel):
123
121
  class Run:
124
122
  """
125
123
  A run represents a collection of tasks and environments.
126
-
124
+
127
125
  This class provides methods to fetch task IDs, create environments,
128
126
  and access analytics for the run.
129
127
  """
@@ -140,7 +138,7 @@ class Run:
140
138
  ) -> None:
141
139
  """
142
140
  Initialize a run.
143
-
141
+
144
142
  Args:
145
143
  id: Unique identifier
146
144
  name: Human-readable name
@@ -167,7 +165,7 @@ class Run:
167
165
  async def fetch_task_ids(self) -> list[str]:
168
166
  """
169
167
  Fetch task IDs for this run from the evalset.
170
-
168
+
171
169
  Returns:
172
170
  list[str]: List of task IDs
173
171
  """
@@ -176,10 +174,10 @@ class Run:
176
174
  async def make(self, metadata: dict[str, Any] | None = None) -> Environment:
177
175
  """
178
176
  Create a new environment for this run.
179
-
177
+
180
178
  Args:
181
179
  metadata: Metadata for the environment
182
-
180
+
183
181
  Returns:
184
182
  Environment: The created environment
185
183
  """
@@ -197,7 +195,7 @@ class Run:
197
195
  async def get_analytics(self) -> RunAnalyticsResponse:
198
196
  """
199
197
  Get analytics for this run.
200
-
198
+
201
199
  Returns:
202
200
  RunAnalyticsResponse: Analytics data including status counts,
203
201
  average score, and other metrics
hud/server/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .requests import RequestError, make_request, make_sync_request
3
+ from .requests import RequestError, make_request
4
4
 
5
- __all__ = ["RequestError", "make_request", "make_sync_request"]
5
+ __all__ = ["RequestError", "make_request"]
hud/server/requests.py CHANGED
@@ -4,22 +4,27 @@ HTTP request utilities for the HUD API.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import asyncio
7
8
  import logging
8
9
  from typing import Any
9
10
 
10
11
  import httpx
11
12
 
13
+ # Set up logger
12
14
  logger = logging.getLogger("hud.http")
15
+ logger.setLevel(logging.DEBUG)
16
+
13
17
 
14
18
  class RequestError(Exception):
15
19
  """Custom exception for API request errors"""
20
+
16
21
  def __init__(
17
22
  self,
18
23
  message: str,
19
24
  status_code: int | None = None,
20
25
  response_text: str | None = None,
21
26
  response_json: dict[str, Any] | None = None,
22
- response_headers: dict[str, str] | None = None
27
+ response_headers: dict[str, str] | None = None,
23
28
  ) -> None:
24
29
  self.message = message
25
30
  self.status_code = status_code
@@ -33,17 +38,22 @@ class RequestError(Exception):
33
38
 
34
39
  if self.status_code:
35
40
  parts.append(f"Status: {self.status_code}")
41
+
36
42
  if self.response_text:
37
43
  parts.append(f"Response Text: {self.response_text}")
44
+
38
45
  if self.response_json:
39
46
  parts.append(f"Response JSON: {self.response_json}")
47
+
40
48
  if self.response_headers:
41
49
  parts.append(f"Headers: {self.response_headers}")
42
50
 
43
51
  return " | ".join(parts)
44
52
 
45
53
  @classmethod
46
- def from_http_error(cls, error: httpx.HTTPStatusError) -> RequestError:
54
+ def from_http_error(
55
+ cls, error: httpx.HTTPStatusError, context: str = ""
56
+ ) -> RequestError:
47
57
  """Create a RequestError from an HTTP error response"""
48
58
  response = error.response
49
59
  status_code = response.status_code
@@ -68,6 +78,10 @@ class RequestError(Exception):
68
78
  # Fallback to simple message if JSON parsing fails
69
79
  message = f"Request failed with status {status_code}"
70
80
 
81
+ # Add context if provided
82
+ if context:
83
+ message = f"{context}: {message}"
84
+
71
85
  # Log the error details
72
86
  logger.error(
73
87
  "HTTP error from HUD SDK: %s | URL: %s | Status: %s | Response: %s%s",
@@ -75,7 +89,7 @@ class RequestError(Exception):
75
89
  response.url,
76
90
  status_code,
77
91
  response_text[:500],
78
- "..." if len(response_text) > 500 else ""
92
+ "..." if len(response_text) > 500 else "",
79
93
  )
80
94
 
81
95
  return cls(
@@ -86,61 +100,44 @@ class RequestError(Exception):
86
100
  response_headers=response_headers,
87
101
  )
88
102
 
89
- async def make_request(
90
- method: str, url: str, json: Any | None = None, api_key: str | None = None
91
- ) -> dict[str, Any]:
92
- """
93
- Make an asynchronous HTTP request to the HUD API.
94
-
95
- Args:
96
- method: HTTP method (GET, POST, etc.)
97
- url: Full URL for the request
98
- json: Optional JSON serializable data
99
- api_key: API key for authentication
100
-
101
- Returns:
102
- dict: JSON response from the server
103
-
104
- Raises:
105
- RequestError: If API key is missing or request fails
106
- """
107
- if not api_key:
108
- raise RequestError("API key is required but not provided")
109
-
110
- headers = {"Authorization": f"Bearer {api_key}"}
111
103
 
112
- async with httpx.AsyncClient(timeout=240.0) as client:
113
- try:
114
- response = await client.request(
115
- method=method, url=url, json=json, headers=headers
116
- )
117
- response.raise_for_status()
118
- result = response.json()
119
- return result
120
- except httpx.HTTPStatusError as e:
121
- raise RequestError.from_http_error(e) from None
122
- except httpx.RequestError as e:
123
- raise RequestError(f"Network error: {e!s}") from None
124
- except Exception as e:
125
- # Catch-all for unexpected errors
126
- raise RequestError(f"Unexpected error: {e!s}") from None
104
+ async def _handle_retry(
105
+ attempt: int, max_retries: int, retry_delay: float, url: str, error_msg: str
106
+ ) -> None:
107
+ """Helper function to handle retry logic and logging."""
108
+ retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
109
+ logger.warning(
110
+ "%s from %s, retrying in %.2f seconds (attempt %d/%d)",
111
+ error_msg,
112
+ url,
113
+ retry_time,
114
+ attempt,
115
+ max_retries,
116
+ )
117
+ await asyncio.sleep(retry_time)
127
118
 
128
119
 
129
- def make_sync_request(
130
- method: str, url: str, json: Any | None = None, api_key: str | None = None
120
+ async def make_request(
121
+ method: str,
122
+ url: str,
123
+ json: Any | None = None,
124
+ api_key: str | None = None,
125
+ max_retries: int = 4,
126
+ retry_delay: float = 2.0,
131
127
  ) -> dict[str, Any]:
132
128
  """
133
- Make a synchronous HTTP request to the HUD API.
134
-
129
+ Make an asynchronous HTTP request to the HUD API.
130
+
135
131
  Args:
136
132
  method: HTTP method (GET, POST, etc.)
137
133
  url: Full URL for the request
138
134
  json: Optional JSON serializable data
139
135
  api_key: API key for authentication
140
-
136
+ max_retries: Maximum number of retries
137
+ retry_delay: Delay between retries
141
138
  Returns:
142
139
  dict: JSON response from the server
143
-
140
+
144
141
  Raises:
145
142
  RequestError: If API key is missing or request fails
146
143
  """
@@ -148,19 +145,49 @@ def make_sync_request(
148
145
  raise RequestError("API key is required but not provided")
149
146
 
150
147
  headers = {"Authorization": f"Bearer {api_key}"}
148
+ retry_status_codes = [502, 503, 504]
149
+ attempt = 0
150
+
151
+ while attempt <= max_retries:
152
+ attempt += 1
151
153
 
152
- with httpx.Client(timeout=240.0) as client:
153
154
  try:
154
- response = client.request(
155
- method=method, url=url, json=json, headers=headers
156
- )
155
+ async with httpx.AsyncClient(
156
+ timeout=240.0,
157
+ limits=httpx.Limits(
158
+ max_connections=1000,
159
+ max_keepalive_connections=1000,
160
+ keepalive_expiry=10.0,
161
+ ),
162
+ ) as client:
163
+ response = await client.request(
164
+ method=method, url=url, json=json, headers=headers
165
+ )
166
+
167
+ # Check if we got a retriable status code
168
+ if response.status_code in retry_status_codes and attempt <= max_retries:
169
+ await _handle_retry(
170
+ attempt,
171
+ max_retries,
172
+ retry_delay,
173
+ url,
174
+ f"Received status {response.status_code}",
175
+ )
176
+ continue
177
+
157
178
  response.raise_for_status()
158
179
  result = response.json()
159
180
  return result
160
181
  except httpx.HTTPStatusError as e:
161
182
  raise RequestError.from_http_error(e) from None
162
183
  except httpx.RequestError as e:
163
- raise RequestError(f"Network error: {e!s}") from None
184
+ if attempt <= max_retries:
185
+ await _handle_retry(
186
+ attempt, max_retries, retry_delay, url, f"Network error: {e}"
187
+ )
188
+ continue
189
+ else:
190
+ raise RequestError(f"Network error: {e!s}") from None
164
191
  except Exception as e:
165
- # Catch-all for unexpected errors
166
192
  raise RequestError(f"Unexpected error: {e!s}") from None
193
+ raise RequestError(f"Request failed after {max_retries} retries with unknown error")
hud/settings.py CHANGED
@@ -7,32 +7,30 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
7
7
  class Settings(BaseSettings):
8
8
  """
9
9
  Global settings for the HUD SDK.
10
-
10
+
11
11
  This class manages configuration values loaded from environment variables
12
12
  and provides global access to settings throughout the application.
13
13
  """
14
- model_config = SettingsConfigDict(
15
- env_file=".env",
16
- env_file_encoding="utf-8",
17
- extra="allow"
18
- )
14
+
15
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="allow")
19
16
 
20
17
  base_url: str = Field(
21
18
  default="https://orchestrator.hud.live/hud-gym/api/v1",
22
19
  description="Base URL for the HUD API",
23
- validation_alias="base_url"
20
+ validation_alias="base_url",
24
21
  )
25
22
 
26
23
  api_key: str | None = Field(
27
24
  default=None,
28
25
  description="API key for authentication with the HUD API",
29
- validation_alias="HUD_API_KEY"
26
+ validation_alias="HUD_API_KEY",
30
27
  )
31
28
 
32
29
 
33
30
  # Create a singleton instance
34
31
  settings = Settings()
35
32
 
33
+
36
34
  # Add utility functions for backwards compatibility
37
35
  def get_settings() -> Settings:
38
36
  """Get the global settings instance."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hud-python
3
- Version: 0.1.0b2
3
+ Version: 0.1.0b3
4
4
  Summary: SDK for the HUD evaluation platform.
5
5
  Project-URL: Homepage, https://github.com/Human-Data/hud-sdk
6
6
  Project-URL: Bug Tracker, https://github.com/Human-Data/hud-sdk/issues
@@ -95,7 +95,7 @@ async def main():
95
95
  evalset = await client.load_evalset(id="OSWorld-Ubuntu")
96
96
 
97
97
  # Create a run and environment
98
- run = client.create_run(name="example-run", gym=gym, evalset=evalset)
98
+ run = await client.create_run(name="example-run", gym=gym, evalset=evalset)
99
99
  env = await run.make(metadata={"agent_id": "OSWORLD-1"})
100
100
  await env.wait_for_ready()
101
101
 
@@ -0,0 +1,21 @@
1
+ hud/__init__.py,sha256=Xam6plJLHFqKPKcnVhwLQf4bsApDuxZ8BJF0FEAjkos,416
2
+ hud/client.py,sha256=7WHXTQhVK-T9Rj4ZooADE_c1pah5Bc1DJ9ZRqUyUnuQ,5724
3
+ hud/environment.py,sha256=39tna-Cpzg9T6HqKebPARP2DXaF2n0xPr1W0qx8y160,8401
4
+ hud/gym.py,sha256=aanBHtlsXrJwrFax9SbXWwk_By-X8wE3M9deS-E_s4c,463
5
+ hud/run.py,sha256=5ukjuRNLjj5fczaWxpR_5NebFbQpoy8w81eRYy309Vg,6401
6
+ hud/settings.py,sha256=1ScSac0ta03LkckkH2gi6SyKY2M7nr15vRGugo2C_xs,1015
7
+ hud/adapters/__init__.py,sha256=y3H7yMl7rC-rrXG2WvePdSojoNFSui02eYTH17Xd7OY,87
8
+ hud/adapters/claude/__init__.py,sha256=i7QEF-29FLb9qxp1eYtXs-adIk_tG54tL-9g6d3xodk,100
9
+ hud/adapters/claude/adapter.py,sha256=oi2lvO42g7i-L151tIWIGQGA80skcYRwzQ52-0f2OpA,4840
10
+ hud/adapters/common/__init__.py,sha256=BjdZWJVs_AKtpFrt-tNsdQRjnz7D97DFEQirJ-r0mp8,118
11
+ hud/adapters/common/adapter.py,sha256=SCtOuRjW5Szzd45LXCaqDEaKr2lhA-nIqSEMJ9KLsKI,5799
12
+ hud/adapters/common/types.py,sha256=d9tIF06tjK7VCb-yBJ9epwHlXRHlObo9YWetrv33s8c,4511
13
+ hud/server/__init__.py,sha256=VPrhyyqg3inge9J7BjcmDBNJRuvkCA9ZDXS_R5Q8ZtY,129
14
+ hud/server/requests.py,sha256=pPPaMpwqmA1RyWwzQN1ybgAnbSHJLeeIaW6MJwhJYks,6052
15
+ hud/utils/__init__.py,sha256=0m8klSLnMLeIJT23ipBXfFACk4hNWPsA6ZNqZDpv6oY,99
16
+ hud/utils/config.py,sha256=dze0BGE4q14omjj9822kL9BeiIgWQvJyuU29A2wa1SE,193
17
+ hud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ hud_python-0.1.0b3.dist-info/METADATA,sha256=mWp4cHyIzYuzxk3alNFspztWL2S8_6ZlvGP0UqzIF48,5146
19
+ hud_python-0.1.0b3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ hud_python-0.1.0b3.dist-info/licenses/LICENSE,sha256=IVdfcZ8xq5apYGJS5GzRLLbm9r03Aecxd03isi-3P9k,1075
21
+ hud_python-0.1.0b3.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- hud/__init__.py,sha256=GmX-LujM2oZR6_tP_mOW09BY8HeK41lLF-P0sMW_1pY,416
2
- hud/client.py,sha256=ztWPiAJyJUdJxdxGqDmsQnVK-_jccinWQUUXmq0OOmY,5843
3
- hud/environment.py,sha256=R-t-21V0gveuHL6LlVLnBp0gYGm8tn5FbLcq_rRlH9g,8587
4
- hud/gym.py,sha256=dKmf0Ol0-XyLhji034pF_5dXnhW1IgIr-dJUg4KfslE,475
5
- hud/run.py,sha256=_K7POPjJyqcJ_DVLAO7hRmvLUcg9gg2KrLHw_26DB9I,6570
6
- hud/settings.py,sha256=FbZHI1q6bDHe7Awl32SDPb-syqtkLI3C7gIIXuMXCiQ,1045
7
- hud/adapters/__init__.py,sha256=y3H7yMl7rC-rrXG2WvePdSojoNFSui02eYTH17Xd7OY,87
8
- hud/adapters/claude/__init__.py,sha256=GsMxaBL5ZuKV6-jJsLfw23n_Ml9e88SXIddYDGkIUKE,101
9
- hud/adapters/claude/adapter.py,sha256=oi2lvO42g7i-L151tIWIGQGA80skcYRwzQ52-0f2OpA,4840
10
- hud/adapters/common/__init__.py,sha256=BjdZWJVs_AKtpFrt-tNsdQRjnz7D97DFEQirJ-r0mp8,118
11
- hud/adapters/common/adapter.py,sha256=SCtOuRjW5Szzd45LXCaqDEaKr2lhA-nIqSEMJ9KLsKI,5799
12
- hud/adapters/common/types.py,sha256=LlWxH9sWucYgnIv6DKrgqToh3k7Bu-xdTxNFU4L8Xg8,1962
13
- hud/server/__init__.py,sha256=HeIXBGb-bxtq3xF20jP4IrOy77PlsqhClOf3bZ9wrwI,169
14
- hud/server/requests.py,sha256=M_pK1oCd4QjIE0yguD6iaybJ_mempOWDQYEpdOkophU,5522
15
- hud/utils/__init__.py,sha256=0m8klSLnMLeIJT23ipBXfFACk4hNWPsA6ZNqZDpv6oY,99
16
- hud/utils/config.py,sha256=dze0BGE4q14omjj9822kL9BeiIgWQvJyuU29A2wa1SE,193
17
- hud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- hud_python-0.1.0b2.dist-info/METADATA,sha256=5skHs5IfSJP4DQAGzpuo_yjO7l65XxHIPWGitHQt0Ug,5140
19
- hud_python-0.1.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- hud_python-0.1.0b2.dist-info/licenses/LICENSE,sha256=IVdfcZ8xq5apYGJS5GzRLLbm9r03Aecxd03isi-3P9k,1075
21
- hud_python-0.1.0b2.dist-info/RECORD,,