Metadata-Version: 2.4
Name: optimizely-opal.opal-tools-sdk
Version: 0.1.28.dev0
Summary: SDK for creating Opal-compatible tools services
Home-page: https://github.com/optimizely/opal-tools-sdk
Author: Optimizely
Author-email: Optimizely <opal-team@optimizely.com>
License: MIT
Project-URL: Homepage, https://github.com/optimizely/opal-tools-sdk
Project-URL: Bug Tracker, https://github.com/optimizely/opal-tools-sdk/issues
Keywords: opal,tools,sdk,ai,llm
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.10
Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: fastapi>=0.100.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: httpx>=0.24.1
Provides-Extra: dev
Requires-Dist: datamodel-code-generator>=0.25.0; extra == "dev"
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Dynamic: author
Dynamic: home-page
Dynamic: requires-python

# Opal Tools SDK for Python

This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.

## Features

- Easy definition of tool functions with decorators
- Automatic generation of discovery endpoints
- Parameter validation and type checking
- Authentication helpers
- FastAPI integration
- Island components for interactive UI responses

## Installation

```bash
pip install optimizely-opal.opal-tools-sdk
```

Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:

```python
# Import using the package name
from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
```

## Usage

```python
from opal_tools_sdk import ToolsService, tool
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()
tools_service = ToolsService(app)

class WeatherParameters(BaseModel):
    location: str
    units: str = "metric"

@tool("get_weather", "Gets current weather for a location")
async def get_weather(parameters: WeatherParameters):
    # Implementation...
    return {"temperature": 22, "condition": "sunny"}

# Discovery endpoint is automatically created at /discovery
```

## Authentication

The SDK provides two ways to require authentication for your tools:

### 1. Using the `@requires_auth` decorator

```python
from opal_tools_sdk import ToolsService, tool, AuthData
from opal_tools_sdk.auth import requires_auth
from pydantic import BaseModel
from fastapi import FastAPI
from typing import Optional

app = FastAPI()
tools_service = ToolsService(app)

class CalendarParameters(BaseModel):
    date: str
    timezone: str = "UTC"

# Single authentication requirement
@requires_auth(provider="google", scope_bundle="calendar", required=True)
@tool("get_calendar_events", "Gets calendar events for a date")
async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
    # The auth_data parameter contains authentication information
    if auth_data:
        token = auth_data["credentials"]["access_token"]

    # Use the token to make authenticated requests
    # ...

    return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}

# Multiple authentication requirements (tool can work with either provider)
@requires_auth(provider="google", scope_bundle="calendar", required=True)
@requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
@tool("get_calendar_availability", "Check calendar availability")
async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
    provider = ""
    token = ""
    
    if auth_data:
        provider = auth_data["provider"]
        token = auth_data["credentials"]["access_token"]

        if provider == "google":
            # Use Google Calendar API
            pass
        elif provider == "microsoft":
            # Use Microsoft Outlook API
            pass

    return {"available": True, "provider_used": provider}
```

### 2. Specifying auth requirements in the `@tool` decorator

```python
@tool(
    "get_email",
    "Gets emails from the user's inbox",
    auth_requirements=[
        {"provider": "google", "scope_bundle": "gmail", "required": True}
    ]
)
async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
    # Implementation...
    return {"emails": ["Email 1", "Email 2"]}
```

## Island Components

The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.

### Weather Tool with Interactive Island

```python
from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()
tools_service = ToolsService(app)

class WeatherParameters(BaseModel):
    location: str
    units: str = "metric"

@tool("get_weather", "Gets current weather for a location")
async def get_weather(parameters: WeatherParameters):
    # Get weather data (implementation details omitted)
    weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
    
    # Create an interactive island for weather settings
    island = IslandConfig(
        fields=[
            IslandConfig.Field(
                name="location",
                label="Location",
                type="string",
                value=parameters.location
            ),
            IslandConfig.Field(
                name="units",
                label="Temperature Units",
                type="string",
                value=parameters.units,
                options=["metric", "imperial", "kelvin"]
            ),
            IslandConfig.Field(
                name="current_temp",
                label="Current Temperature",
                type="string",
                value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
            )
        ],
        actions=[
            IslandConfig.Action(
                name="refresh_weather",
                label="Refresh Weather",
                type="button",
                endpoint="/tools/get_weather",
                operation="update"
            )
        ]
    )
    
    return IslandResponse.create([island])
```

### Island Components

#### IslandConfig.Field
Fields represent data inputs in the UI:
- `name`: Programmatic field identifier
- `label`: Human-readable label
- `type`: Field type (`"string"`, `"boolean"`, `"json"`)
- `value`: Current field value (optional)
- `hidden`: Whether to hide from user (optional, default: False)
- `options`: Available options for selection (optional)

#### IslandConfig.Action
Actions represent buttons or operations:
- `name`: Programmatic action identifier
- `label`: Human-readable button label
- `type`: UI element type (typically `"button"`)
- `endpoint`: API endpoint to call
- `operation`: Operation type (default: `"create"`)

