Spaces:
Running
Running
Upload 16 files
Browse files- Dockerfile +18 -0
- LICENSE +21 -0
- api/__init__.py +0 -0
- api/converter/__init__.py +0 -0
- api/converter/messages.py +192 -0
- api/converter/stream.py +260 -0
- api/provider.py +41 -0
- api/routes/__init__.py +0 -0
- api/routes/chat.py +110 -0
- api/routes/models.py +26 -0
- api/server.py +46 -0
- config.py +33 -0
- docker-compose.yml +8 -0
- main.py +6 -0
- pyproject.toml +14 -0
- uv.lock +382 -0
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13-slim
|
| 2 |
+
|
| 3 |
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
| 4 |
+
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
COPY pyproject.toml uv.lock ./
|
| 8 |
+
RUN uv sync --frozen --no-dev --no-install-project
|
| 9 |
+
|
| 10 |
+
COPY config.py main.py ./
|
| 11 |
+
COPY api/ api/
|
| 12 |
+
|
| 13 |
+
ENV API_HOST=0.0.0.0
|
| 14 |
+
ENV API_PORT=8080
|
| 15 |
+
|
| 16 |
+
EXPOSE 8080
|
| 17 |
+
|
| 18 |
+
CMD ["uv", "run", "uvicorn", "api.server:app", "--host", "0.0.0.0", "--port", "8080"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 XXXxx7258
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
api/__init__.py
ADDED
|
File without changes
|
api/converter/__init__.py
ADDED
|
File without changes
|
api/converter/messages.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Convert between OpenAI chat format and AI SDK v6 format."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
from nanoid import generate as nanoid
|
| 8 |
+
|
| 9 |
+
from config import MODEL_MAP
|
| 10 |
+
|
| 11 |
+
_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _gen_id(prefix: str = "msg", size: int = 12) -> str:
|
| 15 |
+
return f"{prefix}_{nanoid(_ALPHABET, size)}"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _resolve_model(model: str) -> str:
|
| 19 |
+
"""Map short model name to assistant-ui API identifier."""
|
| 20 |
+
if model in MODEL_MAP:
|
| 21 |
+
return MODEL_MAP[model]
|
| 22 |
+
if "/" in model:
|
| 23 |
+
return model
|
| 24 |
+
return f"openai/{model}"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _guess_media_type(url: str) -> str:
|
| 28 |
+
"""Infer media type from a data-URI or file extension."""
|
| 29 |
+
if url.startswith("data:"):
|
| 30 |
+
# data:image/png;base64,...
|
| 31 |
+
header = url.split(",", 1)[0]
|
| 32 |
+
if ";" in header:
|
| 33 |
+
return header[5:].split(";")[0] # strip "data:" prefix
|
| 34 |
+
return header[5:]
|
| 35 |
+
lower = url.lower()
|
| 36 |
+
for ext, mt in (
|
| 37 |
+
(".png", "image/png"), (".jpg", "image/jpeg"), (".jpeg", "image/jpeg"),
|
| 38 |
+
(".gif", "image/gif"), (".webp", "image/webp"), (".svg", "image/svg+xml"),
|
| 39 |
+
):
|
| 40 |
+
if ext in lower:
|
| 41 |
+
return mt
|
| 42 |
+
return "image/png"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _convert_tools(tools: list[dict] | None) -> dict:
|
| 46 |
+
"""Convert OpenAI tools list to AI SDK frontend tools format.
|
| 47 |
+
|
| 48 |
+
OpenAI format:
|
| 49 |
+
[{"type": "function", "function": {"name": "...", "description": "...",
|
| 50 |
+
"parameters": {...}}}]
|
| 51 |
+
|
| 52 |
+
AI SDK format:
|
| 53 |
+
{"tool_name": {"description": "...", "parameters": {...}}}
|
| 54 |
+
"""
|
| 55 |
+
if not tools:
|
| 56 |
+
return {}
|
| 57 |
+
result = {}
|
| 58 |
+
for tool in tools:
|
| 59 |
+
if tool.get("type") != "function":
|
| 60 |
+
continue
|
| 61 |
+
func = tool.get("function", {})
|
| 62 |
+
name = func.get("name", "")
|
| 63 |
+
if not name:
|
| 64 |
+
continue
|
| 65 |
+
entry: dict = {"parameters": func.get("parameters", {"type": "object"})}
|
| 66 |
+
if func.get("description"):
|
| 67 |
+
entry["description"] = func["description"]
|
| 68 |
+
result[name] = entry
|
| 69 |
+
return result
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def openai_to_ai_sdk(
|
| 73 |
+
messages: list[dict],
|
| 74 |
+
model: str,
|
| 75 |
+
tools: list[dict] | None = None,
|
| 76 |
+
) -> dict:
|
| 77 |
+
"""Convert an OpenAI chat-completions request to AI SDK v6 payload."""
|
| 78 |
+
sdk_messages: list[dict] = []
|
| 79 |
+
|
| 80 |
+
for msg in messages:
|
| 81 |
+
role = msg.get("role", "")
|
| 82 |
+
content = msg.get("content", "")
|
| 83 |
+
|
| 84 |
+
if role == "system":
|
| 85 |
+
# Inject as AI SDK system message in the messages array.
|
| 86 |
+
# convertToModelMessages() handles role:"system" and forwards
|
| 87 |
+
# it as a model-level system message — bypasses the server's
|
| 88 |
+
# missing top-level "system" param in streamText().
|
| 89 |
+
text = content if isinstance(content, str) else ""
|
| 90 |
+
if text:
|
| 91 |
+
sdk_messages.append({
|
| 92 |
+
"role": "system",
|
| 93 |
+
"parts": [{"type": "text", "text": text}],
|
| 94 |
+
"metadata": {"custom": {}},
|
| 95 |
+
"id": _gen_id("sys"),
|
| 96 |
+
})
|
| 97 |
+
continue
|
| 98 |
+
|
| 99 |
+
if role == "user":
|
| 100 |
+
if isinstance(content, list):
|
| 101 |
+
parts = []
|
| 102 |
+
for part in content:
|
| 103 |
+
if isinstance(part, str):
|
| 104 |
+
parts.append({"type": "text", "text": part})
|
| 105 |
+
elif isinstance(part, dict):
|
| 106 |
+
ptype = part.get("type", "")
|
| 107 |
+
if ptype == "text":
|
| 108 |
+
parts.append({"type": "text", "text": part["text"]})
|
| 109 |
+
elif ptype == "image_url":
|
| 110 |
+
# OpenAI vision format → AI SDK file part
|
| 111 |
+
img = part.get("image_url", {})
|
| 112 |
+
url = img.get("url", "") if isinstance(img, dict) else str(img)
|
| 113 |
+
media_type = _guess_media_type(url)
|
| 114 |
+
parts.append({
|
| 115 |
+
"type": "file",
|
| 116 |
+
"mediaType": media_type,
|
| 117 |
+
"url": url,
|
| 118 |
+
})
|
| 119 |
+
else:
|
| 120 |
+
parts = [{"type": "text", "text": str(content)}]
|
| 121 |
+
sdk_messages.append({
|
| 122 |
+
"role": "user",
|
| 123 |
+
"parts": parts,
|
| 124 |
+
"metadata": {"custom": {}},
|
| 125 |
+
"id": _gen_id("msg"),
|
| 126 |
+
})
|
| 127 |
+
|
| 128 |
+
elif role == "assistant":
|
| 129 |
+
parts = []
|
| 130 |
+
# Text content
|
| 131 |
+
if isinstance(content, str) and content:
|
| 132 |
+
parts.append({"type": "text", "text": content})
|
| 133 |
+
elif isinstance(content, list):
|
| 134 |
+
for part in content:
|
| 135 |
+
if isinstance(part, dict) and part.get("type") == "text":
|
| 136 |
+
parts.append({"type": "text", "text": part["text"]})
|
| 137 |
+
# Tool calls → tool-invocation parts
|
| 138 |
+
# Initially set state to "input-available" (args ready, no result yet)
|
| 139 |
+
for tc in msg.get("tool_calls") or []:
|
| 140 |
+
func = tc.get("function", {})
|
| 141 |
+
try:
|
| 142 |
+
args = json.loads(func.get("arguments", "{}"))
|
| 143 |
+
except (json.JSONDecodeError, TypeError):
|
| 144 |
+
args = {}
|
| 145 |
+
parts.append({
|
| 146 |
+
"type": "tool-invocation",
|
| 147 |
+
"toolCallId": tc.get("id", _gen_id("call")),
|
| 148 |
+
"toolName": func.get("name", ""),
|
| 149 |
+
"input": args,
|
| 150 |
+
"state": "input-available",
|
| 151 |
+
})
|
| 152 |
+
sdk_messages.append({
|
| 153 |
+
"role": "assistant",
|
| 154 |
+
"parts": parts,
|
| 155 |
+
"metadata": {"custom": {}},
|
| 156 |
+
"id": _gen_id("msg"),
|
| 157 |
+
})
|
| 158 |
+
|
| 159 |
+
elif role == "tool":
|
| 160 |
+
# Tool result — attach output to the matching tool-invocation
|
| 161 |
+
# in the preceding assistant message, using AI SDK v6 field names.
|
| 162 |
+
tool_call_id = msg.get("tool_call_id", "")
|
| 163 |
+
# Parse result: try JSON object first, fall back to string
|
| 164 |
+
if isinstance(content, str):
|
| 165 |
+
try:
|
| 166 |
+
result_obj = json.loads(content)
|
| 167 |
+
except (json.JSONDecodeError, TypeError):
|
| 168 |
+
result_obj = content
|
| 169 |
+
else:
|
| 170 |
+
result_obj = content
|
| 171 |
+
for prev in reversed(sdk_messages):
|
| 172 |
+
if prev["role"] != "assistant":
|
| 173 |
+
continue
|
| 174 |
+
for part in prev["parts"]:
|
| 175 |
+
if (
|
| 176 |
+
part.get("type") == "tool-invocation"
|
| 177 |
+
and part.get("toolCallId") == tool_call_id
|
| 178 |
+
):
|
| 179 |
+
part["state"] = "output-available"
|
| 180 |
+
part["output"] = result_obj
|
| 181 |
+
break
|
| 182 |
+
break
|
| 183 |
+
|
| 184 |
+
return {
|
| 185 |
+
"system": "",
|
| 186 |
+
"config": {"modelName": _resolve_model(model)},
|
| 187 |
+
"tools": _convert_tools(tools),
|
| 188 |
+
"id": _gen_id("thread"),
|
| 189 |
+
"messages": sdk_messages,
|
| 190 |
+
"trigger": "submit-message",
|
| 191 |
+
"metadata": {},
|
| 192 |
+
}
|
api/converter/stream.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Convert AI SDK v6 Data Stream SSE to OpenAI chat-completions SSE format."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import time
|
| 7 |
+
from collections.abc import AsyncIterator
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _make_chunk(
|
| 11 |
+
request_id: str,
|
| 12 |
+
model: str,
|
| 13 |
+
*,
|
| 14 |
+
delta: dict,
|
| 15 |
+
finish_reason: str | None = None,
|
| 16 |
+
usage: dict | None = None,
|
| 17 |
+
) -> str:
|
| 18 |
+
"""Format a single OpenAI SSE chunk."""
|
| 19 |
+
chunk: dict = {
|
| 20 |
+
"id": request_id,
|
| 21 |
+
"object": "chat.completion.chunk",
|
| 22 |
+
"created": int(time.time()),
|
| 23 |
+
"model": model,
|
| 24 |
+
"choices": [
|
| 25 |
+
{
|
| 26 |
+
"index": 0,
|
| 27 |
+
"delta": delta,
|
| 28 |
+
"finish_reason": finish_reason,
|
| 29 |
+
}
|
| 30 |
+
],
|
| 31 |
+
}
|
| 32 |
+
if usage is not None:
|
| 33 |
+
chunk["usage"] = usage
|
| 34 |
+
return f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _extract_usage(event: dict) -> dict | None:
|
| 38 |
+
"""Extract usage from finish event (handles both /api/chat and /api/doc/chat)."""
|
| 39 |
+
meta = event.get("messageMetadata", {})
|
| 40 |
+
# /api/doc/chat nests under custom.usage
|
| 41 |
+
raw = meta.get("custom", {}).get("usage")
|
| 42 |
+
# /api/chat puts usage directly in messageMetadata
|
| 43 |
+
if not raw:
|
| 44 |
+
raw = meta.get("usage")
|
| 45 |
+
if not raw:
|
| 46 |
+
return None
|
| 47 |
+
return {
|
| 48 |
+
"prompt_tokens": raw.get("inputTokens", 0),
|
| 49 |
+
"completion_tokens": raw.get("outputTokens", 0),
|
| 50 |
+
"total_tokens": raw.get("totalTokens", 0),
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# ---------------------------------------------------------------------------
|
| 55 |
+
# Streaming conversion
|
| 56 |
+
# ---------------------------------------------------------------------------
|
| 57 |
+
|
| 58 |
+
async def convert_stream(
|
| 59 |
+
lines: AsyncIterator[str],
|
| 60 |
+
model: str,
|
| 61 |
+
request_id: str,
|
| 62 |
+
) -> AsyncIterator[str]:
|
| 63 |
+
"""Yield OpenAI-compatible SSE strings from an AI SDK data-stream."""
|
| 64 |
+
role_sent = False
|
| 65 |
+
# Accumulate tool call argument deltas per toolCallId
|
| 66 |
+
tool_calls_index: dict[str, int] = {} # toolCallId → index
|
| 67 |
+
next_tool_index = 0
|
| 68 |
+
|
| 69 |
+
async for raw_line in lines:
|
| 70 |
+
line = raw_line.rstrip("\r\n")
|
| 71 |
+
if not line:
|
| 72 |
+
continue
|
| 73 |
+
|
| 74 |
+
if line == "data: [DONE]":
|
| 75 |
+
yield "data: [DONE]\n\n"
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
if not line.startswith("data: "):
|
| 79 |
+
continue
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
event = json.loads(line[6:])
|
| 83 |
+
except json.JSONDecodeError:
|
| 84 |
+
continue
|
| 85 |
+
|
| 86 |
+
event_type = event.get("type")
|
| 87 |
+
|
| 88 |
+
# --- Text events ---
|
| 89 |
+
if event_type == "text-start":
|
| 90 |
+
if not role_sent:
|
| 91 |
+
yield _make_chunk(
|
| 92 |
+
request_id, model, delta={"role": "assistant", "content": ""}
|
| 93 |
+
)
|
| 94 |
+
role_sent = True
|
| 95 |
+
|
| 96 |
+
elif event_type == "text-delta":
|
| 97 |
+
if not role_sent:
|
| 98 |
+
yield _make_chunk(
|
| 99 |
+
request_id, model, delta={"role": "assistant", "content": ""}
|
| 100 |
+
)
|
| 101 |
+
role_sent = True
|
| 102 |
+
yield _make_chunk(
|
| 103 |
+
request_id, model, delta={"content": event.get("delta", "")}
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# --- Tool call events ---
|
| 107 |
+
elif event_type == "tool-input-start":
|
| 108 |
+
tc_id = event.get("toolCallId", "")
|
| 109 |
+
tool_name = event.get("toolName", "")
|
| 110 |
+
idx = next_tool_index
|
| 111 |
+
tool_calls_index[tc_id] = idx
|
| 112 |
+
next_tool_index += 1
|
| 113 |
+
|
| 114 |
+
delta: dict = {"tool_calls": [{
|
| 115 |
+
"index": idx,
|
| 116 |
+
"id": tc_id,
|
| 117 |
+
"type": "function",
|
| 118 |
+
"function": {"name": tool_name, "arguments": ""},
|
| 119 |
+
}]}
|
| 120 |
+
if not role_sent:
|
| 121 |
+
delta["role"] = "assistant"
|
| 122 |
+
role_sent = True
|
| 123 |
+
yield _make_chunk(request_id, model, delta=delta)
|
| 124 |
+
|
| 125 |
+
elif event_type == "tool-input-delta":
|
| 126 |
+
tc_id = event.get("toolCallId", "")
|
| 127 |
+
idx = tool_calls_index.get(tc_id, 0)
|
| 128 |
+
yield _make_chunk(
|
| 129 |
+
request_id, model,
|
| 130 |
+
delta={"tool_calls": [{
|
| 131 |
+
"index": idx,
|
| 132 |
+
"function": {"arguments": event.get("inputTextDelta", "")},
|
| 133 |
+
}]},
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
# tool-input-available — full args ready; nothing extra needed for
|
| 137 |
+
# streaming (client already accumulated deltas), but we can skip it.
|
| 138 |
+
|
| 139 |
+
# --- Finish events ---
|
| 140 |
+
elif event_type == "finish":
|
| 141 |
+
finish_reason = event.get("finishReason", "stop")
|
| 142 |
+
if finish_reason == "tool-calls":
|
| 143 |
+
finish_reason = "tool_calls"
|
| 144 |
+
usage = _extract_usage(event)
|
| 145 |
+
yield _make_chunk(
|
| 146 |
+
request_id, model,
|
| 147 |
+
delta={},
|
| 148 |
+
finish_reason=finish_reason,
|
| 149 |
+
usage=usage,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
yield "data: [DONE]\n\n"
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# ---------------------------------------------------------------------------
|
| 156 |
+
# Non-streaming helpers
|
| 157 |
+
# ---------------------------------------------------------------------------
|
| 158 |
+
|
| 159 |
+
def parse_full_response(lines: list[str]) -> tuple[str, list[dict], str, dict | None]:
|
| 160 |
+
"""Parse all SSE lines into (content, tool_calls, finish_reason, usage)."""
|
| 161 |
+
content_parts: list[str] = []
|
| 162 |
+
tool_calls: list[dict] = []
|
| 163 |
+
# Accumulate args per toolCallId
|
| 164 |
+
tool_args: dict[str, list[str]] = {}
|
| 165 |
+
tool_meta: dict[str, dict] = {} # toolCallId → {name, id}
|
| 166 |
+
finish_reason = "stop"
|
| 167 |
+
usage = None
|
| 168 |
+
|
| 169 |
+
for raw_line in lines:
|
| 170 |
+
line = raw_line.rstrip("\r\n")
|
| 171 |
+
if not line or not line.startswith("data: ") or line == "data: [DONE]":
|
| 172 |
+
continue
|
| 173 |
+
try:
|
| 174 |
+
event = json.loads(line[6:])
|
| 175 |
+
except json.JSONDecodeError:
|
| 176 |
+
continue
|
| 177 |
+
|
| 178 |
+
etype = event.get("type")
|
| 179 |
+
|
| 180 |
+
if etype == "text-delta":
|
| 181 |
+
content_parts.append(event.get("delta", ""))
|
| 182 |
+
|
| 183 |
+
elif etype == "tool-input-start":
|
| 184 |
+
tc_id = event.get("toolCallId", "")
|
| 185 |
+
tool_args[tc_id] = []
|
| 186 |
+
tool_meta[tc_id] = {
|
| 187 |
+
"name": event.get("toolName", ""),
|
| 188 |
+
"id": tc_id,
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
elif etype == "tool-input-delta":
|
| 192 |
+
tc_id = event.get("toolCallId", "")
|
| 193 |
+
tool_args.setdefault(tc_id, []).append(event.get("inputTextDelta", ""))
|
| 194 |
+
|
| 195 |
+
elif etype == "tool-input-available":
|
| 196 |
+
tc_id = event.get("toolCallId", "")
|
| 197 |
+
meta = tool_meta.get(tc_id, {"name": event.get("toolName", ""), "id": tc_id})
|
| 198 |
+
tool_calls.append({
|
| 199 |
+
"id": meta["id"],
|
| 200 |
+
"type": "function",
|
| 201 |
+
"function": {
|
| 202 |
+
"name": meta["name"],
|
| 203 |
+
"arguments": json.dumps(event.get("input", {}), ensure_ascii=False),
|
| 204 |
+
},
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
elif etype == "finish":
|
| 208 |
+
finish_reason = event.get("finishReason", "stop")
|
| 209 |
+
if finish_reason == "tool-calls":
|
| 210 |
+
finish_reason = "tool_calls"
|
| 211 |
+
usage = _extract_usage(event)
|
| 212 |
+
|
| 213 |
+
# If we got tool-input-start/delta but no tool-input-available, build from deltas
|
| 214 |
+
for tc_id, meta in tool_meta.items():
|
| 215 |
+
if not any(tc.get("id") == tc_id for tc in tool_calls):
|
| 216 |
+
tool_calls.append({
|
| 217 |
+
"id": meta["id"],
|
| 218 |
+
"type": "function",
|
| 219 |
+
"function": {
|
| 220 |
+
"name": meta["name"],
|
| 221 |
+
"arguments": "".join(tool_args.get(tc_id, [])),
|
| 222 |
+
},
|
| 223 |
+
})
|
| 224 |
+
|
| 225 |
+
return "".join(content_parts), tool_calls, finish_reason, usage
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def build_non_stream_response(
|
| 229 |
+
request_id: str,
|
| 230 |
+
model: str,
|
| 231 |
+
content: str,
|
| 232 |
+
finish_reason: str = "stop",
|
| 233 |
+
usage: dict | None = None,
|
| 234 |
+
tool_calls: list[dict] | None = None,
|
| 235 |
+
) -> dict:
|
| 236 |
+
"""Build a non-streaming chat.completions response object."""
|
| 237 |
+
message: dict = {"role": "assistant", "content": content or None}
|
| 238 |
+
if tool_calls:
|
| 239 |
+
message["tool_calls"] = tool_calls
|
| 240 |
+
if not content:
|
| 241 |
+
message["content"] = None
|
| 242 |
+
resp: dict = {
|
| 243 |
+
"id": request_id,
|
| 244 |
+
"object": "chat.completion",
|
| 245 |
+
"created": int(time.time()),
|
| 246 |
+
"model": model,
|
| 247 |
+
"choices": [
|
| 248 |
+
{
|
| 249 |
+
"index": 0,
|
| 250 |
+
"message": message,
|
| 251 |
+
"finish_reason": finish_reason,
|
| 252 |
+
}
|
| 253 |
+
],
|
| 254 |
+
"usage": usage or {
|
| 255 |
+
"prompt_tokens": 0,
|
| 256 |
+
"completion_tokens": 0,
|
| 257 |
+
"total_tokens": 0,
|
| 258 |
+
},
|
| 259 |
+
}
|
| 260 |
+
return resp
|
api/provider.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Call the assistant-ui upstream LLM endpoint."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import AsyncIterator
|
| 6 |
+
|
| 7 |
+
import httpx
|
| 8 |
+
|
| 9 |
+
from config import UPSTREAM_HEADERS, UPSTREAM_URL
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class UpstreamError(Exception):
|
| 13 |
+
def __init__(self, status_code: int, message: str):
|
| 14 |
+
self.status_code = status_code
|
| 15 |
+
super().__init__(message)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
async def call_upstream(payload: dict) -> AsyncIterator[str]:
|
| 19 |
+
"""POST to assistant-ui and yield raw SSE lines as they arrive."""
|
| 20 |
+
async with httpx.AsyncClient(timeout=httpx.Timeout(120, connect=10)) as client:
|
| 21 |
+
async with client.stream(
|
| 22 |
+
"POST",
|
| 23 |
+
UPSTREAM_URL,
|
| 24 |
+
json=payload,
|
| 25 |
+
headers=UPSTREAM_HEADERS,
|
| 26 |
+
) as resp:
|
| 27 |
+
if resp.status_code == 429:
|
| 28 |
+
raise UpstreamError(429, "Rate limit exceeded (upstream: 5 req / 30s per IP)")
|
| 29 |
+
if resp.status_code >= 400:
|
| 30 |
+
body = await resp.aread()
|
| 31 |
+
raise UpstreamError(resp.status_code, body.decode(errors="replace")[:200])
|
| 32 |
+
async for line in resp.aiter_lines():
|
| 33 |
+
yield line
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
async def call_upstream_full(payload: dict) -> list[str]:
|
| 37 |
+
"""POST to assistant-ui and collect all SSE lines (for non-stream mode)."""
|
| 38 |
+
lines: list[str] = []
|
| 39 |
+
async for line in call_upstream(payload):
|
| 40 |
+
lines.append(line)
|
| 41 |
+
return lines
|
api/routes/__init__.py
ADDED
|
File without changes
|
api/routes/chat.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""POST /v1/chat/completions — OpenAI-compatible chat endpoint."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from typing import Any
|
| 6 |
+
|
| 7 |
+
from fastapi import APIRouter, Request
|
| 8 |
+
from fastapi.responses import JSONResponse, StreamingResponse
|
| 9 |
+
from pydantic import BaseModel
|
| 10 |
+
|
| 11 |
+
from api.converter.messages import openai_to_ai_sdk, _gen_id
|
| 12 |
+
from api.converter.stream import build_non_stream_response, convert_stream, parse_full_response
|
| 13 |
+
from api.provider import call_upstream, call_upstream_full
|
| 14 |
+
from config import DEFAULT_MODEL
|
| 15 |
+
|
| 16 |
+
router = APIRouter()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class FunctionDef(BaseModel):
|
| 20 |
+
name: str
|
| 21 |
+
description: str = ""
|
| 22 |
+
parameters: dict = {}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class ToolDef(BaseModel):
|
| 26 |
+
type: str = "function"
|
| 27 |
+
function: FunctionDef
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class ChatMessage(BaseModel):
|
| 31 |
+
role: str
|
| 32 |
+
content: Any = ""
|
| 33 |
+
tool_calls: list[dict] | None = None
|
| 34 |
+
tool_call_id: str | None = None
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class ChatRequest(BaseModel):
|
| 38 |
+
model: str = DEFAULT_MODEL
|
| 39 |
+
messages: list[ChatMessage]
|
| 40 |
+
stream: bool = False
|
| 41 |
+
tools: list[ToolDef] | None = None
|
| 42 |
+
tool_choice: Any = None
|
| 43 |
+
temperature: float | None = None
|
| 44 |
+
max_tokens: int | None = None
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@router.post("/v1/chat/completions")
|
| 48 |
+
async def chat_completions(body: ChatRequest, request: Request):
|
| 49 |
+
request_id = f"chatcmpl-{_gen_id('', 24)}"
|
| 50 |
+
model = body.model
|
| 51 |
+
|
| 52 |
+
tools_raw = [t.model_dump() for t in body.tools] if body.tools else None
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
payload = openai_to_ai_sdk(
|
| 56 |
+
[m.model_dump() for m in body.messages],
|
| 57 |
+
model,
|
| 58 |
+
tools=tools_raw,
|
| 59 |
+
)
|
| 60 |
+
except Exception as e:
|
| 61 |
+
return JSONResponse(
|
| 62 |
+
status_code=400,
|
| 63 |
+
content=_error_body(f"Invalid request: {e}", "invalid_request_error"),
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
if body.stream:
|
| 67 |
+
return StreamingResponse(
|
| 68 |
+
_stream_generator(payload, model, request_id),
|
| 69 |
+
media_type="text/event-stream",
|
| 70 |
+
headers={
|
| 71 |
+
"Cache-Control": "no-cache",
|
| 72 |
+
"X-Accel-Buffering": "no",
|
| 73 |
+
},
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# Non-streaming
|
| 77 |
+
try:
|
| 78 |
+
lines = await call_upstream_full(payload)
|
| 79 |
+
except Exception as e:
|
| 80 |
+
return JSONResponse(
|
| 81 |
+
status_code=502,
|
| 82 |
+
content=_error_body(f"Upstream error: {e}", "upstream_error"),
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
content, tool_calls, finish_reason, usage = parse_full_response(lines)
|
| 86 |
+
return build_non_stream_response(
|
| 87 |
+
request_id, model, content, finish_reason, usage,
|
| 88 |
+
tool_calls=tool_calls or None,
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
async def _stream_generator(payload: dict, model: str, request_id: str):
|
| 93 |
+
try:
|
| 94 |
+
upstream = call_upstream(payload)
|
| 95 |
+
async for chunk in convert_stream(upstream, model, request_id):
|
| 96 |
+
yield chunk
|
| 97 |
+
except Exception as e:
|
| 98 |
+
error_chunk = {
|
| 99 |
+
"id": request_id,
|
| 100 |
+
"object": "chat.completion.chunk",
|
| 101 |
+
"choices": [{"index": 0, "delta": {}, "finish_reason": "error"}],
|
| 102 |
+
"error": {"message": str(e), "type": "upstream_error"},
|
| 103 |
+
}
|
| 104 |
+
import json
|
| 105 |
+
yield f"data: {json.dumps(error_chunk)}\n\n"
|
| 106 |
+
yield "data: [DONE]\n\n"
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _error_body(message: str, error_type: str) -> dict:
|
| 110 |
+
return {"error": {"message": message, "type": error_type}}
|
api/routes/models.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GET /v1/models — list available models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
from fastapi import APIRouter
|
| 8 |
+
|
| 9 |
+
from config import MODEL_MAP
|
| 10 |
+
|
| 11 |
+
router = APIRouter()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@router.get("/v1/models")
|
| 15 |
+
async def list_models() -> dict:
|
| 16 |
+
now = int(time.time())
|
| 17 |
+
data = [
|
| 18 |
+
{
|
| 19 |
+
"id": short_name,
|
| 20 |
+
"object": "model",
|
| 21 |
+
"created": now,
|
| 22 |
+
"owned_by": full_name.split("/")[0],
|
| 23 |
+
}
|
| 24 |
+
for short_name, full_name in MODEL_MAP.items()
|
| 25 |
+
]
|
| 26 |
+
return {"object": "list", "data": data}
|
api/server.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FastAPI application — assistant-ui 2API service."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from fastapi import FastAPI, Request
|
| 6 |
+
from fastapi.responses import JSONResponse
|
| 7 |
+
|
| 8 |
+
from api.routes.chat import router as chat_router
|
| 9 |
+
from api.routes.models import router as models_router
|
| 10 |
+
from config import API_KEY
|
| 11 |
+
|
| 12 |
+
app = FastAPI(title="assistant-ui 2API", version="1.0.0")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# ---------- Auth middleware ----------
|
| 16 |
+
@app.middleware("http")
|
| 17 |
+
async def auth_middleware(request: Request, call_next):
|
| 18 |
+
# Skip auth for health / docs
|
| 19 |
+
if request.url.path in ("/", "/health", "/docs", "/openapi.json"):
|
| 20 |
+
return await call_next(request)
|
| 21 |
+
|
| 22 |
+
if API_KEY:
|
| 23 |
+
auth = request.headers.get("authorization", "")
|
| 24 |
+
token = auth.removeprefix("Bearer ").strip()
|
| 25 |
+
if token != API_KEY:
|
| 26 |
+
return JSONResponse(
|
| 27 |
+
status_code=401,
|
| 28 |
+
content={"error": {"message": "Invalid API key", "type": "auth_error"}},
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
return await call_next(request)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# ---------- Routes ----------
|
| 35 |
+
app.include_router(chat_router)
|
| 36 |
+
app.include_router(models_router)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@app.get("/")
|
| 40 |
+
async def root():
|
| 41 |
+
return {"status": "ok", "service": "assistant-ui 2API"}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@app.get("/health")
|
| 45 |
+
async def health():
|
| 46 |
+
return {"status": "ok"}
|
config.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
|
| 4 |
+
load_dotenv()
|
| 5 |
+
|
| 6 |
+
API_HOST = os.getenv("API_HOST", "0.0.0.0")
|
| 7 |
+
API_PORT = int(os.getenv("API_PORT", "8080"))
|
| 8 |
+
API_KEY = os.getenv("API_KEY", "sk-assistant-2api-free")
|
| 9 |
+
|
| 10 |
+
# /api/chat — clean endpoint, no hardcoded system prompt
|
| 11 |
+
# /api/doc/chat — docs assistant with hardcoded persona + tools (avoid)
|
| 12 |
+
UPSTREAM_URL = os.getenv(
|
| 13 |
+
"UPSTREAM_URL", "https://www.assistant-ui.com/api/chat"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# OpenAI-compatible name → assistant-ui API identifier
|
| 17 |
+
# Note: claude-sonnet-4.6 is disabled server-side (falls back to gpt-5.4)
|
| 18 |
+
MODEL_MAP: dict[str, str] = {
|
| 19 |
+
"gpt-5.4": "openai/gpt-5.4",
|
| 20 |
+
"gpt-5-nano": "openai/gpt-5-nano",
|
| 21 |
+
"gemini-3-flash": "google/gemini-3-flash",
|
| 22 |
+
"kimi-k2.5": "moonshotai/kimi-k2.5",
|
| 23 |
+
"deepseek-v3.2": "deepseek/deepseek-v3.2",
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
DEFAULT_MODEL = "gpt-5.4"
|
| 27 |
+
|
| 28 |
+
UPSTREAM_HEADERS: dict[str, str] = {
|
| 29 |
+
"content-type": "application/json",
|
| 30 |
+
"user-agent": "ai-sdk/6.0.116 runtime/browser",
|
| 31 |
+
"origin": "https://www.assistant-ui.com",
|
| 32 |
+
"referer": "https://www.assistant-ui.com/docs",
|
| 33 |
+
}
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
assistant-2api:
|
| 3 |
+
image: zhigengniao/assistant-2api:latest
|
| 4 |
+
ports:
|
| 5 |
+
- "${API_PORT:-8080}:8080"
|
| 6 |
+
env_file:
|
| 7 |
+
- .env
|
| 8 |
+
restart: unless-stopped
|
main.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uvicorn
|
| 2 |
+
|
| 3 |
+
from config import API_HOST, API_PORT
|
| 4 |
+
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
uvicorn.run("api.server:app", host=API_HOST, port=API_PORT)
|
pyproject.toml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "assistant"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
requires-python = ">=3.13"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"fastapi>=0.135.1",
|
| 8 |
+
"httpx>=0.28.1",
|
| 9 |
+
"nanoid>=2.0.0",
|
| 10 |
+
"openai>=2.26.0",
|
| 11 |
+
"pydantic>=2.12.5",
|
| 12 |
+
"python-dotenv>=1.2.2",
|
| 13 |
+
"uvicorn>=0.41.0",
|
| 14 |
+
]
|
uv.lock
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
revision = 3
|
| 3 |
+
requires-python = ">=3.13"
|
| 4 |
+
|
| 5 |
+
[[package]]
|
| 6 |
+
name = "annotated-doc"
|
| 7 |
+
version = "0.0.4"
|
| 8 |
+
source = { registry = "https://pypi.org/simple" }
|
| 9 |
+
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
| 10 |
+
wheels = [
|
| 11 |
+
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
[[package]]
|
| 15 |
+
name = "annotated-types"
|
| 16 |
+
version = "0.7.0"
|
| 17 |
+
source = { registry = "https://pypi.org/simple" }
|
| 18 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
| 19 |
+
wheels = [
|
| 20 |
+
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
[[package]]
|
| 24 |
+
name = "anyio"
|
| 25 |
+
version = "4.12.1"
|
| 26 |
+
source = { registry = "https://pypi.org/simple" }
|
| 27 |
+
dependencies = [
|
| 28 |
+
{ name = "idna" },
|
| 29 |
+
]
|
| 30 |
+
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
| 31 |
+
wheels = [
|
| 32 |
+
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
[[package]]
|
| 36 |
+
name = "assistant"
|
| 37 |
+
version = "0.1.0"
|
| 38 |
+
source = { virtual = "." }
|
| 39 |
+
dependencies = [
|
| 40 |
+
{ name = "fastapi" },
|
| 41 |
+
{ name = "httpx" },
|
| 42 |
+
{ name = "nanoid" },
|
| 43 |
+
{ name = "openai" },
|
| 44 |
+
{ name = "pydantic" },
|
| 45 |
+
{ name = "python-dotenv" },
|
| 46 |
+
{ name = "uvicorn" },
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
[package.metadata]
|
| 50 |
+
requires-dist = [
|
| 51 |
+
{ name = "fastapi", specifier = ">=0.135.1" },
|
| 52 |
+
{ name = "httpx", specifier = ">=0.28.1" },
|
| 53 |
+
{ name = "nanoid", specifier = ">=2.0.0" },
|
| 54 |
+
{ name = "openai", specifier = ">=2.26.0" },
|
| 55 |
+
{ name = "pydantic", specifier = ">=2.12.5" },
|
| 56 |
+
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
| 57 |
+
{ name = "uvicorn", specifier = ">=0.41.0" },
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
[[package]]
|
| 61 |
+
name = "certifi"
|
| 62 |
+
version = "2026.2.25"
|
| 63 |
+
source = { registry = "https://pypi.org/simple" }
|
| 64 |
+
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
| 65 |
+
wheels = [
|
| 66 |
+
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
[[package]]
|
| 70 |
+
name = "click"
|
| 71 |
+
version = "8.3.1"
|
| 72 |
+
source = { registry = "https://pypi.org/simple" }
|
| 73 |
+
dependencies = [
|
| 74 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 75 |
+
]
|
| 76 |
+
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
| 77 |
+
wheels = [
|
| 78 |
+
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
[[package]]
|
| 82 |
+
name = "colorama"
|
| 83 |
+
version = "0.4.6"
|
| 84 |
+
source = { registry = "https://pypi.org/simple" }
|
| 85 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
| 86 |
+
wheels = [
|
| 87 |
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
[[package]]
|
| 91 |
+
name = "distro"
|
| 92 |
+
version = "1.9.0"
|
| 93 |
+
source = { registry = "https://pypi.org/simple" }
|
| 94 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
|
| 95 |
+
wheels = [
|
| 96 |
+
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
[[package]]
|
| 100 |
+
name = "fastapi"
|
| 101 |
+
version = "0.135.1"
|
| 102 |
+
source = { registry = "https://pypi.org/simple" }
|
| 103 |
+
dependencies = [
|
| 104 |
+
{ name = "annotated-doc" },
|
| 105 |
+
{ name = "pydantic" },
|
| 106 |
+
{ name = "starlette" },
|
| 107 |
+
{ name = "typing-extensions" },
|
| 108 |
+
{ name = "typing-inspection" },
|
| 109 |
+
]
|
| 110 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/7b/f8e0211e9380f7195ba3f3d40c292594fd81ba8ec4629e3854c353aaca45/fastapi-0.135.1.tar.gz", hash = "sha256:d04115b508d936d254cea545b7312ecaa58a7b3a0f84952535b4c9afae7668cd", size = 394962, upload-time = "2026-03-01T18:18:29.369Z" }
|
| 111 |
+
wheels = [
|
| 112 |
+
{ url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" },
|
| 113 |
+
]
|
| 114 |
+
|
| 115 |
+
[[package]]
|
| 116 |
+
name = "h11"
|
| 117 |
+
version = "0.16.0"
|
| 118 |
+
source = { registry = "https://pypi.org/simple" }
|
| 119 |
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
| 120 |
+
wheels = [
|
| 121 |
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
| 122 |
+
]
|
| 123 |
+
|
| 124 |
+
[[package]]
|
| 125 |
+
name = "httpcore"
|
| 126 |
+
version = "1.0.9"
|
| 127 |
+
source = { registry = "https://pypi.org/simple" }
|
| 128 |
+
dependencies = [
|
| 129 |
+
{ name = "certifi" },
|
| 130 |
+
{ name = "h11" },
|
| 131 |
+
]
|
| 132 |
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
| 133 |
+
wheels = [
|
| 134 |
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
| 135 |
+
]
|
| 136 |
+
|
| 137 |
+
[[package]]
|
| 138 |
+
name = "httpx"
|
| 139 |
+
version = "0.28.1"
|
| 140 |
+
source = { registry = "https://pypi.org/simple" }
|
| 141 |
+
dependencies = [
|
| 142 |
+
{ name = "anyio" },
|
| 143 |
+
{ name = "certifi" },
|
| 144 |
+
{ name = "httpcore" },
|
| 145 |
+
{ name = "idna" },
|
| 146 |
+
]
|
| 147 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
| 148 |
+
wheels = [
|
| 149 |
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
| 150 |
+
]
|
| 151 |
+
|
| 152 |
+
[[package]]
|
| 153 |
+
name = "idna"
|
| 154 |
+
version = "3.11"
|
| 155 |
+
source = { registry = "https://pypi.org/simple" }
|
| 156 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
| 157 |
+
wheels = [
|
| 158 |
+
{ 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" },
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
[[package]]
|
| 162 |
+
name = "jiter"
|
| 163 |
+
version = "0.13.0"
|
| 164 |
+
source = { registry = "https://pypi.org/simple" }
|
| 165 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" }
|
| 166 |
+
wheels = [
|
| 167 |
+
{ url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" },
|
| 168 |
+
{ url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" },
|
| 169 |
+
{ url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" },
|
| 170 |
+
{ url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" },
|
| 171 |
+
{ url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" },
|
| 172 |
+
{ url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" },
|
| 173 |
+
{ url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" },
|
| 174 |
+
{ url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" },
|
| 175 |
+
{ url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" },
|
| 176 |
+
{ url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" },
|
| 177 |
+
{ url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" },
|
| 178 |
+
{ url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" },
|
| 179 |
+
{ url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" },
|
| 180 |
+
{ url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" },
|
| 181 |
+
{ url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" },
|
| 182 |
+
{ url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" },
|
| 183 |
+
{ url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" },
|
| 184 |
+
{ url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" },
|
| 185 |
+
{ url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" },
|
| 186 |
+
{ url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" },
|
| 187 |
+
{ url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" },
|
| 188 |
+
{ url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" },
|
| 189 |
+
{ url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" },
|
| 190 |
+
{ url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" },
|
| 191 |
+
{ url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" },
|
| 192 |
+
{ url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" },
|
| 193 |
+
{ url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" },
|
| 194 |
+
{ url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" },
|
| 195 |
+
{ url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" },
|
| 196 |
+
{ url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" },
|
| 197 |
+
{ url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" },
|
| 198 |
+
{ url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" },
|
| 199 |
+
{ url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" },
|
| 200 |
+
{ url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" },
|
| 201 |
+
{ url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" },
|
| 202 |
+
{ url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" },
|
| 203 |
+
{ url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" },
|
| 204 |
+
{ url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" },
|
| 205 |
+
{ url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" },
|
| 206 |
+
{ url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" },
|
| 207 |
+
{ url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" },
|
| 208 |
+
{ url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" },
|
| 209 |
+
{ url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" },
|
| 210 |
+
]
|
| 211 |
+
|
| 212 |
+
[[package]]
|
| 213 |
+
name = "nanoid"
|
| 214 |
+
version = "2.0.0"
|
| 215 |
+
source = { registry = "https://pypi.org/simple" }
|
| 216 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290, upload-time = "2018-11-20T14:45:51.578Z" }
|
| 217 |
+
wheels = [
|
| 218 |
+
{ url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844, upload-time = "2018-11-20T14:45:50.165Z" },
|
| 219 |
+
]
|
| 220 |
+
|
| 221 |
+
[[package]]
|
| 222 |
+
name = "openai"
|
| 223 |
+
version = "2.26.0"
|
| 224 |
+
source = { registry = "https://pypi.org/simple" }
|
| 225 |
+
dependencies = [
|
| 226 |
+
{ name = "anyio" },
|
| 227 |
+
{ name = "distro" },
|
| 228 |
+
{ name = "httpx" },
|
| 229 |
+
{ name = "jiter" },
|
| 230 |
+
{ name = "pydantic" },
|
| 231 |
+
{ name = "sniffio" },
|
| 232 |
+
{ name = "tqdm" },
|
| 233 |
+
{ name = "typing-extensions" },
|
| 234 |
+
]
|
| 235 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/91/2a06c4e9597c338cac1e5e5a8dd6f29e1836fc229c4c523529dca387fda8/openai-2.26.0.tar.gz", hash = "sha256:b41f37c140ae0034a6e92b0c509376d907f3a66109935fba2c1b471a7c05a8fb", size = 666702, upload-time = "2026-03-05T23:17:35.874Z" }
|
| 236 |
+
wheels = [
|
| 237 |
+
{ url = "https://files.pythonhosted.org/packages/c6/2e/3f73e8ca53718952222cacd0cf7eecc9db439d020f0c1fe7ae717e4e199a/openai-2.26.0-py3-none-any.whl", hash = "sha256:6151bf8f83802f036117f06cc8a57b3a4da60da9926826cc96747888b57f394f", size = 1136409, upload-time = "2026-03-05T23:17:34.072Z" },
|
| 238 |
+
]
|
| 239 |
+
|
| 240 |
+
[[package]]
|
| 241 |
+
name = "pydantic"
|
| 242 |
+
version = "2.12.5"
|
| 243 |
+
source = { registry = "https://pypi.org/simple" }
|
| 244 |
+
dependencies = [
|
| 245 |
+
{ name = "annotated-types" },
|
| 246 |
+
{ name = "pydantic-core" },
|
| 247 |
+
{ name = "typing-extensions" },
|
| 248 |
+
{ name = "typing-inspection" },
|
| 249 |
+
]
|
| 250 |
+
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
| 251 |
+
wheels = [
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
| 253 |
+
]
|
| 254 |
+
|
| 255 |
+
[[package]]
|
| 256 |
+
name = "pydantic-core"
|
| 257 |
+
version = "2.41.5"
|
| 258 |
+
source = { registry = "https://pypi.org/simple" }
|
| 259 |
+
dependencies = [
|
| 260 |
+
{ name = "typing-extensions" },
|
| 261 |
+
]
|
| 262 |
+
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
| 263 |
+
wheels = [
|
| 264 |
+
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
| 266 |
+
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
| 267 |
+
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
| 268 |
+
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
| 269 |
+
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
| 271 |
+
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
| 273 |
+
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
| 281 |
+
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
| 282 |
+
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
| 283 |
+
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
| 284 |
+
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
| 285 |
+
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
| 286 |
+
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
| 287 |
+
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
| 288 |
+
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
| 289 |
+
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
| 290 |
+
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
| 291 |
+
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
| 292 |
+
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
| 293 |
+
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
| 294 |
+
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
| 295 |
+
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
| 296 |
+
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
| 297 |
+
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
| 298 |
+
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
| 299 |
+
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
| 300 |
+
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
| 301 |
+
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
| 302 |
+
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
| 303 |
+
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
| 304 |
+
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
| 305 |
+
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
| 306 |
+
]
|
| 307 |
+
|
| 308 |
+
[[package]]
|
| 309 |
+
name = "python-dotenv"
|
| 310 |
+
version = "1.2.2"
|
| 311 |
+
source = { registry = "https://pypi.org/simple" }
|
| 312 |
+
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
| 313 |
+
wheels = [
|
| 314 |
+
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
| 315 |
+
]
|
| 316 |
+
|
| 317 |
+
[[package]]
|
| 318 |
+
name = "sniffio"
|
| 319 |
+
version = "1.3.1"
|
| 320 |
+
source = { registry = "https://pypi.org/simple" }
|
| 321 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
| 322 |
+
wheels = [
|
| 323 |
+
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
| 324 |
+
]
|
| 325 |
+
|
| 326 |
+
[[package]]
|
| 327 |
+
name = "starlette"
|
| 328 |
+
version = "0.52.1"
|
| 329 |
+
source = { registry = "https://pypi.org/simple" }
|
| 330 |
+
dependencies = [
|
| 331 |
+
{ name = "anyio" },
|
| 332 |
+
]
|
| 333 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" }
|
| 334 |
+
wheels = [
|
| 335 |
+
{ url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" },
|
| 336 |
+
]
|
| 337 |
+
|
| 338 |
+
[[package]]
|
| 339 |
+
name = "tqdm"
|
| 340 |
+
version = "4.67.3"
|
| 341 |
+
source = { registry = "https://pypi.org/simple" }
|
| 342 |
+
dependencies = [
|
| 343 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 344 |
+
]
|
| 345 |
+
sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
|
| 346 |
+
wheels = [
|
| 347 |
+
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
|
| 348 |
+
]
|
| 349 |
+
|
| 350 |
+
[[package]]
|
| 351 |
+
name = "typing-extensions"
|
| 352 |
+
version = "4.15.0"
|
| 353 |
+
source = { registry = "https://pypi.org/simple" }
|
| 354 |
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
| 355 |
+
wheels = [
|
| 356 |
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
| 357 |
+
]
|
| 358 |
+
|
| 359 |
+
[[package]]
|
| 360 |
+
name = "typing-inspection"
|
| 361 |
+
version = "0.4.2"
|
| 362 |
+
source = { registry = "https://pypi.org/simple" }
|
| 363 |
+
dependencies = [
|
| 364 |
+
{ name = "typing-extensions" },
|
| 365 |
+
]
|
| 366 |
+
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
| 367 |
+
wheels = [
|
| 368 |
+
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
| 369 |
+
]
|
| 370 |
+
|
| 371 |
+
[[package]]
|
| 372 |
+
name = "uvicorn"
|
| 373 |
+
version = "0.41.0"
|
| 374 |
+
source = { registry = "https://pypi.org/simple" }
|
| 375 |
+
dependencies = [
|
| 376 |
+
{ name = "click" },
|
| 377 |
+
{ name = "h11" },
|
| 378 |
+
]
|
| 379 |
+
sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" }
|
| 380 |
+
wheels = [
|
| 381 |
+
{ url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" },
|
| 382 |
+
]
|