camel-ai 0.2.70__py3-none-any.whl → 0.2.71a2__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 +61 -3
- camel/messages/func_message.py +32 -5
- camel/societies/workforce/role_playing_worker.py +4 -4
- camel/societies/workforce/single_agent_worker.py +5 -9
- camel/societies/workforce/workforce.py +304 -49
- camel/societies/workforce/workforce_logger.py +0 -1
- camel/tasks/task.py +83 -7
- camel/toolkits/craw4ai_toolkit.py +27 -7
- camel/toolkits/file_write_toolkit.py +110 -31
- camel/toolkits/human_toolkit.py +29 -9
- camel/toolkits/jina_reranker_toolkit.py +3 -4
- camel/toolkits/non_visual_browser_toolkit/browser_non_visual_toolkit.py +23 -2
- camel/toolkits/non_visual_browser_toolkit/nv_browser_session.py +53 -11
- camel/toolkits/non_visual_browser_toolkit/snapshot.js +211 -131
- camel/toolkits/non_visual_browser_toolkit/snapshot.py +9 -8
- camel/toolkits/terminal_toolkit.py +206 -64
- camel/toolkits/video_download_toolkit.py +6 -3
- camel/utils/message_summarizer.py +148 -0
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/METADATA +4 -4
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/RECORD +23 -22
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,188 +1,268 @@
|
|
|
1
1
|
(() => {
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// Maximum lines allowed before we start dropping lower-priority nodes
|
|
6
|
-
const MAX_LINES = 400;
|
|
7
|
-
|
|
8
|
-
// Priority helper – lower number = higher priority
|
|
9
|
-
function getPriority(tag, role, text) {
|
|
10
|
-
// 1. Interactive elements
|
|
11
|
-
if (["input", "button", "a", "select", "textarea"].includes(tag)) return 1;
|
|
12
|
-
if (["checkbox", "radio"].includes(role)) return 1;
|
|
13
|
-
|
|
14
|
-
// 2. Labels / descriptive adjacent text (label elements)
|
|
15
|
-
if (tag === "label") return 2;
|
|
16
|
-
|
|
17
|
-
// 3. General visible text
|
|
18
|
-
if (text) return 3;
|
|
19
|
-
|
|
20
|
-
// 4. Low-value structural nodes
|
|
21
|
-
return 4;
|
|
22
|
-
}
|
|
2
|
+
// Playwright's snapshot logic focuses on semantics and visibility, not arbitrary limits.
|
|
3
|
+
// We will first build a semantic tree in memory, then render it.
|
|
23
4
|
|
|
24
5
|
function isVisible(node) {
|
|
25
|
-
|
|
26
|
-
if (rect.width === 0 || rect.height === 0) return false;
|
|
27
|
-
|
|
6
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return true;
|
|
28
7
|
const style = window.getComputedStyle(node);
|
|
29
|
-
if (style.display === 'none' || style.visibility === 'hidden'
|
|
30
|
-
|
|
31
|
-
|
|
8
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0')
|
|
9
|
+
return false;
|
|
10
|
+
// An element with `display: contents` is not rendered itself, but its children are.
|
|
11
|
+
if (style.display === 'contents')
|
|
12
|
+
return true;
|
|
13
|
+
const rect = node.getBoundingClientRect();
|
|
14
|
+
return rect.width > 0 && rect.height > 0;
|
|
32
15
|
}
|
|
33
16
|
|
|
34
17
|
function getRole(node) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (node.getAttribute('role')) return node.getAttribute('role');
|
|
18
|
+
const role = node.getAttribute('role');
|
|
19
|
+
if (role) return role;
|
|
39
20
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
21
|
+
const tagName = node.tagName.toLowerCase();
|
|
22
|
+
if (tagName === 'a') return 'link';
|
|
23
|
+
if (tagName === 'button') return 'button';
|
|
24
|
+
if (tagName === 'input') {
|
|
25
|
+
const type = node.getAttribute('type')?.toLowerCase();
|
|
26
|
+
if (['button', 'checkbox', 'radio', 'reset', 'submit'].includes(type)) return type;
|
|
27
|
+
return 'textbox';
|
|
44
28
|
}
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
if (tag === 'a') return 'link';
|
|
48
|
-
if (tag === 'select') return 'select';
|
|
49
|
-
if (tag === 'textarea') return 'textarea';
|
|
50
|
-
if (tag === 'p') return 'paragraph';
|
|
51
|
-
if (tag === 'span') return 'text';
|
|
52
|
-
|
|
29
|
+
if (['select', 'textarea'].includes(tagName)) return tagName;
|
|
30
|
+
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) return 'heading';
|
|
53
31
|
return 'generic';
|
|
54
32
|
}
|
|
55
33
|
|
|
56
34
|
function getAccessibleName(node) {
|
|
57
|
-
if (node.hasAttribute('aria-label'))
|
|
58
|
-
return node.getAttribute('aria-label');
|
|
59
|
-
}
|
|
35
|
+
if (node.hasAttribute('aria-label')) return node.getAttribute('aria-label') || '';
|
|
60
36
|
if (node.hasAttribute('aria-labelledby')) {
|
|
61
37
|
const id = node.getAttribute('aria-labelledby');
|
|
62
38
|
const labelEl = document.getElementById(id);
|
|
63
|
-
if (labelEl) return labelEl.textContent
|
|
39
|
+
if (labelEl) return labelEl.textContent || '';
|
|
64
40
|
}
|
|
65
|
-
|
|
66
|
-
|
|
41
|
+
// This is the new, visibility-aware text extraction logic.
|
|
42
|
+
const text = getVisibleTextContent(node);
|
|
43
|
+
|
|
44
|
+
// Add a heuristic to ignore code-like text that might be in the DOM
|
|
45
|
+
if ((text.match(/[;:{}]/g)?.length || 0) > 2) return '';
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const textCache = new Map();
|
|
50
|
+
function getVisibleTextContent(_node) {
|
|
51
|
+
if (textCache.has(_node)) return textCache.get(_node);
|
|
52
|
+
|
|
53
|
+
if (_node.nodeType === Node.TEXT_NODE) {
|
|
54
|
+
// For a text node, its content is visible if its parent is.
|
|
55
|
+
// The isVisible check on the parent happens before this recursion.
|
|
56
|
+
return _node.nodeValue || '';
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
if (['style', 'script', 'meta', 'noscript', 'svg'].includes(tagName)) {
|
|
59
|
+
if (_node.nodeType !== Node.ELEMENT_NODE || !isVisible(_node) || ['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'HEAD'].includes(_node.tagName)) {
|
|
71
60
|
return '';
|
|
72
61
|
}
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if ((text.match(/[;:{}]/g)?.length || 0) > 2) return '';
|
|
63
|
+
let result = '';
|
|
64
|
+
for (const child of _node.childNodes) {
|
|
65
|
+
result += getVisibleTextContent(child);
|
|
66
|
+
}
|
|
79
67
|
|
|
80
|
-
|
|
68
|
+
// Caching the result for performance.
|
|
69
|
+
textCache.set(_node, result);
|
|
70
|
+
return result;
|
|
81
71
|
}
|
|
82
72
|
|
|
83
73
|
let refCounter = 1;
|
|
74
|
+
function generateRef() {
|
|
75
|
+
return `e${refCounter++}`;
|
|
76
|
+
}
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Phase 1: Build an in-memory representation of the accessibility tree.
|
|
80
|
+
*/
|
|
81
|
+
function buildAriaTree(rootElement) {
|
|
82
|
+
const visited = new Set();
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
function toAriaNode(element) {
|
|
85
|
+
// Only consider visible elements
|
|
86
|
+
if (!isVisible(element)) return null;
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
for (const child of node.children) {
|
|
96
|
-
traverse(child, depth + 1);
|
|
97
|
-
}
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
88
|
+
const role = getRole(element);
|
|
89
|
+
// 'presentation' and 'none' roles are ignored, but their children are processed.
|
|
90
|
+
if (['presentation', 'none'].includes(role)) return null;
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
node.getAttribute('role') || text;
|
|
92
|
+
const name = getAccessibleName(element);
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
// Create the node
|
|
95
|
+
const node = {
|
|
96
|
+
role,
|
|
97
|
+
name,
|
|
98
|
+
children: [],
|
|
99
|
+
element: element,
|
|
100
|
+
ref: generateRef(),
|
|
101
|
+
};
|
|
108
102
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const priority = getPriority(tagName, role, text);
|
|
103
|
+
// Add states for interactive elements, similar to Playwright
|
|
104
|
+
if (element.hasAttribute('disabled')) node.disabled = true;
|
|
105
|
+
if (element.hasAttribute('aria-checked')) node.checked = element.getAttribute('aria-checked');
|
|
106
|
+
if (element.hasAttribute('aria-expanded')) node.expanded = element.getAttribute('aria-expanded');
|
|
114
107
|
|
|
115
|
-
|
|
108
|
+
// Tag element with a ref for later lookup
|
|
109
|
+
element.setAttribute('aria-ref', node.ref);
|
|
116
110
|
|
|
117
|
-
|
|
118
|
-
node.setAttribute('aria-ref', ref);
|
|
111
|
+
return node;
|
|
119
112
|
}
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
function traverse(element, parentNode) {
|
|
115
|
+
if (visited.has(element)) return;
|
|
116
|
+
visited.add(element);
|
|
117
|
+
|
|
118
|
+
// FIX: Completely skip script and style tags and their children.
|
|
119
|
+
const tagName = element.tagName.toLowerCase();
|
|
120
|
+
if (['script', 'style', 'meta', 'noscript'].includes(tagName))
|
|
121
|
+
return;
|
|
122
|
+
|
|
123
|
+
// Check if element is explicitly hidden by CSS - if so, skip entirely including children
|
|
124
|
+
const style = window.getComputedStyle(element);
|
|
125
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const ariaNode = toAriaNode(element);
|
|
130
|
+
// If the element is not rendered or is presentational, its children
|
|
131
|
+
// are attached directly to the parent.
|
|
132
|
+
const newParent = ariaNode || parentNode;
|
|
133
|
+
if (ariaNode) parentNode.children.push(ariaNode);
|
|
134
|
+
|
|
135
|
+
for (const child of element.childNodes) {
|
|
136
|
+
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
137
|
+
traverse(child, newParent);
|
|
138
|
+
} else if (child.nodeType === Node.TEXT_NODE) {
|
|
139
|
+
const text = (child.textContent || '').trim();
|
|
140
|
+
if (text) newParent.children.push(text);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Also traverse into shadow DOM if it exists
|
|
145
|
+
if (element.shadowRoot) {
|
|
146
|
+
for (const child of element.shadowRoot.childNodes) {
|
|
147
|
+
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
148
|
+
traverse(child, newParent);
|
|
149
|
+
} else if (child.nodeType === Node.TEXT_NODE) {
|
|
150
|
+
const text = (child.textContent || '').trim();
|
|
151
|
+
if (text) newParent.children.push(text);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// FIX: If an element's name is the same as its only text child, remove the redundant child.
|
|
157
|
+
if (ariaNode && ariaNode.children.length === 1 && typeof ariaNode.children[0] === 'string' && ariaNode.name === ariaNode.children[0]) {
|
|
158
|
+
ariaNode.children = [];
|
|
159
|
+
}
|
|
123
160
|
}
|
|
161
|
+
|
|
162
|
+
const root = { role: 'Root', name: '', children: [], element: rootElement };
|
|
163
|
+
traverse(rootElement, root);
|
|
164
|
+
return root;
|
|
124
165
|
}
|
|
125
166
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Phase 2: Normalize the tree by removing redundant generic wrappers.
|
|
169
|
+
* This is a key optimization in Playwright to simplify the structure.
|
|
170
|
+
*/
|
|
171
|
+
function normalizeTree(node) {
|
|
172
|
+
if (typeof node === 'string') return [node];
|
|
132
173
|
|
|
133
|
-
const
|
|
134
|
-
for (const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
174
|
+
const newChildren = [];
|
|
175
|
+
for (const child of node.children) {
|
|
176
|
+
newChildren.push(...normalizeTree(child));
|
|
177
|
+
}
|
|
178
|
+
node.children = newChildren;
|
|
179
|
+
|
|
180
|
+
// Remove child elements that have the same name as their parent
|
|
181
|
+
if (node.children.length === 1 && typeof node.children[0] !== 'string') {
|
|
182
|
+
const child = node.children[0];
|
|
183
|
+
if (child.name && node.name && child.name.trim() === node.name.trim()) {
|
|
184
|
+
// Merge child's children into parent and remove the redundant child
|
|
185
|
+
node.children = child.children || [];
|
|
141
186
|
}
|
|
142
187
|
}
|
|
188
|
+
|
|
189
|
+
// A 'generic' role that just wraps a single other element is redundant.
|
|
190
|
+
// We lift its child up to replace it, simplifying the hierarchy.
|
|
191
|
+
const isRedundantWrapper = node.role === 'generic' && node.children.length === 1 && typeof node.children[0] !== 'string';
|
|
192
|
+
if (isRedundantWrapper) {
|
|
193
|
+
return node.children;
|
|
194
|
+
}
|
|
195
|
+
return [node];
|
|
143
196
|
}
|
|
144
197
|
|
|
145
|
-
processDocument(document);
|
|
146
198
|
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Phase 3: Render the normalized tree into the final string format.
|
|
201
|
+
*/
|
|
202
|
+
function renderTree(node, indent = '') {
|
|
203
|
+
const lines = [];
|
|
204
|
+
let meaningfulProps = '';
|
|
205
|
+
if (node.disabled) meaningfulProps += ' disabled';
|
|
206
|
+
if (node.checked !== undefined) meaningfulProps += ` checked=${node.checked}`;
|
|
207
|
+
if (node.expanded !== undefined) meaningfulProps += ` expanded=${node.expanded}`;
|
|
208
|
+
|
|
209
|
+
const ref = node.ref ? ` [ref=${node.ref}]` : '';
|
|
210
|
+
const name = (node.name || '').replace(/\s+/g, ' ').trim();
|
|
149
211
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
212
|
+
// Skip elements with empty names and no meaningful props (ref is not considered meaningful)
|
|
213
|
+
if (!name && !meaningfulProps) {
|
|
214
|
+
// If element has no name and no meaningful props, render its children directly at current level
|
|
215
|
+
for (const child of node.children) {
|
|
216
|
+
if (typeof child === 'string') {
|
|
217
|
+
const childText = child.replace(/\s+/g, ' ').trim();
|
|
218
|
+
if (childText) { // Only add non-empty text
|
|
219
|
+
lines.push(`${indent}- text "${childText}"`);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
lines.push(...renderTree(child, indent));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return lines;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
lines.push(`${indent}- ${node.role}${name ? ` "${name}"` : ''}${meaningfulProps}${ref}`);
|
|
153
229
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
230
|
+
for (const child of node.children) {
|
|
231
|
+
if (typeof child === 'string') {
|
|
232
|
+
const childText = child.replace(/\s+/g, ' ').trim();
|
|
233
|
+
if (childText) { // Only add non-empty text
|
|
234
|
+
lines.push(`${indent} - text "${childText}"`);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
lines.push(...renderTree(child, indent + ' '));
|
|
160
238
|
}
|
|
161
239
|
}
|
|
240
|
+
return lines;
|
|
162
241
|
}
|
|
163
242
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const depthStack = []; // keeps track of kept original depths
|
|
172
|
-
|
|
173
|
-
for (const el of finalElements) {
|
|
174
|
-
// Pop depths that are not ancestors of current element
|
|
175
|
-
while (depthStack.length && depthStack[depthStack.length - 1] >= el.depth) {
|
|
176
|
-
depthStack.pop();
|
|
177
|
-
}
|
|
243
|
+
function processDocument(doc) {
|
|
244
|
+
if (!doc.body) return [];
|
|
245
|
+
|
|
246
|
+
// Clear cache for each new document processing.
|
|
247
|
+
textCache.clear();
|
|
248
|
+
let tree = buildAriaTree(doc.body);
|
|
249
|
+
[tree] = normalizeTree(tree);
|
|
178
250
|
|
|
179
|
-
|
|
180
|
-
depthStack.push(el.depth);
|
|
251
|
+
const lines = renderTree(tree).slice(1); // Skip the root node line
|
|
181
252
|
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
|
|
253
|
+
const frames = doc.querySelectorAll('iframe');
|
|
254
|
+
for (const frame of frames) {
|
|
255
|
+
try {
|
|
256
|
+
if (frame.contentDocument) {
|
|
257
|
+
lines.push(...processDocument(frame.contentDocument));
|
|
258
|
+
}
|
|
259
|
+
} catch (e) {
|
|
260
|
+
// Skip cross-origin iframes
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return lines;
|
|
185
264
|
}
|
|
186
265
|
|
|
266
|
+
const outputLines = processDocument(document);
|
|
187
267
|
return outputLines.join('\n');
|
|
188
268
|
})();
|
|
@@ -48,14 +48,15 @@ class PageSnapshot:
|
|
|
48
48
|
try:
|
|
49
49
|
current_url = self.page.url
|
|
50
50
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
# Previously we skipped regeneration when the URL had not changed
|
|
52
|
+
# and no explicit refresh was requested. This prevented the agent
|
|
53
|
+
# from seeing DOM updates that occur without a navigation (e.g.
|
|
54
|
+
# single-page apps, dynamic games such as Wordle). The early-exit
|
|
55
|
+
# logic has been removed so that we always capture a *fresh* DOM
|
|
56
|
+
# snapshot. If the snapshot happens to be byte-for-byte identical
|
|
57
|
+
# to the previous one we simply return it after the standard
|
|
58
|
+
# comparison step below; otherwise callers receive the updated
|
|
59
|
+
# snapshot even when the URL did not change.
|
|
59
60
|
|
|
60
61
|
# ensure DOM stability
|
|
61
62
|
await self.page.wait_for_load_state(
|