optimizely-opal.opal-tools-sdk 0.1.26.dev0__tar.gz → 0.1.28.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 (22) hide show
  1. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/PKG-INFO +115 -1
  2. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/README.md +114 -0
  3. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/proteus.py +29 -2
  4. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +115 -1
  5. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/pyproject.toml +1 -1
  6. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/setup.py +1 -1
  7. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/__init__.py +0 -0
  8. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/_registry.py +0 -0
  9. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/auth.py +0 -0
  10. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/decorators.py +0 -0
  11. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/logging.py +0 -0
  12. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/models.py +0 -0
  13. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/service.py +0 -0
  14. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/opal_tools_sdk/ui.py +0 -0
  15. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/optimizely_opal.opal_tools_sdk.egg-info/SOURCES.txt +0 -0
  16. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/optimizely_opal.opal_tools_sdk.egg-info/dependency_links.txt +0 -0
  17. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +0 -0
  18. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/optimizely_opal.opal_tools_sdk.egg-info/top_level.txt +0 -0
  19. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/setup.cfg +0 -0
  20. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/tests/test_integration.py +0 -0
  21. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/tests/test_nested_schema.py +0 -0
  22. {optimizely_opal_opal_tools_sdk-0.1.26.dev0 → optimizely_opal_opal_tools_sdk-0.1.28.dev0}/tests/test_proteus.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optimizely-opal.opal-tools-sdk
3
- Version: 0.1.26.dev0
3
+ Version: 0.1.28.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
@@ -235,6 +235,120 @@ The response wrapper for islands:
235
235
  - Use `IslandResponse.create([islands])` to create responses
236
236
  - Supports multiple islands per response
237
237
 
