Image Uploads and imageIds (/converse)
This feature adds short-lived, R2-backed image uploads for the converse path.
Clients upload image bytes once, receive server-issued imageId values, then send those IDs in /agentic-search/converse.
Scope
Implemented:
POST /api/v1/indexes/:index/agentic-search/images/agentic-search/conversesupportsimageIdsandimageUrls- Max 5 images total across
imageIds + imageUrls
Not implemented:
/agentic-searchdoes not acceptimageIds
End-to-End Flow
- Client uploads 1-5 images as data URLs to
/agentic-search/images. - Server validates, normalizes, and stores each image in R2.
- Server returns
{ clientImageId, imageId, mimeType, expiresAt }[]. - Client calls
/agentic-search/conversewith base64 JSON payload containingimageIdsand/orimageUrls. - Worker resolves
imageIds, fetches URL images, and sends all valid images to Gemini asinlineData.
API Contract
Upload endpoint
POST /api/v1/indexes/:index/agentic-search/images
Request body:
{
"images": [
{
"clientImageId": "img-1",
"dataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
}
]
}
Response body:
{
"images": [
{
"clientImageId": "img-1",
"imageId": "img_abc123...",
"mimeType": "image/png",
"expiresAt": "2026-02-25T10:30:00.000Z"
}
]
}
Notes:
clientImageIdis caller-provided and echoed back.imageIdis server-generated and must be used in converse payloads.
Converse payload
GET /api/v1/indexes/:index/agentic-search/converse?payload=<base64-json>
Image fields:
imageUrls?: string[]imageIds?: string[]
Example payload (before base64 encoding):
{
"q": "compare these",
"imageUrls": ["https://example.com/a.png"],
"imageIds": ["img_abc123"]
}
Limits and Validation
- Upload accepts
1..5images. - Converse enforces
imageUrls.length + imageIds.length <= 5(400otherwise). - Upload data URLs must match
data:<mime>;base64,<payload>. - MIME type must be
image/*, then pass image processing validation.
Supported upload formats:
- Resizable: JPEG, PNG, WebP, BMP, TIFF, ICO (
image/x-icon) (max 5 MiB input) - Passthrough-only: HEIC, HEIF (max 1 MiB input)
Processing behavior:
- Oversized images are rejected (
400). - Large dimensions are resized to max 1024px on each side, preserving aspect ratio.
- Normalization uses the same image pipeline as URL-based image processing in converse.
Storage and Expiry (R2)
Bucket binding:
AGENTIC_IMAGES_BUCKET
Object key format:
v1/<systemAccountId>/<indexName>/<imageId>
Custom metadata:
systemAccountIdindexNamemimeTypeexpiresAt
Expiry model:
expiresAt = now + 24h(returned to client and checked during resolution)- R2 lifecycle should delete
v1/*objects after 1 day
Runtime Semantics in /converse
When imageIds are present, each image is resolved by key and validated for:
- object existence
- account/index binding match
- non-expired
expiresAt - non-empty payload
If both imageIds and imageUrls are present:
- ID resolution and URL fetch/processing start in parallel
- successful images are merged into one Gemini message
Failure handling is per image (soft-fail):
- invalid/missing/expired
imageIdemits SSEerror - failed URL fetch/process emits SSE
error - stream continues with remaining valid images
Upload Failure Semantics
Upload is all-or-nothing:
- Validate/process all input images.
- Write all objects to R2.
- If any write fails, delete previously written objects.
- Return
500(Failed to upload images).
Code References
components/agentic_search/src/images/upload-images.tscomponents/agentic_search/src/images/resolve-image-ids.tscomponents/agentic_search/src/converse/converse.tscomponents/agentic_search/src/image-utils.tscomponents/search_proxy/src/app.tscomponents/search_proxy/src/env.ts