orgo 0.0.35__py3-none-any.whl → 0.0.38__py3-none-any.whl
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.
- orgo/computer.py +259 -154
- orgo/prompt.py +775 -379
- {orgo-0.0.35.dist-info → orgo-0.0.38.dist-info}/METADATA +5 -3
- {orgo-0.0.35.dist-info → orgo-0.0.38.dist-info}/RECORD +6 -6
- {orgo-0.0.35.dist-info → orgo-0.0.38.dist-info}/WHEEL +0 -0
- {orgo-0.0.35.dist-info → orgo-0.0.38.dist-info}/top_level.txt +0 -0
orgo/computer.py
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Orgo Computer - Control virtual computers with AI.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from orgo import Computer
|
|
6
|
+
|
|
7
|
+
computer = Computer(project="your-project")
|
|
8
|
+
computer.prompt("Open Firefox and search for AI news")
|
|
9
|
+
"""
|
|
10
|
+
|
|
2
11
|
import os as operating_system
|
|
3
12
|
import base64
|
|
4
13
|
import logging
|
|
5
14
|
import uuid
|
|
6
15
|
import io
|
|
16
|
+
import random
|
|
7
17
|
from typing import Dict, List, Any, Optional, Callable, Literal, Union
|
|
8
18
|
from PIL import Image
|
|
9
19
|
import requests
|
|
@@ -14,7 +24,48 @@ from .prompt import get_provider
|
|
|
14
24
|
|
|
15
25
|
logger = logging.getLogger(__name__)
|
|
16
26
|
|
|
27
|
+
|
|
28
|
+
def _generate_computer_name() -> str:
|
|
29
|
+
"""Generate a random computer name like 'computer-1568'"""
|
|
30
|
+
return f"computer-{random.randint(1000, 9999)}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print_success(message: str):
|
|
34
|
+
"""Print a success message with nice formatting"""
|
|
35
|
+
print(f"✓ {message}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _print_error(message: str):
|
|
39
|
+
"""Print an error message with nice formatting"""
|
|
40
|
+
print(f"✗ {message}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _print_info(message: str):
|
|
44
|
+
"""Print an info message with nice formatting"""
|
|
45
|
+
print(f"→ {message}")
|
|
46
|
+
|
|
47
|
+
|
|
17
48
|
class Computer:
|
|
49
|
+
"""
|
|
50
|
+
Control an Orgo virtual computer.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
# Create computer in new/existing project
|
|
54
|
+
computer = Computer(project="my-project")
|
|
55
|
+
|
|
56
|
+
# Create with specific name
|
|
57
|
+
computer = Computer(project="my-project", name="dev-machine")
|
|
58
|
+
|
|
59
|
+
# Connect to existing computer by ID
|
|
60
|
+
computer = Computer(computer_id="abc123")
|
|
61
|
+
|
|
62
|
+
# AI control (uses Orgo by default)
|
|
63
|
+
computer.prompt("Open Firefox")
|
|
64
|
+
|
|
65
|
+
# AI control with Anthropic directly
|
|
66
|
+
computer.prompt("Open Firefox", provider="anthropic")
|
|
67
|
+
"""
|
|
68
|
+
|
|
18
69
|
def __init__(self,
|
|
19
70
|
project: Optional[Union[str, 'Project']] = None,
|
|
20
71
|
name: Optional[str] = None,
|
|
@@ -26,251 +77,252 @@ class Computer:
|
|
|
26
77
|
cpu: Optional[Literal[1, 2, 4, 8, 16]] = None,
|
|
27
78
|
os: Optional[Literal["linux", "windows"]] = None,
|
|
28
79
|
gpu: Optional[Literal["none", "a10", "l40s", "a100-40gb", "a100-80gb"]] = None,
|
|
29
|
-
image: Optional[Union[str, Any]] = None
|
|
80
|
+
image: Optional[Union[str, Any]] = None,
|
|
81
|
+
verbose: bool = True):
|
|
30
82
|
"""
|
|
31
83
|
Initialize an Orgo virtual computer.
|
|
32
84
|
|
|
33
85
|
Args:
|
|
34
|
-
project: Project name
|
|
35
|
-
name: Computer name
|
|
36
|
-
computer_id:
|
|
86
|
+
project: Project name or Project instance (creates if doesn't exist)
|
|
87
|
+
name: Computer name (auto-generated if not provided)
|
|
88
|
+
computer_id: Connect to existing computer by ID
|
|
37
89
|
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
38
|
-
base_api_url: Custom API URL
|
|
39
|
-
ram/memory: RAM in GB (1, 2, 4, 8, 16, 32,
|
|
40
|
-
cpu: CPU cores (1, 2, 4, 8,
|
|
41
|
-
os:
|
|
42
|
-
gpu:
|
|
43
|
-
image: Custom image reference
|
|
44
|
-
|
|
45
|
-
Examples:
|
|
46
|
-
# Create computer in new project
|
|
47
|
-
computer = Computer(ram=4, cpu=2)
|
|
48
|
-
|
|
49
|
-
# Create computer with custom image
|
|
50
|
-
forge = Forge(org_id="myorg", project_id="myproj").base("ubuntu").run("echo hello")
|
|
51
|
-
computer = Computer(image=forge)
|
|
52
|
-
|
|
53
|
-
# Create computer in existing project
|
|
54
|
-
computer = Computer(project="manus", ram=4, cpu=2)
|
|
55
|
-
|
|
56
|
-
# Connect to existing computer by ID
|
|
57
|
-
computer = Computer(computer_id="11c4fd46-e069-4c32-be65-f82d9f87b9b8")
|
|
90
|
+
base_api_url: Custom API URL
|
|
91
|
+
ram/memory: RAM in GB (1, 2, 4, 8, 16, 32, 64)
|
|
92
|
+
cpu: CPU cores (1, 2, 4, 8, 16)
|
|
93
|
+
os: "linux" or "windows"
|
|
94
|
+
gpu: "none", "a10", "l40s", "a100-40gb", "a100-80gb"
|
|
95
|
+
image: Custom image reference or Forge object
|
|
96
|
+
verbose: Show console output (default: True)
|
|
58
97
|
"""
|
|
59
98
|
self.api_key = api_key or operating_system.environ.get("ORGO_API_KEY")
|
|
60
99
|
self.base_api_url = base_api_url
|
|
61
100
|
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
101
|
+
self.verbose = verbose
|
|
62
102
|
|
|
63
|
-
# Handle memory parameter as an alias for ram
|
|
64
103
|
if ram is None and memory is not None:
|
|
65
104
|
ram = memory
|
|
66
105
|
|
|
67
|
-
# Store configuration
|
|
68
106
|
self.os = os or "linux"
|
|
69
107
|
self.ram = ram or 2
|
|
70
108
|
self.cpu = cpu or 2
|
|
71
109
|
self.gpu = gpu or "none"
|
|
72
|
-
|
|
73
|
-
# Handle image
|
|
74
110
|
self.image = image
|
|
111
|
+
|
|
75
112
|
if hasattr(self.image, 'build') and callable(self.image.build):
|
|
76
|
-
|
|
113
|
+
if self.verbose:
|
|
114
|
+
_print_info("Building image from Forge object...")
|
|
77
115
|
self.image = self.image.build()
|
|
78
116
|
|
|
79
117
|
if computer_id:
|
|
80
|
-
|
|
81
|
-
# Just store the computer ID, no API call needed
|
|
82
118
|
self.computer_id = computer_id
|
|
83
119
|
self.name = name
|
|
84
120
|
self.project_id = None
|
|
85
121
|
self.project_name = None
|
|
86
|
-
|
|
122
|
+
if self.verbose:
|
|
123
|
+
_print_success(f"Connected to computer: {self.computer_id}")
|
|
87
124
|
elif project:
|
|
88
|
-
# Work with specified project
|
|
89
125
|
if isinstance(project, str):
|
|
90
|
-
# Project name provided
|
|
91
126
|
self.project_name = project
|
|
92
127
|
self._initialize_with_project_name(project, name)
|
|
93
128
|
else:
|
|
94
|
-
# Project instance provided
|
|
95
129
|
from .project import Project as ProjectClass
|
|
96
130
|
if isinstance(project, ProjectClass):
|
|
97
131
|
self.project_name = project.name
|
|
98
132
|
self.project_id = project.id
|
|
99
133
|
self._initialize_with_project_instance(project, name)
|
|
100
134
|
else:
|
|
101
|
-
raise ValueError("project must be a string
|
|
135
|
+
raise ValueError("project must be a string or Project instance")
|
|
102
136
|
else:
|
|
103
|
-
# No project specified, create a new one
|
|
104
137
|
self._create_new_project_and_computer(name)
|
|
105
138
|
|
|
139
|
+
# =========================================================================
|
|
140
|
+
# Initialization Helpers
|
|
141
|
+
# =========================================================================
|
|
142
|
+
|
|
106
143
|
def _initialize_with_project_name(self, project_name: str, computer_name: Optional[str]):
|
|
107
|
-
"""Initialize with
|
|
144
|
+
"""Initialize computer with project name (create project if needed)"""
|
|
108
145
|
try:
|
|
109
146
|
# Try to get existing project
|
|
110
147
|
project = self.api.get_project_by_name(project_name)
|
|
111
148
|
self.project_id = project.get("id")
|
|
112
149
|
|
|
113
|
-
#
|
|
114
|
-
|
|
150
|
+
# If no computer name specified, generate one
|
|
151
|
+
if not computer_name:
|
|
152
|
+
computer_name = _generate_computer_name()
|
|
153
|
+
|
|
154
|
+
# Create the computer in this project
|
|
155
|
+
self._create_computer(self.project_id, computer_name, project_name)
|
|
115
156
|
|
|
116
|
-
if computer_name:
|
|
117
|
-
# Look for specific computer
|
|
118
|
-
existing = next((c for c in computers if c.get("name") == computer_name), None)
|
|
119
|
-
if existing:
|
|
120
|
-
self._connect_to_existing_computer(existing)
|
|
121
|
-
else:
|
|
122
|
-
# Create new computer with specified name
|
|
123
|
-
self._create_computer(self.project_id, computer_name)
|
|
124
|
-
elif computers:
|
|
125
|
-
# No name specified, use first available computer
|
|
126
|
-
self._connect_to_existing_computer(computers[0])
|
|
127
|
-
else:
|
|
128
|
-
# No computers exist, create new one
|
|
129
|
-
self._create_computer(self.project_id, computer_name)
|
|
130
|
-
|
|
131
157
|
except Exception:
|
|
132
158
|
# Project doesn't exist, create it
|
|
133
|
-
|
|
159
|
+
if self.verbose:
|
|
160
|
+
_print_info(f"Creating project: {project_name}")
|
|
134
161
|
project = self.api.create_project(project_name)
|
|
135
162
|
self.project_id = project.get("id")
|
|
136
|
-
|
|
163
|
+
|
|
164
|
+
# Generate name if not specified
|
|
165
|
+
if not computer_name:
|
|
166
|
+
computer_name = _generate_computer_name()
|
|
167
|
+
|
|
168
|
+
self._create_computer(self.project_id, computer_name, project_name)
|
|
137
169
|
|
|
138
170
|
def _initialize_with_project_instance(self, project: 'Project', computer_name: Optional[str]):
|
|
139
|
-
"""Initialize with
|
|
140
|
-
|
|
171
|
+
"""Initialize computer with Project instance"""
|
|
172
|
+
# Generate name if not specified
|
|
173
|
+
if not computer_name:
|
|
174
|
+
computer_name = _generate_computer_name()
|
|
141
175
|
|
|
142
|
-
|
|
143
|
-
# Look for specific computer
|
|
144
|
-
existing = next((c for c in computers if c.get("name") == computer_name), None)
|
|
145
|
-
if existing:
|
|
146
|
-
self._connect_to_existing_computer(existing)
|
|
147
|
-
else:
|
|
148
|
-
# Create new computer with specified name
|
|
149
|
-
self._create_computer(project.id, computer_name)
|
|
150
|
-
elif computers:
|
|
151
|
-
# No name specified, use first available computer
|
|
152
|
-
self._connect_to_existing_computer(computers[0])
|
|
153
|
-
else:
|
|
154
|
-
# No computers exist, create new one
|
|
155
|
-
self._create_computer(project.id, computer_name)
|
|
176
|
+
self._create_computer(project.id, computer_name, project.name)
|
|
156
177
|
|
|
157
178
|
def _create_new_project_and_computer(self, computer_name: Optional[str]):
|
|
158
|
-
"""Create a new project and computer"""
|
|
159
|
-
# Generate a unique project name
|
|
179
|
+
"""Create a new project and computer when no project specified"""
|
|
160
180
|
project_name = f"project-{uuid.uuid4().hex[:8]}"
|
|
161
181
|
|
|
162
|
-
|
|
182
|
+
if self.verbose:
|
|
183
|
+
_print_info(f"Creating project: {project_name}")
|
|
184
|
+
|
|
163
185
|
project = self.api.create_project(project_name)
|
|
164
186
|
self.project_id = project.get("id")
|
|
165
187
|
self.project_name = project_name
|
|
166
188
|
|
|
167
|
-
#
|
|
168
|
-
|
|
189
|
+
# Generate name if not specified
|
|
190
|
+
if not computer_name:
|
|
191
|
+
computer_name = _generate_computer_name()
|
|
192
|
+
|
|
193
|
+
self._create_computer(self.project_id, computer_name, project_name)
|
|
169
194
|
|
|
170
195
|
def _connect_to_existing_computer(self, computer_info: Dict[str, Any]):
|
|
171
196
|
"""Connect to an existing computer"""
|
|
172
197
|
self.computer_id = computer_info.get("id")
|
|
173
198
|
self.name = computer_info.get("name")
|
|
174
|
-
|
|
199
|
+
if self.verbose:
|
|
200
|
+
_print_success(f"Connected to: {self.name} ({self.computer_id})")
|
|
175
201
|
|
|
176
|
-
def _create_computer(self, project_id: str, computer_name:
|
|
177
|
-
"""Create a new computer
|
|
178
|
-
# Generate name if not provided
|
|
179
|
-
if not computer_name:
|
|
180
|
-
computer_name = f"desktop-{uuid.uuid4().hex[:8]}"
|
|
181
|
-
|
|
202
|
+
def _create_computer(self, project_id: str, computer_name: str, project_name: str):
|
|
203
|
+
"""Create a new computer with beautiful console output"""
|
|
182
204
|
self.name = computer_name
|
|
183
205
|
|
|
184
206
|
# Validate parameters
|
|
185
207
|
if self.ram not in [1, 2, 4, 8, 16, 32, 64]:
|
|
186
|
-
raise ValueError("ram must be
|
|
208
|
+
raise ValueError("ram must be: 1, 2, 4, 8, 16, 32, or 64 GB")
|
|
187
209
|
if self.cpu not in [1, 2, 4, 8, 16]:
|
|
188
|
-
raise ValueError("cpu must be
|
|
210
|
+
raise ValueError("cpu must be: 1, 2, 4, 8, or 16 cores")
|
|
189
211
|
if self.os not in ["linux", "windows"]:
|
|
190
|
-
raise ValueError("os must be
|
|
212
|
+
raise ValueError("os must be: 'linux' or 'windows'")
|
|
191
213
|
if self.gpu not in ["none", "a10", "l40s", "a100-40gb", "a100-80gb"]:
|
|
192
|
-
raise ValueError("gpu must be
|
|
193
|
-
|
|
194
|
-
# Resolve image
|
|
214
|
+
raise ValueError("gpu must be: 'none', 'a10', 'l40s', 'a100-40gb', or 'a100-80gb'")
|
|
215
|
+
|
|
216
|
+
# Resolve image if needed
|
|
195
217
|
image_ref = self.image
|
|
196
218
|
if image_ref and isinstance(image_ref, str) and not image_ref.startswith("registry.fly.io"):
|
|
197
|
-
logger.info(f"Resolving image name '{image_ref}'...")
|
|
198
219
|
try:
|
|
199
|
-
# Try to get org_id from project info
|
|
200
220
|
project_info = self.api.get_project(project_id)
|
|
201
|
-
org_id = project_info.get("org_id", "orgo")
|
|
202
|
-
|
|
221
|
+
org_id = project_info.get("org_id", "orgo")
|
|
203
222
|
response = self.api.get_latest_build(org_id, project_id, image_ref)
|
|
204
223
|
if response and response.get("build"):
|
|
205
|
-
|
|
206
|
-
if
|
|
207
|
-
|
|
208
|
-
image_ref = resolved_ref
|
|
209
|
-
else:
|
|
210
|
-
logger.warning(f"Build found for '{image_ref}' but no imageRef present.")
|
|
211
|
-
else:
|
|
212
|
-
logger.warning(f"Could not resolve image name '{self.image}'. Using as is.")
|
|
224
|
+
resolved = response.get("build", {}).get("imageRef")
|
|
225
|
+
if resolved:
|
|
226
|
+
image_ref = resolved
|
|
213
227
|
except Exception as e:
|
|
214
|
-
|
|
228
|
+
if self.verbose:
|
|
229
|
+
logger.warning(f"Failed to resolve image: {e}")
|
|
215
230
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
231
|
+
# Create the computer
|
|
232
|
+
try:
|
|
233
|
+
computer = self.api.create_computer(
|
|
234
|
+
project_id=project_id,
|
|
235
|
+
computer_name=computer_name,
|
|
236
|
+
os=self.os,
|
|
237
|
+
ram=self.ram,
|
|
238
|
+
cpu=self.cpu,
|
|
239
|
+
gpu=self.gpu,
|
|
240
|
+
image=image_ref
|
|
241
|
+
)
|
|
242
|
+
self.computer_id = computer.get("id")
|
|
243
|
+
|
|
244
|
+
# Beautiful success message
|
|
245
|
+
if self.verbose:
|
|
246
|
+
_print_success(
|
|
247
|
+
f"Computer [{self.name}] successfully created under workspace [{project_name}]"
|
|
248
|
+
)
|
|
249
|
+
_print_info(f"ID: {self.computer_id}")
|
|
250
|
+
_print_info(f"View at: https://orgo.ai/workspaces/{self.computer_id}")
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
if self.verbose:
|
|
254
|
+
_print_error(f"Failed to create computer: {str(e)}")
|
|
255
|
+
raise
|
|
256
|
+
|
|
257
|
+
# =========================================================================
|
|
258
|
+
# Computer Management
|
|
259
|
+
# =========================================================================
|
|
227
260
|
|
|
228
261
|
def status(self) -> Dict[str, Any]:
|
|
229
|
-
"""Get current computer status"""
|
|
262
|
+
"""Get current computer status."""
|
|
230
263
|
return self.api.get_computer(self.computer_id)
|
|
231
264
|
|
|
232
265
|
def restart(self) -> Dict[str, Any]:
|
|
233
|
-
"""Restart the computer"""
|
|
234
|
-
|
|
266
|
+
"""Restart the computer."""
|
|
267
|
+
if self.verbose:
|
|
268
|
+
_print_info(f"Restarting computer: {self.name}")
|
|
269
|
+
result = self.api.restart_computer(self.computer_id)
|
|
270
|
+
if self.verbose:
|
|
271
|
+
_print_success("Computer restarted")
|
|
272
|
+
return result
|
|
235
273
|
|
|
236
274
|
def destroy(self) -> Dict[str, Any]:
|
|
237
|
-
"""
|
|
238
|
-
|
|
275
|
+
"""Delete the computer."""
|
|
276
|
+
if self.verbose:
|
|
277
|
+
_print_info(f"Deleting computer: {self.name}")
|
|
278
|
+
result = self.api.delete_computer(self.computer_id)
|
|
279
|
+
if self.verbose:
|
|
280
|
+
_print_success("Computer deleted")
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
# =========================================================================
|
|
284
|
+
# Mouse Actions
|
|
285
|
+
# =========================================================================
|
|
239
286
|
|
|
240
|
-
# Navigation methods
|
|
241
287
|
def left_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
242
|
-
"""
|
|
288
|
+
"""Left click at coordinates."""
|
|
243
289
|
return self.api.left_click(self.computer_id, x, y)
|
|
244
290
|
|
|
245
291
|
def right_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
246
|
-
"""
|
|
292
|
+
"""Right click at coordinates."""
|
|
247
293
|
return self.api.right_click(self.computer_id, x, y)
|
|
248
294
|
|
|
249
295
|
def double_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
250
|
-
"""
|
|
296
|
+
"""Double click at coordinates."""
|
|
251
297
|
return self.api.double_click(self.computer_id, x, y)
|
|
252
298
|
|
|
253
299
|
def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
|
254
300
|
button: str = "left", duration: float = 0.5) -> Dict[str, Any]:
|
|
255
|
-
"""
|
|
301
|
+
"""Drag from start to end coordinates."""
|
|
256
302
|
return self.api.drag(self.computer_id, start_x, start_y, end_x, end_y, button, duration)
|
|
257
303
|
|
|
258
304
|
def scroll(self, direction: str = "down", amount: int = 3) -> Dict[str, Any]:
|
|
259
|
-
"""Scroll in
|
|
305
|
+
"""Scroll in direction."""
|
|
260
306
|
return self.api.scroll(self.computer_id, direction, amount)
|
|
261
307
|
|
|
262
|
-
#
|
|
308
|
+
# =========================================================================
|
|
309
|
+
# Keyboard Actions
|
|
310
|
+
# =========================================================================
|
|
311
|
+
|
|
263
312
|
def type(self, text: str) -> Dict[str, Any]:
|
|
264
|
-
"""Type
|
|
313
|
+
"""Type text."""
|
|
265
314
|
return self.api.type_text(self.computer_id, text)
|
|
266
315
|
|
|
267
316
|
def key(self, key: str) -> Dict[str, Any]:
|
|
268
|
-
"""Press
|
|
317
|
+
"""Press key (e.g., "Enter", "ctrl+c")."""
|
|
269
318
|
return self.api.key_press(self.computer_id, key)
|
|
270
319
|
|
|
271
|
-
#
|
|
320
|
+
# =========================================================================
|
|
321
|
+
# Screen Capture
|
|
322
|
+
# =========================================================================
|
|
323
|
+
|
|
272
324
|
def screenshot(self) -> Image.Image:
|
|
273
|
-
"""Capture screenshot
|
|
325
|
+
"""Capture screenshot as PIL Image."""
|
|
274
326
|
response = self.api.get_screenshot(self.computer_id)
|
|
275
327
|
image_data = response.get("image", "")
|
|
276
328
|
|
|
@@ -279,11 +331,10 @@ class Computer:
|
|
|
279
331
|
img_response.raise_for_status()
|
|
280
332
|
return Image.open(io.BytesIO(img_response.content))
|
|
281
333
|
else:
|
|
282
|
-
|
|
283
|
-
return Image.open(io.BytesIO(img_data))
|
|
334
|
+
return Image.open(io.BytesIO(base64.b64decode(image_data)))
|
|
284
335
|
|
|
285
336
|
def screenshot_base64(self) -> str:
|
|
286
|
-
"""Capture screenshot
|
|
337
|
+
"""Capture screenshot as base64 string."""
|
|
287
338
|
response = self.api.get_screenshot(self.computer_id)
|
|
288
339
|
image_data = response.get("image", "")
|
|
289
340
|
|
|
@@ -291,58 +342,102 @@ class Computer:
|
|
|
291
342
|
img_response = requests.get(image_data)
|
|
292
343
|
img_response.raise_for_status()
|
|
293
344
|
return base64.b64encode(img_response.content).decode('utf-8')
|
|
294
|
-
|
|
295
|
-
|
|
345
|
+
return image_data
|
|
346
|
+
|
|
347
|
+
# =========================================================================
|
|
348
|
+
# Code Execution
|
|
349
|
+
# =========================================================================
|
|
296
350
|
|
|
297
|
-
# Execution methods
|
|
298
351
|
def bash(self, command: str) -> str:
|
|
299
|
-
"""Execute
|
|
352
|
+
"""Execute bash command."""
|
|
300
353
|
response = self.api.execute_bash(self.computer_id, command)
|
|
301
354
|
return response.get("output", "")
|
|
302
355
|
|
|
303
356
|
def exec(self, code: str, timeout: int = 10) -> Dict[str, Any]:
|
|
304
|
-
"""Execute Python code
|
|
305
|
-
|
|
306
|
-
return response
|
|
357
|
+
"""Execute Python code."""
|
|
358
|
+
return self.api.execute_python(self.computer_id, code, timeout)
|
|
307
359
|
|
|
308
360
|
def wait(self, seconds: float) -> Dict[str, Any]:
|
|
309
|
-
"""Wait for
|
|
361
|
+
"""Wait for seconds."""
|
|
310
362
|
return self.api.wait(self.computer_id, seconds)
|
|
311
363
|
|
|
312
|
-
#
|
|
364
|
+
# =========================================================================
|
|
365
|
+
# Streaming
|
|
366
|
+
# =========================================================================
|
|
367
|
+
|
|
313
368
|
def start_stream(self, connection: str) -> Dict[str, Any]:
|
|
314
|
-
"""Start
|
|
369
|
+
"""Start RTMP stream."""
|
|
315
370
|
return self.api.start_stream(self.computer_id, connection)
|
|
316
371
|
|
|
317
372
|
def stop_stream(self) -> Dict[str, Any]:
|
|
318
|
-
"""Stop
|
|
373
|
+
"""Stop stream."""
|
|
319
374
|
return self.api.stop_stream(self.computer_id)
|
|
320
375
|
|
|
321
376
|
def stream_status(self) -> Dict[str, Any]:
|
|
322
|
-
"""Get
|
|
377
|
+
"""Get stream status."""
|
|
323
378
|
return self.api.get_stream_status(self.computer_id)
|
|
324
379
|
|
|
325
|
-
#
|
|
380
|
+
# =========================================================================
|
|
381
|
+
# AI Control
|
|
382
|
+
# =========================================================================
|
|
383
|
+
|
|
326
384
|
def prompt(self,
|
|
327
385
|
instruction: str,
|
|
328
|
-
provider: str =
|
|
386
|
+
provider: Optional[str] = None,
|
|
387
|
+
verbose: bool = True,
|
|
388
|
+
callback: Optional[Callable[[str, Any], None]] = None,
|
|
329
389
|
model: str = "claude-sonnet-4-5-20250929",
|
|
330
390
|
display_width: int = 1024,
|
|
331
391
|
display_height: int = 768,
|
|
332
|
-
callback: Optional[Callable[[str, Any], None]] = None,
|
|
333
392
|
thinking_enabled: bool = True,
|
|
334
393
|
thinking_budget: int = 1024,
|
|
335
394
|
max_tokens: int = 4096,
|
|
336
395
|
max_iterations: int = 100,
|
|
337
396
|
max_saved_screenshots: int = 3,
|
|
397
|
+
system_prompt: Optional[str] = None,
|
|
338
398
|
api_key: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
339
|
-
"""
|
|
399
|
+
"""
|
|
400
|
+
Control the computer with natural language.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
instruction: What you want the computer to do
|
|
404
|
+
provider: "orgo" (default) or "anthropic"
|
|
405
|
+
verbose: Show progress logs (default: True)
|
|
406
|
+
callback: Optional callback for events
|
|
407
|
+
model: AI model to use
|
|
408
|
+
display_width: Screen width
|
|
409
|
+
display_height: Screen height
|
|
410
|
+
thinking_enabled: Enable extended thinking
|
|
411
|
+
thinking_budget: Token budget for thinking
|
|
412
|
+
max_tokens: Max response tokens
|
|
413
|
+
max_iterations: Max agent iterations
|
|
414
|
+
max_saved_screenshots: Screenshots to keep in context
|
|
415
|
+
system_prompt: Custom instructions
|
|
416
|
+
api_key: Anthropic key (only for provider="anthropic")
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
List of conversation messages
|
|
420
|
+
|
|
421
|
+
Examples:
|
|
422
|
+
# Default: Uses Orgo hosted agent
|
|
423
|
+
computer.prompt("Open Firefox and search for AI news")
|
|
424
|
+
|
|
425
|
+
# Quiet mode (no logs)
|
|
426
|
+
computer.prompt("Open Firefox", verbose=False)
|
|
427
|
+
|
|
428
|
+
# Use Anthropic directly
|
|
429
|
+
computer.prompt("Open Firefox", provider="anthropic")
|
|
430
|
+
|
|
431
|
+
# With callback
|
|
432
|
+
computer.prompt("Search Google", callback=lambda t, d: print(f"{t}: {d}"))
|
|
433
|
+
"""
|
|
340
434
|
provider_instance = get_provider(provider)
|
|
341
435
|
|
|
342
436
|
return provider_instance.execute(
|
|
343
437
|
computer_id=self.computer_id,
|
|
344
438
|
instruction=instruction,
|
|
345
439
|
callback=callback,
|
|
440
|
+
verbose=verbose,
|
|
346
441
|
api_key=api_key,
|
|
347
442
|
model=model,
|
|
348
443
|
display_width=display_width,
|
|
@@ -352,11 +447,21 @@ class Computer:
|
|
|
352
447
|
max_tokens=max_tokens,
|
|
353
448
|
max_iterations=max_iterations,
|
|
354
449
|
max_saved_screenshots=max_saved_screenshots,
|
|
450
|
+
system_prompt=system_prompt,
|
|
355
451
|
orgo_api_key=self.api_key,
|
|
356
452
|
orgo_base_url=self.base_api_url
|
|
357
453
|
)
|
|
358
454
|
|
|
455
|
+
# =========================================================================
|
|
456
|
+
# URL Helper
|
|
457
|
+
# =========================================================================
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def url(self) -> str:
|
|
461
|
+
"""Get the URL to view this computer."""
|
|
462
|
+
return f"https://orgo.ai/workspaces/{self.computer_id}"
|
|
463
|
+
|
|
359
464
|
def __repr__(self):
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return f"Computer({
|
|
465
|
+
if hasattr(self, 'name') and self.name:
|
|
466
|
+
return f"Computer(name='{self.name}', id='{self.computer_id}')"
|
|
467
|
+
return f"Computer(id='{self.computer_id}')"
|