optimizely-opal.opal-tools-sdk 0.1.11.dev0__tar.gz → 0.1.14.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/PKG-INFO +5 -1
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/__init__.py +6 -2
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/decorators.py +92 -2
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/models.py +4 -0
- optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/proteus.py +2415 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/service.py +89 -4
- optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/ui.py +3 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +5 -1
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/SOURCES.txt +5 -1
- optimizely_opal_opal_tools_sdk-0.1.14.dev0/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +8 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/pyproject.toml +10 -9
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.py +1 -1
- optimizely_opal_opal_tools_sdk-0.1.14.dev0/tests/test_integration.py +352 -0
- optimizely_opal_opal_tools_sdk-0.1.14.dev0/tests/test_proteus.py +106 -0
- optimizely_opal_opal_tools_sdk-0.1.11.dev0/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +0 -3
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/README.md +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/_registry.py +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/auth.py +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/logging.py +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/dependency_links.txt +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/top_level.txt +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.cfg +0 -0
{optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: optimizely-opal.opal-tools-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14.dev0
|
|
4
4
|
Summary: SDK for creating Opal-compatible tools services
|
|
5
5
|
Home-page: https://github.com/optimizely/opal-tools-sdk
|
|
6
6
|
Author: Optimizely
|
|
@@ -18,6 +18,10 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
Requires-Dist: fastapi>=0.100.0
|
|
19
19
|
Requires-Dist: pydantic>=2.0.0
|
|
20
20
|
Requires-Dist: httpx>=0.24.1
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: datamodel-code-generator>=0.25.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
21
25
|
Dynamic: author
|
|
22
26
|
Dynamic: home-page
|
|
23
27
|
Dynamic: requires-python
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
from .service import ToolsService
|
|
2
|
-
from .decorators import tool
|
|
2
|
+
from .decorators import tool, resource
|
|
3
3
|
from .auth import requires_auth
|
|
4
4
|
from .logging import register_logger_factory
|
|
5
5
|
from .models import AuthData, AuthRequirement, Credentials, Environment, IslandConfig, IslandResponse
|
|
6
|
+
from .proteus import UI
|
|
6
7
|
|
|
7
|
-
__version__ = "0.1.
|
|
8
|
+
__version__ = "0.1.14-dev"
|
|
8
9
|
__all__ = [
|
|
9
10
|
"ToolsService",
|
|
10
11
|
"tool",
|
|
12
|
+
"resource",
|
|
11
13
|
"requires_auth",
|
|
12
14
|
"register_logger_factory",
|
|
13
15
|
# Models
|
|
@@ -17,4 +19,6 @@ __all__ = [
|
|
|
17
19
|
"Environment",
|
|
18
20
|
"IslandConfig",
|
|
19
21
|
"IslandResponse",
|
|
22
|
+
# UI
|
|
23
|
+
"UI",
|
|
20
24
|
]
|
|
@@ -10,7 +10,12 @@ from .logging import get_logger
|
|
|
10
10
|
|
|
11
11
|
logger = get_logger(__name__)
|
|
12
12
|
|
|
13
|
-
def tool(
|
|
13
|
+
def tool(
|
|
14
|
+
name: str,
|
|
15
|
+
description: str,
|
|
16
|
+
auth_requirements: Optional[List[Dict[str, Any]]] = None,
|
|
17
|
+
ui_resource: Optional[str] = None,
|
|
18
|
+
):
|
|
14
19
|
"""Decorator to register a function as an Opal tool.
|
|
15
20
|
|
|
16
21
|
Args:
|
|
@@ -19,6 +24,8 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
19
24
|
auth_requirements: Authentication requirements (optional)
|
|
20
25
|
Format: [{"provider": "oauth_provider", "scope_bundle": "permissions_scope", "required": True}, ...]
|
|
21
26
|
Example: [{"provider": "google", "scope_bundle": "calendar", "required": True}]
|
|
27
|
+
ui_resource: URI of associated UI resource for dynamic rendering (optional)
|
|
28
|
+
Example: "ui://my-app/create-form"
|
|
22
29
|
|
|
23
30
|
Returns:
|
|
24
31
|
Decorator function
|
|
@@ -27,6 +34,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
27
34
|
If your tool requires authentication, define your handler function with two parameters:
|
|
28
35
|
async def my_tool(parameters: ParametersModel, auth_data: Optional[Dict] = None):
|
|
29
36
|
...
|
|
37
|
+
|
|
38
|
+
If ui_resource is specified, the frontend can fetch the resource to render a dynamic UI
|
|
39
|
+
for this tool without hardcoded integrations.
|
|
30
40
|
"""
|
|
31
41
|
def decorator(func: Callable):
|
|
32
42
|
# Get the ToolsService instance from the global registry
|
|
@@ -152,7 +162,87 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
152
162
|
handler=func,
|
|
153
163
|
parameters=parameters,
|
|
154
164
|
endpoint=endpoint,
|
|
155
|
-
auth_requirements=auth_req_list
|
|
165
|
+
auth_requirements=auth_req_list,
|
|
166
|
+
ui_resource=ui_resource
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return func
|
|
170
|
+
|
|
171
|
+
return decorator
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def resource(
|
|
175
|
+
uri: str,
|
|
176
|
+
name: str,
|
|
177
|
+
description: Optional[str] = None,
|
|
178
|
+
mime_type: Optional[str] = None,
|
|
179
|
+
title: Optional[str] = None,
|
|
180
|
+
):
|
|
181
|
+
"""Decorator to register a function as an MCP resource.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
uri: The unique URI for this resource (e.g., "ui://my-app/create-form")
|
|
185
|
+
name: Name of the resource
|
|
186
|
+
description: Description of the resource (optional)
|
|
187
|
+
mime_type: MIME type of the resource content (optional, e.g., "application/vnd.opal.proteus+json")
|
|
188
|
+
title: Human-readable title for the resource (optional)
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Decorator function
|
|
192
|
+
|
|
193
|
+
Note:
|
|
194
|
+
The handler function should be async and return either a str or a UI.Document.
|
|
195
|
+
|
|
196
|
+
When returning a UI.Document, the SDK will automatically:
|
|
197
|
+
- Serialize it to JSON based on the document's Pydantic model data
|
|
198
|
+
- Set the MIME type to "application/vnd.opal.proteus+json" (if not specified)
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
# Example 1: Returning a string (manual serialization)
|
|
202
|
+
@resource(
|
|
203
|
+
uri="ui://my-app/create-form",
|
|
204
|
+
name="create-form",
|
|
205
|
+
description="Form for creating new items",
|
|
206
|
+
mime_type="application/vnd.opal.proteus+json"
|
|
207
|
+
)
|
|
208
|
+
async def get_create_form() -> str:
|
|
209
|
+
proteus_spec = {"type": "form", "fields": [...]}
|
|
210
|
+
return json.dumps(proteus_spec)
|
|
211
|
+
|
|
212
|
+
# Example 2: Returning a UI.Document (automatic serialization)
|
|
213
|
+
from opal_tools_sdk.ui import UI
|
|
214
|
+
|
|
215
|
+
@resource(
|
|
216
|
+
uri="ui://my-app/create-form",
|
|
217
|
+
name="create-form",
|
|
218
|
+
description="Form for creating new items"
|
|
219
|
+
)
|
|
220
|
+
async def get_create_form() -> UI.Document:
|
|
221
|
+
return UI.Document(
|
|
222
|
+
body=[
|
|
223
|
+
UI.Heading(children="Create Item"),
|
|
224
|
+
UI.Field(label="Name", children=UI.Input(name="item_name"))
|
|
225
|
+
],
|
|
226
|
+
actions=[UI.Action(children="Save", appearance="primary")]
|
|
227
|
+
)
|
|
228
|
+
"""
|
|
229
|
+
def decorator(func: Callable):
|
|
230
|
+
# Get the ToolsService instance from the global registry
|
|
231
|
+
from . import _registry
|
|
232
|
+
|
|
233
|
+
logger.info(f"Registering resource {name} with URI {uri}")
|
|
234
|
+
|
|
235
|
+
if not _registry.services:
|
|
236
|
+
logger.warning("No services registered in registry! Make sure to create ToolsService before decorating functions.")
|
|
237
|
+
|
|
238
|
+
for service in _registry.services:
|
|
239
|
+
service.register_resource(
|
|
240
|
+
uri=uri,
|
|
241
|
+
name=name,
|
|
242
|
+
description=description,
|
|
243
|
+
mime_type=mime_type,
|
|
244
|
+
title=title,
|
|
245
|
+
handler=func,
|
|
156
246
|
)
|
|
157
247
|
|
|
158
248
|
return func
|
|
@@ -83,6 +83,7 @@ class Function:
|
|
|
83
83
|
endpoint: str
|
|
84
84
|
auth_requirements: Optional[List[AuthRequirement]] = None
|
|
85
85
|
http_method: str = "POST"
|
|
86
|
+
ui_resource: Optional[str] = None # UI resource URI for dynamic UI rendering
|
|
86
87
|
|
|
87
88
|
def to_dict(self) -> Dict[str, Any]:
|
|
88
89
|
"""Convert to dictionary for the discovery endpoint."""
|
|
@@ -97,6 +98,9 @@ class Function:
|
|
|
97
98
|
if self.auth_requirements:
|
|
98
99
|
result["auth_requirements"] = [auth.to_dict() for auth in self.auth_requirements]
|
|
99
100
|
|
|
101
|
+
if self.ui_resource:
|
|
102
|
+
result["ui_resource"] = self.ui_resource
|
|
103
|
+
|
|
100
104
|
return result
|
|
101
105
|
|
|
102
106
|
|