Merge remote-tracking branch 'origin/master' into codex/project-instruction-files

# Conflicts:
#	AGENTS.md
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md
#	docs/rfc/implemented/feature/2026-06-15-code-mode.md
#	docs/rfc/implemented/feature/2026-06-30-hook-bridges.md
#	docs/rfc/implemented/feature/2026-06-30-interception-seams.md
#	docs/rfc/implemented/feature/2026-07-08-repeat-tool-guard.md
#	docs/rfc/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md
#	examples/AGENTS.md
#	examples/acp-agent/cordis.yml
#	examples/acp-agent/tests/acp.snapshot.ts
#	examples/echo-agent/cordis.yml
#	examples/sandbox-acp-agent/cordis.yml
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-core/README.md
#	packages/core/agent-core/src/index.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/loop.ts
#	packages/core/agent-loop/tests/interception.spec.ts
#	packages/core/agent/src/types.ts
#	packages/core/tools/README.md
#	packages/core/tools/src/code-mode.ts
#	packages/core/tools/src/index.ts
#	packages/fs/fs-local/src/index.ts
#	packages/fs/fs/README.md
#	packages/fs/fs/src/index.ts
#	packages/guard/repeat-tool-guard/README.md
#	packages/guard/repeat-tool-guard/src/index.ts
#	packages/hooks/hooks-claude/src/index.ts
#	packages/hooks/hooks-codex/src/index.ts
#	packages/ui/acp-agent/src/index.ts
This commit is contained in:
Yichen Jiang
2026-07-14 19:50:25 +08:00
720 changed files with 21199 additions and 14129 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 441b335b9e850c221fbd6a657c7de539ceca070d
README.zh.md: 65134c0e856b933c793510c2ed528e97f940f47d

40
python/sdk/README.md Normal file
View File

@@ -0,0 +1,40 @@
# DeepSeek Harness Python SDK
English | [中文](README.zh.md)
Python subprocess SDK for driving DeepSeek Harness over JSON-RPC stdio. The
runtime inherits normal DeepSeek Harness environment variables such as
`DEEPSEEK_BASE_URL` and `DEEPSEEK_API_KEY`, so callers can use real model
endpoints directly or point those variables at a local proxy during
benchmark runs.
Installing `deepseek-harness` installs the exact same-version `deepseek-harness-runtime-bin` platform wheel. The normal entry point therefore needs no executable argument:
```py
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness() as harness:
result = harness.run("Say hi.")
```
`DeepSeekHarness` keeps its lazily started runtime subprocess for reuse across calls. Use it as a context manager, as above, or call `close()` explicitly when finished.
By default, the SDK launches the bundled single-file `dsh-jsonrpc-agent` executable from the `deepseek-harness-runtime-bin` package and injects that package's default configuration (the stdio JSON-RPC server, agent core, preloaded DeepSeek adapter, JSONL session persistence, local bash) via `DSH_CORDIS_CONFIG`. To run a plugin composition of your own, keep the `@deepseek-ai/dsh-jsonrpc` entry in the config and pass the Cordis config path.
```py
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
model="deepseek-v4-flash",
cordis="examples/dsbench-coding-agent/cordis.yml",
) as harness:
result = harness.run("Make the requested code change.")
```
`TurnResult.final_response` is the text content from the last
`assistant/message` event in the turn. Use `TurnResult.events` for the complete
event stream, including intermediate assistant messages and tool activity.
The same behavior can be selected for the runtime subprocess with `DSH_CORDIS_CONFIG`. The injection lives in `HarnessClient.start()`, so the low-level client's default launch gets it too: when the launch resolves to the bundled runtime and neither `cordis` nor a non-empty `DSH_CORDIS_CONFIG` is set (the runtime treats an empty value as absent, and so does the injection check), the bundled default configuration is used; an explicit `runtime_bin`, `bridge_bin`, or `launch_args_override` disables the injection entirely. See the [sdk-runtime README](../sdk-runtime/README.md) for the runtime carriers (production exe vs dev-only node closure) and how to obtain them.
`cwd` and `runtime_cwd` are resolved to absolute paths before subprocess launch, environment injection, and the wire handshake. The public API exposes only applied options: deployment persona and persistence belong in `cordis.yml`, while `session_root` remains the high-level convenience that sets `DSH_SESSION_ROOT`.

34
python/sdk/README.zh.md Normal file
View File

