Add optional ensure_state, gesture region and final value feedback shared by Flash and Pro. Preserve completed inputs when verification fails and stop repeated ineffective strategies. Regenerate pinned tool schemas for the new optional arguments.
55 lines
2.1 KiB
Python
55 lines
2.1 KiB
Python
"""Strict, shared geometry for device actions; planning never dispatches input."""
|
|
|
|
from typing import Annotated, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, StrictInt
|
|
|
|
|
|
class GestureModel(BaseModel):
|
|
model_config = ConfigDict(
|
|
extra="forbid", strict=True, allow_inf_nan=False, str_strip_whitespace=True
|
|
)
|
|
|
|
|
|
Point = Annotated[list[StrictInt], Field(min_length=2, max_length=2)]
|
|
Region = Annotated[list[StrictInt], Field(min_length=4, max_length=4)]
|
|
Duration = Annotated[int, Field(strict=True, ge=1, le=10000)]
|
|
CoordinateSpace = Literal["normalized", "pixel"]
|
|
|
|
|
|
def pixel_point(point: list[int], space: CoordinateSpace, width: int, height: int) -> list[int]:
|
|
if width <= 0 or height <= 0:
|
|
raise ValueError("Screen dimensions must be positive.")
|
|
limits = [1001, 1001] if space == "normalized" else [width, height]
|
|
if any(p < 0 or p >= limit for p, limit in zip(point, limits, strict=True)):
|
|
raise ValueError(f"Point {point} is outside the {space} screen bounds.")
|
|
if space == "pixel":
|
|
return list(point)
|
|
return [min(size - 1, p * size // 1000) for p, size in zip(point, [width, height], strict=True)]
|
|
|
|
|
|
def pixel_region(region: list[int], space: CoordinateSpace, width: int, height: int) -> list[int]:
|
|
"""Observed rectangle; right/bottom are exclusive, like Android bounds."""
|
|
left, top, right, bottom = region
|
|
max_x, max_y = (1000, 1000) if space == "normalized" else (width, height)
|
|
if not (0 <= left < right <= max_x and 0 <= top < bottom <= max_y):
|
|
raise ValueError(
|
|
"region must be a nonempty on-screen [left, top, right, bottom] rectangle."
|
|
)
|
|
if space == "pixel":
|
|
return list(region)
|
|
return [
|
|
left * width // 1000,
|
|
top * height // 1000,
|
|
right * width // 1000,
|
|
bottom * height // 1000,
|
|
]
|
|
|
|
|
|
def require_in_region(points: list[list[int]], region: list[int]) -> None:
|
|
left, top, right, bottom = region
|
|
if any(not (left <= x < right and top <= y < bottom) for x, y in points):
|
|
raise ValueError(
|
|
"Gesture leaves the observed region; re-observe the actual gesture surface. No input executed."
|
|
)
|