openvidia 2.2.0__py3-none-any.whl
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.
- openvidia/__init__.py +30 -0
- openvidia/__main__.py +827 -0
- openvidia/_upstream_utils.py +57 -0
- openvidia/anthropic_shim.py +714 -0
- openvidia/compaction.py +851 -0
- openvidia/config.py +513 -0
- openvidia/embedding_cache.py +69 -0
- openvidia/graph_engine.py +466 -0
- openvidia/provider_fallback.py +98 -0
- openvidia/proxy_app.py +863 -0
- openvidia/proxy_state.py +1010 -0
- openvidia/redis_sync.py +93 -0
- openvidia/responses_shim.py +1374 -0
- openvidia/safe_file.py +100 -0
- openvidia/server_manager.py +106 -0
- openvidia/upstream_router.py +50 -0
- openvidia/web/assets/32x32.ico +0 -0
- openvidia/web/assets/64x64.ico +0 -0
- openvidia/web/assets/dashboard.gif +0 -0
- openvidia/web/assets/favicon.ico +0 -0
- openvidia/web/assets/logo.png +0 -0
- openvidia/web/favicon.ico +0 -0
- openvidia/web/index.html +166 -0
- openvidia/web/logo.png +0 -0
- openvidia/web/main.js +732 -0
- openvidia/web/styles.css +425 -0
- openvidia/webui.py +532 -0
- openvidia-2.2.0.dist-info/METADATA +814 -0
- openvidia-2.2.0.dist-info/RECORD +33 -0
- openvidia-2.2.0.dist-info/WHEEL +5 -0
- openvidia-2.2.0.dist-info/entry_points.txt +2 -0
- openvidia-2.2.0.dist-info/licenses/LICENSE +21 -0
- openvidia-2.2.0.dist-info/top_level.txt +1 -0
openvidia/redis_sync.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Optional cross-node state sync via Redis pub/sub.
|
|
2
|
+
|
|
3
|
+
Multi-node setups (several proxy instances behind the same pool of keys)
|
|
4
|
+
share cooldowns and circuit state so node A does not hammer a key that node
|
|
5
|
+
B just cooled down. Redis is an OPTIONAL extra: without it the proxy runs
|
|
6
|
+
standalone, in-memory, exactly as before.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import json
|
|
13
|
+
import uuid
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import redis.asyncio as aioredis
|
|
17
|
+
except ImportError: # redis extra not installed
|
|
18
|
+
aioredis = None
|
|
19
|
+
|
|
20
|
+
_CHANNEL = "openvidia:events"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RedisSync:
|
|
24
|
+
"""Publish local state events and apply remote ones via on_remote."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, url: str) -> None:
|
|
27
|
+
self.url = url
|
|
28
|
+
self.node_id = uuid.uuid4().hex[:8]
|
|
29
|
+
self.on_remote = None
|
|
30
|
+
self._client = aioredis.from_url(url) if aioredis else None
|
|
31
|
+
self._queue: asyncio.Queue[dict] = asyncio.Queue()
|
|
32
|
+
self._tasks: list[asyncio.Task] = []
|
|
33
|
+
self._pubsub = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def enabled(self) -> bool:
|
|
37
|
+
return self._client is not None
|
|
38
|
+
|
|
39
|
+
def broadcast(self, payload: dict) -> None:
|
|
40
|
+
"""Fire-and-forget publish; the queue keeps it non-blocking."""
|
|
41
|
+
if not self.enabled:
|
|
42
|
+
return
|
|
43
|
+
try:
|
|
44
|
+
self._queue.put_nowait(payload)
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
async def start(self) -> None:
|
|
49
|
+
if not self.enabled:
|
|
50
|
+
return
|
|
51
|
+
self._tasks.append(asyncio.create_task(self._publish_loop()))
|
|
52
|
+
self._tasks.append(asyncio.create_task(self._listen_loop()))
|
|
53
|
+
|
|
54
|
+
async def _publish_loop(self) -> None:
|
|
55
|
+
while True:
|
|
56
|
+
payload = await self._queue.get()
|
|
57
|
+
try:
|
|
58
|
+
await self._client.publish(_CHANNEL, json.dumps(payload))
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
async def _listen_loop(self) -> None:
|
|
63
|
+
try:
|
|
64
|
+
self._pubsub = self._client.pubsub()
|
|
65
|
+
await self._pubsub.subscribe(_CHANNEL)
|
|
66
|
+
async for message in self._pubsub.listen():
|
|
67
|
+
if message.get("type") != "message":
|
|
68
|
+
continue
|
|
69
|
+
try:
|
|
70
|
+
event = json.loads(message["data"])
|
|
71
|
+
except (TypeError, ValueError, KeyError):
|
|
72
|
+
continue
|
|
73
|
+
if event.get("node_id") == self.node_id:
|
|
74
|
+
# Our own message comes back from the channel: it must not be
|
|
75
|
+
# re-applied (infinite loop otherwise).
|
|
76
|
+
continue
|
|
77
|
+
callback = self.on_remote
|
|
78
|
+
if callback is not None:
|
|
79
|
+
await callback(event)
|
|
80
|
+
except asyncio.CancelledError:
|
|
81
|
+
raise
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
async def close(self) -> None:
|
|
86
|
+
for task in self._tasks:
|
|
87
|
+
task.cancel()
|
|
88
|
+
if self._tasks:
|
|
89
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
90
|
+
self._tasks.clear()
|
|
91
|
+
if self._client is not None:
|
|
92
|
+
await self._client.aclose()
|
|
93
|
+
self._client = None
|