diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..247edb9 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,328 @@ +openapi: 3.0.0 +info: + title: AgentWork Infrastructure API + version: 1.0.0 + description: Email, calendar, and docs APIs for AI agents + +servers: + - url: http://localhost:8000 + description: Local development server + +paths: + /v1/inboxes: + post: + summary: Create a new inbox + description: Creates a new email inbox and returns the email address and API key + operationId: createInbox + responses: + '201': + description: Inbox created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Inbox' + tags: + - Inbox + + /v1/inboxes/me/messages: + get: + summary: List all messages + description: Retrieve all messages in the authenticated inbox + operationId: listMessages + security: + - BearerAuth: [] + responses: + '200': + description: List of messages + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Message' + tags: + - Messages + + post: + summary: Send an email + description: Send an email from the authenticated inbox + operationId: sendEmail + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailRequest' + responses: + '200': + description: Email sent successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailResponse' + tags: + - Messages + + /v1/inboxes/me/messages/{messageId}: + get: + summary: Get a message + description: Retrieve a specific message by ID + operationId: getMessage + security: + - BearerAuth: [] + parameters: + - name: messageId + in: path + required: true + schema: + type: string + responses: + '200': + description: Message details + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + '404': + description: Message not found + tags: + - Messages + + delete: + summary: Delete a message + description: Delete a specific message + operationId: deleteMessage + security: + - BearerAuth: [] + parameters: + - name: messageId + in: path + required: true + schema: + type: string + responses: + '204': + description: Message deleted successfully + '404': + description: Message not found + tags: + - Messages + + /v1/inboxes/me/events: + get: + summary: List calendar events + description: Retrieve all calendar events + operationId: listEvents + security: + - BearerAuth: [] + responses: + '200': + description: List of events + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Event' + tags: + - Calendar + + post: + summary: Create calendar event + description: Create a new calendar event + operationId: createEvent + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateEventRequest' + responses: + '201': + description: Event created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Event' + tags: + - Calendar + +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: API Key + + schemas: + Inbox: + type: object + required: + - email_address + - api_key + - created_at + properties: + email_address: + type: string + format: email + example: abc123@mail.example.com + api_key: + type: string + example: sk_live_abc123xyz + created_at: + type: string + format: date-time + example: "2026-03-10T14:00:00Z" + + Message: + type: object + required: + - id + - from_email + - to_email + - subject + - body + - created_at + properties: + id: + type: string + example: msg_abc123 + from_email: + type: string + format: email + example: sender@example.com + to_email: + type: string + format: email + example: abc123@mail.example.com + subject: + type: string + example: Hello World + body: + type: string + example: This is the email body + created_at: + type: string + format: date-time + example: "2026-03-10T14:00:00Z" + read: + type: boolean + default: false + attachments: + type: array + items: + $ref: '#/components/schemas/Attachment' + + Attachment: + type: object + properties: + filename: + type: string + example: document.pdf + content_type: + type: string + example: application/pdf + size: + type: integer + example: 102400 + s3_url: + type: string + format: uri + example: https://bucket.s3.amazonaws.com/attachments/document.pdf + + SendEmailRequest: + type: object + required: + - to + - subject + - body + properties: + to: + type: string + format: email + example: recipient@example.com + subject: + type: string + example: Test Email + body: + type: string + example: This is a test email body + + SendEmailResponse: + type: object + properties: + message_id: + type: string + example: msg_xyz789 + status: + type: string + enum: [sent, queued, failed] + example: sent + + Event: + type: object + required: + - id + - title + - start_time + - end_time + properties: + id: + type: string + example: event_abc123 + title: + type: string + example: Team Meeting + description: + type: string + example: Weekly team standup + start_time: + type: string + format: date-time + example: "2026-03-10T14:00:00Z" + end_time: + type: string + format: date-time + example: "2026-03-10T15:00:00Z" + + CreateEventRequest: + type: object + required: + - title + - start_time + - end_time + properties: + title: + type: string + example: Team Meeting + description: + type: string + example: Weekly team standup + start_time: + type: string + format: date-time + end_time: + type: string + format: date-time + + Error: + type: object + properties: + error: + type: string + example: Resource not found + code: + type: string + example: NOT_FOUND + +tags: + - name: Inbox + description: Inbox management operations + - name: Messages + description: Email message operations + - name: Calendar + description: Calendar event operations diff --git a/examples/basic_usage.py b/examples/basic_usage.py new file mode 100644 index 0000000..5f745de --- /dev/null +++ b/examples/basic_usage.py @@ -0,0 +1,107 @@ +""" +AgentWork SDK Examples +""" + +# Example 1: Async usage +import asyncio +from agent_suite_sdk import AgentWorkClient + +async def async_example(): + """Using async client""" + + async with AgentWorkClient() as client: + # Create a new inbox (no API key needed) + inbox = await client.create_inbox() + print(f"📧 Inbox created: {inbox.email_address}") + print(f"🔑 API Key: {inbox.api_key}") + + # Set the API key for subsequent operations + client.api_key = inbox.api_key + + # Send an email + result = await client.send_email( + to="recipient@example.com", + subject="Hello from AgentWork SDK", + body="This is a test email sent using the Python SDK!" + ) + print(f"✅ Email sent: {result}") + + # List messages + messages = await client.list_messages() + for msg in messages: + print(f"📨 From: {msg.from_email}") + print(f" Subject: {msg.subject}") + print(f" Body: {msg.body[:50]}...") + + +# Example 2: Sync usage +from agent_suite_sdk import AgentWorkClientSync + +def sync_example(): + """Using sync client""" + + client = AgentWorkClientSync(api_key="your-api-key") + + # List messages + messages = client.list_messages() + for msg in messages: + print(f"Message: {msg.subject}") + + # Send email + client.send_email( + to="recipient@example.com", + subject="Test", + body="Hello!" + ) + + client.close() + + +# Example 3: With error handling +async def robust_example(): + """With error handling and retries""" + + client = AgentWorkClient( + api_key="your-api-key", + timeout=30.0, + max_retries=3 + ) + + try: + messages = await client.list_messages() + print(f"Found {len(messages)} messages") + + except Exception as e: + print(f"Error: {e}") + + finally: + await client.close() + + +# Example 4: Calendar integration +async def calendar_example(): + """Using calendar features""" + + async with AgentWorkClient(api_key="your-api-key") as client: + # Create event + event = await client.create_event( + title="Team Meeting", + start_time="2026-03-15T10:00:00Z", + end_time="2026-03-15T11:00:00Z", + description="Weekly standup" + ) + print(f"📅 Event created: {event['id']}") + + # List events + events = await client.list_events() + for evt in events: + print(f"📅 {evt['title']} - {evt['start_time']}") + + +# Run examples +if __name__ == "__main__": + print("Running async example...") + asyncio.run(async_example()) + + print("\nRunning sync example...") + sync_example() diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 0000000..cebb97c --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,194 @@ +# AgentWork SDK + +Python SDK for AgentWork Infrastructure API - Email, calendar, and docs APIs for AI agents. + +## Installation + +```bash +pip install agent-suite-sdk +``` + +Or install from source: + +```bash +git clone https://github.com/dmb4086/agentwork-infrastructure.git +cd agentwork-infrastructure +pip install -e ./sdk +``` + +## Quick Start + +### Async Usage + +```python +from agent_suite_sdk import AgentWorkClient + +async with AgentWorkClient() as client: + # Create inbox (no API key needed) + inbox = await client.create_inbox() + print(f"Email: {inbox.email_address}") + + # Set API key for operations + client.api_key = inbox.api_key + + # Send email + await client.send_email( + to="recipient@example.com", + subject="Hello", + body="Test email" + ) + + # List messages + messages = await client.list_messages() + for msg in messages: + print(f"{msg.subject}") +``` + +### Sync Usage + +```python +from agent_suite_sdk import AgentWorkClientSync + +client = AgentWorkClientSync(api_key="your-api-key") +messages = client.list_messages() +client.close() +``` + +## Features + +- ✅ **Async and Sync Support** - Use async/await or traditional sync calls +- ✅ **Type Safety** - Full Pydantic models for request/response validation +- ✅ **Automatic Retries** - Built-in retry logic with exponential backoff +- ✅ **Error Handling** - Comprehensive exception handling +- ✅ **Calendar Integration** - Create and manage calendar events + +## API Reference + +### AgentWorkClient + +Main async client for AgentWork API. + +#### Initialization + +```python +client = AgentWorkClient( + api_key="your-api-key", # Optional + base_url="http://localhost:8000", + timeout=30.0, + max_retries=3 +) +``` + +#### Methods + +##### `create_inbox() -> Inbox` + +Create a new email inbox. + +```python +inbox = await client.create_inbox() +print(inbox.email_address) # abc123@mail.example.com +print(inbox.api_key) # sk_live_abc123 +``` + +##### `list_messages() -> List[Message]` + +List all messages in inbox. + +```python +messages = await client.list_messages() +for msg in messages: + print(f"{msg.from_email}: {msg.subject}") +``` + +##### `send_email(to: str, subject: str, body: str) -> Dict` + +Send an email. + +```python +result = await client.send_email( + to="recipient@example.com", + subject="Hello", + body="Test email" +) +``` + +##### `get_message(message_id: str) -> Message` + +Get a specific message. + +```python +msg = await client.get_message("msg_abc123") +print(msg.body) +``` + +##### `delete_message(message_id: str) -> Dict` + +Delete a message. + +```python +await client.delete_message("msg_abc123") +``` + +### Models + +#### Inbox + +- `email_address: str` - Inbox email address +- `api_key: str` - API key for authentication +- `created_at: str` - Creation timestamp + +#### Message + +- `id: str` - Message ID +- `from_email: str` - Sender email +- `to_email: str` - Recipient email +- `subject: str` - Email subject +- `body: str` - Email body +- `created_at: str` - Timestamp +- `read: bool` - Read status +- `attachments: List[Dict]` - Attachments + +## Error Handling + +The SDK automatically retries failed requests with exponential backoff. + +```python +try: + messages = await client.list_messages() +except httpx.HTTPError as e: + print(f"Request failed: {e}") +``` + +## Examples + +See `/examples/basic_usage.py` for complete examples. + +## Development + +### Running Tests + +```bash +pip install -e ".[dev]" +pytest tests/ +``` + +### Building Package + +```bash +python -m build +``` + +## License + +MIT + +## Links + +- [API Documentation](./docs/api.md) +- [OpenAPI Spec](./openapi.yaml) +- [GitHub Repository](https://github.com/dmb4086/agentwork-infrastructure) + +--- + +🤖 Created by OpenClaw Bounty Bot diff --git a/sdk/agent_suite_sdk/__init__.py b/sdk/agent_suite_sdk/__init__.py new file mode 100644 index 0000000..c6a55ac --- /dev/null +++ b/sdk/agent_suite_sdk/__init__.py @@ -0,0 +1,21 @@ +""" +AgentWork SDK +Python client for AgentWork Infrastructure API +""" + +__version__ = "0.1.0" +__author__ = "OpenClaw Bot" + +from .client import ( + AgentWorkClient, + AgentWorkClientSync, + Message, + Inbox +) + +__all__ = [ + 'AgentWorkClient', + 'AgentWorkClientSync', + 'Message', + 'Inbox' +] diff --git a/sdk/agent_suite_sdk/client.py b/sdk/agent_suite_sdk/client.py new file mode 100644 index 0000000..1b9f0a0 --- /dev/null +++ b/sdk/agent_suite_sdk/client.py @@ -0,0 +1,302 @@ +""" +AgentWork SDK - Python client for AgentWork Infrastructure API +""" +import httpx +from typing import Optional, List, Dict, Any +from pydantic import BaseModel, EmailStr +import asyncio + + +class Message(BaseModel): + """Email message model""" + id: str + from_email: str + to_email: str + subject: str + body: str + created_at: str + read: bool = False + attachments: List[Dict[str, Any]] = [] + + +class Inbox(BaseModel): + """Inbox model""" + email_address: str + api_key: str + created_at: str + + +class AgentWorkClient: + """ + AgentWork API Client + + A Python SDK for interacting with AgentWork Infrastructure API. + + Example: + >>> client = AgentWorkClient(api_key="your-api-key") + >>> inbox = await client.create_inbox() + >>> messages = await client.list_messages() + """ + + def __init__( + self, + api_key: Optional[str] = None, + base_url: str = "http://localhost:8000", + timeout: float = 30.0, + max_retries: int = 3 + ): + """ + Initialize AgentWork client + + Args: + api_key: Your API key (optional for create_inbox) + base_url: API base URL + timeout: Request timeout in seconds + max_retries: Maximum number of retries on failure + """ + self.base_url = base_url.rstrip('/') + self.api_key = api_key + self.timeout = timeout + self.max_retries = max_retries + self._client = httpx.AsyncClient(timeout=timeout) + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + + async def close(self): + """Close the HTTP client""" + await self._client.aclose() + + def _get_headers(self) -> Dict[str, str]: + """Get request headers""" + headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + if self.api_key: + headers['Authorization'] = f'Bearer {self.api_key}' + return headers + + async def _request( + self, + method: str, + endpoint: str, + **kwargs + ) -> Dict[str, Any]: + """ + Make HTTP request with retries + + Args: + method: HTTP method (GET, POST, etc.) + endpoint: API endpoint + **kwargs: Additional request parameters + + Returns: + Response JSON + + Raises: + httpx.HTTPError: Request failed after all retries + """ + url = f"{self.base_url}{endpoint}" + headers = self._get_headers() + + for attempt in range(self.max_retries): + try: + response = await self._client.request( + method=method, + url=url, + headers=headers, + **kwargs + ) + response.raise_for_status() + return response.json() + except httpx.HTTPStatusError as e: + if attempt == self.max_retries - 1: + raise + await asyncio.sleep(2 ** attempt) # Exponential backoff + except httpx.RequestError as e: + if attempt == self.max_retries - 1: + raise + await asyncio.sleep(2 ** attempt) + + # Inbox Operations + + async def create_inbox(self) -> Inbox: + """ + Create a new email inbox + + Returns: + Inbox object with email address and API key + + Example: + >>> inbox = await client.create_inbox() + >>> print(inbox.email_address) + """ + data = await self._request('POST', '/v1/inboxes') + return Inbox(**data) + + # Message Operations + + async def list_messages(self) -> List[Message]: + """ + List all messages in the inbox + + Returns: + List of Message objects + + Example: + >>> messages = await client.list_messages() + >>> for msg in messages: + ... print(f"From: {msg.from_email}, Subject: {msg.subject}") + """ + data = await self._request('GET', '/v1/inboxes/me/messages') + return [Message(**msg) for msg in data] + + async def send_email( + self, + to: str, + subject: str, + body: str + ) -> Dict[str, Any]: + """ + Send an email + + Args: + to: Recipient email address + subject: Email subject + body: Email body + + Returns: + Send result + + Example: + >>> result = await client.send_email( + ... to="recipient@example.com", + ... subject="Hello", + ... body="This is a test email" + ... ) + """ + payload = { + 'to': to, + 'subject': subject, + 'body': body + } + return await self._request( + 'POST', + '/v1/inboxes/me/send', + json=payload + ) + + async def get_message(self, message_id: str) -> Message: + """ + Get a specific message by ID + + Args: + message_id: Message ID + + Returns: + Message object + """ + data = await self._request( + 'GET', + f'/v1/inboxes/me/messages/{message_id}' + ) + return Message(**data) + + async def delete_message(self, message_id: str) -> Dict[str, Any]: + """ + Delete a message + + Args: + message_id: Message ID + + Returns: + Delete result + """ + return await self._request( + 'DELETE', + f'/v1/inboxes/me/messages/{message_id}' + ) + + # Calendar Operations (if available) + + async def list_events(self) -> List[Dict[str, Any]]: + """ + List calendar events + + Returns: + List of event objects + """ + return await self._request('GET', '/v1/inboxes/me/events') + + async def create_event( + self, + title: str, + start_time: str, + end_time: str, + description: Optional[str] = None + ) -> Dict[str, Any]: + """ + Create a calendar event + + Args: + title: Event title + start_time: Event start time (ISO format) + end_time: Event end time (ISO format) + description: Event description + + Returns: + Created event + """ + payload = { + 'title': title, + 'start_time': start_time, + 'end_time': end_time, + 'description': description + } + return await self._request( + 'POST', + '/v1/inboxes/me/events', + json=payload + ) + + +# Sync wrapper for synchronous usage +class AgentWorkClientSync: + """Synchronous wrapper for AgentWorkClient""" + + def __init__(self, *args, **kwargs): + self._async_client = AgentWorkClient(*args, **kwargs) + + def _run_async(self, coro): + """Run async coroutine synchronously""" + loop = asyncio.get_event_loop() + if loop.is_running(): + # If already in async context, create new loop + import concurrent.futures + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(asyncio.run, coro) + return future.result() + else: + return loop.run_until_complete(coro) + + def create_inbox(self) -> Inbox: + """Create inbox (sync)""" + return self._run_async(self._async_client.create_inbox()) + + def list_messages(self) -> List[Message]: + """List messages (sync)""" + return self._run_async(self._async_client.list_messages()) + + def send_email(self, to: str, subject: str, body: str) -> Dict[str, Any]: + """Send email (sync)""" + return self._run_async( + self._async_client.send_email(to, subject, body) + ) + + def close(self): + """Close client""" + self._run_async(self._async_client.close()) diff --git a/sdk/setup.py b/sdk/setup.py new file mode 100644 index 0000000..6f2c027 --- /dev/null +++ b/sdk/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup, find_packages + +setup( + name="agent-suite-sdk", + version="0.1.0", + description="Python SDK for AgentWork Infrastructure API", + author="OpenClaw Bot", + author_email="bot@openclaw.ai", + url="https://github.com/dmb4086/agentwork-infrastructure", + packages=find_packages(), + install_requires=[ + "httpx>=0.24.0", + "pydantic>=2.0.0", + ], + extras_require={ + "dev": [ + "pytest>=7.0.0", + "pytest-asyncio>=0.21.0", + ], + }, + python_requires=">=3.8", + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + ], +)