Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ Three capabilities have no captured payload, so they fail with
| Veo start+end-frame chaining (`/fk-gen-chain-videos`) | unported | Omni first+last (`model_family=omni_flash`), or `FLOW_ALLOW_DEGRADED=1` → i2v off the start frame |
| Omni Flash text-to-video | ported | `POST /api/flow/generate-video-omni-text` (4/6/8/10s) |
| Omni Flash frame / first+last / reference modes | ported | `eb1hJf`, `nprQif`, `MZZa6b` — `POST /api/flow/generate-video` with `model_family=omni_flash` |
| Omni 1.2 Flash (all four modes above) | ported | add `"omni_version": "1.2"` to the same endpoints — `macaron_cookie_{t2v,i2v,i2v_fl,r2v}_<N>s[_360p]`, any whole second 3–15 |

Restoring one starts with a capture, not a guess: [`docs/CAPTURE.md`](docs/CAPTURE.md).

Expand Down
8 changes: 7 additions & 1 deletion agent/api/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class GenerateVideoRequest(BaseModel):
model_family: Literal["veo", "omni_flash"] = "veo"
duration_s: int = 8
resolution: Literal["360p", "720p"] = "720p"
# Omni only: "1.2" = Omni 1.2 Flash (3-15s; first+last only for frames).
omni_version: Literal["1.1", "1.2"] = "1.1"


class GenerateVideoRefsRequest(BaseModel):
Expand All @@ -80,6 +82,7 @@ class GenerateOmniFlashVideoRequest(BaseModel):
resolution: Literal["360p", "720p"] = "720p"
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT"
user_paygate_tier: str = "PAYGATE_TIER_ONE"
omni_version: Literal["1.1", "1.2"] = "1.1"


class GenerateOmniFlashTextVideoRequest(BaseModel):
Expand All @@ -90,6 +93,7 @@ class GenerateOmniFlashTextVideoRequest(BaseModel):
resolution: Literal["360p", "720p"] = "720p"
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT"
user_paygate_tier: str = "PAYGATE_TIER_ONE"
omni_version: Literal["1.1", "1.2"] = "1.1"


