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.
Files changed (24) hide show
  1. {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/PKG-INFO +6 -95
  2. {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/README.md +5 -94
  3. {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
  4. {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
  5. {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
  6. optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/proteus.py +2415 -0
  7. {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
  8. optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/ui.py +3 -0
  9. {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
  10. {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
  11. {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/pyproject.toml +1 -1
  12. {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.py +1 -1
  13. optimizely_opal_opal_tools_sdk-0.1.14.dev0/tests/test_integration.py +352 -0
  14. optimizely_opal_opal_tools_sdk-0.1.14.dev0/tests/test_proteus.py +106 -0
  15. optimizely_opal_opal_tools_sdk-0.1.12.dev0/opal_tools_sdk/block.py +0 -9985
  16. optimizely_opal_opal_tools_sdk-0.1.12.dev0/tests/test_block.py +0 -105
  17. optimizely_opal_opal_tools_sdk-0.1.12.dev0/tests/test_integration.py +0 -181
  18. {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
  19. {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
  20. {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
  21. {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
  22. {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
  23. {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
  24. {optimizely_opal_opal_tools_sdk-0.1.12.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optimizely-opal.opal-tools-sdk
3
- Version: 0.1.12.dev0
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
- - **Adaptive Blocks** for interactive forms and UI flows (new!)
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, Block, BlockResponse, IslandResponse, IslandConfig
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.
@@ -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
- - **Adaptive Blocks** for interactive forms and UI flows (new!)
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, Block, BlockResponse, IslandResponse, IslandConfig
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 .block import Block, BlockResponse
6
+ from .proteus import UI
7
7
 
8
- __version__ = "0.1.0"
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
- # Block
22
- "Block",
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
- type: str = "json",
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
- type: Response type - 'json' (default) or 'block' for Block Documents (Adaptive Blocks)
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 type='block', the tool should return a BlockResponse object.
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
- response_type=type
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