ios: support safe assistant inline images

This commit is contained in:
2026-10-20 14:31:15 -07:00
parent 9098fee32a
commit ee845c9fa3
13 changed files with 369 additions and 15 deletions
+20
View File
@@ -3,6 +3,7 @@
Run with Python + aiohttp, then xcodebuild test -scheme SybilAgentUI.
"""
import asyncio
import base64
import datetime
import json
import hashlib
@@ -23,6 +24,8 @@ reply_tasks = set()
attachments = {}
uploads = {}
attachment_capability = True
assistant_image_capability = False
fail_image_download = False
next_upload_error = None
blocked_proxy = False
transcript_comparison = False
@@ -95,6 +98,7 @@ async def reply(content):
async def route(request):
global read_cursor, cursor, busy, attachment_capability, next_upload_error, blocked_proxy, transcript_comparison, push_available, auth_required
global assistant_image_capability, fail_image_download
path = request.path
log.append(dict(method=request.method, path=path, query=dict(request.query)))
if path == '/control':
@@ -106,6 +110,7 @@ async def route(request):
reply_tasks.clear()
messages.clear(); requests.clear(); attempts.clear(); cursor = 0; read_cursor = 0; busy = False
attachment_capability = True; next_upload_error = None; blocked_proxy = False; transcript_comparison = False
assistant_image_capability = False; fail_image_download = False
push_installations.clear(); push_retired.clear(); push_requests.clear(); log.clear()
push_available = True; auth_required = False
for i in range(body.get('count', 3)):
@@ -120,6 +125,16 @@ async def route(request):
transcript_comparison = True
elif action == 'capability':
attachment_capability = body.get('enabled', True)
assistant_image_capability = body.get('assistantImages', False)
elif action == 'assistant-image':
# Synthetic output supplied by XCTest, never a workspace file or provider.
data = base64.b64decode(body['png'], validate=True)
descriptor = dict(id='fixture-output-image', kind='image', filename='fixture-landscape.png',
mimeType='image/png', sizeBytes=len(data), sha256=hashlib.sha256(data).hexdigest())
attachments[descriptor['id']] = dict(descriptor=descriptor, data=data)
fail_image_download = body.get('failFirstDownload', False)
row = message(body.get('content', ''), info={'agentAttachments': [descriptor]})
await broadcast(dict(type='messages', **page([row])))
elif action == 'upload-error':
next_upload_error = body
elif action == 'blocked-proxy':
@@ -190,6 +205,8 @@ async def route(request):
descriptor = dict(id='fixture-root', title='Sybil Agent', type='persistent-agent', model='fixture', protocol='pi-room.v1', websocket='/v1/agent/ws', activity=activity())
if attachment_capability:
descriptor['capabilities'] = {'attachments': CAPABILITY}
if assistant_image_capability:
descriptor.setdefault('capabilities', {})['assistantImages'] = dict(version=1)
return web.json_response(descriptor)
if path == '/api/v1/agent/push/config':
if not push_available:
@@ -263,6 +280,9 @@ async def route(request):
uploads[upload_id] = (identity, attachment_id)
return web.json_response({'attachment': descriptor}, status=201)
if path.startswith('/api/v1/agent/attachments/') and path.endswith('/content'):
if fail_image_download:
fail_image_download = False
return attachment_error(503, 'fixture_retry', 'Synthetic image unavailable. Retry the download.')
attachment_id = path.removeprefix('/api/v1/agent/attachments/').removesuffix('/content')
stored = attachments.get(attachment_id)
if stored is None: