optimizely-opal.opal-tools-sdk 0.1.12.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.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/PKG-INFO +6 -95
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/README.md +5 -94
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/__init__.py +6 -6
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/decorators.py +85 -4
- {optimizely_opal_opal_tools_sdk-0.1.12.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.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/service.py +89 -26
- optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/ui.py +3 -0
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +6 -95
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/SOURCES.txt +4 -3
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/pyproject.toml +1 -1
- {optimizely_opal_opal_tools_sdk-0.1.12.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.12.dev0/opal_tools_sdk/block.py +0 -9985
- optimizely_opal_opal_tools_sdk-0.1.12.dev0/tests/test_block.py +0 -105
- optimizely_opal_opal_tools_sdk-0.1.12.dev0/tests/test_integration.py +0 -181
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/_registry.py +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/auth.py +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/logging.py +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.12.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.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.12.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.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.cfg +0 -0
{optimizely_opal_opal_tools_sdk-0.1.12.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
|
|
@@ -37,8 +37,7 @@ This SDK simplifies the creation of tools services compatible with the Opal Tool
|
|
|
37
37
|
- Parameter validation and type checking
|
|
38
38
|
- Authentication helpers
|
|
39
39
|
- FastAPI integration
|
|
40
|
-
-
|
|
41
|
-
- Island components for interactive UI responses (legacy)
|
|
40
|
+
- Island components for interactive UI responses
|
|
42
41
|
|
|
43
42
|
## Installation
|
|
44
43
|
|
|
@@ -50,7 +49,7 @@ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll
|
|
|
50
49
|
|
|
51
50
|
```python
|
|
52
51
|
# Import using the package name
|
|
53
|
-
from opal_tools_sdk import ToolsService, tool,
|
|
52
|
+
from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|
## Usage
|
|
@@ -75,65 +74,6 @@ async def get_weather(parameters: WeatherParameters):
|
|
|
75
74
|
# Discovery endpoint is automatically created at /discovery
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
## Adaptive Blocks
|
|
79
|
-
|
|
80
|
-
Adaptive Blocks provide an interactive UI framework for tools to capture user input directly through forms, rather than having the LLM gather parameters through chat messages.
|
|
81
|
-
|
|
82
|
-
### Basic Example
|
|
83
|
-
|
|
84
|
-
```python
|
|
85
|
-
from opal_tools_sdk import Block, BlockResponse, tool
|
|
86
|
-
from pydantic import BaseModel, Field
|
|
87
|
-
|
|
88
|
-
class MyToolParams(BaseModel):
|
|
89
|
-
user_input: str | None = Field(default=None, description="User input")
|
|
90
|
-
|
|
91
|
-
@tool(
|
|
92
|
-
name="my_tool",
|
|
93
|
-
description="Example tool with Block Document",
|
|
94
|
-
type="block" # Important: Specify type="block"
|
|
95
|
-
)
|
|
96
|
-
async def my_tool(params: MyToolParams) -> BlockResponse:
|
|
97
|
-
# If parameters are missing, show a form
|
|
98
|
-
if not params.user_input:
|
|
99
|
-
return BlockResponse(
|
|
100
|
-
content=Block.Document(
|
|
101
|
-
children=[
|
|
102
|
-
Block.Heading(children="Enter Details", level="2"),
|
|
103
|
-
Block.Field(
|
|
104
|
-
label="User Input",
|
|
105
|
-
required=True,
|
|
106
|
-
children=Block.Input(
|
|
107
|
-
name="user_input",
|
|
108
|
-
placeholder="Enter something..."
|
|
109
|
-
)
|
|
110
|
-
),
|
|
111
|
-
],
|
|
112
|
-
actions=[
|
|
113
|
-
Block.Action(name="submit", children="Submit"),
|
|
114
|
-
Block.CancelAction(children="Cancel")
|
|
115
|
-
],
|
|
116
|
-
blocking=True # Hide chat prompt, force form interaction
|
|
117
|
-
)
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
# Process the input and return as Block Document
|
|
121
|
-
return BlockResponse(
|
|
122
|
-
content=Block.Document(
|
|
123
|
-
children=[
|
|
124
|
-
Block.Heading(children="Result", level="2"),
|
|
125
|
-
Block.Text(children=f"You entered: {params.user_input}")
|
|
126
|
-
]
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
**Important:** When using `type="block"`, the function MUST:
|
|
132
|
-
|
|
133
|
-
1. Have a return type annotation of `-> BlockResponse`
|
|
134
|
-
2. Always return a `BlockResponse` object (even for success/error cases)
|
|
135
|
-
3. Wrap all responses in `Block.Document()` for consistency
|
|
136
|
-
|
|
137
77
|
## Authentication
|
|
138
78
|
|
|
139
79
|
The SDK provides two ways to require authentication for your tools:
|
|
@@ -174,7 +114,7 @@ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optiona
|
|
|
174
114
|
async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
|
|
175
115
|
provider = ""
|
|
176
116
|
token = ""
|
|
177
|
-
|
|
117
|
+
|
|
178
118
|
if auth_data:
|
|
179
119
|
provider = auth_data["provider"]
|
|
180
120
|
token = auth_data["credentials"]["access_token"]
|
|
@@ -226,7 +166,7 @@ class WeatherParameters(BaseModel):
|
|
|
226
166
|
async def get_weather(parameters: WeatherParameters):
|
|
227
167
|
# Get weather data (implementation details omitted)
|
|
228
168
|
weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
|
|
229
|
-
|
|
169
|
+
|
|
230
170
|
# Create an interactive island for weather settings
|
|
231
171
|
island = IslandConfig(
|
|
232
172
|
fields=[
|
|
@@ -260,16 +200,14 @@ async def get_weather(parameters: WeatherParameters):
|
|
|
260
200
|
)
|
|
261
201
|
]
|
|
262
202
|
)
|
|
263
|
-
|
|
203
|
+
|
|
264
204
|
return IslandResponse.create([island])
|
|
265
205
|
```
|
|
266
206
|
|
|
267
207
|
### Island Components
|
|
268
208
|
|
|
269
209
|
#### IslandConfig.Field
|
|
270
|
-
|
|
271
210
|
Fields represent data inputs in the UI:
|
|
272
|
-
|
|
273
211
|
- `name`: Programmatic field identifier
|
|
274
212
|
- `label`: Human-readable label
|
|
275
213
|
- `type`: Field type (`"string"`, `"boolean"`, `"json"`)
|
|
@@ -278,9 +216,7 @@ Fields represent data inputs in the UI:
|
|
|
278
216
|
- `options`: Available options for selection (optional)
|
|
279
217
|
|
|
280
218
|
#### IslandConfig.Action
|
|
281
|
-
|
|
282
219
|
Actions represent buttons or operations:
|
|
283
|
-
|
|
284
220
|
- `name`: Programmatic action identifier
|
|
285
221
|
- `label`: Human-readable button label
|
|
286
222
|
- `type`: UI element type (typically `"button"`)
|
|
@@ -288,18 +224,14 @@ Actions represent buttons or operations:
|
|
|
288
224
|
- `operation`: Operation type (default: `"create"`)
|
|
289
225
|
|
|
290
226
|
#### IslandConfig
|
|
291
|
-
|
|
292
227
|
Contains the complete island configuration:
|
|
293
|
-
|
|
294
228
|
- `fields`: List of IslandConfig.Field objects
|
|
295
229
|
- `actions`: List of IslandConfig.Action objects
|
|
296
230
|
- `type`: Island type for UI rendering (optional, default: `None`)
|
|
297
231
|
- `icon`: Icon to display in the island (optional, default: `None`)
|
|
298
232
|
|
|
299
233
|
#### IslandResponse
|
|
300
|
-
|
|
301
234
|
The response wrapper for islands:
|
|
302
|
-
|
|
303
235
|
- Use `IslandResponse.create([islands])` to create responses
|
|
304
236
|
- Supports multiple islands per response
|
|
305
237
|
|
|
@@ -308,41 +240,20 @@ The response wrapper for islands:
|
|
|
308
240
|
The SDK provides several TypedDict and dataclass definitions for better type safety:
|
|
309
241
|
|
|
310
242
|
### Authentication Types
|
|
311
|
-
|
|
312
243
|
- `AuthData`: TypedDict containing provider and credentials information
|
|
313
244
|
- `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
|
|
314
245
|
- `AuthRequirement`: Dataclass for specifying authentication requirements
|
|
315
246
|
|
|
316
247
|
### Execution Environment
|
|
317
|
-
|
|
318
248
|
- `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
|
|
319
249
|
|
|
320
250
|
### Parameter Types
|
|
321
|
-
|
|
322
251
|
- `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
|
|
323
252
|
- `Parameter`: Dataclass for tool parameter definitions
|
|
324
253
|
- `Function`: Dataclass for complete tool function definitions
|
|
325
254
|
|
|
326
255
|
These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
|
|
327
256
|
|
|
328
|
-
## Code Generation
|
|
329
|
-
|
|
330
|
-
The Block Document types is automatically generated from the JSON schema for full type safety and Pydantic validation.
|
|
331
|
-
|
|
332
|
-
### Generating Types
|
|
333
|
-
|
|
334
|
-
To use the code generation:
|
|
335
|
-
|
|
336
|
-
```bash
|
|
337
|
-
# Install dev dependencies
|
|
338
|
-
pip install -e ".[dev]"
|
|
339
|
-
|
|
340
|
-
# Generate types from schema
|
|
341
|
-
make generate-block
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
This generates Pydantic v2 models from `block-document-spec.json` to `opal_tools_sdk/block.py` with builder methods included.
|
|
345
|
-
|
|
346
257
|
## Documentation
|
|
347
258
|
|
|
348
259
|
See full documentation for more examples and configuration options.
|
{optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/README.md
RENAMED
|
@@ -9,8 +9,7 @@ This SDK simplifies the creation of tools services compatible with the Opal Tool
|
|
|
9
9
|
- Parameter validation and type checking
|
|
10
10
|
- Authentication helpers
|
|
11
11
|
- FastAPI integration
|
|
12
|
-
-
|
|
13
|
-
- Island components for interactive UI responses (legacy)
|
|
12
|
+
- Island components for interactive UI responses
|
|
14
13
|
|
|
15
14
|
## Installation
|
|
16
15
|
|
|
@@ -22,7 +21,7 @@ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll
|
|
|
22
21
|
|
|
23
22
|
```python
|
|
24
23
|
# Import using the package name
|
|
25
|
-
from opal_tools_sdk import ToolsService, tool,
|
|
24
|
+
from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
|
|
26
25
|
```
|
|
27
26
|
|
|
28
27
|
## Usage
|
|
@@ -47,65 +46,6 @@ async def get_weather(parameters: WeatherParameters):
|
|
|
47
46
|
# Discovery endpoint is automatically created at /discovery
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
## Adaptive Blocks
|
|
51
|
-
|
|
52
|
-
Adaptive Blocks provide an interactive UI framework for tools to capture user input directly through forms, rather than having the LLM gather parameters through chat messages.
|
|
53
|
-
|
|
54
|
-
### Basic Example
|
|
55
|
-
|
|
56
|
-
```python
|
|
57
|
-
from opal_tools_sdk import Block, BlockResponse, tool
|
|
58
|
-
from pydantic import BaseModel, Field
|
|
59
|
-
|
|
60
|
-
class MyToolParams(BaseModel):
|
|
61
|
-
user_input: str | None = Field(default=None, description="User input")
|
|
62
|
-
|
|
63
|
-
@tool(
|
|
64
|
-
name="my_tool",
|
|
65
|
-
description="Example tool with Block Document",
|
|
66
|
-
type="block" # Important: Specify type="block"
|
|
67
|
-
)
|
|
68
|
-
async def my_tool(params: MyToolParams) -> BlockResponse:
|
|
69
|
-
# If parameters are missing, show a form
|
|
70
|
-
if not params.user_input:
|
|
71
|
-
return BlockResponse(
|
|
72
|
-
content=Block.Document(
|
|
73
|
-
children=[
|
|
74
|
-
Block.Heading(children="Enter Details", level="2"),
|
|
75
|
-
Block.Field(
|
|
76
|
-
label="User Input",
|
|
77
|
-
required=True,
|
|
78
|
-
children=Block.Input(
|
|
79
|
-
name="user_input",
|
|
80
|
-
placeholder="Enter something..."
|
|
81
|
-
)
|
|
82
|
-
),
|
|
83
|
-
],
|
|
84
|
-
actions=[
|
|
85
|
-
Block.Action(name="submit", children="Submit"),
|
|
86
|
-
Block.CancelAction(children="Cancel")
|
|
87
|
-
],
|
|
88
|
-
blocking=True # Hide chat prompt, force form interaction
|
|
89
|
-
)
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# Process the input and return as Block Document
|
|
93
|
-
return BlockResponse(
|
|
94
|
-
content=Block.Document(
|
|
95
|
-
children=[
|
|
96
|
-
Block.Heading(children="Result", level="2"),
|
|
97
|
-
Block.Text(children=f"You entered: {params.user_input}")
|
|
98
|
-
]
|
|
99
|
-
)
|
|
100
|
-
)
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**Important:** When using `type="block"`, the function MUST:
|
|
104
|
-
|
|
105
|
-
1. Have a return type annotation of `-> BlockResponse`
|
|
106
|
-
2. Always return a `BlockResponse` object (even for success/error cases)
|
|
107
|
-
3. Wrap all responses in `Block.Document()` for consistency
|
|
108
|
-
|
|
109
49
|
## Authentication
|
|
110
50
|
|
|
111
51
|
The SDK provides two ways to require authentication for your tools:
|
|
@@ -146,7 +86,7 @@ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optiona
|
|
|
146
86
|
async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
|
|
147
87
|
provider = ""
|
|
148
88
|
token = ""
|
|
149
|
-
|
|
89
|
+
|
|
150
90
|
if auth_data:
|
|
151
91
|
provider = auth_data["provider"]
|
|
152
92
|
token = auth_data["credentials"]["access_token"]
|
|
@@ -198,7 +138,7 @@ class WeatherParameters(BaseModel):
|
|
|
198
138
|
async def get_weather(parameters: WeatherParameters):
|
|
199
139
|
# Get weather data (implementation details omitted)
|
|
200
140
|
weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
|
|
201
|
-
|
|
141
|
+
|
|
202
142
|
# Create an interactive island for weather settings
|
|
203
143
|
island = IslandConfig(
|
|
204
144
|
fields=[
|
|
@@ -232,16 +172,14 @@ async def get_weather(parameters: WeatherParameters):
|
|
|
232
172
|
)
|
|
233
173
|
]
|
|
234
174
|
)
|
|
235
|
-
|
|
175
|
+
|
|
236
176
|
return IslandResponse.create([island])
|
|
237
177
|
```
|
|
238
178
|
|
|
239
179
|
### Island Components
|
|
240
180
|
|
|
241
181
|
#### IslandConfig.Field
|
|
242
|
-
|
|
243
182
|
Fields represent data inputs in the UI:
|
|
244
|
-
|
|
245
183
|
- `name`: Programmatic field identifier
|
|
246
184
|
- `label`: Human-readable label
|
|
247
185
|
- `type`: Field type (`"string"`, `"boolean"`, `"json"`)
|
|
@@ -250,9 +188,7 @@ Fields represent data inputs in the UI:
|
|
|
250
188
|
- `options`: Available options for selection (optional)
|
|
251
189
|
|
|
252
190
|
#### IslandConfig.Action
|
|
253
|
-
|
|
254
191
|
Actions represent buttons or operations:
|
|
255
|
-
|
|
256
192
|
- `name`: Programmatic action identifier
|
|
257
193
|
- `label`: Human-readable button label
|
|
258
194
|
- `type`: UI element type (typically `"button"`)
|
|
@@ -260,18 +196,14 @@ Actions represent buttons or operations:
|
|
|
260
196
|
- `operation`: Operation type (default: `"create"`)
|
|
261
197
|
|
|
262
198
|
#### IslandConfig
|
|
263
|
-
|
|
264
199
|
Contains the complete island configuration:
|
|
265
|
-
|
|
266
200
|
- `fields`: List of IslandConfig.Field objects
|
|
267
201
|
- `actions`: List of IslandConfig.Action objects
|
|
268
202
|
- `type`: Island type for UI rendering (optional, default: `None`)
|
|
269
203
|
- `icon`: Icon to display in the island (optional, default: `None`)
|
|
270
204
|
|
|
271
205
|
#### IslandResponse
|
|
272
|
-
|
|
273
206
|
The response wrapper for islands:
|
|
274
|
-
|
|
275
207
|
- Use `IslandResponse.create([islands])` to create responses
|
|
276
208
|
- Supports multiple islands per response
|
|
277
209
|
|
|
@@ -280,41 +212,20 @@ The response wrapper for islands:
|
|
|
280
212
|
The SDK provides several TypedDict and dataclass definitions for better type safety:
|
|
281
213
|
|
|
282
214
|
### Authentication Types
|
|
283
|
-
|
|
284
215
|
- `AuthData`: TypedDict containing provider and credentials information
|
|
285
216
|
- `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
|
|
286
217
|
- `AuthRequirement`: Dataclass for specifying authentication requirements
|
|
287
218
|
|
|
288
219
|
### Execution Environment
|
|
289
|
-
|
|
290
220
|
- `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
|
|
291
221
|
|
|
292
222
|
### Parameter Types
|
|
293
|
-
|
|
294
223
|
- `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
|
|
295
224
|
- `Parameter`: Dataclass for tool parameter definitions
|
|
296
225
|
- `Function`: Dataclass for complete tool function definitions
|
|
297
226
|
|
|
298
227
|
These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
|
|
299
228
|
|
|
300
|
-
## Code Generation
|
|
301
|
-
|
|
302
|
-
The Block Document types is automatically generated from the JSON schema for full type safety and Pydantic validation.
|
|
303
|
-
|
|
304
|
-
### Generating Types
|
|
305
|
-
|
|
306
|
-
To use the code generation:
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
# Install dev dependencies
|
|
310
|
-
pip install -e ".[dev]"
|
|
311
|
-
|
|
312
|
-
# Generate types from schema
|
|
313
|
-
make generate-block
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
This generates Pydantic v2 models from `block-document-spec.json` to `opal_tools_sdk/block.py` with builder methods included.
|
|
317
|
-
|
|
318
229
|
## Documentation
|
|
319
230
|
|
|
320
231
|
See full documentation for more examples and configuration options.
|
|
@@ -1,14 +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 .
|
|
6
|
+
from .proteus import UI
|
|
7
7
|
|
|
8
|
-
__version__ = "0.1.
|
|
8
|
+
__version__ = "0.1.14-dev"
|
|
9
9
|
__all__ = [
|
|
10
10
|
"ToolsService",
|
|
11
11
|
"tool",
|
|
12
|
+
"resource",
|
|
12
13
|
"requires_auth",
|
|
13
14
|
"register_logger_factory",
|
|
14
15
|
# Models
|
|
@@ -18,7 +19,6 @@ __all__ = [
|
|
|
18
19
|
"Environment",
|
|
19
20
|
"IslandConfig",
|
|
20
21
|
"IslandResponse",
|
|
21
|
-
#
|
|
22
|
-
"
|
|
23
|
-
"BlockResponse",
|
|
22
|
+
# UI
|
|
23
|
+
"UI",
|
|
24
24
|
]
|
|
@@ -14,7 +14,7 @@ def tool(
|
|
|
14
14
|
name: str,
|
|
15
15
|
description: str,
|
|
16
16
|
auth_requirements: Optional[List[Dict[str, Any]]] = None,
|
|
17
|
-
|
|
17
|
+
ui_resource: Optional[str] = None,
|
|
18
18
|
):
|
|
19
19
|
"""Decorator to register a function as an Opal tool.
|
|
20
20
|
|
|
@@ -24,7 +24,8 @@ def tool(
|
|
|
24
24
|
auth_requirements: Authentication requirements (optional)
|
|
25
25
|
Format: [{"provider": "oauth_provider", "scope_bundle": "permissions_scope", "required": True}, ...]
|
|
26
26
|
Example: [{"provider": "google", "scope_bundle": "calendar", "required": True}]
|
|
27
|
-
|
|
27
|
+
ui_resource: URI of associated UI resource for dynamic rendering (optional)
|
|
28
|
+
Example: "ui://my-app/create-form"
|
|
28
29
|
|
|
29
30
|
Returns:
|
|
30
31
|
Decorator function
|
|
@@ -34,7 +35,8 @@ def tool(
|
|
|
34
35
|
async def my_tool(parameters: ParametersModel, auth_data: Optional[Dict] = None):
|
|
35
36
|
...
|
|
36
37
|
|
|
37
|
-
If
|
|
38
|
+
If ui_resource is specified, the frontend can fetch the resource to render a dynamic UI
|
|
39
|
+
for this tool without hardcoded integrations.
|
|
38
40
|
"""
|
|
39
41
|
def decorator(func: Callable):
|
|
40
42
|
# Get the ToolsService instance from the global registry
|
|
@@ -161,7 +163,86 @@ def tool(
|
|
|
161
163
|
parameters=parameters,
|
|
162
164
|
endpoint=endpoint,
|
|
163
165
|
auth_requirements=auth_req_list,
|
|
164
|
-
|
|
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,
|
|
165
246
|
)
|
|
166
247
|
|
|
167
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
|
|