Files
hao-ai-lab-FastVideo/apps/dreamverse/server/session/controller.py
T
SolitaryThinker aef113a861 [fix] dreamverse-monorepo: carry generic-merged files product-local
Source: Dreamverse repo at will/integrate-public-fastvideo @ ec8ef92.\n\nCarry the generic-merged backend modules as Dreamverse product-local code because the public streaming APIs are not drop-in compatible with the product runtime yet. Revert the Phase 2 public compatibility shims so FastVideo's public streaming surface returns to the pre-migration state.\n\nCo-authored-by: Junda (David) Su <90978028+Davids048@users.noreply.github.com>\nCo-authored-by: Matthew Noto <99706358+RandNMR73@users.noreply.github.com>\nCo-authored-by: XOR-op <17672363+XOR-op@users.noreply.github.com>\nCo-authored-by: Zhang Peiyuan <42993249+jzhang38@users.noreply.github.com>
2026-05-07 13:37:25 -07:00

1811 lines
84 KiB
Python

"""Per-WebSocket-connection session controller.
Lift-and-wrap extraction of the original ``main.websocket_endpoint``. The
function body is preserved verbatim inside ``SessionController.run()``;
the only substitutions are references to ``runtime.<service>`` rewritten
to ``self.<service>``, and a local alias ``websocket = self.ws`` at the
top of ``run()`` so the body can keep using the ``websocket`` name it
originally bound as a function parameter.
Captured locals, nested closures, and ``nonlocal`` declarations are
unchanged — this is a module-boundary move, not a state-semantics
rewrite. Decomposing the 1880-line ``run`` body into smaller methods
and extracting a ``SessionState`` dataclass is explicitly deferred until
a real trigger appears (new WS action, reproducible concurrency bug,
etc.).
"""
from __future__ import annotations
# pyright: reportArgumentType=false, reportMissingImports=false, reportMissingTypeArgument=false, reportOptionalMemberAccess=false
# ruff: noqa: SIM105
# mypy: ignore-errors
import asyncio
import time
import uuid
from typing import TYPE_CHECKING
from fastapi import WebSocket, WebSocketDisconnect
from gpu_pool import GPUSlot
from session_init_image import cleanup_session_init_image, persist_session_init_image
from worker_ipc import MediaChunk, MediaComplete, MediaInit
from config import (
DEFAULT_MODEL_ID,
GENERATION_SEGMENT_CAP,
PROMPT_AUTO_SLEEP_MS,
PROMPT_AUTO_TIMEOUT_MS,
PROMPT_TIMEOUT_MS,
SESSION_TIMEOUT_SECONDS,
)
from rewrite_prompt_payload import normalize_prompt_window_prompts
from session.messages import PromptSubmission, ReadyPrompt
from utils import (
_main_print,
_resolve_generation_segment_cap,
PROMPT_EXTENSION_FAILURE_USER_MESSAGE,
)
if TYPE_CHECKING:
from gpu_pool import GPUPool
from session_logger import SessionEventLogger
from prompt_enhancer import PromptEnhancer
from prompt_safety import PromptSafetyFilter
class SessionController:
"""Runs one WebSocket session from accept() through disconnect."""
def __init__(
self,
ws: WebSocket,
gpu_pool: GPUPool,
prompt_enhancer: PromptEnhancer,
prompt_safety_filter: PromptSafetyFilter | None,
session_event_logger: SessionEventLogger | None,
) -> None:
self.ws = ws
self.gpu_pool = gpu_pool
self.prompt_enhancer = prompt_enhancer
self.prompt_safety_filter = prompt_safety_filter
self.session_event_logger = session_event_logger
async def run(self) -> None:
websocket = self.ws
# --- lift-and-wrap body begins ---
await websocket.accept()
if self.gpu_pool is None:
await websocket.send_json({
"type": "error",
"message": "GPU pool not initialized",
})
await websocket.close(code=1011)
return
if self.prompt_enhancer is None:
await websocket.send_json({
"type": "error",
"message": "Prompt enhancer not initialized",
})
await websocket.close(code=1011)
return
client_id = str(uuid.uuid4())
print(f"Client {client_id[:8]} connected")
async def log_event(
event: str,
payload: dict[str, object] | None = None,
) -> None:
if self.session_event_logger is None:
return
try:
await self.session_event_logger.write_event(
event=event,
client_id=client_id,
payload=payload,
)
except Exception as exc:
_main_print(
"WARN",
f"Failed to write session log ({event}): {exc}",
)
await log_event("ws_session_start")
send_lock = asyncio.Lock()
stop_event = asyncio.Event()
async def ws_send_json(payload: dict):
async with send_lock:
await websocket.send_json(payload)
async def ws_send_bytes(payload: bytes):
async with send_lock:
await websocket.send_bytes(payload)
def preview_text(text: str, limit: int = 180) -> str:
normalized = (text or "").replace("\n", "\\n")
if len(normalized) <= limit:
return normalized
return normalized[:limit] + "..."
def get_prompt_safety_error(prompt: str) -> str | None:
if self.prompt_safety_filter is None:
return None
return self.prompt_safety_filter.get_prompt_safety_error(prompt)
def get_first_blocked_prompt(prompts: list[str]):
if self.prompt_safety_filter is None:
return None
return self.prompt_safety_filter.get_first_blocked_prompt(prompts)
# Send initial queue status
status = self.gpu_pool.get_status()
await ws_send_json({
"type": "queue_status",
"position": status["queue_size"] + 1 if status["available_gpus"] == 0 else 0,
"total_gpus": status["total_gpus"],
"available_gpus": status["available_gpus"],
})
gpu_id: int | None = None
slot: GPUSlot | None = None
timeout_task: asyncio.Task | None = None
websocket_reader_task: asyncio.Task | None = None
prompt_worker_task: asyncio.Task | None = None
rewrite_seed_prompts_task: asyncio.Task | None = None
session_init_image = None
async def session_timeout():
"""Close the session after timeout."""
await asyncio.sleep(SESSION_TIMEOUT_SECONDS)
if stop_event.is_set():
return
stop_event.set()
print(f"[GPU {gpu_id}] Session timeout for client {client_id[:8]}")
try:
await ws_send_json({
"type": "session_timeout",
"message": f"Session expired after {SESSION_TIMEOUT_SECONDS} seconds",
})
await websocket.close(code=1000, reason="Session timeout")
except Exception:
pass
async def cancel_task(task: asyncio.Task | None):
if task is None or task.done():
return
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
try:
# Wait for client init message.
init_data = {}
try:
init_data = await asyncio.wait_for(websocket.receive_json(), timeout=10.0)
except asyncio.TimeoutError:
init_data = {}
init_type = init_data.get("type")
preset_id = init_data.get("preset_id")
preset_label = str(init_data.get("preset_label") or "").strip()
initial_rollout_prompt = str(init_data.get("initial_rollout_prompt") or "").strip()
enhancement_enabled = bool(init_data.get("enhancement_enabled", True))
auto_extension_enabled = bool(init_data.get("auto_extension_enabled", False))
loop_generation_enabled = bool(init_data.get("loop_generation_enabled", False))
single_clip_mode = bool(init_data.get("single_clip_mode", False))
rewrite_model = self.prompt_enhancer.resolve_rewrite_model(init_data.get("rewrite_model"))
rewrite_system_prompt_override = str(init_data.get("rewrite_window_system_prompt") or "").strip()
rewrite_user_system_prompt_override = str(init_data.get("rewrite_user_system_prompt") or "").strip()
rewrite_system_prompt = self.prompt_enhancer.resolve_rewrite_system_prompt(rewrite_system_prompt_override)
rewrite_timeout_ms = PROMPT_TIMEOUT_MS
rewrite_temperature = self.prompt_enhancer.resolve_rewrite_temperature(init_data.get("rewrite_temperature"))
if init_type == "session_init_v2":
incoming_prompts = init_data.get("curated_prompts", [])
elif init_type == "select_model":
incoming_prompts = init_data.get("segment_prompts", [])
else:
incoming_prompts = init_data.get("segment_prompts", [])
curated_prompts = []
if isinstance(incoming_prompts, list):
for prompt in incoming_prompts:
if isinstance(prompt, str) and prompt.strip():
curated_prompts.append(prompt.strip())
blocked_init_prompt = get_first_blocked_prompt(curated_prompts)
if blocked_init_prompt is not None:
message = ("Prompt safety filter blocked initial prompt "
f"{blocked_init_prompt.index + 1}. {blocked_init_prompt.error}")
_main_print(
"WARN",
f"Initial prompt blocked for client {client_id[:8]} "
f"index={blocked_init_prompt.index + 1} "
f"preview={preview_text(blocked_init_prompt.prompt)}",
)
await ws_send_json({
"type": "error",
"message": message,
})
await websocket.close(code=1008, reason="Prompt blocked")
return
try:
session_init_image = persist_session_init_image(init_data.get("initial_image"))
except ValueError as exc:
await ws_send_json({
"type": "error",
"message": str(exc),
})
await websocket.close(code=1003, reason="Invalid initial image")
return
if preset_id:
print(f"Client {client_id[:8]} selected preset: {preset_id} "
f"label={preset_label or '(unset)'} "
f"({len(curated_prompts)} curated prompts, "
f"enhancement_enabled={enhancement_enabled}, "
f"auto_extension_enabled={auto_extension_enabled}, "
f"loop_generation_enabled={loop_generation_enabled}, "
f"single_clip_mode={single_clip_mode})")
if session_init_image is not None:
print(f"Client {client_id[:8]} uploaded initial image: "
f"{session_init_image.display_name}")
# Acquire a GPU slot.
gpu_id, slot = await self.gpu_pool.acquire(client_id, websocket)
# Start session timeout.
timeout_task = asyncio.create_task(session_timeout())
# Join the engine on this GPU.
await slot.join_user(client_id, model_id=DEFAULT_MODEL_ID)
# Notify client they're connected to a GPU.
await ws_send_json({
"type": "gpu_assigned",
"gpu_id": gpu_id,
"session_timeout": SESSION_TIMEOUT_SECONDS,
})
await log_event(
"gpu_assigned",
{
"gpu_id": gpu_id,
},
)
# Session queues and mutable state.
raw_prompt_queue: asyncio.Queue[PromptSubmission] = asyncio.Queue()
ready_prompt_queue: asyncio.Queue[ReadyPrompt] = asyncio.Queue()
curated_idx = 0
segment_idx = 0
locked_segment_prompts: list[str] = []
seed_prompt_memory: list[str] = list(curated_prompts)
generated_segment_count = 0
generation_cap_blocked = False
auto_extension_blocked_segment_idx: int | None = None
prompt_sources_drained_logged = False
generation_paused = bool(initial_rollout_prompt and not single_clip_mode and len(curated_prompts) == 0)
pending_seed_reset = False
pending_seed_reset_reason = ""
pending_reset_conditioning = False
loop_iteration = 0 if generation_paused else 1
force_curated_restart_segment = False
pending_simple_prompt_submission: PromptSubmission | None = None
single_clip_waiting_for_request = False
rollout_waiting_for_rewrite = False
initial_rollout_waiting_for_rewrite = generation_paused
segment_generation_active = False
rewrite_restart_pending = False
project_active = True
project_stream_started = False
pending_project_end = False
def replace_session_init_image(initial_image_payload: object) -> None:
nonlocal session_init_image
next_session_init_image = persist_session_init_image(initial_image_payload)
previous_session_init_image = session_init_image
session_init_image = next_session_init_image
if previous_session_init_image is not None:
cleanup_session_init_image(previous_session_init_image)
async def schedule_simple_generate_request(payload: dict[str, object]) -> None:
nonlocal preset_id
nonlocal preset_label
nonlocal enhancement_enabled
nonlocal auto_extension_enabled
nonlocal loop_generation_enabled
nonlocal single_clip_mode
nonlocal generation_paused
nonlocal pending_seed_reset
nonlocal pending_seed_reset_reason
nonlocal seed_prompt_memory
nonlocal curated_prompts
nonlocal pending_simple_prompt_submission
raw_prompt = str(payload.get("prompt", "")).strip()
prompt_id = str(payload.get("prompt_id") or uuid.uuid4())
if not raw_prompt:
await ws_send_json({
"type": "error",
"message": "simple_generate requires a non-empty prompt",
})
return
blocked_error = get_prompt_safety_error(raw_prompt)
if blocked_error is not None:
_main_print(
"WARN",
f"Simple prompt blocked for client {client_id[:8]} "
f"preview={preview_text(raw_prompt)}",
)
await log_event(
"prompt_blocked",
{
"kind": "user_raw",
"prompt_id": prompt_id,
"raw_prompt": raw_prompt,
"error": blocked_error,
},
)
await ws_send_json({
"type": "error",
"message": blocked_error,
})
return
try:
replace_session_init_image(payload.get("initial_image"))
except ValueError as exc:
await ws_send_json({
"type": "error",
"message": str(exc),
})
return
next_preset_id = str(payload.get("preset_id") or "").strip()
if next_preset_id:
preset_id = next_preset_id
next_preset_label = str(payload.get("preset_label") or "").strip()
if next_preset_label:
preset_label = next_preset_label
next_enhancement_enabled = bool(payload.get("enhancement_enabled", True))
enhancement_enabled = next_enhancement_enabled
auto_extension_enabled = False
loop_generation_enabled = False
single_clip_mode = True
generation_paused = False
pending_seed_reset = True
pending_seed_reset_reason = "simple_generate"
seed_prompt_memory = ([] if next_enhancement_enabled else [raw_prompt])
curated_prompts = list(seed_prompt_memory)
pending_simple_prompt_submission = (PromptSubmission(
prompt_id=prompt_id,
raw_prompt=raw_prompt,
created_at_s=time.time(),
) if next_enhancement_enabled else None)
await log_event(
"simple_generate",
{
"prompt_id": prompt_id,
"prompt": raw_prompt,
"preset_id": preset_id,
"enhancement_enabled": next_enhancement_enabled,
"has_initial_image": session_init_image is not None,
},
)
if session_init_image is not None:
_main_print(
"INFO", f"Client {client_id[:8]} set simple initial image: "
f"{session_init_image.display_name}")
if next_enhancement_enabled:
await ws_send_json({
"type": "prompt_received",
"prompt_id": prompt_id,
"queue_depth": 1,
})
async def apply_project_init_payload(payload: dict[str, object]) -> bool:
nonlocal preset_id
nonlocal preset_label
nonlocal initial_rollout_prompt
nonlocal enhancement_enabled
nonlocal auto_extension_enabled
nonlocal loop_generation_enabled
nonlocal single_clip_mode
nonlocal generation_paused
nonlocal curated_prompts
nonlocal seed_prompt_memory
nonlocal curated_idx
nonlocal segment_idx
nonlocal locked_segment_prompts
nonlocal pending_seed_reset
nonlocal pending_seed_reset_reason
nonlocal pending_reset_conditioning
nonlocal force_curated_restart_segment
nonlocal generated_segment_count
nonlocal generation_cap_blocked
nonlocal auto_extension_blocked_segment_idx
nonlocal prompt_sources_drained_logged
nonlocal pending_simple_prompt_submission
nonlocal single_clip_waiting_for_request
nonlocal rollout_waiting_for_rewrite
nonlocal initial_rollout_waiting_for_rewrite
nonlocal rewrite_restart_pending
nonlocal loop_iteration
nonlocal rewrite_model
nonlocal rewrite_system_prompt
nonlocal rewrite_system_prompt_override
nonlocal rewrite_user_system_prompt_override
nonlocal rewrite_temperature
nonlocal project_active
nonlocal project_stream_started
nonlocal pending_project_end
next_initial_rollout_prompt = str(payload.get("initial_rollout_prompt") or "").strip()
next_enhancement_enabled = bool(payload.get("enhancement_enabled", True))
next_auto_extension_enabled = bool(payload.get("auto_extension_enabled", False))
next_loop_generation_enabled = bool(payload.get("loop_generation_enabled", False))
next_single_clip_mode = bool(payload.get("single_clip_mode", False))
next_preset_id = str(payload.get("preset_id") or "").strip()
if next_preset_id:
preset_id = next_preset_id
next_preset_label = str(payload.get("preset_label") or "").strip()
if next_preset_label:
preset_label = next_preset_label
next_rewrite_model = self.prompt_enhancer.resolve_rewrite_model(payload.get("rewrite_model"))
next_rewrite_system_prompt_override = str(payload.get("rewrite_window_system_prompt") or "").strip()
next_rewrite_user_system_prompt_override = str(payload.get("rewrite_user_system_prompt") or "").strip()
next_rewrite_temperature = (self.prompt_enhancer.resolve_rewrite_temperature(
payload.get("rewrite_temperature")))
next_curated_prompts = []
incoming_prompts = payload.get("curated_prompts", [])
if isinstance(incoming_prompts, list):
for prompt in incoming_prompts:
if isinstance(prompt, str) and prompt.strip():
next_curated_prompts.append(prompt.strip())
blocked_init_prompt = get_first_blocked_prompt(next_curated_prompts)
if blocked_init_prompt is not None:
message = ("Prompt safety filter blocked initial prompt "
f"{blocked_init_prompt.index + 1}. "
f"{blocked_init_prompt.error}")
_main_print(
"WARN",
f"Project init prompt blocked for client {client_id[:8]} "
f"index={blocked_init_prompt.index + 1} "
f"preview={preview_text(blocked_init_prompt.prompt)}",
)
await ws_send_json({
"type": "error",
"message": message,
})
return False
try:
replace_session_init_image(payload.get("initial_image"))
except ValueError as exc:
await ws_send_json({
"type": "error",
"message": str(exc),
})
return False
initial_rollout_prompt = next_initial_rollout_prompt
enhancement_enabled = next_enhancement_enabled
auto_extension_enabled = next_auto_extension_enabled
loop_generation_enabled = next_loop_generation_enabled
single_clip_mode = next_single_clip_mode
rewrite_model = next_rewrite_model
rewrite_system_prompt_override = (next_rewrite_system_prompt_override)
rewrite_user_system_prompt_override = (next_rewrite_user_system_prompt_override)
rewrite_system_prompt = (
self.prompt_enhancer.resolve_rewrite_system_prompt(rewrite_system_prompt_override))
rewrite_temperature = next_rewrite_temperature
curated_prompts = list(next_curated_prompts)
seed_prompt_memory = list(next_curated_prompts)
curated_idx = 0
segment_idx = 0
locked_segment_prompts = []
pending_seed_reset = False
pending_seed_reset_reason = ""
pending_reset_conditioning = True
force_curated_restart_segment = False
generated_segment_count = 0
generation_cap_blocked = False
auto_extension_blocked_segment_idx = None
prompt_sources_drained_logged = False
pending_simple_prompt_submission = None
single_clip_waiting_for_request = False
rollout_waiting_for_rewrite = False
generation_paused = bool(initial_rollout_prompt and not single_clip_mode and len(curated_prompts) == 0)
initial_rollout_waiting_for_rewrite = generation_paused
rewrite_restart_pending = False
loop_iteration = 0
project_active = True
project_stream_started = False
pending_project_end = False
return True
async def run_rewrite_seed_prompts(
rewrite_instruction: str,
rewrite_model_name: str,
rewrite_timeout_ms_value: int,
rewrite_temperature_value: float,
prompt_window_prompts: list[str] | None = None,
):
nonlocal seed_prompt_memory
nonlocal curated_prompts
nonlocal preset_id
nonlocal preset_label
nonlocal rewrite_seed_prompts_task
nonlocal pending_seed_reset
nonlocal pending_seed_reset_reason
nonlocal generated_segment_count
nonlocal generation_cap_blocked
nonlocal generation_paused
nonlocal rollout_waiting_for_rewrite
nonlocal initial_rollout_waiting_for_rewrite
nonlocal rewrite_restart_pending
nonlocal rewrite_system_prompt
nonlocal rewrite_system_prompt_override
nonlocal rewrite_user_system_prompt_override
try:
snapshot_prompts = (list(prompt_window_prompts) if isinstance(prompt_window_prompts, list)
and len(prompt_window_prompts) > 0 else list(seed_prompt_memory))
effective_system_prompt = (self.prompt_enhancer.resolve_rewrite_new_rollout_system_prompt(
rewrite_user_system_prompt_override or rewrite_system_prompt_override, )
if len(snapshot_prompts) == 0 else rewrite_system_prompt)
rewrite_result = await self.prompt_enhancer.rewrite_prompt_sequence(
snapshot_prompts,
preset_id=preset_id,
preset_label=preset_label,
rewrite_instruction=rewrite_instruction,
rewrite_model=rewrite_model_name,
rewrite_temperature=rewrite_temperature_value,
timeout_ms=rewrite_timeout_ms_value,
system_prompt_override=effective_system_prompt,
)
if stop_event.is_set():
return
blocked_rewrite_prompt = get_first_blocked_prompt(rewrite_result.prompts)
if blocked_rewrite_prompt is not None:
error_message = ("Prompt safety filter blocked rewritten prompt "
f"{blocked_rewrite_prompt.index + 1}. "
f"{blocked_rewrite_prompt.error}")
await log_event(
"rewrite_blocked",
{
"kind": "seed_rewrite",
"rewrite_instruction": rewrite_instruction,
"model": rewrite_result.model,
"prompt_window_prompts": snapshot_prompts,
"rewritten_prompts": list(rewrite_result.prompts),
"blocked_prompt_index": blocked_rewrite_prompt.index,
"blocked_prompt": blocked_rewrite_prompt.prompt,
"error": error_message,
},
)
await ws_send_json({
"type": "rewrite_seed_prompts_complete",
"fallback_used": True,
"error": error_message,
"model": rewrite_result.model,
"latency_ms": round(rewrite_result.latency_ms, 2),
})
rewrite_restart_pending = False
return
seed_prompt_memory = list(rewrite_result.prompts)
curated_prompts = list(seed_prompt_memory)
if not rewrite_result.fallback_used:
preset_id = rewrite_result.rollout_id
preset_label = rewrite_result.rollout_label
if initial_rollout_waiting_for_rewrite and not rewrite_result.fallback_used:
pending_seed_reset = True
pending_seed_reset_reason = "initial_rewrite"
generation_paused = False
initial_rollout_waiting_for_rewrite = False
await ws_send_json({
"type": "generation_paused_updated",
"paused": generation_paused,
})
if rollout_waiting_for_rewrite and not rewrite_result.fallback_used:
pending_seed_reset = True
pending_seed_reset_reason = "rewrite_rollout"
generated_segment_count = 0
generation_cap_blocked = False
generation_paused = False
elif rewrite_restart_pending and not rewrite_result.fallback_used:
pending_seed_reset = True
pending_seed_reset_reason = "rewrite_during_generation"
generated_segment_count = 0
generation_cap_blocked = False
generation_paused = False
rewrite_restart_pending = False
elif rewrite_restart_pending:
rewrite_restart_pending = False
await log_event(
"rewrite_done",
{
"kind": "seed_rewrite",
"rewrite_instruction": rewrite_instruction,
"latency_ms": round(rewrite_result.latency_ms, 2),
"response": rewrite_result.raw_response_text or "",
},
)
await ws_send_json({
"type": "seed_prompts_updated",
"prompts": seed_prompt_memory,
"preset_id": preset_id,
"preset_label": preset_label,
"reason": "rewrite",
"fallback_used": rewrite_result.fallback_used,
"error": rewrite_result.error,
"model": rewrite_result.model,
"latency_ms": round(rewrite_result.latency_ms, 2),
"raw_llm_output": rewrite_result.raw_response_text,
"rewrite_instruction": rewrite_instruction,
})
await ws_send_json({
"type": "rewrite_seed_prompts_complete",
"fallback_used": rewrite_result.fallback_used,
"error": rewrite_result.error,
"preset_id": preset_id,
"preset_label": preset_label,
"model": rewrite_result.model,
"latency_ms": round(rewrite_result.latency_ms, 2),
})
except asyncio.CancelledError:
rewrite_restart_pending = False
await log_event(
"rewrite_cancelled",
{
"kind": "seed_rewrite",
"rewrite_instruction": rewrite_instruction,
"rewrite_model": rewrite_model_name,
},
)
raise
except Exception as exc:
if stop_event.is_set():
return
await log_event(
"rewrite_exception",
{
"kind": "seed_rewrite",
"rewrite_instruction": rewrite_instruction,
"rewrite_model": rewrite_model_name,
"error": str(exc),
},
)
await ws_send_json({
"type": "rewrite_seed_prompts_complete",
"fallback_used": True,
"error": str(exc),
"model": rewrite_model_name,
"latency_ms": 0.0,
})
rewrite_restart_pending = False
finally:
rewrite_seed_prompts_task = None
async def websocket_reader_loop():
nonlocal enhancement_enabled
nonlocal auto_extension_enabled
nonlocal loop_generation_enabled
nonlocal generation_paused
nonlocal pending_seed_reset
nonlocal pending_seed_reset_reason
nonlocal generated_segment_count
nonlocal generation_cap_blocked
nonlocal auto_extension_blocked_segment_idx
nonlocal seed_prompt_memory
nonlocal curated_prompts
nonlocal rewrite_seed_prompts_task
nonlocal rewrite_model
nonlocal rewrite_system_prompt
nonlocal rewrite_system_prompt_override
nonlocal rewrite_user_system_prompt_override
nonlocal rewrite_timeout_ms
nonlocal rewrite_temperature
nonlocal rollout_waiting_for_rewrite
nonlocal initial_rollout_waiting_for_rewrite
nonlocal rewrite_restart_pending
nonlocal project_active
nonlocal project_stream_started
nonlocal pending_project_end
while not stop_event.is_set():
try:
data = await websocket.receive_json()
except WebSocketDisconnect:
loop_generation_enabled = False
stop_event.set()
return
except Exception as exc:
_main_print("INFO", f"Client {client_id[:8]} reader stopped: {exc}")
loop_generation_enabled = False
stop_event.set()
return
msg_type = data.get("type")
if msg_type == "leave":
loop_generation_enabled = False
stop_event.set()
return
if msg_type == "end_project_keep_session":
if (rewrite_seed_prompts_task is not None and not rewrite_seed_prompts_task.done()):
await cancel_task(rewrite_seed_prompts_task)
loop_generation_enabled = False
generation_paused = False
rollout_waiting_for_rewrite = False
initial_rollout_waiting_for_rewrite = False
rewrite_restart_pending = False
pending_project_end = True
continue
if msg_type == "project_init_v1":
if project_active or pending_project_end:
await ws_send_json({
"type":
"error",
"message": ("Current project is still ending. "
"Wait for the reset to finish before starting another project."),
})
continue
init_applied = await apply_project_init_payload(data)
if not init_applied:
continue
if initial_rollout_waiting_for_rewrite:
await ws_send_json({
"type": "rewrite_seed_prompts_started",
"model": rewrite_model,
})
rewrite_seed_prompts_task = asyncio.create_task(
run_rewrite_seed_prompts(
initial_rollout_prompt,
rewrite_model,
rewrite_timeout_ms,
rewrite_temperature,
[],
))
else:
pending_seed_reset = True
pending_seed_reset_reason = "project_init"
continue
if not project_active:
if msg_type in {
"append_prompt",
"rewrite_seed_prompts",
"reset_to_seed_prompts",
"restart_generation",
}:
await ws_send_json({
"type": "error",
"message": ("Start a new project before sending prompts."),
})
continue
if msg_type == "append_prompt":
if ((rollout_waiting_for_rewrite or initial_rollout_waiting_for_rewrite)
and not single_clip_mode):
await ws_send_json({
"type": "error",
"message": ("Use Rewrite to create or restart the rollout."),
})
continue
raw_prompt = str(data.get("prompt", "")).strip()
prompt_id = str(data.get("prompt_id") or uuid.uuid4())
if not raw_prompt:
await ws_send_json({
"type": "error",
"message": "append_prompt requires a non-empty prompt",
})
continue
blocked_error = get_prompt_safety_error(raw_prompt)
if blocked_error is not None:
_main_print(
"WARN",
f"Live prompt blocked for client {client_id[:8]} "
f"preview={preview_text(raw_prompt)}",
)
await log_event(
"prompt_blocked",
{
"kind": "user_raw",
"prompt_id": prompt_id,
"raw_prompt": raw_prompt,
"error": blocked_error,
},
)
await ws_send_json({
"type": "error",
"message": blocked_error,
})
continue
submission = PromptSubmission(
prompt_id=prompt_id,
raw_prompt=raw_prompt,
created_at_s=time.time(),
)
await raw_prompt_queue.put(submission)
await log_event(
"append_prompt",
{
"prompt": raw_prompt,
},
)
await ws_send_json({
"type": "prompt_received",
"prompt_id": prompt_id,
"queue_depth": raw_prompt_queue.qsize(),
})
continue
if msg_type == "simple_generate":
await schedule_simple_generate_request(data)
continue
if msg_type == "set_enhancement":
enhancement_enabled = bool(data.get("enabled", enhancement_enabled))
await ws_send_json({
"type": "enhancement_updated",
"enabled": enhancement_enabled,
})
continue
if msg_type == "set_auto_extension":
was_enabled = auto_extension_enabled
auto_extension_enabled = bool(data.get("enabled", auto_extension_enabled))
if not auto_extension_enabled:
auto_extension_blocked_segment_idx = None
_main_print(
"INFO",
f"Auto extension disabled for client {client_id[:8]} "
"(cleared_blocked_state=true)",
)
else:
cleared = auto_extension_blocked_segment_idx is not None
auto_extension_blocked_segment_idx = None
_main_print(
"INFO",
f"Auto extension enabled for client {client_id[:8]} "
f"(was_enabled={was_enabled} "
f"cleared_blocked_state={cleared})",
)
await ws_send_json({
"type": "auto_extension_updated",
"enabled": auto_extension_enabled,
})
continue
if msg_type == "set_loop_generation":
loop_generation_enabled = bool(data.get("enabled", loop_generation_enabled))
await ws_send_json({
"type": "loop_generation_updated",
"enabled": loop_generation_enabled,
})
continue
if msg_type == "set_generation_paused":
generation_paused = bool(data.get("paused", generation_paused))
await ws_send_json({
"type": "generation_paused_updated",
"paused": generation_paused,
})
continue
if msg_type == "reset_to_seed_prompts":
pending_seed_reset = True
pending_seed_reset_reason = "manual_reset"
await ws_send_json({
"type": "seed_prompts_reset_pending",
"seed_prompt_count": len(seed_prompt_memory),
})
continue
if msg_type == "restart_generation":
generated_segment_count = 0
generation_cap_blocked = False
rollout_waiting_for_rewrite = False
pending_seed_reset = True
pending_seed_reset_reason = "cap_restart"
generation_paused = False
await ws_send_json({
"type":
"generation_restarted",
"reason":
"cap_restart",
"segment_cap":
_resolve_generation_segment_cap(
single_clip_mode=single_clip_mode,
cap=GENERATION_SEGMENT_CAP,
),
})
continue
if msg_type == "rewrite_seed_prompts":
rewrite_instruction = str(data.get("rewrite_instruction", "")).strip()
rewrite_model = self.prompt_enhancer.resolve_rewrite_model(data.get("rewrite_model"))
rewrite_system_prompt_override = str(data.get("rewrite_window_system_prompt") or "").strip()
rewrite_user_system_prompt_override = str(data.get("rewrite_user_system_prompt") or "").strip()
rewrite_system_prompt = self.prompt_enhancer.resolve_rewrite_system_prompt(
rewrite_system_prompt_override)
rewrite_temperature = self.prompt_enhancer.resolve_rewrite_temperature(
data.get("rewrite_temperature"))
if len(seed_prompt_memory) == 0 and not (initial_rollout_waiting_for_rewrite
and rewrite_instruction):
await ws_send_json({
"type": "error",
"message": "No seed prompts available to rewrite.",
})
continue
if rewrite_seed_prompts_task is not None and not rewrite_seed_prompts_task.done():
await ws_send_json({
"type": "error",
"message": "rewrite_seed_prompts already in progress.",
})
continue
if (segment_generation_active and not single_clip_mode and not rollout_waiting_for_rewrite
and not initial_rollout_waiting_for_rewrite):
rewrite_restart_pending = True
await ws_send_json({
"type": "rewrite_seed_prompts_started",
"model": rewrite_model,
})
requested_prompt_window = normalize_prompt_window_prompts(data.get("prompt_window_prompts"))
rewrite_seed_prompts_task = asyncio.create_task(
run_rewrite_seed_prompts(
rewrite_instruction,
rewrite_model,
rewrite_timeout_ms,
rewrite_temperature,
requested_prompt_window,
))
continue
if msg_type == "set_rewrite_model":
rewrite_model = self.prompt_enhancer.resolve_rewrite_model(data.get("rewrite_model"))
await ws_send_json({
"type": "rewrite_model_updated",
"rewrite_model": rewrite_model,
})
continue
if msg_type == "set_rewrite_temperature":
rewrite_temperature = self.prompt_enhancer.resolve_rewrite_temperature(
data.get("rewrite_temperature"))
await ws_send_json({
"type": "rewrite_temperature_updated",
"rewrite_temperature": rewrite_temperature,
})
continue
async def prompt_worker_loop():
while not stop_event.is_set():
try:
submission = await asyncio.wait_for(raw_prompt_queue.get(), timeout=0.1)
except asyncio.TimeoutError:
continue
_main_print("INFO", f"Received user prompt for enhancement: {submission.raw_prompt}")
prompt_id = submission.prompt_id
raw_prompt = submission.raw_prompt
blocked_raw_prompt_error = get_prompt_safety_error(raw_prompt)
if blocked_raw_prompt_error is not None:
_main_print(
"WARN",
f"User prompt blocked for client {client_id[:8]} "
f"prompt_id={prompt_id} preview={preview_text(raw_prompt)}",
)
await log_event(
"prompt_blocked",
payload={
"kind": "user_raw",
"prompt_id": prompt_id,
"raw_prompt": raw_prompt,
"error": blocked_raw_prompt_error,
},
)
await ws_send_json({
"type": "error",
"message": blocked_raw_prompt_error,
})
continue
await log_event(
"enhance_request",
{
"prompt_id": prompt_id,
"raw_prompt": raw_prompt,
"enhancement_enabled": enhancement_enabled,
"rewrite_model": rewrite_model,
},
)
if enhancement_enabled:
locked_snapshot = list(locked_segment_prompts)
next_segment_idx = len(locked_snapshot) + 1
_main_print(
"INFO",
f"Enhancement context: next_segment={next_segment_idx} "
f"locked_count={len(locked_snapshot)}",
)
await ws_send_json({
"type": "prompt_enhancing",
"prompt_id": prompt_id,
})
result = await self.prompt_enhancer.enhance_prompt(
raw_prompt,
locked_segments=locked_snapshot,
next_segment_idx=next_segment_idx,
preset_id=preset_id,
mode="single_clip" if single_clip_mode else "user_live",
model=rewrite_model,
timeout_ms=PROMPT_TIMEOUT_MS,
)
final_prompt = result.prompt.strip() if result.prompt else ""
blocked_final_prompt_error = get_prompt_safety_error(final_prompt)
_main_print("INFO", f"Enhanced prompt: {final_prompt}")
await log_event(
"rewrite_done",
{
"kind": "enhance_prompt",
"latency_ms": round(result.latency_ms, 2),
"response": final_prompt,
},
)
if blocked_final_prompt_error is not None:
await log_event(
"prompt_blocked",
{
"kind": "user_enhanced",
"prompt_id": prompt_id,
"raw_prompt": raw_prompt,
"output_prompt": final_prompt,
"fallback_used": result.fallback_used,
"latency_ms": round(result.latency_ms, 2),
"provider": result.provider,
"model": result.model,
"error": blocked_final_prompt_error,
},
)
await ws_send_json({
"type": "error",
"message": blocked_final_prompt_error,
})
continue
if result.fallback_used or not final_prompt:
source = "user_enhancement_failed"
_main_print(
"WARN",
f"User prompt enhancement fallback: client={client_id[:8]} "
f"prompt_id={prompt_id} latency_ms={result.latency_ms:.2f} "
f"error={result.error}",
)
await ws_send_json({
"type": "prompt_fallback_used",
"prompt_id": prompt_id,
"prompt": final_prompt,
"source": source,
"latency_ms": round(result.latency_ms, 2),
"error": PROMPT_EXTENSION_FAILURE_USER_MESSAGE,
})
# Enhancement is strict JSON-only; do not enqueue raw
# prompt when enhancement fails.
continue
else:
source = "user_enhanced"
await ws_send_json({
"type": "prompt_ready",
"prompt_id": prompt_id,
"prompt": final_prompt,
"source": source,
"latency_ms": round(result.latency_ms, 2),
})
await ready_prompt_queue.put(
ReadyPrompt(
prompt_id=prompt_id,
prompt=final_prompt,
source=source,
fallback_used=result.fallback_used,
loop_iteration=loop_iteration,
))
else:
await ready_prompt_queue.put(
ReadyPrompt(
prompt_id=prompt_id,
prompt=raw_prompt,
source="user_raw",
fallback_used=False,
loop_iteration=loop_iteration,
))
await ws_send_json({
"type": "prompt_ready",
"prompt_id": prompt_id,
"prompt": raw_prompt,
"source": "user_raw",
"latency_ms": 0.0,
})
def queue_snapshot() -> dict[str, object]:
return {
"user_ready": ready_prompt_queue.qsize(),
"curated_remaining": max(len(curated_prompts) - curated_idx, 0),
"auto_enabled": auto_extension_enabled,
"auto_blocked_segment": auto_extension_blocked_segment_idx,
"loop_enabled": loop_generation_enabled,
"paused": generation_paused,
"seed_prompt_count": len(seed_prompt_memory),
"loop_iteration": loop_iteration,
}
def drain_queue_nowait(queue: asyncio.Queue) -> int:
drained = 0
while True:
try:
queue.get_nowait()
drained += 1
except asyncio.QueueEmpty:
break
return drained
async def enter_project_idle() -> None:
nonlocal curated_prompts
nonlocal seed_prompt_memory
nonlocal curated_idx
nonlocal segment_idx
nonlocal locked_segment_prompts
nonlocal generated_segment_count
nonlocal generation_cap_blocked
nonlocal auto_extension_blocked_segment_idx
nonlocal prompt_sources_drained_logged
nonlocal generation_paused
nonlocal pending_seed_reset
nonlocal pending_seed_reset_reason
nonlocal pending_reset_conditioning
nonlocal force_curated_restart_segment
nonlocal pending_simple_prompt_submission
nonlocal single_clip_waiting_for_request
nonlocal rollout_waiting_for_rewrite
nonlocal initial_rollout_waiting_for_rewrite
nonlocal rewrite_restart_pending
nonlocal initial_rollout_prompt
nonlocal project_active
nonlocal project_stream_started
nonlocal pending_project_end
dropped_raw = drain_queue_nowait(raw_prompt_queue)
dropped_ready = drain_queue_nowait(ready_prompt_queue)
curated_prompts = []
seed_prompt_memory = []
curated_idx = 0
segment_idx = 0
locked_segment_prompts = []
generated_segment_count = 0
generation_cap_blocked = False
auto_extension_blocked_segment_idx = None
prompt_sources_drained_logged = False
generation_paused = False
pending_seed_reset = False
pending_seed_reset_reason = ""
pending_reset_conditioning = True
force_curated_restart_segment = False
pending_simple_prompt_submission = None
single_clip_waiting_for_request = False
rollout_waiting_for_rewrite = False
initial_rollout_waiting_for_rewrite = False
rewrite_restart_pending = False
initial_rollout_prompt = ""
project_active = False
pending_project_end = False
if project_stream_started:
project_stream_started = False
await ws_send_json({"type": "ltx2_stream_complete"})
await log_event("ws_stream_complete")
await ws_send_json({
"type": "project_idle",
"dropped_user_raw_queue": dropped_raw,
"dropped_user_ready_queue": dropped_ready,
})
def pick_next_prompt_nowait() -> ReadyPrompt | None:
nonlocal curated_idx
try:
return ready_prompt_queue.get_nowait()
except asyncio.QueueEmpty:
pass
if curated_idx < len(curated_prompts):
prompt_idx = curated_idx
prompt = curated_prompts[prompt_idx]
curated_idx += 1
return ReadyPrompt(
prompt=prompt,
source="curated",
seed_prompt_index=prompt_idx,
loop_iteration=loop_iteration,
)
return None
websocket_reader_task = asyncio.create_task(websocket_reader_loop())
prompt_worker_task = asyncio.create_task(prompt_worker_loop())
await asyncio.sleep(0)
await ws_send_json({
"type": "loop_generation_updated",
"enabled": loop_generation_enabled,
})
await ws_send_json({
"type": "generation_paused_updated",
"paused": generation_paused,
})
if pending_project_end:
await enter_project_idle()
elif initial_rollout_waiting_for_rewrite:
await ws_send_json({
"type": "rewrite_seed_prompts_started",
"model": rewrite_model,
})
rewrite_seed_prompts_task = asyncio.create_task(
run_rewrite_seed_prompts(
initial_rollout_prompt,
rewrite_model,
rewrite_timeout_ms,
rewrite_temperature,
[],
))
else:
project_stream_started = True
await ws_send_json({
"type":
"ltx2_stream_start",
"total_segments":
len(curated_prompts),
"preset_id":
preset_id,
"stream_mode":
"av_fmp4",
"live_mode":
True,
"loop_generation_enabled":
loop_generation_enabled,
"loop_iteration":
loop_iteration,
"generation_segment_cap":
_resolve_generation_segment_cap(
single_clip_mode=single_clip_mode,
cap=GENERATION_SEGMENT_CAP,
),
})
await ws_send_json({
"type": "seed_prompts_updated",
"prompts": seed_prompt_memory,
"preset_id": preset_id,
"preset_label": preset_label,
"reason": "init",
"seed_prompt_count": len(seed_prompt_memory),
})
av_event_queue = slot.register_stream_queue(client_id)
while not stop_event.is_set():
if pending_project_end:
await enter_project_idle()
await asyncio.sleep(0)
continue
if not project_active:
await asyncio.sleep(0.05)
continue
if pending_seed_reset:
nonlocal_reason = pending_seed_reset_reason
curated_prompts = list(seed_prompt_memory)
curated_idx = 0
segment_idx = 0
locked_segment_prompts = []
auto_extension_blocked_segment_idx = None
prompt_sources_drained_logged = False
pending_seed_reset = False
pending_reset_conditioning = True
force_curated_restart_segment = len(curated_prompts) > 0
single_clip_waiting_for_request = False
dropped_raw = drain_queue_nowait(raw_prompt_queue)
dropped_ready = drain_queue_nowait(ready_prompt_queue)
loop_iteration += 1
project_stream_started = True
await ws_send_json({
"type":
"ltx2_stream_start",
"total_segments":
len(curated_prompts),
"preset_id":
preset_id,
"stream_mode":
"av_fmp4",
"live_mode":
True,
"loop_generation_enabled":
loop_generation_enabled,
"loop_iteration":
loop_iteration,
"generation_segment_cap":
_resolve_generation_segment_cap(
single_clip_mode=single_clip_mode,
cap=GENERATION_SEGMENT_CAP,
),
})
if nonlocal_reason == "loop_restart":
await ws_send_json({
"type": "loop_restarted",
"loop_iteration": loop_iteration,
"seed_prompt_count": len(seed_prompt_memory),
"dropped_user_raw_queue": dropped_raw,
"dropped_user_ready_queue": dropped_ready,
})
else:
await ws_send_json({
"type": "seed_prompts_reset_applied",
"reason": nonlocal_reason or "manual_reset",
"loop_iteration": loop_iteration,
"seed_prompt_count": len(seed_prompt_memory),
"dropped_user_raw_queue": dropped_raw,
"dropped_user_ready_queue": dropped_ready,
})
if nonlocal_reason == "cap_restart":
generation_cap_blocked = False
generated_segment_count = 0
rollout_waiting_for_rewrite = False
pending_seed_reset_reason = ""
if pending_simple_prompt_submission is not None:
await raw_prompt_queue.put(pending_simple_prompt_submission)
pending_simple_prompt_submission = None
if (not single_clip_mode and not generation_cap_blocked and not rollout_waiting_for_rewrite
and GENERATION_SEGMENT_CAP > 0 and generated_segment_count >= GENERATION_SEGMENT_CAP):
loop_generation_enabled = False
rollout_waiting_for_rewrite = True
_main_print(
"INFO",
f"Segment cap reached for client {client_id[:8]} "
f"(cap_segments={GENERATION_SEGMENT_CAP}, "
f"generated_segments={generated_segment_count}); "
"waiting for rollout rewrite",
)
project_stream_started = False
await ws_send_json({"type": "ltx2_stream_complete"})
await log_event("ws_stream_complete")
if generation_cap_blocked:
await asyncio.sleep(0.05)
continue
if rollout_waiting_for_rewrite:
await asyncio.sleep(0.05)
continue
if initial_rollout_waiting_for_rewrite:
await asyncio.sleep(0.05)
continue
if generation_paused:
await asyncio.sleep(0.05)
continue
if rewrite_restart_pending:
if (rewrite_seed_prompts_task is not None and not rewrite_seed_prompts_task.done()):
await asyncio.sleep(0.05)
continue
rewrite_restart_pending = False
if force_curated_restart_segment and curated_idx < len(curated_prompts):
prompt_idx = curated_idx
prompt = curated_prompts[prompt_idx]
curated_idx += 1
selected = ReadyPrompt(
prompt=prompt,
source="curated",
seed_prompt_index=prompt_idx,
loop_iteration=loop_iteration,
)
force_curated_restart_segment = False
else:
force_curated_restart_segment = False
selected = pick_next_prompt_nowait()
if selected is None and loop_generation_enabled and len(seed_prompt_memory) > 0:
pending_seed_reset = True
pending_seed_reset_reason = "loop_restart"
await asyncio.sleep(0)
continue
if selected is None and auto_extension_enabled:
next_segment = len(locked_segment_prompts) + 1
if auto_extension_blocked_segment_idx == next_segment:
if not prompt_sources_drained_logged:
snapshot = queue_snapshot()
_main_print(
"WARN",
f"Prompt sources drained for client {client_id[:8]} "
f"at segment boundary {segment_idx + 1} "
f"(user_ready={snapshot['user_ready']} "
f"curated_remaining={snapshot['curated_remaining']} "
f"auto_enabled={snapshot['auto_enabled']} "
"auto_state=blocked_after_failed_attempt)",
)
prompt_sources_drained_logged = True
await asyncio.sleep(PROMPT_AUTO_SLEEP_MS / 1000.0)
continue
locked_snapshot = list(locked_segment_prompts)
last_locked_preview = (preview_text(locked_snapshot[-1]) if locked_snapshot else "(none)")
_main_print(
"INFO",
f"Auto prompt generation request: client={client_id[:8]} "
f"next_segment={next_segment} "
f"locked_count={len(locked_snapshot)} "
f"last_locked_preview={last_locked_preview}",
)
result = await self.prompt_enhancer.generate_auto_prompt(
locked_segments=locked_snapshot,
next_segment_idx=next_segment,
model=rewrite_model,
timeout_ms=PROMPT_AUTO_TIMEOUT_MS,
)
_main_print(
"INFO",
f"Auto prompt generation response: client={client_id[:8]} "
f"next_segment={next_segment} "
f"fallback_used={result.fallback_used} "
f"returned_len={len(result.prompt)} "
f"latency_ms={result.latency_ms:.2f} error={result.error} "
f"returned_preview={preview_text(result.prompt)}",
)
auto_prompt = result.prompt.strip() if result.prompt else ""
blocked_auto_prompt_error = get_prompt_safety_error(auto_prompt)
if result.fallback_used or not auto_prompt:
auto_extension_blocked_segment_idx = next_segment
_main_print(
"WARN",
f"Auto prompt generation failed: client={client_id[:8]} "
f"next_segment={next_segment} "
f"latency_ms={result.latency_ms:.2f} "
f"error={result.error or 'Auto prompt generation returned no prompt.'} "
"policy=blocked_no_retry_until_external_action",
)
await ws_send_json({
"type": "auto_prompt_failed",
"segment_idx": next_segment,
"latency_ms": round(result.latency_ms, 2),
"error": result.error or "Auto prompt generation returned no prompt.",
})
await asyncio.sleep(PROMPT_AUTO_SLEEP_MS / 1000.0)
continue
if blocked_auto_prompt_error is not None:
auto_extension_blocked_segment_idx = next_segment
await log_event(
"auto_prompt_blocked",
{
"next_segment_idx": next_segment,
"output_prompt": auto_prompt,
"error": blocked_auto_prompt_error,
"latency_ms": round(result.latency_ms, 2),
"provider": result.provider,
"model": result.model,
},
)
await ws_send_json({
"type": "auto_prompt_failed",
"segment_idx": next_segment,
"latency_ms": round(result.latency_ms, 2),
"error": blocked_auto_prompt_error,
})
await asyncio.sleep(PROMPT_AUTO_SLEEP_MS / 1000.0)
continue
auto_extension_blocked_segment_idx = None
selected = ReadyPrompt(
prompt=auto_prompt,
source="auto_llm",
prompt_id=None,
fallback_used=False,
loop_iteration=loop_iteration,
)
if selected is None:
if single_clip_mode:
await asyncio.sleep(PROMPT_AUTO_SLEEP_MS / 1000.0)
continue
if not prompt_sources_drained_logged:
snapshot = queue_snapshot()
_main_print(
"WARN",
f"Prompt sources drained for client {client_id[:8]} "
f"at segment boundary {segment_idx + 1} "
f"(user_ready={snapshot['user_ready']} "
f"curated_remaining={snapshot['curated_remaining']} "
f"auto_enabled={snapshot['auto_enabled']} "
f"auto_blocked_segment={snapshot['auto_blocked_segment']})",
)
prompt_sources_drained_logged = True
await ws_send_json({
"type": "prompt_sources_blocked",
"segment_idx": segment_idx + 1,
})
await asyncio.sleep(PROMPT_AUTO_SLEEP_MS / 1000.0)
continue
if prompt_sources_drained_logged:
prompt_sources_drained_logged = False
_main_print(
"INFO",
f"Prompt source available again for client {client_id[:8]} "
f"at segment boundary {segment_idx + 1} "
f"(source={selected.source} prompt_id={selected.prompt_id})",
)
await ws_send_json({
"type": "prompt_sources_resumed",
"segment_idx": segment_idx + 1,
"source": selected.source,
"prompt_id": selected.prompt_id,
})
segment_idx += 1
single_clip_waiting_for_request = False
total_segments_hint = max(segment_idx, len(curated_prompts))
prompt = selected.prompt
locked_segment_prompts.append(prompt)
if (auto_extension_blocked_segment_idx is not None
and auto_extension_blocked_segment_idx <= segment_idx):
auto_extension_blocked_segment_idx = None
await ws_send_json({
"type": "segment_prompt_source",
"segment_idx": segment_idx,
"source": selected.source,
"prompt_id": selected.prompt_id,
"fallback_used": selected.fallback_used,
"seed_prompt_index": selected.seed_prompt_index,
"loop_iteration": selected.loop_iteration or loop_iteration,
})
await ws_send_json({
"type": "ltx2_segment_start",
"segment_idx": segment_idx,
"total_segments": total_segments_hint,
"prompt": prompt,
"source": selected.source,
"seed_prompt_index": selected.seed_prompt_index,
"loop_iteration": selected.loop_iteration or loop_iteration,
})
await log_event(
"segment_start",
{
"segment_idx": segment_idx,
},
)
t_start = time.perf_counter()
timings: dict = {}
av_chunks_relayed = 0
av_bytes_relayed = 0
av_streamed = False
step_reset_conditioning = pending_reset_conditioning
pending_reset_conditioning = False
step_image_path = (str(session_init_image.file_path)
if segment_idx == 1 and session_init_image is not None else None)
step_task = asyncio.create_task(
slot.user_step(
client_id,
prompt=prompt,
segment_idx=segment_idx,
image_path=step_image_path,
reset_conditioning=step_reset_conditioning,
))
segment_generation_active = True
try:
while not stop_event.is_set() and (not step_task.done() or not av_event_queue.empty()):
try:
event = await asyncio.wait_for(av_event_queue.get(), timeout=0.05)
except asyncio.TimeoutError:
continue
if event.segment_idx != segment_idx:
print(f"[GPU {gpu_id}] Ignoring out-of-order AV event: "
f"event_seg={event.segment_idx}, current_seg={segment_idx}, "
f"kind={type(event).__name__}")
continue
match event:
case MediaInit(mime=mime, stream_id=stream_id):
av_streamed = True
await ws_send_json({
"type": "media_init",
"segment_idx": segment_idx,
"mime": mime,
"stream_id": stream_id,
"mode": "av_fmp4",
})
case MediaChunk(
chunk=chunk_bytes,
chunk_offset=chunk_offset,
chunk_length=chunk_length,
uses_shared_buffer=uses_shared,
):
if (uses_shared and chunk_offset is not None and chunk_length is not None
and slot.shared_stream_buffer is not None):
start = chunk_offset
end = start + chunk_length
# Copy out of shared buffer for websocket send.
chunk = bytes(slot.shared_stream_buffer[start:end])
else:
chunk = chunk_bytes or b""
if chunk:
av_chunks_relayed += 1
av_bytes_relayed += len(chunk)
await ws_send_bytes(chunk)
case MediaComplete(stream_id=stream_id, chunks=n):
await ws_send_json({
"type": "media_segment_complete",
"segment_idx": segment_idx,
"stream_id": stream_id,
"chunks": n if n is not None else av_chunks_relayed,
})
case _:
print(f"[GPU {gpu_id}] Unknown AV event: "
f"{type(event).__name__}")
if not step_task.done():
step_task.cancel()
else:
timings = await step_task
finally:
segment_generation_active = False
if not step_task.done():
try:
await step_task
except asyncio.CancelledError:
pass
if stop_event.is_set():
break
t_generation = time.perf_counter() - t_start
worker_e2e_ms = float(timings.get("e2e_latency_ms", 0.0) or 0.0)
main_user_step_ms = t_generation * 1000.0
overhead_vs_worker_ms = main_user_step_ms - worker_e2e_ms
ipc_put_start_ns = timings.get("ipc_put_start_ns")
ipc_get_done_ns = timings.get("ipc_get_done_ns")
ipc_queue_transfer_ms = None
if isinstance(ipc_put_start_ns, int) and isinstance(ipc_get_done_ns, int):
ipc_queue_transfer_ms = (ipc_get_done_ns - ipc_put_start_ns) / 1_000_000.0
ipc_queue_str = (f"{ipc_queue_transfer_ms:.0f}ms" if ipc_queue_transfer_ms is not None else "n/a")
print(f"[GPU {gpu_id}] Segment {segment_idx}: "
f"source={selected.source}, "
f"worker_e2e={worker_e2e_ms:.0f}ms, "
f"main_user_step={main_user_step_ms:.0f}ms, "
f"overhead={overhead_vs_worker_ms:.0f}ms, "
f"ipc_queue={ipc_queue_str}")
if not av_streamed:
raise RuntimeError(f"Segment {segment_idx} AV stream did not initialize "
"(no media_init event)")
generated_segment_count += 1
print(f"[GPU {gpu_id}] Segment {segment_idx}: "
f"relayed av chunks={av_chunks_relayed}, "
f"bytes={av_bytes_relayed / (1024 * 1024):.1f}MB")
_main_print(
"INFO",
f"Segments generated for client {client_id[:8]}: "
f"{generated_segment_count}",
)
await log_event(
"segment_complete",
{
"segment_idx": segment_idx,
"latency_ms": {
"total": round(main_user_step_ms, 2),
"worker_e2e": round(worker_e2e_ms, 2),
"main_user_step": round(main_user_step_ms, 2),
"overhead": round(overhead_vs_worker_ms, 2),
},
"data_size_bytes": av_bytes_relayed,
},
)
await ws_send_json({
"type": "step_complete",
"latency_ms": {
"total": round(main_user_step_ms, 2),
"worker_e2e": round(worker_e2e_ms, 2),
"main_user_step": round(main_user_step_ms, 2),
"overhead": round(overhead_vs_worker_ms, 2),
},
})
await ws_send_json({
"type": "ltx2_segment_complete",
"segment_idx": segment_idx,
"total_segments": total_segments_hint,
})
if single_clip_mode and not single_clip_waiting_for_request:
single_clip_waiting_for_request = True
project_stream_started = False
await ws_send_json({"type": "ltx2_stream_complete"})
await log_event("ws_stream_complete")
_main_print(
"INFO",
f"Single-clip session idle for client {client_id[:8]} "
f"after segment {segment_idx}; waiting for next create",
)
if not stop_event.is_set() and project_stream_started:
project_stream_started = False
await ws_send_json({"type": "ltx2_stream_complete"})
await log_event("ws_stream_complete")
print(f"[GPU {gpu_id}] LTX2 streaming complete for {client_id[:8]}")
except WebSocketDisconnect:
stop_event.set()
_main_print("INFO", f"Client {client_id[:8]} disconnected")
except Exception as exc:
stop_event.set()
_main_print("ERROR", f"Client {client_id[:8]} error: {exc}")
try:
await ws_send_json({
"type": "error",
"message": f"AV streaming failed: {exc}",
})
except Exception:
pass
import traceback
traceback.print_exc()
finally:
stop_event.set()
await cancel_task(websocket_reader_task)
await cancel_task(prompt_worker_task)
await cancel_task(rewrite_seed_prompts_task)
if slot is not None and client_id:
slot.unregister_stream_queue(client_id)
if timeout_task and not timeout_task.done():
timeout_task.cancel()
try:
await timeout_task
except asyncio.CancelledError:
pass
try:
if client_id and self.gpu_pool is not None:
await self.gpu_pool.release(client_id)
finally:
cleanup_session_init_image(session_init_image)