Skip to content

Commit 6d81109

Browse files
authored
Merge branch 'strands-agents:main' into main
2 parents 9b20e12 + c2ba0f7 commit 6d81109

130 files changed

Lines changed: 10328 additions & 428 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codecov.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: 90% # overall coverage threshold
6+
patch:
7+
default:
8+
target: 90% # patch coverage threshold
9+
base: auto
10+
# Only post patch coverage on decreases
11+
only_pulls: true
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: PR Size Labeler
2+
3+
on:
4+
pull_request_target:
5+
branches: main
6+
7+
jobs:
8+
label-size:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
pull-requests: write
12+
issues: write
13+
steps:
14+
- name: Calculate PR size and apply label
15+
uses: actions/github-script@v8
16+
with:
17+
script: |
18+
const pr = context.payload.pull_request;
19+
const totalChanges = pr.additions + pr.deletions;
20+
21+
// Remove existing size labels
22+
const labels = await github.rest.issues.listLabelsOnIssue({
23+
owner: context.repo.owner,
24+
repo: context.repo.repo,
25+
issue_number: context.payload.pull_request.number
26+
});
27+
28+
for (const label of labels.data) {
29+
if (label.name.startsWith('size/')) {
30+
await github.rest.issues.removeLabel({
31+
owner: context.repo.owner,
32+
repo: context.repo.repo,
33+
issue_number: context.payload.pull_request.number,
34+
name: label.name
35+
});
36+
}
37+
}
38+
39+
// Determine and apply new size label
40+
let sizeLabel;
41+
if (totalChanges <= 20) sizeLabel = 'size/xs';
42+
else if (totalChanges <= 100) sizeLabel = 'size/s';
43+
else if (totalChanges <= 500) sizeLabel = 'size/m';
44+
else if (totalChanges <= 1000) sizeLabel = 'size/l';
45+
else {
46+
sizeLabel = 'size/xl';
47+
}
48+
49+
await github.rest.issues.addLabels({
50+
owner: context.repo.owner,
51+
repo: context.repo.repo,
52+
issue_number: context.payload.pull_request.number,
53+
labels: [sizeLabel]
54+
});
55+
56+
if (sizeLabel === 'size/xl') {
57+
core.setFailed(`PR is too large (${totalChanges} lines). Please split into smaller PRs.`);
58+
}

.github/workflows/test-lint.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ jobs:
6666
id: tests
6767
run: hatch test tests --cover
6868
continue-on-error: false
69+
70+
- name: Upload coverage reports to Codecov
71+
uses: codecov/codecov-action@v5
72+
with:
73+
token: ${{ secrets.CODECOV_TOKEN }}
6974
lint:
7075
name: Lint
7176
runs-on: ubuntu-latest

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ __pycache__*
1111
.vscode
1212
dist
1313
repl_state
14-
.kiro
14+
.kiro
15+
uv.lock

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ Before starting work on any issue:
3636
3. Wait for maintainer confirmation before beginning significant work
3737

3838

39+
## Development Tenets
40+
Our team follows these core principles when designing and implementing features. These tenets help us make consistent decisions, resolve trade-offs, and maintain the quality and coherence of the SDK. When contributing, please consider how your changes align with these principles:
41+
42+
1. **Simple at any scale:** We believe that simple things should be simple. The same clean abstractions that power a weekend prototype should scale effortlessly to production workloads. We reject the notion that enterprise-grade means enterprise-complicated - Strands remains approachable whether it's your first agent or your millionth.
43+
2. **Extensible by design:** We allow for as much configuration as possible, from hooks to model providers, session managers, tools, etc. We meet customers where they are with flexible extension points that are simple to integrate with.
44+
3. **Composability:** Primitives are building blocks with each other. Each feature of Strands is developed with all other features in mind, they are consistent and complement one another.
45+
4. **The obvious path is the happy path:** Through intuitive naming, helpful error messages, and thoughtful API design, we guide developers toward correct patterns and away from common pitfalls.
46+
5. **We are accessible to humans and agents:** Strands is designed for both humans and AI to understand equally well. We don’t take shortcuts on curated DX for humans and we go the extra mile to make sure coding assistants can help you use those interfaces the right way.
47+
6. **Embrace common standards:** We respect what came before, and do not want to reinvent something that is already widely adopted or done better.
48+
49+
When proposing solutions or reviewing code, we reference these principles to guide our decisions. If two approaches seem equally valid, we choose the one that best aligns with our tenets.
50+
3951
## Development Environment
4052

4153
This project uses [hatchling](https://hatch.pypa.io/latest/build/#hatchling) as the build backend and [hatch](https://hatch.pypa.io/latest/) for development workflow management.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies = [
3030
"boto3>=1.26.0,<2.0.0",
3131
"botocore>=1.29.0,<2.0.0",
3232
"docstring_parser>=0.15,<1.0",
33+
"jsonschema>=4.0.0,<5.0.0",
3334
"mcp>=1.11.0,<2.0.0",
3435
"pydantic>=2.4.0,<3.0.0",
3536
"typing-extensions>=4.13.2,<5.0.0",

src/strands/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,12 @@
55
from .tools.decorator import tool
66
from .types.tools import ToolContext
77

8-
__all__ = ["Agent", "agent", "models", "tool", "types", "telemetry", "ToolContext"]
8+
__all__ = [
9+
"Agent",
10+
"agent",
11+
"models",
12+
"tool",
13+
"ToolContext",
14+
"types",
15+
"telemetry",
16+
]

src/strands/_async.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Private async execution utilities."""
2+
3+
import asyncio
4+
from concurrent.futures import ThreadPoolExecutor
5+
from typing import Awaitable, Callable, TypeVar
6+
7+
T = TypeVar("T")
8+
9+
10+
def run_async(async_func: Callable[[], Awaitable[T]]) -> T:
11+
"""Run an async function in a separate thread to avoid event loop conflicts.
12+
13+
This utility handles the common pattern of running async code from sync contexts
14+
by using ThreadPoolExecutor to isolate the async execution.
15+
16+
Args:
17+
async_func: A callable that returns an awaitable
18+
19+
Returns:
20+
The result of the async function
21+
"""
22+
23+
async def execute_async() -> T:
24+
return await async_func()
25+
26+
def execute() -> T:
27+
return asyncio.run(execute_async())
28+
29+
with ThreadPoolExecutor() as executor:
30+
future = executor.submit(execute)
31+
return future.result()

src/strands/_exception_notes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Exception note utilities for Python 3.10+ compatibility."""
2+
3+
# add_note was added in 3.11 - we hoist to a constant to facilitate testing
4+
supports_add_note = hasattr(Exception, "add_note")
5+
6+
7+
def add_exception_note(exception: Exception, note: str) -> None:
8+
"""Add a note to an exception, compatible with Python 3.10+.
9+
10+
Uses add_note() if it's available (Python 3.11+) or modifies the exception message if it is not.
11+
"""
12+
if supports_add_note:
13+
# we ignore the mypy error because the version-check for add_note is extracted into a constant up above and
14+
# mypy doesn't detect that
15+
exception.add_note(note) # type: ignore
16+
else:
17+
# For Python 3.10, append note to the exception message
18+
if hasattr(exception, "args") and exception.args:
19+
exception.args = (f"{exception.args[0]}\n{note}",) + exception.args[1:]
20+
else:
21+
exception.args = (note,)

0 commit comments

Comments
 (0)