diff --git a/Dockerfile b/Dockerfile
index ba133ed..9c2e0fa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,9 +3,11 @@ FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
TZ=Asia/Shanghai \
- VIRTUAL_ENV=/opt/venv
+ # 修改:让 uv 直接把包安装到系统 Python 环境,或者指定明确的虚拟环境
+ UV_PROJECT_ENVIRONMENT=/opt/venv
-ENV PATH="$VIRTUAL_ENV/bin:$PATH"
+# 确保虚拟环境的 bin 目录在最前面
+ENV PATH="$UV_PROJECT_ENVIRONMENT/bin:$PATH"
RUN apt-get update \
&& apt-get install -y --no-install-recommends tzdata ca-certificates \
@@ -13,21 +15,26 @@ RUN apt-get update \
WORKDIR /app
-RUN python -m venv "$VIRTUAL_ENV" \
- && pip install --no-cache-dir uv
+# 安装 uv
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+# 或者保留你原有的 pip install uv
-COPY pyproject.toml uv.lock /app/
+COPY pyproject.toml uv.lock ./
-RUN uv sync --frozen --no-dev --no-install-project --active
+# 修改:使用 --frozen 且确保同步到指定的 /opt/venv
+RUN uv sync --frozen --no-dev --no-install-project
-COPY config.defaults.toml /app/config.defaults.toml
-COPY app /app/app
-COPY main.py /app/main.py
-COPY scripts /app/scripts
+COPY config.defaults.toml ./
+COPY app ./app
+COPY main.py ./
+COPY scripts ./scripts
-RUN mkdir -p /app/data /app/data/tmp /app/logs
+RUN mkdir -p /app/data /app/data/tmp /app/logs \
+ && chmod +x /app/scripts/entrypoint.sh
EXPOSE 8000
ENTRYPOINT ["/app/scripts/entrypoint.sh"]
-CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
+
+# 建议:直接调用 uvicorn,因为 /opt/venv/bin 已经在 PATH 中
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
\ No newline at end of file
diff --git a/app/api/v1/voice.py b/app/api/v1/voice.py
new file mode 100644
index 0000000..3ebf197
--- /dev/null
+++ b/app/api/v1/voice.py
@@ -0,0 +1,69 @@
+"""
+Voice Mode API
+"""
+
+from fastapi import APIRouter, Depends
+from pydantic import BaseModel
+
+from app.core.auth import verify_api_key
+from app.services.grok.voice import VoiceService
+from app.services.token import get_token_manager
+from app.core.exceptions import AppException
+
+router = APIRouter(tags=["Voice"])
+
+
+class VoiceTokenResponse(BaseModel):
+ token: str
+ url: str
+ participant_name: str = ""
+ room_name: str = ""
+
+
+@router.get("/voice/token", response_model=VoiceTokenResponse)
+async def get_voice_token(
+ voice: str = "ara",
+ personality: str = "assistant",
+ speed: float = 1.0,
+ api_key: str = Depends(verify_api_key)
+):
+ """
+ Get Grok Voice Mode (LiveKit) Token
+
+ Returns:
+ token: Access Token
+ url: LiveKit Server URL (wss://livekit.grok.com)
+ """
+ token_mgr = await get_token_manager()
+ # Use 'grok-3' or similar model to get a valid pool token
+ sso_token = token_mgr.get_token("ssoBasic")
+
+ if not sso_token:
+ raise AppException("No available tokens for voice mode", code="no_token", status_code=503)
+
+ service = VoiceService()
+ try:
+ data = await service.get_token(
+ token=sso_token,
+ voice=voice,
+ personality=personality,
+ speed=speed
+ )
+
+ # Check for errors in data structure
+ token = data.get("token")
+ if not token:
+ raise AppException("Upstream returned no voice token", code="upstream_error", status_code=502)
+
+ # Parse payload if needed, but for now return raw
+ return VoiceTokenResponse(
+ token=token,
+ url="wss://livekit.grok.com",
+ participant_name="",
+ room_name=""
+ )
+
+ except Exception as e:
+ if isinstance(e, AppException):
+ raise
+ raise AppException(f"Voice token error: {str(e)}", code="voice_error", status_code=500)
diff --git a/app/services/grok/voice.py b/app/services/grok/voice.py
new file mode 100644
index 0000000..61b025f
--- /dev/null
+++ b/app/services/grok/voice.py
@@ -0,0 +1,115 @@
+"""
+Grok Voice Mode Service
+"""
+
+import uuid
+import orjson
+from typing import Dict, Any, Optional
+
+from curl_cffi.requests import AsyncSession
+
+from app.core.logger import logger
+from app.core.config import get_config
+from app.core.exceptions import UpstreamException
+from app.services.grok.statsig import StatsigService
+
+
+LIVEKIT_TOKEN_API = "https://grok.com/rest/livekit/tokens"
+TIMEOUT = 30
+BROWSER = "chrome136"
+
+
+class VoiceService:
+ """Voice Mode Service (LiveKit)"""
+
+ def __init__(self, proxy: str = None):
+ self.proxy = proxy or get_config("grok.base_proxy_url", "")
+
+ async def get_token(
+ self,
+ token: str,
+ model: str = "grok-4.1",
+ voice: str = "ara",
+ personality: str = "assistant",
+ speed: float = 1.0,
+ ) -> Dict[str, Any]:
+ """
+ Get LiveKit token
+
+ Args:
+ token: Auth token
+ model: Model name (default:grok-4.1, though voice uses specific settings)
+
+ Returns:
+ Dict containing token and livekitUrl
+ """
+ headers = self._build_headers(token)
+ payload = self._build_payload(model, voice, personality, speed)
+
+ proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
+
+ try:
+ async with AsyncSession(impersonate=BROWSER) as session:
+ response = await session.post(
+ LIVEKIT_TOKEN_API,
+ headers=headers,
+ data=orjson.dumps(payload),
+ timeout=TIMEOUT,
+ proxies=proxies,
+ )
+
+ if response.status_code != 200:
+ logger.error(
+ f"Voice token failed: {response.status_code}",
+ extra={"response": response.text[:200]}
+ )
+ raise UpstreamException(
+ message=f"Failed to get voice token: {response.status_code}",
+ details={"status": response.status_code, "body": response.text}
+ )
+
+ return response.json()
+
+ except Exception as e:
+ logger.error(f"Voice service error: {e}")
+ if isinstance(e, UpstreamException):
+ raise
+ raise UpstreamException(f"Voice service error: {str(e)}")
+
+ def _build_headers(self, token: str) -> Dict[str, str]:
+ headers = {
+ "Accept": "*/*",
+ "Content-Type": "application/json",
+ "Origin": "https://grok.com",
+ "Referer": "https://grok.com/",
+ # Statsig ID is crucial
+ "x-statsig-id": StatsigService.gen_id(),
+ "x-xai-request-id": str(uuid.uuid4()),
+ }
+
+ token = token[4:] if token.startswith("sso=") else token
+ cf = get_config("grok.cf_clearance", "")
+ headers["Cookie"] = f"sso={token};cf_clearance={cf}" if cf else f"sso={token}"
+
+ return headers
+
+ def _build_payload(
+ self,
+ model: str,
+ voice: str = "ara",
+ personality: str = "assistant",
+ speed: float = 1.0,
+ ) -> Dict[str, Any]:
+ """Construct payload with voice settings"""
+ return {
+ "sessionPayload": orjson.dumps({
+ "voice": voice,
+ "personality": personality,
+ "playback_speed": speed,
+ "enable_vision": False,
+ "turn_detection": {"type": "server_vad"}
+ }).decode(),
+ "requestAgentDispatch": False,
+ "livekitUrl": "wss://livekit.grok.com",
+ "params": {"enable_markdown_transcript": "true"}
+ }
diff --git a/app/static/test_voice.html b/app/static/test_voice.html
new file mode 100644
index 0000000..cea709e
--- /dev/null
+++ b/app/static/test_voice.html
@@ -0,0 +1,391 @@
+
+
+
+
+
+
+ Grok Voice Live 测试
+
+
+
+
+
+
+
Grok Voice Live
+
+
+ 未连接
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/token.json b/data/token.json
deleted file mode 100644
index 9e26dfe..0000000
--- a/data/token.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
\ No newline at end of file
diff --git a/main.py b/main.py
index bd81ff2..04d4fbb 100644
--- a/main.py
+++ b/main.py
@@ -29,6 +29,7 @@ from app.api.v1.chat import router as chat_router # noqa: E402
from app.api.v1.image import router as image_router # noqa: E402
from app.api.v1.files import router as files_router # noqa: E402
from app.api.v1.models import router as models_router # noqa: E402
+from app.api.v1.voice import router as voice_router # noqa: E402
from app.services.token import get_scheduler # noqa: E402
@@ -108,6 +109,9 @@ def create_app() -> FastAPI:
app.include_router(
models_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
+ app.include_router(
+ voice_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
+ )
app.include_router(files_router, prefix="/v1/files")
# 静态文件服务
@@ -131,6 +135,10 @@ app = create_app()
if __name__ == "__main__":
import uvicorn
+ # Set proxy
+ os.environ["HTTP_PROXY"] = "http://127.0.0.1:19347"
+ os.environ["HTTPS_PROXY"] = "http://127.0.0.1:19347"
+
host = os.getenv("SERVER_HOST", "0.0.0.0")
port = int(os.getenv("SERVER_PORT", "8000"))
workers = int(os.getenv("SERVER_WORKERS", "1"))
diff --git a/pyproject.toml b/pyproject.toml
index 3c213d3..8ffb1cb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,6 +12,7 @@ dependencies = [
"fastapi>=0.119.0",
"greenlet>=3.3.1",
"httpx>=0.28.1",
+ "livekit>=1.0.25",
"loguru>=0.7.3",
"orjson>=3.11.4",
"pydantic-settings>=2.12.0",
diff --git a/scripts/test_voice_connection.py b/scripts/test_voice_connection.py
new file mode 100644
index 0000000..fa976c5
--- /dev/null
+++ b/scripts/test_voice_connection.py
@@ -0,0 +1,103 @@
+import asyncio
+import os
+import sys
+from pathlib import Path
+
+# Add project root to path
+sys.path.append(str(Path(__file__).parent.parent))
+
+from dotenv import load_dotenv
+
+# Load env vars
+load_dotenv(Path(__file__).parent.parent / ".env")
+
+from app.core.config import config
+from app.services.token import get_token_manager
+from app.services.grok.voice import VoiceService
+
+try:
+ from livekit import rtc
+except ImportError:
+ print("Please install livekit: pip install livekit")
+ sys.exit(1)
+
+
+async def main():
+
+
+ print("Testing Grok Voice Connection...")
+
+ # Load config
+ await config.load()
+
+ # Get token
+ token = None
+
+ # 1. Try TokenManager
+ try:
+ token_mgr = await get_token_manager()
+ token = token_mgr.get_token('ssoBasic')
+ if token:
+ print(f"Using Token from Manager: {token[:10]}...")
+ except Exception as e:
+ print(f"TokenManager failed: {e}")
+
+ # 2. Try Env Vars
+ if not token:
+ token = os.getenv("SSO_TOKEN") or os.getenv("GROK_TOKEN")
+ if token:
+ print(f"Using Token from Env: {token[:10]}...")
+
+ if not token:
+ print("Error: No SSO token available. Please set SSO_TOKEN env var or check token pool.")
+ return
+
+ sso_token = token
+
+ # Get Voice Token
+ service = VoiceService()
+ try:
+ data = await service.get_token(sso_token)
+ token = data.get("token")
+ url = "wss://livekit.grok.com"
+
+ print(f"Got LiveKit Token: {token[:20]}...")
+
+ except Exception as e:
+ print(f"Failed to get voice token: {e}")
+ return
+
+ # Connect to LiveKit
+ print(f"Connecting to {url}...")
+ room = rtc.Room()
+
+ @room.on("participant_connected")
+ def on_participant_connected(participant: rtc.RemoteParticipant):
+ print(f"Participant connected: {participant.identity}")
+
+ @room.on("participant_disconnected")
+ def on_participant_disconnected(participant: rtc.RemoteParticipant):
+ print(f"Participant disconnected: {participant.identity}")
+
+ @room.on("track_subscribed")
+ def on_track_subscribed(track: rtc.Track, publication: rtc.RemoteTrackPublication, participant: rtc.RemoteParticipant):
+ print(f"Track subscribed: {publication.kind} from {participant.identity}")
+ if publication.kind == rtc.TrackKind.KIND_AUDIO:
+ print("Audio track received! Voice mode is working.")
+
+ try:
+ await room.connect(url, token)
+ print("Connected to Room!")
+
+ # Keep connection open for a few seconds
+ await asyncio.sleep(10)
+
+ except Exception as e:
+ print(f"Failed to connect to LiveKit: {e}")
+ finally:
+ await room.disconnect()
+ print("Disconnected")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/uv.lock b/uv.lock
index cfd7ab0..4b48cdf 100644
--- a/uv.lock
+++ b/uv.lock
@@ -278,6 +278,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
+[[package]]
+name = "livekit"
+version = "1.0.25"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiofiles" },
+ { name = "numpy" },
+ { name = "protobuf" },
+ { name = "types-protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f6/80/b6dbac61e7c10edee295dba3d0431b8a8504cd6db5286709b03d66a66d0a/livekit-1.0.25.tar.gz", hash = "sha256:a28ef3a1f420616facfb36a92cd590e5510b9cfc5b8b9bd425587a159edfd57b", size = 316847, upload-time = "2026-01-30T17:07:25.236Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/bc/ad8d6e340c02db57cc624c025cc6d838703fa6a8a211b643beaf5c5f789d/livekit-1.0.25-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:1ec9e16e30cc9831c447cdf05fcffc74453ae40a33ed7e2ef2178f98a3bb2de4", size = 11059941, upload-time = "2026-01-30T17:07:13.644Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/fc/dfbbab4f3168f2e0ed4c255b9f387f37ff70a29d8026ca7b411e368a565a/livekit-1.0.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6237866efbbf033a3d174f15edc891cb793e9fbafe9432d1fd5bb62080704fbb", size = 9807946, upload-time = "2026-01-30T17:07:15.986Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/b6/1ec8e007631ba8da62d963547f2d7237e1696106e9ddb69b3cd1bc20318b/livekit-1.0.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5a93e854744074fa37eb02f77b4dcdbafcefddc3763f28ec5b267059ddb0c65e", size = 15175694, upload-time = "2026-01-30T17:07:18.101Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/e7/1963f93804697de51c34677405e26960ca5934fe865632422eb712bca2e7/livekit-1.0.25-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:abe498700566e90c89dbbf73731f850f4b5f7d2185bac88be71a34657b51fd3a", size = 12636407, upload-time = "2026-01-30T17:07:21.013Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/3a/d5427fe78f2c5395d3da4b895978c693a2652f9480e75abf93d8ab315a94/livekit-1.0.25-py3-none-win_amd64.whl", hash = "sha256:6ef59cca92db6f207d40b4499b8d2338f8c7278e8cae9e24d8208d66a7d2b8b0", size = 11727716, upload-time = "2026-01-30T17:07:23.007Z" },
+]
+
[[package]]
name = "loguru"
version = "0.7.3"
@@ -303,6 +322,7 @@ dependencies = [
{ name = "fastapi" },
{ name = "greenlet" },
{ name = "httpx" },
+ { name = "livekit" },
{ name = "loguru" },
{ name = "orjson" },
{ name = "pydantic-settings" },
@@ -324,6 +344,7 @@ requires-dist = [
{ name = "fastapi", specifier = ">=0.119.0" },
{ name = "greenlet", specifier = ">=3.3.1" },
{ name = "httpx", specifier = ">=0.28.1" },
+ { name = "livekit", specifier = ">=1.0.25" },
{ name = "loguru", specifier = ">=0.7.3" },
{ name = "orjson", specifier = ">=3.11.4" },
{ name = "pydantic-settings", specifier = ">=2.12.0" },
@@ -336,6 +357,56 @@ requires-dist = [
{ name = "uvicorn", specifier = ">=0.37.0" },
]
+[[package]]
+name = "numpy"
+version = "2.4.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" },
+ { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" },
+ { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" },
+ { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" },
+ { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" },
+ { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" },
+ { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" },
+ { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" },
+ { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" },
+ { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" },
+ { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" },
+ { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" },
+ { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" },
+ { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" },
+ { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" },
+ { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" },
+ { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" },
+ { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" },
+ { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" },
+ { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" },
+ { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" },
+ { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" },
+ { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" },
+ { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" },
+]
+
[[package]]
name = "orjson"
version = "3.11.6"
@@ -374,6 +445,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/55/74/f473a3ec7a0a7ebc825ca8e3c86763f7d039f379860c81ba12dcdd456547/orjson-3.11.6-cp314-cp314-win_arm64.whl", hash = "sha256:fe71f6b283f4f1832204ab8235ce07adad145052614f77c876fcf0dac97bc06f", size = 135168, upload-time = "2026-01-29T15:13:05.932Z" },
]
+[[package]]
+name = "protobuf"
+version = "6.33.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" },
+ { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" },
+ { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" },
+]
+
[[package]]
name = "pycparser"
version = "3.0"
@@ -620,6 +706,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
]
+[[package]]
+name = "types-protobuf"
+version = "6.32.1.20251210"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" },
+]
+
[[package]]
name = "typing-extensions"
version = "4.15.0"