From 005af00669375d5f8ea0d8bcbaed2ba419ff8d1a Mon Sep 17 00:00:00 2001 From: Luffy2208 Date: Wed, 20 May 2026 18:05:53 +0530 Subject: [PATCH 1/2] Add Moss E2B cookbook example --- README.md | 2 + examples/cookbook/e2b/.env.example | 17 + examples/cookbook/e2b/README.md | 155 +++++ examples/cookbook/e2b/code_agent.py | 346 +++++++++++ examples/cookbook/e2b/code_index.py | 158 +++++ examples/cookbook/e2b/pyproject.toml | 32 + .../e2b/sample_project/pyproject.toml | 5 + .../e2b/sample_project/src/ledger/__init__.py | 5 + .../e2b/sample_project/src/ledger/totals.py | 21 + .../e2b/sample_project/tests/test_totals.py | 22 + examples/cookbook/e2b/sandbox_runner.py | 254 ++++++++ examples/cookbook/e2b/test_integration.py | 224 +++++++ examples/cookbook/e2b/uv.lock | 575 ++++++++++++++++++ 13 files changed, 1816 insertions(+) create mode 100644 examples/cookbook/e2b/.env.example create mode 100644 examples/cookbook/e2b/README.md create mode 100644 examples/cookbook/e2b/code_agent.py create mode 100644 examples/cookbook/e2b/code_index.py create mode 100644 examples/cookbook/e2b/pyproject.toml create mode 100644 examples/cookbook/e2b/sample_project/pyproject.toml create mode 100644 examples/cookbook/e2b/sample_project/src/ledger/__init__.py create mode 100644 examples/cookbook/e2b/sample_project/src/ledger/totals.py create mode 100644 examples/cookbook/e2b/sample_project/tests/test_totals.py create mode 100644 examples/cookbook/e2b/sandbox_runner.py create mode 100644 examples/cookbook/e2b/test_integration.py create mode 100644 examples/cookbook/e2b/uv.lock diff --git a/README.md b/README.md index 07f89e2..6f82f01 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,7 @@ examples/ ├── autogen/ # AutoGen integration ├── mastra/ # Mastra retriever ├── pydantic-ai/ # Pydantic AI integration + ├── e2b/ # E2B sandbox example └── daytona/ # Daytona sandbox example apps/ @@ -220,6 +221,7 @@ Full API reference: [docs.moss.dev](https://docs.moss.dev). | [Haystack](https://github.com/deepset-ai/haystack) | Available | [`examples/cookbook/haystack/`](examples/cookbook/haystack/) | | [Mastra](https://mastra.ai) | Available | [`examples/cookbook/mastra/`](examples/cookbook/mastra/) | | [Pydantic AI](https://ai.pydantic.dev) | Available | [`examples/cookbook/pydantic-ai/`](examples/cookbook/pydantic-ai/) | +| [E2B](https://e2b.dev) | Available | [`examples/cookbook/e2b/`](examples/cookbook/e2b/) | | [Pipecat](https://github.com/pipecat-ai/pipecat) | Available | [`apps/pipecat-moss/`](apps/pipecat-moss/) | | [LiveKit](https://github.com/livekit/livekit) | Available | [`apps/livekit-moss-vercel/`](apps/livekit-moss-vercel/) | | [Vapi](https://vapi.ai) | Available | [`apps/vapi-moss/`](apps/vapi-moss/) | diff --git a/examples/cookbook/e2b/.env.example b/examples/cookbook/e2b/.env.example new file mode 100644 index 0000000..650759d --- /dev/null +++ b/examples/cookbook/e2b/.env.example @@ -0,0 +1,17 @@ +# Copy this file to .env and fill in your keys. +# cp .env.example .env + +# Moss - https://moss.dev -> project settings +MOSS_PROJECT_ID=your_moss_project_id +MOSS_PROJECT_KEY=your_moss_project_key + +# E2B - https://e2b.dev/dashboard -> API keys +E2B_API_KEY=your_e2b_api_key + +# Groq - https://console.groq.com/keys +GROQ_API_KEY=your_groq_api_key + +# Optional +# GROQ_MODEL=llama-3.3-70b-versatile +# Used with `uv run python code_agent.py --reuse-index` +# MOSS_INDEX_NAME=moss-e2b-code-agent diff --git a/examples/cookbook/e2b/README.md b/examples/cookbook/e2b/README.md new file mode 100644 index 0000000..2592a01 --- /dev/null +++ b/examples/cookbook/e2b/README.md @@ -0,0 +1,155 @@ +# Self-Healing Code Agent - Moss + E2B + +Use [Moss](https://moss.dev) semantic search to find relevant code, then validate a candidate fix inside an isolated [E2B](https://e2b.dev) cloud sandbox. + +The flow is intentionally small: + +1. Index a local codebase into Moss. +2. Search Moss with a bug report. +3. Ask a Groq-hosted LLM for a source-only patch. +4. Copy the project into E2B, run tests, apply the patch, and rerun tests. +5. Print the validated diff and clean up the sandbox. + +## Project Structure + +```text +e2b/ +|-- code_agent.py # entrypoint - Moss retrieval, Groq patching, E2B validation +|-- code_index.py # source scanner + Moss document builder +|-- sandbox_runner.py # E2B file/command helpers + patch parser +|-- sample_project/ # tiny broken Python project for the demo +|-- test_integration.py # mocked helper tests, no credentials required +|-- pyproject.toml +|-- .env.example # copy to .env and fill in keys +`-- .env # your keys, git-ignored +``` + +## Setup + +### 1. Install dependencies + +```bash +cd examples/cookbook/e2b +uv sync +``` + +For local tests and lint checks, include the dev extra: + +```bash +uv sync --extra dev +``` + +### 2. Set environment variables + +```bash +cp .env.example .env +# then open .env and fill in your keys +``` + +Fill in the keys: + +| Variable | Where to get it | +|---|---| +| `MOSS_PROJECT_ID` / `MOSS_PROJECT_KEY` | [moss.dev](https://moss.dev) project settings | +| `E2B_API_KEY` | [e2b.dev/dashboard](https://e2b.dev/dashboard) API keys | +| `GROQ_API_KEY` | [console.groq.com/keys](https://console.groq.com/keys) | +| `GROQ_MODEL` | Optional, defaults to `llama-3.3-70b-versatile` | + +## Usage + +Run the sample agent: + +```bash +uv run python code_agent.py +``` + +The bundled sample project has a tax calculation bug. The agent retrieves the relevant code and tests with Moss, asks Groq for a patch, then validates the patch in E2B: + +```text +Indexing 4 source files in Moss (index: e2b-code-agent-a1b2c3d4)... +Moss retrieved: + - src/ledger/totals.py + - tests/test_totals.py + +Creating E2B sandbox... +Baseline tests: failed (exit 1) +Patched tests: passed + +Validated patch diff: + diff --git a/src/ledger/totals.py b/src/ledger/totals.py + ... +``` + +Run it against your own project: + +```bash +uv run python code_agent.py \ + --project-root /path/to/repo \ + --issue "Users with annual plans are charged monthly tax twice" \ + --setup-command "python -m pip install -e . pytest" \ + --test-command "python -m pytest -q" +``` + +By default the demo creates a temporary Moss index and deletes it at the end so repeated runs do not consume your index quota. To keep one reusable index: + +```bash +uv run python code_agent.py --index-name moss-e2b-code-agent --reuse-index +``` + +The first run creates the index. Later runs load it. Recreate the index when your project changes substantially so Moss does not retrieve stale code. + +Keep the E2B sandbox for inspection: + +```bash +uv run python code_agent.py --keep-sandbox +``` + +## How Moss Is Used + +`code_index.py` turns source files into Moss `DocumentInfo` records with path, language, and Python symbol metadata. The document text includes the file path and source content, so a natural-language bug report can retrieve both implementation and tests. + +```python +DocumentInfo( + id="src/ledger/totals.py::a1b2c3d4e5f6", + text="Path: src/ledger/totals.py\n\n```text\n...\n```", + metadata={"path": "src/ledger/totals.py", "language": "python", "symbols": "format_total"}, +) +``` + +The agent calls `client.query(..., QueryOptions(top_k=6, alpha=0.75))`, formats the hits, and passes only that focused context to the LLM. + +## How E2B Is Used + +`sandbox_runner.py` uses E2B's Python SDK to create a disposable sandbox, write project files, and run shell commands: + +```python +from e2b import AsyncSandbox + +sandbox = await AsyncSandbox.create(timeout=600) +await sandbox.files.write("/home/user/app/src/ledger/totals.py", source) +result = await sandbox.commands.run("python -m pytest -q", cwd="/home/user/app") +await sandbox.kill() +``` + +Commands are wrapped so non-zero test exits become structured results instead of aborting the flow. Patch paths are validated before writing, and the agent refuses patches that target `tests/` or `test_*.py` files. + +## Run The Mocked Tests + +These tests cover the source scanner, patch parser, safe sandbox paths, and command-result normalization without Moss, Groq, or E2B credentials: + +```bash +uv run python test_integration.py +``` + +With the dev extra installed: + +```bash +uv run python -m pytest -q +uv run python -m ruff check . +``` + +## Adapting The Pattern + +- For AI code review, index the target branch, ask Moss for code related to the diff, then run the candidate change in E2B before posting review notes. +- For self-healing agents, keep the patch format as complete file replacements or switch to unified diffs if your agent already has a trusted patch applier. +- For larger repos, chunk files by function or class before creating Moss documents, and include test files in the index so retrieval can find validation targets too. diff --git a/examples/cookbook/e2b/code_agent.py b/examples/cookbook/e2b/code_agent.py new file mode 100644 index 0000000..fa133f2 --- /dev/null +++ b/examples/cookbook/e2b/code_agent.py @@ -0,0 +1,346 @@ +"""Self-healing code agent powered by Moss search and E2B validation.""" + +from __future__ import annotations + +import argparse +import asyncio +import os +import shlex +import sys +import textwrap +import uuid +from pathlib import Path +from typing import Any, List + +from code_index import build_code_documents, format_search_results, search_code +from sandbox_runner import ( + DEFAULT_SANDBOX_ROOT, + CommandOutput, + ProposedPatch, + apply_patch_files, + create_e2b_sandbox, + ensure_source_patch_only, + parse_patch_json, + run_sandbox_command, + run_tests, + sandbox_identifier, + write_project_to_sandbox, +) + +SAMPLE_PROJECT = Path(__file__).parent / "sample_project" +DEFAULT_REUSABLE_INDEX_NAME = "moss-e2b-code-agent" +DEFAULT_ISSUE = ( + "Checkout totals are too low when tax_rate is a decimal rate such as 0.0825. " + "Tax should be calculated from the subtotal, not added as a flat amount." +) + + +def _load_dotenv() -> None: + try: + from dotenv import load_dotenv + except ImportError: + return + load_dotenv() + + +def _require_env(names: List[str]) -> None: + missing = [name for name in names if not os.getenv(name)] + if missing: + joined = ", ".join(missing) + raise RuntimeError(f"Missing required environment variables: {joined}") + + +def _default_index_name() -> str: + return f"e2b-code-agent-{uuid.uuid4().hex[:8]}" + + +def _resolve_index_name(args: argparse.Namespace) -> str: + configured_name = args.index_name or os.getenv("MOSS_INDEX_NAME") + if configured_name: + return configured_name + if args.reuse_index: + return DEFAULT_REUSABLE_INDEX_NAME + return _default_index_name() + + +async def _create_moss_index(client: Any, project_root: Path, index_name: str) -> None: + documents = build_code_documents(project_root) + if not documents: + raise ValueError(f"No indexable source files found in {project_root}") + + print(f"Indexing {len(documents)} source files in Moss (index: {index_name})...") + result = await client.create_index(index_name, documents) + print(f"Index ready (job_id: {result.job_id})") + await client.load_index(index_name) + + +async def _prepare_moss_index( + project_root: Path, + index_name: str, + *, + reuse_index: bool, +) -> tuple[Any, bool]: + from moss import MossClient + + client = MossClient(os.environ["MOSS_PROJECT_ID"], os.environ["MOSS_PROJECT_KEY"]) + + if reuse_index: + try: + print(f"Loading existing Moss index '{index_name}'...") + await client.load_index(index_name) + print("Reusing Moss index.") + return client, False + except Exception: + print("Reusable Moss index not found; creating it...") + + await _create_moss_index(client, project_root, index_name) + return client, True + + +async def _propose_patch(issue: str, context: str, model: str) -> ProposedPatch: + from groq import AsyncGroq + + client = AsyncGroq() + system = ( + "You are a senior Python engineer fixing a small failing codebase. " + "Use the retrieved Moss context to make a minimal source-code fix. " + "Do not modify tests. Preserve existing formatting, imports, comments, " + "and quote style unless a change is required for the fix. Return only " + "valid JSON. Do not use markdown, triple-quoted strings, or raw " + "multiline JSON strings. Use this schema: " + '{"summary": "short summary", "files": [{"path": "relative/path.py", ' + '"content_lines": ["first line", "second line"]}]}. ' + "Each content_lines item must be exactly one source line without a " + "trailing newline." + ) + user = ( + f"Issue:\n{issue}\n\n" + "Relevant code retrieved by Moss:\n" + f"{context}\n\n" + "Return complete replacement contents for only the files that must change. " + "Keep the patch as small as possible. Represent file contents as " + "content_lines arrays." + ) + response = await client.chat.completions.create( + model=model, + response_format={"type": "json_object"}, + messages=[ + {"role": "system", "content": system}, + {"role": "user", "content": user}, + ], + ) + content = response.choices[0].message.content or "" + return parse_patch_json(content) + + +def _git_pathspec(files: List[Any]) -> str: + return " ".join(shlex.quote(patch_file.path) for patch_file in files) + + +def _print_command(label: str, output: CommandOutput) -> None: + status = "passed" if output.ok else f"failed (exit {output.exit_code})" + print(f"\n{label}: {status}") + if output.stdout.strip(): + print(textwrap.indent(output.stdout.strip(), " ")) + if output.stderr.strip(): + print(textwrap.indent(output.stderr.strip(), " ")) + + +async def run_code_repair(args: argparse.Namespace) -> bool: + _load_dotenv() + _require_env(["MOSS_PROJECT_ID", "MOSS_PROJECT_KEY", "E2B_API_KEY", "GROQ_API_KEY"]) + + project_root = Path(args.project_root).resolve() + index_name = _resolve_index_name(args) + issue = args.issue or DEFAULT_ISSUE + model = args.model or os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile") + + moss_client = None + sandbox = None + created_index = False + + try: + moss_client, created_index = await _prepare_moss_index( + project_root, + index_name, + reuse_index=args.reuse_index, + ) + + hits = await search_code( + moss_client, + index_name, + issue, + top_k=args.top_k, + alpha=args.alpha, + ) + context = format_search_results(hits) + paths = [getattr(hit, "metadata", {}).get("path", hit.id) for hit in hits] + print("\nMoss retrieved:") + for path in paths: + print(f" - {path}") + + print(f"\nAsking Groq model {model} for a minimal patch...") + patch = await _propose_patch(issue, context, model) + patch_files = ensure_source_patch_only(patch.files) + print(f"Patch summary: {patch.summary or '(no summary)'}") + for patch_file in patch_files: + print(f" - {patch_file.path}") + + print("\nCreating E2B sandbox...") + sandbox = await create_e2b_sandbox(timeout=args.sandbox_timeout) + print(f"Sandbox ready (id: {sandbox_identifier(sandbox)})") + + file_count = await write_project_to_sandbox( + sandbox, project_root, args.sandbox_root + ) + print(f"Wrote {file_count} files to {args.sandbox_root}") + + await run_sandbox_command( + sandbox, + "git init -q && git add .", + cwd=args.sandbox_root, + timeout=30, + ) + + if args.setup_command: + setup = await run_sandbox_command( + sandbox, + args.setup_command, + cwd=args.sandbox_root, + timeout=args.setup_timeout, + ) + _print_command("Setup command", setup) + if not setup.ok: + return False + + baseline = await run_tests( + sandbox, + command=args.test_command, + cwd=args.sandbox_root, + timeout=args.test_timeout, + ) + _print_command("Baseline tests", baseline) + + await apply_patch_files(sandbox, patch_files, args.sandbox_root) + validation = await run_tests( + sandbox, + command=args.test_command, + cwd=args.sandbox_root, + timeout=args.test_timeout, + ) + _print_command("Patched tests", validation) + + pathspec = _git_pathspec(patch_files) + diff = await run_sandbox_command( + sandbox, + f"git add -N -- {pathspec} && git diff -- {pathspec}", + cwd=args.sandbox_root, + timeout=30, + ) + if diff.stdout.strip(): + print("\nValidated patch diff:") + print(textwrap.indent(diff.stdout.strip(), " ")) + + return validation.ok + + finally: + if sandbox is not None and not args.keep_sandbox: + print("\nCleaning up E2B sandbox...") + try: + await sandbox.kill() + except Exception as exc: + print(f"Warning: sandbox cleanup failed: {exc}") + + if moss_client is not None and created_index and not args.reuse_index: + print(f"Deleting Moss index '{index_name}'...") + try: + await moss_client.delete_index(index_name) + except Exception as exc: + print(f"Warning: index cleanup failed: {exc}") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Self-healing code agent - Moss + E2B") + parser.add_argument( + "--project-root", + default=str(SAMPLE_PROJECT), + help="Local project to copy into E2B", + ) + parser.add_argument( + "--issue", default=DEFAULT_ISSUE, help="Bug report or task to repair" + ) + parser.add_argument( + "--index-name", + default=None, + help=( + "Moss index name; defaults to a unique temporary index, or " + f"{DEFAULT_REUSABLE_INDEX_NAME!r} with --reuse-index" + ), + ) + parser.add_argument( + "--reuse-index", + action="store_true", + help=( + "Load an existing Moss index instead of recreating it. If the index " + "does not exist, create it and keep it for future runs." + ), + ) + parser.add_argument( + "--top-k", + type=int, + default=6, + help="Moss results to include in the patch prompt", + ) + parser.add_argument( + "--alpha", type=float, default=0.75, help="Moss hybrid search blend" + ) + parser.add_argument( + "--model", + default=None, + help="Groq model; defaults to GROQ_MODEL or llama-3.3-70b-versatile", + ) + parser.add_argument( + "--sandbox-root", default=DEFAULT_SANDBOX_ROOT, help="Workspace path inside E2B" + ) + parser.add_argument( + "--sandbox-timeout", + type=int, + default=600, + help="E2B sandbox lifetime in seconds", + ) + parser.add_argument( + "--setup-command", + default="python -m pip install -q pytest", + help="Command run before tests", + ) + parser.add_argument( + "--setup-timeout", + type=float, + default=180, + help="Setup command timeout in seconds", + ) + parser.add_argument( + "--test-command", default="python -m pytest -q", help="Validation command" + ) + parser.add_argument( + "--test-timeout", + type=float, + default=120, + help="Test command timeout in seconds", + ) + parser.add_argument( + "--keep-sandbox", + action="store_true", + help="Do not kill the E2B sandbox after running", + ) + return parser + + +async def main() -> int: + args = build_parser().parse_args() + ok = await run_code_repair(args) + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/examples/cookbook/e2b/code_index.py b/examples/cookbook/e2b/code_index.py new file mode 100644 index 0000000..6b4bd23 --- /dev/null +++ b/examples/cookbook/e2b/code_index.py @@ -0,0 +1,158 @@ +"""Helpers for indexing source files into Moss. + +This module stays intentionally small: one source file becomes one Moss +document, with path and symbol metadata attached for agent prompts. +""" + +from __future__ import annotations + +import ast +import hashlib +from pathlib import Path +from typing import Any, Iterable, Iterator, List + +SOURCE_EXTENSIONS = { + ".c", + ".cc", + ".cpp", + ".go", + ".h", + ".hpp", + ".java", + ".js", + ".jsx", + ".json", + ".md", + ".py", + ".rs", + ".toml", + ".ts", + ".tsx", + ".txt", + ".yaml", + ".yml", +} + +IGNORED_DIRS = { + ".git", + ".mypy_cache", + ".pytest_cache", + ".ruff_cache", + ".venv", + "__pycache__", + "build", + "dist", + "node_modules", +} + + +def iter_source_files(project_root: str | Path) -> Iterator[Path]: + """Yield text source files that are useful for semantic code search.""" + root = Path(project_root) + for path in sorted(root.rglob("*")): + if not path.is_file(): + continue + if path.suffix.lower() not in SOURCE_EXTENSIONS: + continue + if any(part in IGNORED_DIRS for part in path.relative_to(root).parts): + continue + yield path + + +def extract_python_symbols(source: str) -> List[str]: + """Return top-level Python classes and functions from a source string.""" + try: + tree = ast.parse(source) + except SyntaxError: + return [] + + symbols: List[str] = [] + for node in tree.body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + symbols.append(node.name) + return symbols + + +def _language_for(path: Path) -> str: + suffix = path.suffix.lower().lstrip(".") + return { + "md": "markdown", + "py": "python", + "toml": "toml", + "ts": "typescript", + "tsx": "typescript", + "js": "javascript", + "jsx": "javascript", + "yml": "yaml", + }.get(suffix, suffix or "text") + + +def _document_text(relative_path: str, content: str, max_file_chars: int) -> str: + truncated = len(content) > max_file_chars + body = content[:max_file_chars] + if truncated: + body = f"{body}\n\n[truncated]" + return f"Path: {relative_path}\n\n```text\n{body}\n```" + + +def build_code_documents( + project_root: str | Path, + *, + max_file_chars: int = 12_000, +) -> List[Any]: + """Build Moss DocumentInfo objects for a local project tree.""" + from moss import DocumentInfo + + root = Path(project_root) + documents: List[Any] = [] + for path in iter_source_files(root): + relative_path = path.relative_to(root).as_posix() + content = path.read_text(encoding="utf-8") + digest = hashlib.sha1(content.encode("utf-8")).hexdigest()[:12] + symbols = extract_python_symbols(content) if path.suffix == ".py" else [] + documents.append( + DocumentInfo( + id=f"{relative_path}::{digest}", + text=_document_text(relative_path, content, max_file_chars), + metadata={ + "path": relative_path, + "language": _language_for(path), + "symbols": ", ".join(symbols), + }, + ) + ) + return documents + + +async def search_code( + client: Any, + index_name: str, + query: str, + *, + top_k: int = 6, + alpha: float = 0.75, +) -> List[Any]: + """Query Moss for code context relevant to a bug report or task.""" + from moss import QueryOptions + + results = await client.query( + index_name, query, QueryOptions(top_k=top_k, alpha=alpha) + ) + return list(results.docs) + + +def format_search_results(docs: Iterable[Any]) -> str: + """Format Moss hits for an LLM prompt.""" + blocks: List[str] = [] + for index, doc in enumerate(docs, 1): + metadata = getattr(doc, "metadata", {}) or {} + path = metadata.get("path") or getattr(doc, "id", "unknown") + score = getattr(doc, "score", None) + score_text = f" score={score:.3f}" if isinstance(score, (float, int)) else "" + symbols = metadata.get("symbols") + symbol_text = f" symbols={symbols}" if symbols else "" + blocks.append( + f"Result {index}: {path}{score_text}{symbol_text}\n" + f"{getattr(doc, 'text', '')}" + ) + return "\n\n---\n\n".join(blocks) diff --git a/examples/cookbook/e2b/pyproject.toml b/examples/cookbook/e2b/pyproject.toml new file mode 100644 index 0000000..5911cb6 --- /dev/null +++ b/examples/cookbook/e2b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "e2b-moss-code-agent" +version = "0.1.0" +description = "Self-healing code agent powered by Moss + E2B" +readme = "README.md" +requires-python = ">=3.11" +license = { text = "BSD-2-Clause" } +authors = [ + { name = "InferEdge Inc.", email = "contact@moss.dev" } +] +dependencies = [ + "moss>=1.0.0", + "e2b>=2.13.0", + "groq>=0.22.0", + "python-dotenv>=1.0.0", +] + +[project.optional-dependencies] +dev = ["pytest", "ruff"] + +[tool.setuptools] +py-modules = ["code_agent", "code_index", "sandbox_runner", "test_integration"] + +[tool.ruff.lint] +select = ["E", "F", "I"] + +[tool.pytest.ini_options] +testpaths = ["test_integration.py"] diff --git a/examples/cookbook/e2b/sample_project/pyproject.toml b/examples/cookbook/e2b/sample_project/pyproject.toml new file mode 100644 index 0000000..5212ddd --- /dev/null +++ b/examples/cookbook/e2b/sample_project/pyproject.toml @@ -0,0 +1,5 @@ +[project] +name = "moss-e2b-sample-ledger" +version = "0.1.0" +requires-python = ">=3.11" +description = "Tiny broken project used by the Moss + E2B cookbook" diff --git a/examples/cookbook/e2b/sample_project/src/ledger/__init__.py b/examples/cookbook/e2b/sample_project/src/ledger/__init__.py new file mode 100644 index 0000000..8212601 --- /dev/null +++ b/examples/cookbook/e2b/sample_project/src/ledger/__init__.py @@ -0,0 +1,5 @@ +"""Sample ledger package used by the Moss + E2B cookbook.""" + +from .totals import format_total + +__all__ = ["format_total"] diff --git a/examples/cookbook/e2b/sample_project/src/ledger/totals.py b/examples/cookbook/e2b/sample_project/src/ledger/totals.py new file mode 100644 index 0000000..8337470 --- /dev/null +++ b/examples/cookbook/e2b/sample_project/src/ledger/totals.py @@ -0,0 +1,21 @@ +"""Checkout total calculations for the sample ledger app.""" + +from decimal import ROUND_HALF_UP, Decimal +from typing import Iterable, Mapping + +MoneyLine = Mapping[str, object] + + +def _money(value: object) -> Decimal: + return Decimal(str(value)) + + +def format_total(line_items: Iterable[MoneyLine], tax_rate: Decimal) -> Decimal: + """Return the order total rounded to cents.""" + subtotal = sum( + _money(item["price"]) * int(item.get("quantity", 1)) for item in line_items + ) + + # Bug for the cookbook: tax_rate is a rate like 0.0825, not a flat amount. + total = subtotal + _money(tax_rate) + return total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) diff --git a/examples/cookbook/e2b/sample_project/tests/test_totals.py b/examples/cookbook/e2b/sample_project/tests/test_totals.py new file mode 100644 index 0000000..4d232c9 --- /dev/null +++ b/examples/cookbook/e2b/sample_project/tests/test_totals.py @@ -0,0 +1,22 @@ +import sys +from decimal import Decimal +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from ledger import format_total + + +def test_format_total_applies_tax_rate_to_subtotal(): + items = [ + {"price": "10.00", "quantity": 2}, + {"price": "5.50", "quantity": 1}, + ] + + assert format_total(items, Decimal("0.0825")) == Decimal("27.60") + + +def test_format_total_rounds_half_up(): + items = [{"price": "19.99", "quantity": 1}] + + assert format_total(items, Decimal("0.08875")) == Decimal("21.76") diff --git a/examples/cookbook/e2b/sandbox_runner.py b/examples/cookbook/e2b/sandbox_runner.py new file mode 100644 index 0000000..c5f1908 --- /dev/null +++ b/examples/cookbook/e2b/sandbox_runner.py @@ -0,0 +1,254 @@ +"""E2B sandbox helpers for validating candidate code patches.""" + +from __future__ import annotations + +import json +import re +import shlex +from dataclasses import dataclass +from pathlib import Path, PurePosixPath +from typing import Any, Iterable, List + +from code_index import IGNORED_DIRS + +DEFAULT_SANDBOX_ROOT = "/home/user/moss-e2b-workspace" +_EXIT_SENTINEL = "__MOSS_COMMAND_EXIT_CODE__=" +MAX_PROJECT_FILE_BYTES = 2_000_000 +IGNORED_FILE_NAMES = {".env"} + + +@dataclass(frozen=True) +class PatchFile: + """A complete file replacement proposed by the coding agent.""" + + path: str + content: str + + +@dataclass(frozen=True) +class ProposedPatch: + """Structured patch response returned by the LLM.""" + + summary: str + files: List[PatchFile] + + +@dataclass(frozen=True) +class CommandOutput: + """A command result normalized across E2B SDK behavior.""" + + command: str + exit_code: int + stdout: str + stderr: str + + @property + def ok(self) -> bool: + return self.exit_code == 0 + + +async def create_e2b_sandbox(timeout: int = 600) -> Any: + """Create an E2B sandbox. + + The E2B SDK reads E2B_API_KEY from the environment. + """ + from e2b import AsyncSandbox + + return await AsyncSandbox.create( + timeout=timeout, + metadata={"cookbook": "moss-e2b-code-agent"}, + ) + + +def sandbox_identifier(sandbox: Any) -> str: + """Return a useful sandbox identifier for logs.""" + return ( + getattr(sandbox, "sandbox_id", None) + or getattr(sandbox, "id", None) + or "unknown" + ) + + +def _validate_relative_path(path: str) -> str: + normalized = path.replace("\\", "/").strip() + if not normalized: + raise ValueError("Patch path cannot be empty.") + + posix_path = PurePosixPath(normalized) + if posix_path.is_absolute() or any(part in {"", ".."} for part in posix_path.parts): + raise ValueError(f"Unsafe patch path: {path}") + return posix_path.as_posix() + + +def _sandbox_path(root: str, relative_path: str) -> str: + return (PurePosixPath(root) / _validate_relative_path(relative_path)).as_posix() + + +def _is_ignored_project_file(root: Path, path: Path) -> bool: + relative = path.relative_to(root) + if any(part in IGNORED_DIRS for part in relative.parts): + return True + if path.name in IGNORED_FILE_NAMES or path.name.startswith(".env."): + return True + return path.stat().st_size > MAX_PROJECT_FILE_BYTES + + +def iter_project_files(local_root: str | Path) -> Iterable[Path]: + """Yield project files that should be copied into the sandbox.""" + root = Path(local_root) + for path in sorted(root.rglob("*")): + if not path.is_file(): + continue + if _is_ignored_project_file(root, path): + continue + yield path + + +async def write_project_to_sandbox( + sandbox: Any, + local_root: str | Path, + sandbox_root: str = DEFAULT_SANDBOX_ROOT, +) -> int: + """Copy runnable project files from a local project into an E2B sandbox.""" + root = Path(local_root) + count = 0 + for path in iter_project_files(root): + relative_path = path.relative_to(root).as_posix() + await sandbox.files.write( + _sandbox_path(sandbox_root, relative_path), + path.read_bytes(), + ) + count += 1 + return count + + +def _looks_like_test_path(path: str) -> bool: + posix_path = PurePosixPath(path.replace("\\", "/")) + parts = [part.lower() for part in posix_path.parts] + name = posix_path.name.lower() + return "tests" in parts or name.startswith("test_") or name.endswith("_test.py") + + +def ensure_source_patch_only(files: Iterable[PatchFile]) -> List[PatchFile]: + """Reject patch files that target tests, preserving validation integrity.""" + patch_files = list(files) + test_paths = [ + patch_file.path + for patch_file in patch_files + if _looks_like_test_path(patch_file.path) + ] + if test_paths: + joined = ", ".join(test_paths) + raise ValueError(f"Refusing to apply patches to test files: {joined}") + return patch_files + + +async def apply_patch_files( + sandbox: Any, + files: Iterable[PatchFile], + sandbox_root: str = DEFAULT_SANDBOX_ROOT, +) -> int: + """Apply complete file replacements inside the sandbox.""" + count = 0 + for patch_file in files: + await sandbox.files.write( + _sandbox_path(sandbox_root, patch_file.path), + patch_file.content, + ) + count += 1 + return count + + +def _wrapped_shell_command(command: str) -> str: + script = ( + "set +e\n" + "(\n" + f"{command}\n" + ")\n" + "code=$?\n" + f"printf '\\n{_EXIT_SENTINEL}%s\\n' \"$code\"\n" + "exit 0\n" + ) + return f"bash -lc {shlex.quote(script)}" + + +def _parse_wrapped_exit_code(stdout: str, fallback: int | None) -> tuple[int, str]: + match = re.search(rf"\n?{re.escape(_EXIT_SENTINEL)}(-?\d+)\s*$", stdout) + if not match: + return (0 if fallback is None else int(fallback), stdout) + exit_code = int(match.group(1)) + clean_stdout = stdout[: match.start()].rstrip() + return exit_code, clean_stdout + + +async def run_sandbox_command( + sandbox: Any, + command: str, + *, + cwd: str = DEFAULT_SANDBOX_ROOT, + timeout: float = 120, +) -> CommandOutput: + """Run a command in E2B and preserve non-zero exit codes as data.""" + result = await sandbox.commands.run( + _wrapped_shell_command(command), + cwd=cwd, + timeout=timeout, + ) + raw_stdout = getattr(result, "stdout", "") or "" + stderr = getattr(result, "stderr", "") or "" + fallback_exit_code = getattr(result, "exit_code", None) + exit_code, stdout = _parse_wrapped_exit_code(raw_stdout, fallback_exit_code) + return CommandOutput( + command=command, exit_code=exit_code, stdout=stdout, stderr=stderr + ) + + +async def run_tests( + sandbox: Any, + *, + command: str = "python -m pytest -q", + cwd: str = DEFAULT_SANDBOX_ROOT, + timeout: float = 120, +) -> CommandOutput: + """Run the project test command in the sandbox.""" + return await run_sandbox_command(sandbox, command, cwd=cwd, timeout=timeout) + + +def _extract_json_object(raw: str) -> str: + fenced = re.search(r"```(?:json)?\s*(.*?)```", raw, flags=re.IGNORECASE | re.DOTALL) + if fenced: + return fenced.group(1).strip() + + start = raw.find("{") + end = raw.rfind("}") + if start == -1 or end == -1 or end <= start: + raise ValueError("LLM response did not contain a JSON object.") + return raw[start : end + 1] + + +def parse_patch_json(raw: str) -> ProposedPatch: + """Parse the LLM JSON patch format used by the cookbook.""" + data = json.loads(_extract_json_object(raw)) + files_data = data.get("files") + if not isinstance(files_data, list) or not files_data: + raise ValueError("Patch JSON must include a non-empty 'files' list.") + + files: List[PatchFile] = [] + for item in files_data: + if not isinstance(item, dict): + raise ValueError("Each patch file must be an object.") + path = item.get("path") + content = item.get("content") + content_lines = item.get("content_lines") + if isinstance(content_lines, list) and all( + isinstance(line, str) for line in content_lines + ): + content = "\n".join(content_lines) + "\n" + if not isinstance(path, str) or not isinstance(content, str): + raise ValueError( + "Each patch file needs string 'path' and either string " + "'content' or string-list 'content_lines'." + ) + files.append(PatchFile(path=_validate_relative_path(path), content=content)) + + return ProposedPatch(summary=str(data.get("summary", "")).strip(), files=files) diff --git a/examples/cookbook/e2b/test_integration.py b/examples/cookbook/e2b/test_integration.py new file mode 100644 index 0000000..a1fae2e --- /dev/null +++ b/examples/cookbook/e2b/test_integration.py @@ -0,0 +1,224 @@ +"""Mocked tests for the Moss + E2B cookbook helpers.""" + +from __future__ import annotations + +import os +import tempfile +import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +from code_agent import DEFAULT_REUSABLE_INDEX_NAME, _resolve_index_name +from code_index import extract_python_symbols, iter_source_files +from sandbox_runner import ( + DEFAULT_SANDBOX_ROOT, + PatchFile, + apply_patch_files, + ensure_source_patch_only, + iter_project_files, + parse_patch_json, + run_sandbox_command, + write_project_to_sandbox, +) + + +class FakeFiles: + def __init__(self): + self.writes = {} + + async def write(self, path, data): + self.writes[path] = data + + +class FakeCommands: + def __init__(self, stdout="", stderr="", exit_code=0): + self.stdout = stdout + self.stderr = stderr + self.exit_code = exit_code + self.calls = [] + + async def run(self, command, cwd=None, timeout=None): + self.calls.append((command, cwd, timeout)) + return SimpleNamespace( + stdout=self.stdout, + stderr=self.stderr, + exit_code=self.exit_code, + ) + + +class FakeSandbox: + def __init__(self, stdout="", stderr="", exit_code=0): + self.files = FakeFiles() + self.commands = FakeCommands(stdout, stderr, exit_code) + + +class CodeIndexTests(unittest.TestCase): + def test_extract_python_symbols(self): + source = """ +class Cart: + pass + +def format_total(): + pass + +async def load_cart(): + pass +""" + + self.assertEqual( + extract_python_symbols(source), ["Cart", "format_total", "load_cart"] + ) + + def test_iter_source_files_skips_ignored_dirs(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "src").mkdir() + (root / "src" / "app.py").write_text("print('ok')", encoding="utf-8") + (root / "Makefile").write_text("test:\n\tpytest\n", encoding="utf-8") + (root / ".venv").mkdir() + (root / ".venv" / "ignored.py").write_text("print('no')", encoding="utf-8") + (root / "blob.png").write_bytes(b"no") + + files = [ + path.relative_to(root).as_posix() for path in iter_source_files(root) + ] + + self.assertEqual(files, ["src/app.py"]) + + def test_iter_project_files_includes_runnable_config(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "src").mkdir() + (root / "src" / "app.py").write_text("print('ok')", encoding="utf-8") + (root / "Makefile").write_text("test:\n\tpytest\n", encoding="utf-8") + (root / ".env").write_text("secret", encoding="utf-8") + (root / ".env.local").write_text("secret", encoding="utf-8") + + files = [ + path.relative_to(root).as_posix() for path in iter_project_files(root) + ] + + self.assertEqual(files, ["Makefile", "src/app.py"]) + + +class CodeAgentConfigTests(unittest.TestCase): + def test_resolve_index_name_prefers_explicit_name(self): + args = SimpleNamespace(index_name="my-index", reuse_index=True) + + self.assertEqual(_resolve_index_name(args), "my-index") + + def test_resolve_index_name_uses_reusable_default_with_reuse_flag(self): + args = SimpleNamespace(index_name=None, reuse_index=True) + + with patch.dict(os.environ, {}, clear=True): + self.assertEqual(_resolve_index_name(args), DEFAULT_REUSABLE_INDEX_NAME) + + def test_resolve_index_name_uses_env_name(self): + args = SimpleNamespace(index_name=None, reuse_index=False) + + with patch.dict(os.environ, {"MOSS_INDEX_NAME": "env-index"}, clear=True): + self.assertEqual(_resolve_index_name(args), "env-index") + + +class SandboxRunnerTests(unittest.IsolatedAsyncioTestCase): + async def test_write_project_to_sandbox_uses_posix_paths(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "src").mkdir() + (root / "src" / "app.py").write_text("print('ok')", encoding="utf-8") + (root / "pyproject.toml").write_bytes(b"[project]\nname = 'x'\n") + sandbox = FakeSandbox() + + count = await write_project_to_sandbox(sandbox, root, DEFAULT_SANDBOX_ROOT) + + self.assertEqual(count, 2) + self.assertEqual( + sandbox.files.writes[f"{DEFAULT_SANDBOX_ROOT}/src/app.py"], + b"print('ok')", + ) + self.assertEqual( + sandbox.files.writes[f"{DEFAULT_SANDBOX_ROOT}/pyproject.toml"], + b"[project]\nname = 'x'\n", + ) + + async def test_apply_patch_files_rejects_path_traversal(self): + sandbox = FakeSandbox() + + with self.assertRaises(ValueError): + await apply_patch_files( + sandbox, + [PatchFile("../outside.py", "print('no')")], + DEFAULT_SANDBOX_ROOT, + ) + + async def test_run_sandbox_command_parses_wrapped_exit_code(self): + stdout = "tests failed\n__MOSS_COMMAND_EXIT_CODE__=2\n" + sandbox = FakeSandbox(stdout=stdout, stderr="details", exit_code=0) + + result = await run_sandbox_command(sandbox, "python -m pytest -q") + + self.assertEqual(result.exit_code, 2) + self.assertEqual(result.stdout, "tests failed") + self.assertEqual(result.stderr, "details") + self.assertFalse(result.ok) + self.assertIn("bash -lc", sandbox.commands.calls[0][0]) + + +class PatchParsingTests(unittest.TestCase): + def test_parse_patch_json_accepts_fenced_json(self): + raw = """```json +{"summary": "fix tax", "files": [{"path": "src/ledger/totals.py", "content": "fixed"}]} +```""" + + patch = parse_patch_json(raw) + + self.assertEqual(patch.summary, "fix tax") + self.assertEqual(patch.files, [PatchFile("src/ledger/totals.py", "fixed")]) + + def test_parse_patch_json_accepts_content_lines(self): + raw = """ +{ + "summary": "fix tax", + "files": [ + { + "path": "src/ledger/totals.py", + "content_lines": [ + "from decimal import Decimal", + "", + "def total(subtotal: Decimal, tax_rate: Decimal) -> Decimal:", + " return subtotal + (subtotal * tax_rate)" + ] + } + ] +} +""" + + patch = parse_patch_json(raw) + + self.assertEqual(patch.summary, "fix tax") + self.assertEqual( + patch.files, + [ + PatchFile( + "src/ledger/totals.py", + "from decimal import Decimal\n\n" + "def total(subtotal: Decimal, tax_rate: Decimal) -> Decimal:\n" + " return subtotal + (subtotal * tax_rate)\n", + ) + ], + ) + + def test_parse_patch_json_rejects_absolute_paths(self): + raw = '{"summary": "bad", "files": [{"path": "/tmp/x.py", "content": "bad"}]}' + + with self.assertRaises(ValueError): + parse_patch_json(raw) + + def test_ensure_source_patch_only_rejects_tests(self): + with self.assertRaises(ValueError): + ensure_source_patch_only([PatchFile("tests/test_totals.py", "pass")]) + + +if __name__ == "__main__": + unittest.main() diff --git a/examples/cookbook/e2b/uv.lock b/examples/cookbook/e2b/uv.lock new file mode 100644 index 0000000..85cbfc2 --- /dev/null +++ b/examples/cookbook/e2b/uv.lock @@ -0,0 +1,575 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[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 = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[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 = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "dockerfile-parse" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/df/929ee0b5d2c8bd8d713c45e71b94ab57c7e11e322130724d54f469b2cd48/dockerfile-parse-2.0.1.tar.gz", hash = "sha256:3184ccdc513221983e503ac00e1aa504a2aa8f84e5de673c46b0b6eee99ec7bc", size = 24556, upload-time = "2023-07-18T13:36:07.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/6c/79cd5bc1b880d8c1a9a5550aa8dacd57353fa3bb2457227e1fb47383eb49/dockerfile_parse-2.0.1-py2.py3-none-any.whl", hash = "sha256:bdffd126d2eb26acf1066acb54cb2e336682e1d72b974a40894fac76a4df17f6", size = 14845, upload-time = "2023-07-18T13:36:06.052Z" }, +] + +[[package]] +name = "e2b" +version = "2.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "dockerfile-parse" }, + { name = "h2" }, + { name = "httpcore" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "python-dateutil" }, + { name = "rich" }, + { name = "typing-extensions" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/97/0e86ccb9e05c18e6e795e0808f14e2dc9f5c9ffb7be2a5cb77afd6d9f59e/e2b-2.21.1.tar.gz", hash = "sha256:2eff473ca03173cee1ccd9f9ec9e90c2b4705cca418e080e3104753d7ec33490", size = 157458, upload-time = "2026-05-14T17:36:02.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/d4/8b6a9a120e724dd8f91aededa89348a667a01fffbba28ae1a42cb397b0f0/e2b-2.21.1-py3-none-any.whl", hash = "sha256:9ec4646f3dba4a6da855baa8adeab239aa988e15904611e38bf12aeec2562ac9", size = 297476, upload-time = "2026-05-14T17:36:00.351Z" }, +] + +[[package]] +name = "e2b-moss-code-agent" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "e2b" }, + { name = "groq" }, + { name = "moss" }, + { name = "python-dotenv" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "e2b", specifier = ">=2.13.0" }, + { name = "groq", specifier = ">=0.22.0" }, + { name = "moss", specifier = ">=1.0.0" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "ruff", marker = "extra == 'dev'" }, +] +provides-extras = ["dev"] + +[[package]] +name = "groq" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/51/4728c13611849ff6cf8536740ae78ba3ee5e665d67b572a47c9ead0f9788/groq-1.2.0.tar.gz", hash = "sha256:85459e27c9c17f22404349c785cd08680362cfe85e07cc060be46c4832f108c3", size = 155609, upload-time = "2026-04-18T10:43:50.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/82/748639c95c60ad8846c65b167ca611c815d06d5f67a9e73b23486dce4fdf/groq-1.2.0-py3-none-any.whl", hash = "sha256:1002060a743b27c8f86765e1bc9749c98498e961d9fe2e4902bf7804a71c3c84", size = 142334, upload-time = "2026-04-18T10:43:49.125Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "inferedge-moss-core" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/7a/5636c0e3d3589ca4ee8a1f60b722022924273dfb16cdf32f33e889846e8d/inferedge_moss_core-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ec55bd0bb53f581cb73cd3ab5f2a042e55a255baabdcf0fba93aba848eecf5e", size = 12153240, upload-time = "2026-05-08T00:28:11.976Z" }, + { url = "https://files.pythonhosted.org/packages/cc/33/4bf9b2c31b2bc0f7ae8bcd0a6e1ccd707fb4e3c448532a63b40cf6ab69b3/inferedge_moss_core-0.11.0-cp311-cp311-manylinux_2_38_x86_64.whl", hash = "sha256:91b4e6fb99928ea4acaccd35dcaa65eaf4c4f583477e3b784ea84484fc915ae8", size = 13275638, upload-time = "2026-05-08T00:29:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/04/7f/d9ccd714445326f841b089332b22495f59eded243c08c1abc21ccaf014d2/inferedge_moss_core-0.11.0-cp311-cp311-manylinux_2_39_aarch64.whl", hash = "sha256:b3f8122dfe40e4a556451c049c50c9010cfdc7bea92fbe08972b61fe6e2d0d66", size = 14786208, upload-time = "2026-05-08T00:28:48.235Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ec/38af5b6f34efaac90e8db3999b52d462af9d259028ba42638af6c2f5dafb/inferedge_moss_core-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:eef3c8341660a3660dd58e038192f826d6ec9388d2a26d9d19caf8cbed5b941e", size = 11194639, upload-time = "2026-05-08T00:33:39.39Z" }, + { url = "https://files.pythonhosted.org/packages/db/2b/9fd7415a0e87c754ca26ac42fbb4768df54ec114e27706d9578466674e6b/inferedge_moss_core-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd2552903465abb49b7b53133de72daded94c25f7a21af297b120c787f7f501a", size = 12151808, upload-time = "2026-05-08T00:27:13.789Z" }, + { url = "https://files.pythonhosted.org/packages/22/e9/9e63b9a9f84b562f0eb91350b808badb0bcb3ca560bd25a3e8a43d7239cd/inferedge_moss_core-0.11.0-cp312-cp312-manylinux_2_38_x86_64.whl", hash = "sha256:8cb5ed306640787d5708b7b80f4f8630b26d63efabcb87f6382607008ada28ae", size = 13274468, upload-time = "2026-05-08T00:29:22.755Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c8/1c257d174a1a3e25432344dd31700663971cc469b06b9acf97627a11540d/inferedge_moss_core-0.11.0-cp312-cp312-manylinux_2_39_aarch64.whl", hash = "sha256:881ccfc628f53b1606fd9f5b53b111dffe920b01c86085e640c1f853c29cb6ec", size = 14783373, upload-time = "2026-05-08T00:29:28.397Z" }, + { url = "https://files.pythonhosted.org/packages/09/f9/640068346b9addda0ad9a992c9abd8a6d27d1e3f1da6346800b1aed771c4/inferedge_moss_core-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:770ef7a0e305e96e37fa1acf1acd53d76d0d4bfa3b69f2111095665c40254e4f", size = 11193645, upload-time = "2026-05-08T00:33:18.476Z" }, + { url = "https://files.pythonhosted.org/packages/8b/52/58fe2ec9f297086b05535042fb7197ebd01faf003d3295703b362c19f597/inferedge_moss_core-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb6f8e556d9c4ca7d89b1bb6a6a452b44da40dc0978afbe3fa4d057f8d3f6fbf", size = 12151527, upload-time = "2026-05-08T00:27:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/93/81/44a6341437b2f113e1eb2f4dfd78e3d17b0f28cc6e4dca091953abc4ae5b/inferedge_moss_core-0.11.0-cp313-cp313-manylinux_2_38_x86_64.whl", hash = "sha256:bc817dd6ac67c540a6e619960594c4b86f660306f2adb4b6cfd297968253d17d", size = 13274523, upload-time = "2026-05-08T00:29:17.109Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a2/264813473c593130ac51b46db277a56f12c2c16f858c727ad25a3796a065/inferedge_moss_core-0.11.0-cp313-cp313-manylinux_2_39_aarch64.whl", hash = "sha256:2fe9f775884fb54bdb1e4dfd5728aebb918ab5da06f3a7cf04009909da9acd78", size = 14789528, upload-time = "2026-05-08T00:28:56.511Z" }, + { url = "https://files.pythonhosted.org/packages/06/26/97fc463e4e44d8f090d7ed64a7cec7b6da16ab84532f92b966625bdbdab4/inferedge_moss_core-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:7e4366f1b00b8ac7b3fc8e8bb3d02b33a8e557352ff0d87cf3d5ee4547116360", size = 11192865, upload-time = "2026-05-08T00:33:43.801Z" }, + { url = "https://files.pythonhosted.org/packages/04/ba/4d5143455985a3f842295237c1d17c61884094495c34483ccfb28b50b91e/inferedge_moss_core-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:72dc9e747351da903882eefbbbb002fae240b23812d4c1e1db4a0c5b5fdce42d", size = 12152689, upload-time = "2026-05-08T00:28:19.785Z" }, + { url = "https://files.pythonhosted.org/packages/e8/b6/319b11970b883fbf9a819b938f303d644a1ecaa797b68b81f684f4aec4ba/inferedge_moss_core-0.11.0-cp314-cp314-manylinux_2_38_x86_64.whl", hash = "sha256:3a5db60e328319edc2d0e6ebb4e0ef7acde0a491453daf49fb4dae4340a14c2d", size = 13274985, upload-time = "2026-05-08T00:29:19.599Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/0490b0f1ce2ea73b558d970cbb3c55df6707ac85f150dfb7b30b6d7ca47f/inferedge_moss_core-0.11.0-cp314-cp314-manylinux_2_39_aarch64.whl", hash = "sha256:795149de1ec32fd477c152a2d6ddb38e08f81c2cc9a4d2e4e36ee1860c799031", size = 14790225, upload-time = "2026-05-08T00:28:46.514Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b7/58a163ae1749cc386658dc523bf353e99b36d1daa729422951e4e3176125/inferedge_moss_core-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:bf21d518c69d6244655792872c126905e139f929c8e9cd725db871589b1d68bb", size = 11196485, upload-time = "2026-05-08T00:33:11.716Z" }, +] + +[[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 = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "moss" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "inferedge-moss-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/7705541c8514f15784d555786f9628689a269a7ee473d1803a9ad5494ae9/moss-1.1.0.tar.gz", hash = "sha256:cad07364bd2641f5d59b0e4f7eacc501e59d7264b4fe13e438298a2d8256cc71", size = 38538, upload-time = "2026-05-08T00:35:16.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/94/8ca1183f46af694509c050c3716e748e984df6a98a9c4b9291df27cc4d26/moss-1.1.0-py3-none-any.whl", hash = "sha256:a717dc5ee58b065608aff4c493d1f1fa1566b19a7651bd9dfc3cdaa8d42cc06e", size = 13874, upload-time = "2026-05-08T00:35:15.49Z" }, +] + +[[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 = "protobuf" +version = "7.35.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" }, + { url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" }, + { url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" }, + { url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" }, +] + +[[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/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.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[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" }, +] + +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, +] From 0505511cc7fdbc8b80afca684ed9a9cdcd0542ba Mon Sep 17 00:00:00 2001 From: Luffy2208 Date: Wed, 20 May 2026 19:17:14 +0530 Subject: [PATCH 2/2] Handled None case in code_agent.py --- examples/cookbook/e2b/code_agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/cookbook/e2b/code_agent.py b/examples/cookbook/e2b/code_agent.py index fa133f2..dc3c454 100644 --- a/examples/cookbook/e2b/code_agent.py +++ b/examples/cookbook/e2b/code_agent.py @@ -174,7 +174,9 @@ async def run_code_repair(args: argparse.Namespace) -> bool: alpha=args.alpha, ) context = format_search_results(hits) - paths = [getattr(hit, "metadata", {}).get("path", hit.id) for hit in hits] + paths = [ + (getattr(hit, "metadata", {}) or {}).get("path", hit.id) for hit in hits + ] print("\nMoss retrieved:") for path in paths: print(f" - {path}")