238
+ ## Resources & Proteus UI
239
+
240
+ 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.
241
+
242
+ For the full Proteus component reference and visual designer, see the [Proteus documentation](https://optimizely-axiom.github.io/optiaxiom/guides/proteus/).
243
+
244
+ ### Defining a Resource with `@resource`
245
+
246
+ Use the `@resource` decorator to register a function as an MCP resource:
247
+
248
+ ```python
249
+ from opal_tools_sdk import UI
250
+ from opal_tools_sdk.decorators import resource
251
+
252
+ @resource(
253
+ uri="ui://my-app/create-form",
254
+ name="create-form",
255
+ description="Form for creating new items",
256
+ )
257
+ async def get_create_form():
258
+ return UI.Document(
259
+ title="Create Item",
260
+ body=[
261
+ UI.Heading(children="New Item"),
262
+ UI.Field(
263
+ label="Item Name",
264
+ children=UI.Input(name="item_name", placeholder="Enter item name"),
265
+ ),
266
+ UI.Field(
267
+ label="Description",
268
+ children=UI.Textarea(name="description", placeholder="Enter description"),
269
+ ),
270
+ ],
271
+ actions=[
272
+ UI.Action(children="Save", appearance="primary"),
273
+ UI.CancelAction(children="Cancel"),
274
+ ],
275
+ )
276
+ ```
277
+
278
+ **Parameters:**
279
+ - `uri` (required): Unique URI for the resource (e.g., `"ui://my-app/create-form"`)
280
+ - `name` (required): Name of the resource
281
+ - `description` (optional): Description of the resource
282
+ - `mime_type` (optional): MIME type of the content. Auto-set to `"application/vnd.opal.proteus+json"` when returning a `UI.Document`
283
+ - `title` (optional): Human-readable title
284
+
285
+ The handler function can return either a `str` (manual JSON serialization) or a `UI.Document` (automatic serialization with MIME type set automatically).
286
+
287
+ ### Linking a Tool to a UI Resource
288
+
289
+ 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:
290
+
291
+ ```python
292
+ from opal_tools_sdk import tool
293
+ from pydantic import BaseModel, Field
294
+
295
+ class CreateItemParams(BaseModel):
296
+ item_name: str = Field(description="Name of the item")
297
+ description: str = Field(description="Item description")
298
+
299
+ @tool(
300
+ "create_item",
301
+ "Create a new item",
302
+ ui_resource="ui://my-app/create-form",
303
+ )
304
+ async def create_item(parameters: CreateItemParams):
305
+ return {"id": "item-123", "name": parameters.item_name, "status": "created"}
306
+ ```
307
+
308
+ ### Building UI with `UI.Document`
309
+
310
+ Import the `UI` namespace from `opal_tools_sdk` or `opal_tools_sdk.ui`. It provides type-safe builders for all Proteus components:
311
+
312
+ ```python
313
+ from opal_tools_sdk import UI
314
+ ```
315
+
316
+ **Available components:**
317
+
318
+ | Category | Components |
319
+ |----------|-----------|
320
+ | Layout | `UI.Document`, `UI.Group`, `UI.Card`, `UI.CardHeader`, `UI.CardLink`, `UI.Separator` |
321
+ | Typography | `UI.Heading`, `UI.Text`, `UI.Link` |
322
+ | Data Display | `UI.Avatar`, `UI.Badge`, `UI.DataTable`, `UI.Chart`, `UI.IconCalendar`, `UI.Image`, `UI.ImageCarousel`, `UI.Time` |
323
+ | Form Controls | `UI.Field`, `UI.Input`, `UI.Textarea`, `UI.Select`, `UI.SelectTrigger`, `UI.SelectContent`, `UI.Switch`, `UI.Range`, `UI.Question` |
324
+ | Actions | `UI.Action`, `UI.CancelAction` |
325
+ | Dynamic | `UI.Value`, `UI.Map`, `UI.MapIndex`, `UI.Show`, `UI.Concat`, `UI.Zip` |
326
+
327
+ **Data binding** with `UI.Value` resolves paths from the tool response:
328
+
329
+ ```python
330
+ @resource(uri="ui://my-app/results", name="results")
331
+ async def get_results():
332
+ return UI.Document(
333
+ title=UI.Value(path="/title"),
334
+ body=UI.Map(
335
+ path="/items",
336
+ children=UI.Text(children=UI.Value(path="name")),
337
+ ),
338
+ )
339
+ ```
340
+
341
+ **Conditional rendering** with `UI.Show`:
342
+
343
+ ```python
344
+ UI.Show(
345
+ when={"!!": UI.Value(path="/error")},
346
+ children=UI.Text(children="An error occurred", color="fg.error"),
347
+ )
348
+ ```
349
+
350
+ The MIME type constant is available as `UI.MIME_TYPE` (`"application/vnd.opal.proteus+json"`).
351
+
238
352
  ## Type Definitions
239
353
 
240
354
  The SDK provides several TypedDict and dataclass definitions for better type safety:
@@ -207,6 +207,120 @@ The response wrapper for islands:
207
207
  - Use `IslandResponse.create([islands])` to create responses
208
208
  - Supports multiple islands per response
209
209
 
210
+ ## Resources & Proteus UI
211
+
212
+ 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.
213
+
214
+ For the full Proteus component reference and visual designer, see the [Proteus documentation](https://optimizely-axiom.github.io/optiaxiom/guides/proteus/).
215
+
216
+ ### Defining a Resource with `@resource`
217
+
218
+ Use the `@resource` decorator to register a function as an MCP resource:
219
+
220
+ ```python
221
+ from opal_tools_sdk import UI
222
+ from opal_tools_sdk.decorators import resource
223
+
224
+ @resource(
225
+ uri="ui://my-app/create-form",
226
+ name="create-form",
227
+ description="Form for creating new items",
228
+ )
229
+ async def get_create_form():
230
+ return UI.Document(
231
+ title="Create Item",
232
+ body=[
233
+ UI.Heading(children="New Item"),
234
+ UI.Field(
235
+ label="Item Name",
236
+ children=UI.Input(name="item_name", placeholder="Enter item name"),
237
+ ),
238
+ UI.Field(
239
+ label="Description",
240
+ children=UI.Textarea(name="description", placeholder="Enter description"),
241
+ ),
242
+ ],
243
+ actions=[
244
+ UI.Action(children="Save", appearance="primary"),
245
+ UI.CancelAction(children="Cancel"),
246
+ ],
247
+ )
248
+ ```
249
+
250
+ **Parameters:**
251
+ - `uri` (required): Unique URI for the resource (e.g., `"ui://my-app/create-form"`)
252
+ - `name` (required): Name of the resource
253
+ - `description` (optional): Description of the resource
254
+ - `mime_type` (optional): MIME type of the content. Auto-set to `"application/vnd.opal.proteus+json"` when returning a `UI.Document`
255
+ - `title` (optional): Human-readable title
256
+
257
+ The handler function can return either a `str` (manual JSON serialization) or a `UI.Document` (automatic serialization with MIME type set automatically).
258
+
259
+ ### Linking a Tool to a UI Resource
260
+
261
+ 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:
262
+
263
+ ```python
264
+ from opal_tools_sdk import tool
265
+ from pydantic import BaseModel, Field
266
+
267
+ class CreateItemParams(BaseModel):
268
+ item_name: str = Field(description="Name of the item")
269
+ description: str = Field(description="Item description")
270
+
271
+ @tool(
272
+ "create_item",
273
+ "Create a new item",
274
+ ui_resource="ui://my-app/create-form",
275
+ )
276
+ async def create_item(parameters: CreateItemParams):
277
+ return {"id": "item-123", "name": parameters.item_name, "status": "created"}
278
+ ```
279
+
280
+ ### Building UI with `UI.Document`
281
+
282
+ Import the `UI` namespace from `opal_tools_sdk` or `opal_tools_sdk.ui`. It provides type-safe builders for all Proteus components:
283
+
284
+ ```python
285
+ from opal_tools_sdk import UI
286
+ ```
287
+
288
+ **Available components:**
289
+
290
+ | Category | Components |
291
+ |----------|-----------|
292
+ | Layout | `UI.Document`, `UI.Group`, `UI.Card`, `UI.CardHeader`, `UI.CardLink`, `UI.Separator` |
293
+ | Typography | `UI.Heading`, `UI.Text`, `UI.Link` |
294
+ | Data Display | `UI.Avatar`, `UI.Badge`, `UI.DataTable`, `UI.Chart`, `UI.IconCalendar`, `UI.Image`, `UI.ImageCarousel`, `UI.Time` |
295
+ | Form Controls | `UI.Field`, `UI.Input`, `UI.Textarea`, `UI.Select`, `UI.SelectTrigger`, `UI.SelectContent`, `UI.Switch`, `UI.Range`, `UI.Question` |
296
+ | Actions | `UI.Action`, `UI.CancelAction` |
297
+ | Dynamic | `UI.Value`, `UI.Map`, `UI.MapIndex`, `UI.Show`, `UI.Concat`, `UI.Zip` |
298
+
299
+ **Data binding** with `UI.Value` resolves paths from the tool response:
300
+
301
+ ```python
302
+ @resource(uri="ui://my-app/results", name="results")
303
+ async def get_results():
304
+ return UI.Document(
305
+ title=UI.Value(path="/title"),
306
+ body=UI.Map(
307
+ path="/items",
308
+ children=UI.Text(children=UI.Value(path="name")),
309
+ ),
310
+ )
311
+ ```
312
+
313
+ **Conditional rendering** with `UI.Show`:
314
+
315
+ ```python
316
+ UI.Show(
317
+ when={"!!": UI.Value(path="/error")},
318
+ children=UI.Text(children="An error occurred", color="fg.error"),
319
+ )
320
+ ```
321
+
322
+ The MIME type constant is available as `UI.MIME_TYPE` (`"application/vnd.opal.proteus+json"`).
323
+
210
324
  ## Type Definitions
211
325
 
212
326
  The SDK provides several TypedDict and dataclass definitions for better type safety:
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: proteus-document-spec.json
3
- # timestamp: 2026-04-17T20:57:26+00:00
3
+ # timestamp: 2026-04-27T14:20:49+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -22,6 +22,9 @@ class ProteusEventHandler1(BaseModel):
22
22
  extra='forbid',
23
23
  )
24
24
  interaction: str = Field(..., description='Name of registered interaction to call')
25
+ params: dict[str, Any] | None = Field(
26
+ default=None, description='Parameters to pass to the interaction handler'
27
+ )
25
28
 
26
29
 
27
30
  class Series(BaseModel):
@@ -276,6 +279,10 @@ class ProteusDocument(BaseModel):
276
279
  description='If true, hides chat prompt and forces user interaction with document. User can press ESC or close to abandon.',
277
280
  )
