Spaces:
Running
Running
sync: 181 file da Baida98/AI@a636f462 (2026-08-25 13:51 UTC) [deploy-all]
#47
by Baida07 - opened
- agents/unified_loop_tools.py +18 -1
- api/agent.py +12 -2
- tests/test_direct_file_conversion.py +13 -1
agents/unified_loop_tools.py
CHANGED
|
@@ -11,6 +11,7 @@ Python MRO garantisce che self.xxx funzioni per attr definite su UnifiedAgentLoo
|
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
| 13 |
import asyncio
|
|
|
|
| 14 |
import os
|
| 15 |
import re
|
| 16 |
from typing import Any
|
|
@@ -370,11 +371,27 @@ class DirectToolsMixin:
|
|
| 370 |
f"https://image.pollinations.ai/prompt/{quote(_img_prompt, safe='')}"
|
| 371 |
f"?width=512&height=512&seed={_img_seed}&nologo=true&enhance=true"
|
| 372 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
return (
|
| 374 |
"[DIRECT_TERMINAL]\n"
|
| 375 |
f"\n\n"
|
| 376 |
-
"E2E_IMAGE_OK: immagine generata e
|
| 377 |
f"[Apri o scarica l’immagine]({img_url}).\n\n"
|
|
|
|
| 378 |
f"Prompt usato: {_img_prompt[:200]}\n"
|
| 379 |
"Dimensioni: 512x512 px"
|
| 380 |
)
|
|
|
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
| 13 |
import asyncio
|
| 14 |
+
import hashlib
|
| 15 |
import os
|
| 16 |
import re
|
| 17 |
from typing import Any
|
|
|
|
| 371 |
f"https://image.pollinations.ai/prompt/{quote(_img_prompt, safe='')}"
|
| 372 |
f"?width=512&height=512&seed={_img_seed}&nologo=true&enhance=true"
|
| 373 |
)
|
| 374 |
+
# L'URL viene materializzato dal client nel VFS come data URI JPEG
|
| 375 |
+
# durante il commit atomico del task. Il nome è derivato dal prompt
|
| 376 |
+
# normalizzato: è sicuro, ripetibile e non contiene input utente.
|
| 377 |
+
_artifact_id = hashlib.sha256(_img_prompt.encode("utf-8")).hexdigest()[:12]
|
| 378 |
+
_artifact_path = f"generated-image-{_artifact_id}.jpg"
|
| 379 |
+
if on_step:
|
| 380 |
+
await _maybe_await(on_step({
|
| 381 |
+
"action": "file_written",
|
| 382 |
+
"status": "done",
|
| 383 |
+
"path": _artifact_path,
|
| 384 |
+
"source_url": img_url,
|
| 385 |
+
"mime_type": "image/jpeg",
|
| 386 |
+
"title": "Immagine salvata nel workspace",
|
| 387 |
+
"explanation": f"Salvo {_artifact_path} nel VFS…",
|
| 388 |
+
}))
|
| 389 |
return (
|
| 390 |
"[DIRECT_TERMINAL]\n"
|
| 391 |
f"\n\n"
|
| 392 |
+
"E2E_IMAGE_OK: immagine generata, visualizzata e salvata nel workspace. "
|
| 393 |
f"[Apri o scarica l’immagine]({img_url}).\n\n"
|
| 394 |
+
f"File VFS: `{_artifact_path}`\n"
|
| 395 |
f"Prompt usato: {_img_prompt[:200]}\n"
|
| 396 |
"Dimensioni: 512x512 px"
|
| 397 |
)
|
api/agent.py
CHANGED
|
@@ -1222,11 +1222,21 @@ async def stream_agent_task(task_id: str, request: Request, resume: int = 0, rol
|
|
| 1222 |
step_data.get('output', '')[:500])
|
| 1223 |
_vfs_op = 'delete' if 'delete' in _action else 'write'
|
| 1224 |
_vfs_evt: dict = {'taskId': task_id, 'file': str(_vfs_file)[:500], 'op': _vfs_op}
|
| 1225 |
-
# SYNC-1: includi content nel SSE event per file_written (≤60KB)
|
| 1226 |
-
#
|
|
|
|
|
|
|
| 1227 |
if _action == 'file_written' and step_data.get('content'):
|
| 1228 |
_vfs_evt['content'] = str(step_data['content'])[:60_000]
|
| 1229 |
_vfs_written_paths.add(str(_vfs_file)[:500])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1230 |
_sse('vfs_update', _vfs_evt)
|
| 1231 |
|
| 1232 |
# S363-UI: thought event — emitted when planner completes
|
|
|
|
| 1222 |
step_data.get('output', '')[:500])
|
| 1223 |
_vfs_op = 'delete' if 'delete' in _action else 'write'
|
| 1224 |
_vfs_evt: dict = {'taskId': task_id, 'file': str(_vfs_file)[:500], 'op': _vfs_op}
|
| 1225 |
+
# SYNC-1: includi content nel SSE event per file_written (≤60KB).
|
| 1226 |
+
# Per immagini dirette il backend non serializza byte nell'SSE:
|
| 1227 |
+
# inoltra soltanto l'URL HTTPS generato internamente, che il client
|
| 1228 |
+
# materializza come data URI nel proprio VFS prima del commit atomico.
|
| 1229 |
if _action == 'file_written' and step_data.get('content'):
|
| 1230 |
_vfs_evt['content'] = str(step_data['content'])[:60_000]
|
| 1231 |
_vfs_written_paths.add(str(_vfs_file)[:500])
|
| 1232 |
+
elif _action == 'file_written':
|
| 1233 |
+
_source_url = str(step_data.get('source_url') or '')
|
| 1234 |
+
_mime_type = str(step_data.get('mime_type') or '')
|
| 1235 |
+
if (_source_url.startswith('https://image.pollinations.ai/') and
|
| 1236 |
+
_mime_type in {'image/jpeg', 'image/png', 'image/webp'}):
|
| 1237 |
+
_vfs_evt['sourceUrl'] = _source_url[:2_000]
|
| 1238 |
+
_vfs_evt['mimeType'] = _mime_type
|
| 1239 |
+
_vfs_written_paths.add(str(_vfs_file)[:500])
|
| 1240 |
_sse('vfs_update', _vfs_evt)
|
| 1241 |
|
| 1242 |
# S363-UI: thought event — emitted when planner completes
|
tests/test_direct_file_conversion.py
CHANGED
|
@@ -76,15 +76,27 @@ def test_direct_image_returns_renderable_terminal_markdown_without_llm():
|
|
| 76 |
def _max_tokens_for_goal(self, _goal):
|
| 77 |
return 4096
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
output, called, succeeded, errors = asyncio.run(
|
| 80 |
Harness()._run_direct_tools(
|
| 81 |
-
"Genera un’immagine quadrata di un aeroplanino arancione su fondo blu notte."
|
|
|
|
| 82 |
)
|
| 83 |
)
|
| 84 |
|
| 85 |
assert output.startswith("[DIRECT_TERMINAL]\n
|
| 86 |
assert "E2E_IMAGE_OK" in output
|
| 87 |
assert "aeroplanino%20arancione" in output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
assert called == 1
|
| 89 |
assert succeeded == 1
|
| 90 |
assert errors == 0
|
|
|
|
| 76 |
def _max_tokens_for_goal(self, _goal):
|
| 77 |
return 4096
|
| 78 |
|
| 79 |
+
events = []
|
| 80 |
+
|
| 81 |
+
async def on_step(event):
|
| 82 |
+
events.append(event)
|
| 83 |
+
|
| 84 |
output, called, succeeded, errors = asyncio.run(
|
| 85 |
Harness()._run_direct_tools(
|
| 86 |
+
"Genera un’immagine quadrata di un aeroplanino arancione su fondo blu notte.",
|
| 87 |
+
on_step=on_step,
|
| 88 |
)
|
| 89 |
)
|
| 90 |
|
| 91 |
assert output.startswith("[DIRECT_TERMINAL]\n
|
| 92 |
assert "E2E_IMAGE_OK" in output
|
| 93 |
assert "aeroplanino%20arancione" in output
|
| 94 |
+
assert "File VFS: `generated-image-" in output
|
| 95 |
+
image_event = next(event for event in events if event.get("action") == "file_written")
|
| 96 |
+
assert image_event["path"].startswith("generated-image-")
|
| 97 |
+
assert image_event["path"].endswith(".jpg")
|
| 98 |
+
assert image_event["source_url"].startswith("https://image.pollinations.ai/prompt/")
|
| 99 |
+
assert image_event["mime_type"] == "image/jpeg"
|
| 100 |
assert called == 1
|
| 101 |
assert succeeded == 1
|
| 102 |
assert errors == 0
|