#### IslandConfig
Contains the complete island configuration:
- `fields`: List of IslandConfig.Field objects
- `actions`: List of IslandConfig.Action objects
- `type`: Island type for UI rendering (optional, default: `None`)
- `icon`: Icon to display in the island (optional, default: `None`)

#### IslandResponse
The response wrapper for islands:
- Use `IslandResponse.create([islands])` to create responses
- Supports multiple islands per response

## Resources & Proteus UI

The SDK supports defining MCP resources that serve dynamic UI specifications using the Proteus framework. This enables tools to render rich, interactive interfaces without hardcoded frontend integrations.

For the full Proteus component reference and visual designer, see the [Proteus documentation](https://optimizely-axiom.github.io/optiaxiom/guides/proteus/).

### Defining a Resource with `@resource`

Use the `@resource` decorator to register a function as an MCP resource:

```python
from opal_tools_sdk import UI
from opal_tools_sdk.decorators import resource

@resource(
    uri="ui://my-app/create-form",
    name="create-form",
    description="Form for creating new items",
)
async def get_create_form():
    return UI.Document(
        title="Create Item",
        body=[
            UI.Heading(children="New Item"),
            UI.Field(
                label="Item Name",
                children=UI.Input(name="item_name", placeholder="Enter item name"),
            ),
            UI.Field(
                label="Description",
                children=UI.Textarea(name="description", placeholder="Enter description"),
            ),
        ],
        actions=[
            UI.Action(children="Save", appearance="primary"),
            UI.CancelAction(children="Cancel"),
        ],
    )
```

**Parameters:**
- `uri` (required): Unique URI for the resource (e.g., `"ui://my-app/create-form"`)
- `name` (required): Name of the resource
- `description` (optional): Description of the resource
- `mime_type` (optional): MIME type of the content. Auto-set to `"application/vnd.opal.proteus+json"` when returning a `UI.Document`
- `title` (optional): Human-readable title

The handler function can return either a `str` (manual JSON serialization) or a `UI.Document` (automatic serialization with MIME type set automatically).

### Linking a Tool to a UI Resource

Use the `ui_resource` parameter on `@tool` to associate a tool with a Proteus UI resource. The frontend fetches and renders the resource when the tool is invoked:

```python
from opal_tools_sdk import tool
from pydantic import BaseModel, Field

class CreateItemParams(BaseModel):
    item_name: str = Field(description="Name of the item")
    description: str = Field(description="Item description")

@tool(
    "create_item",
    "Create a new item",
    ui_resource="ui://my-app/create-form",
)
async def create_item(parameters: CreateItemParams):
    return {"id": "item-123", "name": parameters.item_name, "status": "created"}
```

### Building UI with `UI.Document`

Import the `UI` namespace from `opal_tools_sdk` or `opal_tools_sdk.ui`. It provides type-safe builders for all Proteus components:

```python
from opal_tools_sdk import UI
```

**Available components:**

| Category | Components |
|----------|-----------|
| Layout | `UI.Document`, `UI.Group`, `UI.Card`, `UI.CardHeader`, `UI.CardLink`, `UI.Separator` |
| Typography | `UI.Heading`, `UI.Text`, `UI.Link` |
| Data Display | `UI.Avatar`, `UI.Badge`, `UI.DataTable`, `UI.Chart`, `UI.IconCalendar`, `UI.Image`, `UI.ImageCarousel`, `UI.Time` |
| Form Controls | `UI.Field`, `UI.Input`, `UI.Textarea`, `UI.Select`, `UI.SelectTrigger`, `UI.SelectContent`, `UI.Switch`, `UI.Range`, `UI.Question` |
| Actions | `UI.Action`, `UI.CancelAction` |
| Dynamic | `UI.Value`, `UI.Map`, `UI.MapIndex`, `UI.Show`, `UI.Concat`, `UI.Zip` |

**Data binding** with `UI.Value` resolves paths from the tool response:

```python
@resource(uri="ui://my-app/results", name="results")
async def get_results():
    return UI.Document(
        title=UI.Value(path="/title"),
        body=UI.Map(
            path="/items",
            children=UI.Text(children=UI.Value(path="name")),
        ),
    )
```

**Conditional rendering** with `UI.Show`:

```python
UI.Show(
    when={"!!": UI.Value(path="/error")},
    children=UI.Text(children="An error occurred", color="fg.error"),
)
```

The MIME type constant is available as `UI.MIME_TYPE` (`"application/vnd.opal.proteus+json"`).

## Type Definitions

The SDK provides several TypedDict and dataclass definitions for better type safety:

### Authentication Types
- `AuthData`: TypedDict containing provider and credentials information
- `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
- `AuthRequirement`: Dataclass for specifying authentication requirements

### Execution Environment
- `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)

### Parameter Types
- `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
- `Parameter`: Dataclass for tool parameter definitions
- `Function`: Dataclass for complete tool function definitions

These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.

## Documentation

See full documentation for more examples and configuration options.
