diff --git a/python/thirdweb-ai/pyproject.toml b/python/thirdweb-ai/pyproject.toml index 7c1d939..8e58934 100644 --- a/python/thirdweb-ai/pyproject.toml +++ b/python/thirdweb-ai/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "pydantic>=2.10.6,<3", "jsonref>=1.1.0,<2", "httpx>=0.28.1,<0.29", + "aiohttp>=3.11.14", ] [project.optional-dependencies] @@ -51,6 +52,7 @@ dev = [ "pytest-asyncio>=0.23.5,<0.24", "pytest-mock>=3.12.0,<4", "pytest-cov>=4.1.0,<5", + "ipython>=8.34.0", ] [tool.hatch.build.targets.sdist] diff --git a/python/thirdweb-ai/src/thirdweb_ai/services/storage.py b/python/thirdweb-ai/src/thirdweb_ai/services/storage.py new file mode 100644 index 0000000..9e965a1 --- /dev/null +++ b/python/thirdweb-ai/src/thirdweb_ai/services/storage.py @@ -0,0 +1,182 @@ +import asyncio +import hashlib +import json +import mimetypes +import os +from collections.abc import AsyncGenerator +from dataclasses import asdict, is_dataclass +from io import BytesIO +from pathlib import Path +from typing import Annotated, Any + +import httpx +from pydantic import BaseModel + +from thirdweb_ai.services.service import Service +from thirdweb_ai.tools.tool import tool + + +async def async_read_file_chunks(file_path: str | Path, chunk_size: int = 8192) -> AsyncGenerator[bytes, None]: + """Read file in chunks asynchronously to avoid loading entire file into memory.""" + async with asyncio.Lock(): + path_obj = Path(file_path) if isinstance(file_path, str) else file_path + with path_obj.open("rb") as f: + while chunk := f.read(chunk_size): + yield chunk + + +class Storage(Service): + def __init__(self, secret_key: str): + super().__init__(base_url="https://storage.thirdweb.com", secret_key=secret_key) + self.gateway_url = self._get_gateway_url() + self.gateway_hostname = "ipfscdn.io" + + def _get_gateway_url(self) -> str: + return hashlib.sha256(self.secret_key.encode()).hexdigest()[:32] + + @tool(description="Fetch content from IPFS by hash. Retrieves data stored on IPFS using the thirdweb gateway.") + def fetch_ipfs_content( + self, + ipfs_hash: Annotated[ + str, "The IPFS hash/URI to fetch content from (e.g., 'ipfs://QmXyZ...'). Must start with 'ipfs://'." + ], + ) -> dict[str, Any]: + if not ipfs_hash.startswith("ipfs://"): + return {"error": "Invalid IPFS hash"} + + ipfs_hash = ipfs_hash.removeprefix("ipfs://") + path = f"https://{self.gateway_url}.{self.gateway_hostname}.ipfscdn.io/ipfs/{ipfs_hash}" + return self._get(path) + + async def _async_post_file(self, url: str, files: dict[str, Any]) -> dict[str, Any]: + """Post files to a URL using async client with proper authorization headers.""" + headers = self._make_headers() + # Remove the Content-Type as httpx will set it correctly for multipart/form-data + headers.pop("Content-Type", None) + + async with httpx.AsyncClient() as client: + response = await client.post(url, files=files, headers=headers) + response.raise_for_status() + return response.json() + + def _is_json_serializable(self, data: Any) -> bool: + """Check if data is JSON serializable (dict, dataclass, or BaseModel).""" + return isinstance(data, dict) or is_dataclass(data) or isinstance(data, BaseModel) + + def _convert_to_json(self, data: Any) -> str: + """Convert data to JSON string.""" + if isinstance(data, dict): + return json.dumps(data) + if is_dataclass(data): + # Handle dataclass properly + if isinstance(data, type): + raise ValueError(f"Expected dataclass instance, got dataclass type: {data}") + return json.dumps(asdict(data)) + if isinstance(data, BaseModel): + return data.model_dump_json() + raise ValueError(f"Cannot convert {type(data)} to JSON") + + def _is_valid_path(self, path: str) -> bool: + """Check if the string is a valid file or directory path.""" + return Path(path).exists() + + async def _prepare_directory_files( + self, directory_path: Path, chunk_size: int = 8192 + ) -> list[tuple[str, BytesIO, str]]: + """ + Prepare files from a directory for upload, preserving directory structure. + Returns a list of tuples (relative_path, file_buffer, content_type). + """ + files_data = [] + + for root, _, files in os.walk(directory_path): + for file in files: + file_path = Path(root) / file + # Preserve the directory structure in the relative path + relative_path = str(file_path.relative_to(directory_path)) + content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream" + + # Create a buffer and read the file in chunks + buffer = BytesIO() + async for chunk in async_read_file_chunks(file_path, chunk_size): + buffer.write(chunk) + buffer.seek(0) # Reset buffer position + + files_data.append((relative_path, buffer, content_type)) + + return files_data + + @tool( + description="Upload a file, directory, or JSON data to IPFS. Stores any type on decentralized storage and returns an IPFS URI." + ) + async def upload_to_ipfs( + self, + data: Annotated[ + Any, "Data to upload: can be a file path, directory path, dict, dataclass, or BaseModel instance." + ], + ) -> str: + """ + Upload data to IPFS and return the IPFS hash. + + Supports: + - File paths (streams content) + - Directory paths (preserves directory structure) + - Dict objects (converted to JSON) + - Dataclass instances (converted to JSON) + - Pydantic BaseModel instances (converted to JSON) + + Always uses streaming for file uploads to handle large files efficiently. + """ + storage_url = f"{self.base_url}/ipfs/upload" + + # Handle JSON-serializable data types + if self._is_json_serializable(data): + json_content = self._convert_to_json(data) + files = {"file": ("data.json", BytesIO(json_content.encode()), "application/json")} + body = await self._async_post_file(storage_url, files) + return f"ipfs://{body['IpfsHash']}" + + # Handle string paths to files or directories + if isinstance(data, str) and self._is_valid_path(data): + path = Path(data) + + # Single file upload with streaming + if path.is_file(): + content_type = mimetypes.guess_type(str(path))[0] or "application/octet-stream" + + # Create a buffer to hold chunks for streaming upload + buffer = BytesIO() + async for chunk in async_read_file_chunks(path): + buffer.write(chunk) + + buffer.seek(0) # Reset buffer position + files = {"file": (path.name, buffer, content_type)} + body = await self._async_post_file(storage_url, files) + return f"ipfs://{body['IpfsHash']}" + + # Directory upload - preserve directory structure + if path.is_dir(): + # Prepare all files from the directory with preserved structure + files_data = await self._prepare_directory_files(path) + + if not files_data: + raise ValueError(f"Directory is empty: {data}") + + files_dict = { + f"file{i}": (relative_path, buffer, content_type) + for i, (relative_path, buffer, content_type) in enumerate(files_data) + } + body = await self._async_post_file(storage_url, files_dict) + return f"ipfs://{body['IpfsHash']}" + + raise ValueError(f"Path exists but is neither a file nor a directory: {data}") + + try: + content_type = mimetypes.guess_type(data)[0] or "application/octet-stream" + files = {"file": ("data.txt", BytesIO(data.encode()), content_type)} + body = await self._async_post_file(storage_url, files) + return f"ipfs://{body['IpfsHash']}" + except TypeError as e: + raise TypeError( + f"Unsupported data type: {type(data)}. Must be a valid file/directory path, dict, dataclass, or BaseModel." + ) from e diff --git a/python/thirdweb-ai/uv.lock b/python/thirdweb-ai/uv.lock index ca191f2..9a00f5e 100644 --- a/python/thirdweb-ai/uv.lock +++ b/python/thirdweb-ai/uv.lock @@ -171,6 +171,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045 }, ] +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + [[package]] name = "async-timeout" version = "5.0.1" @@ -893,6 +902,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, ] +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +] + [[package]] name = "deprecated" version = "1.2.18" @@ -1091,6 +1109,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + [[package]] name = "fastavro" version = "1.10.0" @@ -1430,6 +1457,82 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, ] +[[package]] +name = "ipython" +version = "8.34.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/18/1a60aa62e9d272fcd7e658a89e1c148da10e1a5d38edcbcd834b52ca7492/ipython-8.34.0.tar.gz", hash = "sha256:c31d658e754673ecc6514583e7dda8069e47136eb62458816b7d1e6625948b5a", size = 5508477 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/78/45615356bb973904856808183ae2a5fba1f360e9d682314d79766f4b88f2/ipython-8.34.0-py3-none-any.whl", hash = "sha256:0419883fa46e0baa182c5d50ebb8d6b49df1889fdb70750ad6d8cfe678eda6e3", size = 826731 }, +] + +[[package]] +name = "ipython" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4'", + "python_full_version >= '3.12' and python_full_version < '3.12.4'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ce/012a0f40ca58a966f87a6e894d6828e2817657cbdf522b02a5d3a87d92ce/ipython-9.0.2.tar.gz", hash = "sha256:ec7b479e3e5656bf4f58c652c120494df1820f4f28f522fb7ca09e213c2aab52", size = 4366102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/3a/917cb9e72f4e1a4ea13c862533205ae1319bd664119189ee5cc9e4e95ebf/ipython-9.0.2-py3-none-any.whl", hash = "sha256:143ef3ea6fb1e1bffb4c74b114051de653ffb7737a3f7ab1670e657ca6ae8c44", size = 600524 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -1804,6 +1907,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + [[package]] name = "mcp" version = "1.5.0" @@ -2226,6 +2341,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/0f/c8b64d9b54ea631fcad4e9e3c8dbe8c11bb32a623be94f22974c88e71eaf/parsimonious-0.10.0-py3-none-any.whl", hash = "sha256:982ab435fabe86519b57f6b35610aa4e4e977e9f02a14353edf4bbc75369fc0f", size = 48427 }, ] +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + [[package]] name = "pillow" version = "11.1.0" @@ -2433,6 +2569,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/fb/a586e0c973c95502e054ac5f81f88394f24ccc7982dac19c515acd9e2c93/protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862", size = 172551 }, ] +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + [[package]] name = "py-sr25519-bindings" version = "0.2.2" @@ -3228,6 +3382,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + [[package]] name = "starlette" version = "0.46.1" @@ -3251,9 +3419,10 @@ wheels = [ [[package]] name = "thirdweb-ai" -version = "0.1.6" +version = "0.1.7" source = { editable = "." } dependencies = [ + { name = "aiohttp" }, { name = "httpx" }, { name = "jsonref" }, { name = "pydantic" }, @@ -3301,6 +3470,8 @@ smolagents = [ [package.dev-dependencies] dev = [ + { name = "ipython", version = "8.34.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -3311,6 +3482,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "aiohttp", specifier = ">=3.11.14" }, { name = "autogen-core", marker = "extra == 'all'", specifier = ">=0.4.0" }, { name = "autogen-core", marker = "extra == 'autogen'", specifier = ">=0.4.0" }, { name = "coinbase-agentkit", marker = "extra == 'agentkit'", specifier = ">=0.1.0,<0.2" }, @@ -3337,6 +3509,7 @@ provides-extras = ["all", "langchain", "goat", "openai", "autogen", "llama-index [package.metadata.requires-dev] dev = [ + { name = "ipython", specifier = ">=8.34.0" }, { name = "pyright", specifier = ">=1.1.396,<2" }, { name = "pytest", specifier = ">=7.4.0,<8" }, { name = "pytest-asyncio", specifier = ">=0.23.5,<0.24" }, @@ -3466,6 +3639,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, ] +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + [[package]] name = "types-requests" version = "2.32.0.20250306"