use-computer 0.0.38__tar.gz → 0.0.40__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.
- {use_computer-0.0.38 → use_computer-0.0.40}/PKG-INFO +3 -1
- {use_computer-0.0.38 → use_computer-0.0.40}/pyproject.toml +3 -1
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/recordings.py +25 -11
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/providers/generic.py +20 -30
- use_computer-0.0.40/use_computer/agents/providers/tinker_backend.py +163 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/client.py +18 -8
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/__init__.py +2 -1
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/ubuntu.py +24 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/sandbox.py +14 -4
- {use_computer-0.0.38 → use_computer-0.0.40}/uv.lock +879 -29
- {use_computer-0.0.38 → use_computer-0.0.40}/.env.example +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/.gitignore +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/.pre-commit-config.yaml +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/README.md +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/scripts/smoke_wheel.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/accessibility.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/actions.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/agent.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/artifacts.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/desktop.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/final_state.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/prompts.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/sandbox.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/screenshots.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/base/trajectory.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/core.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/debug/README.md +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/debug/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/debug/actions.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/debug/agent.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/debug/models.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/ios/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/ios/agent.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/ios/tools.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/prompts/anthropic.txt +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/prompts/gemini.txt +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/prompts/ios.txt +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/prompts/openai.txt +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/prompts/pyautogui.txt +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/providers/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/providers/anthropic.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/providers/gemini.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/providers/openai.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/agents/types.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/ax_transpile.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/display.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/errors.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/agents.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/gateway.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/ios.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/macos.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/windows.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/setup_files.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/use_computer.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/utils/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/utils/verifier.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/ios/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/ios/apps.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/ios/environment.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/ios/input.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/macos/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/macos/keyboard.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/macos/mouse.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/models.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/parsers.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/py.typed +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/recording.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/resources/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/resources/accessibility.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/resources/display.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/resources/recording.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/resources/screenshot.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/retry.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/screenshot.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/simulators.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/pre_command.ps1 +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/pre_command.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/task.toml +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_ios.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_macos.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_macos_check.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_ubuntu.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_ubuntu_check.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_ubuntu_nograder.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_windows.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_windows_check.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/tasks/templates/test_windows_nograder.sh +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/windows/__init__.py +0 -0
- {use_computer-0.0.38 → use_computer-0.0.40}/use_computer/windows/shell.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: use-computer
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.40
|
|
4
4
|
Summary: Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu
|
|
5
5
|
Project-URL: Homepage, https://use.computer
|
|
6
6
|
Project-URL: Documentation, https://api.use.computer/docs
|
|
@@ -24,6 +24,8 @@ Requires-Dist: google-genai>=1; extra == 'agents'
|
|
|
24
24
|
Requires-Dist: litellm>=1; extra == 'agents'
|
|
25
25
|
Requires-Dist: openai>=1; extra == 'agents'
|
|
26
26
|
Requires-Dist: pillow>=10; extra == 'agents'
|
|
27
|
+
Requires-Dist: tinker-cookbook>=0.1.0; (python_full_version >= '3.11') and extra == 'agents'
|
|
28
|
+
Requires-Dist: tinker>=0.14.0; (python_full_version >= '3.11') and extra == 'agents'
|
|
27
29
|
Provides-Extra: harbor
|
|
28
30
|
Requires-Dist: harbor>=0.4.0; (python_version >= '3.12') and extra == 'harbor'
|
|
29
31
|
Requires-Dist: pyyaml>=6; extra == 'harbor'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "use-computer"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.40"
|
|
8
8
|
description = "Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -30,6 +30,8 @@ agents = [
|
|
|
30
30
|
"anthropic>=0.86",
|
|
31
31
|
"litellm>=1",
|
|
32
32
|
"google-genai>=1",
|
|
33
|
+
"tinker>=0.14.0 ; python_full_version >= '3.11'",
|
|
34
|
+
"tinker-cookbook>=0.1.0 ; python_full_version >= '3.11'",
|
|
33
35
|
]
|
|
34
36
|
harbor = [
|
|
35
37
|
"harbor>=0.4.0; python_version >= '3.12'",
|
|
@@ -11,6 +11,11 @@ from use_computer.sandbox import AsyncSandbox
|
|
|
11
11
|
|
|
12
12
|
class RecordingMixin:
|
|
13
13
|
async def start_recording(self: Any, sandbox: AsyncSandbox) -> None:
|
|
14
|
+
if getattr(getattr(sandbox, "type", None), "value", "") == "ios":
|
|
15
|
+
try:
|
|
16
|
+
await sandbox.screenshot.take_full_screen()
|
|
17
|
+
except Exception as e:
|
|
18
|
+
self.logger.warning(f"iOS recording warm-up screenshot failed: {e}")
|
|
14
19
|
try:
|
|
15
20
|
result = await sandbox.recording.start(name="trial")
|
|
16
21
|
self._recording_id = result.id
|
|
@@ -22,6 +27,7 @@ class RecordingMixin:
|
|
|
22
27
|
if not self._recording_id:
|
|
23
28
|
return
|
|
24
29
|
await asyncio.sleep(3.0)
|
|
30
|
+
rec_path = self.logs_dir / "recording.mp4"
|
|
25
31
|
try:
|
|
26
32
|
info = await sandbox.recording.stop(self._recording_id)
|
|
27
33
|
self.logger.info(
|
|
@@ -33,16 +39,24 @@ class RecordingMixin:
|
|
|
33
39
|
|
|
34
40
|
await asyncio.sleep(2)
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
self.logger.info(f"Downloading recording → {rec_path}")
|
|
38
|
-
t_dl = time.monotonic()
|
|
39
|
-
await sandbox.recording.download(self._recording_id, str(rec_path), timeout=180.0)
|
|
40
|
-
dl_elapsed = time.monotonic() - t_dl
|
|
41
|
-
local_size = rec_path.stat().st_size
|
|
42
|
-
self.logger.info(
|
|
43
|
-
f"Recording saved: {rec_path} "
|
|
44
|
-
f"local_size={local_size}B ({local_size / (1024 * 1024):.2f} MB) "
|
|
45
|
-
f"download_elapsed={dl_elapsed:.1f}s"
|
|
46
|
-
)
|
|
42
|
+
await self._download_recording(sandbox, rec_path)
|
|
47
43
|
except Exception as e:
|
|
48
44
|
self.logger.warning(f"Recording save failed: {type(e).__name__}: {e}")
|
|
45
|
+
try:
|
|
46
|
+
await self._download_recording(sandbox, rec_path)
|
|
47
|
+
except Exception as fallback:
|
|
48
|
+
self.logger.warning(
|
|
49
|
+
f"Recording fallback download failed: {type(fallback).__name__}: {fallback}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def _download_recording(self: Any, sandbox: AsyncSandbox, rec_path) -> None:
|
|
53
|
+
self.logger.info(f"Downloading recording → {rec_path}")
|
|
54
|
+
t_dl = time.monotonic()
|
|
55
|
+
await sandbox.recording.download(self._recording_id, str(rec_path), timeout=180.0)
|
|
56
|
+
dl_elapsed = time.monotonic() - t_dl
|
|
57
|
+
local_size = rec_path.stat().st_size
|
|
58
|
+
self.logger.info(
|
|
59
|
+
f"Recording saved: {rec_path} "
|
|
60
|
+
f"local_size={local_size}B ({local_size / (1024 * 1024):.2f} MB) "
|
|
61
|
+
f"download_elapsed={dl_elapsed:.1f}s"
|
|
62
|
+
)
|
|
@@ -83,6 +83,11 @@ class GenericCUAAgent(BaseCUAAgent):
|
|
|
83
83
|
coord_mode: str | None = None,
|
|
84
84
|
reasoning_effort: str | None = None,
|
|
85
85
|
reasoning_history: str | None = None,
|
|
86
|
+
renderer_name: str | None = None,
|
|
87
|
+
model_path: str | None = None,
|
|
88
|
+
temperature: float = 1.0,
|
|
89
|
+
top_p: float = 0.95,
|
|
90
|
+
top_k: int = -1,
|
|
86
91
|
**kwargs: Any,
|
|
87
92
|
):
|
|
88
93
|
super().__init__(logs_dir=logs_dir, model_name=model_name, **kwargs)
|
|
@@ -106,7 +111,12 @@ class GenericCUAAgent(BaseCUAAgent):
|
|
|
106
111
|
ReasoningHistory(reasoning_history) if reasoning_history is not None else None
|
|
107
112
|
),
|
|
108
113
|
).as_dict()
|
|
109
|
-
self.
|
|
114
|
+
self._tinker_backend = None
|
|
115
|
+
self._renderer_name = renderer_name
|
|
116
|
+
self._model_path = model_path
|
|
117
|
+
self._temperature = temperature
|
|
118
|
+
self._top_p = top_p
|
|
119
|
+
self._top_k = top_k
|
|
110
120
|
# Coordinate convention emitted by the model:
|
|
111
121
|
# "api_pixel" — Kimi default: 0..1 floats OR pixel ints in API space (1280x800)
|
|
112
122
|
# "normalized_1000" — Qwen-VL default: 0..1000 ints as per-mille of screen
|
|
@@ -168,39 +178,19 @@ class GenericCUAAgent(BaseCUAAgent):
|
|
|
168
178
|
async def _tinker_completion(
|
|
169
179
|
self, model: str, messages: list[dict], max_tokens: int
|
|
170
180
|
) -> tuple[str, int, int]:
|
|
171
|
-
if self.
|
|
172
|
-
from
|
|
181
|
+
if self._tinker_backend is None:
|
|
182
|
+
from use_computer.agents.providers.tinker_backend import TinkerBackend
|
|
173
183
|
|
|
174
|
-
self.
|
|
184
|
+
self._tinker_backend = TinkerBackend(
|
|
175
185
|
model_name=model,
|
|
186
|
+
model_path=self._model_path,
|
|
187
|
+
renderer_name=self._renderer_name,
|
|
188
|
+
temperature=self._temperature,
|
|
189
|
+
top_p=self._top_p,
|
|
190
|
+
top_k=self._top_k,
|
|
176
191
|
max_tokens=max_tokens,
|
|
177
192
|
)
|
|
178
|
-
|
|
179
|
-
# TinkerLLM.call() expects (prompt, message_history)
|
|
180
|
-
# Split system + history from the final user message
|
|
181
|
-
history = messages[:-1] if len(messages) > 1 else []
|
|
182
|
-
prompt = ""
|
|
183
|
-
last_msg = messages[-1] if messages else {"content": ""}
|
|
184
|
-
content = last_msg.get("content")
|
|
185
|
-
if isinstance(content, str):
|
|
186
|
-
prompt = content
|
|
187
|
-
elif isinstance(content, list):
|
|
188
|
-
prompt = " ".join(
|
|
189
|
-
p.get("text", "")
|
|
190
|
-
for p in content
|
|
191
|
-
if isinstance(p, dict) and p.get("type") == "text"
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
response = await self._tinker_llm.call(prompt=prompt, message_history=history)
|
|
195
|
-
content = response.content or ""
|
|
196
|
-
text = (
|
|
197
|
-
content
|
|
198
|
-
if isinstance(content, str)
|
|
199
|
-
else "".join(p.get("text", "") if isinstance(p, dict) else str(p) for p in content)
|
|
200
|
-
)
|
|
201
|
-
prompt_tokens = response.usage.prompt_tokens if response.usage else 0
|
|
202
|
-
completion_tokens = response.usage.completion_tokens if response.usage else 0
|
|
203
|
-
return text, prompt_tokens, completion_tokens
|
|
193
|
+
return await self._tinker_backend.complete(messages, max_tokens)
|
|
204
194
|
|
|
205
195
|
async def _run_agent(
|
|
206
196
|
self,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Image-aware Tinker sampling backend for the generic CUA agent.
|
|
2
|
+
|
|
3
|
+
harbor's TinkerLLM forwards text only, so a vision agent loses its screenshots
|
|
4
|
+
on the tinker path. This backend builds the generation prompt through a
|
|
5
|
+
tinker-cookbook renderer with the model's image processor attached, so the
|
|
6
|
+
OpenAI-style `image_url` parts the generic agent emits reach the model.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import base64
|
|
13
|
+
import importlib
|
|
14
|
+
import io
|
|
15
|
+
from typing import Any, cast
|
|
16
|
+
|
|
17
|
+
from PIL import Image
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TinkerBackend:
|
|
21
|
+
"""Vision-capable tinker sampler that consumes OpenAI chat messages."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
model_name: str,
|
|
27
|
+
model_path: str | None = None,
|
|
28
|
+
renderer_name: str | None = None,
|
|
29
|
+
temperature: float = 1.0,
|
|
30
|
+
top_p: float = 0.95,
|
|
31
|
+
top_k: int = -1,
|
|
32
|
+
max_tokens: int = 2048,
|
|
33
|
+
retry_times: int = 5,
|
|
34
|
+
retry_interval: float = 5.0,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.model_name = model_name
|
|
37
|
+
self.model_path = model_path
|
|
38
|
+
self.renderer_name = renderer_name
|
|
39
|
+
self.temperature = temperature
|
|
40
|
+
self.top_p = top_p
|
|
41
|
+
self.top_k = top_k
|
|
42
|
+
self.max_tokens = max_tokens
|
|
43
|
+
self.retry_times = retry_times
|
|
44
|
+
self.retry_interval = retry_interval
|
|
45
|
+
self._sampling_client = None
|
|
46
|
+
self._renderer = None
|
|
47
|
+
|
|
48
|
+
async def _ensure_ready(self) -> None:
|
|
49
|
+
if self._sampling_client is not None and self._renderer is not None:
|
|
50
|
+
return
|
|
51
|
+
try:
|
|
52
|
+
tinker = importlib.import_module("tinker")
|
|
53
|
+
get_image_processor = importlib.import_module(
|
|
54
|
+
"tinker_cookbook.image_processing_utils"
|
|
55
|
+
).get_image_processor
|
|
56
|
+
get_model_attributes = importlib.import_module(
|
|
57
|
+
"tinker_cookbook.model_info"
|
|
58
|
+
).get_model_attributes
|
|
59
|
+
get_renderer = importlib.import_module("tinker_cookbook.renderers").get_renderer
|
|
60
|
+
get_tokenizer = importlib.import_module("tinker_cookbook.tokenizer_utils").get_tokenizer
|
|
61
|
+
except ImportError as exc:
|
|
62
|
+
raise ImportError(
|
|
63
|
+
"TinkerBackend requires `tinker` and `tinker-cookbook`. "
|
|
64
|
+
"Install them with `uv pip install harbor[tinker]`."
|
|
65
|
+
) from exc
|
|
66
|
+
|
|
67
|
+
service_client = tinker.ServiceClient()
|
|
68
|
+
if self.model_path:
|
|
69
|
+
self._sampling_client = await service_client.create_sampling_client_async(
|
|
70
|
+
model_path=self.model_path
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
self._sampling_client = await service_client.create_sampling_client_async(
|
|
74
|
+
base_model=self.model_name
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
tokenizer = get_tokenizer(self.model_name)
|
|
78
|
+
attrs = get_model_attributes(self.model_name)
|
|
79
|
+
image_processor = get_image_processor(self.model_name) if attrs.is_vl else None
|
|
80
|
+
renderer_name = self.renderer_name or _default_renderer(self.model_name)
|
|
81
|
+
self._renderer = get_renderer(
|
|
82
|
+
renderer_name,
|
|
83
|
+
tokenizer,
|
|
84
|
+
image_processor=image_processor,
|
|
85
|
+
model_name=self.model_name,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
async def complete(
|
|
89
|
+
self, messages: list[dict[str, Any]], max_tokens: int
|
|
90
|
+
) -> tuple[str, int, int]:
|
|
91
|
+
"""Sample one response; returns (text, prompt_tokens, completion_tokens)."""
|
|
92
|
+
await self._ensure_ready()
|
|
93
|
+
assert self._sampling_client is not None
|
|
94
|
+
assert self._renderer is not None
|
|
95
|
+
|
|
96
|
+
tinker = importlib.import_module("tinker")
|
|
97
|
+
get_text_content = importlib.import_module("tinker_cookbook.renderers").get_text_content
|
|
98
|
+
|
|
99
|
+
model_input = self._renderer.build_generation_prompt(
|
|
100
|
+
cast(Any, _to_tinker_messages(messages))
|
|
101
|
+
)
|
|
102
|
+
stop = self._renderer.get_stop_sequences()
|
|
103
|
+
|
|
104
|
+
last_error: Exception | None = None
|
|
105
|
+
for attempt in range(self.retry_times):
|
|
106
|
+
try:
|
|
107
|
+
response = await self._sampling_client.sample_async(
|
|
108
|
+
prompt=model_input,
|
|
109
|
+
num_samples=1,
|
|
110
|
+
sampling_params=tinker.SamplingParams(
|
|
111
|
+
temperature=self.temperature,
|
|
112
|
+
top_p=self.top_p,
|
|
113
|
+
top_k=self.top_k,
|
|
114
|
+
max_tokens=max_tokens,
|
|
115
|
+
stop=stop,
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
sequence = response.sequences[0]
|
|
119
|
+
parsed_message, _termination = self._renderer.parse_response(sequence.tokens)
|
|
120
|
+
return get_text_content(parsed_message), model_input.length, len(sequence.tokens)
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
last_error = exc
|
|
123
|
+
if attempt == self.retry_times - 1:
|
|
124
|
+
break
|
|
125
|
+
await asyncio.sleep(self.retry_interval)
|
|
126
|
+
assert last_error is not None
|
|
127
|
+
raise last_error
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _to_tinker_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
131
|
+
"""Convert OpenAI chat messages to tinker-cookbook messages (text + PIL images)."""
|
|
132
|
+
converted: list[dict[str, Any]] = []
|
|
133
|
+
for message in messages:
|
|
134
|
+
content = message.get("content")
|
|
135
|
+
if isinstance(content, str):
|
|
136
|
+
converted.append({"role": message["role"], "content": content})
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
parts: list[dict[str, Any]] = []
|
|
140
|
+
for part in content or []:
|
|
141
|
+
if part.get("type") == "text":
|
|
142
|
+
parts.append({"type": "text", "text": part.get("text", "")})
|
|
143
|
+
elif part.get("type") == "image_url":
|
|
144
|
+
url = part.get("image_url", {}).get("url", "")
|
|
145
|
+
b64 = url.split(",", 1)[1] if "," in url else url
|
|
146
|
+
image = Image.open(io.BytesIO(base64.b64decode(b64))).convert("RGB")
|
|
147
|
+
parts.append({"type": "image", "image": image})
|
|
148
|
+
if len(parts) == 1 and parts[0]["type"] == "text":
|
|
149
|
+
converted.append({"role": message["role"], "content": parts[0]["text"]})
|
|
150
|
+
else:
|
|
151
|
+
converted.append({"role": message["role"], "content": parts})
|
|
152
|
+
return converted
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _default_renderer(model_name: str) -> str:
|
|
156
|
+
get_recommended_renderer_names = importlib.import_module(
|
|
157
|
+
"tinker_cookbook.model_info"
|
|
158
|
+
).get_recommended_renderer_names
|
|
159
|
+
|
|
160
|
+
recommended = get_recommended_renderer_names(model_name)
|
|
161
|
+
if "qwen3_5_disable_thinking" in recommended:
|
|
162
|
+
return "qwen3_5_disable_thinking"
|
|
163
|
+
return recommended[0]
|
|
@@ -53,6 +53,12 @@ def _normalize_sandbox_type(type: SandboxTypeInput) -> SandboxType:
|
|
|
53
53
|
return SandboxType(type)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def _expand_core_simulator_id(value: str, kind: str) -> str:
|
|
57
|
+
if not value or value.startswith("com.apple."):
|
|
58
|
+
return value
|
|
59
|
+
return f"com.apple.CoreSimulator.{kind}.{value}"
|
|
60
|
+
|
|
61
|
+
|
|
56
62
|
@dataclass
|
|
57
63
|
class RunStatus:
|
|
58
64
|
"""Status of an ad-hoc or model run."""
|
|
@@ -183,7 +189,7 @@ class Computer:
|
|
|
183
189
|
|
|
184
190
|
Args:
|
|
185
191
|
type: SandboxType.MACOS (default), IOS, WINDOWS, UBUNTU, or the raw API string.
|
|
186
|
-
host: Pin sandbox to a specific host machine
|
|
192
|
+
host: Pin sandbox to a specific host machine; for iOS this must be a Mini IP.
|
|
187
193
|
family: Simulator family enum/alias, for example SimulatorFamily.TV.
|
|
188
194
|
device_type: Simulator device type identifier, for exact pinning.
|
|
189
195
|
runtime: Simulator runtime identifier, for exact pinning.
|
|
@@ -195,7 +201,7 @@ class Computer:
|
|
|
195
201
|
"""
|
|
196
202
|
sandbox_type = _normalize_sandbox_type(type)
|
|
197
203
|
body: dict = {"type": sandbox_type.value}
|
|
198
|
-
if host:
|
|
204
|
+
if host and sandbox_type != SandboxType.IOS:
|
|
199
205
|
body["host"] = host
|
|
200
206
|
if sandbox_type in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
201
207
|
if version and snapshot:
|
|
@@ -207,14 +213,16 @@ class Computer:
|
|
|
207
213
|
if resources:
|
|
208
214
|
body["resources"] = resources
|
|
209
215
|
if sandbox_type == SandboxType.IOS:
|
|
216
|
+
if host:
|
|
217
|
+
body["mini_ip"] = host
|
|
210
218
|
if family and (not device_type or not runtime):
|
|
211
219
|
choice = select_simulator(self.platforms(), family)
|
|
212
220
|
device_type = device_type or choice.device_type
|
|
213
221
|
runtime = runtime or choice.runtime
|
|
214
222
|
if device_type:
|
|
215
|
-
body["device_type"] = device_type
|
|
223
|
+
body["device_type"] = _expand_core_simulator_id(device_type, "SimDeviceType")
|
|
216
224
|
if runtime:
|
|
217
|
-
body["runtime"] = runtime
|
|
225
|
+
body["runtime"] = _expand_core_simulator_id(runtime, "SimRuntime")
|
|
218
226
|
|
|
219
227
|
resp = self._http.post("/v1/sandboxes", json=body, timeout=180.0)
|
|
220
228
|
resp.raise_for_status()
|
|
@@ -425,7 +433,7 @@ class AsyncComputer:
|
|
|
425
433
|
|
|
426
434
|
Args:
|
|
427
435
|
type: SandboxType.MACOS (default), IOS, WINDOWS, UBUNTU, or the raw API string.
|
|
428
|
-
host: Pin sandbox to a specific host machine
|
|
436
|
+
host: Pin sandbox to a specific host machine; for iOS this must be a Mini IP.
|
|
429
437
|
family: Simulator family enum/alias, for example SimulatorFamily.TV.
|
|
430
438
|
device_type: Simulator device type identifier, for exact pinning.
|
|
431
439
|
runtime: Simulator runtime identifier, for exact pinning.
|
|
@@ -437,7 +445,7 @@ class AsyncComputer:
|
|
|
437
445
|
"""
|
|
438
446
|
sandbox_type = _normalize_sandbox_type(type)
|
|
439
447
|
body: dict = {"type": sandbox_type.value}
|
|
440
|
-
if host:
|
|
448
|
+
if host and sandbox_type != SandboxType.IOS:
|
|
441
449
|
body["host"] = host
|
|
442
450
|
if sandbox_type in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
443
451
|
if version and snapshot:
|
|
@@ -449,14 +457,16 @@ class AsyncComputer:
|
|
|
449
457
|
if resources:
|
|
450
458
|
body["resources"] = resources
|
|
451
459
|
if sandbox_type == SandboxType.IOS:
|
|
460
|
+
if host:
|
|
461
|
+
body["mini_ip"] = host
|
|
452
462
|
if family and (not device_type or not runtime):
|
|
453
463
|
choice = select_simulator(await self.platforms(), family)
|
|
454
464
|
device_type = device_type or choice.device_type
|
|
455
465
|
runtime = runtime or choice.runtime
|
|
456
466
|
if device_type:
|
|
457
|
-
body["device_type"] = device_type
|
|
467
|
+
body["device_type"] = _expand_core_simulator_id(device_type, "SimDeviceType")
|
|
458
468
|
if runtime:
|
|
459
|
-
body["runtime"] = runtime
|
|
469
|
+
body["runtime"] = _expand_core_simulator_id(runtime, "SimRuntime")
|
|
460
470
|
|
|
461
471
|
resp = await self._http.post("/v1/sandboxes", json=body, timeout=180.0)
|
|
462
472
|
resp.raise_for_status()
|
{use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/__init__.py
RENAMED
|
@@ -104,12 +104,13 @@ class IOSAdapter(PlatformAdapter):
|
|
|
104
104
|
env.logger.info(f"ios pin: device_type={device_type!r} runtime={runtime!r}")
|
|
105
105
|
return await env._client.create(
|
|
106
106
|
type="ios",
|
|
107
|
+
host=env._host,
|
|
107
108
|
device_type=device_type,
|
|
108
109
|
runtime=runtime,
|
|
109
110
|
)
|
|
110
111
|
|
|
111
112
|
async def setup(self, env: Any) -> None:
|
|
112
|
-
await asyncio.sleep(
|
|
113
|
+
await asyncio.sleep(2.0)
|
|
113
114
|
|
|
114
115
|
async def exec(
|
|
115
116
|
self,
|
{use_computer-0.0.38 → use_computer-0.0.40}/use_computer/harbor/environment/platforms/ubuntu.py
RENAMED
|
@@ -109,6 +109,8 @@ async def exec_local(
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
async def run_setup(env: Any) -> None:
|
|
112
|
+
await wake_desktop(env)
|
|
113
|
+
|
|
112
114
|
setup_dir = env._task_dir / "tests" / "setup"
|
|
113
115
|
|
|
114
116
|
for setup_file in read_setup_files(setup_dir):
|
|
@@ -139,6 +141,28 @@ async def run_setup(env: Any) -> None:
|
|
|
139
141
|
env.logger.info("task setup complete")
|
|
140
142
|
|
|
141
143
|
|
|
144
|
+
async def wake_desktop(env: Any) -> None:
|
|
145
|
+
script = """
|
|
146
|
+
echo 'user:lume' | sudo chpasswd || true
|
|
147
|
+
export DISPLAY=:0
|
|
148
|
+
export XAUTHORITY=/home/user/.Xauthority
|
|
149
|
+
gsettings set org.gnome.desktop.screensaver lock-enabled false || true
|
|
150
|
+
gsettings set org.gnome.desktop.session idle-delay 0 || true
|
|
151
|
+
xset s off -dpms || true
|
|
152
|
+
xset dpms force on || true
|
|
153
|
+
xdotool key Escape || true
|
|
154
|
+
sleep 1
|
|
155
|
+
xdotool key ctrl+a || true
|
|
156
|
+
xdotool type lume || true
|
|
157
|
+
xdotool key Return || true
|
|
158
|
+
sleep 3
|
|
159
|
+
xset dpms force on || true
|
|
160
|
+
"""
|
|
161
|
+
result = await env.ubuntu_sandbox.shell.run(script, shell="bash", timeout=60)
|
|
162
|
+
if result.return_code != 0:
|
|
163
|
+
env.logger.warning(f"ubuntu desktop wake rc={result.return_code}: {result.stderr or ''}")
|
|
164
|
+
|
|
165
|
+
|
|
142
166
|
async def upload_file(env: Any, source: Path | str, target: str) -> None:
|
|
143
167
|
await env.ubuntu_sandbox.upload(str(source), target)
|
|
144
168
|
|
|
@@ -236,7 +236,11 @@ class MacOSSandbox(Sandbox):
|
|
|
236
236
|
return ActResult(data=resp.json())
|
|
237
237
|
|
|
238
238
|
def exec_ssh(self, command: str, timeout: int = 120) -> ExecResult:
|
|
239
|
-
resp = self._http.post(
|
|
239
|
+
resp = self._http.post(
|
|
240
|
+
f"{self._prefix}/exec",
|
|
241
|
+
json={"command": command, "timeout": timeout},
|
|
242
|
+
timeout=timeout,
|
|
243
|
+
)
|
|
240
244
|
resp.raise_for_status()
|
|
241
245
|
return ExecResult.from_dict(resp.json())
|
|
242
246
|
|
|
@@ -254,7 +258,9 @@ class MacOSSandbox(Sandbox):
|
|
|
254
258
|
command if you need brew binaries.
|
|
255
259
|
"""
|
|
256
260
|
resp = self._http.post(
|
|
257
|
-
f"{self._prefix}/exec_ax",
|
|
261
|
+
f"{self._prefix}/exec_ax",
|
|
262
|
+
json={"command": command, "timeout": timeout},
|
|
263
|
+
timeout=timeout,
|
|
258
264
|
)
|
|
259
265
|
resp.raise_for_status()
|
|
260
266
|
return ExecResult.from_dict(resp.json())
|
|
@@ -650,7 +656,9 @@ class AsyncMacOSSandbox(AsyncSandbox):
|
|
|
650
656
|
|
|
651
657
|
async def exec_ssh(self, command: str, timeout: int = 120) -> ExecResult:
|
|
652
658
|
resp = await self._http.post(
|
|
653
|
-
f"{self._prefix}/exec",
|
|
659
|
+
f"{self._prefix}/exec",
|
|
660
|
+
json={"command": command, "timeout": timeout},
|
|
661
|
+
timeout=timeout,
|
|
654
662
|
)
|
|
655
663
|
resp.raise_for_status()
|
|
656
664
|
return ExecResult.from_dict(resp.json())
|
|
@@ -658,7 +666,9 @@ class AsyncMacOSSandbox(AsyncSandbox):
|
|
|
658
666
|
async def exec_ax(self, command: str, timeout: int = 120) -> ExecResult:
|
|
659
667
|
"""Async version of exec_ax — see MacOSSandbox.exec_ax for details."""
|
|
660
668
|
resp = await self._http.post(
|
|
661
|
-
f"{self._prefix}/exec_ax",
|
|
669
|
+
f"{self._prefix}/exec_ax",
|
|
670
|
+
json={"command": command, "timeout": timeout},
|
|
671
|
+
timeout=timeout,
|
|
662
672
|
)
|
|
663
673
|
resp.raise_for_status()
|
|
664
674
|
return ExecResult.from_dict(resp.json())
|