Download tiny_computer_agent/actions.py from hkblockchaintechnology/tiny-computer-agent: direct link, hf CLI and curl.
- Browser
- Download file 4.97 kB
-
https://huggingface.co/hkblockchaintechnology/tiny-computer-agent/resolve/main/tiny_computer_agent/actions.py
- Command line
-
hf download hf://hkblockchaintechnology/tiny-computer-agent/tiny_computer_agent/actions.py
-
curl -L -o actions.py https://huggingface.co/hkblockchaintechnology/tiny-computer-agent/resolve/main/tiny_computer_agent/actions.py
4.97 kB
| """The closed action vocabulary. | |
| A tiny model is only dependable when its output space is small and every mistake | |
| is cheap to catch. The harness therefore accepts exactly six verbs, and validates | |
| each field *before* anything touches the machine. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Any, Final, Mapping | |
| ACTION_KINDS: Final[tuple[str, ...]] = ("click", "type", "key", "scroll", "wait", "done") | |
| class ActionError(ValueError): | |
| """Raised when a brain proposes something outside the vocabulary.""" | |
| class Action: | |
| """One step of computer use: exactly one verb, validated on construction. | |
| label : click target *by element label* — the preferred form, because it | |
| survives window moves that would break raw pixel coordinates | |
| x, y : pixel fallback, used only when no label matches | |
| text : literal text to type | |
| keys : key chord, e.g. ``ctrl+s`` | |
| amount : scroll ticks; negative scrolls up | |
| seconds : how long to wait | |
| reason : the brain's stated rationale — kept for traces, never executed | |
| """ | |
| kind: str | |
| label: str | None = None | |
| x: int | None = None | |
| y: int | None = None | |
| text: str | None = None | |
| keys: str | None = None | |
| amount: int = 0 | |
| seconds: float = 0.0 | |
| reason: str = "" | |
| def __post_init__(self) -> None: | |
| if self.kind not in ACTION_KINDS: | |
| raise ActionError(f"unknown action {self.kind!r}; expected one of {ACTION_KINDS}") | |
| if self.kind == "click": | |
| if not self.label and not (self.x is not None and self.y is not None): | |
| raise ActionError("click needs a label, or x and y") | |
| if (self.x is not None and self.x < 0) or (self.y is not None and self.y < 0): | |
| raise ActionError("click coordinates must be non-negative") | |
| if self.kind == "type" and not self.text: | |
| raise ActionError("type needs non-empty text") | |
| if self.kind == "key" and not self.keys: | |
| raise ActionError("key needs a chord, e.g. 'ctrl+s'") | |
| if self.kind == "scroll" and self.amount == 0: | |
| raise ActionError("scroll needs a non-zero amount") | |
| if self.kind == "wait" and self.seconds <= 0: | |
| raise ActionError("wait needs a positive number of seconds") | |
| def describe(self) -> str: | |
| """One-line, human-readable form used in traces and dry-run logs.""" | |
| parts: list[str] = [self.kind] | |
| if self.label: | |
| parts.append(f"label={self.label!r}") | |
| if self.x is not None and self.y is not None: | |
| parts.append(f"at=({self.x},{self.y})") | |
| if self.text is not None: | |
| parts.append(f"text={self.text!r}") | |
| if self.keys: | |
| parts.append(f"keys={self.keys!r}") | |
| if self.kind == "scroll": | |
| parts.append(f"amount={self.amount}") | |
| if self.kind == "wait": | |
| parts.append(f"seconds={self.seconds}") | |
| if self.reason: | |
| parts.append(f"reason={self.reason!r}") | |
| return " ".join(parts) | |
| def to_dict(self) -> dict[str, Any]: | |
| return { | |
| "kind": self.kind, | |
| "label": self.label, | |
| "x": self.x, | |
| "y": self.y, | |
| "text": self.text, | |
| "keys": self.keys, | |
| "amount": self.amount, | |
| "seconds": self.seconds, | |
| "reason": self.reason, | |
| } | |
| ACTION_MENU: Final[str] = """\ | |
| Reply with exactly one JSON object and nothing else. Allowed verbs: | |
| {"kind": "click", "label": "<text of the element to click>", "reason": "..."} | |
| {"kind": "click", "x": 640, "y": 400, "reason": "..."} // pixels, only if no label fits | |
| {"kind": "type", "text": "...", "reason": "..."} | |
| {"kind": "key", "keys": "ctrl+s", "reason": "..."} | |
| {"kind": "scroll", "amount": -3, "reason": "..."} | |
| {"kind": "wait", "seconds": 1.5, "reason": "..."} | |
| {"kind": "done", "reason": "why the goal is met"} | |
| Rules: one action per reply; prefer a label over guessing pixels; reply "done" | |
| as soon as the goal is met; never invent new verbs.""" | |
| _FIELDS: Final[tuple[str, ...]] = ( | |
| "label", "x", "y", "text", "keys", "amount", "seconds", "reason", | |
| ) | |
| def parse_action(payload: Mapping[str, Any]) -> Action: | |
| """Turn a brain's raw mapping into a validated :class:`Action`. | |
| Unknown keys are ignored, so a chatty model cannot crash the loop. An unknown | |
| verb or a missing field raises :class:`ActionError`, which the loop records as | |
| a failed step *without executing anything*. | |
| """ | |
| if not isinstance(payload, Mapping): | |
| raise ActionError(f"expected a mapping, got {type(payload).__name__}") | |
| kind = payload.get("kind") | |
| if kind not in ACTION_KINDS: | |
| raise ActionError(f"unknown action {kind!r}; expected one of {ACTION_KINDS}") | |
| kwargs = {k: payload[k] for k in _FIELDS if k in payload} | |
| return Action(kind=str(kind), **kwargs) | |