@@ -0,0 +1,34 @@
# DeepSeek Harness Python SDK
[English](README.md) | 中文
通过 JSON-RPC stdio 驱动 DeepSeek Harness 的 Python 子进程 SDK。运行时继承常规的 DeepSeek Harness 环境变量(如 `DEEPSEEK_BASE_URL``DEEPSEEK_API_KEY`),调用方可以直接用真实模型端点,也可以在跑基准测试时把它们指向本地代理。
安装 `deepseek-harness` 会同时安装版本完全相同的 `deepseek-harness-runtime-bin` 平台 wheel 包。因此常规入口不需要传可执行文件参数:
```py
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness() as harness:
result = harness.run("Say hi.")
```
`DeepSeekHarness` 会保留延迟启动的运行时子进程,以供多次调用复用。请像上例一样将其用作上下文管理器,或在用完后显式调用 `close()`
默认情况下SDK 启动 `deepseek-harness-runtime-bin` 包内置的单文件 `dsh-jsonrpc-agent` 可执行程序,并通过 `DSH_CORDIS_CONFIG` 注入该包的默认配置stdio JSON-RPC 服务器、`agent-core`、预载的 DeepSeek 适配器、JSONL 会话持久化、本地 bash。要运行自己的插件组合请在配置里保留 `@deepseek-ai/dsh-jsonrpc` 条目,并传入 Cordis 配置路径。
```py
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
model="deepseek-v4-flash",
cordis="examples/dsbench-coding-agent/cordis.yml",
) as harness:
result = harness.run("Make the requested code change.")
```
`TurnResult.final_response` 是本轮次最后一个 `assistant/message` 事件的文本内容。完整的事件流(包括中间的助手消息与工具活动)用 `TurnResult.events` 获取。
同样的行为也可以通过 `DSH_CORDIS_CONFIG` 为运行时子进程选定。注入逻辑位于 `HarnessClient.start()`,因此底层客户端的默认启动也具有此行为:当启动解析到内置运行时,且 `cordis` 与非空的 `DSH_CORDIS_CONFIG` 均未设置时(运行时把空值视为缺省,注入检查与之一致),使用内置的默认配置;显式给出 `runtime_bin``bridge_bin``launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](../sdk-runtime/README.md)。
`cwd``runtime_cwd` 会在启动子进程、注入环境变量和协议握手前解析为绝对路径。公开 API 只暴露真正生效的选项:部署的角色设定与持久化配置归 `cordis.yml` 管理,而 `session_root` 继续作为设置 `DSH_SESSION_ROOT` 的高层便捷选项。

30
python/sdk/pyproject.toml Normal file
View File

@@ -0,0 +1,30 @@
[build-system]
requires = ["hatchling>=1.24.0"]
build-backend = "hatchling.build"
[project]
name = "deepseek-harness"
version = "0.0.0.dev0"
description = "Python SDK for DeepSeek Harness"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "BSD-3-Clause" }
dependencies = [
"pydantic>=2.12",
"deepseek-harness-runtime-bin==0.0.0.dev0",
]
[dependency-groups]
test = ["pytest>=8.0"]
[tool.pytest.ini_options]
addopts = "-q"
testpaths = ["tests"]
[tool.hatch.build.targets.wheel]
packages = ["src/deepseek_harness"]
# Editable installs see runtime executables injected after installation instead
# of freezing a wheel snapshot.
[tool.uv.sources]
deepseek-harness-runtime-bin = { path = "../sdk-runtime", editable = true }

View File

@@ -0,0 +1,17 @@
from .api import DeepSeekHarness, DeepSeekHarnessConfig, Session, TurnResult
from .client import HarnessClient, HarnessConfig
from .models import IncomingRequest, InitializeResponse, JsonObject, Notification, ServerInfo
__all__ = [
"DeepSeekHarness",
"DeepSeekHarnessConfig",
"Session",
"TurnResult",
"HarnessClient",
"HarnessConfig",
"IncomingRequest",
"InitializeResponse",
"JsonObject",
"Notification",
"ServerInfo",
]

View File

@@ -0,0 +1,195 @@
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable
from .client import HarnessClient, HarnessConfig
from .models import JsonObject, Notification
@dataclass(slots=True)
class DeepSeekHarnessConfig:
"""Configuration for launching the local DeepSeek Harness SDK runtime.
The runtime inherits the caller's environment by default, so existing
DEEPSEEK_API_KEY and DEEPSEEK_BASE_URL settings keep working. Use ``env`` to
intentionally override or inject variables for a subprocess.
"""
model: str = "deepseek-v4-flash"
cwd: str | None = None
runtime_cwd: str | None = None
session_root: str | None = None
cordis: str | None = None
env: dict[str, str] = field(default_factory=dict)
runtime_bin: str | None = None
launch_args_override: tuple[str, ...] | None = None
request_timeout_seconds: float | None = None
shutdown_timeout_seconds: float | None = 1.0
base_url: str | None = None
api_key: str | None = None
@dataclass(slots=True)
class TurnResult:
session_id: str
status: str
final_response: str
events: list[JsonObject]
notifications: list[Notification]
session_root: str | None = None
class DeepSeekHarness:
"""Reusable synchronous SDK for running DeepSeek Harness agent turns.
The runtime subprocess starts lazily and remains owned by this instance
across calls to :meth:`run`. Use the instance as a context manager, or call
:meth:`close` explicitly when finished, so the subprocess is always reaped.
"""
def __init__(self, config: DeepSeekHarnessConfig | None = None, **kwargs: object) -> None:
if config is not None and kwargs:
raise TypeError("pass either DeepSeekHarnessConfig or keyword options, not both")
self.config = config or DeepSeekHarnessConfig(**kwargs)
cwd = str(Path(self.config.cwd or Path.cwd()).resolve())
runtime_cwd = str(Path(self.config.runtime_cwd).resolve()) if self.config.runtime_cwd is not None else cwd
self._cwd = cwd
env = dict(self.config.env)
if self.config.session_root is not None:
env["DSH_SESSION_ROOT"] = self.config.session_root
if self.config.cordis is not None:
env["DSH_CORDIS_CONFIG"] = self.config.cordis
env["DSH_CWD"] = cwd
if self.config.base_url is not None:
env["DEEPSEEK_BASE_URL"] = self.config.base_url
if self.config.api_key is not None:
env["DEEPSEEK_API_KEY"] = self.config.api_key
self._client = HarnessClient(
HarnessConfig(
runtime_bin=self.config.runtime_bin,
launch_args_override=self.config.launch_args_override,
cwd=runtime_cwd,
env=env,
request_timeout_seconds=self.config.request_timeout_seconds,
shutdown_timeout_seconds=self.config.shutdown_timeout_seconds,
)
)
self._initialized = False
def __enter__(self) -> "DeepSeekHarness":
self.start()
return self
def __exit__(self, _exc_type, _exc, _tb) -> None:
self.close()
@property
def client(self) -> HarnessClient:
return self._client
def start(self) -> None:
if self._initialized:
return
self._client.start()
self._client.initialize(
cwd=self._cwd,
model=self.config.model,
)
self._initialized = True
def close(self) -> None:
self._client.close()
self._initialized = False
def start_session(self, session_id: str | None = None) -> "Session":
self.start()
return Session(self, session_id or f"session-{uuid.uuid4().hex}")
def run(
self,
input: str | list[JsonObject],
*,
session_id: str | None = None,
on_notification: Callable[[Notification], None] | None = None,
) -> TurnResult:
return self.start_session(session_id).run(input, on_notification=on_notification)
class Session:
def __init__(self, harness: DeepSeekHarness, session_id: str) -> None:
self.harness = harness
self.id = session_id
def run(
self,
input: str | list[JsonObject],
*,
on_notification: Callable[[Notification], None] | None = None,
) -> TurnResult:
content_blocks = normalize_input(input)
notifications: list[Notification] = []
events: list[JsonObject] = []
status = "error"
finished = False
def collect(notification: Notification) -> None:
nonlocal finished, status
notifications.append(notification)
if on_notification is not None:
on_notification(notification)
if notification.method == "session.event":
event = notification.payload.get("event")
if isinstance(event, dict):
events.append(event)
if notification.method == "session.finished" and notification.payload.get("sessionId") == self.id:
status = str(notification.payload.get("status") or "ok")
finished = True
with self.harness.client.subscribe_session_notifications(self.id) as subscription:
self.harness.client.session_prompt(
self.id,
content_blocks,
on_notification=collect,
notification_subscription=subscription,
)
while not finished:
notification = subscription.next()
collect(notification)
return TurnResult(
session_id=self.id,
status=status,
final_response=final_response(events),
events=events,
notifications=notifications,
session_root=self.harness.config.session_root,
)
def normalize_input(input: str | list[JsonObject]) -> list[JsonObject]:
if isinstance(input, str):
return [{"type": "text", "text": input}]
return input
def final_response(events: list[JsonObject]) -> str:
for event in reversed(events):
if event.get("type") != "assistant/message":
continue
data = event.get("data")
if not isinstance(data, dict):
continue
content = data.get("content")
if not isinstance(content, list):
continue
parts: list[str] = []
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
parts.append(str(block.get("text") or ""))
return "".join(parts)
return ""

View File

@@ -0,0 +1,503 @@
from __future__ import annotations
import json
import os
import queue
import subprocess
import threading
import time
import uuid
from collections import deque
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Literal, TypeAlias, TypeVar
from pydantic import BaseModel
from .errors import JsonRpcError, TransportClosedError
from .models import IncomingRequest, InitializeResponse, JsonObject, JsonValue, Notification
ModelT = TypeVar("ModelT", bound=BaseModel)
NotificationFilter: TypeAlias = Callable[[Notification], bool]
@dataclass(slots=True)
class HarnessConfig:
"""Configuration for launching the local DeepSeek Harness SDK runtime."""
runtime_bin: str | None = None
bridge_bin: str | None = None
launch_args_override: tuple[str, ...] | None = None
cwd: str | None = None
env: dict[str, str] | None = None
request_timeout_seconds: float | None = None
shutdown_timeout_seconds: float | None = 1.0
class HarnessClient:
"""Synchronous JSON-RPC client for the DeepSeek Harness SDK runtime over stdio."""
def __init__(self, config: HarnessConfig | None = None) -> None:
self.config = config or HarnessConfig()
self._proc: subprocess.Popen[str] | None = None
self._lock = threading.Lock()
self._write_lock = threading.Lock()
self._responses: dict[str, queue.Queue[JsonValue | BaseException]] = {}
self._notifications: queue.Queue[Notification | BaseException] = queue.Queue()
self._notification_subscribers: dict[
str, tuple[queue.Queue[Notification | BaseException], NotificationFilter | None]
] = {}
self._requests: queue.Queue[IncomingRequest | BaseException] = queue.Queue()
self._stderr_lines: deque[str] = deque(maxlen=400)
self._reader_thread: threading.Thread | None = None
self._stderr_thread: threading.Thread | None = None
def __enter__(self) -> "HarnessClient":
self.start()
return self
def __exit__(self, _exc_type, _exc, _tb) -> None:
self.close()
def start(self) -> None:
if self._proc is not None:
return
args = list(self.config.launch_args_override or self._default_launch_args())
env = os.environ.copy()
if self.config.env:
env.update(self.config.env)
self._inject_bundled_default_config(env)
self._proc = subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
cwd=None if self.config.cwd is None else str(Path(self.config.cwd).resolve()),
env=env,
bufsize=1,
)
self._start_reader_thread()
self._start_stderr_thread()
def close(self) -> None:
proc = self._proc
if proc is None:
return
try:
self.request("shutdown", None, response_model=_ShutdownResponse, timeout_seconds=self.config.shutdown_timeout_seconds)
except Exception as exc:
self._stderr_lines.append(f"shutdown request failed: {exc}")
if proc.stdin:
try:
proc.stdin.close()
except Exception as exc:
self._stderr_lines.append(f"stdin close failed: {exc}")
if proc.poll() is None:
try:
proc.terminate()
except ProcessLookupError:
pass
try:
proc.wait(timeout=self.config.shutdown_timeout_seconds)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
self._proc = None
self._fail_waiters(self._runtime_closed_error("DeepSeek Harness runtime closed"))
if self._reader_thread and self._reader_thread.is_alive():
self._reader_thread.join(timeout=0.5)
if self._stderr_thread and self._stderr_thread.is_alive():
self._stderr_thread.join(timeout=0.5)
def initialize(
self,
*,
cwd: str,
model: str,
) -> InitializeResponse:
payload: JsonObject = {
"cwd": str(Path(cwd).resolve()),
"model": model,
}
try:
return self.request("initialize", payload, response_model=InitializeResponse)
except BaseException:
self.close()
raise
def session_prompt(
self,
session_id: str,
content_blocks: list[JsonObject],
*,
on_notification: Callable[[Notification], None] | None = None,
notification_subscription: "NotificationSubscription | None" = None,
) -> None:
payload: JsonObject = {"sessionId": session_id, "contentBlocks": content_blocks}
self.request(
"session/prompt",
payload,
response_model=_SessionPromptResponse,
on_notification=on_notification,
notification_filter=_notification_belongs_to_session(session_id),
notification_subscription=notification_subscription,
)
def request(
self,
method: str,
params: JsonObject | None,
*,
response_model: type[ModelT],
timeout_seconds: float | None = None,
on_notification: Callable[[Notification], None] | None = None,
notification_filter: NotificationFilter | None = None,
notification_subscription: "NotificationSubscription | None" = None,
) -> ModelT:
result = self._request_raw(
method,
params,
timeout_seconds=timeout_seconds,
on_notification=on_notification,
notification_filter=notification_filter,
notification_subscription=notification_subscription,
)
if not isinstance(result, dict):
raise TypeError(f"{method} response must be a JSON object")
return response_model.model_validate(result)
def notify(self, method: str, params: JsonObject | None = None) -> None:
message: JsonObject = {"jsonrpc": "2.0", "method": method}
if params is not None:
message["params"] = params
self._write_message(message)
def next_notification(self) -> Notification:
item = self._notifications.get()
if isinstance(item, BaseException):
raise item
return item
def subscribe_notifications(
self,
notification_filter: NotificationFilter | None = None,
) -> "NotificationSubscription":
subscription_id = str(uuid.uuid4())
notifications: queue.Queue[Notification | BaseException] = queue.Queue()
with self._lock:
self._notification_subscribers[subscription_id] = (notifications, notification_filter)
return NotificationSubscription(self, subscription_id, notifications)
def subscribe_session_notifications(self, session_id: str) -> "NotificationSubscription":
return self.subscribe_notifications(_notification_belongs_to_session(session_id))
def next_request(self) -> IncomingRequest:
item = self._requests.get()
if isinstance(item, BaseException):
raise item
return item
def respond(self, request_id: str | int, result: JsonValue) -> None:
self._write_message({"jsonrpc": "2.0", "id": request_id, "result": result})
def respond_error(
self,
request_id: str | int,
*,
code: int,
message: str,
data: JsonValue | None = None,
) -> None:
error: JsonObject = {"code": code, "message": message}
if data is not None:
error["data"] = data
self._write_message({"jsonrpc": "2.0", "id": request_id, "error": error})
def _request_raw(
self,
method: str,
params: JsonObject | None = None,
*,
timeout_seconds: float | None = None,
on_notification: Callable[[Notification], None] | None = None,
notification_filter: NotificationFilter | None = None,
notification_subscription: "NotificationSubscription | None" = None,
) -> JsonValue:
request_id = str(uuid.uuid4())
waiter: queue.Queue[JsonValue | BaseException] = queue.Queue(maxsize=1)
temp_subscription: NotificationSubscription | None = None
subscription = notification_subscription
with self._lock:
self._responses[request_id] = waiter
if on_notification is not None and subscription is None:
temp_subscription = self.subscribe_notifications(notification_filter)
subscription = temp_subscription
try:
message: JsonObject = {"jsonrpc": "2.0", "id": request_id, "method": method}
if params is not None:
message["params"] = params
self._write_message(message)
except BaseException:
with self._lock:
self._responses.pop(request_id, None)
if temp_subscription is not None:
temp_subscription.close()
raise
timeout = self.config.request_timeout_seconds if timeout_seconds is None else timeout_seconds
deadline = None if timeout is None else time.monotonic() + timeout
try:
while True:
if on_notification is not None and subscription is not None:
subscription.drain(on_notification)
wait_timeout = None
if on_notification is not None:
wait_timeout = 0.05
if deadline is not None:
remaining = deadline - time.monotonic()
if remaining <= 0:
with self._lock:
self._responses.pop(request_id, None)
raise TimeoutError(f"{method} timed out waiting for DeepSeek Harness runtime")
wait_timeout = remaining if wait_timeout is None else min(wait_timeout, remaining)
try:
item = waiter.get(timeout=wait_timeout)
if on_notification is not None and subscription is not None:
subscription.drain(on_notification)
break
except queue.Empty:
continue
except BaseException:
with self._lock:
self._responses.pop(request_id, None)
if temp_subscription is not None:
temp_subscription.close()
raise
finally:
if temp_subscription is not None:
temp_subscription.close()
if isinstance(item, BaseException):
raise item
return item
def _write_message(self, message: JsonObject) -> None:
proc = self._proc
if proc is None or proc.stdin is None:
raise TransportClosedError("DeepSeek Harness runtime is not running")
try:
payload = json.dumps(message, separators=(",", ":")) + "\n"
with self._write_lock:
proc.stdin.write(payload)
proc.stdin.flush()
except Exception as exc:
raise self._runtime_closed_error("Failed to write to DeepSeek Harness runtime") from exc
def _start_reader_thread(self) -> None:
self._reader_thread = threading.Thread(target=self._reader_loop, name="dsh-runtime-reader", daemon=True)
self._reader_thread.start()
def _start_stderr_thread(self) -> None:
self._stderr_thread = threading.Thread(target=self._stderr_loop, name="dsh-runtime-stderr", daemon=True)
self._stderr_thread.start()
def _reader_loop(self) -> None:
proc = self._proc
if proc is None or proc.stdout is None:
return
try:
for line in proc.stdout:
if not line.strip():
continue
try:
message = json.loads(line)
except json.JSONDecodeError:
continue
self._handle_message(message)
except BaseException as exc:
self._fail_waiters(exc)
finally:
self._fail_waiters(self._runtime_closed_error("DeepSeek Harness runtime stdout closed"))
def _stderr_loop(self) -> None:
proc = self._proc
if proc is None or proc.stderr is None:
return
for line in proc.stderr:
self._stderr_lines.append(line.rstrip())
def _handle_message(self, message: object) -> None:
if not isinstance(message, dict):
return
msg_id = message.get("id")
method = message.get("method")
if isinstance(msg_id, (str, int)) and isinstance(method, str):
params = message.get("params")
self._requests.put(IncomingRequest(id=msg_id, method=method, payload=params if isinstance(params, dict) else {}))
return
if isinstance(msg_id, (str, int)):
with self._lock:
waiter = self._responses.pop(str(msg_id), None)
if waiter is None:
return
if isinstance(message.get("error"), dict):
err = message["error"]
waiter.put(JsonRpcError(_int_or_none(err.get("code")), str(err.get("message", "JSON-RPC error")), err.get("data")))
else:
waiter.put(message.get("result"))
return
if isinstance(method, str):
params = message.get("params")
notification = Notification(method=method, payload=params if isinstance(params, dict) else {})
with self._lock:
subscribers = list(self._notification_subscribers.items())
delivered = False
for subscription_id, (subscriber, predicate) in subscribers:
try:
matches = predicate is None or predicate(notification)
except BaseException as exc:
with self._lock:
current = self._notification_subscribers.get(subscription_id)
if current is not None and current[0] is subscriber:
self._notification_subscribers.pop(subscription_id, None)
subscriber.put(exc)
continue
if matches:
subscriber.put(notification)
delivered = True
if not delivered:
self._notifications.put(notification)
def _fail_waiters(self, exc: BaseException) -> None:
with self._lock:
waiters = list(self._responses.values())
self._responses.clear()
subscribers = list(self._notification_subscribers.values())
self._notification_subscribers.clear()
for waiter in waiters:
waiter.put(exc)
for subscriber, _predicate in subscribers:
subscriber.put(exc)
self._notifications.put(exc)
self._requests.put(exc)
def _runtime_closed_error(self, reason: str) -> TransportClosedError:
proc = self._proc
if (
proc is not None
and proc.poll() is not None
and self._stderr_thread is not None
and self._stderr_thread.is_alive()
and threading.current_thread() is not self._stderr_thread
):
self._stderr_thread.join(timeout=0.1)
parts = [reason]
if proc is not None:
exit_code = proc.poll()
if exit_code is not None:
parts.append(f"exit code: {exit_code}")
if self._stderr_lines:
parts.append("stderr tail:\n" + "\n".join(self._stderr_lines))
return TransportClosedError("\n".join(parts))
def _default_launch_args(self) -> tuple[str, ...]:
if self.config.runtime_bin is not None:
return (self.config.runtime_bin,)
if self.config.bridge_bin is not None:
return (self.config.bridge_bin,)
try:
from deepseek_harness_runtime import resolve_bundled_launch_args
except ImportError as exc:
raise FileNotFoundError(
"Unable to locate the bundled DeepSeek Harness SDK runtime. "
"Install deepseek-harness-runtime-bin or set HarnessConfig.runtime_bin."
) from exc
return resolve_bundled_launch_args()
def _inject_bundled_default_config(self, env: dict[str, str]) -> None:
"""Inject the default config for a bundled launch with no non-empty config.
Both bundled carriers require an explicit config. Explicit runtime,
launch-argument, and config channels remain untouched.
"""
uses_bundled_runtime = (
self.config.launch_args_override is None
and self.config.runtime_bin is None
and self.config.bridge_bin is None
)
if not uses_bundled_runtime or env.get("DSH_CORDIS_CONFIG"):
return
# _default_launch_args already imported the package or raised its install error.
from deepseek_harness_runtime import bundled_default_config_path
env["DSH_CORDIS_CONFIG"] = str(bundled_default_config_path())
def _unsubscribe_notifications(self, subscription_id: str) -> None:
with self._lock:
self._notification_subscribers.pop(subscription_id, None)
class NotificationSubscription:
def __init__(
self,
client: HarnessClient,
subscription_id: str,
notifications: queue.Queue[Notification | BaseException],
) -> None:
self._client = client
self._subscription_id = subscription_id
self._notifications = notifications
self._closed = False
def __enter__(self) -> "NotificationSubscription":
return self
def __exit__(self, _exc_type, _exc, _tb) -> None:
self.close()
def close(self) -> None:
if self._closed:
return
self._closed = True
self._client._unsubscribe_notifications(self._subscription_id)
def next(self) -> Notification:
item = self._notifications.get()
if isinstance(item, BaseException):
raise item
return item
def drain(self, on_notification: Callable[[Notification], None]) -> None:
while True:
try:
item = self._notifications.get_nowait()
except queue.Empty:
return
if isinstance(item, BaseException):
raise item
on_notification(item)
class _SessionPromptResponse(BaseModel):
accepted: Literal[True]
class _ShutdownResponse(BaseModel):
pass
def _int_or_none(value: object) -> int | None:
return value if isinstance(value, int) else None
def _notification_belongs_to_session(session_id: str) -> NotificationFilter:
def belongs(notification: Notification) -> bool:
payload = notification.payload
return (
payload.get("sessionId") == session_id
or payload.get("parentSessionId") == session_id
or payload.get("childSessionId") == session_id
)
return belongs

View File

@@ -0,0 +1,19 @@
from __future__ import annotations
class HarnessError(Exception):
"""Base exception for SDK and runtime failures."""
class TransportClosedError(HarnessError):
"""Raised when the runtime subprocess exits or closes stdout."""
class JsonRpcError(HarnessError):
"""Raised when the runtime returns a JSON-RPC error response."""
def __init__(self, code: int | None, message: str, data: object | None = None) -> None:
super().__init__(message)
self.code = code
self.message = message
self.data = data

View File

@@ -0,0 +1,32 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TypeAlias
from pydantic import BaseModel
JsonScalar: TypeAlias = str | int | float | bool | None
JsonValue: TypeAlias = JsonScalar | dict[str, "JsonValue"] | list["JsonValue"]
JsonObject: TypeAlias = dict[str, JsonValue]
@dataclass(slots=True)
class Notification:
method: str
payload: JsonObject
@dataclass(slots=True)
class IncomingRequest:
id: str | int
method: str
payload: JsonObject
class ServerInfo(BaseModel):
name: str | None = None
version: str | None = None
class InitializeResponse(BaseModel):
serverInfo: ServerInfo | None = None

View File

@@ -0,0 +1,120 @@
"""Drive the repo-source JSON-RPC bin through the SDK and a keyless mock SSE server.
Requires ``pnpm install`` but no build. This manual test is not collected by
pytest; run ``python tests/manual_sdk_agent_smoke.py``.
"""
from __future__ import annotations
import argparse
import json
import shutil
import tempfile
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from deepseek_harness import DeepSeekHarness
from deepseek_harness_runtime import bundled_default_config_path
class MockCompletionHandler(BaseHTTPRequestHandler):
requests: list[dict[str, Any]] = []
def do_POST(self) -> None:
length = int(self.headers.get("content-length", "0"))
body = self.rfile.read(length).decode("utf-8")
self.requests.append({
"path": self.path,
"authorization": self.headers.get("authorization"),
"body": json.loads(body),
})
self.send_response(200)
self.send_header("content-type", "text/event-stream")
self.end_headers()
self.wfile.write(b'data: {"choices":[{"delta":{"role":"assistant","content":null,"reasoning_content":""}}]}\n\n')
self.wfile.write(b'data: {"choices":[{"delta":{"content":"SDK runtime reached the configured HTTP model endpoint."}}]}\n\n')
self.wfile.write(b'data: {"choices":[{"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":7,"completion_tokens":9}}\n\n')
self.wfile.write(b"data: [DONE]\n\n")
def log_message(self, _format: str, *_args: object) -> None:
return
def run_smoke(repo_root: Path, keep_sessions: bool) -> None:
session_root = Path(tempfile.mkdtemp(prefix="dsh-sdk-smoke-sessions-"))
runtime_entry = repo_root / "packages/ui/jsonrpc-agent/src/bin.ts"
server = ThreadingHTTPServer(("127.0.0.1", 0), MockCompletionHandler)
thread = threading.Thread(target=server.serve_forever, name="mock-openai-compatible-server", daemon=True)
thread.start()
base_url = f"http://127.0.0.1:{server.server_address[1]}"
print(f"repo_root={repo_root}")
print(f"session_root={session_root}")
print(f"mock_base_url={base_url}")
try:
with DeepSeekHarness(
model="sdk-smoke-model",
cwd=str(repo_root / "python/sdk"),
runtime_cwd=str(repo_root),
session_root=str(session_root),
cordis=str(bundled_default_config_path()),
launch_args_override=("node", "--import", "tsx", str(runtime_entry)),
env={
"DEEPSEEK_BASE_URL": base_url,
"DEEPSEEK_API_KEY": "sdk-smoke-key",
},
request_timeout_seconds=20,
shutdown_timeout_seconds=2,
) as harness:
result = harness.run(
"Please reply with a short confirmation and do not call tools.",
session_id="sdk-smoke-main",
)
print(f"turn_status={result.status}")
print(f"final_response={result.final_response}")
assert result.status == "ok", result
assert "configured HTTP model endpoint" in result.final_response
assert len(MockCompletionHandler.requests) == 1
request = MockCompletionHandler.requests[0]
print(json.dumps(request, ensure_ascii=False, indent=2)[:4000])
assert request["authorization"] == "Bearer sdk-smoke-key"
assert request["body"]["model"] == "sdk-smoke-model"
jsonl_files = sorted(session_root.rglob("*.jsonl"))
assert jsonl_files, f"no jsonl sessions were written under {session_root}"
print("session_jsonl_files:")
for path in jsonl_files:
print(f" {path} bytes={path.stat().st_size}")
with path.open("r", encoding="utf-8") as handle:
first_line = handle.readline().strip()
if first_line:
print(f" first_line={first_line[:500]}")
finally:
server.shutdown()
server.server_close()
if keep_sessions:
print(f"kept_session_root={session_root}")
else:
shutil.rmtree(session_root)
print("removed temporary session root")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--repo-root",
type=Path,
default=Path(__file__).resolve().parents[3],
help="Path to the deepseek-harness checkout.",
)
parser.add_argument("--keep-sessions", action="store_true")
args = parser.parse_args()
run_smoke(args.repo_root.resolve(), args.keep_sessions)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,116 @@
"""Keyless boot tests for the production exe and development node carrier.
Each carrier skips independently when absent. The dummy API key only satisfies
adapter loading; initialize and shutdown do not call a model.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig
from deepseek_harness.errors import TransportClosedError
from deepseek_harness_runtime import resolve_bundled_launch_args
_MODES = ("exe", "node")
# The config must include the JSON-RPC serving plugin.
_CORDIS_YML = """\
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
- id: agent-core
name: '@deepseek-ai/dsh-agent-core'
config:
workspaceContext: false
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: './sessions'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: '.'
- id: todo
name: '@deepseek-ai/dsh-tool-todo'
"""
def _launch_args(mode: str) -> tuple[str, ...]:
try:
return resolve_bundled_launch_args(mode)
except FileNotFoundError as exc:
pytest.skip(f"bundled {mode}-mode runtime unavailable on this machine: {exc}")
def _client(tmp_path: Path, launch_args: tuple[str, ...]) -> HarnessClient:
return HarnessClient(
HarnessConfig(
launch_args_override=launch_args,
cwd=str(tmp_path),
env={
"DSH_CORDIS_CONFIG": "./cordis.yml",
"DSH_SESSION_ROOT": str(tmp_path / "sessions"),
"DSH_CWD": str(tmp_path),
# The lazily mounted adapter requires a key even without a model call.
"DEEPSEEK_API_KEY": "sk-dummy-for-boot",
"DEEPSEEK_BASE_URL": "http://127.0.0.1:9",
},
request_timeout_seconds=120,
)
)
@pytest.mark.parametrize("mode", _MODES)
def test_bundled_runtime_boots_a_cordis_config(tmp_path: Path, mode: str) -> None:
launch_args = _launch_args(mode)
(tmp_path / "cordis.yml").write_text(_CORDIS_YML)
with _client(tmp_path, launch_args) as client:
init = client.initialize(cwd=str(tmp_path), model="deepseek-v4-pro")
assert init.serverInfo is not None
assert init.serverInfo.name == "deepseek-harness-sdk-runtime"
@pytest.mark.parametrize("mode", _MODES)
def test_bundled_runtime_surfaces_unbundled_plugin_failure(tmp_path: Path, mode: str) -> None:
launch_args = _launch_args(mode)
(tmp_path / "cordis.yml").write_text(
"- id: missing\n name: '@deepseek-ai/dsh-does-not-exist'\n"
)
client = _client(tmp_path, launch_args)
client.start()
try:
with pytest.raises((TransportClosedError, TimeoutError)) as excinfo:
client.initialize(cwd=str(tmp_path), model="deepseek-v4-pro")
finally:
client.close()
assert "@deepseek-ai/dsh-does-not-exist" in str(excinfo.value)
@pytest.mark.parametrize("mode", _MODES)
@pytest.mark.parametrize("ambient_config", [None, ""], ids=["unset", "empty-counts-as-absent"])
def test_zero_config_run_injects_bundled_default_cordis_config(
tmp_path: Path, mode: str, ambient_config: str | None, monkeypatch: pytest.MonkeyPatch
) -> None:
_launch_args(mode) # skip early when this carrier is unavailable
monkeypatch.setenv("DSH_RUNTIME_MODE", mode)
if ambient_config is None:
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
else:
monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
harness = DeepSeekHarness(
model="deepseek-v4-pro",
cwd=str(tmp_path),
session_root=str(tmp_path / "sessions"),
api_key="sk-dummy-for-boot",
base_url="http://127.0.0.1:9",
request_timeout_seconds=120,
)
with harness:
pass

View File

@@ -0,0 +1,765 @@
from __future__ import annotations
import json
import inspect
import sys
import threading
import time
from pathlib import Path
import pytest
from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig
def test_high_level_sdk_runs_turn_and_collects_final_response(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
env_dump = tmp_path / "env.json"
script.write_text(
"""
import json
import os
import sys
env_dump = os.environ["ENV_DUMP"]
json.dump({
"DEEPSEEK_API_KEY": os.environ.get("DEEPSEEK_API_KEY"),
"DEEPSEEK_BASE_URL": os.environ.get("DEEPSEEK_BASE_URL"),
"DSH_CWD": os.environ.get("DSH_CWD"),
"DSH_SESSION_ROOT": os.environ.get("DSH_SESSION_ROOT"),
"DSH_CORDIS_CONFIG": os.environ.get("DSH_CORDIS_CONFIG"),
}, open(env_dump, "w"))
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({
"jsonrpc": "2.0",
"method": "session.event",
"params": {
"sessionId": params["sessionId"],
"event": {
"type": "assistant/message",
"data": {"content": [{"type": "text", "text": "hello from runtime"}]},
},
},
}), flush=True)
print(json.dumps({
"jsonrpc": "2.0",
"method": "session.finished",
"params": {"sessionId": params["sessionId"], "status": "ok"},
}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with DeepSeekHarness(
model="deepseek-v4-flash",
cwd=str(tmp_path),
cordis=str(tmp_path / "cordis.yml"),
session_root=str(tmp_path / "sessions"),
launch_args_override=(sys.executable, str(script)),
env={
"ENV_DUMP": str(env_dump),
"DEEPSEEK_API_KEY": "env-key",
"DEEPSEEK_BASE_URL": "http://127.0.0.1:4321",
},
) as harness:
result = harness.run("say hello", session_id="main")
assert result.status == "ok"
assert result.final_response == "hello from runtime"
assert result.events[0]["type"] == "assistant/message"
dumped_env = json.loads(env_dump.read_text())
assert dumped_env["DEEPSEEK_API_KEY"] == "env-key"
assert dumped_env["DEEPSEEK_BASE_URL"] == "http://127.0.0.1:4321"
assert dumped_env["DSH_CWD"] == str(tmp_path)
assert dumped_env["DSH_SESSION_ROOT"] == str(tmp_path / "sessions")
assert dumped_env["DSH_CORDIS_CONFIG"] == str(tmp_path / "cordis.yml")
def test_session_run_invokes_notification_callback_before_returning(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "main", "childSessionId": "child"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "main", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
seen: list[str] = []
with DeepSeekHarness(
launch_args_override=(sys.executable, str(script)),
cwd=str(tmp_path),
) as harness:
session = harness.start_session("main")
result = session.run(
"spawn a helper",
on_notification=lambda notification: seen.append(notification.method),
)
assert result.status == "ok"
assert seen == ["subagent.started", "session.finished"]
def test_relative_cwd_is_absolute_in_process_environment_and_wire(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
script = tmp_path / "capture_cwd.py"
capture = tmp_path / "cwd.json"
script.write_text(
"""
import json
import os
import sys
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
json.dump({"process": os.getcwd(), "environment": os.environ.get("DSH_CWD"), "wire": msg["params"]["cwd"]}, open(os.environ["CAPTURE"], "w"))
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
monkeypatch.chdir(tmp_path)
with DeepSeekHarness(
cwd=".",
runtime_cwd=".",
launch_args_override=(sys.executable, str(script)),
env={"CAPTURE": str(capture)},
):
pass
expected = str(tmp_path.resolve())
assert json.loads(capture.read_text()) == {
"process": expected,
"environment": expected,
"wire": expected,
}
def test_session_run_includes_subagent_finished_for_parent_session(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "main", "childSessionId": "child"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": "main", "childSessionId": "child", "status": "ok", "stopReason": "completed"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "main", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with DeepSeekHarness(
launch_args_override=(sys.executable, str(script)),
cwd=str(tmp_path),
) as harness:
result = harness.run("spawn a helper", session_id="main")
assert result.status == "ok"
assert [notification.method for notification in result.notifications] == [
"subagent.started",
"subagent.finished",
"session.finished",
]
def test_session_run_ignores_notifications_for_other_sessions(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "other", "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "wrong session"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "other", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "right session"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": params["sessionId"], "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with DeepSeekHarness(
launch_args_override=(sys.executable, str(script)),
cwd=str(tmp_path),
) as harness:
result = harness.run("stay in your lane", session_id="main")
assert result.status == "ok"
assert result.final_response == "right session"
assert [notification.payload.get("sessionId") for notification in result.notifications] == ["main", "main"]
def test_high_level_session_run_does_not_accumulate_global_notifications(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "ok"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": params["sessionId"], "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
result = harness.run("one turn", session_id="main")
assert result.status == "ok"
assert harness.client._notifications.qsize() == 0
def test_session_run_waits_for_late_finished_without_replaying_stale_notifications(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
script.write_text(
"""
import json
import sys
import time
turn = 0
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
turn += 1
params = msg.get("params") or {}
session_id = params["sessionId"]
if turn == 1:
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": session_id, "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "first"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": session_id, "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
else:
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
time.sleep(0.05)
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": session_id, "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "second"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": session_id, "status": "ok"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
first = harness.run("first turn", session_id="main")
second = harness.run("second turn", session_id="main")
assert first.final_response == "first"
assert second.final_response == "second"
assert [notification.payload.get("sessionId") for notification in second.notifications] == ["main", "main"]
def test_client_starts_subprocess_sends_requests_and_routes_notifications(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({"jsonrpc": "2.0", "method": "llm/request", "params": {"requestId": "req-1", "sessionId": params["sessionId"], "model": "dsagent", "messages": []}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
init = client.initialize(cwd="/workspace", model="dsagent")
assert init.serverInfo.name == "fake-dsh"
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
notification = client.next_notification()
assert notification.method == "llm/request"
assert notification.payload["requestId"] == "req-1"
assert notification.payload["sessionId"] == "main"
def test_client_keeps_unmatched_notifications_available_globally_while_subscribed() -> None:
client = HarnessClient()
with client.subscribe_session_notifications("main"):
client._handle_message({
"jsonrpc": "2.0",
"method": "session.event",
"params": {"sessionId": "other", "event": {"type": "assistant/message"}},
})
assert client._notifications.qsize() == 1
notification = client._notifications.get_nowait()
assert not isinstance(notification, BaseException)
assert notification.method == "session.event"
assert notification.payload["sessionId"] == "other"
def test_client_contains_notification_filter_failure_to_its_subscription(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif method in {"emit-first", "emit-second"}:
print(json.dumps({"jsonrpc": "2.0", "method": "tick", "params": {"source": method}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
def broken_filter(_notification: object) -> bool:
raise RuntimeError("bad notification filter")
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
client.initialize(cwd="/workspace", model="dsagent")
with (
client.subscribe_notifications(broken_filter) as broken,
client.subscribe_notifications(lambda notification: notification.method == "tick") as healthy,
):
client.notify("emit-first")
with pytest.raises(RuntimeError, match="bad notification filter"):
broken.next()
assert healthy.next().payload == {"source": "emit-first"}
assert client._notifications.qsize() == 0
client.session_prompt("main", [{"type": "text", "text": "reader still works"}])
client.notify("emit-second")
assert healthy.next().payload == {"source": "emit-second"}
def test_client_rejects_unaccepted_session_prompt_response(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": False}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
client.initialize(cwd="/workspace", model="dsagent")
with pytest.raises(ValueError):
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
def test_client_routes_bridge_requests_and_sends_responses(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
method = msg.get("method")
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": "bridge-req-1", "method": "llm.request", "params": {"requestId": "req-1", "sessionId": "main", "model": "dsagent", "messages": []}}), flush=True)
elif "id" in msg and "method" not in msg:
print(json.dumps({"jsonrpc": "2.0", "method": "response/seen", "params": {"result": msg.get("result")}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
client.initialize(cwd="/workspace", model="dsagent")
request = client.next_request()
assert request.id == "bridge-req-1"
assert request.method == "llm.request"
assert request.payload["requestId"] == "req-1"
client.respond(request.id, {"content_blocks": [{"type": "text", "text": "done"}]})
notification = client.next_notification()
assert notification.method == "response/seen"
assert notification.payload["result"]["content_blocks"][0]["text"] == "done"
def test_client_ignores_non_json_stdout_lines(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import sys
print("node warning: experimental loader", flush=True)
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
init = client.initialize(cwd="/workspace", model="dsagent")
assert init.serverInfo.name == "fake-dsh"
def test_client_request_times_out_when_bridge_does_not_respond(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import time
time.sleep(60)
""".strip()
)
with HarnessClient(
HarnessConfig(
launch_args_override=(sys.executable, str(script)),
request_timeout_seconds=0.1,
)
) as client:
start = time.monotonic()
try:
client.initialize(cwd="/workspace", model="dsagent")
except TimeoutError:
assert time.monotonic() - start < 2
else:
raise AssertionError("initialize should time out")
def test_client_close_times_out_when_shutdown_does_not_respond(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import signal
import sys
import time
signal.signal(signal.SIGTERM, signal.SIG_IGN)
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif msg.get("method") == "shutdown":
time.sleep(60)
""".strip()
)
client = HarnessClient(
HarnessConfig(
launch_args_override=(sys.executable, str(script)),
shutdown_timeout_seconds=0.1,
)
)
client.start()
proc = client._proc
assert proc is not None
client.initialize(cwd="/workspace", model="dsagent")
start = time.monotonic()
client.close()
assert time.monotonic() - start < 2
assert proc.poll() is not None
assert client._proc is None
def test_initialize_failure_reaps_started_runtime(tmp_path: Path) -> None:
script = tmp_path / "rejecting_runtime.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "error": {"code": -32000, "message": "bad initialize"}}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
client.start()
proc = client._proc
assert proc is not None
with pytest.raises(Exception, match="bad initialize"):
client.initialize(cwd=".", model="dsagent")
assert proc.wait(timeout=1) is not None
assert client._proc is None
def test_public_signatures_omit_unsupported_wire_parameters() -> None:
from deepseek_harness import DeepSeekHarnessConfig, Session
assert "session_root" not in inspect.signature(HarnessClient.initialize).parameters
assert "system_prompt" not in inspect.signature(HarnessClient.initialize).parameters
assert "profile" not in inspect.signature(HarnessClient.session_prompt).parameters
assert "profile" not in inspect.signature(DeepSeekHarness.run).parameters
assert "profile" not in inspect.signature(Session.run).parameters
assert "system_prompt" not in DeepSeekHarnessConfig.__dataclass_fields__
assert "client_name" not in HarnessConfig.__dataclass_fields__
assert "client_version" not in HarnessConfig.__dataclass_fields__
def test_client_close_is_idempotent_before_and_after_start(tmp_path: Path) -> None:
HarnessClient().close()
script = tmp_path / "fake_bridge.py"
script.write_text(
"""
import json
import sys
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
client.start()
client.initialize(cwd="/workspace", model="dsagent")
client.close()
client.close()
def test_runtime_closed_error_includes_stderr_tail(tmp_path: Path) -> None:
script = tmp_path / "crashing_runtime.py"
script.write_text(
"""
import sys
print("fatal bridge exploded", file=sys.stderr, flush=True)
sys.exit(42)
""".strip()
)
with HarnessClient(
HarnessConfig(
launch_args_override=(sys.executable, str(script)),
request_timeout_seconds=2,
)
) as client:
with pytest.raises(Exception, match="fatal bridge exploded"):
client.initialize(cwd="/workspace", model="dsagent")
def test_client_serializes_concurrent_writes(tmp_path: Path) -> None:
script = tmp_path / "fake_bridge.py"
output = tmp_path / "seen.jsonl"
script.write_text(
"""
import json
import os
import sys
with open(os.environ["SEEN"], "w") as seen:
for line in sys.stdin:
seen.write(line)
seen.flush()
msg = json.loads(line)
if "id" in msg and msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif "id" in msg and msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
with HarnessClient(
HarnessConfig(
launch_args_override=(sys.executable, str(script)),
env={"SEEN": str(output)},
)
) as client:
client.initialize(cwd="/workspace", model="dsagent")
threads = [
threading.Thread(target=client.notify, args=(f"notice-{index}", {"index": index}))
for index in range(50)
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
for line in output.read_text().splitlines():
json.loads(line)
def _install_fake_bundled_runtime(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> Path:
"""Install a fake runtime package that records config and serves lifecycle calls.
Returns the fake bundled default config path.
"""
runtime = tmp_path / "dsh-jsonrpc-agent"
runtime.write_text(
"""#!/usr/bin/env python3
import json
import os
import sys
json.dump({"DSH_CORDIS_CONFIG": os.environ.get("DSH_CORDIS_CONFIG")}, open(os.environ["ENV_DUMP"], "w"))
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "bundled-runtime"}}}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
""".strip()
)
runtime.chmod(0o755)
default_config = tmp_path / "default-cordis.yml"
module_dir = tmp_path / "deepseek_harness_runtime"
module_dir.mkdir()
(module_dir / "__init__.py").write_text(
f"""
def resolve_bundled_launch_args(mode=None):
return ({str(runtime)!r},)
def bundled_default_config_path():
return {str(default_config)!r}
""".strip()
)
monkeypatch.syspath_prepend(str(tmp_path))
monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
return default_config
@pytest.mark.parametrize("ambient_config", [None, ""], ids=["unset", "empty-counts-as-absent"])
def test_client_default_launch_uses_bundled_runtime_and_injects_default_config(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ambient_config: str | None
) -> None:
env_dump = tmp_path / "env.json"
default_config = _install_fake_bundled_runtime(tmp_path, monkeypatch)
if ambient_config is None:
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
else:
monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
with HarnessClient(HarnessConfig(env={"ENV_DUMP": str(env_dump)})) as client:
init = client.initialize(cwd="/workspace", model="deepseek-v4-pro")
assert init.serverInfo.name == "bundled-runtime"
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == str(default_config)
def test_client_respects_explicit_config_over_bundled_default(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
env_dump = tmp_path / "env.json"
_install_fake_bundled_runtime(tmp_path, monkeypatch)
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
with HarnessClient(
HarnessConfig(env={"ENV_DUMP": str(env_dump), "DSH_CORDIS_CONFIG": "./explicit.yml"})
) as client:
client.initialize(cwd="/workspace", model="deepseek-v4-pro")
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == "./explicit.yml"
def test_client_reports_missing_bundled_runtime_dependency(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
monkeypatch.setattr(sys, "path", [])
with pytest.raises(FileNotFoundError, match="Install deepseek-harness-runtime-bin"):
HarnessClient().start()

View File

@@ -0,0 +1,39 @@
"""Tests for repository-owned Python release versions."""
from __future__ import annotations
import json
import runpy
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path(__file__).resolve().parents[3]
SCRIPT = ROOT / "scripts" / "build-python-release.py"
build_python_release = SimpleNamespace(**runpy.run_path(str(SCRIPT)))
def test_repository_version_matches_root_package_json() -> None:
expected = json.loads((ROOT / "package.json").read_text())["version"]
assert build_python_release.repository_version() == expected
def test_release_tag_is_optional_for_non_release_builds() -> None:
build_python_release.validate_release_tag(None, "1.2.3")
def test_release_tag_must_match_repository_version() -> None:
build_python_release.validate_release_tag("python-v1.2.3", "1.2.3")
with pytest.raises(ValueError, match="expected 'python-v1.2.3'"):
build_python_release.validate_release_tag("python-v1.2.4", "1.2.3")
def test_repository_version_rejects_non_stable_versions(tmp_path: Path) -> None:
(tmp_path / "package.json").write_text('{"version":"1.2.3-dev"}\n')
with pytest.raises(ValueError, match="must be stable X.Y.Z"):
build_python_release.repository_version(tmp_path)

View File

@@ -0,0 +1,38 @@
"""Keyless runtime-resolution tests; launch coverage lives in test_bundled_runtime.py."""
from __future__ import annotations
import pytest
from deepseek_harness_runtime import (
RUNTIME_MODE_ENV_VAR,
bundled_default_config_path,
bundled_package_dir,
resolve_bundled_launch_args,
)
def test_default_config_is_shipped_with_the_package() -> None:
path = bundled_default_config_path()
assert path == bundled_package_dir() / "runtime" / "cordis.yml"
assert "@deepseek-ai/dsh-agent-core" in path.read_text()
def test_unknown_explicit_mode_fails_loud() -> None:
with pytest.raises(ValueError, match="expected 'exe' or 'node'"):
resolve_bundled_launch_args("bogus")
def test_unknown_env_mode_fails_loud(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv(RUNTIME_MODE_ENV_VAR, "bogus")
with pytest.raises(ValueError, match="expected 'exe' or 'node'"):
resolve_bundled_launch_args()
def test_explicit_mode_wins_over_env_mode(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv(RUNTIME_MODE_ENV_VAR, "bogus")
try:
args = resolve_bundled_launch_args("exe")
except FileNotFoundError:
return # explicit 'exe' was honored; only the artifact is missing
assert args[0].endswith(("-x64", "-arm64"))

321
python/sdk/uv.lock generated Normal file
View File

@@ -0,0 +1,321 @@
version = 1
revision = 3
requires-python = ">=3.10"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
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" }
wheels = [
{ 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" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
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" }
wheels = [
{ 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" },
]
[[package]]
name = "deepseek-harness"
version = "0.0.0.dev0"
source = { editable = "." }
dependencies = [
{ name = "deepseek-harness-runtime-bin" },
{ name = "pydantic" },
]
[package.dev-dependencies]
test = [
{ name = "pytest" },
]
[package.metadata]
requires-dist = [
{ name = "deepseek-harness-runtime-bin", editable = "../sdk-runtime" },
{ name = "pydantic", specifier = ">=2.12" },
]
[package.metadata.requires-dev]
test = [{ name = "pytest", specifier = ">=8.0" }]
[[package]]
name = "deepseek-harness-runtime-bin"
version = "0.0.0.dev0"
source = { editable = "../sdk-runtime" }
[[package]]
name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "packaging"
version = "26.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pydantic"
version = "2.13.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
]
[[package]]
name = "pydantic-core"
version = "2.46.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" },
{ url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" },
{ url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" },
{ url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" },
{ url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" },
{ url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" },
{ url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" },
{ url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" },
{ url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" },
{ url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" },
{ url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" },
{ url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" },
{ url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" },
{ url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" },
{ url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
{ url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
{ url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
{ url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
{ url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
{ url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
{ url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
{ url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
{ url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
{ url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
{ url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
{ url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
{ url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
{ url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
{ url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
{ url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
{ url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
{ url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
{ url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
{ url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
{ url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
{ url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
{ url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
{ url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
{ url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
{ url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
{ url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
{ url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
{ url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
{ url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
{ url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
{ url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
{ url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
{ url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
{ url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
{ url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
{ url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
{ url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
{ url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
{ url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
{ url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
{ url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
{ url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
{ url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "pytest"
version = "9.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "tomli"
version = "2.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
{ url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
{ url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
{ url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
{ url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
{ url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
{ url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
{ url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
{ url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
{ url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
{ url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
{ url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
{ url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
{ url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
{ url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
{ url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
{ url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
{ url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
{ url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
{ url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
{ url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
{ url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
{ url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
{ url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
{ url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
{ url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
]
[[package]]
name = "typing-extensions"
version = "4.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
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" }
wheels = [
{ 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" },
]