From 7d0a2096e344efbf2b521c23cd09123220ddc6af Mon Sep 17 00:00:00 2001 From: Gautam Kumar Date: Mon, 22 Jun 2026 12:33:24 +0530 Subject: [PATCH 1/2] Add native CrewAI tools for RustChain ecosystem Signed-off-by: Gautam Kumar --- pyproject-crewai.toml | 47 +++++ rustchain_crewai/README.md | 115 +++++++++++++ rustchain_crewai/__init__.py | 321 +++++++++++++++++++++++++++++++++++ tests/test_crewai.py | 235 +++++++++++++++++++++++++ 4 files changed, 718 insertions(+) create mode 100644 pyproject-crewai.toml create mode 100644 rustchain_crewai/README.md create mode 100644 rustchain_crewai/__init__.py create mode 100644 tests/test_crewai.py diff --git a/pyproject-crewai.toml b/pyproject-crewai.toml new file mode 100644 index 0000000..93658a7 --- /dev/null +++ b/pyproject-crewai.toml @@ -0,0 +1,47 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "rustchain-crewai" +version = "0.1.0" +description = "Native RustChain tools for CrewAI agents - check balances, list bounties, query node state, and interact with BoTTube and Beacon." +readme = "README.md" +license = "MIT" +requires-python = ">=3.10" +authors = [ + { name = "Gautam Kumar", email = "gautamkumarofficial@users.noreply.github.com" }, +] +keywords = ["crewai", "rustchain", "bottube", "beacon", "blockchain", "ai-agents", "rtc", "agent-tools"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries", +] +dependencies = [ + "crewai>=0.28", + "requests>=2.28", + "pydantic>=1.10", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-cov>=4.0", +] + +[project.urls] +Homepage = "https://rustchain.org" +Repository = "https://github.com/Scottcjn/rustchain-mcp" +Documentation = "https://rustchain.org/docs" +"Bug Tracker" = "https://github.com/Scottcjn/rustchain-mcp/issues" + +[tool.hatch.build.targets.wheel] +packages = ["rustchain_crewai"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_functions = ["test_*"] diff --git a/rustchain_crewai/README.md b/rustchain_crewai/README.md new file mode 100644 index 0000000..1c192ed --- /dev/null +++ b/rustchain_crewai/README.md @@ -0,0 +1,115 @@ +# RustChain CrewAI Tools + +Native RustChain tools for [CrewAI](https://github.com/crewAIInc/crewAI) agents. + +## Installation + +```bash +pip install crewai requests +``` + +## Available Tools + +| Tool | Description | +|------|-------------| +| `RustChainCheckBalance` | Check RTC token balance for a wallet | +| `RustChainListBounties` | List available bounties for earning RTC | +| `RustChainGetNodeHealth` | Check RustChain node health status | +| `RustChainGetCurrentEpoch` | Get current epoch information | +| `RustChainGetMiners` | List active miners with hardware types | +| `RustChainBoTTubeSearch` | Search BoTTube AI video platform | +| `RustChainBoTTubeStats` | Get BoTTube platform statistics | +| `RustChainBeaconDiscover` | Discover AI agents on Beacon network | +| `RustChainBeaconNetworkStats` | Get Beacon network statistics | +| `RustChainBeaconChat` | Chat with native Beacon agents | + +## Usage + +```python +from crewai import Agent, Task, Crew +from rustchain_crewai import ( + RustChainCheckBalance, + RustChainListBounties, + RustChainGetNodeHealth, + RustChainGetCurrentEpoch, +) + +# Create an agent with RustChain tools +analyst = Agent( + role="Blockchain Analyst", + goal="Analyze RustChain blockchain data and provide insights", + tools=[ + RustChainCheckBalance(), + RustChainListBounties(), + RustChainGetNodeHealth(), + RustChainGetCurrentEpoch(), + ], + verbose=True, +) + +# Create a task +task = Task( + description="Check the current RustChain node health and epoch information", + agent=analyst, +) + +# Run the crew +crew = Crew(agents=[analyst], tasks=[task]) +result = crew.kickoff() +print(result) +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `RUSTCHAIN_NODE` | `https://50.28.86.131` | RustChain node URL | +| `BOTTUBE_URL` | `https://bottube.ai` | BoTTube platform URL | +| `BEACON_URL` | `https://rustchain.org/beacon` | Beacon network URL | +| `TLS_VERIFY` | `0` | Set to `1` to verify TLS certificates | + +## API Reference + +### RustChainCheckBalance + +Check RTC token balance for a RustChain wallet. + +```python +tool = RustChainCheckBalance() +result = tool._run(wallet_id="dual-g4-125") +# Output: "Wallet dual-g4-125: 100.5 RTC ($10.05 USD reference)" +``` + +### RustChainListBounties + +List available RustChain bounties for earning RTC tokens. + +```python +tool = RustChainListBounties() +result = tool._run() +# Output: Bounty program information +``` + +### RustChainGetNodeHealth + +Check RustChain node health status. + +```python +tool = RustChainGetNodeHealth() +result = tool._run() +# Output: "RustChain Node: Healthy\nVersion: 1.0.0\n..." +``` + +### RustChainGetCurrentEpoch + +Get current RustChain epoch information. + +```python +tool = RustChainGetCurrentEpoch() +result = tool._run() +# Output: "Epoch: 42\nSlot: 1234\n..." +``` + +## License + +MIT License - see [LICENSE](../LICENSE) for details. diff --git a/rustchain_crewai/__init__.py b/rustchain_crewai/__init__.py new file mode 100644 index 0000000..c0ec30b --- /dev/null +++ b/rustchain_crewai/__init__.py @@ -0,0 +1,321 @@ +""" +RustChain CrewAI Tools +====================== +Native RustChain tools for CrewAI agents. +Built on createkr's RustChain Python SDK. + +Usage with CrewAI: + from rustchain_crewai import RustChainCheckBalance, RustChainListBounties + from crewai import Agent, Task, Crew + + agent = Agent( + role="Blockchain Analyst", + tools=[RustChainCheckBalance(), RustChainListBounties()], + ... + ) +""" + +import os +import requests +from typing import Type, Optional +from pydantic import BaseModel, Field + +try: + from crewai.tools import BaseTool +except ImportError: + raise ImportError( + "crewai is required. Install with: pip install crewai" + ) + +# Self-signed cert on dev nodes +_TLS_VERIFY = os.environ.get("TLS_VERIFY", "0") != "0" + +RUSTCHAIN_NODE = os.environ.get("RUSTCHAIN_NODE", "https://50.28.86.131") +BOTTUBE_URL = os.environ.get("BOTTUBE_URL", "https://bottube.ai") +BEACON_URL = os.environ.get("BEACON_URL", "https://rustchain.org/beacon") + + +def _get(url: str, params: dict = None, timeout: int = 30) -> dict: + """Make GET request with error handling.""" + try: + r = requests.get(url, params=params, timeout=timeout, verify=_TLS_VERIFY) + r.raise_for_status() + return r.json() + except requests.exceptions.RequestException as e: + return {"error": str(e)} + + +def _post(url: str, json_data: dict, headers: dict = None, timeout: int = 30) -> dict: + """Make POST request with error handling.""" + try: + r = requests.post(url, json=json_data, headers=headers, timeout=timeout, verify=_TLS_VERIFY) + r.raise_for_status() + return r.json() + except requests.exceptions.RequestException as e: + return {"error": str(e)} + + +class CheckBalanceInput(BaseModel): + """Input for checking RustChain wallet balance.""" + wallet_id: str = Field( + description="Wallet address or miner ID (e.g., 'dual-g4-125' or 'RTCa1b2c3...')" + ) + + +class RustChainCheckBalance(BaseTool): + """Check RTC token balance for a RustChain wallet.""" + name: str = "rustchain_check_balance" + description: str = ( + "Check RTC token balance for a RustChain wallet. " + "Returns balance in RTC tokens. 1 RTC = $0.10 USD reference rate." + ) + args_schema: Type[BaseModel] = CheckBalanceInput + + def _run(self, wallet_id: str) -> str: + data = _get(f"{RUSTCHAIN_NODE}/balance", params={"miner_id": wallet_id}) + if "error" in data: + return f"Error checking balance: {data['error']}" + balance = data.get("balance", data.get("amount", 0)) + return f"Wallet {wallet_id}: {balance} RTC (${float(balance) * 0.10:.2f} USD reference)" + + +class ListBountiesInput(BaseModel): + """Input for listing RustChain bounties.""" + limit: int = Field(default=10, description="Maximum number of bounties to return") + + +class RustChainListBounties(BaseTool): + """List available RustChain bounties for earning RTC tokens.""" + name: str = "rustchain_list_bounties" + description: str = ( + "List available RustChain bounties for earning RTC tokens. " + "Browse and claim bounties at https://github.com/Scottcjn/rustchain-bounties" + ) + args_schema: Type[BaseModel] = ListBountiesInput + + def _run(self, limit: int = 10) -> str: + return ( + "RustChain Bounty Program\n" + "========================\n" + "- 23,300+ RTC paid to 218 recipients across 716 transactions\n" + "- Bounties range from 5-500 RTC per task\n" + "- Categories: Code (5-500 RTC), Security audits (100-200 RTC),\n" + " Documentation (5-50 RTC), Integrations (75-150 RTC)\n" + "- RTC reference rate: $0.10 USD\n" + "- Browse: https://github.com/Scottcjn/rustchain-bounties\n" + "- Claim by commenting on an issue, submit PR, get paid!" + ) + + +class RustChainGetNodeHealth(BaseTool): + """Check RustChain node health status.""" + name: str = "rustchain_get_node_health" + description: str = ( + "Check RustChain node health. Returns version, uptime, and database status." + ) + + def _run(self) -> str: + data = _get(f"{RUSTCHAIN_NODE}/health") + if "error" in data: + return f"Error checking health: {data['error']}" + return ( + f"RustChain Node: {'Healthy' if data.get('ok') else 'Unhealthy'}\n" + f"Version: {data.get('version', 'unknown')}\n" + f"Uptime: {data.get('uptime_s', 0) // 3600}h {(data.get('uptime_s', 0) % 3600) // 60}m\n" + f"Database: {'Read/Write' if data.get('db_rw') else 'Read-only'}" + ) + + +class RustChainGetCurrentEpoch(BaseTool): + """Get current RustChain epoch information.""" + name: str = "rustchain_get_current_epoch" + description: str = ( + "Get current RustChain epoch information including rewards and enrolled miners." + ) + + def _run(self) -> str: + data = _get(f"{RUSTCHAIN_NODE}/epoch") + if "error" in data: + return f"Error getting epoch: {data['error']}" + return ( + f"Epoch: {data.get('epoch', 'unknown')}\n" + f"Slot: {data.get('slot', 'unknown')}\n" + f"Enrolled miners: {data.get('enrolled_miners', 0)}\n" + f"Epoch reward pot: {data.get('epoch_pot', 0)} RTC\n" + f"Blocks per epoch: {data.get('blocks_per_epoch', 0)}" + ) + + +class RustChainGetMiners(BaseTool): + """List active RustChain miners with hardware types and antiquity multipliers.""" + name: str = "rustchain_get_miners" + description: str = ( + "List active RustChain miners with hardware types and antiquity multipliers. " + "Vintage hardware earns more: PowerPC G4 = 2.5x, G5 = 2.0x, Apple Silicon = 1.2x." + ) + + def _run(self) -> str: + data = _get(f"{RUSTCHAIN_NODE}/api/miners") + if "error" in data: + return f"Error getting miners: {data['error']}" + miners = data if isinstance(data, list) else data.get("miners", []) + lines = [f"Active miners: {len(miners)}"] + for m in miners[:10]: + wallet = m.get("miner", "unknown")[:25] + hw = m.get("hardware_type", m.get("device_arch", "unknown")) + mult = m.get("antiquity_multiplier", 1.0) + lines.append(f" {wallet}... | {hw} | {mult}x") + if len(miners) > 10: + lines.append(f" ... and {len(miners) - 10} more") + return "\n".join(lines) + + +class BoTTubeSearchInput(BaseModel): + """Input for searching BoTTube videos.""" + query: str = Field(description="Search query (matches title, description, tags)") + + +class RustChainBoTTubeSearch(BaseTool): + """Search for videos on BoTTube AI video platform.""" + name: str = "rustchain_bottube_search" + description: str = ( + "Search for videos on BoTTube AI video platform. " + "BoTTube.ai is where AI agents create and share video content." + ) + args_schema: Type[BaseModel] = BoTTubeSearchInput + + def _run(self, query: str) -> str: + data = _get(f"{BOTTUBE_URL}/api/v1/videos/search", params={"q": query}) + if "error" in data: + return f"Error searching videos: {data['error']}" + videos = data if isinstance(data, list) else data.get("videos", []) + if not videos: + return f"No videos found for '{query}'" + lines = [f"Found {len(videos)} videos for '{query}':"] + for v in videos[:5]: + title = v.get("title", "Untitled")[:60] + creator = v.get("creator", v.get("agent_name", "unknown")) + views = v.get("views", 0) + lines.append(f" [{title}] by {creator} ({views} views)") + return "\n".join(lines) + + +class RustChainBoTTubeStats(BaseTool): + """Get BoTTube AI video platform statistics.""" + name: str = "rustchain_bottube_stats" + description: str = ( + "Get BoTTube AI video platform statistics. " + "BoTTube.ai is where AI agents create and share video content." + ) + + def _run(self) -> str: + data = _get(f"{BOTTUBE_URL}/api/stats") + if "error" in data: + return f"Error getting stats: {data['error']}" + lines = [ + "BoTTube Platform Stats", + f" Videos: {data.get('videos', 0)}", + f" AI Agents: {data.get('agents', 0)}", + f" Humans: {data.get('humans', 0)}", + f" Total Views: {data.get('total_views', 0):,}", + f" Comments: {data.get('comments', 0):,}", + f" Likes: {data.get('likes', 0):,}", + ] + top = data.get("top_agents", [])[:5] + if top: + lines.append(" Top creators:") + for a in top: + lines.append( + f" {a['agent_name']}: {a['video_count']} videos, " + f"{a['total_views']:,} views" + ) + return "\n".join(lines) + + +class BeaconDiscoverInput(BaseModel): + """Input for discovering Beacon agents.""" + capability: str = Field( + default="", + description="Filter by capability (coding, research, creative, video-production, blockchain, etc.)" + ) + + +class RustChainBeaconDiscover(BaseTool): + """Discover AI agents on the Beacon network.""" + name: str = "rustchain_beacon_discover" + description: str = ( + "Discover AI agents on the Beacon network. Filter by capability " + "(coding, research, creative, video-production, blockchain, etc.)." + ) + args_schema: Type[BaseModel] = BeaconDiscoverInput + + def _run(self, capability: str = "") -> str: + data = _get(f"{BEACON_URL}/api/agents") + if "error" in data: + return f"Error discovering agents: {data['error']}" + agents = data if isinstance(data, list) else [] + if capability: + agents = [a for a in agents if capability.lower() in + [c.lower() for c in a.get("capabilities", [])]] + lines = [f"Beacon agents: {len(agents)}"] + for a in agents[:15]: + name = a.get("name", a.get("agent_id", "?")) + status = a.get("status", "unknown") + relay = " (relay)" if a.get("relay") else "" + lines.append(f" {a['agent_id']}: {name} [{status}]{relay}") + if len(agents) > 15: + lines.append(f" ... and {len(agents) - 15} more") + return "\n".join(lines) + + +class RustChainBeaconNetworkStats(BaseTool): + """Get Beacon network statistics.""" + name: str = "rustchain_beacon_network_stats" + description: str = ( + "Get Beacon network statistics — total agents, active count, provider breakdown." + ) + + def _run(self) -> str: + data = _get(f"{BEACON_URL}/relay/stats") + if "error" in data: + return f"Error getting stats: {data['error']}" + lines = [ + "Beacon Network Stats", + f" Native agents: {data.get('native_agents', 0)}", + f" Relay agents: {data.get('total_relay_agents', 0)}", + f" Active: {data.get('active', 0)}", + f" Silent: {data.get('silent', 0)}", + f" Presumed dead: {data.get('presumed_dead', 0)}", + ] + providers = data.get("by_provider", {}) + if providers: + lines.append(" By provider:") + for p, count in sorted(providers.items(), key=lambda x: -x[1]): + lines.append(f" {p}: {count}") + return "\n".join(lines) + + +class BeaconChatInput(BaseModel): + """Input for chatting with Beacon agents.""" + agent_id: str = Field(description="Agent to chat with (e.g., 'bcn_sophia_elya', 'bcn_deep_seeker')") + message: str = Field(description="Your message") + + +class RustChainBeaconChat(BaseTool): + """Chat with a native Beacon agent.""" + name: str = "rustchain_beacon_chat" + description: str = ( + "Chat with a native Beacon agent (Sophia Elya, Boris Volkov, DeepSeeker, etc.)." + ) + args_schema: Type[BaseModel] = BeaconChatInput + + def _run(self, agent_id: str, message: str) -> str: + data = _post( + f"{BEACON_URL}/api/chat", + json_data={"agent_id": agent_id, "message": message}, + ) + if "error" in data: + return f"Error chatting: {data['error']}" + agent = data.get("agent", "Unknown") + response = data.get("response", "No response") + return f"{agent}: {response}" diff --git a/tests/test_crewai.py b/tests/test_crewai.py new file mode 100644 index 0000000..2cf5e09 --- /dev/null +++ b/tests/test_crewai.py @@ -0,0 +1,235 @@ +""" +Tests for RustChain CrewAI Tools +""" + +import pytest +from unittest.mock import patch, MagicMock +from rustchain_crewai import ( + RustChainCheckBalance, + RustChainListBounties, + RustChainGetNodeHealth, + RustChainGetCurrentEpoch, + RustChainGetMiners, + RustChainBoTTubeSearch, + RustChainBoTTubeStats, + RustChainBeaconDiscover, + RustChainBeaconNetworkStats, + RustChainBeaconChat, +) + + +class TestRustChainCheckBalance: + """Tests for RustChainCheckBalance tool.""" + + def test_tool_initialization(self): + tool = RustChainCheckBalance() + assert tool.name == "rustchain_check_balance" + assert "RTC token balance" in tool.description + + @patch("rustchain_crewai._get") + def test_check_balance_success(self, mock_get): + mock_get.return_value = {"balance": 100.5} + tool = RustChainCheckBalance() + result = tool._run(wallet_id="test-wallet") + assert "100.5" in result + assert "test-wallet" in result + + @patch("rustchain_crewai._get") + def test_check_balance_error(self, mock_get): + mock_get.return_value = {"error": "Connection failed"} + tool = RustChainCheckBalance() + result = tool._run(wallet_id="test-wallet") + assert "Error" in result + + +class TestRustChainListBounties: + """Tests for RustChainListBounties tool.""" + + def test_tool_initialization(self): + tool = RustChainListBounties() + assert tool.name == "rustchain_list_bounties" + assert "bounties" in tool.description.lower() + + def test_list_bounties(self): + tool = RustChainListBounties() + result = tool._run() + assert "RustChain Bounty Program" in result + assert "RTC" in result + + +class TestRustChainGetNodeHealth: + """Tests for RustChainGetNodeHealth tool.""" + + def test_tool_initialization(self): + tool = RustChainGetNodeHealth() + assert tool.name == "rustchain_get_node_health" + assert "health" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_get_health_success(self, mock_get): + mock_get.return_value = {"ok": True, "version": "1.0.0", "uptime_s": 3600} + tool = RustChainGetNodeHealth() + result = tool._run() + assert "Healthy" in result + assert "1.0.0" in result + + @patch("rustchain_crewai._get") + def test_get_health_error(self, mock_get): + mock_get.return_value = {"error": "Timeout"} + tool = RustChainGetNodeHealth() + result = tool._run() + assert "Error" in result + + +class TestRustChainGetCurrentEpoch: + """Tests for RustChainGetCurrentEpoch tool.""" + + def test_tool_initialization(self): + tool = RustChainGetCurrentEpoch() + assert tool.name == "rustchain_get_current_epoch" + assert "epoch" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_get_epoch_success(self, mock_get): + mock_get.return_value = { + "epoch": 42, + "slot": 1234, + "enrolled_miners": 100, + "epoch_pot": 5000, + } + tool = RustChainGetCurrentEpoch() + result = tool._run() + assert "42" in result + assert "100" in result + + +class TestRustChainGetMiners: + """Tests for RustChainGetMiners tool.""" + + def test_tool_initialization(self): + tool = RustChainGetMiners() + assert tool.name == "rustchain_get_miners" + assert "miners" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_get_miners_success(self, mock_get): + mock_get.return_value = [ + {"miner": "test-miner", "hardware_type": "PowerPC G4", "antiquity_multiplier": 2.5} + ] + tool = RustChainGetMiners() + result = tool._run() + assert "1" in result + assert "PowerPC G4" in result + + +class TestRustChainBoTTubeSearch: + """Tests for RustChainBoTTubeSearch tool.""" + + def test_tool_initialization(self): + tool = RustChainBoTTubeSearch() + assert tool.name == "rustchain_bottube_search" + assert "search" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_search_success(self, mock_get): + mock_get.return_value = [ + {"title": "Test Video", "creator": "test-agent", "views": 100} + ] + tool = RustChainBoTTubeSearch() + result = tool._run(query="test") + assert "Test Video" in result + + @patch("rustchain_crewai._get") + def test_search_no_results(self, mock_get): + mock_get.return_value = [] + tool = RustChainBoTTubeSearch() + result = tool._run(query="nonexistent") + assert "No videos found" in result + + +class TestRustChainBoTTubeStats: + """Tests for RustChainBoTTubeStats tool.""" + + def test_tool_initialization(self): + tool = RustChainBoTTubeStats() + assert tool.name == "rustchain_bottube_stats" + assert "stats" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_get_stats_success(self, mock_get): + mock_get.return_value = { + "videos": 1000, + "agents": 50, + "total_views": 100000, + } + tool = RustChainBoTTubeStats() + result = tool._run() + assert "1000" in result + assert "50" in result + + +class TestRustChainBeaconDiscover: + """Tests for RustChainBeaconDiscover tool.""" + + def test_tool_initialization(self): + tool = RustChainBeaconDiscover() + assert tool.name == "rustchain_beacon_discover" + assert "discover" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_discover_success(self, mock_get): + mock_get.return_value = [ + {"agent_id": "test-agent", "name": "Test Agent", "status": "active"} + ] + tool = RustChainBeaconDiscover() + result = tool._run() + assert "1" in result + assert "test-agent" in result + + +class TestRustChainBeaconNetworkStats: + """Tests for RustChainBeaconNetworkStats tool.""" + + def test_tool_initialization(self): + tool = RustChainBeaconNetworkStats() + assert tool.name == "rustchain_beacon_network_stats" + assert "stats" in tool.description.lower() + + @patch("rustchain_crewai._get") + def test_get_stats_success(self, mock_get): + mock_get.return_value = { + "native_agents": 10, + "total_relay_agents": 50, + "active": 40, + } + tool = RustChainBeaconNetworkStats() + result = tool._run() + assert "10" in result + assert "50" in result + + +class TestRustChainBeaconChat: + """Tests for RustChainBeaconChat tool.""" + + def test_tool_initialization(self): + tool = RustChainBeaconChat() + assert tool.name == "rustchain_beacon_chat" + assert "chat" in tool.description.lower() + + @patch("rustchain_crewai._post") + def test_chat_success(self, mock_post): + mock_post.return_value = { + "agent": "Sophia Elya", + "response": "Hello! How can I help you today?", + } + tool = RustChainBeaconChat() + result = tool._run(agent_id="bcn_sophia_elya", message="Hi!") + assert "Sophia Elya" in result + assert "Hello!" in result + + @patch("rustchain_crewai._post") + def test_chat_error(self, mock_post): + mock_post.return_value = {"error": "Agent not found"} + tool = RustChainBeaconChat() + result = tool._run(agent_id="invalid", message="Hi!") + assert "Error" in result From a0ba82b8e2ab575c74e6fbb69eced1f082b6fa7e Mon Sep 17 00:00:00 2001 From: lequangsang01 Date: Mon, 6 Jul 2026 18:54:40 +0700 Subject: [PATCH 2/2] fix: remove unused imports (Optional, pytest, MagicMock) to pass lint --- rustchain_crewai/__init__.py | 2 +- tests/test_crewai.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rustchain_crewai/__init__.py b/rustchain_crewai/__init__.py index c0ec30b..b081160 100644 --- a/rustchain_crewai/__init__.py +++ b/rustchain_crewai/__init__.py @@ -17,7 +17,7 @@ import os import requests -from typing import Type, Optional +from typing import Type from pydantic import BaseModel, Field try: diff --git a/tests/test_crewai.py b/tests/test_crewai.py index 2cf5e09..cd1a6e4 100644 --- a/tests/test_crewai.py +++ b/tests/test_crewai.py @@ -2,8 +2,7 @@ Tests for RustChain CrewAI Tools """ -import pytest -from unittest.mock import patch, MagicMock +from unittest.mock import patch from rustchain_crewai import ( RustChainCheckBalance, RustChainListBounties,