Skip to content

Commit 332d2b9

Browse files
committed
chore: resolve merge conflicts and apply code formatting
- Resolve conflicts in README.md and python_repl.py - Apply automatic code formatting via pre-commit hooks
2 parents adbb8c2 + b7ffaed commit 332d2b9

28 files changed

+1709
-153
lines changed

CONTRIBUTING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ Please try to include as much information as you can. Details like these are inc
2424
* Any modifications you've made relevant to the bug
2525
* Anything unusual about your environment or deployment
2626

27+
## Finding contributions to work on
28+
Looking at the existing issues is a great way to find something to contribute to. We label issues that are well-defined and ready for community contributions with the "ready for contribution" label.
29+
30+
Check our [Ready for Contribution](../../issues?q=is%3Aissue%20state%3Aopen%20label%3A%22ready%20for%20contribution%22) issues for items you can work on.
31+
32+
Before starting work on any issue:
33+
1. Check if someone is already assigned or working on it
34+
2. Comment on the issue to express your interest and ask any clarifying questions
35+
3. Wait for maintainer confirmation before beginning significant work
36+
2737

2838
## Development Environment
2939

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Strands Agents Tools is a community-driven project that provides a powerful set
3939
- 📁 **File Operations** - Read, write, and edit files with syntax highlighting and intelligent modifications
4040
- 🖥️ **Shell Integration** - Execute and interact with shell commands securely
4141
- 🧠 **Memory** - Store user and agent memories across agent runs to provide personalized experiences with both Mem0 and Amazon Bedrock Knowledge Bases
42+
- 🕸️ **Web Infrastructure** - Perform web searches, extract page content, and crawl websites with Tavily-powered tools
4243
- 🌐 **HTTP Client** - Make API requests with comprehensive authentication support
4344
- 💬 **Slack Client** - Real-time Slack events, message processing, and Slack API access
4445
- 🐍 **Python Execution** - Run Python code snippets with state persistence, user confirmation for code execution, and safety features
@@ -103,6 +104,10 @@ Below is a comprehensive table of all available tools, how to use them with an a
103104
| editor | `agent.tool.editor(command="view", path="path/to/file.py")` | Advanced file operations like syntax highlighting, pattern replacement, and multi-file edits |
104105
| shell* | `agent.tool.shell(command="ls -la")` | Executing shell commands, interacting with the operating system, running scripts |
105106
| http_request | `agent.tool.http_request(method="GET", url="https://api.example.com/data")` | Making API calls, fetching web data, sending data to external services |
107+
| tavily_search | `agent.tool.tavily_search(query="What is artificial intelligence?", search_depth="advanced")` | Real-time web search optimized for AI agents with a variety of custom parameters |
108+
| tavily_extract | `agent.tool.tavily_extract(urls=["www.tavily.com"], extract_depth="advanced")` | Extract clean, structured content from web pages with advanced processing and noise removal |
109+
| tavily_crawl | `agent.tool.tavily_crawl(url="www.tavily.com", max_depth=2, instructions="Find API docs")` | Crawl websites intelligently starting from a base URL with filtering and extraction |
110+
| tavily_map | `agent.tool.tavily_map(url="www.tavily.com", max_depth=2, instructions="Find all pages")` | Map website structure and discover URLs starting from a base URL without content extraction |
106111
| python_repl* | `agent.tool.python_repl(code="import pandas as pd\ndf = pd.read_csv('data.csv')\nprint(df.head())")` | Running Python code snippets, data analysis, executing complex logic with user confirmation for security |
107112
| calculator | `agent.tool.calculator(expression="2 * sin(pi/4) + log(e**2)")` | Performing mathematical operations, symbolic math, equation solving |
108113
| code_interpreter | `code_interpreter = AgentCoreCodeInterpreter(region="us-west-2"); agent = Agent(tools=[code_interpreter.code_interpreter])` | Execute code in isolated sandbox environments with multi-language support (Python, JavaScript, TypeScript), persistent sessions, and file operations |
@@ -268,6 +273,49 @@ response = agent.tool.http_request(
268273
)
269274
```
270275

276+
### Tavily Search, Extract, Crawl, and Map
277+
278+
```python
279+
from strands import Agent
280+
from strands_tools.tavily import (
281+
tavily_search, tavily_extract, tavily_crawl, tavily_map
282+
)
283+
284+
# For async usage, call the corresponding *_async function with await.
285+
# Synchronous usage
286+
agent = Agent(tools=[tavily_search, tavily_extract, tavily_crawl, tavily_map])
287+
288+
# Real-time web search
289+
result = agent.tool.tavily_search(
290+
query="Latest developments in renewable energy",
291+
search_depth="advanced",
292+
topic="news",
293+
max_results=10,
294+
include_raw_content=True
295+
)
296+
297+
# Extract content from multiple URLs
298+
result = agent.tool.tavily_extract(
299+
urls=["www.tavily.com", "www.apple.com"],
300+
extract_depth="advanced",
301+
format="markdown"
302+
)
303+
304+
# Advanced crawl with instructions and filtering
305+
result = agent.tool.tavily_crawl(
306+
url="www.tavily.com",
307+
max_depth=2,
308+
limit=50,
309+
instructions="Find all API documentation and developer guides",
310+
extract_depth="advanced",
311+
include_images=True
312+
)
313+
314+
# Basic website mapping
315+
result = agent.tool.tavily_map(url="www.tavily.com")
316+
317+
```
318+
271319
### Python Code Execution
272320

273321
*Note: `python_repl` does not work on Windows.*
@@ -682,6 +730,13 @@ These variables affect multiple tools:
682730
|----------------------|-------------|---------|
683731
| MAX_SLEEP_SECONDS | Maximum allowed sleep duration in seconds | 300 |
684732

733+
#### Tavily Search, Extract, Crawl, and Map Tools
734+
735+
| Environment Variable | Description | Default |
736+
|----------------------|-------------|---------|
737+
| TAVILY_API_KEY | Tavily API key (required for all Tavily functionality) | None |
738+
- Visit https://www.tavily.com/ to create a free account and API key.
739+
685740
#### Mem0 Memory Tool
686741

687742
The Mem0 Memory Tool supports three different backend configurations:
@@ -730,6 +785,8 @@ The Mem0 Memory Tool supports three different backend configurations:
730785
| Environment Variable | Description | Default |
731786
|----------------------|-------------|---------|
732787
| PYTHON_REPL_BINARY_MAX_LEN | Maximum length for binary content before truncation | 100 |
788+
| PYTHON_REPL_INTERACTIVE | Whether to enable interactive PTY mode | None |
789+
| PYTHON_REPL_RESET_STATE | Whether to reset the REPL state before execution | None |
733790
| PYTHON_REPL_RESTRICTED_MODE | Enable/disable restricted mode | false |
734791
| PYTHON_REPL_ALLOWED_PATHS | Comma-separated allowed directories | None |
735792
| PYTHON_REPL_ALLOW_CURRENT_DIR | Allow current directory access | true |

pyproject.toml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ dependencies = [
3737
"slack_bolt>=1.23.0,<2.0.0",
3838
"markdownify>=1.0.0,<2.0.0",
3939
"readabilipy>=0.2.0,<1.0.0",
40+
"requests>=2.28.0,<3.0.0",
41+
"aiohttp>=3.8.0,<4.0.0",
4042
# Note: Always want the latest tzdata
4143
"tzdata ; platform_system == 'Windows'",
4244
"botocore>=1.39.7,<2.0.0",
@@ -90,7 +92,7 @@ agent_core_browser = [
9092
agent_core_code_interpreter = [
9193
"bedrock-agentcore==0.1.0"
9294
]
93-
a2a_client = ["a2a-sdk[sql]>=0.2.16"]
95+
a2a_client = ["a2a-sdk[sql]>=0.3.0,<0.4.0"]
9496
diagram = [
9597
"matplotlib>=3.5.0,<4.0.0",
9698
"graphviz>=0.20.0,<1.0.0",
@@ -99,9 +101,9 @@ diagram = [
99101
]
100102
rss = ["feedparser>=6.0.10,<7.0.0", "html2text>=2020.1.16,<2021.0.0"]
101103
use_computer = [
102-
"opencv-python>=4.5.0,<5.0.0",
103-
"psutil>=5.8.0,<6.0.0",
104-
"pyautogui>=0.9.53,<1.0.0",
104+
"opencv-python>=4.5.0,<5.0.0",
105+
"psutil>=5.8.0,<6.0.0",
106+
"pyautogui>=0.9.53,<1.0.0",
105107
"pytesseract>=0.3.8,<1.0.0"
106108
]
107109

@@ -215,4 +217,4 @@ name = "cz_conventional_commits"
215217
tag_format = "v$version"
216218
bump_message = "chore(release): bump version $current_version -> $new_version"
217219
version_files = ["pyproject.toml:version"]
218-
update_changelog_on_bump = true
220+
update_changelog_on_bump = true

src/strands_tools/a2a_client.py

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Key Features:
77
- Agent discovery through agent cards from multiple URLs
88
- Message sending to specific A2A agents
9+
- Push notification support for real-time task completion alerts
910
"""
1011

1112
import asyncio
@@ -14,8 +15,8 @@
1415
from uuid import uuid4
1516

1617
import httpx
17-
from a2a.client import A2ACardResolver, A2AClient
18-
from a2a.types import AgentCard, Message, MessageSendParams, Part, Role, SendMessageRequest, TextPart
18+
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
19+
from a2a.types import AgentCard, Message, Part, PushNotificationConfig, Role, TextPart
1920
from strands import tool
2021
from strands.types.tools import AgentTool
2122

@@ -31,20 +32,35 @@ def __init__(
3132
self,
3233
known_agent_urls: list[str] | None = None,
3334
timeout: int = DEFAULT_TIMEOUT,
35+
webhook_url: str | None = None,
36+
webhook_token: str | None = None,
3437
):
3538
"""
3639
Initialize A2A client tool provider.
3740
3841
Args:
39-
agent_urls: List of A2A agent URLs to use (defaults to None)
40-
timeout: Timeout for HTTP operations in seconds (defaults to 30)
42+
known_agent_urls: List of A2A agent URLs to use (defaults to None)
43+
timeout: Timeout for HTTP operations in seconds (defaults to 300)
44+
webhook_url: Optional webhook URL for push notifications
45+
webhook_token: Optional authentication token for webhook notifications
4146
"""
4247
self.timeout = timeout
4348
self._known_agent_urls: list[str] = known_agent_urls or []
4449
self._discovered_agents: dict[str, AgentCard] = {}
4550
self._httpx_client: httpx.AsyncClient | None = None
51+
self._client_factory: ClientFactory | None = None
4652
self._initial_discovery_done: bool = False
4753

54+
# Push notification configuration
55+
self._webhook_url = webhook_url
56+
self._webhook_token = webhook_token
57+
self._push_config: PushNotificationConfig | None = None
58+
59+
if self._webhook_url and self._webhook_token:
60+
self._push_config = PushNotificationConfig(
61+
id=f"strands-webhook-{uuid4().hex[:8]}", url=self._webhook_url, token=self._webhook_token
62+
)
63+
4864
@property
4965
def tools(self) -> list[AgentTool]:
5066
"""Extract all @tool decorated methods from this instance."""
@@ -66,15 +82,20 @@ async def _ensure_httpx_client(self) -> httpx.AsyncClient:
6682
self._httpx_client = httpx.AsyncClient(timeout=self.timeout)
6783
return self._httpx_client
6884

69-
async def _create_a2a_client(self, url: str) -> A2AClient:
70-
"""Create a new A2A client for the given URL."""
71-
httpx_client = await self._ensure_httpx_client()
72-
agent_card = await self._discover_agent_card(url)
73-
logger.info(f"A2AClient created for {url}")
74-
return A2AClient(httpx_client=httpx_client, agent_card=agent_card)
85+
async def _ensure_client_factory(self) -> ClientFactory:
86+
"""Ensure the ClientFactory is initialized."""
87+
if self._client_factory is None:
88+
httpx_client = await self._ensure_httpx_client()
89+
config = ClientConfig(
90+
httpx_client=httpx_client,
91+
streaming=False, # Use non-streaming mode for simpler response handling
92+
push_notification_configs=[self._push_config] if self._push_config else [],
93+
)
94+
self._client_factory = ClientFactory(config)
95+
return self._client_factory
7596

7697
async def _create_a2a_card_resolver(self, url: str) -> A2ACardResolver:
77-
"""Create a new A2A client for the given URL."""
98+
"""Create a new A2A card resolver for the given URL."""
7899
httpx_client = await self._ensure_httpx_client()
79100
logger.info(f"A2ACardResolver created for {url}")
80101
return A2ACardResolver(httpx_client=httpx_client, base_url=url)
@@ -213,7 +234,11 @@ async def _send_message(
213234

214235
try:
215236
await self._ensure_discovered_known_agents()
216-
client = await self._create_a2a_client(target_agent_url)
237+
238+
# Get the agent card and create client using factory
239+
agent_card = await self._discover_agent_card(target_agent_url)
240+
client_factory = await self._ensure_client_factory()
241+
client = client_factory.create(agent_card)
217242

218243
if message_id is None:
219244
message_id = uuid4().hex
@@ -225,14 +250,45 @@ async def _send_message(
225250
message_id=message_id,
226251
)
227252

228-
request = SendMessageRequest(id=str(uuid4()), params=MessageSendParams(message=message))
229-
230253
logger.info(f"Sending message to {target_agent_url}")
231-
response = await client.send_message(request)
232254

255+
# With streaming=False, this will yield exactly one result
256+
async for event in client.send_message(message):
257+
if isinstance(event, Message):
258+
# Direct message response
259+
return {
260+
"status": "success",
261+
"response": event.model_dump(mode="python", exclude_none=True),
262+
"message_id": message_id,
263+
"target_agent_url": target_agent_url,
264+
}
265+
elif isinstance(event, tuple) and len(event) == 2:
266+
# (Task, UpdateEvent) tuple - extract the task
267+
task, update_event = event
268+
return {
269+
"status": "success",
270+
"response": {
271+
"task": task.model_dump(mode="python", exclude_none=True),
272+
"update": (
273+
update_event.model_dump(mode="python", exclude_none=True) if update_event else None
274+
),
275+
},
276+
"message_id": message_id,
277+
"target_agent_url": target_agent_url,
278+
}
279+
else:
280+
# Fallback for unexpected response types
281+
return {
282+
"status": "success",
283+
"response": {"raw_response": str(event)},
284+
"message_id": message_id,
285+
"target_agent_url": target_agent_url,
286+
}
287+
288+
# This should never be reached with streaming=False
233289
return {
234-
"status": "success",
235-
"response": response.model_dump(mode="python", exclude_none=True),
290+
"status": "error",
291+
"error": "No response received from agent",
236292
"message_id": message_id,
237293
"target_agent_url": target_agent_url,
238294
}

src/strands_tools/browser/__init__.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,34 @@
1919
agent = Agent(tools=[browser.browser])
2020
2121
# Use the browser
22-
agent.tool.browser({
23-
"action": {
24-
"type": "navigate",
25-
"url": "https://example.com"
22+
agent.tool.browser(
23+
browser_input={
24+
"action": {
25+
"type": "init_session",
26+
"description": "Example ession",
27+
"session_name": "example-session"
28+
}
2629
}
27-
})
30+
)
31+
32+
agent.tool.browser(
33+
browser_input={
34+
"action": {
35+
"type": "navigate",
36+
"url": "https://example.com",
37+
"session_name": "example-session"
38+
}
39+
}
40+
)
41+
42+
agent.tool.browser(
43+
browser_input={
44+
"action": {
45+
"type": "close",
46+
"session_name": "example-session"
47+
}
48+
}
49+
)
2850
```
2951
"""
3052

src/strands_tools/browser/agent_core_browser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
logger = logging.getLogger(__name__)
1818

19+
DEFAULT_IDENTIFIER = "aws.browser.v1"
20+
1921

2022
class AgentCoreBrowser(Browser):
2123
"""Bedrock AgentCore browser implementation."""
2224

23-
def __init__(self, region: Optional[str] = None, session_timeout: int = 3600):
25+
def __init__(self, region: Optional[str] = None, identifier: str = DEFAULT_IDENTIFIER, session_timeout: int = 3600):
2426
"""
2527
Initialize the browser.
2628
@@ -44,7 +46,7 @@ async def create_browser_session(self) -> PlaywrightBrowser:
4446

4547
# Create new browser client for this session
4648
session_client = AgentCoreBrowserClient(region=self.region)
47-
session_id = session_client.start(session_timeout_seconds=self.session_timeout)
49+
session_id = session_client.start(identifier=self.identifier, session_timeout_seconds=self.session_timeout)
4850

4951
logger.info(f"started Bedrock AgentCore browser session: {session_id}")
5052

0 commit comments

Comments
 (0)