openadapt-types 0.2.0__tar.gz → 0.3.0__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.
Files changed (23) hide show
  1. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/CHANGELOG.md +20 -0
  2. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/PKG-INFO +1 -1
  3. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/__init__.py +11 -0
  4. openadapt_types-0.3.0/openadapt_types/benchmark.py +202 -0
  5. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/pyproject.toml +2 -1
  6. openadapt_types-0.3.0/tests/test_benchmark.py +86 -0
  7. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/.github/workflows/publish.yml +0 -0
  8. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/.github/workflows/test.yml +0 -0
  9. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/.gitignore +0 -0
  10. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/LICENSE +0 -0
  11. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/README.md +0 -0
  12. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/_compat.py +0 -0
  13. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/action.py +0 -0
  14. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/computer_state.py +0 -0
  15. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/episode.py +0 -0
  16. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/failure.py +0 -0
  17. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/parsing.py +0 -0
  18. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/openadapt_types/py.typed +0 -0
  19. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/tests/__init__.py +0 -0
  20. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/tests/test_compat.py +0 -0
  21. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/tests/test_parsing.py +0 -0
  22. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/tests/test_schemas.py +0 -0
  23. {openadapt_types-0.2.0 → openadapt_types-0.3.0}/uv.lock +0 -0
@@ -1,6 +1,26 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.3.0 (2026-07-13)
5
+
6
+ ### Features
7
+
8
+ - Add canonical Benchmark* types (Task/Observation/Action/Agent)
9
+ ([#5](https://github.com/OpenAdaptAI/openadapt-types/pull/5),
10
+ [`82ad485`](https://github.com/OpenAdaptAI/openadapt-types/commit/82ad48590e89e23c44cdf25ce636035195d6ea14))
11
+
12
+ Move the Benchmark* vocabulary into the canonical schema package so both openadapt-ml and
13
+ openadapt-evals can import it without depending on each other, breaking the historical ml<->evals
14
+ import cycle.
15
+
16
+ Definitions are dependency-free (dataclasses + abc) and faithfully match the previous
17
+ openadapt-evals definitions.
18
+
19
+ Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
20
+
21
+ Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
22
+
23
+
4
24
  ## v0.2.0 (2026-03-29)
5
25
 
6
26
  ### Continuous Integration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openadapt-types
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Canonical Pydantic schemas for computer-use agents: ComputerState, Action, ActionResult, UINode
5
5
  Project-URL: Homepage, https://github.com/OpenAdaptAI/openadapt-types
6
6
  Project-URL: Repository, https://github.com/OpenAdaptAI/openadapt-types
@@ -26,6 +26,12 @@ from openadapt_types.action import (
26
26
  ActionTarget,
27
27
  ActionType,
28
28
  )
29
+ from openadapt_types.benchmark import (
30
+ BenchmarkAction,
31
+ BenchmarkAgent,
32
+ BenchmarkObservation,
33
+ BenchmarkTask,
34
+ )
29
35
  from openadapt_types.computer_state import (
30
36
  BoundingBox,
31
37
  ComputerState,
@@ -57,6 +63,11 @@ __all__ = [
57
63
  "ActionResult",
58
64
  "ActionTarget",
59
65
  "ActionType",
66
+ # benchmark
67
+ "BenchmarkAction",
68
+ "BenchmarkAgent",
69
+ "BenchmarkObservation",
70
+ "BenchmarkTask",
60
71
  # episode
61
72
  "Episode",
62
73
  "Step",
@@ -0,0 +1,202 @@
1
+ """Canonical benchmark vocabulary shared across the OpenAdapt ecosystem.
2
+
3
+ This module hosts the ``Benchmark*`` types that describe the interaction
4
+ surface between GUI-agent benchmarks and the agents/environments that run
5
+ them. They live here (in the canonical schema package) so that both
6
+ ``openadapt-ml`` (model library) and ``openadapt-evals`` (evaluation
7
+ harness) can import them without depending on each other -- breaking the
8
+ historical ``ml <-> evals`` import cycle.
9
+
10
+ The definitions are deliberately dependency-free (plain ``dataclasses`` and
11
+ an ``abc.ABC``) so importing them never pulls in heavy optional deps.
12
+
13
+ Example::
14
+
15
+ from openadapt_types import (
16
+ BenchmarkAgent,
17
+ BenchmarkAction,
18
+ BenchmarkObservation,
19
+ BenchmarkTask,
20
+ )
21
+
22
+ class MyAgent(BenchmarkAgent):
23
+ def act(self, observation, task, history=None):
24
+ return BenchmarkAction(type="click", x=0.5, y=0.5)
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from abc import ABC, abstractmethod
30
+ from dataclasses import dataclass, field
31
+ from typing import Any
32
+
33
+
34
+ @dataclass
35
+ class BenchmarkTask:
36
+ """Canonical task representation.
37
+
38
+ Attributes:
39
+ task_id: Unique identifier for the task.
40
+ instruction: Natural language task instruction.
41
+ domain: Task domain ("web", "desktop", "mobile").
42
+ initial_state_ref: Reference to initial state (VM snapshot, URL, etc.).
43
+ time_limit_steps: Maximum steps allowed for the task.
44
+ raw_config: Original benchmark config (lossless preservation).
45
+ evaluation_spec: Benchmark-native evaluation specification.
46
+ """
47
+
48
+ task_id: str
49
+ instruction: str
50
+ domain: str # "web", "desktop", "mobile"
51
+
52
+ # Environment setup
53
+ initial_state_ref: str | None = None # VM snapshot, storage_state, start URL
54
+ time_limit_steps: int | None = None
55
+
56
+ # Preserve original config losslessly
57
+ raw_config: dict[str, Any] = field(default_factory=dict)
58
+
59
+ # Evaluation spec (benchmark-native)
60
+ evaluation_spec: dict[str, Any] | None = None
61
+
62
+
63
+ @dataclass
64
+ class BenchmarkObservation:
65
+ """Canonical observation at each step.
66
+
67
+ Supports multiple observation modalities:
68
+ - Visual: screenshots with viewport info
69
+ - Structured UI: accessibility tree (UIA/AXTree/DOM)
70
+ - Context: URL, window title, focused element
71
+
72
+ Attributes:
73
+ screenshot: PNG image bytes.
74
+ screenshot_path: Path to saved screenshot.
75
+ viewport: (width, height) of the viewport.
76
+ accessibility_tree: Platform-specific UI tree (UIA/AXTree/DOM).
77
+ dom_html: Raw HTML for web tasks.
78
+ url: Current URL for web tasks.
79
+ window_title: Active window title for desktop tasks.
80
+ focused_element: Currently focused UI element.
81
+ raw_observation: Original benchmark observation (lossless).
82
+ """
83
+
84
+ # Visual
85
+ screenshot: bytes | None = None # PNG image bytes
86
+ screenshot_path: str | None = None
87
+ viewport: tuple[int, int] | None = None # (width, height)
88
+
89
+ # Structured UI (format varies by platform)
90
+ accessibility_tree: dict | None = None # UIA (Windows), AXTree (macOS), DOM (web)
91
+ dom_html: str | None = None # Raw HTML for web
92
+
93
+ # Context
94
+ url: str | None = None # For web tasks
95
+ window_title: str | None = None # For desktop tasks
96
+ app_name: str | None = None # Active application
97
+ focused_element: dict | None = None # {node_id, bbox, text}
98
+
99
+ # Raw benchmark-specific data (lossless)
100
+ raw_observation: dict[str, Any] | None = None
101
+
102
+
103
+ @dataclass
104
+ class BenchmarkAction:
105
+ """Canonical action representation.
106
+
107
+ Supports multiple action types with both coordinate-based and element-based
108
+ grounding. The "grounding-first" approach stores both when available.
109
+
110
+ Attributes:
111
+ type: Action type ("click", "type", "scroll", "key", "drag", "answer", "done").
112
+ x: X coordinate (normalized [0,1] or pixels).
113
+ y: Y coordinate (normalized [0,1] or pixels).
114
+ target_node_id: Element ID from accessibility tree.
115
+ target_bbox: Element bounding box.
116
+ target_role: Element role (button, textfield, etc.).
117
+ target_name: Element accessible name.
118
+ text: Text to type (for "type" action).
119
+ key: Single key (for "key" action, e.g., "Enter", "Tab").
120
+ modifiers: Key modifiers (["ctrl", "shift", "alt"]).
121
+ scroll_direction: Scroll direction ("up", "down", "left", "right").
122
+ scroll_amount: Scroll amount (pixels or normalized).
123
+ end_x: Drag end X coordinate.
124
+ end_y: Drag end Y coordinate.
125
+ answer: Answer string (for benchmarks that score by answer).
126
+ raw_action: Original benchmark action (lossless).
127
+ """
128
+
129
+ type: str # "click", "type", "scroll", "key", "drag", "answer", "done", "error"
130
+
131
+ # Pointer actions - coordinates
132
+ x: float | None = None # Normalized [0,1] or pixel
133
+ y: float | None = None
134
+
135
+ # Element grounding (when available)
136
+ target_node_id: str | None = None # DOM/AX/UIA node ID
137
+ target_bbox: tuple[float, float, float, float] | None = None
138
+ target_role: str | None = None # "button", "textfield", etc.
139
+ target_name: str | None = None # Accessible name
140
+
141
+ # Keyboard actions
142
+ text: str | None = None # For "type" action - text to type
143
+ key: str | None = None # For "key" action - single key
144
+ modifiers: list[str] | None = None # ["ctrl", "shift", "alt"]
145
+
146
+ # Scroll actions
147
+ scroll_direction: str | None = None # "up", "down", "left", "right"
148
+ scroll_amount: float | None = None # Pixels or normalized
149
+
150
+ # Drag actions
151
+ end_x: float | None = None
152
+ end_y: float | None = None
153
+
154
+ # Answer action (some benchmarks score by final answer)
155
+ answer: str | None = None
156
+
157
+ # Raw benchmark-specific format (lossless)
158
+ raw_action: dict[str, Any] | None = None
159
+
160
+
161
+ class BenchmarkAgent(ABC):
162
+ """Abstract interface for agents evaluated on benchmarks.
163
+
164
+ Agents must implement the `act` method to receive observations
165
+ and return actions. The agent can maintain internal state across
166
+ steps within an episode.
167
+ """
168
+
169
+ @abstractmethod
170
+ def act(
171
+ self,
172
+ observation: BenchmarkObservation,
173
+ task: BenchmarkTask,
174
+ history: list[tuple[BenchmarkObservation, BenchmarkAction]] | None = None,
175
+ ) -> BenchmarkAction:
176
+ """Given observation and task, return next action.
177
+
178
+ Args:
179
+ observation: Current observation from the environment.
180
+ task: Task being performed.
181
+ history: Optional list of previous (observation, action) pairs.
182
+
183
+ Returns:
184
+ Action to execute.
185
+ """
186
+ ...
187
+
188
+ def reset(self) -> None:
189
+ """Reset agent state between episodes.
190
+
191
+ Called before starting a new task. Override to clear any
192
+ internal state.
193
+ """
194
+ pass
195
+
196
+
197
+ __all__ = [
198
+ "BenchmarkAction",
199
+ "BenchmarkAgent",
200
+ "BenchmarkObservation",
201
+ "BenchmarkTask",
202
+ ]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openadapt-types"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Canonical Pydantic schemas for computer-use agents: ComputerState, Action, ActionResult, UINode"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -40,6 +40,7 @@ dev = [
40
40
  "pytest-cov",
41
41
  ]
42
42
 
43
+
43
44
  [tool.hatch.build.targets.wheel]
44
45
  packages = ["openadapt_types"]
45
46
 
@@ -0,0 +1,86 @@
1
+ """Tests for the canonical Benchmark* vocabulary."""
2
+
3
+ from openadapt_types import (
4
+ BenchmarkAction,
5
+ BenchmarkAgent,
6
+ BenchmarkObservation,
7
+ BenchmarkTask,
8
+ )
9
+
10
+
11
+ def test_benchmark_task_defaults():
12
+ task = BenchmarkTask(task_id="t1", instruction="do it", domain="desktop")
13
+ assert task.task_id == "t1"
14
+ assert task.instruction == "do it"
15
+ assert task.domain == "desktop"
16
+ assert task.initial_state_ref is None
17
+ assert task.time_limit_steps is None
18
+ assert task.raw_config == {}
19
+ assert task.evaluation_spec is None
20
+
21
+
22
+ def test_benchmark_task_raw_config_is_independent():
23
+ a = BenchmarkTask(task_id="a", instruction="i", domain="web")
24
+ b = BenchmarkTask(task_id="b", instruction="i", domain="web")
25
+ a.raw_config["k"] = "v"
26
+ assert b.raw_config == {}
27
+
28
+
29
+ def test_benchmark_observation_defaults():
30
+ obs = BenchmarkObservation()
31
+ assert obs.screenshot is None
32
+ assert obs.viewport is None
33
+ assert obs.raw_observation is None
34
+
35
+
36
+ def test_benchmark_observation_fields():
37
+ obs = BenchmarkObservation(
38
+ screenshot=b"png",
39
+ viewport=(1920, 1080),
40
+ url="https://example.com",
41
+ window_title="Notepad",
42
+ )
43
+ assert obs.screenshot == b"png"
44
+ assert obs.viewport == (1920, 1080)
45
+ assert obs.url == "https://example.com"
46
+ assert obs.window_title == "Notepad"
47
+
48
+
49
+ def test_benchmark_action_click():
50
+ action = BenchmarkAction(type="click", x=0.5, y=0.25)
51
+ assert action.type == "click"
52
+ assert action.x == 0.5
53
+ assert action.y == 0.25
54
+ assert action.text is None
55
+
56
+
57
+ def test_benchmark_action_type_and_key():
58
+ typing = BenchmarkAction(type="type", text="hello")
59
+ assert typing.text == "hello"
60
+ key = BenchmarkAction(type="key", key="Enter", modifiers=["ctrl"])
61
+ assert key.key == "Enter"
62
+ assert key.modifiers == ["ctrl"]
63
+
64
+
65
+ def test_benchmark_agent_is_abstract():
66
+ # BenchmarkAgent is an ABC; instantiating directly must fail.
67
+ try:
68
+ BenchmarkAgent() # type: ignore[abstract]
69
+ except TypeError:
70
+ pass
71
+ else: # pragma: no cover
72
+ raise AssertionError("BenchmarkAgent should be abstract")
73
+
74
+
75
+ def test_benchmark_agent_subclass_roundtrip():
76
+ class Echo(BenchmarkAgent):
77
+ def act(self, observation, task, history=None):
78
+ return BenchmarkAction(type="done")
79
+
80
+ agent = Echo()
81
+ obs = BenchmarkObservation()
82
+ task = BenchmarkTask(task_id="t", instruction="i", domain="desktop")
83
+ action = agent.act(obs, task)
84
+ assert action.type == "done"
85
+ # Default reset is a no-op and must not raise.
86
+ agent.reset()
File without changes
File without changes