refactor: clean up legacy code, rename hopper to entity_extractor, and add third-party notices

- Add NOTICE file and README license references for mobile-use and finalrun-agent
- Add derivative attribution headers to 17 files derived from mobile-use
- Rename hopper agent to entity_extractor across prompts, tools, and configs
- Remove obsolete adb_tunnel, ApiBaseUrl, and legacy CLI utility scripts
- Replace legacy examples with native Artemis diagnostic and workflow examples
- Update unit and tool tests to validate entity_extractor and SDK builders
This commit is contained in:
somew1nd committed 2026-09-11 16:19:42 -07:00
1 parent 0860788192
commit 54fcef9887
83 files changed
+1996 -3660

No files matched your search

+62 -159
View File
@@ -11,123 +11,71 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Portions of this file are derived from mobile-use (https://github.com/minitap-ai/mobile-use)
# Copyright 2025-2026 Minitap, Inc. Licensed under the Apache License 2.0.
"""Builder for TaskRequest objects using a fluent interface."""
"""Fluent specification builders for ARTEMIS automation task requests.
Provides type-safe configuration chaining for task objectives, execution bounds,
device sandboxing constraints, and structured schema validations.
"""
from __future__ import annotations
from pathlib import Path
from typing import Generic, TypeVar, cast
from typing import Generic, Self, TypeVar, cast
try:
from typing import Self
except ImportError:
from typing import Self
from pydantic import BaseModel
from artemis.constants import RECURSION_LIMIT
from artemis.sdk.types.agent import AgentProfile
from artemis.sdk.types.task import TaskRequest, TaskRequestCommon
from pydantic import BaseModel
TIn = TypeVar("TIn", bound=BaseModel | None)
TOut = TypeVar("TOut", bound=BaseModel)
class _CyFunctionDetectorMeta(type):
def __instancecheck__(self, instance):
name = type(instance).__name__
return (
name
in (
"cyfunction",
"cython_function_or_method",
"builtin_function_or_method",
)
or "cyfunction" in name.lower()
)
class TaskRequestCommonBuilder:
"""Configurator for common execution bounds and sandbox constraints."""
class CyFunctionDetector(metaclass=_CyFunctionDetectorMeta):
pass
class TaskRequestCommonBuilder(BaseModel):
"""Builder class providing a fluent interface for creating TaskRequestCommon objects."""
model_config = {"ignored_types": (CyFunctionDetector,)}
def __init__(self):
self._max_steps = RECURSION_LIMIT
self._record_trace = True
self._trace_path = Path("traces")
def __init__(self) -> None:
self._max_steps: int = RECURSION_LIMIT
self._record_trace: bool = True
self._trace_path: Path = Path("traces")
self._llm_output_path: Path | None = None
self._locked_app_package: str | None = None
self._app_path: Path | None = None
def with_max_steps(self, max_steps: int) -> Self:
"""Set the maximum number of steps the task can take.
Args:
max_steps: Maximum number of steps
"""
"""Constrain the maximum interaction turns allowed."""
self._max_steps = max_steps
return self
def with_trace_recording(self, enabled: bool = True, path: str | None = None) -> Self:
"""Configure trace recording for the task.
Traces record screenshots and actions during execution.
Args:
enabled: Whether to enable trace recording
path: Directory path where traces should be saved
"""
def with_trace_recording(self, enabled: bool = True, path: str | Path | None = None) -> Self:
"""Toggle telemetry snapshot capture and set output storage directory."""
self._record_trace = enabled
if enabled and path:
self._trace_path = Path(path)
return self
def with_llm_output_saving(self, path: str) -> Self:
"""Configure LLM output saving for the task.
Args:
path: Path where to save the LLM output message
"""
def with_llm_output_saving(self, path: str | Path) -> Self:
"""Designate destination path for persisting raw reasoning payloads."""
self._llm_output_path = Path(path)
return self
def with_locked_app_package(self, package_name: str) -> Self:
"""Set the app package to lock execution to.
This ensures the specified app is launched and in the foreground before
the agentic loop starts.
Args:
package_name: Package name (Android, e.g., 'com.whatsapp')
"""
self._locked_app_package = package_name
"""Bind test execution strictly to the target Android package namespace."""
self._locked_app_package = package_name.strip()
return self
def with_app_path(self, app_path: str | Path) -> Self:
"""Set the path to an app to install before running the task.
For Android: Path to an APK file.
The app will be installed automatically before the task starts.
Args:
app_path: Path to the app file to install
"""
self._app_path = Path(app_path) if isinstance(app_path, str) else app_path
"""Provide path to local APK for automatic installation prior to run."""
self._app_path = Path(app_path)
return self
def build(self) -> TaskRequestCommon:
"""Build the TaskRequestCommon object.
Returns:
A configured TaskRequestCommon object
Raises:
ValueError: If required fields are missing
"""
"""Materialize configured constraints into a TaskRequestCommon contract."""
return TaskRequestCommon(
max_steps=self._max_steps,
record_trace=self._record_trace,
@@ -139,110 +87,66 @@ class TaskRequestCommonBuilder(BaseModel):
class TaskRequestBuilder(TaskRequestCommonBuilder, Generic[TIn]):
"""Builder class providing a fluent interface for creating TaskRequest objects.
"""Fluent configurator constructing immutable TaskRequest specifications."""
This builder allows for step-by-step construction of a TaskRequest with
clear methods that make the configuration process intuitive and type-safe.
Examples:
>>> builder = TaskRequestBuilder[None](goal="Open Gmail and check unread
emails")
>>> task_request = (
... builder
... .with_max_steps(30)
... .using_profile("LowReasoning")
... .with_output_description("A list of email subjects and senders")
... .build()
... )
"""
model_config = {"ignored_types": (CyFunctionDetector,)}
def __init__(self, goal: str):
"""Initialize an empty TaskRequestBuilder."""
def __init__(self, goal: str) -> None:
super().__init__()
self._goal = goal
self._goal: str = goal
self._profile: str | AgentProfile | None = None
self._name: str | None = None
self._output_description = None
self._output_description: str | None = None
self._output_format: type[TIn] | None = None
@classmethod
def from_common(cls, goal: str, common: TaskRequestCommon):
res = cls(goal=goal)
res._max_steps = common.max_steps
res._record_trace = common.record_trace
res._trace_path = common.trace_path
res._llm_output_path = common.llm_output_path
res._locked_app_package = common.locked_app_package
res._app_path = common.app_path
return res
def from_common(cls, goal: str, common: TaskRequestCommon) -> TaskRequestBuilder[None]:
"""Derive a new task builder initialized with existing environment constraints."""
builder = TaskRequestBuilder[None](goal=goal)
builder._max_steps = common.max_steps
builder._record_trace = common.record_trace
builder._trace_path = common.trace_path
builder._llm_output_path = common.llm_output_path
builder._locked_app_package = common.locked_app_package
builder._app_path = common.app_path
return builder
def using_profile(self, profile: str | AgentProfile) -> "TaskRequestBuilder[TIn]":
"""Set the agent profile for executing the task.
Args:
profile: The agent profile to use
"""
def using_profile(self, profile: str | AgentProfile) -> Self:
"""Select execution profile (e.g., 'flash', 'pro', or custom AgentProfile)."""
self._profile = profile
return self
def with_name(self, name: str) -> "TaskRequestBuilder[TIn]":
"""Set the name of the task - useful when recording traces.
Otherwise, a random name will be generated.
Args:
name: Name of the task
"""
self._name = name
def with_name(self, name: str) -> Self:
"""Assign human-readable identifier to this test task."""
self._name = name.strip()
return self
def without_llm_output_saving(self) -> Self:
"""Disable LLM output saving for the task."""
"""Omit persisting standalone model reasoning outputs."""
self._llm_output_path = None
return self
def with_output_description(self, description: str) -> "TaskRequestBuilder[TIn]":
"""Set the description of the expected output format.
This is especially useful for data extraction tasks.
Args:
description: Description of the expected output format
"""
def with_output_description(self, description: str) -> Self:
"""Supply natural language extraction instructions for unformatted deliverables."""
self._output_description = description
return self
def with_output_format(self, output_format: type[TOut]) -> "TaskRequestBuilder[TOut]":
"""Set the pydantic model for the expected output format.
Args:
output_format: Pydantic model instance defining the output format
"""
self._output_format = output_format # type: ignore
def with_output_format(self, output_format: type[TOut]) -> TaskRequestBuilder[TOut]:
"""Bind output schema to a structured Pydantic model class."""
self._output_format = cast(type[TIn], output_format)
return cast(TaskRequestBuilder[TOut], self)
def build(self) -> TaskRequest[TIn]:
"""Build the TaskRequest object.
"""Validate criteria and construct final TaskRequest instance."""
if not self._goal or not self._goal.strip():
raise ValueError("Task goal must be a non-empty natural language objective.")
Returns:
A configured TaskRequest object
if self._output_format is not None and self._output_description is not None:
raise ValueError("Cannot specify both output_format and output_description simultaneously.")
Raises:
ValueError: If required fields are missing
"""
if not self._goal:
raise ValueError("Task goal is required")
profile_name = self._profile.name if isinstance(self._profile, AgentProfile) else self._profile
if self._output_format and self._output_description:
raise ValueError("Output format and description are mutually exclusive")
task_request = TaskRequest(
goal=self._goal,
profile=self._profile.name
if isinstance(self._profile, AgentProfile)
else self._profile,
return TaskRequest(
goal=self._goal.strip(),
profile=profile_name,
task_name=self._name,
output_description=self._output_description,
output_format=self._output_format,
@@ -253,4 +157,3 @@ class TaskRequestBuilder(TaskRequestCommonBuilder, Generic[TIn]):
locked_app_package=self._locked_app_package,
app_path=self._app_path,
)
return task_request