camel-ai 0.2.74a5__py3-none-any.whl → 0.2.75__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.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -1
- camel/agents/chat_agent.py +149 -95
- camel/configs/__init__.py +3 -0
- camel/configs/nebius_config.py +103 -0
- camel/interpreters/e2b_interpreter.py +34 -1
- camel/models/__init__.py +2 -0
- camel/models/aiml_model.py +1 -16
- camel/models/anthropic_model.py +6 -22
- camel/models/aws_bedrock_model.py +1 -16
- camel/models/azure_openai_model.py +1 -16
- camel/models/base_model.py +0 -12
- camel/models/cohere_model.py +1 -16
- camel/models/crynux_model.py +1 -16
- camel/models/deepseek_model.py +1 -16
- camel/models/gemini_model.py +1 -16
- camel/models/groq_model.py +1 -17
- camel/models/internlm_model.py +1 -16
- camel/models/litellm_model.py +1 -16
- camel/models/lmstudio_model.py +1 -17
- camel/models/mistral_model.py +1 -16
- camel/models/model_factory.py +2 -0
- camel/models/modelscope_model.py +1 -16
- camel/models/moonshot_model.py +6 -22
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +0 -5
- camel/models/netmind_model.py +1 -16
- camel/models/novita_model.py +1 -16
- camel/models/nvidia_model.py +1 -16
- camel/models/ollama_model.py +4 -19
- camel/models/openai_compatible_model.py +0 -3
- camel/models/openai_model.py +1 -22
- camel/models/openrouter_model.py +1 -17
- camel/models/ppio_model.py +1 -16
- camel/models/qianfan_model.py +1 -16
- camel/models/qwen_model.py +1 -16
- camel/models/reka_model.py +1 -16
- camel/models/samba_model.py +0 -32
- camel/models/sglang_model.py +1 -16
- camel/models/siliconflow_model.py +1 -16
- camel/models/stub_model.py +0 -4
- camel/models/togetherai_model.py +1 -16
- camel/models/vllm_model.py +1 -16
- camel/models/volcano_model.py +0 -17
- camel/models/watsonx_model.py +1 -16
- camel/models/yi_model.py +1 -16
- camel/models/zhipuai_model.py +1 -16
- camel/societies/workforce/prompts.py +1 -8
- camel/societies/workforce/task_channel.py +120 -27
- camel/societies/workforce/workforce.py +35 -3
- camel/toolkits/__init__.py +0 -2
- camel/toolkits/github_toolkit.py +104 -17
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +3 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +260 -5
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +288 -37
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +3 -1
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +209 -41
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +22 -3
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +28 -1
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +101 -0
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +312 -3
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
- camel/toolkits/math_toolkit.py +64 -10
- camel/toolkits/mcp_toolkit.py +39 -14
- camel/toolkits/openai_image_toolkit.py +55 -24
- camel/toolkits/search_toolkit.py +153 -29
- camel/types/__init__.py +2 -2
- camel/types/enums.py +54 -10
- camel/types/openai_types.py +2 -2
- camel/types/unified_model_type.py +5 -0
- camel/utils/mcp.py +2 -2
- camel/utils/token_counting.py +18 -3
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/METADATA +9 -15
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/RECORD +79 -78
- camel/toolkits/openai_agent_toolkit.py +0 -135
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Page, Browser, BrowserContext, chromium } from 'playwright';
|
|
1
|
+
import { Page, Browser, BrowserContext, chromium, ConsoleMessage } from 'playwright';
|
|
2
2
|
import { BrowserToolkitConfig, SnapshotResult, SnapshotElement, ActionResult, TabInfo, BrowserAction, DetailedTiming } from './types';
|
|
3
3
|
import { ConfigLoader, StealthConfig } from './config-loader';
|
|
4
4
|
|
|
@@ -6,18 +6,43 @@ export class HybridBrowserSession {
|
|
|
6
6
|
private browser: Browser | null = null;
|
|
7
7
|
private context: BrowserContext | null = null;
|
|
8
8
|
private pages: Map<string, Page> = new Map();
|
|
9
|
+
private consoleLogs: Map<string, ConsoleMessage[]> = new Map();
|
|
9
10
|
private currentTabId: string | null = null;
|
|
10
11
|
private tabCounter = 0;
|
|
11
12
|
private configLoader: ConfigLoader;
|
|
12
13
|
private scrollPosition: { x: number; y: number } = {x: 0, y: 0};
|
|
13
14
|
private hasNavigatedBefore = false; // Track if we've navigated before
|
|
15
|
+
private logLimit: number;
|
|
14
16
|
|
|
15
17
|
constructor(config: BrowserToolkitConfig = {}) {
|
|
16
18
|
// Use ConfigLoader's fromPythonConfig to handle conversion properly
|
|
17
19
|
this.configLoader = ConfigLoader.fromPythonConfig(config);
|
|
20
|
+
// Load browser configuration for console log limit, default to 1000
|
|
21
|
+
this.logLimit = this.configLoader.getBrowserConfig().consoleLogLimit || 1000;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
private registerNewPage(tabId: string, page: Page): void {
|
|
25
|
+
// Register page and logs with tabId
|
|
26
|
+
this.pages.set(tabId, page);
|
|
27
|
+
this.consoleLogs.set(tabId, []);
|
|
28
|
+
// Set up console log listener for the page
|
|
29
|
+
page.on('console', (msg: ConsoleMessage) => {
|
|
30
|
+
const logs = this.consoleLogs.get(tabId);
|
|
31
|
+
if (logs) {
|
|
32
|
+
logs.push(msg);
|
|
33
|
+
if (logs.length > this.logLimit) {
|
|
34
|
+
logs.shift();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Clean logs on page close
|
|
40
|
+
page.on('close', () => {
|
|
41
|
+
this.consoleLogs.delete(tabId);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async ensureBrowser(): Promise<void> {
|
|
21
46
|
if (this.browser) {
|
|
22
47
|
return;
|
|
23
48
|
}
|
|
@@ -47,21 +72,19 @@ export class HybridBrowserSession {
|
|
|
47
72
|
this.context = await this.browser.newContext(contextOptions);
|
|
48
73
|
}
|
|
49
74
|
|
|
50
|
-
// Handle existing pages
|
|
51
75
|
const pages = this.context.pages();
|
|
52
76
|
if (pages.length > 0) {
|
|
53
|
-
// Map existing pages - for CDP,
|
|
77
|
+
// Map existing pages - for CDP, find ONE available blank page
|
|
54
78
|
let availablePageFound = false;
|
|
55
79
|
for (const page of pages) {
|
|
56
80
|
const pageUrl = page.url();
|
|
57
|
-
|
|
58
|
-
if (pageUrl === 'about:blank') {
|
|
81
|
+
if (this.isBlankPageUrl(pageUrl)) {
|
|
59
82
|
const tabId = this.generateTabId();
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
this.registerNewPage(tabId, page);
|
|
84
|
+
this.currentTabId = tabId;
|
|
85
|
+
availablePageFound = true;
|
|
86
|
+
console.log(`[CDP] Registered blank page as initial tab: ${tabId}, URL: ${pageUrl}`);
|
|
87
|
+
break; // Only register ONE page initially
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
|
|
@@ -97,7 +120,7 @@ export class HybridBrowserSession {
|
|
|
97
120
|
const pages = this.context.pages();
|
|
98
121
|
if (pages.length > 0) {
|
|
99
122
|
const initialTabId = this.generateTabId();
|
|
100
|
-
this.
|
|
123
|
+
this.registerNewPage(initialTabId, pages[0]);
|
|
101
124
|
this.currentTabId = initialTabId;
|
|
102
125
|
}
|
|
103
126
|
} else {
|
|
@@ -115,7 +138,7 @@ export class HybridBrowserSession {
|
|
|
115
138
|
|
|
116
139
|
const initialPage = await this.context.newPage();
|
|
117
140
|
const initialTabId = this.generateTabId();
|
|
118
|
-
this.
|
|
141
|
+
this.registerNewPage(initialTabId, initialPage);
|
|
119
142
|
this.currentTabId = initialTabId;
|
|
120
143
|
}
|
|
121
144
|
}
|
|
@@ -132,13 +155,57 @@ export class HybridBrowserSession {
|
|
|
132
155
|
return `${browserConfig.tabIdPrefix}${String(++this.tabCounter).padStart(browserConfig.tabCounterPadding, '0')}`;
|
|
133
156
|
}
|
|
134
157
|
|
|
158
|
+
private isBlankPageUrl(url: string): boolean {
|
|
159
|
+
// Unified blank page detection logic used across the codebase
|
|
160
|
+
const browserConfig = this.configLoader.getBrowserConfig();
|
|
161
|
+
return (
|
|
162
|
+
// Standard about:blank variations (prefix match for query params)
|
|
163
|
+
url === 'about:blank' ||
|
|
164
|
+
url.startsWith('about:blank?') ||
|
|
165
|
+
// Configured blank page URLs (exact match for compatibility)
|
|
166
|
+
browserConfig.blankPageUrls.includes(url) ||
|
|
167
|
+
// Empty URL
|
|
168
|
+
url === '' ||
|
|
169
|
+
// Data URLs (often used for blank pages)
|
|
170
|
+
url.startsWith(browserConfig.dataUrlPrefix || 'data:')
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
135
174
|
async getCurrentPage(): Promise<Page> {
|
|
136
175
|
if (!this.currentTabId || !this.pages.has(this.currentTabId)) {
|
|
176
|
+
// In CDP mode, try to create a new page if none exists
|
|
177
|
+
const browserConfig = this.configLoader.getBrowserConfig();
|
|
178
|
+
if (browserConfig.connectOverCdp && this.context) {
|
|
179
|
+
console.log('[CDP] No active page found, attempting to create new page...');
|
|
180
|
+
try {
|
|
181
|
+
const newPage = await this.context.newPage();
|
|
182
|
+
const newTabId = this.generateTabId();
|
|
183
|
+
this.registerNewPage(newTabId, newPage);
|
|
184
|
+
this.currentTabId = newTabId;
|
|
185
|
+
|
|
186
|
+
// Set page timeouts
|
|
187
|
+
newPage.setDefaultNavigationTimeout(browserConfig.navigationTimeout);
|
|
188
|
+
newPage.setDefaultTimeout(browserConfig.navigationTimeout);
|
|
189
|
+
|
|
190
|
+
console.log(`[CDP] Created new page with tab ID: ${newTabId}`);
|
|
191
|
+
return newPage;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('[CDP] Failed to create new page:', error);
|
|
194
|
+
throw new Error('No active page available and failed to create new page in CDP mode');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
137
197
|
throw new Error('No active page available');
|
|
138
198
|
}
|
|
139
199
|
return this.pages.get(this.currentTabId)!;
|
|
140
200
|
}
|
|
141
201
|
|
|
202
|
+
async getCurrentLogs(): Promise<ConsoleMessage[]> {
|
|
203
|
+
if (!this.currentTabId || !this.consoleLogs.has(this.currentTabId)) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
return this.consoleLogs.get(this.currentTabId) || [];
|
|
207
|
+
}
|
|
208
|
+
|
|
142
209
|
/**
|
|
143
210
|
* Get current scroll position from the page
|
|
144
211
|
*/
|
|
@@ -343,7 +410,7 @@ export class HybridBrowserSession {
|
|
|
343
410
|
|
|
344
411
|
// Generate tab ID for the new page
|
|
345
412
|
const newTabId = this.generateTabId();
|
|
346
|
-
this.
|
|
413
|
+
this.registerNewPage(newTabId, newPage);
|
|
347
414
|
|
|
348
415
|
// Set up page properties
|
|
349
416
|
const browserConfig = this.configLoader.getBrowserConfig();
|
|
@@ -384,25 +451,67 @@ export class HybridBrowserSession {
|
|
|
384
451
|
|
|
385
452
|
/**
|
|
386
453
|
* Simplified type implementation using Playwright's aria-ref selector
|
|
454
|
+
* Supports both single and multiple input operations
|
|
387
455
|
*/
|
|
388
|
-
private async performType(page: Page, ref: string, text: string): Promise<{ success: boolean; error?: string }> {
|
|
456
|
+
private async performType(page: Page, ref: string | undefined, text: string | undefined, inputs?: Array<{ ref: string; text: string }>): Promise<{ success: boolean; error?: string; details?: Record<string, any> }> {
|
|
389
457
|
try {
|
|
390
458
|
// Ensure we have the latest snapshot
|
|
391
459
|
await (page as any)._snapshotForAI();
|
|
392
460
|
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
461
|
+
// Handle multiple inputs if provided
|
|
462
|
+
if (inputs && inputs.length > 0) {
|
|
463
|
+
const results: Record<string, { success: boolean; error?: string }> = {};
|
|
464
|
+
|
|
465
|
+
for (const input of inputs) {
|
|
466
|
+
const selector = `aria-ref=${input.ref}`;
|
|
467
|
+
const element = await page.locator(selector).first();
|
|
468
|
+
|
|
469
|
+
const exists = await element.count() > 0;
|
|
470
|
+
if (!exists) {
|
|
471
|
+
results[input.ref] = { success: false, error: `Element with ref ${input.ref} not found` };
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
// Type text using Playwright's built-in fill method
|
|
477
|
+
await element.fill(input.text);
|
|
478
|
+
results[input.ref] = { success: true };
|
|
479
|
+
} catch (error) {
|
|
480
|
+
results[input.ref] = { success: false, error: `Type failed: ${error}` };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Check if all inputs were successful
|
|
485
|
+
const allSuccess = Object.values(results).every(r => r.success);
|
|
486
|
+
const errors = Object.entries(results)
|
|
487
|
+
.filter(([_, r]) => !r.success)
|
|
488
|
+
.map(([ref, r]) => `${ref}: ${r.error}`)
|
|
489
|
+
.join('; ');
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
success: allSuccess,
|
|
493
|
+
error: allSuccess ? undefined : `Some inputs failed: ${errors}`,
|
|
494
|
+
details: results
|
|
495
|
+
};
|
|
400
496
|
}
|
|
401
497
|
|
|
402
|
-
//
|
|
403
|
-
|
|
498
|
+
// Handle single input (backward compatibility)
|
|
499
|
+
if (ref && text !== undefined) {
|
|
500
|
+
const selector = `aria-ref=${ref}`;
|
|
501
|
+
const element = await page.locator(selector).first();
|
|
502
|
+
|
|
503
|
+
const exists = await element.count() > 0;
|
|
504
|
+
if (!exists) {
|
|
505
|
+
return { success: false, error: `Element with ref ${ref} not found` };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Type text using Playwright's built-in fill method
|
|
509
|
+
await element.fill(text);
|
|
510
|
+
|
|
511
|
+
return { success: true };
|
|
512
|
+
}
|
|
404
513
|
|
|
405
|
-
return { success:
|
|
514
|
+
return { success: false, error: 'No valid input provided' };
|
|
406
515
|
} catch (error) {
|
|
407
516
|
return { success: false, error: `Type failed: ${error}` };
|
|
408
517
|
}
|
|
@@ -434,7 +543,97 @@ export class HybridBrowserSession {
|
|
|
434
543
|
}
|
|
435
544
|
}
|
|
436
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Simplified mouse control implementation
|
|
548
|
+
*/
|
|
549
|
+
private async performMouseControl(page: Page, control: string, x: number, y: number): Promise<{ success: boolean; error?: string }> {
|
|
550
|
+
try {
|
|
551
|
+
const viewport = page.viewportSize();
|
|
552
|
+
if (!viewport) {
|
|
553
|
+
return { success: false, error: 'Viewport size not available from page.' };
|
|
554
|
+
}
|
|
555
|
+
if (x < 0 || y < 0 || x > viewport.width || y > viewport.height) {
|
|
556
|
+
return { success: false, error: `Invalid coordinates, outside viewport bounds: (${x}, ${y})` };
|
|
557
|
+
}
|
|
558
|
+
switch (control) {
|
|
559
|
+
case 'click': {
|
|
560
|
+
await page.mouse.click(x, y);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case 'right_click': {
|
|
564
|
+
await page.mouse.click(x, y, { button: 'right' });
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'dblclick': {
|
|
568
|
+
await page.mouse.dblclick(x, y);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
default:
|
|
572
|
+
return { success: false, error: `Invalid control action: ${control}` };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return { success: true };
|
|
576
|
+
} catch (error) {
|
|
577
|
+
return { success: false, error: `Mouse action failed: ${error}` };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Enhanced mouse drag and drop implementation using ref IDs
|
|
583
|
+
*/
|
|
584
|
+
private async performMouseDrag(page: Page, fromRef: string, toRef: string): Promise<{ success: boolean; error?: string }> {
|
|
585
|
+
try {
|
|
586
|
+
// Ensure we have the latest snapshot
|
|
587
|
+
await (page as any)._snapshotForAI();
|
|
588
|
+
|
|
589
|
+
// Get elements using Playwright's aria-ref selector
|
|
590
|
+
const fromSelector = `aria-ref=${fromRef}`;
|
|
591
|
+
const toSelector = `aria-ref=${toRef}`;
|
|
592
|
+
|
|
593
|
+
const fromElement = await page.locator(fromSelector).first();
|
|
594
|
+
const toElement = await page.locator(toSelector).first();
|
|
595
|
+
|
|
596
|
+
// Check if elements exist
|
|
597
|
+
const fromExists = await fromElement.count() > 0;
|
|
598
|
+
const toExists = await toElement.count() > 0;
|
|
599
|
+
|
|
600
|
+
if (!fromExists) {
|
|
601
|
+
return { success: false, error: `Source element with ref ${fromRef} not found` };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!toExists) {
|
|
605
|
+
return { success: false, error: `Target element with ref ${toRef} not found` };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Get the center coordinates of both elements
|
|
609
|
+
const fromBox = await fromElement.boundingBox();
|
|
610
|
+
const toBox = await toElement.boundingBox();
|
|
611
|
+
|
|
612
|
+
if (!fromBox) {
|
|
613
|
+
return { success: false, error: `Could not get bounding box for source element with ref ${fromRef}` };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (!toBox) {
|
|
617
|
+
return { success: false, error: `Could not get bounding box for target element with ref ${toRef}` };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const fromX = fromBox.x + fromBox.width / 2;
|
|
621
|
+
const fromY = fromBox.y + fromBox.height / 2;
|
|
622
|
+
const toX = toBox.x + toBox.width / 2;
|
|
623
|
+
const toY = toBox.y + toBox.height / 2;
|
|
624
|
+
|
|
625
|
+
// Perform the drag operation
|
|
626
|
+
await page.mouse.move(fromX, fromY);
|
|
627
|
+
await page.mouse.down();
|
|
628
|
+
// Destination coordinates
|
|
629
|
+
await page.mouse.move(toX, toY);
|
|
630
|
+
await page.mouse.up();
|
|
437
631
|
|
|
632
|
+
return { success: true };
|
|
633
|
+
} catch (error) {
|
|
634
|
+
return { success: false, error: `Mouse drag action failed: ${error}` };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
438
637
|
|
|
439
638
|
async executeAction(action: BrowserAction): Promise<ActionResult> {
|
|
440
639
|
const startTime = Date.now();
|
|
@@ -450,6 +649,8 @@ export class HybridBrowserSession {
|
|
|
450
649
|
// No need to pre-fetch snapshot - each action method handles this
|
|
451
650
|
|
|
452
651
|
let newTabId: string | undefined;
|
|
652
|
+
let customMessage: string | undefined;
|
|
653
|
+
let actionDetails: Record<string, any> | undefined;
|
|
453
654
|
|
|
454
655
|
switch (action.type) {
|
|
455
656
|
case 'click': {
|
|
@@ -474,12 +675,20 @@ export class HybridBrowserSession {
|
|
|
474
675
|
elementSearchTime = Date.now() - elementSearchStart;
|
|
475
676
|
const typeStart = Date.now();
|
|
476
677
|
|
|
477
|
-
const typeResult = await this.performType(page, action.ref, action.text);
|
|
678
|
+
const typeResult = await this.performType(page, action.ref, action.text, action.inputs);
|
|
478
679
|
|
|
479
680
|
if (!typeResult.success) {
|
|
480
681
|
throw new Error(`Type failed: ${typeResult.error}`);
|
|
481
682
|
}
|
|
482
683
|
|
|
684
|
+
// Set custom message and details if multiple inputs were used
|
|
685
|
+
if (typeResult.details) {
|
|
686
|
+
const successCount = Object.values(typeResult.details).filter((r: any) => r.success).length;
|
|
687
|
+
const totalCount = Object.keys(typeResult.details).length;
|
|
688
|
+
customMessage = `Typed text into ${successCount}/${totalCount} elements`;
|
|
689
|
+
actionDetails = typeResult.details;
|
|
690
|
+
}
|
|
691
|
+
|
|
483
692
|
actionExecutionTime = Date.now() - typeStart;
|
|
484
693
|
break;
|
|
485
694
|
}
|
|
@@ -519,6 +728,40 @@ export class HybridBrowserSession {
|
|
|
519
728
|
actionExecutionTime = Date.now() - enterStart;
|
|
520
729
|
break;
|
|
521
730
|
}
|
|
731
|
+
|
|
732
|
+
case 'mouse_control': {
|
|
733
|
+
elementSearchTime = Date.now() - elementSearchStart;
|
|
734
|
+
const mouseControlStart = Date.now();
|
|
735
|
+
const mouseControlResult = await this.performMouseControl(page, action.control, action.x, action.y);
|
|
736
|
+
|
|
737
|
+
if (!mouseControlResult.success) {
|
|
738
|
+
throw new Error(`Action failed: ${mouseControlResult.error}`);
|
|
739
|
+
}
|
|
740
|
+
actionExecutionTime = Date.now() - mouseControlStart;
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
case 'mouse_drag': {
|
|
745
|
+
elementSearchTime = Date.now() - elementSearchStart;
|
|
746
|
+
const mouseDragStart = Date.now();
|
|
747
|
+
const mouseDragResult = await this.performMouseDrag(page, action.from_ref, action.to_ref);
|
|
748
|
+
|
|
749
|
+
if (!mouseDragResult.success) {
|
|
750
|
+
throw new Error(`Action failed: ${mouseDragResult.error}`);
|
|
751
|
+
}
|
|
752
|
+
actionExecutionTime = Date.now() - mouseDragStart;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
case 'press_key': {
|
|
757
|
+
elementSearchTime = Date.now() - elementSearchStart;
|
|
758
|
+
const keyPressStart = Date.now();
|
|
759
|
+
// concatenate keys with '+' for key combinations
|
|
760
|
+
const keys = action.keys.join('+');
|
|
761
|
+
await page.keyboard.press(keys);
|
|
762
|
+
actionExecutionTime = Date.now() - keyPressStart;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
522
765
|
|
|
523
766
|
default:
|
|
524
767
|
throw new Error(`Unknown action type: ${(action as any).type}`);
|
|
@@ -533,7 +776,7 @@ export class HybridBrowserSession {
|
|
|
533
776
|
|
|
534
777
|
return {
|
|
535
778
|
success: true,
|
|
536
|
-
message: `Action ${action.type} executed successfully`,
|
|
779
|
+
message: customMessage || `Action ${action.type} executed successfully`,
|
|
537
780
|
timing: {
|
|
538
781
|
total_time_ms: totalTime,
|
|
539
782
|
element_search_time_ms: elementSearchTime,
|
|
@@ -543,6 +786,7 @@ export class HybridBrowserSession {
|
|
|
543
786
|
network_idle_time_ms: stabilityResult.networkIdleTime,
|
|
544
787
|
},
|
|
545
788
|
...(newTabId && { newTabId }), // Include new tab ID if present
|
|
789
|
+
...(actionDetails && { details: actionDetails }), // Include action details if present
|
|
546
790
|
};
|
|
547
791
|
} catch (error) {
|
|
548
792
|
const totalTime = Date.now() - startTime;
|
|
@@ -584,16 +828,23 @@ export class HybridBrowserSession {
|
|
|
584
828
|
|
|
585
829
|
try {
|
|
586
830
|
// Get current page to check if it's blank
|
|
587
|
-
|
|
588
|
-
|
|
831
|
+
let currentPage: Page;
|
|
832
|
+
let currentUrl: string;
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
currentPage = await this.getCurrentPage();
|
|
836
|
+
currentUrl = currentPage.url();
|
|
837
|
+
} catch (error: any) {
|
|
838
|
+
// If no active page is available, getCurrentPage() will create one in CDP mode
|
|
839
|
+
console.log('[visitPage] Failed to get current page:', error);
|
|
840
|
+
throw new Error(`No active page available: ${error?.message || error}`);
|
|
841
|
+
}
|
|
589
842
|
|
|
590
843
|
// Check if current page is blank or if this is the first navigation
|
|
591
844
|
const browserConfig = this.configLoader.getBrowserConfig();
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
currentUrl.startsWith(browserConfig.dataUrlPrefix) // data URLs are often used for blank pages
|
|
596
|
-
);
|
|
845
|
+
|
|
846
|
+
// Use unified blank page detection
|
|
847
|
+
const isBlankPage = this.isBlankPageUrl(currentUrl) || currentUrl === browserConfig.defaultStartUrl;
|
|
597
848
|
|
|
598
849
|
const shouldUseCurrentTab = isBlankPage || !this.hasNavigatedBefore;
|
|
599
850
|
|
|
@@ -648,10 +899,10 @@ export class HybridBrowserSession {
|
|
|
648
899
|
const pageUrl = page.url();
|
|
649
900
|
// Check if this page is not already tracked and is blank
|
|
650
901
|
const isTracked = Array.from(this.pages.values()).includes(page);
|
|
651
|
-
if (!isTracked && pageUrl
|
|
902
|
+
if (!isTracked && this.isBlankPageUrl(pageUrl)) {
|
|
652
903
|
newPage = page;
|
|
653
904
|
newTabId = this.generateTabId();
|
|
654
|
-
this.
|
|
905
|
+
this.registerNewPage(newTabId, newPage);
|
|
655
906
|
break;
|
|
656
907
|
}
|
|
657
908
|
}
|
|
@@ -663,7 +914,7 @@ export class HybridBrowserSession {
|
|
|
663
914
|
// Non-CDP mode: create new page as usual
|
|
664
915
|
newPage = await this.context.newPage();
|
|
665
916
|
newTabId = this.generateTabId();
|
|
666
|
-
this.
|
|
917
|
+
this.registerNewPage(newTabId, newPage);
|
|
667
918
|
}
|
|
668
919
|
|
|
669
920
|
// Set up page properties
|
|
@@ -30,6 +30,7 @@ export interface BrowserConfig {
|
|
|
30
30
|
// Tab management
|
|
31
31
|
tabIdPrefix: string;
|
|
32
32
|
tabCounterPadding: number;
|
|
33
|
+
consoleLogLimit: number;
|
|
33
34
|
|
|
34
35
|
// Scroll and positioning
|
|
35
36
|
scrollPositionScale: number;
|
|
@@ -113,9 +114,10 @@ function getDefaultBrowserConfig(): BrowserConfig {
|
|
|
113
114
|
clickTimeout: 3000,
|
|
114
115
|
tabIdPrefix: 'tab-',
|
|
115
116
|
tabCounterPadding: 3,
|
|
117
|
+
consoleLogLimit: 1000,
|
|
116
118
|
scrollPositionScale: 0.1,
|
|
117
119
|
navigationDelay: 100,
|
|
118
|
-
blankPageUrls: [
|
|
120
|
+
blankPageUrls: [],
|
|
119
121
|
dataUrlPrefix: 'data:',
|
|
120
122
|
domContentLoadedState: 'domcontentloaded',
|
|
121
123
|
networkIdleState: 'networkidle',
|