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"