class UpscaleVideoRequest(BaseModel):
Expand Down Expand Up @@ -271,6 +275,7 @@ async def generate_video(body: GenerateVideoRequest):
resolution=body.resolution,
aspect_ratio=body.aspect_ratio,
user_paygate_tier=body.user_paygate_tier,
omni_version=body.omni_version,
)
if body.end_image_media_id:
result = await generate_omni_flash_first_last_video(
Expand All @@ -283,7 +288,8 @@ async def generate_video(body: GenerateVideoRequest):
raise HTTPException(400, str(exc)) from exc
else:
payload = body.model_dump(
exclude={"model_family", "duration_s", "resolution"}, exclude_none=True
exclude={"model_family", "duration_s", "resolution", "omni_version"},
exclude_none=True,
)
payload["project_id"] = project_id
result = await client.generate_video(**payload)
Expand Down
34 changes: 22 additions & 12 deletions agent/services/flow_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,18 +443,21 @@ def video_request(prompt: str, project_id: str, source_media_id: str,
def omni_first_frame_request(prompt: str, project_id: str, source_media_id: str,
*, duration_s: int = 8, resolution: str = "720p",
aspect: Any = VIDEO_ASPECT_LANDSCAPE,
crop: Optional[list] = None) -> str:
crop: Optional[list] = None,
model: Optional[str] = None) -> str:
"""Build current Omni first-frame I2V (RPC ``eb1hJf``).

Live-captured from Flow on 2026-09-14. 720p uses ``abra_i2v_<N>s``;
360p appends ``_360p`` and carries the UI's low-resolution option slot.
``model`` overrides the key (Omni 1.2).
"""
if duration_s not in (4, 6, 8, 10):
raise ValueError("Omni duration must be 4, 6, 8 or 10 seconds")
res = str(resolution).strip().lower()
if res not in {"360p", "720p"}:
raise ValueError("Omni resolution must be 360p or 720p")
model = f"abra_i2v_{duration_s}s" + ("_360p" if res == "360p" else "")
if model is None:
if duration_s not in (4, 6, 8, 10):
raise ValueError("Omni duration must be 4, 6, 8 or 10 seconds")
model = f"abra_i2v_{duration_s}s" + ("_360p" if res == "360p" else "")
request = [
[None, None, [[[prompt]]]],
model,
Expand All @@ -478,14 +481,20 @@ def omni_first_last_request(prompt: str, project_id: str,
*, duration_s: int = 8, resolution: str = "720p",
aspect: Any = VIDEO_ASPECT_LANDSCAPE,
start_crop: Optional[list] = None,
end_crop: Optional[list] = None) -> str:
"""Build Omni First+Last frames submit (RPC ``nprQif``)."""
if duration_s not in (4, 6, 8, 10):
raise ValueError("Omni duration must be 4, 6, 8 or 10 seconds")
end_crop: Optional[list] = None,
model: Optional[str] = None) -> str:
"""Build Omni First+Last frames submit (RPC ``nprQif``).

``model`` overrides the 1.1 key; Omni 1.2 uses the same payload with
``macaron_cookie_i2v_fl_<N>s[_360p]`` (live-captured 2026-11-06).
"""
res = str(resolution).strip().lower()
if res not in {"360p", "720p"}:
raise ValueError("Omni resolution must be 360p or 720p")
model = f"omni_flash_i2v_{duration_s}s_first_last" + ("_360p" if res == "360p" else "")
if model is None:
if duration_s not in (4, 6, 8, 10):
raise ValueError("Omni duration must be 4, 6, 8 or 10 seconds")
model = f"omni_flash_i2v_{duration_s}s_first_last" + ("_360p" if res == "360p" else "")
request = [
[None, None, [[[prompt]]]],
model,
Expand All @@ -507,17 +516,18 @@ def omni_first_last_request(prompt: str, project_id: str,
def omni_reference_video_request(prompt: str, project_id: str,
reference_media_ids: list[str],
*, duration_s: int = 8, resolution: str = "720p",
aspect: Any = VIDEO_ASPECT_LANDSCAPE) -> str:
aspect: Any = VIDEO_ASPECT_LANDSCAPE,
model: Optional[str] = None) -> str:
"""Build Omni Ingredients/reference-to-video submit (RPC ``MZZa6b``)."""
refs = [str(mid) for mid in reference_media_ids if str(mid)]
if not refs:
raise ValueError("Omni reference-to-video requires at least one reference image")
if duration_s not in (4, 6, 8, 10):
if model is None and duration_s not in (4, 6, 8, 10):
raise ValueError("Omni duration must be 4, 6, 8 or 10 seconds")
res = str(resolution).strip().lower()
if res not in {"360p", "720p"}:
raise ValueError("Omni resolution must be 360p or 720p")
model = f"abra_r2v_{duration_s}s" + ("_360p" if res == "360p" else "")
model = model or f"abra_r2v_{duration_s}s" + ("_360p" if res == "360p" else "")
request = [
[None, None, [[[prompt]]]],
[[None, mid] for mid in refs],
Expand Down
60 changes: 50 additions & 10 deletions agent/services/omni_flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@
_MODELS_FILE = Path(__file__).parent.parent / "models.json"

OMNI_FLASH_VALID_DURATIONS = (4, 6, 8, 10)
# Omni 1.2 Flash (Flow codename ``macaron_cookie``): any whole second 3-15.
# t2v / first+last keys live-captured from flow.google.com on 2026-11-06;
# first-frame (i2v) and Ingredients (r2v) follow the 1.1 naming by analogy.
OMNI_VERSIONS = ("1.1", "1.2")
OMNI_12_DURATIONS = tuple(range(3, 16))


def omni12_model_key(mode: str, duration_s: int, resolution: str) -> str:
if duration_s not in OMNI_12_DURATIONS:
raise ValueError(f"Omni 1.2 duration must be 3-15 seconds, got {duration_s}")
stem = {"t2v": "t2v", "first_last": "i2v_fl", "first_frame": "i2v", "r2v": "r2v"}.get(mode)
if stem is None:
raise ValueError(
f"Omni 1.2 mode {mode!r} is not captured yet; use omni_version='1.1'"
)
return f"macaron_cookie_{stem}_{duration_s}s" + ("_360p" if resolution == "360p" else "")
OMNI_FLASH_VALID_ASPECTS = {
"VIDEO_ASPECT_RATIO_PORTRAIT",
"VIDEO_ASPECT_RATIO_LANDSCAPE",
Expand Down Expand Up @@ -178,15 +194,19 @@ async def generate_omni_flash_text_video(
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT",
user_paygate_tier: str = "PAYGATE_TIER_ONE",
seed: int | None = None,
omni_version: str = "1.1",
) -> dict:
"""Submit Omni 1.1 Flash text-to-video on the migrated Flow batch API."""
_validate_duration(duration_s)
"""Submit Omni Flash text-to-video (1.1 ``abra`` or 1.2 ``macaron_cookie``)."""
resolution = _validate_resolution(resolution)
if omni_version == "1.2":
model_key = omni12_model_key("t2v", duration_s, resolution)
else:
_validate_duration(duration_s)
model_key = f"abra_t2v_{duration_s}s" + ("_360p" if resolution == "360p" else "")
_validate_aspect(aspect_ratio)
client = get_flow_client()
try:
pid = client._batch_project_id(project_id)
model_key = f"abra_t2v_{duration_s}s" + ("_360p" if resolution == "360p" else "")
freq = fb.text_video_request(
prompt,
pid,
Expand Down Expand Up @@ -235,15 +255,24 @@ async def _submit_omni_frame_video(
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT",
user_paygate_tier: str = "PAYGATE_TIER_ONE",
seed: int | None = None,
omni_version: str = "1.1",
) -> dict:
"""Submit Omni first-frame or First+Last generation.

Batch payloads were live-captured from ``flow.google.com`` on 2026-09-14:
first-frame uses ``eb1hJf``; First+Last uses ``nprQif``. Both return normal
batch operations and are polled through ``/api/flow/check-status``.
"""
_validate_frame_inputs(start_image_media_id, end_image_media_id, duration_s, aspect_ratio)
resolution = _validate_resolution(resolution)
model_override = None
if omni_version == "1.2":
model_override = omni12_model_key(
"first_last" if end_image_media_id is not None else "first_frame",
duration_s, resolution,
)
_validate_frame_inputs(start_image_media_id, end_image_media_id, 4, aspect_ratio)
else:
_validate_frame_inputs(start_image_media_id, end_image_media_id, duration_s, aspect_ratio)
mode = "start_end_frame_to_video" if end_image_media_id is not None else "frame_to_video"
client = get_flow_client()

Expand All @@ -252,17 +281,18 @@ async def _submit_omni_frame_video(
if end_image_media_id is None:
freq = fb.omni_first_frame_request(
prompt, pid, start_image_media_id, duration_s=duration_s,
resolution=resolution, aspect=aspect_ratio,
resolution=resolution, aspect=aspect_ratio, model=model_override,
)
rpcid = fb.RPC_GEN_VIDEO
batch_model = f"abra_i2v_{duration_s}s" + ("_360p" if resolution == "360p" else "")
batch_model = model_override or f"abra_i2v_{duration_s}s" + ("_360p" if resolution == "360p" else "")
else:
freq = fb.omni_first_last_request(
prompt, pid, start_image_media_id, end_image_media_id,
duration_s=duration_s, resolution=resolution, aspect=aspect_ratio,
model=model_override,
)
rpcid = fb.RPC_GEN_VIDEO_FIRST_LAST
batch_model = f"omni_flash_i2v_{duration_s}s_first_last" + (
batch_model = model_override or f"omni_flash_i2v_{duration_s}s_first_last" + (
"_360p" if resolution == "360p" else ""
)
payload = await client._batch_payload(
Expand All @@ -284,6 +314,7 @@ async def generate_omni_flash_first_frame_video(
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT",
user_paygate_tier: str = "PAYGATE_TIER_ONE",
seed: int | None = None,
omni_version: str = "1.1",
) -> dict:
"""Submit Omni Flash First frame -> video."""
return await _submit_omni_frame_video(
Expand All @@ -297,6 +328,7 @@ async def generate_omni_flash_first_frame_video(
aspect_ratio=aspect_ratio,
user_paygate_tier=user_paygate_tier,
seed=seed,
omni_version=omni_version,
)


Expand All @@ -311,6 +343,7 @@ async def generate_omni_flash_first_last_video(
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT",
user_paygate_tier: str = "PAYGATE_TIER_ONE",
seed: int | None = None,
omni_version: str = "1.1",
) -> dict:
"""Submit Omni Flash First + Last frame -> video."""
return await _submit_omni_frame_video(
Expand All @@ -324,6 +357,7 @@ async def generate_omni_flash_first_last_video(
aspect_ratio=aspect_ratio,
user_paygate_tier=user_paygate_tier,
seed=seed,
omni_version=omni_version,
)


Expand All @@ -337,21 +371,27 @@ async def generate_omni_flash_video(
aspect_ratio: str = "VIDEO_ASPECT_RATIO_PORTRAIT",
user_paygate_tier: str = "PAYGATE_TIER_ONE",
seed: int | None = None,
omni_version: str = "1.1",
) -> dict:
"""Submit Omni Flash Ingredients/reference-to-video generation.

The migrated UI uses RPC ``MZZa6b`` with ``abra_r2v_<duration>s`` (720p)
or ``abra_r2v_<duration>s_360p``. Batch jobs use normal operation polling.
"""
refs = _validate_reference_inputs(reference_media_ids, duration_s, aspect_ratio)
resolution = _validate_resolution(resolution)
model_override = None
if omni_version == "1.2":
model_override = omni12_model_key("r2v", duration_s, resolution)
refs = _validate_reference_inputs(reference_media_ids, 4, aspect_ratio)
else:
refs = _validate_reference_inputs(reference_media_ids, duration_s, aspect_ratio)
client = get_flow_client()

try:
pid = client._batch_project_id(project_id)
freq = fb.omni_reference_video_request(
prompt, pid, refs, duration_s=duration_s,
resolution=resolution, aspect=aspect_ratio,
resolution=resolution, aspect=aspect_ratio, model=model_override,
)
payload = await client._batch_payload(
fb.RPC_GEN_VIDEO_REFERENCES, freq, fb.CAPTCHA_VIDEO, timeout=120,
Expand All @@ -360,7 +400,7 @@ async def generate_omni_flash_video(
client._remember_operation(operation.operation_id, pid)
except Exception as exc:
return {"status": 502, "error": f"{type(exc).__name__}: {exc}"}
batch_model = f"abra_r2v_{duration_s}s" + ("_360p" if resolution == "360p" else "")
batch_model = model_override or f"abra_r2v_{duration_s}s" + ("_360p" if resolution == "360p" else "")
return _batch_operation_result(operation, pid, batch_model, duration_s, resolution)

async def _check_omni_batch_media(
Expand Down
15 changes: 13 additions & 2 deletions extension/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,15 @@ const MAX_RPC_TEXT = 32000000; // the project listing alone is past 17 MB

async function runBatchRpc(cmd) {
const tabs = await chrome.tabs.query({ url: flowUrls });
// With several Flow tabs open (possibly on different Google accounts), use
// the one whose project id appears in this request.
const freqText = String(cmd.freq || '');
const projectTab = tabs.find((t) => {
const m = t.url && t.url.match(/\/project\/([0-9a-f-]{36})/i);
return m && freqText.includes(m[1]) && !t.discarded;
});
let candidate =
projectTab ||
tabs.find((t) => t.url && t.url.includes('/project/') && !t.discarded) ||
tabs.find((t) => t.active && !t.discarded) ||
tabs.find((t) => !t.discarded) ||
Expand Down Expand Up @@ -710,9 +718,12 @@ async function runBatchRpc(cmd) {
// image generation when source-path is missing even though Lite may not.
const sourcePath = location.pathname || '/';
const hl = (document.documentElement.lang || navigator.language || 'en').split('-')[0];
// Multi-account: a tab on /u/<n>/ must hit /u/<n>/_/..., otherwise the
// cookie jar resolves to the default account and its projects 404.
const acct = (location.pathname.match(/^\/u\/\d+(?=\/)/) || [''])[0];
const url = customPath
? `${customPath}?bl=${encodeURIComponent(bl || '')}&f.sid=${encodeURIComponent(sid || '')}&hl=${encodeURIComponent(hl)}&_reqid=${reqid}&rt=c`
: `/_/AiSandboxAngularFrontend/data/batchexecute?rpcids=${encodeURIComponent(rpcid)}` +
? `${customPath.startsWith('/_/') ? acct : ''}${customPath}?bl=${encodeURIComponent(bl || '')}&f.sid=${encodeURIComponent(sid || '')}&hl=${encodeURIComponent(hl)}&_reqid=${reqid}&rt=c`
: `${acct}/_/AiSandboxAngularFrontend/data/batchexecute?rpcids=${encodeURIComponent(rpcid)}` +
`&source-path=${encodeURIComponent(sourcePath)}` +
`&bl=${encodeURIComponent(bl || '')}&f.sid=${encodeURIComponent(sid || '')}` +
`&hl=${encodeURIComponent(hl)}&_reqid=${reqid}&rt=c`;
Expand Down
Loading