grasp-sdk 0.1.7__tar.gz → 0.1.9__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.
Potentially problematic release.
This version of grasp-sdk might be problematic. Click here for more details.
- {grasp_sdk-0.1.7/grasp_sdk.egg-info → grasp_sdk-0.1.9}/PKG-INFO +1 -1
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/__init__.py +3 -3
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/services/browser.py +53 -73
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/services/sandbox.py +16 -19
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9/grasp_sdk.egg-info}/PKG-INFO +1 -1
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk.egg-info/SOURCES.txt +0 -3
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/pyproject.toml +1 -1
- grasp_sdk-0.1.7/grasp_sdk/sandbox/chrome-stable.mjs +0 -424
- grasp_sdk-0.1.7/grasp_sdk/sandbox/chromium.mjs +0 -395
- grasp_sdk-0.1.7/grasp_sdk/sandbox/http-proxy.mjs +0 -322
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/MANIFEST.in +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/README.md +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/build_and_publish.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/example_usage.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/models/__init__.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/services/__init__.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/utils/__init__.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/utils/auth.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/utils/config.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk/utils/logger.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk.egg-info/dependency_links.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk.egg-info/entry_points.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk.egg-info/not-zip-safe +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk.egg-info/requires.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/grasp_sdk.egg-info/top_level.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/py.typed +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/requirements.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/setup.cfg +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/setup.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.9}/test_install.py +0 -0
|
@@ -24,7 +24,7 @@ from .models import (
|
|
|
24
24
|
SandboxStatus,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
__version__ = "0.1.
|
|
27
|
+
__version__ = "0.1.9"
|
|
28
28
|
__author__ = "Grasp Team"
|
|
29
29
|
__email__ = "team@grasp.dev"
|
|
30
30
|
|
|
@@ -168,7 +168,7 @@ class GraspServer:
|
|
|
168
168
|
self.config['sandbox'],
|
|
169
169
|
browser_config
|
|
170
170
|
)
|
|
171
|
-
await self.browser_service.initialize()
|
|
171
|
+
await self.browser_service.initialize(browser_type)
|
|
172
172
|
|
|
173
173
|
# Register server
|
|
174
174
|
_servers[str(self.browser_service.id)] = self
|
|
@@ -177,7 +177,7 @@ class GraspServer:
|
|
|
177
177
|
})
|
|
178
178
|
|
|
179
179
|
self.logger.info('🌐 Launching Chromium browser with CDP...')
|
|
180
|
-
cdp_connection = await self.browser_service.launch_browser(
|
|
180
|
+
cdp_connection = await self.browser_service.launch_browser()
|
|
181
181
|
|
|
182
182
|
self.logger.info('✅ Browser launched successfully!')
|
|
183
183
|
self.logger.debug(
|
|
@@ -91,19 +91,34 @@ class BrowserService:
|
|
|
91
91
|
})
|
|
92
92
|
return default_logger.child('BrowserService')
|
|
93
93
|
|
|
94
|
-
async def initialize(self) -> None:
|
|
94
|
+
async def initialize(self, browser_type: str) -> None:
|
|
95
95
|
"""Initialize the Grasp sandbox.
|
|
96
96
|
|
|
97
97
|
Returns:
|
|
98
98
|
Promise that resolves when sandbox is ready
|
|
99
99
|
"""
|
|
100
100
|
self.logger.info('Initializing Browser service')
|
|
101
|
-
|
|
101
|
+
envs = {
|
|
102
|
+
'CDP_PORT': '9222',
|
|
103
|
+
'BROWSER_ARGS': json.dumps(self.config['args']),
|
|
104
|
+
'LAUNCH_TIMEOUT': str(self.config['launchTimeout']),
|
|
105
|
+
'SANDBOX_TIMEOUT': str(self.sandbox_service.timeout),
|
|
106
|
+
'HEADLESS': str(self.config['headless']).lower(),
|
|
107
|
+
'NODE_ENV': 'production',
|
|
108
|
+
# 'SANDBOX_ID': self.sandbox_service.id,
|
|
109
|
+
'WORKSPACE': self.sandbox_service.workspace,
|
|
110
|
+
'BS_SOURCE_TOKEN': 'Qth8JGboEKVersqr1PSsUFMW',
|
|
111
|
+
'BS_INGESTING_HOST': 's1363065.eu-nbg-2.betterstackdata.com',
|
|
112
|
+
'SENTRY_DSN': 'https://21fa729ceb72d7f0adef06b4f786c067@o4509574910509056.ingest.us.sentry.io/4509574913720320',
|
|
113
|
+
**self.config['envs']
|
|
114
|
+
}
|
|
115
|
+
await self.sandbox_service.create_sandbox(f'grasp-run-{browser_type}', envs)
|
|
116
|
+
if(self.sandbox_service.sandbox is not None):
|
|
117
|
+
await self.sandbox_service.sandbox.files.write('/home/user/.sandbox_id', self.id)
|
|
102
118
|
self.logger.info('Grasp sandbox initialized successfully')
|
|
103
119
|
|
|
104
120
|
async def launch_browser(
|
|
105
121
|
self,
|
|
106
|
-
browser_type: str = 'chromium'
|
|
107
122
|
) -> CDPConnection:
|
|
108
123
|
"""Launch Chromium browser with CDP server.
|
|
109
124
|
|
|
@@ -123,41 +138,16 @@ class BrowserService:
|
|
|
123
138
|
self.logger.info(
|
|
124
139
|
f'Launching Chromium browser with CDP (port: 9222, headless: {self.config["headless"]})')
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
# Check if adblock is enabled and adjust browser type
|
|
128
|
-
if (
|
|
129
|
-
self.config['envs'].get('ADBLOCK') == 'true' and
|
|
130
|
-
browser_type == 'chromium'
|
|
131
|
-
):
|
|
132
|
-
self.logger.warn(
|
|
133
|
-
'⚠️ Adblock is enabled. Should use chrome-stable instead.'
|
|
134
|
-
)
|
|
135
|
-
browser_type = 'chrome-stable'
|
|
136
|
-
|
|
137
141
|
# Read the Playwright script
|
|
138
|
-
script_path = Path(__file__).parent.parent / 'sandbox' / 'http-proxy.mjs'
|
|
142
|
+
# script_path = Path(__file__).parent.parent / 'sandbox' / 'http-proxy.mjs'
|
|
139
143
|
|
|
140
|
-
try:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
except FileNotFoundError:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
envs = {
|
|
148
|
-
'CDP_PORT': '9222',
|
|
149
|
-
'BROWSER_ARGS': json.dumps(self.config['args']),
|
|
150
|
-
'LAUNCH_TIMEOUT': str(self.config['launchTimeout']),
|
|
151
|
-
'SANDBOX_TIMEOUT': str(self.sandbox_service.timeout),
|
|
152
|
-
'HEADLESS': str(self.config['headless']).lower(),
|
|
153
|
-
'NODE_ENV': 'production',
|
|
154
|
-
'SANDBOX_ID': self.sandbox_service.id,
|
|
155
|
-
'WORKSPACE': self.sandbox_service.workspace,
|
|
156
|
-
'BS_SOURCE_TOKEN': 'Qth8JGboEKVersqr1PSsUFMW',
|
|
157
|
-
'BS_INGESTING_HOST': 's1363065.eu-nbg-2.betterstackdata.com',
|
|
158
|
-
'SENTRY_DSN': 'https://21fa729ceb72d7f0adef06b4f786c067@o4509574910509056.ingest.us.sentry.io/4509574913720320',
|
|
159
|
-
**self.config['envs']
|
|
160
|
-
}
|
|
144
|
+
# try:
|
|
145
|
+
# with open(script_path, 'r', encoding='utf-8') as f:
|
|
146
|
+
# playwright_script = f.read()
|
|
147
|
+
# except FileNotFoundError:
|
|
148
|
+
# raise RuntimeError(f'Browser script not found: {script_path}')
|
|
149
|
+
|
|
150
|
+
playwright_script = '/home/user/http-proxy.js'
|
|
161
151
|
|
|
162
152
|
# Prepare script options
|
|
163
153
|
from ..models import IScriptOptions
|
|
@@ -166,8 +156,7 @@ class BrowserService:
|
|
|
166
156
|
'background': True,
|
|
167
157
|
'nohup': not self.sandbox_service.is_debug,
|
|
168
158
|
'timeoutMs': 0,
|
|
169
|
-
'
|
|
170
|
-
'preCommand': '' if self.config['headless'] else 'xvfb-run -a -s "-screen 0 1280x1024x24" '
|
|
159
|
+
'preCommand': ''
|
|
171
160
|
}
|
|
172
161
|
|
|
173
162
|
# Run the Playwright script in background
|
|
@@ -237,7 +226,7 @@ class BrowserService:
|
|
|
237
226
|
Raises:
|
|
238
227
|
RuntimeError: If CDP server fails to become ready within timeout
|
|
239
228
|
"""
|
|
240
|
-
delay_ms =
|
|
229
|
+
delay_ms = 50
|
|
241
230
|
max_attempts = self.config['launchTimeout'] // delay_ms
|
|
242
231
|
|
|
243
232
|
for attempt in range(1, max_attempts + 1):
|
|
@@ -251,40 +240,31 @@ class BrowserService:
|
|
|
251
240
|
)
|
|
252
241
|
|
|
253
242
|
# Check if CDP endpoint is responding
|
|
254
|
-
|
|
255
|
-
'
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
connection = CDPConnection(
|
|
281
|
-
ws_url=ws_url,
|
|
282
|
-
http_url=http_url,
|
|
283
|
-
port=9222
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
self.logger.info(f'CDP server is ready (metadata: {metadata})')
|
|
287
|
-
return connection
|
|
243
|
+
async with aiohttp.ClientSession() as session:
|
|
244
|
+
async with session.get(f'https://{host}/json/version') as response:
|
|
245
|
+
if response.status == 200:
|
|
246
|
+
response_text = await response.text()
|
|
247
|
+
if 'Browser' in response_text:
|
|
248
|
+
stdout_content = response_text
|
|
249
|
+
metadata = json.loads(stdout_content)
|
|
250
|
+
|
|
251
|
+
# Update URLs for external access
|
|
252
|
+
ws_url = metadata['webSocketDebuggerUrl'].replace(
|
|
253
|
+
'ws://', 'wss://'
|
|
254
|
+
).replace(
|
|
255
|
+
f'localhost:9222', host
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
http_url = f'https://{host}'
|
|
259
|
+
|
|
260
|
+
connection = CDPConnection(
|
|
261
|
+
ws_url=ws_url,
|
|
262
|
+
http_url=http_url,
|
|
263
|
+
port=9222
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
self.logger.info(f'CDP server is ready (metadata: {metadata})')
|
|
267
|
+
return connection
|
|
288
268
|
|
|
289
269
|
except Exception as error:
|
|
290
270
|
self.logger.debug(
|
|
@@ -123,7 +123,7 @@ class SandboxService:
|
|
|
123
123
|
"""Get timeout value."""
|
|
124
124
|
return self.config['timeout']
|
|
125
125
|
|
|
126
|
-
async def create_sandbox(self, template_id: str) -> None:
|
|
126
|
+
async def create_sandbox(self, template_id: str, envs: Optional[Dict[str, str]] = None) -> None:
|
|
127
127
|
"""
|
|
128
128
|
Creates and starts a new sandbox.
|
|
129
129
|
|
|
@@ -149,7 +149,8 @@ class SandboxService:
|
|
|
149
149
|
self.sandbox = await AsyncSandbox.create(
|
|
150
150
|
template=template_id,
|
|
151
151
|
api_key=api_key,
|
|
152
|
-
timeout=self.config['timeout'] // 1000 # Convert ms to seconds
|
|
152
|
+
timeout=self.config['timeout'] // 1000, # Convert ms to seconds
|
|
153
|
+
envs=envs,
|
|
153
154
|
)
|
|
154
155
|
|
|
155
156
|
self.status = SandboxStatus.RUNNING
|
|
@@ -322,7 +323,7 @@ class SandboxService:
|
|
|
322
323
|
Runs JavaScript code in the sandbox.
|
|
323
324
|
|
|
324
325
|
Args:
|
|
325
|
-
code: JavaScript code to execute
|
|
326
|
+
code: JavaScript code to execute, or file path starting with '/home/user/'
|
|
326
327
|
options: Script execution options
|
|
327
328
|
|
|
328
329
|
Returns:
|
|
@@ -331,6 +332,10 @@ class SandboxService:
|
|
|
331
332
|
|
|
332
333
|
Raises:
|
|
333
334
|
RuntimeError: If sandbox is not running or script execution fails
|
|
335
|
+
|
|
336
|
+
Note:
|
|
337
|
+
If code starts with '/home/user/', it will be treated as a file path.
|
|
338
|
+
Otherwise, it will be treated as JavaScript code and written to a temporary file.
|
|
334
339
|
"""
|
|
335
340
|
if not self.sandbox or self.status != SandboxStatus.RUNNING:
|
|
336
341
|
raise RuntimeError('Sandbox is not running. Call create_sandbox() first.')
|
|
@@ -341,9 +346,14 @@ class SandboxService:
|
|
|
341
346
|
try:
|
|
342
347
|
# Generate temporary file name in working directory
|
|
343
348
|
timestamp = int(time.time() * 1000)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
349
|
+
script_path = code
|
|
350
|
+
|
|
351
|
+
if not code.startswith('/home/user/'):
|
|
352
|
+
extension = 'mjs' if options['type'] == 'esm' else 'js'
|
|
353
|
+
working_dir = options.get('cwd', self.DEFAULT_WORKING_DIRECTORY)
|
|
354
|
+
script_path = f'{working_dir}/script_{timestamp}.{extension}'
|
|
355
|
+
# Write code to temporary file
|
|
356
|
+
await self.sandbox.files.write(script_path, code)
|
|
347
357
|
|
|
348
358
|
self.logger.debug('Running JavaScript code in sandbox', {
|
|
349
359
|
'type': options['type'],
|
|
@@ -351,9 +361,6 @@ class SandboxService:
|
|
|
351
361
|
'codeLength': len(code),
|
|
352
362
|
})
|
|
353
363
|
|
|
354
|
-
# Write code to temporary file
|
|
355
|
-
await self.sandbox.files.write(script_path, code)
|
|
356
|
-
|
|
357
364
|
# Choose execution command based on type
|
|
358
365
|
pre_command = options.get('preCommand', '')
|
|
359
366
|
command = f'{pre_command}node {script_path}'
|
|
@@ -374,16 +381,6 @@ class SandboxService:
|
|
|
374
381
|
# Execute the script
|
|
375
382
|
result = await self.run_command(command, cmd_options)
|
|
376
383
|
|
|
377
|
-
# Cleanup temporary file (if not background execution)
|
|
378
|
-
if not options.get('background', False):
|
|
379
|
-
try:
|
|
380
|
-
await self.run_command(f'rm -f {script_path}')
|
|
381
|
-
except Exception as cleanup_error:
|
|
382
|
-
self.logger.warn('Failed to cleanup script file', {
|
|
383
|
-
'scriptPath': script_path,
|
|
384
|
-
'error': cleanup_error,
|
|
385
|
-
})
|
|
386
|
-
|
|
387
384
|
return result
|
|
388
385
|
|
|
389
386
|
except Exception as error:
|
|
@@ -30,9 +30,6 @@ grasp_sdk.egg-info/not-zip-safe
|
|
|
30
30
|
grasp_sdk.egg-info/requires.txt
|
|
31
31
|
grasp_sdk.egg-info/top_level.txt
|
|
32
32
|
grasp_sdk/models/__init__.py
|
|
33
|
-
grasp_sdk/sandbox/chrome-stable.mjs
|
|
34
|
-
grasp_sdk/sandbox/chromium.mjs
|
|
35
|
-
grasp_sdk/sandbox/http-proxy.mjs
|
|
36
33
|
grasp_sdk/services/__init__.py
|
|
37
34
|
grasp_sdk/services/browser.py
|
|
38
35
|
grasp_sdk/services/sandbox.py
|