reflex-sdk 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/PKG-INFO +1 -1
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/pyproject.toml +1 -1
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/_webrtc_client.py +71 -24
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/webrtc.py +6 -1
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/PKG-INFO +1 -1
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/README.md +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/__init__.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/__main__.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_convex.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_region_probe.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_transport.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_version.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/actions.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/auth_runner.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/__init__.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/base.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/realsense.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/shm.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/v4l2.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cli.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/client.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connect_runner.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/__init__.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/base.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/shell.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/yam_bimanual.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/datasets.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/deployments.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/instances.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/keys.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/models.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/product.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/receipts.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/robot_runtime.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/robots.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/sessions.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/so101.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/training.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/__init__.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/_webrtc_streaming_client.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/base.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/edge_http.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/platform.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/SOURCES.txt +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/dependency_links.txt +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/entry_points.txt +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/requires.txt +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/top_level.txt +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/setup.cfg +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_connect_runner.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_prod1_smoke.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_product_cli.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_public_sdk.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_region_probe.py +0 -0
- {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_so101_actions.py +0 -0
|
@@ -415,33 +415,70 @@ class WebRTCClient:
|
|
|
415
415
|
method="POST",
|
|
416
416
|
headers=signaling_headers,
|
|
417
417
|
)
|
|
418
|
-
# Auto-wake cold Modal worker
|
|
419
|
-
#
|
|
420
|
-
#
|
|
418
|
+
# Auto-wake cold Modal worker. Modal scales workers to zero after idle;
|
|
419
|
+
# first request takes 30-90s to cold-start. Both the signaling POST and
|
|
420
|
+
# the ICE wait below share a single deadline = `timeout` seconds from
|
|
421
|
+
# start, so callers tune one number to govern the whole connect.
|
|
422
|
+
deadline = time.monotonic() + float(timeout)
|
|
423
|
+
loop = asyncio.get_event_loop()
|
|
424
|
+
|
|
425
|
+
def _do_post(per_attempt_timeout: float) -> dict:
|
|
426
|
+
with urllib.request.urlopen(req, timeout=per_attempt_timeout) as r:
|
|
427
|
+
return json.loads(r.read())
|
|
428
|
+
|
|
421
429
|
t0 = time.perf_counter()
|
|
422
|
-
|
|
423
|
-
|
|
430
|
+
attempt = 0
|
|
431
|
+
last_err: BaseException | None = None
|
|
432
|
+
warned = False
|
|
433
|
+
resp = None
|
|
434
|
+
while True:
|
|
435
|
+
remaining = deadline - time.monotonic()
|
|
436
|
+
if remaining <= 0:
|
|
437
|
+
msg = (
|
|
438
|
+
f"WebRTC signaling timeout after {time.perf_counter()-t0:.1f}s "
|
|
439
|
+
f"(budget {timeout:.0f}s)"
|
|
440
|
+
)
|
|
441
|
+
if last_err is not None:
|
|
442
|
+
raise RuntimeError(f"{msg} — last error: {last_err!r}") from last_err
|
|
443
|
+
raise RuntimeError(f"{msg} — no response from {self.signaling_url}")
|
|
444
|
+
attempt += 1
|
|
445
|
+
# Cap each POST at 30s so a stalled connect can't burn the whole
|
|
446
|
+
# budget on one attempt — we'd rather retry sooner with feedback.
|
|
447
|
+
per_attempt = min(remaining, 30.0)
|
|
424
448
|
try:
|
|
425
|
-
|
|
426
|
-
resp = json.loads(r.read())
|
|
449
|
+
resp = await loop.run_in_executor(None, _do_post, per_attempt)
|
|
427
450
|
last_err = None
|
|
428
451
|
break
|
|
429
452
|
except urllib.error.HTTPError as exc:
|
|
430
453
|
last_err = exc
|
|
431
|
-
if exc.code in (502, 503, 504):
|
|
432
|
-
|
|
433
|
-
print(f"[webrtc-client] worker cold (HTTP {exc.code}) — waking...", flush=True)
|
|
434
|
-
time.sleep(10)
|
|
435
|
-
continue
|
|
436
|
-
raise
|
|
454
|
+
if exc.code not in (502, 503, 504):
|
|
455
|
+
raise
|
|
437
456
|
except (urllib.error.URLError, TimeoutError) as exc:
|
|
438
457
|
last_err = exc
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
458
|
+
|
|
459
|
+
if not warned:
|
|
460
|
+
warned = True
|
|
461
|
+
if isinstance(last_err, urllib.error.HTTPError):
|
|
462
|
+
print(
|
|
463
|
+
f"[webrtc-client] worker cold (HTTP {last_err.code}) — "
|
|
464
|
+
f"waking, budget {timeout:.0f}s...",
|
|
465
|
+
flush=True,
|
|
466
|
+
)
|
|
467
|
+
else:
|
|
468
|
+
print(
|
|
469
|
+
f"[webrtc-client] worker unreachable ({last_err!r}) — "
|
|
470
|
+
f"retrying, budget {timeout:.0f}s...",
|
|
471
|
+
flush=True,
|
|
472
|
+
)
|
|
473
|
+
elapsed = time.perf_counter() - t0
|
|
474
|
+
print(
|
|
475
|
+
f"[webrtc-client] cold-wake attempt {attempt} failed at "
|
|
476
|
+
f"{elapsed:.0f}s of {timeout:.0f}s — sleeping before retry",
|
|
477
|
+
flush=True,
|
|
478
|
+
)
|
|
479
|
+
sleep_for = min(10.0, max(0.0, deadline - time.monotonic()))
|
|
480
|
+
if sleep_for > 0:
|
|
481
|
+
await asyncio.sleep(sleep_for)
|
|
445
482
|
print(f"[webrtc-client] signaling roundtrip: {(time.perf_counter()-t0)*1000:.0f}ms", flush=True)
|
|
446
483
|
|
|
447
484
|
await self.pc.setRemoteDescription(RTCSessionDescription(
|
|
@@ -449,13 +486,23 @@ class WebRTCClient:
|
|
|
449
486
|
))
|
|
450
487
|
self.session_id = resp.get("session_id")
|
|
451
488
|
|
|
452
|
-
# Wait for ICE + DTLS + DataChannel
|
|
453
|
-
|
|
489
|
+
# Wait for ICE + DTLS + DataChannel using whatever time is left in the
|
|
490
|
+
# shared deadline. Floor at 1s so a near-exhausted budget still gives
|
|
491
|
+
# ICE a meaningful chance instead of insta-timing-out.
|
|
492
|
+
ice_remaining = max(1.0, deadline - time.monotonic())
|
|
493
|
+
ice_t0 = time.perf_counter()
|
|
454
494
|
try:
|
|
455
|
-
await asyncio.wait_for(self._connected.wait(), timeout=
|
|
456
|
-
print(
|
|
495
|
+
await asyncio.wait_for(self._connected.wait(), timeout=ice_remaining)
|
|
496
|
+
print(
|
|
497
|
+
f"[webrtc-client] connected in {(time.perf_counter()-ice_t0)*1000:.0f}ms "
|
|
498
|
+
f"session={self.session_id}",
|
|
499
|
+
flush=True,
|
|
500
|
+
)
|
|
457
501
|
except asyncio.TimeoutError:
|
|
458
|
-
raise RuntimeError(
|
|
502
|
+
raise RuntimeError(
|
|
503
|
+
f"WebRTC ICE/DataChannel timeout after {ice_remaining:.1f}s — "
|
|
504
|
+
"likely NAT/firewall blocked direct UDP"
|
|
505
|
+
)
|
|
459
506
|
|
|
460
507
|
# H.264 keyframes can take ~500ms-1s to arrive at the server after
|
|
461
508
|
# negotiation. Sleep briefly so the first measured infer() finds a
|
|
@@ -148,7 +148,12 @@ class WebRTCTransport(Transport):
|
|
|
148
148
|
fut = asyncio.run_coroutine_threadsafe(
|
|
149
149
|
self._client.connect(timeout=self._connect_timeout), self._loop
|
|
150
150
|
)
|
|
151
|
-
|
|
151
|
+
# The client owns the deadline (signaling cold-wake + ICE share one
|
|
152
|
+
# budget = connect_timeout). +30s slack here lets an in-flight urllib
|
|
153
|
+
# POST in an executor thread unwind its TCP timeout naturally after
|
|
154
|
+
# the inner coroutine cancels — avoids a bare TimeoutError leaking up
|
|
155
|
+
# while the inner is in the middle of raising its own diagnostic one.
|
|
156
|
+
fut.result(timeout=self._connect_timeout + 30.0)
|
|
152
157
|
|
|
153
158
|
def stop(self) -> None:
|
|
154
159
|
if self._client is not None and self._loop is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|