278
281
  body: ProteusNode | None = Field(..., description='The main content of the document.')
282
+ compact: bool | None = Field(
283
+ default=None,
284
+ description='If true, constrains the body to a max height and makes it scrollable when content overflows.',
285
+ )
279
286
  subtitle: ProteusNode | None = Field(
280
287
  default=None,
281
288
  description="A brief description or tagline that provides additional context about the Proteus document's purpose.",
@@ -588,6 +595,22 @@ class ProteusBadge(BaseModel):
588
595
  z: SprinklePropZ | None = None
589
596
 
590
597
 
598
+ class ProteusBridge(BaseModel):
599
+ model_config = ConfigDict(
600
+ extra='forbid',
601
+ )
602
+ field_type: Literal['Bridge'] = Field(default='Bridge', alias='$type')
603
+ fallback: ProteusNode | None = Field(
604
+ default=None,
605
+ description="Content rendered on platforms without iframe support (Teams, Slack, mobile). If omitted, a default 'View in Opal web' message is shown.",
606
+ )
607
+ height: float | None = Field(default=None, description='Height of the iframe in pixels')
608
+ resource: str = Field(
609
+ ...,
610
+ description="Resource URI identifying the MCP app to render (e.g., 'ui://sample-widget')",
611
+ )
612
+
613
+
591
614
  class ProteusButton(BaseModel):
592
615
  model_config = ConfigDict(
593
616
  extra='forbid',
@@ -918,7 +941,7 @@ class ProteusDataTable(BaseModel):
918
941
  extra='forbid',
919
942
  )
920
943
  field_type: Literal['DataTable'] = Field(default='DataTable', alias='$type')
921
- columns: list[Column] | None = Field(default=None, description='Column definitions')
944
+ columns: list[Column] | ProteusExpression | None = None
922
945
  data: list[dict[str, Any]] | ProteusExpression | ProteusZip | None = None
923
946
 
924
947
 
@@ -2166,6 +2189,7 @@ class ProteusElement(
2166
2189
  ProteusAction
2167
2190
  | ProteusAvatar
2168
2191
  | ProteusBadge
2192
+ | ProteusBridge
2169
2193
  | ProteusButton
2170
2194
  | ProteusCard
2171
2195
  | ProteusCardHeader
@@ -2201,6 +2225,7 @@ class ProteusElement(
2201
2225
  ProteusAction
2202
2226
  | ProteusAvatar
2203
2227
  | ProteusBadge
2228
+ | ProteusBridge
2204
2229
  | ProteusButton
2205
2230
  | ProteusCard
2206
2231
  | ProteusCardHeader
@@ -3877,6 +3902,7 @@ ProteusZip.model_rebuild()
3877
3902
  ProteusAction.model_rebuild()
3878
3903
  ProteusAvatar.model_rebuild()
3879
3904
  ProteusBadge.model_rebuild()
3905
+ ProteusBridge.model_rebuild()
3880
3906
  ProteusButton.model_rebuild()
3881
3907
  ProteusCard.model_rebuild()
3882
3908
  ProteusCardHeader.model_rebuild()
@@ -3921,6 +3947,7 @@ class UI:
3921
3947
  Action = ProteusAction
3922
3948
  Avatar = ProteusAvatar
3923
3949
  Badge = ProteusBadge
3950
+ Bridge = ProteusBridge
3924
3951
  Button = ProteusButton
3925
3952
  Card = ProteusCard
3926
3953
  CardHeader = ProteusCardHeader
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optimizely-opal.opal-tools-sdk
3
- Version: 0.1.26.dev0
3
+ Version: 0.1.28.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
@@ -235,6 +235,120 @@ The response wrapper for islands:
235
235
  - Use `IslandResponse.create([islands])` to create responses
236
236
  - Supports multiple islands per response
237
237
 
238
+ ## Resources & Proteus UI
239
+
240
+ 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.
241
+
242
+ For the full Proteus component reference and visual designer, see the [Proteus documentation](https://optimizely-axiom.github.io/optiaxiom/guides/proteus/).
243
+
244
+ ### Defining a Resource with `@resource`
245
+
246
+ Use the `@resource` decorator to register a function as an MCP resource:
247
+
248
+ ```python
249
+ from opal_tools_sdk import UI
250
+ from opal_tools_sdk.decorators import resource
251
+
252
+ @resource(
253
+ uri="ui://my-app/create-form",
254
+ name="create-form",
255
+ description="Form for creating new items",
256
+ )
257
+ async def get_create_form():
258
+ return UI.Document(
259
+ title="Create Item",
260
+ body=[
261
+ UI.Heading(children="New Item"),
262
+ UI.Field(
263
+ label="Item Name",
264
+ children=UI.Input(name="item_name", placeholder="Enter item name"),
265
+ ),
266
+ UI.Field(
267
+ label="Description",
268
+ children=UI.Textarea(name="description", placeholder="Enter description"),
269
+ ),
270
+ ],
271
+ actions=[
272
+ UI.Action(children="Save", appearance="primary"),
273
+ UI.CancelAction(children="Cancel"),
274
+ ],
275
+ )
276
+ ```
277
+
278
+ **Parameters:**
279
+ - `uri` (required): Unique URI for the resource (e.g., `"ui://my-app/create-form"`)
280
+ - `name` (required): Name of the resource
281
+ - `description` (optional): Description of the resource
282
+ - `mime_type` (optional): MIME type of the content. Auto-set to `"application/vnd.opal.proteus+json"` when returning a `UI.Document`
283
+ - `title` (optional): Human-readable title
284
+
285
+ The handler function can return either a `str` (manual JSON serialization) or a `UI.Document` (automatic serialization with MIME type set automatically).
286
+
287
+ ### Linking a Tool to a UI Resource
288
+
289
+ 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:
290
+
291
+ ```python
292
+ from opal_tools_sdk import tool
293
+ from pydantic import BaseModel, Field
294
+
295
+ class CreateItemParams(BaseModel):
296
+ item_name: str = Field(description="Name of the item")
297
+ description: str = Field(description="Item description")
298
+
299
+ @tool(
300
+ "create_item",
301
+ "Create a new item",
302
+ ui_resource="ui://my-app/create-form",
303
+ )
304
+ async def create_item(parameters: CreateItemParams):
305
+ return {"id": "item-123", "name": parameters.item_name, "status": "created"}
306
+ ```
307
+
308
+ ### Building UI with `UI.Document`
309
+
310
+ Import the `UI` namespace from `opal_tools_sdk` or `opal_tools_sdk.ui`. It provides type-safe builders for all Proteus components:
311
+
312
+ ```python
313
+ from opal_tools_sdk import UI
314
+ ```
315
+
316
+ **Available components:**
317
+
318
+ | Category | Components |
319
+ |----------|-----------|
320
+ | Layout | `UI.Document`, `UI.Group`, `UI.Card`, `UI.CardHeader`, `UI.CardLink`, `UI.Separator` |
321
+ | Typography | `UI.Heading`, `UI.Text`, `UI.Link` |
322
+ | Data Display | `UI.Avatar`, `UI.Badge`, `UI.DataTable`, `UI.Chart`, `UI.IconCalendar`, `UI.Image`, `UI.ImageCarousel`, `UI.Time` |
323
+ | Form Controls | `UI.Field`, `UI.Input`, `UI.Textarea`, `UI.Select`, `UI.SelectTrigger`, `UI.SelectContent`, `UI.Switch`, `UI.Range`, `UI.Question` |
324
+ | Actions | `UI.Action`, `UI.CancelAction` |
325
+ | Dynamic | `UI.Value`, `UI.Map`, `UI.MapIndex`, `UI.Show`, `UI.Concat`, `UI.Zip` |
326
+
327
+ **Data binding** with `UI.Value` resolves paths from the tool response:
328
+
329
+ ```python
330
+ @resource(uri="ui://my-app/results", name="results")
331
+ async def get_results():
332
+ return UI.Document(
333
+ title=UI.Value(path="/title"),
334
+ body=UI.Map(
335
+ path="/items",
336
+ children=UI.Text(children=UI.Value(path="name")),
337
+ ),
338
+ )
339
+ ```
340
+
341
+ **Conditional rendering** with `UI.Show`:
342
+
343
+ ```python
344
+ UI.Show(
345
+ when={"!!": UI.Value(path="/error")},
346
+ children=UI.Text(children="An error occurred", color="fg.error"),
347
+ )
348
+ ```
349
+
350
+ The MIME type constant is available as `UI.MIME_TYPE` (`"application/vnd.opal.proteus+json"`).
351
+
238
352
  ## Type Definitions
239
353
 
240
354
  The SDK provides several TypedDict and dataclass definitions for better type safety:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "optimizely-opal.opal-tools-sdk"
7
- version = "0.1.26-dev"
7
+ version = "0.1.28-dev"
8
8
  description = "SDK for creating Opal-compatible tools services"
9
9
  authors = [{ name = "Optimizely", email = "opal-team@optimizely.com" }]
10
10
  readme = "README.md"
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="optimizely-opal.opal-tools-sdk",
5
- version="0.1.26-dev",
5
+ version="0.1.28-dev",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  "fastapi>=0.100.0",