#
tokens: 48660/50000 2/1784 files (page 89/114)
lines: off (toggle) GitHub
raw markdown copy
This is page 89 of 114. Use http://codebase.md/microsoft/semanticworkbench?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .devcontainer
│   ├── .vscode
│   │   └── settings.json
│   ├── devcontainer.json
│   ├── OPTIMIZING_FOR_CODESPACES.md
│   ├── POST_SETUP_README.md
│   └── README.md
├── .dockerignore
├── .gitattributes
├── .github
│   ├── policheck.yml
│   └── workflows
│       ├── assistants-codespace-assistant.yml
│       ├── assistants-document-assistant.yml
│       ├── assistants-explorer-assistant.yml
│       ├── assistants-guided-conversation-assistant.yml
│       ├── assistants-knowledge-transfer-assistant.yml
│       ├── assistants-navigator-assistant.yml
│       ├── assistants-project-assistant.yml
│       ├── assistants-prospector-assistant.yml
│       ├── assistants-skill-assistant.yml
│       ├── libraries.yml
│       ├── mcp-server-giphy.yml
│       ├── mcp-server-memory-filesystem-edit.yml
│       ├── mcp-server-memory-user-bio.yml
│       ├── mcp-server-memory-whiteboard.yml
│       ├── mcp-server-open-deep-research-clone.yml
│       ├── mcp-server-web-research.yml
│       ├── workbench-app.yml
│       └── workbench-service.yml
├── .gitignore
├── .multi-root-tools
│   ├── Makefile
│   └── README.md
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── ai_context
│   └── generated
│       ├── ASPIRE_ORCHESTRATOR.md
│       ├── ASSISTANT_CODESPACE.md
│       ├── ASSISTANT_DOCUMENT.md
│       ├── ASSISTANT_NAVIGATOR.md
│       ├── ASSISTANT_PROJECT.md
│       ├── ASSISTANT_PROSPECTOR.md
│       ├── ASSISTANTS_OTHER.md
│       ├── ASSISTANTS_OVERVIEW.md
│       ├── CONFIGURATION.md
│       ├── DOTNET_LIBRARIES.md
│       ├── EXAMPLES.md
│       ├── MCP_SERVERS.md
│       ├── PYTHON_LIBRARIES_AI_CLIENTS.md
│       ├── PYTHON_LIBRARIES_CORE.md
│       ├── PYTHON_LIBRARIES_EXTENSIONS.md
│       ├── PYTHON_LIBRARIES_SKILLS.md
│       ├── PYTHON_LIBRARIES_SPECIALIZED.md
│       ├── TOOLS.md
│       ├── WORKBENCH_FRONTEND.md
│       └── WORKBENCH_SERVICE.md
├── aspire-orchestrator
│   ├── .editorconfig
│   ├── Aspire.AppHost
│   │   ├── .gitignore
│   │   ├── appsettings.json
│   │   ├── Aspire.AppHost.csproj
│   │   ├── Program.cs
│   │   └── Properties
│   │       └── launchSettings.json
│   ├── Aspire.Extensions
│   │   ├── Aspire.Extensions.csproj
│   │   ├── Dashboard.cs
│   │   ├── DockerFileExtensions.cs
│   │   ├── PathNormalizer.cs
│   │   ├── UvAppHostingExtensions.cs
│   │   ├── UvAppResource.cs
│   │   ├── VirtualEnvironment.cs
│   │   └── WorkbenchServiceHostingExtensions.cs
│   ├── Aspire.ServiceDefaults
│   │   ├── Aspire.ServiceDefaults.csproj
│   │   └── Extensions.cs
│   ├── README.md
│   ├── run.sh
│   ├── SemanticWorkbench.Aspire.sln
│   └── SemanticWorkbench.Aspire.sln.DotSettings
├── assistants
│   ├── codespace-assistant
│   │   ├── .claude
│   │   │   └── settings.local.json
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── extensions.json
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── assets
│   │   │   │   ├── icon_context_transfer.svg
│   │   │   │   └── icon.svg
│   │   │   ├── chat.py
│   │   │   ├── config.py
│   │   │   ├── helpers.py
│   │   │   ├── response
│   │   │   │   ├── __init__.py
│   │   │   │   ├── completion_handler.py
│   │   │   │   ├── models.py
│   │   │   │   ├── request_builder.py
│   │   │   │   ├── response.py
│   │   │   │   ├── step_handler.py
│   │   │   │   └── utils
│   │   │   │       ├── __init__.py
│   │   │   │       ├── abbreviations.py
│   │   │   │       ├── formatting_utils.py
│   │   │   │       ├── message_utils.py
│   │   │   │       └── openai_utils.py
│   │   │   ├── text_includes
│   │   │   │   ├── card_content_context_transfer.md
│   │   │   │   ├── card_content.md
│   │   │   │   ├── codespace_assistant_info.md
│   │   │   │   ├── context_transfer_assistant_info.md
│   │   │   │   ├── guardrails_prompt.txt
│   │   │   │   ├── guidance_prompt_context_transfer.txt
│   │   │   │   ├── guidance_prompt.txt
│   │   │   │   ├── instruction_prompt_context_transfer.txt
│   │   │   │   └── instruction_prompt.txt
│   │   │   └── whiteboard
│   │   │       ├── __init__.py
│   │   │       ├── _inspector.py
│   │   │       └── _whiteboard.py
│   │   ├── assistant.code-workspace
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── document-assistant
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── assets
│   │   │   │   └── icon.svg
│   │   │   ├── chat.py
│   │   │   ├── config.py
│   │   │   ├── context_management
│   │   │   │   ├── __init__.py
│   │   │   │   └── inspector.py
│   │   │   ├── filesystem
│   │   │   │   ├── __init__.py
│   │   │   │   ├── _convert.py
│   │   │   │   ├── _file_sources.py
│   │   │   │   ├── _filesystem.py
│   │   │   │   ├── _inspector.py
│   │   │   │   ├── _model.py
│   │   │   │   ├── _prompts.py
│   │   │   │   └── _tasks.py
│   │   │   ├── guidance
│   │   │   │   ├── __init__.py
│   │   │   │   ├── dynamic_ui_inspector.py
│   │   │   │   ├── guidance_config.py
│   │   │   │   ├── guidance_prompts.py
│   │   │   │   └── README.md
│   │   │   ├── response
│   │   │   │   ├── __init__.py
│   │   │   │   ├── completion_handler.py
│   │   │   │   ├── models.py
│   │   │   │   ├── prompts.py
│   │   │   │   ├── responder.py
│   │   │   │   └── utils
│   │   │   │       ├── __init__.py
│   │   │   │       ├── formatting_utils.py
│   │   │   │       ├── message_utils.py
│   │   │   │       ├── openai_utils.py
│   │   │   │       ├── tokens_tiktoken.py
│   │   │   │       └── workbench_messages.py
│   │   │   ├── text_includes
│   │   │   │   └── document_assistant_info.md
│   │   │   ├── types.py
│   │   │   └── whiteboard
│   │   │       ├── __init__.py
│   │   │       ├── _inspector.py
│   │   │       └── _whiteboard.py
│   │   ├── assistant.code-workspace
│   │   ├── CLAUDE.md
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   ├── __init__.py
│   │   │   ├── test_convert.py
│   │   │   └── test_data
│   │   │       ├── blank_image.png
│   │   │       ├── Formatting Test.docx
│   │   │       ├── sample_data.csv
│   │   │       ├── sample_data.xlsx
│   │   │       ├── sample_page.html
│   │   │       ├── sample_presentation.pptx
│   │   │       └── simple_pdf.pdf
│   │   └── uv.lock
│   ├── explorer-assistant
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── chat.py
│   │   │   ├── config.py
│   │   │   ├── helpers.py
│   │   │   ├── response
│   │   │   │   ├── __init__.py
│   │   │   │   ├── model.py
│   │   │   │   ├── response_anthropic.py
│   │   │   │   ├── response_openai.py
│   │   │   │   └── response.py
│   │   │   └── text_includes
│   │   │       └── guardrails_prompt.txt
│   │   ├── assistant.code-workspace
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── guided-conversation-assistant
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── agents
│   │   │   │   ├── guided_conversation
│   │   │   │   │   ├── config.py
│   │   │   │   │   ├── definition.py
│   │   │   │   │   └── definitions
│   │   │   │   │       ├── er_triage.py
│   │   │   │   │       ├── interview.py
│   │   │   │   │       ├── patient_intake.py
│   │   │   │   │       └── poem_feedback.py
│   │   │   │   └── guided_conversation_agent.py
│   │   │   ├── chat.py
│   │   │   ├── config.py
│   │   │   └── text_includes
│   │   │       └── guardrails_prompt.txt
│   │   ├── assistant.code-workspace
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── knowledge-transfer-assistant
│   │   ├── .claude
│   │   │   └── settings.local.json
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── agentic
│   │   │   │   ├── __init__.py
│   │   │   │   ├── analysis.py
│   │   │   │   ├── coordinator_support.py
│   │   │   │   └── team_welcome.py
│   │   │   ├── assets
│   │   │   │   ├── icon-knowledge-transfer.svg
│   │   │   │   └── icon.svg
│   │   │   ├── assistant.py
│   │   │   ├── common.py
│   │   │   ├── config.py
│   │   │   ├── conversation_clients.py
│   │   │   ├── conversation_share_link.py
│   │   │   ├── data.py
│   │   │   ├── domain
│   │   │   │   ├── __init__.py
│   │   │   │   ├── audience_manager.py
│   │   │   │   ├── information_request_manager.py
│   │   │   │   ├── knowledge_brief_manager.py
│   │   │   │   ├── knowledge_digest_manager.py
│   │   │   │   ├── learning_objectives_manager.py
│   │   │   │   └── share_manager.py
│   │   │   ├── files.py
│   │   │   ├── logging.py
│   │   │   ├── notifications.py
│   │   │   ├── respond.py
│   │   │   ├── storage_models.py
│   │   │   ├── storage.py
│   │   │   ├── string_utils.py
│   │   │   ├── text_includes
│   │   │   │   ├── assistant_info.md
│   │   │   │   ├── card_content.md
│   │   │   │   ├── coordinator_instructions.txt
│   │   │   │   ├── coordinator_role.txt
│   │   │   │   ├── knowledge_digest_instructions.txt
│   │   │   │   ├── knowledge_digest_prompt.txt
│   │   │   │   ├── share_information_request_detection.txt
│   │   │   │   ├── team_instructions.txt
│   │   │   │   ├── team_role.txt
│   │   │   │   └── welcome_message_generation.txt
│   │   │   ├── tools
│   │   │   │   ├── __init__.py
│   │   │   │   ├── base.py
│   │   │   │   ├── information_requests.py
│   │   │   │   ├── learning_objectives.py
│   │   │   │   ├── learning_outcomes.py
│   │   │   │   ├── progress_tracking.py
│   │   │   │   └── share_setup.py
│   │   │   ├── ui_tabs
│   │   │   │   ├── __init__.py
│   │   │   │   ├── brief.py
│   │   │   │   ├── common.py
│   │   │   │   ├── debug.py
│   │   │   │   ├── learning.py
│   │   │   │   └── sharing.py
│   │   │   └── utils.py
│   │   ├── CLAUDE.md
│   │   ├── docs
│   │   │   ├── design
│   │   │   │   ├── actions.md
│   │   │   │   └── inference.md
│   │   │   ├── DEV_GUIDE.md
│   │   │   ├── how-kta-works.md
│   │   │   ├── JTBD.md
│   │   │   ├── knowledge-transfer-goals.md
│   │   │   ├── learning_assistance.md
│   │   │   ├── notable_claude_conversations
│   │   │   │   ├── clarifying_quad_modal_design.md
│   │   │   │   ├── CLAUDE_PROMPTS.md
│   │   │   │   ├── transfer_state.md
│   │   │   │   └── trying_the_context_agent.md
│   │   │   └── opportunities-of-knowledge-transfer.md
│   │   ├── knowledge-transfer-assistant.code-workspace
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   ├── __init__.py
│   │   │   ├── test_artifact_loading.py
│   │   │   ├── test_inspector.py
│   │   │   ├── test_share_manager.py
│   │   │   ├── test_share_storage.py
│   │   │   ├── test_share_tools.py
│   │   │   └── test_team_mode.py
│   │   └── uv.lock
│   ├── Makefile
│   ├── navigator-assistant
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── assets
│   │   │   │   ├── card_content.md
│   │   │   │   └── icon.svg
│   │   │   ├── chat.py
│   │   │   ├── config.py
│   │   │   ├── helpers.py
│   │   │   ├── response
│   │   │   │   ├── __init__.py
│   │   │   │   ├── completion_handler.py
│   │   │   │   ├── completion_requestor.py
│   │   │   │   ├── local_tool
│   │   │   │   │   ├── __init__.py
│   │   │   │   │   ├── add_assistant_to_conversation.py
│   │   │   │   │   ├── list_assistant_services.py
│   │   │   │   │   └── model.py
│   │   │   │   ├── models.py
│   │   │   │   ├── prompt.py
│   │   │   │   ├── request_builder.py
│   │   │   │   ├── response.py
│   │   │   │   ├── step_handler.py
│   │   │   │   └── utils
│   │   │   │       ├── __init__.py
│   │   │   │       ├── formatting_utils.py
│   │   │   │       ├── message_utils.py
│   │   │   │       ├── openai_utils.py
│   │   │   │       └── tools.py
│   │   │   ├── text_includes
│   │   │   │   ├── guardrails_prompt.md
│   │   │   │   ├── guidance_prompt.md
│   │   │   │   ├── instruction_prompt.md
│   │   │   │   ├── navigator_assistant_info.md
│   │   │   │   └── semantic_workbench_features.md
│   │   │   └── whiteboard
│   │   │       ├── __init__.py
│   │   │       ├── _inspector.py
│   │   │       └── _whiteboard.py
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── project-assistant
│   │   ├── .cspell
│   │   │   └── custom-dictionary-workspace.txt
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── agentic
│   │   │   │   ├── __init__.py
│   │   │   │   ├── act.py
│   │   │   │   ├── coordinator_next_action.py
│   │   │   │   ├── create_invitation.py
│   │   │   │   ├── detect_audience_and_takeaways.py
│   │   │   │   ├── detect_coordinator_actions.py
│   │   │   │   ├── detect_information_request_needs.py
│   │   │   │   ├── detect_knowledge_package_gaps.py
│   │   │   │   ├── focus.py
│   │   │   │   ├── respond.py
│   │   │   │   ├── team_welcome.py
│   │   │   │   └── update_digest.py
│   │   │   ├── assets
│   │   │   │   ├── icon-knowledge-transfer.svg
│   │   │   │   └── icon.svg
│   │   │   ├── assistant.py
│   │   │   ├── common.py
│   │   │   ├── config.py
│   │   │   ├── conversation_clients.py
│   │   │   ├── data.py
│   │   │   ├── domain
│   │   │   │   ├── __init__.py
│   │   │   │   ├── audience_manager.py
│   │   │   │   ├── conversation_preferences_manager.py
│   │   │   │   ├── information_request_manager.py
│   │   │   │   ├── knowledge_brief_manager.py
│   │   │   │   ├── knowledge_digest_manager.py
│   │   │   │   ├── learning_objectives_manager.py
│   │   │   │   ├── share_manager.py
│   │   │   │   ├── tasks_manager.py
│   │   │   │   └── transfer_manager.py
│   │   │   ├── errors.py
│   │   │   ├── files.py
│   │   │   ├── logging.py
│   │   │   ├── notifications.py
│   │   │   ├── prompt_utils.py
│   │   │   ├── storage.py
│   │   │   ├── string_utils.py
│   │   │   ├── text_includes
│   │   │   │   ├── actor_instructions.md
│   │   │   │   ├── assistant_info.md
│   │   │   │   ├── card_content.md
│   │   │   │   ├── coordinator_instructions copy.md
│   │   │   │   ├── coordinator_instructions.md
│   │   │   │   ├── create_invitation.md
│   │   │   │   ├── detect_audience.md
│   │   │   │   ├── detect_coordinator_actions.md
│   │   │   │   ├── detect_information_request_needs.md
│   │   │   │   ├── detect_knowledge_package_gaps.md
│   │   │   │   ├── focus.md
│   │   │   │   ├── knowledge_digest_instructions.txt
│   │   │   │   ├── team_instructions.txt
│   │   │   │   ├── to_do.md
│   │   │   │   ├── update_knowledge_brief.md
│   │   │   │   ├── update_knowledge_digest.md
│   │   │   │   └── welcome_message_generation.txt
│   │   │   ├── tools
│   │   │   │   ├── __init__.py
│   │   │   │   ├── base.py
│   │   │   │   ├── conversation_preferences.py
│   │   │   │   ├── information_requests.py
│   │   │   │   ├── learning_objectives.py
│   │   │   │   ├── learning_outcomes.py
│   │   │   │   ├── progress_tracking.py
│   │   │   │   ├── share_setup.py
│   │   │   │   ├── system_reminders.py
│   │   │   │   ├── tasks.py
│   │   │   │   └── todo.py
│   │   │   ├── ui_tabs
│   │   │   │   ├── __init__.py
│   │   │   │   ├── brief.py
│   │   │   │   ├── common.py
│   │   │   │   ├── debug.py
│   │   │   │   ├── learning.py
│   │   │   │   └── sharing.py
│   │   │   └── utils.py
│   │   ├── CLAUDE.md
│   │   ├── docs
│   │   │   ├── design
│   │   │   │   ├── actions.md
│   │   │   │   ├── control_options.md
│   │   │   │   ├── design.md
│   │   │   │   ├── inference.md
│   │   │   │   └── PXL_20250814_190140267.jpg
│   │   │   ├── DEV_GUIDE.md
│   │   │   ├── how-kta-works.md
│   │   │   ├── JTBD.md
│   │   │   ├── knowledge-transfer-goals.md
│   │   │   ├── learning_assistance.md
│   │   │   ├── notable_claude_conversations
│   │   │   │   ├── clarifying_quad_modal_design.md
│   │   │   │   ├── CLAUDE_PROMPTS.md
│   │   │   │   ├── transfer_state.md
│   │   │   │   └── trying_the_context_agent.md
│   │   │   └── opportunities-of-knowledge-transfer.md
│   │   ├── knowledge-transfer-assistant.code-workspace
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   ├── __init__.py
│   │   │   ├── test_artifact_loading.py
│   │   │   ├── test_inspector.py
│   │   │   ├── test_share_manager.py
│   │   │   ├── test_share_storage.py
│   │   │   └── test_team_mode.py
│   │   └── uv.lock
│   ├── prospector-assistant
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── assistant
│   │   │   ├── __init__.py
│   │   │   ├── agents
│   │   │   │   ├── artifact_agent.py
│   │   │   │   ├── document
│   │   │   │   │   ├── config.py
│   │   │   │   │   ├── gc_draft_content_feedback_config.py
│   │   │   │   │   ├── gc_draft_outline_feedback_config.py
│   │   │   │   │   ├── guided_conversation.py
│   │   │   │   │   └── state.py
│   │   │   │   └── document_agent.py
│   │   │   ├── artifact_creation_extension
│   │   │   │   ├── __init__.py
│   │   │   │   ├── _llm.py
│   │   │   │   ├── config.py
│   │   │   │   ├── document.py
│   │   │   │   ├── extension.py
│   │   │   │   ├── store.py
│   │   │   │   ├── test
│   │   │   │   │   ├── conftest.py
│   │   │   │   │   ├── evaluation.py
│   │   │   │   │   ├── test_completion_with_tools.py
│   │   │   │   │   └── test_extension.py
│   │   │   │   └── tools.py
│   │   │   ├── chat.py
│   │   │   ├── config.py
│   │   │   ├── form_fill_extension
│   │   │   │   ├── __init__.py
│   │   │   │   ├── config.py
│   │   │   │   ├── extension.py
│   │   │   │   ├── inspector.py
│   │   │   │   ├── state.py
│   │   │   │   └── steps
│   │   │   │       ├── __init__.py
│   │   │   │       ├── _guided_conversation.py
│   │   │   │       ├── _llm.py
│   │   │   │       ├── acquire_form_step.py
│   │   │   │       ├── extract_form_fields_step.py
│   │   │   │       ├── fill_form_step.py
│   │   │   │       └── types.py
│   │   │   ├── helpers.py
│   │   │   ├── legacy.py
│   │   │   └── text_includes
│   │   │       ├── artifact_agent_enabled.md
│   │   │       ├── guardrails_prompt.txt
│   │   │       ├── guided_conversation_agent_enabled.md
│   │   │       └── skills_agent_enabled.md
│   │   ├── assistant.code-workspace
│   │   ├── gc_learnings
│   │   │   ├── gc_learnings.md
│   │   │   └── images
│   │   │       ├── gc_conversation_plan_fcn.png
│   │   │       ├── gc_conversation_plan_template.png
│   │   │       ├── gc_execute_plan_callstack.png
│   │   │       ├── gc_functions.png
│   │   │       ├── gc_generate_plan_callstack.png
│   │   │       ├── gc_get_resource_instructions.png
│   │   │       ├── gc_get_termination_instructions.png
│   │   │       ├── gc_kernel_arguments.png
│   │   │       ├── gc_plan_calls.png
│   │   │       ├── gc_termination_instructions.png
│   │   │       ├── sk_get_chat_message_contents.png
│   │   │       ├── sk_inner_get_chat_message_contents.png
│   │   │       ├── sk_send_request_prep.png
│   │   │       └── sk_send_request.png
│   │   ├── Makefile
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   └── skill-assistant
│       ├── .env.example
│       ├── .vscode
│       │   ├── launch.json
│       │   └── settings.json
│       ├── assistant
│       │   ├── __init__.py
│       │   ├── config.py
│       │   ├── logging.py
│       │   ├── skill_assistant.py
│       │   ├── skill_engine_registry.py
│       │   ├── skill_event_mapper.py
│       │   ├── text_includes
│       │   │   └── guardrails_prompt.txt
│       │   └── workbench_helpers.py
│       ├── assistant.code-workspace
│       ├── Makefile
│       ├── pyproject.toml
│       ├── README.md
│       ├── tests
│       │   └── test_setup.py
│       └── uv.lock
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── docs
│   ├── .vscode
│   │   └── settings.json
│   ├── ASSISTANT_CONFIG.md
│   ├── ASSISTANT_DEVELOPMENT_GUIDE.md
│   ├── CUSTOM_APP_REGISTRATION.md
│   ├── HOSTED_ASSISTANT_WITH_LOCAL_MCP_SERVERS.md
│   ├── images
│   │   ├── architecture-animation.gif
│   │   ├── configure_assistant.png
│   │   ├── conversation_canvas_open.png
│   │   ├── conversation_duplicate.png
│   │   ├── conversation_export.png
│   │   ├── conversation_share_dialog.png
│   │   ├── conversation_share_link.png
│   │   ├── dashboard_configured_view.png
│   │   ├── dashboard_view.png
│   │   ├── license_agreement.png
│   │   ├── message_bar.png
│   │   ├── message_inspection.png
│   │   ├── message_link.png
│   │   ├── new_prospector_assistant_dialog.png
│   │   ├── open_conversation_canvas.png
│   │   ├── prospector_example.png
│   │   ├── readme1.png
│   │   ├── readme2.png
│   │   ├── readme3.png
│   │   ├── rewind.png
│   │   ├── signin_page.png
│   │   └── splash_screen.png
│   ├── LOCAL_ASSISTANT_WITH_REMOTE_WORKBENCH.md
│   ├── SETUP_DEV_ENVIRONMENT.md
│   └── WORKBENCH_APP.md
├── examples
│   ├── dotnet
│   │   ├── .editorconfig
│   │   ├── dotnet-01-echo-bot
│   │   │   ├── appsettings.json
│   │   │   ├── dotnet-01-echo-bot.csproj
│   │   │   ├── MyAgent.cs
│   │   │   ├── MyAgentConfig.cs
│   │   │   ├── MyWorkbenchConnector.cs
│   │   │   ├── Program.cs
│   │   │   └── README.md
│   │   ├── dotnet-02-message-types-demo
│   │   │   ├── appsettings.json
│   │   │   ├── ConnectorExtensions.cs
│   │   │   ├── docs
│   │   │   │   ├── abc.png
│   │   │   │   ├── code.png
│   │   │   │   ├── config.png
│   │   │   │   ├── echo.png
│   │   │   │   ├── markdown.png
│   │   │   │   ├── mermaid.png
│   │   │   │   ├── reverse.png
│   │   │   │   └── safety-check.png
│   │   │   ├── dotnet-02-message-types-demo.csproj
│   │   │   ├── MyAgent.cs
│   │   │   ├── MyAgentConfig.cs
│   │   │   ├── MyWorkbenchConnector.cs
│   │   │   ├── Program.cs
│   │   │   └── README.md
│   │   └── dotnet-03-simple-chatbot
│   │       ├── appsettings.json
│   │       ├── ConnectorExtensions.cs
│   │       ├── dotnet-03-simple-chatbot.csproj
│   │       ├── MyAgent.cs
│   │       ├── MyAgentConfig.cs
│   │       ├── MyWorkbenchConnector.cs
│   │       ├── Program.cs
│   │       └── README.md
│   ├── Makefile
│   └── python
│       ├── python-01-echo-bot
│       │   ├── .env.example
│       │   ├── .vscode
│       │   │   ├── launch.json
│       │   │   └── settings.json
│       │   ├── assistant
│       │   │   ├── __init__.py
│       │   │   ├── chat.py
│       │   │   └── config.py
│       │   ├── assistant.code-workspace
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── python-02-simple-chatbot
│       │   ├── .env.example
│       │   ├── .vscode
│       │   │   ├── launch.json
│       │   │   └── settings.json
│       │   ├── assistant
│       │   │   ├── __init__.py
│       │   │   ├── chat.py
│       │   │   ├── config.py
│       │   │   └── text_includes
│       │   │       └── guardrails_prompt.txt
│       │   ├── assistant.code-workspace
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       └── python-03-multimodel-chatbot
│           ├── .env.example
│           ├── .vscode
│           │   ├── launch.json
│           │   └── settings.json
│           ├── assistant
│           │   ├── __init__.py
│           │   ├── chat.py
│           │   ├── config.py
│           │   ├── model_adapters.py
│           │   └── text_includes
│           │       └── guardrails_prompt.txt
│           ├── assistant.code-workspace
│           ├── Makefile
│           ├── pyproject.toml
│           ├── README.md
│           └── uv.lock
├── KNOWN_ISSUES.md
├── libraries
│   ├── dotnet
│   │   ├── .editorconfig
│   │   ├── pack.sh
│   │   ├── README.md
│   │   ├── SemanticWorkbench.sln
│   │   ├── SemanticWorkbench.sln.DotSettings
│   │   └── WorkbenchConnector
│   │       ├── AgentBase.cs
│   │       ├── AgentConfig
│   │       │   ├── AgentConfigBase.cs
│   │       │   ├── AgentConfigPropertyAttribute.cs
│   │       │   └── ConfigUtils.cs
│   │       ├── Constants.cs
│   │       ├── IAgentBase.cs
│   │       ├── icon.png
│   │       ├── Models
│   │       │   ├── Command.cs
│   │       │   ├── Conversation.cs
│   │       │   ├── ConversationEvent.cs
│   │       │   ├── DebugInfo.cs
│   │       │   ├── Insight.cs
│   │       │   ├── Message.cs
│   │       │   ├── MessageMetadata.cs
│   │       │   ├── Participant.cs
│   │       │   ├── Sender.cs
│   │       │   └── ServiceInfo.cs
│   │       ├── Storage
│   │       │   ├── AgentInfo.cs
│   │       │   ├── AgentServiceStorage.cs
│   │       │   └── IAgentServiceStorage.cs
│   │       ├── StringLoggingExtensions.cs
│   │       ├── Webservice.cs
│   │       ├── WorkbenchConfig.cs
│   │       ├── WorkbenchConnector.cs
│   │       └── WorkbenchConnector.csproj
│   ├── Makefile
│   └── python
│       ├── anthropic-client
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── anthropic_client
│       │   │   ├── __init__.py
│       │   │   ├── client.py
│       │   │   ├── config.py
│       │   │   └── messages.py
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── assistant-data-gen
│       │   ├── .vscode
│       │   │   ├── launch.json
│       │   │   └── settings.json
│       │   ├── assistant_data_gen
│       │   │   ├── __init__.py
│       │   │   ├── assistant_api.py
│       │   │   ├── config.py
│       │   │   ├── gce
│       │   │   │   ├── __init__.py
│       │   │   │   ├── gce_agent.py
│       │   │   │   └── prompts.py
│       │   │   └── pydantic_ai_utils.py
│       │   ├── configs
│       │   │   └── document_assistant_example_config.yaml
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── scripts
│       │   │   ├── gce_simulation.py
│       │   │   └── generate_scenario.py
│       │   └── uv.lock
│       ├── assistant-drive
│       │   ├── .gitignore
│       │   ├── .vscode
│       │   │   ├── extensions.json
│       │   │   └── settings.json
│       │   ├── assistant_drive
│       │   │   ├── __init__.py
│       │   │   ├── drive.py
│       │   │   └── tests
│       │   │       └── test_basic.py
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── pytest.ini
│       │   ├── README.md
│       │   ├── usage.ipynb
│       │   └── uv.lock
│       ├── assistant-extensions
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── assistant_extensions
│       │   │   ├── __init__.py
│       │   │   ├── ai_clients
│       │   │   │   └── config.py
│       │   │   ├── artifacts
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _artifacts.py
│       │   │   │   ├── _inspector.py
│       │   │   │   └── _model.py
│       │   │   ├── attachments
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _attachments.py
│       │   │   │   ├── _convert.py
│       │   │   │   ├── _model.py
│       │   │   │   ├── _shared.py
│       │   │   │   └── _summarizer.py
│       │   │   ├── chat_context_toolkit
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _config.py
│       │   │   │   ├── archive
│       │   │   │   │   ├── __init__.py
│       │   │   │   │   ├── _archive.py
│       │   │   │   │   └── _summarizer.py
│       │   │   │   ├── message_history
│       │   │   │   │   ├── __init__.py
│       │   │   │   │   ├── _history.py
│       │   │   │   │   └── _message.py
│       │   │   │   └── virtual_filesystem
│       │   │   │       ├── __init__.py
│       │   │   │       ├── _archive_file_source.py
│       │   │   │       └── _attachments_file_source.py
│       │   │   ├── dashboard_card
│       │   │   │   ├── __init__.py
│       │   │   │   └── _dashboard_card.py
│       │   │   ├── document_editor
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _extension.py
│       │   │   │   ├── _inspector.py
│       │   │   │   └── _model.py
│       │   │   ├── mcp
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _assistant_file_resource_handler.py
│       │   │   │   ├── _client_utils.py
│       │   │   │   ├── _devtunnel.py
│       │   │   │   ├── _model.py
│       │   │   │   ├── _openai_utils.py
│       │   │   │   ├── _sampling_handler.py
│       │   │   │   ├── _tool_utils.py
│       │   │   │   └── _workbench_file_resource_handler.py
│       │   │   ├── navigator
│       │   │   │   ├── __init__.py
│       │   │   │   └── _navigator.py
│       │   │   └── workflows
│       │   │       ├── __init__.py
│       │   │       ├── _model.py
│       │   │       ├── _workflows.py
│       │   │       └── runners
│       │   │           └── _user_proxy.py
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── test
│       │   │   └── attachments
│       │   │       └── test_attachments.py
│       │   └── uv.lock
│       ├── chat-context-toolkit
│       │   ├── .claude
│       │   │   └── settings.local.json
│       │   ├── .env.sample
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── assets
│       │   │   ├── archive_v1.png
│       │   │   ├── history_v1.png
│       │   │   └── vfs_v1.png
│       │   ├── chat_context_toolkit
│       │   │   ├── __init__.py
│       │   │   ├── archive
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _archive_reader.py
│       │   │   │   ├── _archive_task_queue.py
│       │   │   │   ├── _state.py
│       │   │   │   ├── _types.py
│       │   │   │   └── summarization
│       │   │   │       ├── __init__.py
│       │   │   │       └── _summarizer.py
│       │   │   ├── history
│       │   │   │   ├── __init__.py
│       │   │   │   ├── _budget.py
│       │   │   │   ├── _decorators.py
│       │   │   │   ├── _history.py
│       │   │   │   ├── _prioritize.py
│       │   │   │   ├── _types.py
│       │   │   │   └── tool_abbreviations
│       │   │   │       ├── __init__.py
│       │   │   │       └── _tool_abbreviations.py
│       │   │   └── virtual_filesystem
│       │   │       ├── __init__.py
│       │   │       ├── _types.py
│       │   │       ├── _virtual_filesystem.py
│       │   │       ├── README.md
│       │   │       └── tools
│       │   │           ├── __init__.py
│       │   │           ├── _ls_tool.py
│       │   │           ├── _tools.py
│       │   │           └── _view_tool.py
│       │   ├── CLAUDE.md
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── test
│       │   │   ├── archive
│       │   │   │   └── test_archive_reader.py
│       │   │   ├── history
│       │   │   │   ├── test_abbreviate_messages.py
│       │   │   │   ├── test_history.py
│       │   │   │   ├── test_pair_and_order_tool_messages.py
│       │   │   │   ├── test_prioritize.py
│       │   │   │   └── test_truncate_messages.py
│       │   │   └── virtual_filesystem
│       │   │       ├── test_virtual_filesystem.py
│       │   │       └── tools
│       │   │           ├── test_ls_tool.py
│       │   │           ├── test_tools.py
│       │   │           └── test_view_tool.py
│       │   └── uv.lock
│       ├── content-safety
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── content_safety
│       │   │   ├── __init__.py
│       │   │   ├── evaluators
│       │   │   │   ├── __init__.py
│       │   │   │   ├── azure_content_safety
│       │   │   │   │   ├── __init__.py
│       │   │   │   │   ├── config.py
│       │   │   │   │   └── evaluator.py
│       │   │   │   ├── config.py
│       │   │   │   ├── evaluator.py
│       │   │   │   └── openai_moderations
│       │   │   │       ├── __init__.py
│       │   │   │       ├── config.py
│       │   │   │       └── evaluator.py
│       │   │   └── README.md
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── events
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── events
│       │   │   ├── __init__.py
│       │   │   └── events.py
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── guided-conversation
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── guided_conversation
│       │   │   ├── __init__.py
│       │   │   ├── functions
│       │   │   │   ├── __init__.py
│       │   │   │   ├── conversation_plan.py
│       │   │   │   ├── execution.py
│       │   │   │   └── final_update_plan.py
│       │   │   ├── guided_conversation_agent.py
│       │   │   ├── plugins
│       │   │   │   ├── __init__.py
│       │   │   │   ├── agenda.py
│       │   │   │   └── artifact.py
│       │   │   └── utils
│       │   │       ├── __init__.py
│       │   │       ├── base_model_llm.py
│       │   │       ├── conversation_helpers.py
│       │   │       ├── openai_tool_calling.py
│       │   │       ├── plugin_helpers.py
│       │   │       └── resources.py
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── llm-client
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── llm_client
│       │   │   ├── __init__.py
│       │   │   └── model.py
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── Makefile
│       ├── mcp-extensions
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── Makefile
│       │   ├── mcp_extensions
│       │   │   ├── __init__.py
│       │   │   ├── _client_session.py
│       │   │   ├── _model.py
│       │   │   ├── _sampling.py
│       │   │   ├── _server_extensions.py
│       │   │   ├── _tool_utils.py
│       │   │   ├── llm
│       │   │   │   ├── __init__.py
│       │   │   │   ├── chat_completion.py
│       │   │   │   ├── helpers.py
│       │   │   │   ├── llm_types.py
│       │   │   │   ├── mcp_chat_completion.py
│       │   │   │   └── openai_chat_completion.py
│       │   │   └── server
│       │   │       ├── __init__.py
│       │   │       └── storage.py
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── tests
│       │   │   └── test_tool_utils.py
│       │   └── uv.lock
│       ├── mcp-tunnel
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── Makefile
│       │   ├── mcp_tunnel
│       │   │   ├── __init__.py
│       │   │   ├── _devtunnel.py
│       │   │   ├── _dir.py
│       │   │   └── _main.py
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   └── uv.lock
│       ├── openai-client
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── Makefile
│       │   ├── openai_client
│       │   │   ├── __init__.py
│       │   │   ├── chat_driver
│       │   │   │   ├── __init__.py
│       │   │   │   ├── chat_driver.ipynb
│       │   │   │   ├── chat_driver.py
│       │   │   │   ├── message_history_providers
│       │   │   │   │   ├── __init__.py
│       │   │   │   │   ├── in_memory_message_history_provider.py
│       │   │   │   │   ├── local_message_history_provider.py
│       │   │   │   │   ├── message_history_provider.py
│       │   │   │   │   └── tests
│       │   │   │   │       └── formatted_instructions_test.py
│       │   │   │   └── README.md
│       │   │   ├── client.py
│       │   │   ├── completion.py
│       │   │   ├── config.py
│       │   │   ├── errors.py
│       │   │   ├── logging.py
│       │   │   ├── messages.py
│       │   │   ├── tokens.py
│       │   │   └── tools.py
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── tests
│       │   │   ├── test_command_parsing.py
│       │   │   ├── test_formatted_messages.py
│       │   │   ├── test_messages.py
│       │   │   └── test_tokens.py
│       │   └── uv.lock
│       ├── semantic-workbench-api-model
│       │   ├── .vscode
│       │   │   └── settings.json
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── semantic_workbench_api_model
│       │   │   ├── __init__.py
│       │   │   ├── assistant_model.py
│       │   │   ├── assistant_service_client.py
│       │   │   ├── workbench_model.py
│       │   │   └── workbench_service_client.py
│       │   └── uv.lock
│       ├── semantic-workbench-assistant
│       │   ├── .vscode
│       │   │   ├── launch.json
│       │   │   └── settings.json
│       │   ├── Makefile
│       │   ├── pyproject.toml
│       │   ├── README.md
│       │   ├── semantic_workbench_assistant
│       │   │   ├── __init__.py
│       │   │   ├── assistant_app
│       │   │   │   ├── __init__.py
│       │   │   │   ├── assistant.py
│       │   │   │   ├── config.py
│       │   │   │   ├── content_safety.py
│       │   │   │   ├── context.py
│       │   │   │   ├── error.py
│       │   │   │   ├── export_import.py
│       │   │   │   ├── protocol.py
│       │   │   │   └── service.py
│       │   │   ├── assistant_service.py
│       │   │   ├── auth.py
│       │   │   ├── canonical.py
│       │   │   ├── command.py
│       │   │   ├── config.py
│       │   │   ├── logging_config.py
│       │   │   ├── settings.py
│       │   │   ├── start.py
│       │   │   └── storage.py
│       │   ├── tests
│       │   │   ├── conftest.py
│       │   │   ├── test_assistant_app.py
│       │   │   ├── test_canonical.py
│       │   │   ├── test_config.py
│       │   │   └── test_storage.py
│       │   └── uv.lock
│       └── skills
│           ├── .vscode
│           │   └── settings.json
│           ├── Makefile
│           ├── README.md
│           └── skill-library
│               ├── .vscode
│               │   └── settings.json
│               ├── docs
│               │   └── vs-recipe-tool.md
│               ├── Makefile
│               ├── pyproject.toml
│               ├── README.md
│               ├── skill_library
│               │   ├── __init__.py
│               │   ├── chat_driver_helpers.py
│               │   ├── cli
│               │   │   ├── azure_openai.py
│               │   │   ├── conversation_history.py
│               │   │   ├── README.md
│               │   │   ├── run_routine.py
│               │   │   ├── settings.py
│               │   │   └── skill_logger.py
│               │   ├── engine.py
│               │   ├── llm_info.txt
│               │   ├── logging.py
│               │   ├── README.md
│               │   ├── routine_stack.py
│               │   ├── skill.py
│               │   ├── skills
│               │   │   ├── common
│               │   │   │   ├── __init__.py
│               │   │   │   ├── common_skill.py
│               │   │   │   └── routines
│               │   │   │       ├── bing_search.py
│               │   │   │       ├── consolidate.py
│               │   │   │       ├── echo.py
│               │   │   │       ├── gather_context.py
│               │   │   │       ├── get_content_from_url.py
│               │   │   │       ├── gpt_complete.py
│               │   │   │       ├── select_user_intent.py
│               │   │   │       └── summarize.py
│               │   │   ├── eval
│               │   │   │   ├── __init__.py
│               │   │   │   ├── eval_skill.py
│               │   │   │   └── routines
│               │   │   │       └── eval.py
│               │   │   ├── fabric
│               │   │   │   ├── __init__.py
│               │   │   │   ├── fabric_skill.py
│               │   │   │   ├── patterns
│               │   │   │   │   ├── agility_story
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── ai
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_answers
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_candidates
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_cfp_submission
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_claims
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_comments
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_debate
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_email_headers
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_incident
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_interviewer_techniques
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_logs
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_malware
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_military_strategy
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_mistakes
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_paper
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_patent
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_personality
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_presentation
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_product_feedback
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_proposition
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_prose
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_prose_json
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_prose_pinker
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_risk
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_sales_call
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_spiritual_text
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_tech_impact
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_threat_report
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── analyze_threat_report_cmds
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── analyze_threat_report_trends
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── answer_interview_question
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── ask_secure_by_design_questions
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── ask_uncle_duke
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── capture_thinkers_work
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── check_agreement
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── clean_text
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── coding_master
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── compare_and_contrast
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── convert_to_markdown
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_5_sentence_summary
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_academic_paper
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_ai_jobs_analysis
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_aphorisms
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_art_prompt
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_better_frame
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_coding_project
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_command
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_cyber_summary
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_design_document
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_diy
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_formal_email
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_git_diff_commit
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_graph_from_input
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_hormozi_offer
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_idea_compass
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_investigation_visualization
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_keynote
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_logo
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_markmap_visualization
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_mermaid_visualization
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_mermaid_visualization_for_github
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_micro_summary
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_network_threat_landscape
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_newsletter_entry
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_npc
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_pattern
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_prd
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_prediction_block
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_quiz
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_reading_plan
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_recursive_outline
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_report_finding
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_rpg_summary
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_security_update
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_show_intro
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_sigma_rules
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_story_explanation
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_stride_threat_model
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_summary
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_tags
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_threat_scenarios
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_ttrc_graph
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_ttrc_narrative
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_upgrade_pack
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_user_story
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── create_video_chapters
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── create_visualization
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── dialog_with_socrates
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── enrich_blog_post
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── explain_code
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── explain_docs
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── explain_math
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── explain_project
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── explain_terms
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── export_data_as_csv
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_algorithm_update_recommendations
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── extract_article_wisdom
│               │   │   │   │   │   ├── dmiessler
│               │   │   │   │   │   │   └── extract_wisdom-1.0.0
│               │   │   │   │   │   │       ├── system.md
│               │   │   │   │   │   │       └── user.md
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── extract_book_ideas
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_book_recommendations
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_business_ideas
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_controversial_ideas
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_core_message
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_ctf_writeup
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_domains
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_extraordinary_claims
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_ideas
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_insights
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_insights_dm
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_instructions
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_jokes
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_latest_video
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_main_idea
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_most_redeeming_thing
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_patterns
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_poc
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── extract_predictions
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_primary_problem
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_primary_solution
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_product_features
│               │   │   │   │   │   ├── dmiessler
│               │   │   │   │   │   │   └── extract_wisdom-1.0.0
│               │   │   │   │   │   │       ├── system.md
│               │   │   │   │   │   │       └── user.md
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_questions
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_recipe
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_recommendations
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── extract_references
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── extract_skills
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_song_meaning
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_sponsors
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_videoid
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── extract_wisdom
│               │   │   │   │   │   ├── dmiessler
│               │   │   │   │   │   │   └── extract_wisdom-1.0.0
│               │   │   │   │   │   │       ├── system.md
│               │   │   │   │   │   │       └── user.md
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_wisdom_agents
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_wisdom_dm
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── extract_wisdom_nometa
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── find_hidden_message
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── find_logical_fallacies
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── get_wow_per_minute
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── get_youtube_rss
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── humanize
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── identify_dsrp_distinctions
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── identify_dsrp_perspectives
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── identify_dsrp_relationships
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── identify_dsrp_systems
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── identify_job_stories
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── improve_academic_writing
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── improve_prompt
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── improve_report_finding
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── improve_writing
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── judge_output
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── label_and_rate
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── loaded
│               │   │   │   │   ├── md_callout
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── official_pattern_template
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── pattern_explanations.md
│               │   │   │   │   ├── prepare_7s_strategy
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── provide_guidance
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── rate_ai_response
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── rate_ai_result
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── rate_content
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── rate_value
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── raw_query
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── raycast
│               │   │   │   │   │   ├── capture_thinkers_work
│               │   │   │   │   │   ├── create_story_explanation
│               │   │   │   │   │   ├── extract_primary_problem
│               │   │   │   │   │   ├── extract_wisdom
│               │   │   │   │   │   └── yt
│               │   │   │   │   ├── recommend_artists
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── recommend_pipeline_upgrades
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── recommend_talkpanel_topics
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── refine_design_document
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── review_design
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── sanitize_broken_html_to_markdown
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── show_fabric_options_markmap
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── solve_with_cot
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── stringify
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── suggest_pattern
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── summarize
│               │   │   │   │   │   ├── dmiessler
│               │   │   │   │   │   │   └── summarize
│               │   │   │   │   │   │       ├── system.md
│               │   │   │   │   │   │       └── user.md
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── summarize_debate
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_git_changes
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_git_diff
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_lecture
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_legislation
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_meeting
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_micro
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── summarize_newsletter
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── summarize_paper
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── summarize_prompt
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── summarize_pull-requests
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── summarize_rpg_session
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_analyze_challenge_handling
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_check_metrics
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_create_h3_career
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_create_opening_sentences
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_describe_life_outlook
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_extract_intro_sentences
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_extract_panel_topics
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_find_blindspots
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_find_negative_thinking
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_find_neglected_goals
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_give_encouragement
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_red_team_thinking
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_threat_model_plans
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_visualize_mission_goals_projects
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── t_year_in_review
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── to_flashcards
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── transcribe_minutes
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── translate
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── tweet
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── write_essay
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── write_hackerone_report
│               │   │   │   │   │   ├── README.md
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── write_latex
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── write_micro_essay
│               │   │   │   │   │   └── system.md
│               │   │   │   │   ├── write_nuclei_template_rule
│               │   │   │   │   │   ├── system.md
│               │   │   │   │   │   └── user.md
│               │   │   │   │   ├── write_pull-request
│               │   │   │   │   │   └── system.md
│               │   │   │   │   └── write_semgrep_rule
│               │   │   │   │       ├── system.md
│               │   │   │   │       └── user.md
│               │   │   │   └── routines
│               │   │   │       ├── list.py
│               │   │   │       ├── run.py
│               │   │   │       └── show.py
│               │   │   ├── guided_conversation
│               │   │   │   ├── __init__.py
│               │   │   │   ├── agenda.py
│               │   │   │   ├── artifact_helpers.py
│               │   │   │   ├── chat_completions
│               │   │   │   │   ├── fix_agenda_error.py
│               │   │   │   │   ├── fix_artifact_error.py
│               │   │   │   │   ├── generate_agenda.py
│               │   │   │   │   ├── generate_artifact_updates.py
│               │   │   │   │   ├── generate_final_artifact.py
│               │   │   │   │   └── generate_message.py
│               │   │   │   ├── conversation_guides
│               │   │   │   │   ├── __init__.py
│               │   │   │   │   ├── acrostic_poem.py
│               │   │   │   │   ├── er_triage.py
│               │   │   │   │   ├── interview.py
│               │   │   │   │   └── patient_intake.py
│               │   │   │   ├── guide.py
│               │   │   │   ├── guided_conversation_skill.py
│               │   │   │   ├── logging.py
│               │   │   │   ├── message.py
│               │   │   │   ├── resources.py
│               │   │   │   ├── routines
│               │   │   │   │   └── guided_conversation.py
│               │   │   │   └── tests
│               │   │   │       ├── conftest.py
│               │   │   │       ├── test_artifact_helpers.py
│               │   │   │       ├── test_generate_agenda.py
│               │   │   │       ├── test_generate_artifact_updates.py
│               │   │   │       ├── test_generate_final_artifact.py
│               │   │   │       └── test_resource.py
│               │   │   ├── meta
│               │   │   │   ├── __init__.py
│               │   │   │   ├── meta_skill.py
│               │   │   │   ├── README.md
│               │   │   │   └── routines
│               │   │   │       └── generate_routine.py
│               │   │   ├── posix
│               │   │   │   ├── __init__.py
│               │   │   │   ├── posix_skill.py
│               │   │   │   ├── routines
│               │   │   │   │   ├── append_file.py
│               │   │   │   │   ├── cd.py
│               │   │   │   │   ├── ls.py
│               │   │   │   │   ├── make_home_dir.py
│               │   │   │   │   ├── mkdir.py
│               │   │   │   │   ├── mv.py
│               │   │   │   │   ├── pwd.py
│               │   │   │   │   ├── read_file.py
│               │   │   │   │   ├── rm.py
│               │   │   │   │   ├── touch.py
│               │   │   │   │   └── write_file.py
│               │   │   │   └── sandbox_shell.py
│               │   │   ├── README.md
│               │   │   ├── research
│               │   │   │   ├── __init__.py
│               │   │   │   ├── README.md
│               │   │   │   ├── research_skill.py
│               │   │   │   └── routines
│               │   │   │       ├── answer_question_about_content.py
│               │   │   │       ├── evaluate_answer.py
│               │   │   │       ├── generate_research_plan.py
│               │   │   │       ├── generate_search_query.py
│               │   │   │       ├── update_research_plan.py
│               │   │   │       ├── web_research.py
│               │   │   │       └── web_search.py
│               │   │   ├── research2
│               │   │   │   ├── __init__.py
│               │   │   │   ├── README.md
│               │   │   │   ├── research_skill.py
│               │   │   │   └── routines
│               │   │   │       ├── facts.py
│               │   │   │       ├── make_final_report.py
│               │   │   │       ├── research.py
│               │   │   │       ├── search_plan.py
│               │   │   │       ├── search.py
│               │   │   │       └── visit_pages.py
│               │   │   └── web_research
│               │   │       ├── __init__.py
│               │   │       ├── README.md
│               │   │       ├── research_skill.py
│               │   │       └── routines
│               │   │           ├── facts.py
│               │   │           ├── make_final_report.py
│               │   │           ├── research.py
│               │   │           ├── search_plan.py
│               │   │           ├── search.py
│               │   │           └── visit_pages.py
│               │   ├── tests
│               │   │   ├── test_common_skill.py
│               │   │   ├── test_integration.py
│               │   │   ├── test_routine_stack.py
│               │   │   ├── tst_skill
│               │   │   │   ├── __init__.py
│               │   │   │   └── routines
│               │   │   │       ├── __init__.py
│               │   │   │       └── a_routine.py
│               │   │   └── utilities
│               │   │       ├── test_find_template_vars.py
│               │   │       ├── test_make_arg_set.py
│               │   │       ├── test_paramspec.py
│               │   │       ├── test_parse_command_string.py
│               │   │       └── test_to_string.py
│               │   ├── types.py
│               │   ├── usage.py
│               │   └── utilities.py
│               └── uv.lock
├── LICENSE
├── Makefile
├── mcp-servers
│   ├── ai-assist-content
│   │   ├── .vscode
│   │   │   └── settings.json
│   │   ├── mcp-example-brave-search.md
│   │   ├── mcp-fastmcp-typescript-README.md
│   │   ├── mcp-llms-full.txt
│   │   ├── mcp-metadata-tips.md
│   │   ├── mcp-python-sdk-README.md
│   │   ├── mcp-typescript-sdk-README.md
│   │   ├── pydanticai-documentation.md
│   │   ├── pydanticai-example-question-graph.md
│   │   ├── pydanticai-example-weather.md
│   │   ├── pydanticai-tutorial.md
│   │   └── README.md
│   ├── Makefile
│   ├── mcp-server-bing-search
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server_bing_search
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── prompts
│   │   │   │   ├── __init__.py
│   │   │   │   ├── clean_website.py
│   │   │   │   └── filter_links.py
│   │   │   ├── server.py
│   │   │   ├── start.py
│   │   │   ├── tools.py
│   │   │   ├── types.py
│   │   │   ├── utils.py
│   │   │   └── web
│   │   │       ├── __init__.py
│   │   │       ├── get_content.py
│   │   │       ├── llm_processing.py
│   │   │       ├── process_website.py
│   │   │       └── search_bing.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   └── test_tools.py
│   │   └── uv.lock
│   ├── mcp-server-bundle
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server_bundle
│   │   │   ├── __init__.py
│   │   │   └── main.py
│   │   ├── pyinstaller.spec
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── mcp-server-filesystem
│   │   ├── .env.example
│   │   ├── .github
│   │   │   └── workflows
│   │   │       └── ci.yml
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server_filesystem
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── server.py
│   │   │   └── start.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   └── test_filesystem.py
│   │   └── uv.lock
│   ├── mcp-server-filesystem-edit
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── data
│   │   │   ├── attachments
│   │   │   │   ├── Daily Game Ideas.txt
│   │   │   │   ├── Frontend Framework Proposal.txt
│   │   │   │   ├── ReDoodle.txt
│   │   │   │   └── Research Template.tex
│   │   │   ├── test_cases.yaml
│   │   │   └── transcripts
│   │   │       ├── transcript_research_simple.md
│   │   │       ├── transcript_Startup_Idea_1_202503031513.md
│   │   │       ├── transcript_Startup_Idea_2_202503031659.md
│   │   │       └── transcript_Web_Frontends_202502281551.md
│   │   ├── Makefile
│   │   ├── mcp_server_filesystem_edit
│   │   │   ├── __init__.py
│   │   │   ├── app_handling
│   │   │   │   ├── __init__.py
│   │   │   │   ├── excel.py
│   │   │   │   ├── miktex.py
│   │   │   │   ├── office_common.py
│   │   │   │   ├── powerpoint.py
│   │   │   │   └── word.py
│   │   │   ├── config.py
│   │   │   ├── evals
│   │   │   │   ├── __init__.py
│   │   │   │   ├── common.py
│   │   │   │   ├── run_comments.py
│   │   │   │   ├── run_edit.py
│   │   │   │   └── run_ppt_edit.py
│   │   │   ├── prompts
│   │   │   │   ├── __init__.py
│   │   │   │   ├── add_comments.py
│   │   │   │   ├── analyze_comments.py
│   │   │   │   ├── latex_edit.py
│   │   │   │   ├── markdown_draft.py
│   │   │   │   ├── markdown_edit.py
│   │   │   │   └── powerpoint_edit.py
│   │   │   ├── server.py
│   │   │   ├── start.py
│   │   │   ├── tools
│   │   │   │   ├── __init__.py
│   │   │   │   ├── add_comments.py
│   │   │   │   ├── edit_adapters
│   │   │   │   │   ├── __init__.py
│   │   │   │   │   ├── common.py
│   │   │   │   │   ├── latex.py
│   │   │   │   │   └── markdown.py
│   │   │   │   ├── edit.py
│   │   │   │   └── helpers.py
│   │   │   └── types.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   ├── app_handling
│   │   │   │   ├── test_excel.py
│   │   │   │   ├── test_miktext.py
│   │   │   │   ├── test_office_common.py
│   │   │   │   ├── test_powerpoint.py
│   │   │   │   └── test_word.py
│   │   │   ├── conftest.py
│   │   │   └── tools
│   │   │       └── edit_adapters
│   │   │           ├── test_latex.py
│   │   │           └── test_markdown.py
│   │   └── uv.lock
│   ├── mcp-server-fusion
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── AddInIcon.svg
│   │   ├── config.py
│   │   ├── FusionMCPServerAddIn.manifest
│   │   ├── FusionMCPServerAddIn.py
│   │   ├── mcp_server_fusion
│   │   │   ├── __init__.py
│   │   │   ├── fusion_mcp_server.py
│   │   │   ├── fusion_utils
│   │   │   │   ├── __init__.py
│   │   │   │   ├── event_utils.py
│   │   │   │   ├── general_utils.py
│   │   │   │   └── tool_utils.py
│   │   │   ├── mcp_tools
│   │   │   │   ├── __init__.py
│   │   │   │   ├── fusion_3d_operation.py
│   │   │   │   ├── fusion_geometry.py
│   │   │   │   ├── fusion_pattern.py
│   │   │   │   └── fusion_sketch.py
│   │   │   └── vendor
│   │   │       └── README.md
│   │   ├── README.md
│   │   └── requirements.txt
│   ├── mcp-server-giphy
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── giphy_search.py
│   │   │   ├── sampling.py
│   │   │   ├── server.py
│   │   │   ├── start.py
│   │   │   └── utils.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── mcp-server-memory-user-bio
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server_memory_user_bio
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── server.py
│   │   │   └── start.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── mcp-server-memory-whiteboard
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server_memory_whiteboard
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── server.py
│   │   │   └── start.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── mcp-server-office
│   │   ├── .env.example
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── build.sh
│   │   ├── data
│   │   │   ├── attachments
│   │   │   │   ├── Daily Game Ideas.txt
│   │   │   │   ├── Frontend Framework Proposal.txt
│   │   │   │   └── ReDoodle.txt
│   │   │   └── word
│   │   │       ├── test_cases.yaml
│   │   │       └── transcripts
│   │   │           ├── transcript_Startup_Idea_1_202503031513.md
│   │   │           ├── transcript_Startup_Idea_2_202503031659.md
│   │   │           └── transcript_Web_Frontends_202502281551.md
│   │   ├── Makefile
│   │   ├── mcp_server
│   │   │   ├── __init__.py
│   │   │   ├── app_interaction
│   │   │   │   ├── __init__.py
│   │   │   │   ├── excel_editor.py
│   │   │   │   ├── powerpoint_editor.py
│   │   │   │   └── word_editor.py
│   │   │   ├── config.py
│   │   │   ├── constants.py
│   │   │   ├── evals
│   │   │   │   ├── __init__.py
│   │   │   │   ├── common.py
│   │   │   │   ├── run_comment_analysis.py
│   │   │   │   ├── run_feedback.py
│   │   │   │   └── run_markdown_edit.py
│   │   │   ├── helpers.py
│   │   │   ├── markdown_edit
│   │   │   │   ├── __init__.py
│   │   │   │   ├── comment_analysis.py
│   │   │   │   ├── feedback_step.py
│   │   │   │   ├── markdown_edit.py
│   │   │   │   └── utils.py
│   │   │   ├── prompts
│   │   │   │   ├── __init__.py
│   │   │   │   ├── comment_analysis.py
│   │   │   │   ├── feedback.py
│   │   │   │   ├── markdown_draft.py
│   │   │   │   └── markdown_edit.py
│   │   │   ├── server.py
│   │   │   ├── start.py
│   │   │   └── types.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── tests
│   │   │   └── test_word_editor.py
│   │   └── uv.lock
│   ├── mcp-server-open-deep-research
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── libs
│   │   │   │   └── open_deep_research
│   │   │   │       ├── cookies.py
│   │   │   │       ├── mdconvert.py
│   │   │   │       ├── run_agents.py
│   │   │   │       ├── text_inspector_tool.py
│   │   │   │       ├── text_web_browser.py
│   │   │   │       └── visual_qa.py
│   │   │   ├── open_deep_research.py
│   │   │   ├── server.py
│   │   │   └── start.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── mcp-server-open-deep-research-clone
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── .vscode
│   │   │   ├── launch.json
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── mcp_server_open_deep_research_clone
│   │   │   ├── __init__.py
│   │   │   ├── azure_openai.py
│   │   │   ├── config.py
│   │   │   ├── logging.py
│   │   │   ├── sampling.py
│   │   │   ├── server.py
│   │   │   ├── start.py
│   │   │   ├── utils.py
│   │   │   └── web_research.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── test
│   │   │   └── test_open_deep_research_clone.py
│   │   └── uv.lock
│   ├── mcp-server-template
│   │   ├── .taplo.toml
│   │   ├── .vscode
│   │   │   └── settings.json
│   │   ├── copier.yml
│   │   ├── README.md
│   │   └── template
│   │       └── {{ project_slug }}
│   │           ├── .env.example.jinja
│   │           ├── .gitignore
│   │           ├── .vscode
│   │           │   ├── launch.json.jinja
│   │           │   └── settings.json
│   │           ├── {{ module_name }}
│   │           │   ├── __init__.py
│   │           │   ├── config.py.jinja
│   │           │   ├── server.py.jinja
│   │           │   └── start.py.jinja
│   │           ├── Makefile.jinja
│   │           ├── pyproject.toml.jinja
│   │           └── README.md.jinja
│   ├── mcp-server-vscode
│   │   ├── .eslintrc.cjs
│   │   ├── .gitignore
│   │   ├── .npmrc
│   │   ├── .vscode
│   │   │   ├── extensions.json
│   │   │   ├── launch.json
│   │   │   ├── settings.json
│   │   │   └── tasks.json
│   │   ├── .vscode-test.mjs
│   │   ├── .vscodeignore
│   │   ├── ASSISTANT_BOOTSTRAP.md
│   │   ├── eslint.config.mjs
│   │   ├── images
│   │   │   └── icon.png
│   │   ├── LICENSE
│   │   ├── Makefile
│   │   ├── out
│   │   │   ├── extension.d.ts
│   │   │   ├── extension.js
│   │   │   ├── test
│   │   │   │   ├── extension.test.d.ts
│   │   │   │   └── extension.test.js
│   │   │   ├── tools
│   │   │   │   ├── code_checker.d.ts
│   │   │   │   ├── code_checker.js
│   │   │   │   ├── debug_tools.d.ts
│   │   │   │   ├── debug_tools.js
│   │   │   │   ├── focus_editor.d.ts
│   │   │   │   ├── focus_editor.js
│   │   │   │   ├── search_symbol.d.ts
│   │   │   │   └── search_symbol.js
│   │   │   └── utils
│   │   │       ├── port.d.ts
│   │   │       └── port.js
│   │   ├── package.json
│   │   ├── pnpm-lock.yaml
│   │   ├── prettier.config.cjs
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── extension.d.ts
│   │   │   ├── extension.ts
│   │   │   ├── test
│   │   │   │   ├── extension.test.d.ts
│   │   │   │   └── extension.test.ts
│   │   │   ├── tools
│   │   │   │   ├── code_checker.d.ts
│   │   │   │   ├── code_checker.ts
│   │   │   │   ├── debug_tools.d.ts
│   │   │   │   ├── debug_tools.ts
│   │   │   │   ├── focus_editor.d.ts
│   │   │   │   ├── focus_editor.ts
│   │   │   │   ├── search_symbol.d.ts
│   │   │   │   └── search_symbol.ts
│   │   │   └── utils
│   │   │       ├── port.d.ts
│   │   │       └── port.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.tsbuildinfo
│   │   ├── vsc-extension-quickstart.md
│   │   └── webpack.config.js
│   └── mcp-server-web-research
│       ├── .env.example
│       ├── .gitignore
│       ├── .vscode
│       │   ├── launch.json
│       │   └── settings.json
│       ├── Makefile
│       ├── mcp_server_web_research
│       │   ├── __init__.py
│       │   ├── azure_openai.py
│       │   ├── config.py
│       │   ├── logging.py
│       │   ├── sampling.py
│       │   ├── server.py
│       │   ├── start.py
│       │   ├── utils.py
│       │   └── web_research.py
│       ├── pyproject.toml
│       ├── README.md
│       ├── test
│       │   └── test_web_research.py
│       └── uv.lock
├── README.md
├── RESPONSIBLE_AI_FAQ.md
├── ruff.toml
├── SECURITY.md
├── semantic-workbench.code-workspace
├── SUPPORT.md
├── tools
│   ├── build_ai_context_files.py
│   ├── collect_files.py
│   ├── docker
│   │   ├── azure_website_sshd.conf
│   │   ├── docker-entrypoint.sh
│   │   ├── Dockerfile.assistant
│   │   └── Dockerfile.mcp-server
│   ├── makefiles
│   │   ├── docker-assistant.mk
│   │   ├── docker-mcp-server.mk
│   │   ├── docker.mk
│   │   ├── python.mk
│   │   ├── recursive.mk
│   │   └── shell.mk
│   ├── reset-service-data.ps1
│   ├── reset-service-data.sh
│   ├── run-app.ps1
│   ├── run-app.sh
│   ├── run-canonical-agent.ps1
│   ├── run-canonical-agent.sh
│   ├── run-dotnet-examples-with-aspire.sh
│   ├── run-python-example1.sh
│   ├── run-python-example2.ps1
│   ├── run-python-example2.sh
│   ├── run-service.ps1
│   ├── run-service.sh
│   ├── run-workbench-chatbot.ps1
│   └── run-workbench-chatbot.sh
├── workbench-app
│   ├── .dockerignore
│   ├── .env.example
│   ├── .eslintrc.cjs
│   ├── .gitignore
│   ├── .vscode
│   │   ├── launch.json
│   │   └── settings.json
│   ├── docker-entrypoint.sh
│   ├── Dockerfile
│   ├── docs
│   │   ├── APP_DEV_GUIDE.md
│   │   ├── MESSAGE_METADATA.md
│   │   ├── MESSAGE_TYPES.md
│   │   ├── README.md
│   │   └── STATE_INSPECTORS.md
│   ├── index.html
│   ├── Makefile
│   ├── nginx.conf
│   ├── package.json
│   ├── pnpm-lock.yaml
│   ├── prettier.config.cjs
│   ├── public
│   │   └── assets
│   │       ├── background-1-upscaled.jpg
│   │       ├── background-1-upscaled.png
│   │       ├── background-1.jpg
│   │       ├── background-1.png
│   │       ├── background-2.jpg
│   │       ├── background-2.png
│   │       ├── experimental-feature.jpg
│   │       ├── favicon.svg
│   │       ├── workflow-designer-1.jpg
│   │       ├── workflow-designer-outlets.jpg
│   │       ├── workflow-designer-states.jpg
│   │       └── workflow-designer-transitions.jpg
│   ├── README.md
│   ├── run.sh
│   ├── src
│   │   ├── components
│   │   │   ├── App
│   │   │   │   ├── AppFooter.tsx
│   │   │   │   ├── AppHeader.tsx
│   │   │   │   ├── AppMenu.tsx
│   │   │   │   ├── AppView.tsx
│   │   │   │   ├── CodeLabel.tsx
│   │   │   │   ├── CommandButton.tsx
│   │   │   │   ├── ConfirmLeave.tsx
│   │   │   │   ├── ContentExport.tsx
│   │   │   │   ├── ContentImport.tsx
│   │   │   │   ├── CopyButton.tsx
│   │   │   │   ├── DialogControl.tsx
│   │   │   │   ├── DynamicIframe.tsx
│   │   │   │   ├── ErrorListFromAppState.tsx
│   │   │   │   ├── ErrorMessageBar.tsx
│   │   │   │   ├── ExperimentalNotice.tsx
│   │   │   │   ├── FormWidgets
│   │   │   │   │   ├── BaseModelEditorWidget.tsx
│   │   │   │   │   ├── CustomizedArrayFieldTemplate.tsx
│   │   │   │   │   ├── CustomizedFieldTemplate.tsx
│   │   │   │   │   ├── CustomizedObjectFieldTemplate.tsx
│   │   │   │   │   └── InspectableWidget.tsx
│   │   │   │   ├── LabelWithDescription.tsx
│   │   │   │   ├── Loading.tsx
│   │   │   │   ├── MenuItemControl.tsx
│   │   │   │   ├── MiniControl.tsx
│   │   │   │   ├── MyAssistantServiceRegistrations.tsx
│   │   │   │   ├── MyItemsManager.tsx
│   │   │   │   ├── OverflowMenu.tsx
│   │   │   │   ├── PresenceMotionList.tsx
│   │   │   │   ├── ProfileSettings.tsx
│   │   │   │   └── TooltipWrapper.tsx
│   │   │   ├── Assistants
│   │   │   │   ├── ApplyConfigButton.tsx
│   │   │   │   ├── AssistantAdd.tsx
│   │   │   │   ├── AssistantConfigExportButton.tsx
│   │   │   │   ├── AssistantConfigImportButton.tsx
│   │   │   │   ├── AssistantConfiguration.tsx
│   │   │   │   ├── AssistantConfigure.tsx
│   │   │   │   ├── AssistantCreate.tsx
│   │   │   │   ├── AssistantDelete.tsx
│   │   │   │   ├── AssistantDuplicate.tsx
│   │   │   │   ├── AssistantExport.tsx
│   │   │   │   ├── AssistantImport.tsx
│   │   │   │   ├── AssistantRemove.tsx
│   │   │   │   ├── AssistantRename.tsx
│   │   │   │   ├── AssistantServiceInfo.tsx
│   │   │   │   ├── AssistantServiceMetadata.tsx
│   │   │   │   └── MyAssistants.tsx
│   │   │   ├── AssistantServiceRegistrations
│   │   │   │   ├── AssistantServiceRegistrationApiKey.tsx
│   │   │   │   ├── AssistantServiceRegistrationApiKeyReset.tsx
│   │   │   │   ├── AssistantServiceRegistrationCreate.tsx
│   │   │   │   └── AssistantServiceRegistrationRemove.tsx
│   │   │   ├── Conversations
│   │   │   │   ├── Canvas
│   │   │   │   │   ├── AssistantCanvas.tsx
│   │   │   │   │   ├── AssistantCanvasList.tsx
│   │   │   │   │   ├── AssistantInspector.tsx
│   │   │   │   │   ├── AssistantInspectorList.tsx
│   │   │   │   │   └── ConversationCanvas.tsx
│   │   │   │   ├── ChatInputPlugins
│   │   │   │   │   ├── ClearEditorPlugin.tsx
│   │   │   │   │   ├── LexicalMenu.ts
│   │   │   │   │   ├── ParticipantMentionsPlugin.tsx
│   │   │   │   │   ├── TypeaheadMenuPlugin.css
│   │   │   │   │   └── TypeaheadMenuPlugin.tsx
│   │   │   │   ├── ContentRenderers
│   │   │   │   │   ├── CodeContentRenderer.tsx
│   │   │   │   │   ├── ContentListRenderer.tsx
│   │   │   │   │   ├── ContentRenderer.tsx
│   │   │   │   │   ├── DiffRenderer.tsx
│   │   │   │   │   ├── HtmlContentRenderer.tsx
│   │   │   │   │   ├── JsonSchemaContentRenderer.tsx
│   │   │   │   │   ├── MarkdownContentRenderer.tsx
│   │   │   │   │   ├── MarkdownEditorRenderer.tsx
│   │   │   │   │   ├── MermaidContentRenderer.tsx
│   │   │   │   │   ├── MusicABCContentRenderer.css
│   │   │   │   │   └── MusicABCContentRenderer.tsx
│   │   │   │   ├── ContextWindow.tsx
│   │   │   │   ├── ConversationCreate.tsx
│   │   │   │   ├── ConversationDuplicate.tsx
│   │   │   │   ├── ConversationExport.tsx
│   │   │   │   ├── ConversationFileIcon.tsx
│   │   │   │   ├── ConversationRemove.tsx
│   │   │   │   ├── ConversationRename.tsx
│   │   │   │   ├── ConversationShare.tsx
│   │   │   │   ├── ConversationShareCreate.tsx
│   │   │   │   ├── ConversationShareList.tsx
│   │   │   │   ├── ConversationShareView.tsx
│   │   │   │   ├── ConversationsImport.tsx
│   │   │   │   ├── ConversationTranscript.tsx
│   │   │   │   ├── DebugInspector.tsx
│   │   │   │   ├── FileItem.tsx
│   │   │   │   ├── FileList.tsx
│   │   │   │   ├── InputAttachmentList.tsx
│   │   │   │   ├── InputOptionsControl.tsx
│   │   │   │   ├── InteractHistory.tsx
│   │   │   │   ├── InteractInput.tsx
│   │   │   │   ├── Message
│   │   │   │   │   ├── AttachmentSection.tsx
│   │   │   │   │   ├── ContentRenderer.tsx
│   │   │   │   │   ├── ContentSafetyNotice.tsx
│   │   │   │   │   ├── InteractMessage.tsx
│   │   │   │   │   ├── MessageActions.tsx
│   │   │   │   │   ├── MessageBase.tsx
│   │   │   │   │   ├── MessageBody.tsx
│   │   │   │   │   ├── MessageContent.tsx
│   │   │   │   │   ├── MessageFooter.tsx
│   │   │   │   │   ├── MessageHeader.tsx
│   │   │   │   │   ├── NotificationAccordion.tsx
│   │   │   │   │   └── ToolResultMessage.tsx
│   │   │   │   ├── MessageDelete.tsx
│   │   │   │   ├── MessageLink.tsx
│   │   │   │   ├── MyConversations.tsx
│   │   │   │   ├── MyShares.tsx
│   │   │   │   ├── ParticipantAvatar.tsx
│   │   │   │   ├── ParticipantAvatarGroup.tsx
│   │   │   │   ├── ParticipantItem.tsx
│   │   │   │   ├── ParticipantList.tsx
│   │   │   │   ├── ParticipantStatus.tsx
│   │   │   │   ├── RewindConversation.tsx
│   │   │   │   ├── ShareRemove.tsx
│   │   │   │   ├── SpeechButton.tsx
│   │   │   │   └── ToolCalls.tsx
│   │   │   └── FrontDoor
│   │   │       ├── Chat
│   │   │       │   ├── AssistantDrawer.tsx
│   │   │       │   ├── CanvasDrawer.tsx
│   │   │       │   ├── Chat.tsx
│   │   │       │   ├── ChatCanvas.tsx
│   │   │       │   ├── ChatControls.tsx
│   │   │       │   └── ConversationDrawer.tsx
│   │   │       ├── Controls
│   │   │       │   ├── AssistantCard.tsx
│   │   │       │   ├── AssistantSelector.tsx
│   │   │       │   ├── AssistantServiceSelector.tsx
│   │   │       │   ├── ConversationItem.tsx
│   │   │       │   ├── ConversationList.tsx
│   │   │       │   ├── ConversationListOptions.tsx
│   │   │       │   ├── NewConversationButton.tsx
│   │   │       │   ├── NewConversationForm.tsx
│   │   │       │   └── SiteMenuButton.tsx
│   │   │       ├── GlobalContent.tsx
│   │   │       └── MainContent.tsx
│   │   ├── Constants.ts
│   │   ├── global.d.ts
│   │   ├── index.css
│   │   ├── libs
│   │   │   ├── AppStorage.ts
│   │   │   ├── AuthHelper.ts
│   │   │   ├── EventSubscriptionManager.ts
│   │   │   ├── Theme.ts
│   │   │   ├── useAssistantCapabilities.ts
│   │   │   ├── useChatCanvasController.ts
│   │   │   ├── useConversationEvents.ts
│   │   │   ├── useConversationUtility.ts
│   │   │   ├── useCreateConversation.ts
│   │   │   ├── useDebugComponentLifecycle.ts
│   │   │   ├── useDragAndDrop.ts
│   │   │   ├── useEnvironment.ts
│   │   │   ├── useExportUtility.ts
│   │   │   ├── useHistoryUtility.ts
│   │   │   ├── useKeySequence.ts
│   │   │   ├── useMediaQuery.ts
│   │   │   ├── useMicrosoftGraph.ts
│   │   │   ├── useNotify.tsx
│   │   │   ├── useParticipantUtility.tsx
│   │   │   ├── useSiteUtility.ts
│   │   │   ├── useWorkbenchEventSource.ts
│   │   │   ├── useWorkbenchService.ts
│   │   │   └── Utility.ts
│   │   ├── main.tsx
│   │   ├── models
│   │   │   ├── Assistant.ts
│   │   │   ├── AssistantCapability.ts
│   │   │   ├── AssistantServiceInfo.ts
│   │   │   ├── AssistantServiceRegistration.ts
│   │   │   ├── Config.ts
│   │   │   ├── Conversation.ts
│   │   │   ├── ConversationFile.ts
│   │   │   ├── ConversationMessage.ts
│   │   │   ├── ConversationMessageDebug.ts
│   │   │   ├── ConversationParticipant.ts
│   │   │   ├── ConversationShare.ts
│   │   │   ├── ConversationShareRedemption.ts
│   │   │   ├── ConversationState.ts
│   │   │   ├── ConversationStateDescription.ts
│   │   │   ├── ServiceEnvironment.ts
│   │   │   └── User.ts
│   │   ├── redux
│   │   │   ├── app
│   │   │   │   ├── hooks.ts
│   │   │   │   ├── rtkQueryErrorLogger.ts
│   │   │   │   └── store.ts
│   │   │   └── features
│   │   │       ├── app
│   │   │       │   ├── appSlice.ts
│   │   │       │   └── AppState.ts
│   │   │       ├── chatCanvas
│   │   │       │   ├── chatCanvasSlice.ts
│   │   │       │   └── ChatCanvasState.ts
│   │   │       ├── localUser
│   │   │       │   ├── localUserSlice.ts
│   │   │       │   └── LocalUserState.ts
│   │   │       └── settings
│   │   │           ├── settingsSlice.ts
│   │   │           └── SettingsState.ts
│   │   ├── Root.tsx
│   │   ├── routes
│   │   │   ├── AcceptTerms.tsx
│   │   │   ├── AssistantEditor.tsx
│   │   │   ├── AssistantServiceRegistrationEditor.tsx
│   │   │   ├── Dashboard.tsx
│   │   │   ├── ErrorPage.tsx
│   │   │   ├── FrontDoor.tsx
│   │   │   ├── Login.tsx
│   │   │   ├── Settings.tsx
│   │   │   ├── ShareRedeem.tsx
│   │   │   └── Shares.tsx
│   │   ├── services
│   │   │   └── workbench
│   │   │       ├── assistant.ts
│   │   │       ├── assistantService.ts
│   │   │       ├── conversation.ts
│   │   │       ├── file.ts
│   │   │       ├── index.ts
│   │   │       ├── participant.ts
│   │   │       ├── share.ts
│   │   │       ├── state.ts
│   │   │       └── workbench.ts
│   │   └── vite-env.d.ts
│   ├── tools
│   │   └── filtered-ts-prune.cjs
│   ├── tsconfig.json
│   └── vite.config.ts
└── workbench-service
    ├── .env.example
    ├── .vscode
    │   ├── extensions.json
    │   ├── launch.json
    │   └── settings.json
    ├── alembic.ini
    ├── devdb
    │   ├── docker-compose.yaml
    │   └── postgresql-init.sh
    ├── Dockerfile
    ├── Makefile
    ├── migrations
    │   ├── env.py
    │   ├── README
    │   ├── script.py.mako
    │   └── versions
    │       ├── 2024_09_19_000000_69dcda481c14_init.py
    │       ├── 2024_09_19_190029_dffb1d7e219a_file_version_filename.py
    │       ├── 2024_09_20_204130_b29524775484_share.py
    │       ├── 2024_10_30_231536_039bec8edc33_index_message_type.py
    │       ├── 2024_11_04_204029_5149c7fb5a32_conversationmessagedebug.py
    │       ├── 2024_11_05_015124_245baf258e11_double_check_debugs.py
    │       ├── 2024_11_25_191056_a106de176394_drop_workflow.py
    │       ├── 2025_03_19_140136_aaaf792d4d72_set_user_title_set.py
    │       ├── 2025_03_21_153250_3763629295ad_add_assistant_template_id.py
    │       ├── 2025_05_19_163613_b2f86e981885_delete_context_transfer_assistants.py
    │       └── 2025_06_18_174328_503c739152f3_delete_knowlege_transfer_assistants.py
    ├── pyproject.toml
    ├── README.md
    ├── semantic_workbench_service
    │   ├── __init__.py
    │   ├── api.py
    │   ├── assistant_api_key.py
    │   ├── auth.py
    │   ├── azure_speech.py
    │   ├── config.py
    │   ├── controller
    │   │   ├── __init__.py
    │   │   ├── assistant_service_client_pool.py
    │   │   ├── assistant_service_registration.py
    │   │   ├── assistant.py
    │   │   ├── conversation_share.py
    │   │   ├── conversation.py
    │   │   ├── convert.py
    │   │   ├── exceptions.py
    │   │   ├── export_import.py
    │   │   ├── file.py
    │   │   ├── participant.py
    │   │   └── user.py
    │   ├── db.py
    │   ├── event.py
    │   ├── files.py
    │   ├── logging_config.py
    │   ├── middleware.py
    │   ├── query.py
    │   ├── service_user_principals.py
    │   ├── service.py
    │   └── start.py
    ├── tests
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── docker-compose.yaml
    │   ├── test_assistant_api_key.py
    │   ├── test_files.py
    │   ├── test_integration.py
    │   ├── test_middleware.py
    │   ├── test_migrations.py
    │   ├── test_workbench_service.py
    │   └── types.py
    └── uv.lock
