code-puppy 0.0.357__py3-none-any.whl → 0.0.359__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.
- code_puppy/agents/event_stream_handler.py +27 -7
- code_puppy/tools/browser/browser_screenshot.py +41 -28
- code_puppy/tools/browser/terminal_screenshot_tools.py +113 -77
- {code_puppy-0.0.357.dist-info → code_puppy-0.0.359.dist-info}/METADATA +1 -1
- {code_puppy-0.0.357.dist-info → code_puppy-0.0.359.dist-info}/RECORD +10 -10
- {code_puppy-0.0.357.data → code_puppy-0.0.359.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.357.data → code_puppy-0.0.359.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.357.dist-info → code_puppy-0.0.359.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.357.dist-info → code_puppy-0.0.359.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.357.dist-info → code_puppy-0.0.359.dist-info}/licenses/LICENSE +0 -0
|
@@ -119,6 +119,7 @@ async def event_stream_handler(
|
|
|
119
119
|
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
120
120
|
banner_printed: set[int] = set() # Track if banner was already printed
|
|
121
121
|
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
122
|
+
tool_names: dict[int, str] = {} # Track tool name per tool part index
|
|
122
123
|
did_stream_anything = False # Track if we streamed any content
|
|
123
124
|
|
|
124
125
|
# Termflow streaming state for text parts
|
|
@@ -203,6 +204,8 @@ async def event_stream_handler(
|
|
|
203
204
|
streaming_parts.add(event.index)
|
|
204
205
|
tool_parts.add(event.index)
|
|
205
206
|
token_count[event.index] = 0 # Initialize token counter
|
|
207
|
+
# Capture tool name from the start event
|
|
208
|
+
tool_names[event.index] = part.tool_name or ""
|
|
206
209
|
# Track tool name for display
|
|
207
210
|
banner_printed.add(
|
|
208
211
|
event.index
|
|
@@ -253,20 +256,36 @@ async def event_stream_handler(
|
|
|
253
256
|
escaped = escape(delta.content_delta)
|
|
254
257
|
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
255
258
|
elif isinstance(delta, ToolCallPartDelta):
|
|
256
|
-
# For tool calls,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
259
|
+
# For tool calls, estimate tokens from args_delta content
|
|
260
|
+
# args_delta contains the streaming JSON arguments
|
|
261
|
+
args_delta = getattr(delta, "args_delta", "") or ""
|
|
262
|
+
if args_delta:
|
|
263
|
+
# Rough estimate: 4 chars ≈ 1 token (same heuristic as subagent_stream_handler)
|
|
264
|
+
estimated_tokens = max(1, len(args_delta) // 4)
|
|
265
|
+
token_count[event.index] += estimated_tokens
|
|
266
|
+
else:
|
|
267
|
+
# Even empty deltas count as activity
|
|
268
|
+
token_count[event.index] += 1
|
|
269
|
+
|
|
270
|
+
# Update tool name if delta provides more of it
|
|
271
|
+
tool_name_delta = getattr(delta, "tool_name_delta", "") or ""
|
|
272
|
+
if tool_name_delta:
|
|
273
|
+
tool_names[event.index] = (
|
|
274
|
+
tool_names.get(event.index, "") + tool_name_delta
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Use stored tool name for display
|
|
278
|
+
tool_name = tool_names.get(event.index, "")
|
|
260
279
|
count = token_count[event.index]
|
|
261
280
|
# Display with tool wrench icon and tool name
|
|
262
281
|
if tool_name:
|
|
263
282
|
console.print(
|
|
264
|
-
f" \U0001f527 Calling {tool_name}... {count}
|
|
283
|
+
f" \U0001f527 Calling {tool_name}... {count} token(s) ",
|
|
265
284
|
end="\r",
|
|
266
285
|
)
|
|
267
286
|
else:
|
|
268
287
|
console.print(
|
|
269
|
-
f" \U0001f527 Calling tool... {count}
|
|
288
|
+
f" \U0001f527 Calling tool... {count} token(s) ",
|
|
270
289
|
end="\r",
|
|
271
290
|
)
|
|
272
291
|
|
|
@@ -311,8 +330,9 @@ async def event_stream_handler(
|
|
|
311
330
|
elif event.index in banner_printed:
|
|
312
331
|
console.print() # Final newline after streaming
|
|
313
332
|
|
|
314
|
-
# Clean up token count
|
|
333
|
+
# Clean up token count and tool names
|
|
315
334
|
token_count.pop(event.index, None)
|
|
335
|
+
tool_names.pop(event.index, None)
|
|
316
336
|
# Clean up all tracking sets
|
|
317
337
|
streaming_parts.discard(event.index)
|
|
318
338
|
thinking_parts.discard(event.index)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"""Screenshot tool for browser automation.
|
|
2
2
|
|
|
3
|
-
Captures screenshots and returns them
|
|
4
|
-
models can directly see and analyze - no separate VQA agent needed.
|
|
3
|
+
Captures screenshots and returns them via ToolReturn with BinaryContent
|
|
4
|
+
so multimodal models can directly see and analyze - no separate VQA agent needed.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import time
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from tempfile import gettempdir, mkdtemp
|
|
11
|
-
from typing import Any, Dict, Optional
|
|
11
|
+
from typing import Any, Dict, Optional, Union
|
|
12
12
|
|
|
13
|
-
from pydantic_ai import RunContext
|
|
13
|
+
from pydantic_ai import BinaryContent, RunContext, ToolReturn
|
|
14
14
|
|
|
15
15
|
from code_puppy.messaging import emit_error, emit_info, emit_success
|
|
16
16
|
from code_puppy.tools.common import generate_group_id
|
|
@@ -54,7 +54,6 @@ async def _capture_screenshot(
|
|
|
54
54
|
result: Dict[str, Any] = {
|
|
55
55
|
"success": True,
|
|
56
56
|
"screenshot_bytes": screenshot_bytes,
|
|
57
|
-
"base64_data": base64.b64encode(screenshot_bytes).decode("utf-8"),
|
|
58
57
|
"timestamp": timestamp,
|
|
59
58
|
}
|
|
60
59
|
|
|
@@ -80,11 +79,11 @@ async def take_screenshot(
|
|
|
80
79
|
full_page: bool = False,
|
|
81
80
|
element_selector: Optional[str] = None,
|
|
82
81
|
save_screenshot: bool = True,
|
|
83
|
-
) -> Dict[str, Any]:
|
|
82
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
84
83
|
"""Take a screenshot of the browser page.
|
|
85
84
|
|
|
86
|
-
Returns
|
|
87
|
-
|
|
85
|
+
Returns a ToolReturn with BinaryContent so multimodal models can
|
|
86
|
+
directly see and analyze the screenshot.
|
|
88
87
|
|
|
89
88
|
Args:
|
|
90
89
|
full_page: Whether to capture full page or just viewport.
|
|
@@ -92,12 +91,11 @@ async def take_screenshot(
|
|
|
92
91
|
save_screenshot: Whether to save the screenshot to disk.
|
|
93
92
|
|
|
94
93
|
Returns:
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
- error (str): Error message if unsuccessful.
|
|
94
|
+
ToolReturn containing:
|
|
95
|
+
- return_value: Success message with screenshot path
|
|
96
|
+
- content: List with description and BinaryContent image
|
|
97
|
+
- metadata: Screenshot details (path, target, timestamp)
|
|
98
|
+
Or Dict with error info if failed.
|
|
101
99
|
"""
|
|
102
100
|
target = element_selector or ("full_page" if full_page else "viewport")
|
|
103
101
|
group_id = generate_group_id("browser_screenshot", target)
|
|
@@ -122,15 +120,30 @@ async def take_screenshot(
|
|
|
122
120
|
|
|
123
121
|
if not result["success"]:
|
|
124
122
|
emit_error(result.get("error", "Screenshot failed"), message_group=group_id)
|
|
125
|
-
return result
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
123
|
+
return {"success": False, "error": result.get("error")}
|
|
124
|
+
|
|
125
|
+
screenshot_path = result.get("screenshot_path", "(not saved)")
|
|
126
|
+
|
|
127
|
+
# Return as ToolReturn with BinaryContent so the model can SEE the image!
|
|
128
|
+
return ToolReturn(
|
|
129
|
+
return_value=f"Screenshot captured successfully. Saved to: {screenshot_path}",
|
|
130
|
+
content=[
|
|
131
|
+
f"Here's the browser screenshot ({target}):",
|
|
132
|
+
BinaryContent(
|
|
133
|
+
data=result["screenshot_bytes"],
|
|
134
|
+
media_type="image/png",
|
|
135
|
+
),
|
|
136
|
+
"Please analyze what you see and describe any relevant details.",
|
|
137
|
+
],
|
|
138
|
+
metadata={
|
|
139
|
+
"success": True,
|
|
140
|
+
"screenshot_path": screenshot_path,
|
|
141
|
+
"target": target,
|
|
142
|
+
"full_page": full_page,
|
|
143
|
+
"element_selector": element_selector,
|
|
144
|
+
"timestamp": time.time(),
|
|
145
|
+
},
|
|
146
|
+
)
|
|
134
147
|
|
|
135
148
|
except Exception as e:
|
|
136
149
|
error_msg = f"Screenshot failed: {str(e)}"
|
|
@@ -146,19 +159,19 @@ def register_take_screenshot_and_analyze(agent):
|
|
|
146
159
|
context: RunContext,
|
|
147
160
|
full_page: bool = False,
|
|
148
161
|
element_selector: Optional[str] = None,
|
|
149
|
-
) -> Dict[str, Any]:
|
|
162
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
150
163
|
"""
|
|
151
164
|
Take a screenshot of the browser page.
|
|
152
165
|
|
|
153
|
-
Returns the screenshot
|
|
154
|
-
Use this to see what's displayed in the browser.
|
|
166
|
+
Returns the screenshot via ToolReturn with BinaryContent that you can
|
|
167
|
+
see directly. Use this to see what's displayed in the browser.
|
|
155
168
|
|
|
156
169
|
Args:
|
|
157
170
|
full_page: Capture full page (True) or just viewport (False).
|
|
158
171
|
element_selector: Optional CSS selector to screenshot specific element.
|
|
159
172
|
|
|
160
173
|
Returns:
|
|
161
|
-
|
|
174
|
+
ToolReturn with the screenshot image you can analyze, or error dict.
|
|
162
175
|
"""
|
|
163
176
|
return await take_screenshot(
|
|
164
177
|
full_page=full_page,
|
|
@@ -5,22 +5,22 @@ This module provides tools for:
|
|
|
5
5
|
- Reading terminal output by scraping xterm.js DOM
|
|
6
6
|
- Loading images from the filesystem
|
|
7
7
|
|
|
8
|
-
Screenshots are returned
|
|
9
|
-
can directly see and analyze
|
|
8
|
+
Screenshots and images are returned via ToolReturn with BinaryContent
|
|
9
|
+
so multimodal models can directly see and analyze them.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Images are automatically resized to reduce token usage.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
import base64
|
|
15
14
|
import io
|
|
16
15
|
import logging
|
|
16
|
+
import time
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from tempfile import gettempdir, mkdtemp
|
|
20
|
-
from typing import Any, Dict
|
|
20
|
+
from typing import Any, Dict, Union
|
|
21
21
|
|
|
22
22
|
from PIL import Image
|
|
23
|
-
from pydantic_ai import RunContext
|
|
23
|
+
from pydantic_ai import BinaryContent, RunContext, ToolReturn
|
|
24
24
|
from rich.text import Text
|
|
25
25
|
|
|
26
26
|
from code_puppy.messaging import emit_error, emit_info, emit_success
|
|
@@ -178,7 +178,6 @@ async def _capture_terminal_screenshot(
|
|
|
178
178
|
result: Dict[str, Any] = {
|
|
179
179
|
"success": True,
|
|
180
180
|
"screenshot_bytes": screenshot_bytes,
|
|
181
|
-
"base64_data": base64.b64encode(screenshot_bytes).decode("utf-8"),
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
# Save to disk if requested (save the resized version)
|
|
@@ -205,11 +204,11 @@ async def _capture_terminal_screenshot(
|
|
|
205
204
|
async def terminal_screenshot(
|
|
206
205
|
full_page: bool = False,
|
|
207
206
|
save_to_disk: bool = True,
|
|
208
|
-
) -> Dict[str, Any]:
|
|
207
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
209
208
|
"""Take a screenshot of the terminal browser.
|
|
210
209
|
|
|
211
|
-
Captures a screenshot and returns it
|
|
212
|
-
|
|
210
|
+
Captures a screenshot and returns it via ToolReturn with BinaryContent
|
|
211
|
+
so multimodal models can directly see and analyze the image.
|
|
213
212
|
|
|
214
213
|
Args:
|
|
215
214
|
full_page: Whether to capture the full page or just viewport.
|
|
@@ -218,18 +217,11 @@ async def terminal_screenshot(
|
|
|
218
217
|
Defaults to True.
|
|
219
218
|
|
|
220
219
|
Returns:
|
|
221
|
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
|
|
226
|
-
- error (str): Error message if unsuccessful.
|
|
227
|
-
|
|
228
|
-
Example:
|
|
229
|
-
>>> result = await terminal_screenshot()
|
|
230
|
-
>>> if result["success"]:
|
|
231
|
-
... # The base64_image can be shown to multimodal models
|
|
232
|
-
... print(f"Screenshot saved to: {result['screenshot_path']}")
|
|
220
|
+
ToolReturn containing:
|
|
221
|
+
- return_value: Success message with screenshot path
|
|
222
|
+
- content: List with description and BinaryContent image
|
|
223
|
+
- metadata: Screenshot details (path, target, timestamp)
|
|
224
|
+
Or Dict with error info if failed.
|
|
233
225
|
"""
|
|
234
226
|
target = "full_page" if full_page else "viewport"
|
|
235
227
|
group_id = generate_group_id("terminal_screenshot", target)
|
|
@@ -249,14 +241,27 @@ async def terminal_screenshot(
|
|
|
249
241
|
emit_error(result.get("error", "Screenshot failed"), message_group=group_id)
|
|
250
242
|
return result
|
|
251
243
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
244
|
+
screenshot_path = result.get("screenshot_path", "(not saved)")
|
|
245
|
+
|
|
246
|
+
# Return as ToolReturn with BinaryContent so the model can SEE the image!
|
|
247
|
+
return ToolReturn(
|
|
248
|
+
return_value=f"Terminal screenshot captured. Saved to: {screenshot_path}",
|
|
249
|
+
content=[
|
|
250
|
+
f"Here's the terminal screenshot ({target}):",
|
|
251
|
+
BinaryContent(
|
|
252
|
+
data=result["screenshot_bytes"],
|
|
253
|
+
media_type="image/png",
|
|
254
|
+
),
|
|
255
|
+
"Please analyze what you see in the terminal.",
|
|
256
|
+
],
|
|
257
|
+
metadata={
|
|
258
|
+
"success": True,
|
|
259
|
+
"screenshot_path": screenshot_path,
|
|
260
|
+
"target": target,
|
|
261
|
+
"full_page": full_page,
|
|
262
|
+
"timestamp": time.time(),
|
|
263
|
+
},
|
|
264
|
+
)
|
|
260
265
|
|
|
261
266
|
|
|
262
267
|
async def terminal_read_output(lines: int = 50) -> Dict[str, Any]:
|
|
@@ -328,23 +333,22 @@ async def terminal_read_output(lines: int = 50) -> Dict[str, Any]:
|
|
|
328
333
|
async def load_image(
|
|
329
334
|
image_path: str,
|
|
330
335
|
max_height: int = DEFAULT_MAX_HEIGHT,
|
|
331
|
-
) -> Dict[str, Any]:
|
|
332
|
-
"""Load an image from the filesystem
|
|
336
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
337
|
+
"""Load an image from the filesystem for visual analysis.
|
|
333
338
|
|
|
334
339
|
Loads any image file, resizes it to reduce token usage, and returns
|
|
335
|
-
it
|
|
340
|
+
it via ToolReturn with BinaryContent so multimodal models can see it.
|
|
336
341
|
|
|
337
342
|
Args:
|
|
338
343
|
image_path: Path to the image file.
|
|
339
344
|
max_height: Maximum height for resizing (default 768px).
|
|
340
345
|
|
|
341
346
|
Returns:
|
|
342
|
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
346
|
-
|
|
347
|
-
- error (str): Error message if unsuccessful.
|
|
347
|
+
ToolReturn containing:
|
|
348
|
+
- return_value: Success message with path info
|
|
349
|
+
- content: List with description and BinaryContent image
|
|
350
|
+
- metadata: Image details (path, resized height)
|
|
351
|
+
Or Dict with error info if failed.
|
|
348
352
|
"""
|
|
349
353
|
group_id = generate_group_id("load_image", image_path)
|
|
350
354
|
emit_info(f"LOAD IMAGE 🖼️ {image_path}", message_group=group_id)
|
|
@@ -368,18 +372,26 @@ async def load_image(
|
|
|
368
372
|
# Resize to reduce token usage
|
|
369
373
|
image_bytes = _resize_image(original_bytes, max_height=max_height)
|
|
370
374
|
|
|
371
|
-
# Always return as PNG after resizing (consistent format)
|
|
372
|
-
base64_data = base64.b64encode(image_bytes).decode("utf-8")
|
|
373
|
-
|
|
374
375
|
emit_success(f"Loaded image: {image_path}", message_group=group_id)
|
|
375
376
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
377
|
+
# Return as ToolReturn with BinaryContent so the model can SEE the image!
|
|
378
|
+
return ToolReturn(
|
|
379
|
+
return_value=f"Image loaded from: {image_path}",
|
|
380
|
+
content=[
|
|
381
|
+
f"Here's the image from {image_file.name}:",
|
|
382
|
+
BinaryContent(
|
|
383
|
+
data=image_bytes,
|
|
384
|
+
media_type="image/png", # Always PNG after resize
|
|
385
|
+
),
|
|
386
|
+
"Please analyze what you see in this image.",
|
|
387
|
+
],
|
|
388
|
+
metadata={
|
|
389
|
+
"success": True,
|
|
390
|
+
"image_path": image_path,
|
|
391
|
+
"max_height": max_height,
|
|
392
|
+
"timestamp": time.time(),
|
|
393
|
+
},
|
|
394
|
+
)
|
|
383
395
|
|
|
384
396
|
except Exception as e:
|
|
385
397
|
error_msg = f"Failed to load image: {str(e)}"
|
|
@@ -400,18 +412,18 @@ def register_terminal_screenshot(agent):
|
|
|
400
412
|
async def terminal_screenshot_analyze(
|
|
401
413
|
context: RunContext,
|
|
402
414
|
full_page: bool = False,
|
|
403
|
-
) -> Dict[str, Any]:
|
|
415
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
404
416
|
"""
|
|
405
417
|
Take a screenshot of the terminal browser.
|
|
406
418
|
|
|
407
|
-
Returns the screenshot
|
|
408
|
-
Use this to see what's displayed in the terminal.
|
|
419
|
+
Returns the screenshot via ToolReturn with BinaryContent that you can
|
|
420
|
+
see directly. Use this to see what's displayed in the terminal.
|
|
409
421
|
|
|
410
422
|
Args:
|
|
411
423
|
full_page: Capture full page (True) or just viewport (False).
|
|
412
424
|
|
|
413
425
|
Returns:
|
|
414
|
-
|
|
426
|
+
ToolReturn with the terminal screenshot you can analyze, or error dict.
|
|
415
427
|
"""
|
|
416
428
|
# Session is set by invoke_agent via contextvar
|
|
417
429
|
return await terminal_screenshot(full_page=full_page)
|
|
@@ -449,17 +461,18 @@ def register_load_image(agent):
|
|
|
449
461
|
async def load_image_for_analysis(
|
|
450
462
|
context: RunContext,
|
|
451
463
|
image_path: str,
|
|
452
|
-
) -> Dict[str, Any]:
|
|
464
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
453
465
|
"""
|
|
454
466
|
Load an image file so you can see and analyze it.
|
|
455
467
|
|
|
456
|
-
Returns the image
|
|
468
|
+
Returns the image via ToolReturn with BinaryContent that you can
|
|
469
|
+
see directly.
|
|
457
470
|
|
|
458
471
|
Args:
|
|
459
472
|
image_path: Path to the image file.
|
|
460
473
|
|
|
461
474
|
Returns:
|
|
462
|
-
|
|
475
|
+
ToolReturn with the image you can analyze, or error dict.
|
|
463
476
|
"""
|
|
464
477
|
# Session is set by invoke_agent via contextvar
|
|
465
478
|
return await load_image(image_path=image_path)
|
|
@@ -472,18 +485,18 @@ def register_terminal_compare_mockup(agent):
|
|
|
472
485
|
async def terminal_compare_mockup(
|
|
473
486
|
context: RunContext,
|
|
474
487
|
mockup_path: str,
|
|
475
|
-
) -> Dict[str, Any]:
|
|
488
|
+
) -> Union[ToolReturn, Dict[str, Any]]:
|
|
476
489
|
"""
|
|
477
490
|
Compare the terminal to a mockup image.
|
|
478
491
|
|
|
479
492
|
Takes a screenshot of the terminal and loads the mockup image.
|
|
480
|
-
Returns both
|
|
493
|
+
Returns both via ToolReturn with BinaryContent so you can compare them.
|
|
481
494
|
|
|
482
495
|
Args:
|
|
483
496
|
mockup_path: Path to the mockup/expected image.
|
|
484
497
|
|
|
485
498
|
Returns:
|
|
486
|
-
|
|
499
|
+
ToolReturn with both images (terminal and mockup) you can compare.
|
|
487
500
|
"""
|
|
488
501
|
# Session is set by invoke_agent via contextvar
|
|
489
502
|
group_id = generate_group_id("terminal_compare_mockup", mockup_path)
|
|
@@ -493,28 +506,51 @@ def register_terminal_compare_mockup(agent):
|
|
|
493
506
|
message_group=group_id,
|
|
494
507
|
)
|
|
495
508
|
|
|
496
|
-
#
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
509
|
+
# Capture terminal screenshot (get raw result for bytes)
|
|
510
|
+
terminal_capture = await _capture_terminal_screenshot(
|
|
511
|
+
full_page=False,
|
|
512
|
+
save_to_disk=True,
|
|
513
|
+
group_id=group_id,
|
|
514
|
+
)
|
|
515
|
+
if not terminal_capture["success"]:
|
|
516
|
+
return terminal_capture
|
|
500
517
|
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
if not
|
|
504
|
-
|
|
518
|
+
# Load the mockup image
|
|
519
|
+
mockup_file = Path(mockup_path)
|
|
520
|
+
if not mockup_file.exists():
|
|
521
|
+
error_msg = f"Mockup file not found: {mockup_path}"
|
|
522
|
+
emit_error(error_msg, message_group=group_id)
|
|
523
|
+
return {"success": False, "error": error_msg}
|
|
524
|
+
|
|
525
|
+
mockup_bytes = _resize_image(mockup_file.read_bytes())
|
|
505
526
|
|
|
506
527
|
emit_success(
|
|
507
528
|
"Both images loaded. Compare them visually.",
|
|
508
529
|
message_group=group_id,
|
|
509
530
|
)
|
|
510
531
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
"
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
532
|
+
terminal_path = terminal_capture.get("screenshot_path", "(not saved)")
|
|
533
|
+
|
|
534
|
+
# Return as ToolReturn with BOTH images as BinaryContent!
|
|
535
|
+
return ToolReturn(
|
|
536
|
+
return_value=f"Comparison ready: terminal vs mockup ({mockup_path})",
|
|
537
|
+
content=[
|
|
538
|
+
"Here's the CURRENT terminal screenshot:",
|
|
539
|
+
BinaryContent(
|
|
540
|
+
data=terminal_capture["screenshot_bytes"],
|
|
541
|
+
media_type="image/png",
|
|
542
|
+
),
|
|
543
|
+
f"And here's the EXPECTED mockup ({mockup_file.name}):",
|
|
544
|
+
BinaryContent(
|
|
545
|
+
data=mockup_bytes,
|
|
546
|
+
media_type="image/png",
|
|
547
|
+
),
|
|
548
|
+
"Please compare these images and describe any differences.",
|
|
549
|
+
],
|
|
550
|
+
metadata={
|
|
551
|
+
"success": True,
|
|
552
|
+
"terminal_path": terminal_path,
|
|
553
|
+
"mockup_path": mockup_path,
|
|
554
|
+
"timestamp": time.time(),
|
|
555
|
+
},
|
|
556
|
+
)
|
|
@@ -43,7 +43,7 @@ code_puppy/agents/agent_security_auditor.py,sha256=SpiYNA0XAsIwBj7S2_EQPRslRUmF_
|
|
|
43
43
|
code_puppy/agents/agent_terminal_qa.py,sha256=U-iyP7OBWdAmchW_oUU8k6asH2aignTMmgqqYDyf-ms,10343
|
|
44
44
|
code_puppy/agents/agent_typescript_reviewer.py,sha256=vsnpp98xg6cIoFAEJrRTUM_i4wLEWGm5nJxs6fhHobM,10275
|
|
45
45
|
code_puppy/agents/base_agent.py,sha256=oKlX9CEIWSvdXyQDVi9F1jauA6rjKleY_n6044Ux5DY,73840
|
|
46
|
-
code_puppy/agents/event_stream_handler.py,sha256=
|
|
46
|
+
code_puppy/agents/event_stream_handler.py,sha256=JttLZJpNADE5HXiXY-GZ6tpwaBeFRODcy34KiquPOvU,14952
|
|
47
47
|
code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
|
|
48
48
|
code_puppy/agents/prompt_reviewer.py,sha256=JJrJ0m5q0Puxl8vFsyhAbY9ftU9n6c6UxEVdNct1E-Q,5558
|
|
49
49
|
code_puppy/agents/subagent_stream_handler.py,sha256=5imUFYOJCv7blfv4fTHm6OQ7JpqlyFv_luBYGSj16MA,10329
|
|
@@ -198,20 +198,20 @@ code_puppy/tools/browser/browser_control.py,sha256=YntpjfWTIv0TDlAO5BqTV_hDbUBw-
|
|
|
198
198
|
code_puppy/tools/browser/browser_interactions.py,sha256=ZyJmA2-ZtIATF76uGMt08cfVaYiqg7W2-cHfAzNI0F8,16775
|
|
199
199
|
code_puppy/tools/browser/browser_locators.py,sha256=sxXNm-K087poeSp7Um5Gc1sZxb7HlSZOu0F0r2b0ty8,19177
|
|
200
200
|
code_puppy/tools/browser/browser_navigation.py,sha256=RJdG14UXtA6wz4PNLw2Tqeu4oUDQilOyNbyTjgIFCrY,7416
|
|
201
|
-
code_puppy/tools/browser/browser_screenshot.py,sha256=
|
|
201
|
+
code_puppy/tools/browser/browser_screenshot.py,sha256=AJe9JbZv8vC93AFWzsAUlrg1YNshv4SWNde-O-_mfQU,6282
|
|
202
202
|
code_puppy/tools/browser/browser_screenshot_vqa.py,sha256=DBdQuV7eIaJX2Qy_liwitfakIzrcVziB-zAGIngM5GE,7349
|
|
203
203
|
code_puppy/tools/browser/browser_scripts.py,sha256=CYWdQMtjKTNvJNSCkB2vGo-MOzmT_gw2oFMGtkfuzuA,14779
|
|
204
204
|
code_puppy/tools/browser/browser_workflows.py,sha256=nitW42vCf0ieTX1gLabozTugNQ8phtoFzZbiAhw1V90,6491
|
|
205
205
|
code_puppy/tools/browser/camoufox_manager.py,sha256=WIr98SrGeC5jd6jX5tjhFR6A3janqV4tq9Mbznnlh44,13920
|
|
206
206
|
code_puppy/tools/browser/chromium_terminal_manager.py,sha256=w1thQ_ACb6oV45L93TSqPQD0o0cTh3FqT5I9zcOOWlM,8226
|
|
207
207
|
code_puppy/tools/browser/terminal_command_tools.py,sha256=9byOZku-dwvTtCl532xt7Lumed_jTn0sLvUe_X75XCQ,19068
|
|
208
|
-
code_puppy/tools/browser/terminal_screenshot_tools.py,sha256=
|
|
208
|
+
code_puppy/tools/browser/terminal_screenshot_tools.py,sha256=J_21YO_495NvYgNFu9KQP6VYg2K_f8CtSdZuF94Yhnw,18448
|
|
209
209
|
code_puppy/tools/browser/terminal_tools.py,sha256=F5LjVH3udSCFHmqC3O1UJLoLozZFZsEdX42jOmkqkW0,17853
|
|
210
210
|
code_puppy/tools/browser/vqa_agent.py,sha256=0IbS1X3l8ADZI9pGcJbKFoN0-ZuTJa8QvHZ_hGKBKRM,6339
|
|
211
|
-
code_puppy-0.0.
|
|
212
|
-
code_puppy-0.0.
|
|
213
|
-
code_puppy-0.0.
|
|
214
|
-
code_puppy-0.0.
|
|
215
|
-
code_puppy-0.0.
|
|
216
|
-
code_puppy-0.0.
|
|
217
|
-
code_puppy-0.0.
|
|
211
|
+
code_puppy-0.0.359.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
|
|
212
|
+
code_puppy-0.0.359.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
213
|
+
code_puppy-0.0.359.dist-info/METADATA,sha256=sON8OiWf6tHAK4zV99qE3T1ouWDQVMOCm81jVNjcUCI,27614
|
|
214
|
+
code_puppy-0.0.359.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
215
|
+
code_puppy-0.0.359.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
|
216
|
+
code_puppy-0.0.359.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
217
|
+
code_puppy-0.0.359.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|