grasp-sdk 0.1.7__tar.gz → 0.1.8__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.8}/PKG-INFO +1 -1
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/__init__.py +3 -3
- grasp_sdk-0.1.8/grasp_sdk/sandbox/bootstrap-chrome-stable.mjs +69 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/sandbox/http-proxy.mjs +6 -4
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/services/browser.py +26 -37
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/services/sandbox.py +16 -19
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8/grasp_sdk.egg-info}/PKG-INFO +1 -1
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk.egg-info/SOURCES.txt +1 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/pyproject.toml +1 -1
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/MANIFEST.in +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/README.md +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/build_and_publish.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/example_usage.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/models/__init__.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/sandbox/chrome-stable.mjs +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/sandbox/chromium.mjs +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/services/__init__.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/utils/__init__.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/utils/auth.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/utils/config.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk/utils/logger.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk.egg-info/dependency_links.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk.egg-info/entry_points.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk.egg-info/not-zip-safe +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk.egg-info/requires.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/grasp_sdk.egg-info/top_level.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/py.typed +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/requirements.txt +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/setup.cfg +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/setup.py +0 -0
- {grasp_sdk-0.1.7 → grasp_sdk-0.1.8}/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.8"
|
|
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(
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
// const asblockPlugin = '/home/user/.config/google-chrome/Default/Extensions/adblock';
|
|
4
|
+
|
|
5
|
+
const args = [
|
|
6
|
+
'--no-sandbox',
|
|
7
|
+
'--disable-setuid-sandbox',
|
|
8
|
+
'--disable-dev-shm-usage',
|
|
9
|
+
'--disable-gpu',
|
|
10
|
+
'--disable-software-rasterizer',
|
|
11
|
+
'--user-data-dir=/home/user/.browser-context'
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
args.push(
|
|
15
|
+
// 避免缓存积累影响性能
|
|
16
|
+
'--disable-application-cache',
|
|
17
|
+
|
|
18
|
+
// 关闭所有硬件加速特性,防止 GPU 相关崩溃
|
|
19
|
+
'--disable-accelerated-2d-canvas',
|
|
20
|
+
'--disable-accelerated-video-decode',
|
|
21
|
+
|
|
22
|
+
// 禁用后台渲染,减少无关资源消耗
|
|
23
|
+
'--disable-background-timer-throttling',
|
|
24
|
+
'--disable-backgrounding-occluded-windows',
|
|
25
|
+
'--disable-renderer-backgrounding',
|
|
26
|
+
|
|
27
|
+
// 避免过度日志影响性能
|
|
28
|
+
'--disable-logging',
|
|
29
|
+
|
|
30
|
+
// 禁用不必要的多媒体解码
|
|
31
|
+
'--mute-audio',
|
|
32
|
+
|
|
33
|
+
// 避免崩溃时弹窗
|
|
34
|
+
'--no-default-browser-check',
|
|
35
|
+
'--no-first-run',
|
|
36
|
+
'--headless=new',
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
args.push(
|
|
40
|
+
`--remote-debugging-port=9222`,
|
|
41
|
+
'--remote-debugging-address=0.0.0.0',
|
|
42
|
+
'about:blank',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const chromePath = '/usr/bin/google-chrome';
|
|
46
|
+
|
|
47
|
+
// 启动 Chrome 并启用远程调试
|
|
48
|
+
const chrome = spawn(chromePath, args, {
|
|
49
|
+
env: { ...process.env, DISPLAY: ':99' }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
chrome.stdout.on('data', (data) => {
|
|
53
|
+
console.log(`stdout: ${data}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
chrome.stderr.on('data', (data) => {
|
|
57
|
+
console.error(`stderr: ${data}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
chrome.on('close', (code) => {
|
|
61
|
+
console.log(`Chrome process exited with code ${code}`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log('Browser launched and ready...');
|
|
65
|
+
|
|
66
|
+
// 保持进程不退出,并监听中止信号
|
|
67
|
+
process.stdin.resume();
|
|
68
|
+
process.on('SIGINT', () => process.exit());
|
|
69
|
+
process.on('SIGTERM', () => process.exit());
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import httpProxy from 'http-proxy';
|
|
2
2
|
import http from 'http';
|
|
3
|
+
import fs from 'fs';
|
|
3
4
|
|
|
4
5
|
import { Logtail } from '@logtail/node';
|
|
5
6
|
import * as Sentry from "@sentry/node";
|
|
@@ -94,7 +95,7 @@ function parseWebSocketFrame(buffer) {
|
|
|
94
95
|
return payloadData.toString('utf8');
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
let sandboxId = '';
|
|
98
99
|
const cdpPort = Number(process.env.CDP_PORT);
|
|
99
100
|
const headless = process.env.HEADLESS !== 'false';
|
|
100
101
|
const enableAdblock = process.env.ADBLOCK !== 'false';
|
|
@@ -115,7 +116,8 @@ try {
|
|
|
115
116
|
|
|
116
117
|
// 监听 WebSocket 事件
|
|
117
118
|
proxy.on('open', () => {
|
|
118
|
-
|
|
119
|
+
sandboxId = fs.readFileSync('/home/user/.sandbox_id', 'utf-8');
|
|
120
|
+
console.log('🔌 CDP WebSocket connection established', sandboxId);
|
|
119
121
|
const wsId = Date.now();
|
|
120
122
|
logger.info('CDP WebSocket connection established', { sandboxId, wsId });
|
|
121
123
|
Sentry.addBreadcrumb({
|
|
@@ -230,7 +232,7 @@ try {
|
|
|
230
232
|
console.log('📨 CDP WebSocket message:', parsed);
|
|
231
233
|
logger.info('CDP WebSocket message received', {
|
|
232
234
|
data: parsed,
|
|
233
|
-
sandboxId
|
|
235
|
+
sandboxId,
|
|
234
236
|
});
|
|
235
237
|
Sentry.addBreadcrumb({
|
|
236
238
|
category: 'websocket',
|
|
@@ -251,7 +253,7 @@ try {
|
|
|
251
253
|
logger.warn('Failed to parse CDP WebSocket message', {
|
|
252
254
|
error: err.message,
|
|
253
255
|
data: message,
|
|
254
|
-
sandboxId
|
|
256
|
+
sandboxId,
|
|
255
257
|
});
|
|
256
258
|
}
|
|
257
259
|
}
|
|
@@ -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.mjs'
|
|
161
151
|
|
|
162
152
|
# Prepare script options
|
|
163
153
|
from ..models import IScriptOptions
|
|
@@ -166,7 +156,6 @@ class BrowserService:
|
|
|
166
156
|
'background': True,
|
|
167
157
|
'nohup': not self.sandbox_service.is_debug,
|
|
168
158
|
'timeoutMs': 0,
|
|
169
|
-
'envs': envs,
|
|
170
159
|
'preCommand': '' if self.config['headless'] else 'xvfb-run -a -s "-screen 0 1280x1024x24" '
|
|
171
160
|
}
|
|
172
161
|
|
|
@@ -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,6 +30,7 @@ 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/bootstrap-chrome-stable.mjs
|
|
33
34
|
grasp_sdk/sandbox/chrome-stable.mjs
|
|
34
35
|
grasp_sdk/sandbox/chromium.mjs
|
|
35
36
|
grasp_sdk/sandbox/http-proxy.mjs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|