```

# Files

--------------------------------------------------------------------------------
/workbench-service/semantic_workbench_service/service.py:
--------------------------------------------------------------------------------

```python
import asyncio
import contextlib
import datetime
import json
import logging
import urllib.parse
import uuid
from collections import defaultdict
from contextlib import asynccontextmanager
from typing import (
    Annotated,
    Any,
    AsyncContextManager,
    AsyncIterator,
    Callable,
    NoReturn,
)

import asgi_correlation_id
import starlette.background
from asgi_correlation_id import CorrelationIdMiddleware
from fastapi import (
    BackgroundTasks,
    FastAPI,
    File,
    Form,
    HTTPException,
    Query,
    Request,
    Response,
    UploadFile,
    status,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, StreamingResponse
from semantic_workbench_api_model.assistant_model import (
    ConfigPutRequestModel,
    ConfigResponseModel,
    ServiceInfoModel,
    StateDescriptionListResponseModel,
    StatePutRequestModel,
    StateResponseModel,
)
from semantic_workbench_api_model.workbench_model import (
    Assistant,
    AssistantList,
    AssistantServiceInfoList,
    AssistantServiceRegistration,
    AssistantServiceRegistrationList,
    AssistantStateEvent,
    Conversation,
    ConversationEvent,
    ConversationEventType,
    ConversationImportResult,
    ConversationList,
    ConversationMessage,
    ConversationMessageDebug,
    ConversationMessageList,
    ConversationParticipant,
    ConversationParticipantList,
    ConversationShare,
    ConversationShareList,
    ConversationShareRedemption,
    ConversationShareRedemptionList,
    FileList,
    FileVersions,
    MessageType,
    NewAssistant,
    NewAssistantServiceRegistration,
    NewConversation,
    NewConversationMessage,
    NewConversationShare,
    ParticipantRole,
    UpdateAssistant,
    UpdateAssistantServiceRegistration,
    UpdateAssistantServiceRegistrationUrl,
    UpdateConversation,
    UpdateFile,
    UpdateParticipant,
    UpdateUser,
    User,
    UserList,
)
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
from sse_starlette import EventSourceResponse, ServerSentEvent

from semantic_workbench_service import azure_speech
from semantic_workbench_service.logging_config import log_request_middleware

from . import assistant_api_key, auth, controller, db, files, middleware, settings
from .event import ConversationEventQueueItem

logger = logging.getLogger(__name__)


def init(
    app: FastAPI,
    register_lifespan_handler: Callable[[Callable[[], AsyncContextManager[None]]], None],
) -> None:
    api_key_store = assistant_api_key.get_store()
    stop_signal: asyncio.Event = asyncio.Event()

    conversation_sse_queues_lock = asyncio.Lock()
    conversation_sse_queues: dict[uuid.UUID, set[asyncio.Queue[ConversationEvent]]] = defaultdict(set)

    user_sse_queues_lock = asyncio.Lock()
    user_sse_queues: dict[str, set[asyncio.Queue[ConversationEvent]]] = defaultdict(set)

    assistant_event_queues: dict[uuid.UUID, asyncio.Queue[ConversationEvent]] = {}

    background_tasks: set[asyncio.Task] = set()

    def _controller_get_session() -> AsyncContextManager[AsyncSession]:
        return db.create_session(app.state.db_engine)

    async def _forward_events_to_assistant(
        assistant_id: uuid.UUID, event_queue: asyncio.Queue[ConversationEvent]
    ) -> NoReturn:
        while True:
            try:
                event = await event_queue.get()
                event_queue.task_done()

                asgi_correlation_id.correlation_id.set(event.correlation_id)

                start_time = datetime.datetime.now(datetime.UTC)

                await assistant_controller.forward_event_to_assistant(assistant_id=assistant_id, event=event)

                end_time = datetime.datetime.now(datetime.UTC)
                logger.debug(
                    "forwarded event to assistant; assistant_id: %s, conversation_id: %s, event_id: %s,"
                    " duration: %s, time since event: %s",
                    assistant_id,
                    event.conversation_id,
                    event.id,
                    end_time - start_time,
                    end_time - event.timestamp,
                )

            except Exception:
                logger.exception("exception in _forward_events_to_assistant")

    async def _notify_event(queue_item: ConversationEventQueueItem) -> None:
        if stop_signal.is_set():
            logger.warning(
                "ignoring event due to stop signal; conversation_id: %s, event: %s, id: %s",
                queue_item.event.conversation_id,
                queue_item.event.event,
                queue_item.event.id,
            )
            return

        logger.debug(
            "received event to notify; conversation_id: %s, event: %s, event_id: %s, audience: %s",
            queue_item.event.conversation_id,
            queue_item.event.event,
            queue_item.event.id,
            queue_item.event_audience,
        )

        if "user" in queue_item.event_audience:
            enqueued_count = 0
            async with conversation_sse_queues_lock:
                for queue in conversation_sse_queues.get(queue_item.event.conversation_id, {}):
                    enqueued_count += 1
                    await queue.put(queue_item.event)

            logger.debug(
                "enqueued event for SSE; count: %d, conversation_id: %s, event: %s, event_id: %s",
                enqueued_count,
                queue_item.event.conversation_id,
                queue_item.event.event,
                queue_item.event.id,
            )

            if queue_item.event.event in [
                ConversationEventType.message_created,
                ConversationEventType.message_deleted,
                ConversationEventType.conversation_updated,
                ConversationEventType.participant_created,
                ConversationEventType.participant_updated,
            ]:
                task = asyncio.create_task(_notify_user_event(queue_item.event), name="notify_user_event")
                background_tasks.add(task)
                task.add_done_callback(background_tasks.discard)

        if "assistant" in queue_item.event_audience:
            async with _controller_get_session() as session:
                assistant_ids = (
                    await session.exec(
                        select(db.Assistant.assistant_id)
                        .join(
                            db.AssistantParticipant,
                            col(db.Assistant.assistant_id) == col(db.AssistantParticipant.assistant_id),
                        )
                        .join(db.AssistantServiceRegistration)
                        .where(col(db.AssistantServiceRegistration.assistant_service_online).is_(True))
                        .where(col(db.AssistantParticipant.active_participant).is_(True))
                        .where(db.AssistantParticipant.conversation_id == queue_item.event.conversation_id)
                    )
                ).all()

            for assistant_id in assistant_ids:
                if assistant_id not in assistant_event_queues:
                    queue = asyncio.Queue()
                    assistant_event_queues[assistant_id] = queue
                    task = asyncio.create_task(
                        _forward_events_to_assistant(assistant_id, queue),
                        name=f"forward_events_to_{assistant_id}",
                    )
                    background_tasks.add(task)

                await assistant_event_queues[assistant_id].put(queue_item.event)
                logger.debug(
                    "enqueued event for assistant; conversation_id: %s, event: %s, event_id: %s, assistant_id: %s",
                    queue_item.event.conversation_id,
                    queue_item.event.event,
                    queue_item.event.id,
                    assistant_id,
                )

    async def _notify_user_event(event: ConversationEvent) -> None:
        listening_user_ids = set(user_sse_queues.keys())
        async with _controller_get_session() as session:
            active_user_participants = (
                await session.exec(
                    select(db.UserParticipant.user_id).where(
                        col(db.UserParticipant.active_participant).is_(True),
                        db.UserParticipant.conversation_id == event.conversation_id,
                        col(db.UserParticipant.user_id).in_(listening_user_ids),
                    )
                )
            ).all()

        if not active_user_participants:
            return

        async with user_sse_queues_lock:
            for user_id in active_user_participants:
                for queue in user_sse_queues.get(user_id, {}):
                    await queue.put(event)
                    logger.debug(
                        "enqueued event for user SSE; user_id: %s, conversation_id: %s", user_id, event.conversation_id
                    )

    assistant_client_pool = controller.AssistantServiceClientPool(api_key_store=api_key_store)

    assistant_service_registration_controller = controller.AssistantServiceRegistrationController(
        get_session=_controller_get_session,
        notify_event=_notify_event,
        api_key_store=api_key_store,
        client_pool=assistant_client_pool,
    )

    app.add_middleware(
        middleware.AuthMiddleware,
        exclude_methods={"OPTIONS"},
        exclude_paths=set(settings.service.anonymous_paths),
        api_key_source=assistant_service_registration_controller.api_key_source,
    )
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
        expose_headers=["*"],
    )
    app.add_middleware(CorrelationIdMiddleware)

    app.middleware("http")(log_request_middleware())

    user_controller = controller.UserController(get_session=_controller_get_session)
    assistant_controller = controller.AssistantController(
        get_session=_controller_get_session,
        notify_event=_notify_event,
        client_pool=assistant_client_pool,
        file_storage=files.Storage(settings.storage),
    )
    conversation_controller = controller.ConversationController(
        get_session=_controller_get_session,
        notify_event=_notify_event,
        assistant_controller=assistant_controller,
    )
    conversation_share_controller = controller.ConversationShareController(
        get_session=_controller_get_session,
        notify_event=_notify_event,
    )

    file_controller = controller.FileController(
        get_session=_controller_get_session,
        notify_event=_notify_event,
        file_storage=files.Storage(settings.storage),
    )

    @asynccontextmanager
    async def _lifespan() -> AsyncIterator[None]:
        async with db.create_engine(settings.db) as engine:
            await db.bootstrap_db(engine, settings=settings.db)

            app.state.db_engine = engine

            background_tasks.add(
                asyncio.create_task(
                    _update_assistant_service_online_status(), name="update_assistant_service_online_status"
                ),
            )

            try:
                yield

            finally:
                stop_signal.set()

                for task in background_tasks:
                    task.cancel()

                with contextlib.suppress(asyncio.CancelledError):
                    await asyncio.gather(*background_tasks, return_exceptions=True)

    register_lifespan_handler(_lifespan)

    async def _update_assistant_service_online_status() -> NoReturn:
        while True:
            try:
                await asyncio.sleep(settings.service.assistant_service_online_check_interval_seconds)
                await assistant_service_registration_controller.check_assistant_service_online_expired()

            except Exception:
                logger.exception("exception in _update_assistant_service_online_status")

    @app.get("/")
    async def root() -> Response:
        return Response(status_code=status.HTTP_200_OK, content="")

    @app.get("/users")
    async def list_users(
        user_ids: list[str] = Query(alias="id"),
    ) -> UserList:
        return await user_controller.get_users(user_ids=user_ids)

    @app.get("/users/me")
    async def get_user_me(
        user_principal: auth.DependsUserPrincipal,
    ) -> User:
        return await user_controller.get_user_me(user_principal=user_principal)

    @app.put("/users/me")
    async def update_user_me(
        user_principal: auth.DependsUserPrincipal,
        update_user: UpdateUser,
    ) -> User:
        return await user_controller.update_user(
            user_principal=user_principal, user_id=user_principal.user_id, update_user=update_user
        )

    @app.post("/assistant-service-registrations")
    async def create_assistant_service_registration(
        user_principal: auth.DependsUserPrincipal,
        new_assistant_service: NewAssistantServiceRegistration,
    ) -> AssistantServiceRegistration:
        return await assistant_service_registration_controller.create_registration(
            user_principal=user_principal, new_assistant_service=new_assistant_service
        )

    @app.put("/assistant-service-registrations/{assistant_service_id:path}")
    async def update_assistant_service_registration_url(
        principal: auth.DependsAssistantServicePrincipal,
        assistant_service_id: str,
        update_assistant_service: UpdateAssistantServiceRegistrationUrl,
        background_tasks: BackgroundTasks,
    ) -> AssistantServiceRegistration:
        registration, task_args = await assistant_service_registration_controller.update_assistant_service_url(
            assistant_service_principal=principal,
            assistant_service_id=assistant_service_id,
            update_assistant_service_url=update_assistant_service,
        )
        if task_args:
            background_tasks.add_task(*task_args)
        return registration

    @app.patch("/assistant-service-registrations/{assistant_service_id:path}")
    async def update_assistant_service_registration(
        principal: auth.DependsUserPrincipal,
        assistant_service_id: str,
        update_assistant_service: UpdateAssistantServiceRegistration,
    ) -> AssistantServiceRegistration:
        return await assistant_service_registration_controller.update_registration(
            user_principal=principal,
            assistant_service_id=assistant_service_id,
            update_assistant_service=update_assistant_service,
        )

    @app.post("/assistant-service-registrations/{assistant_service_id:path}/api-key")
    async def reset_assistant_service_registration_api_key(
        user_principal: auth.DependsUserPrincipal,
        assistant_service_id: str,
    ) -> AssistantServiceRegistration:
        return await assistant_service_registration_controller.reset_api_key(
            user_principal=user_principal, assistant_service_id=assistant_service_id
        )

    @app.get("/assistant-service-registrations")
    async def list_assistant_service_registrations(
        user_principal: auth.DependsUserPrincipal,
        user_ids: Annotated[list[str], Query(alias="user_id")] = [],
        assistant_service_online: Annotated[bool | None, Query(alias="assistant_service_online")] = None,
    ) -> AssistantServiceRegistrationList:
        user_id_set = set([user_principal.user_id if user_id == "me" else user_id for user_id in user_ids])
        return await assistant_service_registration_controller.get_registrations(
            user_ids=user_id_set, assistant_service_online=assistant_service_online
        )

    @app.get("/assistant-service-registrations/{assistant_service_id:path}")
    async def get_assistant_service_registration(
        user_principal: auth.DependsUserPrincipal, assistant_service_id: str
    ) -> AssistantServiceRegistration:
        return await assistant_service_registration_controller.get_registration(
            assistant_service_id=assistant_service_id
        )

    @app.delete(
        "/assistant-service-registrations/{assistant_service_id:path}",
        status_code=status.HTTP_204_NO_CONTENT,
    )
    async def delete_assistant_service(
        user_principal: auth.DependsUserPrincipal,
        assistant_service_id: str,
    ) -> None:
        return await assistant_service_registration_controller.delete_registration(
            user_principal=user_principal, assistant_service_id=assistant_service_id
        )

    @app.get("/assistant-services/{assistant_service_id:path}/info")
    @app.get("/assistant-services/{assistant_service_id:path}")
    async def get_assistant_service_info(
        user_principal: auth.DependsUserPrincipal, assistant_service_id: str
    ) -> ServiceInfoModel:
        return await assistant_service_registration_controller.get_service_info(
            assistant_service_id=assistant_service_id
        )

    @app.get("/assistant-services")
    async def list_assistant_service_infos(
        principal: auth.DependsPrincipal,
        user_ids: Annotated[list[str], Query(alias="user_id")] = [],
    ) -> AssistantServiceInfoList:
        match principal:
            case auth.UserPrincipal():
                user_id_set = set([principal.user_id if user_id == "me" else user_id for user_id in user_ids])

            case auth.AssistantServicePrincipal():
                user_id_set = set(user_ids)

        return await assistant_service_registration_controller.get_service_infos(user_ids=user_id_set)

    @app.get("/assistants")
    async def list_assistants(
        user_principal: auth.DependsUserPrincipal, conversation_id: uuid.UUID | None = None
    ) -> AssistantList:
        return await assistant_controller.get_assistants(user_principal=user_principal, conversation_id=conversation_id)

    @app.get("/assistants/{assistant_id}")
    async def get_assistant(user_principal: auth.DependsUserPrincipal, assistant_id: uuid.UUID) -> Assistant:
        return await assistant_controller.get_assistant(user_principal=user_principal, assistant_id=assistant_id)

    @app.post("/assistants", status_code=status.HTTP_201_CREATED)
    async def create_assistant(
        new_assistant: NewAssistant,
        user_principal: auth.DependsUserPrincipal,
    ) -> Assistant:
        return await assistant_controller.create_assistant(user_principal=user_principal, new_assistant=new_assistant)

    @app.patch("/assistants/{assistant_id}")
    async def update_assistant(
        assistant_id: uuid.UUID,
        update_assistant: UpdateAssistant,
        user_principal: auth.DependsUserPrincipal,
    ) -> Assistant:
        return await assistant_controller.update_assistant(
            user_principal=user_principal, assistant_id=assistant_id, update_assistant=update_assistant
        )

    @app.get(
        "/assistants/{assistant_id}/export", description="Export an assistant's configuration and conversation data."
    )
    async def export_assistant(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
    ) -> FileResponse:
        result = await assistant_controller.export_assistant(user_principal=user_principal, assistant_id=assistant_id)

        return FileResponse(
            path=result.file_path,
            media_type=result.content_type,
            filename=result.filename,
            background=starlette.background.BackgroundTask(result.cleanup),
        )

    @app.get(
        "/conversations/export",
        description="Export  one or more conversations and the assistants that participate in them.",
    )
    async def export_conversations(
        user_principal: auth.DependsUserPrincipal,
        conversation_ids: list[uuid.UUID] = Query(alias="id"),
    ) -> FileResponse:
        result = await assistant_controller.export_conversations(
            user_principal=user_principal, conversation_ids=set(conversation_ids)
        )

        return FileResponse(
            path=result.file_path,
            media_type=result.content_type,
            filename=result.filename,
            background=starlette.background.BackgroundTask(result.cleanup),
        )

    @app.post("/conversations/import")
    async def import_conversations(
        from_export: Annotated[UploadFile, File(alias="from_export")],
        user_principal: auth.DependsUserPrincipal,
    ) -> ConversationImportResult:
        return await assistant_controller.import_conversations(
            user_principal=user_principal, from_export=from_export.file
        )

    @app.get("/assistants/{assistant_id}/config")
    async def get_assistant_config(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
    ) -> ConfigResponseModel:
        return await assistant_controller.get_assistant_config(user_principal=user_principal, assistant_id=assistant_id)

    @app.put("/assistants/{assistant_id}/config")
    async def update_assistant_config(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
        updated_config: ConfigPutRequestModel,
    ) -> ConfigResponseModel:
        return await assistant_controller.update_assistant_config(
            user_principal=user_principal,
            assistant_id=assistant_id,
            updated_config=updated_config,
        )

    @app.get("/assistants/{assistant_id}/conversations/{conversation_id}/states")
    async def get_assistant_conversation_state_descriptions(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
        conversation_id: uuid.UUID,
    ) -> StateDescriptionListResponseModel:
        return await assistant_controller.get_assistant_conversation_state_descriptions(
            user_principal=user_principal,
            assistant_id=assistant_id,
            conversation_id=conversation_id,
        )

    @app.get("/assistants/{assistant_id}/conversations/{conversation_id}/states/{state_id}")
    async def get_assistant_conversation_state(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
        conversation_id: uuid.UUID,
        state_id: str,
    ) -> StateResponseModel:
        return await assistant_controller.get_assistant_conversation_state(
            user_principal=user_principal,
            assistant_id=assistant_id,
            conversation_id=conversation_id,
            state_id=state_id,
        )

    @app.put("/assistants/{assistant_id}/conversations/{conversation_id}/states/{state_id}")
    async def update_assistant_conversation_state(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
        conversation_id: uuid.UUID,
        state_id: str,
        updated_state: StatePutRequestModel,
    ) -> StateResponseModel:
        return await assistant_controller.update_assistant_conversation_state(
            user_principal=user_principal,
            assistant_id=assistant_id,
            conversation_id=conversation_id,
            state_id=state_id,
            updated_state=updated_state,
        )

    @app.post("/assistants/{assistant_id}/states/events", status_code=status.HTTP_204_NO_CONTENT)
    async def post_assistant_state_event(
        assistant_id: uuid.UUID,
        state_event: AssistantStateEvent,
        assistant_principal: auth.DependsAssistantPrincipal,
        conversation_id: Annotated[uuid.UUID | None, Query()] = None,
    ) -> None:
        await assistant_controller.post_assistant_state_event(
            assistant_id=assistant_id,
            state_event=state_event,
            assistant_principal=assistant_principal,
            conversation_ids=[conversation_id] if conversation_id else [],
        )

    @app.delete(
        "/assistants/{assistant_id}",
        status_code=status.HTTP_204_NO_CONTENT,
    )
    async def delete_assistant(
        user_principal: auth.DependsUserPrincipal,
        assistant_id: uuid.UUID,
    ) -> None:
        await assistant_controller.delete_assistant(
            user_principal=user_principal,
            assistant_id=assistant_id,
        )

    @app.get("/assistants/{assistant_id}/conversations")
    async def get_assistant_conversations(
        assistant_id: uuid.UUID,
        user_principal: auth.DependsUserPrincipal,
        latest_message_types: Annotated[list[MessageType], Query(alias="latest_message_type")] = [MessageType.chat],
    ) -> ConversationList:
        return await conversation_controller.get_assistant_conversations(
            user_principal=user_principal,
            assistant_id=assistant_id,
            latest_message_types=set(latest_message_types),
        )

    @app.get("/conversations/{conversation_id}/events")
    async def conversation_server_sent_events(
        conversation_id: uuid.UUID, request: Request, principal: auth.DependsActorPrincipal
    ) -> EventSourceResponse:
        # ensure the principal has access to the conversation
        await conversation_controller.get_conversation(
            conversation_id=conversation_id,
            principal=principal,
            latest_message_types=set(),
        )

        principal_id_type = "assistant_id" if isinstance(principal, auth.AssistantPrincipal) else "user_id"
        principal_id = principal.assistant_id if isinstance(principal, auth.AssistantPrincipal) else principal.user_id

        logger.debug(
            "client connected to sse; %s: %s, conversation_id: %s",
            principal_id_type,
            principal_id,
            conversation_id,
        )
        event_queue = asyncio.Queue[ConversationEvent]()

        async with conversation_sse_queues_lock:
            queues = conversation_sse_queues[conversation_id]
            queues.add(event_queue)

        async def event_generator() -> AsyncIterator[ServerSentEvent]:
            try:
                while True:
                    if stop_signal.is_set():
                        logger.debug("sse stopping due to signal; conversation_id: %s", conversation_id)
                        break

                    try:
                        if await request.is_disconnected():
                            logger.debug("client disconnected from sse; conversation_id: %s", conversation_id)
                            break
                    except Exception:
                        logger.exception(
                            "error checking if client disconnected from sse; conversation_id: %s", conversation_id
                        )
                        break

                    try:
                        try:
                            async with asyncio.timeout(1):
                                conversation_event = await event_queue.get()
                        except asyncio.TimeoutError:
                            continue

                        server_sent_event = ServerSentEvent(
                            id=conversation_event.id,
                            event=conversation_event.event.value,
                            data=conversation_event.model_dump_json(include={"timestamp", "data"}),
                            retry=1000,
                        )
                        yield server_sent_event
                        logger.debug(
                            "sent event to sse client; %s: %s, conversation_id: %s, event: %s, id: %s, time since"
                            " event: %s",
                            principal_id_type,
                            principal_id,
                            conversation_id,
                            conversation_event.event,
                            conversation_event.id,
                            datetime.datetime.now(datetime.UTC) - conversation_event.timestamp,
                        )

                    except Exception:
                        logger.exception("error sending event to sse client; conversation_id: %s", conversation_id)

            finally:
                queues.discard(event_queue)
                if len(queues) == 0:
                    async with conversation_sse_queues_lock:
                        if len(queues) == 0:
                            conversation_sse_queues.pop(conversation_id, None)

        return EventSourceResponse(event_generator(), sep="\n")

    @app.get("/events")
    async def user_server_sent_events(
        request: Request, user_principal: auth.DependsUserPrincipal
    ) -> EventSourceResponse:
        logger.debug("client connected to user events sse; user_id: %s", user_principal.user_id)

        event_queue = asyncio.Queue[ConversationEvent]()

        async with user_sse_queues_lock:
            queues = user_sse_queues[user_principal.user_id]
            queues.add(event_queue)

        async def event_generator() -> AsyncIterator[ServerSentEvent]:
            try:
                while True:
                    if stop_signal.is_set():
                        logger.debug("sse stopping due to signal; user_id: %s", user_principal.user_id)
                        break

                    try:
                        if await request.is_disconnected():
                            logger.debug("client disconnected from sse; user_id: %s", user_principal.user_id)
                            break
                    except Exception:
                        logger.exception(
                            "error checking if client disconnected from sse; user_id: %s", user_principal.user_id
                        )
                        break

                    try:
                        try:
                            async with asyncio.timeout(1):
                                conversation_event = await event_queue.get()
                        except asyncio.TimeoutError:
                            continue

                        server_sent_event = ServerSentEvent(
                            id=conversation_event.id,
                            event=conversation_event.event.value,
                            data=json.dumps({
                                **conversation_event.model_dump(mode="json", include={"timestamp", "data"}),
                                "conversation_id": str(conversation_event.conversation_id),
                            }),
                            retry=1000,
                        )
                        yield server_sent_event
                        logger.debug(
                            "sent event to user sse client; user_id: %s, event: %s",
                            user_principal.user_id,
                            server_sent_event.event,
                        )

                    except Exception:
                        logger.exception("error sending event to sse client; user_id: %s", user_principal.user_id)

            finally:
                queues.discard(event_queue)
                if len(queues) == 0:
                    async with conversation_sse_queues_lock:
                        if len(queues) == 0:
                            user_sse_queues.pop(user_principal.user_id, None)

        return EventSourceResponse(event_generator(), sep="\n")

    @app.post("/conversations")
    async def create_conversation(
        new_conversation: NewConversation,
        user_principal: auth.DependsUserPrincipal,
    ) -> Conversation:
        return await conversation_controller.create_conversation(
            user_principal=user_principal,
            new_conversation=new_conversation,
        )

    @app.post("/conversations/{owner_id}")
    async def create_conversation_with_owner(
        assistant_principal: auth.DependsAssistantPrincipal,
        new_conversation: NewConversation,
        owner_id: str,
    ) -> Conversation:
        return await conversation_controller.create_conversation_with_owner(
            new_conversation=new_conversation,
            principal=assistant_principal,
            owner_id=owner_id,
        )

    @app.post("/conversations/{conversation_id}")
    async def duplicate_conversation(
        conversation_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
        new_conversation: NewConversation,
    ) -> ConversationImportResult:
        return await assistant_controller.duplicate_conversation(
            principal=principal, conversation_id=conversation_id, new_conversation=new_conversation
        )

    @app.get("/conversations")
    async def list_conversations(
        principal: auth.DependsActorPrincipal,
        include_inactive: bool = False,
        latest_message_types: Annotated[list[MessageType], Query(alias="latest_message_type")] = [MessageType.chat],
    ) -> ConversationList:
        return await conversation_controller.get_conversations(
            principal=principal,
            include_all_owned=include_inactive,
            latest_message_types=set(latest_message_types),
        )

    @app.get("/conversations/{conversation_id}")
    async def get_conversation(
        conversation_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
        latest_message_types: Annotated[list[MessageType], Query(alias="latest_message_type")] = [MessageType.chat],
    ) -> Conversation:
        return await conversation_controller.get_conversation(
            principal=principal,
            conversation_id=conversation_id,
            latest_message_types=set(latest_message_types),
        )

    @app.patch("/conversations/{conversation_id}")
    async def update_conversation(
        conversation_id: uuid.UUID,
        update_conversation: UpdateConversation,
        user_principal: auth.DependsActorPrincipal,
    ) -> Conversation:
        return await conversation_controller.update_conversation(
            user_principal=user_principal,
            conversation_id=conversation_id,
            update_conversation=update_conversation,
        )

    @app.get("/conversations/{conversation_id}/participants")
    async def list_conversation_participants(
        conversation_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
        include_inactive: bool = False,
    ) -> ConversationParticipantList:
        return await conversation_controller.get_conversation_participants(
            principal=principal,
            conversation_id=conversation_id,
            include_inactive=include_inactive,
        )

    def _translate_participant_id_me(principal: auth.ActorPrincipal, participant_id: str) -> str:
        if participant_id != "me":
            return participant_id

        match principal:
            case auth.UserPrincipal():
                return principal.user_id
            case auth.AssistantPrincipal():
                return str(principal.assistant_id)

    @app.get("/conversations/{conversation_id}/participants/{participant_id}")
    async def get_conversation_participant(
        conversation_id: uuid.UUID,
        participant_id: str,
        principal: auth.DependsActorPrincipal,
    ) -> ConversationParticipant:
        participant_id = _translate_participant_id_me(principal, participant_id)

        return await conversation_controller.get_conversation_participant(
            principal=principal,
            conversation_id=conversation_id,
            participant_id=participant_id,
        )

    @app.patch("/conversations/{conversation_id}/participants/{participant_id}")
    @app.put("/conversations/{conversation_id}/participants/{participant_id}")
    async def add_or_update_conversation_participant(
        conversation_id: uuid.UUID,
        participant_id: str,
        update_participant: UpdateParticipant,
        principal: auth.DependsActorPrincipal,
    ) -> ConversationParticipant:
        participant_id = _translate_participant_id_me(principal, participant_id)

        return await conversation_controller.add_or_update_conversation_participant(
            participant_id=participant_id,
            update_participant=update_participant,
            conversation_id=conversation_id,
            principal=principal,
        )

    @app.get("/conversations/{conversation_id}/messages")
    async def list_conversation_messages(
        conversation_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
        participant_roles: Annotated[list[ParticipantRole] | None, Query(alias="participant_role")] = None,
        participant_ids: Annotated[list[str] | None, Query(alias="participant_id")] = None,
        message_types: Annotated[list[MessageType] | None, Query(alias="message_type")] = None,
        before: Annotated[uuid.UUID | None, Query()] = None,
        after: Annotated[uuid.UUID | None, Query()] = None,
        limit: Annotated[int, Query(lte=500)] = 100,
    ) -> ConversationMessageList:
        return await conversation_controller.get_messages(
            conversation_id=conversation_id,
            principal=principal,
            participant_ids=participant_ids,
            participant_roles=participant_roles,
            message_types=message_types,
            before=before,
            after=after,
            limit=limit,
        )

    @app.post("/conversations/{conversation_id}/messages")
    async def create_conversation_message(
        conversation_id: uuid.UUID,
        new_message: NewConversationMessage,
        principal: auth.DependsActorPrincipal,
        background_tasks: BackgroundTasks,
    ) -> ConversationMessage:
        response, task_args = await conversation_controller.create_conversation_message(
            conversation_id=conversation_id,
            new_message=new_message,
            principal=principal,
        )
        if task_args:
            background_tasks.add_task(*task_args)
        return response

    @app.get(
        "/conversations/{conversation_id}/messages/{message_id}",
    )
    async def get_message(
        conversation_id: uuid.UUID,
        message_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
    ) -> ConversationMessage:
        return await conversation_controller.get_message(
            conversation_id=conversation_id,
            message_id=message_id,
            principal=principal,
        )

    @app.get(
        "/conversations/{conversation_id}/messages/{message_id}/debug_data",
    )
    async def get_message_debug_data(
        conversation_id: uuid.UUID,
        message_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
    ) -> ConversationMessageDebug:
        return await conversation_controller.get_message_debug(
            conversation_id=conversation_id,
            message_id=message_id,
            principal=principal,
        )

    @app.delete(
        "/conversations/{conversation_id}/messages/{message_id}",
        status_code=status.HTTP_204_NO_CONTENT,
    )
    async def delete_message(
        conversation_id: uuid.UUID,
        message_id: uuid.UUID,
        user_principal: auth.DependsUserPrincipal,
    ) -> None:
        await conversation_controller.delete_message(
            conversation_id=conversation_id,
            message_id=message_id,
            user_principal=user_principal,
        )

    @app.put("/conversations/{conversation_id}/files")
    async def upload_files(
        conversation_id: uuid.UUID,
        upload_files: Annotated[list[UploadFile], File(alias="files")],
        principal: auth.DependsActorPrincipal,
        file_metadata_raw: str = Form(alias="metadata", default="{}"),
    ) -> FileList:
        try:
            file_metadata: dict[str, dict[str, Any]] = json.loads(file_metadata_raw)
        except json.JSONDecodeError as e:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e

        if not isinstance(file_metadata, dict):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="metadata must be a JSON object as a string"
            )

        return await file_controller.upload_files(
            conversation_id=conversation_id,
            upload_files=upload_files,
            principal=principal,
            file_metadata=file_metadata,
        )

    @app.get("/conversations/{conversation_id}/files")
    async def list_files(
        conversation_id: uuid.UUID,
        principal: auth.DependsActorPrincipal,
        prefix: str | None = None,
    ) -> FileList:
        return await file_controller.list_files(conversation_id=conversation_id, principal=principal, prefix=prefix)

    @app.get("/conversations/{conversation_id}/files/{filename:path}/versions")
    async def file_versions(
        conversation_id: uuid.UUID,
        filename: str,
        principal: auth.DependsActorPrincipal,
        version: int | None = None,
    ) -> FileVersions:
        return await file_controller.file_versions(
            conversation_id=conversation_id, filename=filename, principal=principal, version=version
        )

    @app.get("/conversations/{conversation_id}/files/{filename:path}")
    async def download_file(
        conversation_id: uuid.UUID,
        filename: str,
        principal: auth.DependsActorPrincipal,
        version: int | None = None,
    ) -> StreamingResponse:
        result = await file_controller.download_file(
            conversation_id=conversation_id,
            filename=filename,
            principal=principal,
            version=version,
        )

        return StreamingResponse(
            result.stream,
            media_type=result.content_type,
            headers={"Content-Disposition": f'attachment; filename="{urllib.parse.quote(result.filename)}"'},
        )

    @app.patch("/conversations/{conversation_id}/files/{filename:path}")
    async def update_file(
        conversation_id: uuid.UUID,
        filename: str,
        principal: auth.DependsActorPrincipal,
        update_file: UpdateFile,
    ) -> FileVersions:
        return await file_controller.update_file_metadata(
            conversation_id=conversation_id,
            filename=filename,
            principal=principal,
            metadata=update_file.metadata,
        )

    @app.delete("/conversations/{conversation_id}/files/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
    async def delete_file(
        conversation_id: uuid.UUID,
        filename: str,
        principal: auth.DependsActorPrincipal,
    ) -> None:
        await file_controller.delete_file(
            conversation_id=conversation_id,
            filename=filename,
            principal=principal,
        )

    @app.post("/conversation-shares")
    async def create_conversation_share(
        user_principal: auth.DependsUserPrincipal,
        new_conversation_share: NewConversationShare,
    ) -> ConversationShare:
        return await conversation_share_controller.create_conversation_share(
            user_principal=user_principal,
            new_conversation_share=new_conversation_share,
        )

    # create_conversation_share_with_owner
    @app.post("/conversation-shares/{owner_id}")
    async def create_conversation_share_with_owner(
        new_conversation_share: NewConversationShare,
        owner_id: str,
    ) -> ConversationShare:
        return await conversation_share_controller.create_conversation_share_with_owner(
            new_conversation_share=new_conversation_share,
            owner_id=owner_id,
        )

    @app.get("/conversation-shares")
    async def list_conversation_shares(
        user_principal: auth.DependsUserPrincipal,
        include_unredeemable: bool = False,
        conversation_id: uuid.UUID | None = None,
    ) -> ConversationShareList:
        return await conversation_share_controller.get_conversation_shares(
            user_principal=user_principal,
            conversation_id=conversation_id,
            include_unredeemable=include_unredeemable,
        )

    @app.get("/conversation-shares/{conversation_share_id}")
    async def get_conversation_share(
        user_principal: auth.DependsUserPrincipal,
        conversation_share_id: uuid.UUID,
    ) -> ConversationShare:
        return await conversation_share_controller.get_conversation_share(
            user_principal=user_principal,
            conversation_share_id=conversation_share_id,
        )

    @app.delete("/conversation-shares/{conversation_share_id}", status_code=status.HTTP_204_NO_CONTENT)
    async def delete_conversation_share(
        user_principal: auth.DependsUserPrincipal,
        conversation_share_id: uuid.UUID,
    ) -> None:
        await conversation_share_controller.delete_conversation_share(
            user_principal=user_principal,
            conversation_share_id=conversation_share_id,
        )

    @app.post("/conversation-shares/{conversation_share_id}/redemptions")
    async def redeem_conversation_share(
        user_principal: auth.DependsUserPrincipal,
        conversation_share_id: uuid.UUID,
    ) -> ConversationShareRedemption:
        return await conversation_share_controller.redeem_conversation_share(
            user_principal=user_principal,
            conversation_share_id=conversation_share_id,
        )

    @app.get("/conversation-shares/{conversation_share_id}/redemptions")
    async def list_conversation_share_redemptions(
        user_principal: auth.DependsUserPrincipal,
        conversation_share_id: uuid.UUID,
    ) -> ConversationShareRedemptionList:
        return await conversation_share_controller.get_redemptions_for_share(
            user_principal=user_principal,
            conversation_share_id=conversation_share_id,
        )

    @app.get("/conversation-share-redemptions")
    async def list_conversation_share_redemptions_for_user(
        user_principal: auth.DependsUserPrincipal,
    ) -> ConversationShareRedemptionList:
        return await conversation_share_controller.get_redemptions_for_user(
            user_principal=user_principal,
        )

    @app.get("/azure-speech/token")
    async def get_azure_speech_token() -> dict[str, str]:
        return azure_speech.get_token()

```

--------------------------------------------------------------------------------
/workbench-service/semantic_workbench_service/controller/conversation.py:
--------------------------------------------------------------------------------

```python
import datetime
import logging
import uuid
from typing import (
    Annotated,
    AsyncContextManager,
    Awaitable,
    Callable,
    Iterable,
    Literal,
    Sequence,
)

import deepmerge
import openai_client
from openai.types.chat import ChatCompletionMessageParam
from pydantic import BaseModel, Field, HttpUrl
from semantic_workbench_api_model.assistant_service_client import AssistantError
from semantic_workbench_api_model.workbench_model import (
    Conversation,
    ConversationEvent,
    ConversationEventType,
    ConversationList,
    ConversationMessage,
    ConversationMessageDebug,
    ConversationMessageList,
    ConversationParticipant,
    ConversationParticipantList,
    MessageType,
    NewConversation,
    NewConversationMessage,
    ParticipantRole,
    UpdateConversation,
    UpdateParticipant,
)
from sqlalchemy.orm.attributes import flag_modified
from sqlmodel import and_, col, or_, select
from sqlmodel.ext.asyncio.session import AsyncSession

from .. import auth, db, query, settings
from ..event import ConversationEventQueueItem
from . import assistant, convert, exceptions
from . import participant as participant_
from . import user as user_

logger = logging.getLogger(__name__)


class ConversationTitleResponse(BaseModel):
    """Model for responses from LLM for automatic conversation re-titling."""

    title: Annotated[
        str,
        Field(
            description="The updated title of the conversation. If the subject matter of the conversation has changed significantly from the current title, suggest a short, but descriptive title for the conversation. Ideally 4 words or less in length. Leave it blank to keep the current title.",
        ),
    ]


META_DATA_KEY_USER_SET_TITLE = "__user_set_title"
META_DATA_KEY_AUTO_TITLE_COUNT = "__auto_title_count"
AUTO_TITLE_COUNT_LIMIT = 3
"""
The maximum number of times a conversation can be automatically retitled.
"""


class ConversationController:
    def __init__(
        self,
        get_session: Callable[[], AsyncContextManager[AsyncSession]],
        notify_event: Callable[[ConversationEventQueueItem], Awaitable],
        assistant_controller: assistant.AssistantController,
    ) -> None:
        self._get_session = get_session
        self._notify_event = notify_event
        self._assistant_controller = assistant_controller

    async def create_conversation(
        self,
        new_conversation: NewConversation,
        user_principal: auth.UserPrincipal,
    ) -> Conversation:
        async with self._get_session() as session:
            await user_.add_or_update_user_from(session=session, user_principal=user_principal)

            conversation = db.Conversation(
                owner_id=user_principal.user_id,
                title=new_conversation.title or NewConversation().title,
                meta_data=new_conversation.metadata,
                imported_from_conversation_id=None,
            )

            if new_conversation.title and new_conversation.title != NewConversation().title:
                conversation.meta_data = {
                    **conversation.meta_data,
                    META_DATA_KEY_USER_SET_TITLE: True,
                }

            session.add(conversation)

            session.add(
                db.UserParticipant(
                    conversation_id=conversation.conversation_id,
                    user_id=user_principal.user_id,
                    conversation_permission="read_write",
                )
            )

            await session.commit()
            await session.refresh(conversation)

        return await self.get_conversation(
            conversation_id=conversation.conversation_id,
            principal=user_principal,
            latest_message_types=set(),
        )

    async def create_conversation_with_owner(
        self,
        new_conversation: NewConversation,
        owner_id: str,
        principal: auth.AssistantPrincipal,
    ) -> Conversation:
        async with self._get_session() as session:
            conversation = db.Conversation(
                owner_id=owner_id,
                title=new_conversation.title or NewConversation().title,
                meta_data=new_conversation.metadata,
                imported_from_conversation_id=None,
            )

            if new_conversation.title and new_conversation.title != NewConversation().title:
                conversation.meta_data = {
                    **conversation.meta_data,
                    META_DATA_KEY_USER_SET_TITLE: True,
                }

            session.add(conversation)

            # session.add(
            #     db.UserParticipant(
            #         conversation_id=conversation.conversation_id,
            #         user_id=owner_id,
            #         conversation_permission="read_write",
            #     )
            # )

            session.add(
                db.AssistantParticipant(
                    conversation_id=conversation.conversation_id,
                    assistant_id=principal.assistant_id,
                )
            )

            await session.commit()
            await session.refresh(conversation)

            assistant = (
                await session.exec(select(db.Assistant).where(db.Assistant.assistant_id == principal.assistant_id))
            ).one_or_none()
            if assistant is None:
                raise exceptions.NotFoundError()

        await self._assistant_controller.connect_assistant_to_conversation(
            assistant=assistant,
            conversation=conversation,
            from_export=None,
        )

        return await self.get_conversation(
            conversation_id=conversation.conversation_id,
            principal=principal,
            latest_message_types=set(),
        )

    async def _projections_with_participants(
        self,
        session: AsyncSession,
        conversation_projections: Sequence[tuple[db.Conversation, db.ConversationMessage | None, bool, str]],
    ) -> Iterable[
        tuple[
            db.Conversation,
            Iterable[db.UserParticipant],
            Iterable[db.AssistantParticipant],
            dict[uuid.UUID, db.Assistant],
            db.ConversationMessage | None,
            bool,
            str,
        ]
    ]:
        user_participants = (
            await session.exec(
                select(db.UserParticipant).where(
                    col(db.UserParticipant.conversation_id).in_([
                        c[0].conversation_id for c in conversation_projections
                    ])
                )
            )
        ).all()

        assistant_participants = (
            await session.exec(
                select(db.AssistantParticipant).where(
                    col(db.AssistantParticipant.conversation_id).in_([
                        c[0].conversation_id for c in conversation_projections
                    ])
                )
            )
        ).all()

        assistants = (
            await session.exec(
                select(db.Assistant).where(
                    col(db.Assistant.assistant_id).in_([p.assistant_id for p in assistant_participants])
                )
            )
        ).all()
        assistants_map = {assistant.assistant_id: assistant for assistant in assistants}

        def merge() -> Iterable[
            tuple[
                db.Conversation,
                Iterable[db.UserParticipant],
                Iterable[db.AssistantParticipant],
                dict[uuid.UUID, db.Assistant],
                db.ConversationMessage | None,
                bool,
                str,
            ]
        ]:
            for (
                conversation,
                latest_message,
                latest_message_has_debug,
                permission,
            ) in conversation_projections:
                conversation_id = conversation.conversation_id
                conversation_user_participants = (
                    up for up in user_participants if up.conversation_id == conversation_id
                )
                conversation_assistant_participants = (
                    ap for ap in assistant_participants if ap.conversation_id == conversation_id
                )
                yield (
                    conversation,
                    conversation_user_participants,
                    conversation_assistant_participants,
                    assistants_map,
                    latest_message,
                    latest_message_has_debug,
                    permission,
                )

        return merge()

    async def get_conversations(
        self,
        principal: auth.ActorPrincipal,
        latest_message_types: set[MessageType],
        include_all_owned: bool = False,
    ) -> ConversationList:
        async with self._get_session() as session:
            include_all_owned = include_all_owned and isinstance(principal, auth.UserPrincipal)

            conversation_projections = (
                await session.exec(
                    query.select_conversation_projections_for(
                        principal=principal,
                        include_all_owned=include_all_owned,
                        include_observer=True,
                        latest_message_types=latest_message_types,
                    ).order_by(col(db.Conversation.created_datetime).desc())
                )
            ).all()

            projections_with_participants = await self._projections_with_participants(
                session=session, conversation_projections=conversation_projections
            )

            return convert.conversation_list_from_db(models=projections_with_participants)

    async def get_assistant_conversations(
        self,
        user_principal: auth.UserPrincipal,
        assistant_id: uuid.UUID,
        latest_message_types: set[MessageType],
    ) -> ConversationList:
        async with self._get_session() as session:
            assistant = (
                await session.exec(
                    query.select_assistants_for(user_principal=user_principal).where(
                        db.Assistant.assistant_id == assistant_id
                    )
                )
            ).one_or_none()
            if assistant is None:
                raise exceptions.NotFoundError()

            conversation_projections = (
                await session.exec(
                    query.select_conversation_projections_for(
                        principal=auth.AssistantPrincipal(
                            assistant_service_id=assistant.assistant_service_id,
                            assistant_id=assistant_id,
                        ),
                        latest_message_types=latest_message_types,
                    )
                )
            ).all()

            projections_with_participants = await self._projections_with_participants(
                session=session, conversation_projections=conversation_projections
            )

            return convert.conversation_list_from_db(models=projections_with_participants)

    async def get_conversation(
        self,
        conversation_id: uuid.UUID,
        principal: auth.ActorPrincipal,
        latest_message_types: set[MessageType],
    ) -> Conversation:
        async with self._get_session() as session:
            include_all_owned = isinstance(principal, auth.UserPrincipal)

            conversation_projection = (
                await session.exec(
                    query.select_conversation_projections_for(
                        principal=principal,
                        include_all_owned=include_all_owned,
                        include_observer=True,
                        latest_message_types=latest_message_types,
                    ).where(db.Conversation.conversation_id == conversation_id)
                )
            ).one_or_none()
            if conversation_projection is None:
                raise exceptions.NotFoundError()

            projections_with_participants = await self._projections_with_participants(
                session=session,
                conversation_projections=[conversation_projection],
            )

            (
                conversation,
                user_participants,
                assistant_participants,
                assistants,
                latest_message,
                latest_message_has_debug,
                permission,
            ) = next(iter(projections_with_participants))

            return convert.conversation_from_db(
                model=conversation,
                latest_message=latest_message,
                latest_message_has_debug=latest_message_has_debug,
                permission=permission,
                user_participants=user_participants,
                assistant_participants=assistant_participants,
                assistants=assistants,
            )

    async def update_conversation(
        self,
        conversation_id: uuid.UUID,
        update_conversation: UpdateConversation,
        user_principal: auth.ActorPrincipal,
    ) -> Conversation:
        async with self._get_session() as session:
            conversation = (
                await session.exec(
                    query.select_conversations_for(
                        principal=user_principal,
                        include_all_owned=True,
                    )
                    .where(
                        db.Conversation.conversation_id == conversation_id,
                    )
                    .with_for_update()
                )
            ).one_or_none()
            if conversation is None:
                raise exceptions.NotFoundError()

            for key, value in update_conversation.model_dump(exclude_unset=True).items():
                match key:
                    case "metadata":
                        system_entries = {k: v for k, v in conversation.meta_data.items() if k.startswith("__")}
                        conversation.meta_data = {
                            **conversation.meta_data,
                            **value,
                            **system_entries,
                        }
                    case "title":
                        if value == conversation.title:
                            continue
                        conversation.title = value
                        conversation.meta_data = {
                            **conversation.meta_data,
                            META_DATA_KEY_USER_SET_TITLE: True,
                        }
                    case _:
                        setattr(conversation, key, value)

            session.add(conversation)
            await session.commit()
            await session.refresh(conversation)

        conversation_model = await self.get_conversation(
            conversation_id=conversation.conversation_id,
            principal=user_principal,
            latest_message_types=set(),
        )

        await self._notify_event(
            ConversationEventQueueItem(
                event=ConversationEvent(
                    conversation_id=conversation.conversation_id,
                    event=ConversationEventType.conversation_updated,
                    data={
                        "conversation": conversation_model.model_dump(),
                    },
                )
            )
        )

        return conversation_model

    async def get_conversation_participants(
        self,
        conversation_id: uuid.UUID,
        principal: auth.ActorPrincipal,
        include_inactive: bool = False,
    ) -> ConversationParticipantList:
        async with self._get_session() as session:
            conversation = (
                await session.exec(
                    query.select_conversations_for(
                        principal=principal,
                        include_all_owned=True,
                        include_observer=True,
                    ).where(db.Conversation.conversation_id == conversation_id)
                )
            ).one_or_none()
            if conversation is None:
                raise exceptions.NotFoundError()

            return await participant_.get_conversation_participants(
                session=session,
                conversation_id=conversation.conversation_id,
                include_inactive=include_inactive,
            )

    async def get_conversation_participant(
        self,
        conversation_id: uuid.UUID,
        participant_id: str,
        principal: auth.ActorPrincipal,
    ) -> ConversationParticipant:
        async with self._get_session() as session:
            conversation = (
                await session.exec(
                    query.select_conversations_for(
                        principal=principal,
                        include_all_owned=True,
                        include_observer=True,
                    ).where(db.Conversation.conversation_id == conversation_id)
                )
            ).one_or_none()
            if conversation is None:
                raise exceptions.NotFoundError()

            possible_user_participant = (
                await session.exec(
                    select(db.UserParticipant)
                    .where(db.UserParticipant.conversation_id == conversation.conversation_id)
                    .where(db.UserParticipant.user_id == participant_id)
                )
            ).one_or_none()
            if possible_user_participant is not None:
                return convert.conversation_participant_from_db_user(possible_user_participant)

            possible_assistant_participant = (
                await session.exec(
                    select(db.AssistantParticipant)
                    .where(db.AssistantParticipant.conversation_id == conversation.conversation_id)
                    .where(db.AssistantParticipant.assistant_id == participant_id)
                )
            ).one_or_none()
            if possible_assistant_participant is not None:
                assistant = (
                    await session.exec(
                        select(db.Assistant).where(
                            db.Assistant.assistant_id == possible_assistant_participant.assistant_id
                        )
                    )
                ).one_or_none()
                return convert.conversation_participant_from_db_assistant(
                    possible_assistant_participant, assistant=assistant
                )

        raise exceptions.NotFoundError()

    async def add_or_update_conversation_participant(
        self,
        conversation_id: uuid.UUID,
        participant_id: str,
        update_participant: UpdateParticipant,
        principal: auth.ActorPrincipal,
    ) -> ConversationParticipant:
        if update_participant.active_participant is not None and not update_participant.active_participant:
            update_participant.status = None

        async with self._get_session() as session:

            async def update_user_participant(
                conversation: db.Conversation, user: db.User
            ) -> tuple[
                ConversationParticipant,
                Literal[ConversationEventType.participant_updated,] | None,
            ]:
                event_type: ConversationEventType | None = None
                participant = (
                    await session.exec(
                        select(db.UserParticipant)
                        .join(db.Conversation)
                        .where(db.UserParticipant.conversation_id == conversation.conversation_id)
                        .where(db.UserParticipant.user_id == user.user_id)
                        .where(
                            or_(
                                col(db.UserParticipant.active_participant).is_(True),
                                db.Conversation.owner_id == user.user_id,
                            )
                        )
                        .with_for_update()
                    )
                ).one_or_none()
                if participant is None:
                    raise exceptions.NotFoundError()

                if update_participant.active_participant is not None:
                    event_type = ConversationEventType.participant_updated
                    participant.active_participant = update_participant.active_participant

                if update_participant.status != participant.status:
                    event_type = event_type or ConversationEventType.participant_updated
                    participant.status = update_participant.status
                    participant.status_updated_datetime = datetime.datetime.now(datetime.UTC)

                if update_participant.metadata is not None:
                    event_type = event_type or ConversationEventType.participant_updated
                    participant.meta_data = deepmerge.always_merger.merge(
                        {**participant.meta_data}, update_participant.metadata
                    )

                if event_type is not None:
                    session.add(participant)
                    await session.commit()
                    await session.refresh(participant)

                return convert.conversation_participant_from_db_user(participant), event_type

            async def update_assistant_participant(
                conversation: db.Conversation,
                assistant: db.Assistant,
            ) -> tuple[
                ConversationParticipant,
                Literal[
                    ConversationEventType.participant_created,
                    ConversationEventType.participant_updated,
                ]
                | None,
            ]:
                new_participant = await db.insert_if_not_exists(
                    session,
                    db.AssistantParticipant(
                        conversation_id=conversation.conversation_id,
                        assistant_id=assistant.assistant_id,
                        status=update_participant.status,
                        name=assistant.name,
                        image=assistant.image,
                    ),
                )
                event_type = ConversationEventType.participant_created if new_participant else None
                participant = (
                    await session.exec(
                        select(db.AssistantParticipant)
                        .where(db.AssistantParticipant.conversation_id == conversation.conversation_id)
                        .where(db.AssistantParticipant.assistant_id == assistant.assistant_id)
                        .with_for_update()
                    )
                ).one()

                original_participant = participant.model_copy(deep=True)

                active_participant_changed = new_participant
                if update_participant.active_participant is not None:
                    event_type = event_type or ConversationEventType.participant_updated
                    active_participant_changed = active_participant_changed or (
                        participant.active_participant != update_participant.active_participant
                    )
                    participant.active_participant = update_participant.active_participant

                if participant.status != update_participant.status:
                    event_type = event_type or ConversationEventType.participant_updated
                    participant.status = update_participant.status
                    participant.status_updated_datetime = datetime.datetime.now(datetime.UTC)

                if update_participant.metadata is not None:
                    event_type = event_type or ConversationEventType.participant_updated
                    participant.meta_data = {
                        **deepmerge.always_merger.merge(participant.meta_data.copy(), update_participant.metadata)
                    }
                    flag_modified(participant, "meta_data")

                if event_type is not None:
                    session.add(participant)
                    await session.commit()
                    await session.refresh(participant)

                if active_participant_changed and participant.active_participant:
                    try:
                        await self._assistant_controller.connect_assistant_to_conversation(
                            assistant=assistant,
                            conversation=conversation,
                            from_export=None,
                        )
                    except AssistantError:
                        logger.error(
                            f"failed to connect assistant {assistant.assistant_id} to conversation"
                            f" {conversation.conversation_id}",
                            exc_info=True,
                        )
                        session.add(original_participant)
                        await session.commit()
                        raise

                if active_participant_changed and not participant.active_participant:
                    try:
                        await self._assistant_controller.disconnect_assistant_from_conversation(
                            assistant=assistant,
                            conversation_id=conversation.conversation_id,
                        )
                    except AssistantError:
                        logger.error(
                            f"failed to disconnect assistant {assistant.assistant_id} from conversation"
                            f" {conversation.conversation_id}",
                            exc_info=True,
                        )

                return (
                    convert.conversation_participant_from_db_assistant(
                        participant,
                        assistant=assistant,
                    ),
                    event_type,
                )

            match principal:
                case auth.UserPrincipal():
                    await user_.add_or_update_user_from(user_principal=principal, session=session)

                    # users can update participants in any conversation they own or are participants of
                    conversation = (
                        await session.exec(
                            query.select_conversations_for(
                                principal=principal,
                                include_all_owned=True,
                                include_observer=True,
                            ).where(db.Conversation.conversation_id == conversation_id)
                        )
                    ).one_or_none()
                    if conversation is None:
                        raise exceptions.NotFoundError()

                    assistant_id: uuid.UUID | None = None
                    try:
                        assistant_id = uuid.UUID(participant_id)
                        participant_role = "assistant"
                    except ValueError:
                        participant_role = "user"

                    match participant_role:
                        case "user":
                            # users can only update their own participant
                            if participant_id != principal.user_id:
                                raise exceptions.ForbiddenError()

                            user = (
                                await session.exec(select(db.User).where(db.User.user_id == participant_id))
                            ).one_or_none()
                            if user is None:
                                raise exceptions.NotFoundError()

                            participant, event_type = await update_user_participant(conversation, user)

                        case "assistant":
                            # users can add any of their assistants to conversation
                            assistant = (
                                await session.exec(
                                    query.select_assistants_for(user_principal=principal).where(
                                        db.Assistant.assistant_id == assistant_id
                                    )
                                )
                            ).one_or_none()
                            if assistant is None:
                                raise exceptions.NotFoundError()

                            (
                                participant,
                                event_type,
                            ) = await update_assistant_participant(conversation, assistant)

                case auth.AssistantServicePrincipal():
                    # assistants can update participants in conversations they are already participants of
                    conversation = (
                        await session.exec(
                            query.select_conversations_for(principal=principal).where(
                                db.Conversation.conversation_id == conversation_id
                            )
                        )
                    ).one_or_none()

                    if conversation is None:
                        raise exceptions.NotFoundError()

                    # assistants can only update their own status
                    if participant_id != str(principal.assistant_id):
                        raise exceptions.ForbiddenError()

                    assistant = (
                        await session.exec(
                            select(db.Assistant).where(db.Assistant.assistant_id == principal.assistant_id)
                        )
                    ).one_or_none()
                    if assistant is None:
                        raise exceptions.NotFoundError()

                    if assistant.assistant_service_id != principal.assistant_service_id:
                        raise exceptions.ForbiddenError()

                    participant, event_type = await update_assistant_participant(conversation, assistant)

            if event_type is not None:
                participants = await participant_.get_conversation_participants(
                    session=session,
                    conversation_id=conversation.conversation_id,
                    include_inactive=True,
                )

                await self._notify_event(
                    ConversationEventQueueItem(
                        event=participant_.participant_event(
                            event_type=event_type,
                            conversation_id=conversation.conversation_id,
                            participant=participant,
                            participants=participants,
                        ),
                    )
                )

            return participant

    async def create_conversation_message(
        self,
        principal: auth.ActorPrincipal,
        conversation_id: uuid.UUID,
        new_message: NewConversationMessage,
    ) -> tuple[ConversationMessage, Iterable]:
        async with self._get_session() as session:
            conversation = (
                await session.exec(
                    query.select_conversations_for(principal=principal).where(
                        db.Conversation.conversation_id == conversation_id
                    )
                )
            ).one_or_none()
            if conversation is None:
                raise exceptions.NotFoundError()

            if (
                new_message.id is not None
                and (
                    await session.exec(
                        query.select(db.ConversationMessage)
                        .where(db.ConversationMessage.conversation_id == conversation_id)
                        .where(db.ConversationMessage.message_id == new_message.id)
                    )
                ).one_or_none()
                is not None
            ):
                raise exceptions.ConflictError(f"message with id {new_message.id} already exists")

            match principal:
                case auth.UserPrincipal():
                    role = "user"
                    participant_id = principal.user_id
                case auth.AssistantServicePrincipal():
                    # allow assistants to send messages as users, if provided
                    if new_message.sender is not None and new_message.sender.participant_role == "user":
                        role = "user"
                        participant_id = new_message.sender.participant_id
                    else:
                        role = "assistant"
                        participant_id = str(principal.assistant_id)

            # pop "debug" from metadata, if it exists, and merge with the debug field
            message_debug = (new_message.metadata or {}).pop("debug", None)
            # ensure that message_debug is a dictionary, in cases like {"debug": "some message"}, or {"debug": [1,2]}
            if message_debug and not isinstance(message_debug, dict):
                message_debug = {"debug": message_debug}
            message_debug = deepmerge.always_merger.merge(message_debug or {}, new_message.debug_data or {})

            message = db.ConversationMessage(
                conversation_id=conversation.conversation_id,
                sender_participant_role=role,
                sender_participant_id=participant_id,
                message_type=new_message.message_type.value,
                content=new_message.content,
                content_type=new_message.content_type,
                filenames=new_message.filenames or [],
                meta_data=new_message.metadata or {},
            )
            if new_message.id is not None:
                message.message_id = new_message.id

            session.add(message)

            if message_debug:
                debug = db.ConversationMessageDebug(
                    message_id=message.message_id,
                    data=message_debug,
                )
                session.add(debug)

            await session.commit()
            await session.refresh(message)

            background_task: Iterable = ()
            if self._conversation_candidate_for_retitling(
                conversation=conversation
            ) and self._message_candidate_for_retitling(message=message):
                background_task = (
                    self._retitle_conversation,
                    principal,
                    conversation_id,
                    message.sequence,
                )

        message_response = convert.conversation_message_from_db(message, has_debug=bool(message_debug))

        await self._notify_event(
            ConversationEventQueueItem(
                event=ConversationEvent(
                    conversation_id=conversation_id,
                    event=ConversationEventType.message_created,
                    data={
                        "message": message_response.model_dump(),
                    },
                ),
            )
        )

        return message_response, background_task

    def _message_candidate_for_retitling(self, message: db.ConversationMessage) -> bool:
        """Check if the message is a candidate for retitling the conversation."""
        if message.sender_participant_role != ParticipantRole.user.value:
            return False

        if message.message_type != MessageType.chat.value:
            return False

        return True

    def _conversation_candidate_for_retitling(self, conversation: db.Conversation) -> bool:
        """Check if the conversation is a candidate for retitling."""
        if conversation.meta_data.get(META_DATA_KEY_USER_SET_TITLE, False):
            return False

        if conversation.meta_data.get(META_DATA_KEY_AUTO_TITLE_COUNT, 0) >= AUTO_TITLE_COUNT_LIMIT:
            return False

        return True

    async def _retitle_conversation(
        self,
        principal: auth.ActorPrincipal,
        conversation_id: uuid.UUID,
        latest_message_sequence: int,
    ) -> None:
        """Retitle the conversation based on the most recent messages."""

        if not settings.service.azure_openai_endpoint:
            logger.warning(
                "Azure OpenAI endpoint is not configured, skipping retitling conversation %s",
                conversation_id,
            )
            return

        if not settings.service.azure_openai_deployment:
            logger.warning(
                "Azure OpenAI deployment is not configured, skipping retitling conversation %s",
                conversation_id,
            )
            return

        async with self._get_session() as session:
            # Retrieve the most recent messages
            messages = list(
                (
                    await session.exec(
                        select(db.ConversationMessage)
                        .where(
                            db.ConversationMessage.conversation_id == conversation_id,
                            db.ConversationMessage.sequence <= latest_message_sequence,
                            db.ConversationMessage.message_type == MessageType.chat.value,
                        )
                        .order_by(col(db.ConversationMessage.sequence).desc())
                        .limit(10)
                    )
                ).all()
            )
            if not messages:
                return

        messages.reverse()

        completion_messages: list[ChatCompletionMessageParam] = []
        for message in messages:
            match message.sender_participant_role:
                case ParticipantRole.user.value:
                    completion_messages.append({
                        "role": "user",
                        "content": message.content,
                    })

                case _:
                    completion_messages.append({
                        "role": "assistant",
                        "content": message.content,
                    })

        # Call the LLM to get a new title
        try:
            async with openai_client.create_client(
                openai_client.AzureOpenAIServiceConfig(
                    auth_config=openai_client.AzureOpenAIAzureIdentityAuthConfig(),
                    azure_openai_deployment=settings.service.azure_openai_deployment,
                    azure_openai_endpoint=HttpUrl(settings.service.azure_openai_endpoint),
                ),
            ) as client:
                response = await client.beta.chat.completions.parse(
                    messages=[
                        *completion_messages,
                        {
                            "role": "developer",
                            "content": ("The current conversation title is: {conversation.title}"),
                        },
                    ],
                    model=settings.service.azure_openai_model,
                    # the model's description also contains instructions
                    response_format=ConversationTitleResponse,
                )

                if not response.choices:
                    raise RuntimeError("No choices in azure openai response")

                result = response.choices[0].message.parsed
                if result is None:
                    raise RuntimeError("No parsed result in azure openai response")

        except Exception:
            logger.exception("Failed to retitle conversation %s", conversation_id)
            return

        # Update the conversation title if it has not already been changed from the default
        async with self._get_session() as session:
            conversation = (
                await session.exec(
                    select(db.Conversation).where(db.Conversation.conversation_id == conversation_id).with_for_update()
                )
            ).one_or_none()
            if conversation is None:
                return

            if not self._conversation_candidate_for_retitling(conversation):
                return

            if result.title.strip():
                conversation.title = result.title.strip()

            conversation.meta_data = {
                **conversation.meta_data,
                META_DATA_KEY_AUTO_TITLE_COUNT: conversation.meta_data.get(META_DATA_KEY_AUTO_TITLE_COUNT, 0) + 1,
            }

            session.add(conversation)
            await session.commit()
            await session.refresh(conversation)

        conversation_model = await self.get_conversation(
            conversation_id=conversation.conversation_id,
            principal=principal,
            latest_message_types=set(),
        )

        await self._notify_event(
            ConversationEventQueueItem(
                event=ConversationEvent(
                    conversation_id=conversation.conversation_id,
                    event=ConversationEventType.conversation_updated,
                    data={
                        "conversation": conversation_model.model_dump(),
                    },
                )
            )
        )

    async def get_message(
        self,
        principal: auth.ActorPrincipal,
        conversation_id: uuid.UUID,
        message_id: uuid.UUID,
    ) -> ConversationMessage:
        async with self._get_session() as session:
            projection = (
                await session.exec(
                    query.select_conversation_message_projections_for(principal=principal)
                    .where(db.ConversationMessage.conversation_id == conversation_id)
                    .where(db.ConversationMessage.message_id == message_id)
                )
            ).one_or_none()
            if projection is None:
                raise exceptions.NotFoundError()

        message, has_debug = projection

        return convert.conversation_message_from_db(message, has_debug=has_debug)

    async def get_message_debug(
        self,
        principal: auth.ActorPrincipal,
        conversation_id: uuid.UUID,
        message_id: uuid.UUID,
    ) -> ConversationMessageDebug:
        async with self._get_session() as session:
            message_debug = (
                await session.exec(
                    query.select_conversation_message_debugs_for(principal=principal).where(
                        db.Conversation.conversation_id == conversation_id,
                        db.ConversationMessageDebug.message_id == message_id,
                    )
                )
            ).one_or_none()
            if message_debug is None:
                raise exceptions.NotFoundError()

        return convert.conversation_message_debug_from_db(message_debug)

    async def get_messages(
        self,
        principal: auth.ActorPrincipal,
        conversation_id: uuid.UUID,
        participant_roles: list[ParticipantRole] | None = None,
        participant_ids: list[str] | None = None,
        message_types: list[MessageType] | None = None,
        before: uuid.UUID | None = None,
        after: uuid.UUID | None = None,
        limit: int = 100,
    ) -> ConversationMessageList:
        async with self._get_session() as session:
            conversation = (
                await session.exec(
                    query.select_conversations_for(principal=principal, include_observer=True).where(
                        db.Conversation.conversation_id == conversation_id
                    )
                )
            ).one_or_none()
            if conversation is None:
                raise exceptions.NotFoundError()

            select_query = query.select_conversation_message_projections_for(principal=principal).where(
                db.ConversationMessage.conversation_id == conversation_id
            )

            if participant_roles is not None:
                select_query = select_query.where(
                    col(db.ConversationMessage.sender_participant_role).in_([r.value for r in participant_roles])
                )

            if participant_ids is not None:
                select_query = select_query.where(
                    col(db.ConversationMessage.sender_participant_id).in_(participant_ids)
                )

            if message_types is not None:
                select_query = select_query.where(
                    col(db.ConversationMessage.message_type).in_([t.value for t in message_types])
                )

            if before is not None:
                boundary = (
                    await session.exec(
                        query.select_conversation_messages_for(principal=principal).where(
                            db.ConversationMessage.message_id == before
                        )
                    )
                ).one_or_none()
                if boundary is not None:
                    select_query = select_query.where(db.ConversationMessage.sequence < boundary.sequence)

            if after is not None:
                boundary = (
                    await session.exec(
                        query.select_conversation_messages_for(principal=principal).where(
                            db.ConversationMessage.message_id == after
                        )
                    )
                ).one_or_none()
                if boundary is not None:
                    select_query = select_query.where(db.ConversationMessage.sequence > boundary.sequence)

            messages = list(
                (
                    await session.exec(select_query.order_by(col(db.ConversationMessage.sequence).desc()).limit(limit))
                ).all()
            )
            messages.reverse()

            return convert.conversation_message_list_from_db(messages)

    async def delete_message(
        self,
        conversation_id: uuid.UUID,
        message_id: uuid.UUID,
        user_principal: auth.UserPrincipal,
    ) -> None:
        async with self._get_session() as session:
            message = (
                await session.exec(
                    query.select_conversation_messages_for(principal=user_principal).where(
                        db.ConversationMessage.conversation_id == conversation_id,
                        db.ConversationMessage.message_id == message_id,
                        or_(
                            db.Conversation.owner_id == user_principal.user_id,
                            db.ConversationMessage.sender_participant_id == user_principal.user_id,
                            and_(
                                col(db.UserParticipant.active_participant).is_(True),
                                db.UserParticipant.conversation_permission == "read_write",
                            ),
                        ),
                    )
                )
            ).one_or_none()
            if message is None:
                raise exceptions.NotFoundError()

            message_response = convert.conversation_message_from_db(message, has_debug=False)

            await session.delete(message)
            await session.commit()

            await self._notify_event(
                ConversationEventQueueItem(
                    event=ConversationEvent(
                        conversation_id=conversation_id,
                        event=ConversationEventType.message_deleted,
                        data={
                            "message": message_response.model_dump(),
                        },
                    ),
                )
            )

```
Page 89/114FirstPrevNextLast