v0.2.0 - token-efficient screenshots
Screenshots dominate the cost of driving a desktop, and not just once: every image stays in the conversation and is re-sent as history on every later turn. Claude bills vision in 28x28 patches (tokens = ceil(w/28) * ceil(h/28)), so this release attacks that directly.
Batch your steps
Pass steps - a list of action dicts - instead of one call per action, and the whole run shares one screenshot at the end:
That is 1,125 visual tokens instead of 5,625, and one round trip instead of five - the larger saving, since each avoided turn also avoids re-sending the entire transcript. A failing step stops the run, reports which step failed, and still returns the screen. Add "screenshot": false to skip the trailing image too.
Patch-aligned downscaling
A dimension that isn't a multiple of 28 pays for a partial patch row/column carrying almost no pixels. The default MAX_DIM moves 1280 -> 1260, so a 1920x1080 primary sends 1260x700 = exactly 45x25 patches = 1,125 tokens, versus 1,196 for 1280x720. 6% off for 1.5% fewer pixels. Each axis rounds independently, so to_real() now maps each with its own scale factor and stays exact.
Unchanged-screen suppression
If under COMPUTER_USE_CHANGE_THRESHOLD of pixels moved since the last image sent, the reply is a line of text instead of a screenshot. A real desktop never produces two byte-identical frames (clock, caret, hover states all jitter ~0.5-1% of pixels), so this is a threshold, not an equality check. After COMPUTER_USE_MAX_SKIPS suppressions in a row it force-sends one, so the model can't fly blind if it lost the earlier image to context compaction.
Also
- Tool description trimmed from ~833 to ~705 tokens (charged on every request).
- Documentation fix:
.env.examplepreviously claimedCOMPUTER_USE_IMAGE_FORMAT=jpeg"cuts tokens further". It does not - cost is by pixel dimensions, not bytes. JPEG only cuts payload size (~977KB -> ~141KB), which helps latency at some risk to small-text legibility.
Keep the long edge <= 2576px: an image returned inside a tool_result is rejected rather than downscaled when it exceeds the model's limit.
Install: pip install --upgrade realhands
New config: COMPUTER_USE_PATCH_ALIGN, COMPUTER_USE_CHANGE_THRESHOLD, COMPUTER_USE_MAX_SKIPS. All existing calls keep working unchanged.
{"steps": [{"action": "left_click", "coordinate": [420, 300]}, {"action": "type", "text": "hello@example.com"}, {"action": "key", "text": "Tab"}, {"action": "type", "text": "secret"}, {"action": "key", "text": "Return"}]}