diff --git a/pkg-py/src/shinychat/_chat_normalize_chatlas.py b/pkg-py/src/shinychat/_chat_normalize_chatlas.py index 991060f7..4f77cfc7 100644 --- a/pkg-py/src/shinychat/_chat_normalize_chatlas.py +++ b/pkg-py/src/shinychat/_chat_normalize_chatlas.py @@ -288,8 +288,18 @@ def tool_result_contents(x: "ContentToolResult") -> Tagifiable: tool = x.request.tool tool_title = None + icon = None if tool and tool.annotations: tool_title = tool.annotations.get("title") + icon = tool.annotations.get("extras", {}).get("icon") + icon = icon or tool.annotations.get("icon") + + # Icon strings and HTML display never get escaped + icon = display.icon or icon + if icon and isinstance(icon, str): + icon = HTML(icon) + if value_type == "html" and isinstance(value, str): + value = HTML(value) # display (tool *result* level) takes precedence over # annotations (tool *definition* level) @@ -301,7 +311,7 @@ def tool_result_contents(x: "ContentToolResult") -> Tagifiable: status="success" if x.error is None else "error", value=value, value_type=value_type, - icon=display.icon, + icon=icon, intent=intent, show_request=display.show_request, expanded=display.open, diff --git a/pkg-py/src/shinychat/_markdown_stream.py b/pkg-py/src/shinychat/_markdown_stream.py index a8cbdc17..4c2f778a 100644 --- a/pkg-py/src/shinychat/_markdown_stream.py +++ b/pkg-py/src/shinychat/_markdown_stream.py @@ -92,9 +92,9 @@ def __init__( async def _mock_task() -> str: return "" - self._latest_stream: reactive.Value[reactive.ExtendedTask[[], str]] = ( - reactive.Value(_mock_task) - ) + self._latest_stream: reactive.Value[ + reactive.ExtendedTask[[], str] + ] = reactive.Value(_mock_task) async def stream( self, @@ -149,7 +149,9 @@ async def _task(): ui = self._session._process_ui(x) result += ui["html"] - await self._send_content_message(ui["html"], "append", ui["deps"]) + await self._send_content_message( + ui["html"], "append", ui["deps"] + ) return result @@ -249,7 +251,9 @@ async def _send_custom_message( ): if self._session.is_stub_session(): return - await self._session.send_custom_message("shinyMarkdownStreamMessage", {**msg}) + await self._session.send_custom_message( + "shinyMarkdownStreamMessage", {**msg} + ) async def _raise_exception(self, e: BaseException): if self.on_error == "unhandled": diff --git a/pkg-py/tests/playwright/tools/basic/app.py b/pkg-py/tests/playwright/tools/basic/app.py index b8e7a433..5bcfdcdc 100644 --- a/pkg-py/tests/playwright/tools/basic/app.py +++ b/pkg-py/tests/playwright/tools/basic/app.py @@ -6,7 +6,6 @@ import faicons from chatlas import ChatAuto, ContentToolResult from chatlas.types import ToolAnnotations -from pydantic import BaseModel, Field from shiny import reactive from shiny.express import input, ui from shinychat.express import Chat @@ -39,20 +38,6 @@ def list_files_impl(): ) -class ListFileParams(BaseModel): - """ - List files in the user's current directory. Always check again when asked. - """ - - path: str = Field(..., description="The path to list files from") - - -class ListFileParamsWithIntent(ListFileParams): - intent: str = Field( - ..., description="The user's intent for this tool", alias="_intent" - ) - - annotations: ToolAnnotations = {} if TOOL_OPTS["with_title"]: annotations["title"] = "List Files" @@ -61,56 +46,60 @@ class ListFileParamsWithIntent(ListFileParams): if TOOL_OPTS["async"]: if TOOL_OPTS["with_intent"]: - async def list_files_func1(path: str, _intent: str): + async def list_files(path: str, _intent: str): # pyright: ignore[reportRedeclaration] + """ + List files in the user's current directory. Always check again when asked. + + Parameters + ---------- + path + The path to list files from. + _intent + Reason for the request to explain the tool call to the user. + """ await asyncio.sleep(random.uniform(1, 10)) return list_files_impl() - chat_client.register_tool( - list_files_func1, - name="list_files", - model=ListFileParamsWithIntent, - annotations=annotations, - ) - else: - async def list_files_func2(path: str): + async def list_files(path: str): # pyright: ignore[reportRedeclaration] + """ + List files in the user's current directory. Always check again when asked. + """ await asyncio.sleep(random.uniform(1, 10)) return list_files_impl() - chat_client.register_tool( - list_files_func2, - name="list_files", - model=ListFileParams, - annotations=annotations, - ) - else: if TOOL_OPTS["with_intent"]: - def list_files_func3(path: str, _intent: str): + def list_files(path: str, _intent: str): # pyright: ignore[reportRedeclaration] + """ + List files in the user's current directory. Always check again when asked. + + Parameters + ---------- + path + The path to list files from. + _intent + Reason for the request to explain the tool call to the user. + """ time.sleep(random.uniform(1, 3)) return list_files_impl() - chat_client.register_tool( - list_files_func3, - name="list_files", - model=ListFileParamsWithIntent, - annotations=annotations, - ) - else: - def list_files_func4(path: str): + def list_files(path: str): # pyright: ignore[reportRedeclaration] + """ + List files in the user's current directory. Always check again when asked. + """ time.sleep(random.uniform(1, 3)) return list_files_impl() - chat_client.register_tool( - list_files_func4, - name="list_files", - model=ListFileParams, - annotations=annotations, - ) + +chat_client.register_tool( + list_files, + annotations=annotations, +) ui.page_opts(fillable=True)