#
tokens: 49760/50000 1/1784 files (page 97/114)
lines: off (toggle) GitHub
raw markdown copy
This is page 97 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/tests/test_workbench_service.py:
--------------------------------------------------------------------------------

```python
import asyncio
import datetime
import io
import json
import logging
import re
import time
import uuid
from unittest.mock import AsyncMock, Mock

import httpx
import openai_client
import pytest
import semantic_workbench_api_model.assistant_model as api_model
import semantic_workbench_service
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import HttpUrl
from pytest_httpx import HTTPXMock
from semantic_workbench_api_model import workbench_model, workbench_service_client

from .types import MockUser


def test_service_init(workbench_service: FastAPI):
    with TestClient(app=workbench_service):
        pass


id_segment = "[0-9a-f-]+"


def register_assistant_service(client: TestClient) -> workbench_model.AssistantServiceRegistration:
    new_registration = workbench_model.NewAssistantServiceRegistration(
        assistant_service_id=uuid.uuid4().hex,
        name="test-assistant-service",
        description="",
    )
    http_response = client.post("/assistant-service-registrations", json=new_registration.model_dump(mode="json"))
    assert httpx.codes.is_success(http_response.status_code)

    registration = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

    update_with_url = workbench_model.UpdateAssistantServiceRegistrationUrl(
        name=new_registration.name,
        description=new_registration.description,
        url=HttpUrl("http://testassistantservice"),
        online_expires_in_seconds=60,
    )
    http_response = client.put(
        f"/assistant-service-registrations/{new_registration.assistant_service_id}",
        json=update_with_url.model_dump(mode="json"),
        headers=workbench_service_client.AssistantServiceRequestHeaders(
            assistant_service_id=registration.assistant_service_id,
            api_key=registration.api_key or "",
        ).to_headers(),
    )
    assert httpx.codes.is_success(http_response.status_code)

    return registration


def test_create_assistant(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        new_assistant = workbench_model.NewAssistant(
            name="test-assistant",
            assistant_service_id=registration.assistant_service_id,
            metadata={"test": "value"},
        )
        http_response = client.post("/assistants", json=new_assistant.model_dump(mode="json"))
        assert httpx.codes.is_success(http_response.status_code)

        logging.info("response: %s", http_response.json())

        assistant_response = workbench_model.Assistant.model_validate(http_response.json())
        assert assistant_response.name == new_assistant.name
        assert assistant_response.assistant_service_id == new_assistant.assistant_service_id
        assert assistant_response.metadata == new_assistant.metadata


def test_create_assistant_request_failure(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_exception(httpx.NetworkError("test error"))

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        new_assistant = workbench_model.NewAssistant(
            name="test-assistant",
            assistant_service_id=registration.assistant_service_id,
            image="pass an image to circumvent the request to the assistant service to get one",
            metadata={"test": "value"},
        )
        http_response = client.post("/assistants", json=new_assistant.model_dump(mode="json"))

        assert http_response.status_code == httpx.codes.FAILED_DEPENDENCY
        response_body = http_response.json()
        assert "detail" in response_body
        assert re.match(
            r"Failed to connect to assistant at url http://testassistantservice/[0-9a-f-]{36}; NetworkError: test"
            r" error",
            response_body["detail"],
        )


def exclude_system_keys(metadata: dict) -> dict:
    """Omit system metadata from the given metadata dictionary."""
    return {k: v for k, v in metadata.items() if not k.startswith("__")}


def test_create_conversation(workbench_service: FastAPI, test_user: MockUser):
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_conversation = workbench_model.NewConversation(title="test", metadata={"test": "value"})
        http_response = client.post("/conversations", json=new_conversation.model_dump(mode="json"))
        assert httpx.codes.is_success(http_response.status_code)

        logging.info("response: %s", http_response.json())

        conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        assert conversation_response.title == new_conversation.title
        assert exclude_system_keys(conversation_response.metadata) == new_conversation.metadata

        http_response = client.get(f"/conversations/{conversation_response.id}")
        assert httpx.codes.is_success(http_response.status_code)

        get_conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        assert get_conversation_response.title == new_conversation.title
        assert exclude_system_keys(get_conversation_response.metadata) == new_conversation.metadata


class AsyncContextManagerMock:
    def __init__(self, mock: Mock) -> None:
        self.mock = mock

    async def __aenter__(self):
        """Enter async context manager."""
        return self.mock

    async def __aexit__(self, exc_type, exc, tb):
        """Exit async context manager."""
        pass


def test_create_conversation_and_retitle(
    workbench_service: FastAPI, test_user: MockUser, monkeypatch: pytest.MonkeyPatch
):
    from semantic_workbench_service.controller.conversation import ConversationTitleResponse

    mock_parsed_choice = Mock()
    mock_parsed_choice.message.parsed = ConversationTitleResponse(title="A sweet title")

    mock_parsed_completion = Mock()
    mock_parsed_completion.choices = [mock_parsed_choice]

    mock_client = Mock()
    mock_client.beta.chat.completions.parse = AsyncMock()
    mock_client.beta.chat.completions.parse.return_value = mock_parsed_completion

    mock_create_client = Mock(spec=openai_client.create_client)
    mock_create_client.return_value = AsyncContextManagerMock(mock_client)

    monkeypatch.setattr(openai_client, "create_client", mock_create_client)

    monkeypatch.setattr(semantic_workbench_service.settings.service, "azure_openai_endpoint", "https://something/")
    monkeypatch.setattr(semantic_workbench_service.settings.service, "azure_openai_deployment", "something")

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_conversation = workbench_model.NewConversation(metadata={"test": "value"})
        http_response = client.post("/conversations", json=new_conversation.model_dump(mode="json"))
        assert httpx.codes.is_success(http_response.status_code)

        conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        assert conversation_response.title == "New Conversation"

        http_response = client.get(f"/conversations/{conversation_response.id}")
        assert httpx.codes.is_success(http_response.status_code)

        get_conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        assert get_conversation_response.title == new_conversation.title

        new_message = workbench_model.NewConversationMessage(content="hi")
        http_response = client.post(
            f"/conversations/{conversation_response.id}/messages", json=new_message.model_dump(mode="json")
        )
        assert httpx.codes.is_success(http_response.status_code)

        for _ in range(10):
            http_response = client.get(f"/conversations/{conversation_response.id}")
            assert httpx.codes.is_success(http_response.status_code)

            get_conversation_response = workbench_model.Conversation.model_validate(http_response.json())
            if get_conversation_response.title != "New Conversation":
                break
            time.sleep(0.1)

        get_conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        assert get_conversation_response.title == "A sweet title"


def test_create_update_conversation(workbench_service: FastAPI, test_user: MockUser):
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_conversation = workbench_model.NewConversation(title="test-conversation", metadata={"test": "value"})
        http_response = client.post("/conversations", json=new_conversation.model_dump(mode="json"))
        assert httpx.codes.is_success(http_response.status_code)

        conversation_response = workbench_model.Conversation.model_validate(http_response.json())

        updated_title = f"new-title{uuid.uuid4()}"
        updated_metadata = {"test": uuid.uuid4().hex}

        http_response = client.patch(
            f"/conversations/{conversation_response.id}",
            json=workbench_model.UpdateConversation(title=updated_title, metadata=updated_metadata).model_dump(
                mode="json",
            ),
        )
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation_response.id}")
        assert httpx.codes.is_success(http_response.status_code)

        get_conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        assert get_conversation_response.title == updated_title
        assert exclude_system_keys(get_conversation_response.metadata) == updated_metadata


def test_create_assistant_add_to_conversation(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        assistant = workbench_model.Assistant.model_validate(http_response.json())

        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)

        conversation = workbench_model.Conversation.model_validate(http_response.json())

        http_response = client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/assistants/{assistant.id}/conversations")
        assert httpx.codes.is_success(http_response.status_code)

        assistant_conversations = workbench_model.ConversationList.model_validate(http_response.json())
        assert len(assistant_conversations.conversations) == 1
        assert assistant_conversations.conversations[0].id == conversation.id


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_assistant_add_to_conversation_delete_assistant_retains_participant(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="DELETE",
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        assistant = workbench_model.Assistant.model_validate(http_response.json())

        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)

        conversation = workbench_model.Conversation.model_validate(http_response.json())

        http_response = client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation.id}/participants")
        assert httpx.codes.is_success(http_response.status_code)

        participants = workbench_model.ConversationParticipantList.model_validate(http_response.json())

        assert len(participants.participants) == 2
        assert {p.id for p in participants.participants} == {test_user.id, str(assistant.id)}

        assistant_participant = next(p for p in participants.participants if p.id == str(assistant.id))
        assert assistant_participant.name == assistant.name
        assert assistant_participant.image == assistant.image
        assert assistant_participant.active_participant is True
        assert assistant_participant.online is True

        # update assistant and verify that the participant attributes are updated
        http_response = client.patch(
            f"/assistants/{assistant.id}",
            json=workbench_model.UpdateAssistant(name="new-name", image="foo").model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        assistant = workbench_model.Assistant.model_validate(http_response.json())
        assert assistant.name == "new-name"
        assert assistant.image == "foo"

        http_response = client.get(f"/conversations/{conversation.id}/participants")
        assert httpx.codes.is_success(http_response.status_code)

        participants = workbench_model.ConversationParticipantList.model_validate(http_response.json())

        assert len(participants.participants) == 2
        assert {p.id for p in participants.participants} == {test_user.id, str(assistant.id)}

        assistant_participant = next(p for p in participants.participants if p.id == str(assistant.id))
        assert assistant_participant.name == assistant.name
        assert assistant_participant.image == assistant.image
        assert assistant_participant.active_participant is True
        assert assistant_participant.online is True

        # delete assistant and verify that the participant is still in the conversation
        http_response = client.delete(f"/assistants/{assistant.id}")
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation.id}/participants", params={"include_inactive": True})
        assert httpx.codes.is_success(http_response.status_code)

        participants = workbench_model.ConversationParticipantList.model_validate(http_response.json())

        assert len(participants.participants) == 2
        assert {p.id for p in participants.participants} == {test_user.id, str(assistant.id)}

        assistant_participant = next(p for p in participants.participants if p.id == str(assistant.id))
        assert assistant_participant.name == assistant.name
        assert assistant_participant.image == assistant.image
        assert assistant_participant.active_participant is False
        assert assistant_participant.online is False


def test_create_get_assistant(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = http_response.json()
        logging.info("response: %s", assistant_response)
        assert "id" in assistant_response
        assistant_id = assistant_response["id"]

        http_response = client.get(f"/assistants/{assistant_id}")
        assert httpx.codes.is_success(http_response.status_code)
        assert http_response.json() == assistant_response

        http_response = client.get("/assistants")
        assert httpx.codes.is_success(http_response.status_code)
        assistants_response = http_response.json()
        assert "assistants" in assistants_response
        assert assistants_response["assistants"] == [assistant_response]


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_update_assistant(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
    test_user_2: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
                metadata={"test": "value"},
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = http_response.json()
        logging.info("response: %s", assistant_response)
        assert "id" in assistant_response
        assistant_id = assistant_response["id"]

        updated_name = f"new-name{uuid.uuid4()}"
        updated_metadata = {"test": uuid.uuid4().hex}
        http_response = client.patch(
            f"/assistants/{assistant_id}",
            json=workbench_model.UpdateAssistant(name=updated_name, metadata=updated_metadata).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/assistants/{assistant_id}")
        assert httpx.codes.is_success(http_response.status_code)
        assistants_response = workbench_model.Assistant.model_validate(http_response.json())
        assert assistants_response.name == updated_name
        assert assistants_response.metadata == updated_metadata

        # ensure another user cannot update
        http_response = client.patch(
            f"/assistants/{assistant_id}",
            json=workbench_model.UpdateAssistant(name=updated_name, metadata=updated_metadata).model_dump(mode="json"),
            headers=test_user_2.authorization_headers,
        )
        assert httpx.codes.is_client_error(http_response.status_code)


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_delete_assistant(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
    test_user_2: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="DELETE",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="DELETE",
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = http_response.json()
        logging.info("response: %s", assistant_response)
        assert "id" in assistant_response
        assistant_id = assistant_response["id"]

        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id = conversation_response["id"]

        http_response = client.put(f"/conversations/{conversation_id}/participants/{assistant_id}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        payload = {"content": "hello"}
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        assert httpx.codes.is_success(http_response.status_code)

        # ensure another user cannot delete
        http_response = client.delete(f"/assistants/{assistant_id}", headers=test_user_2.authorization_headers)
        assert httpx.codes.is_client_error(http_response.status_code)

        http_response = client.delete(f"/assistants/{assistant_id}")
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/assistants/{assistant_id}")
        assert http_response.status_code == httpx.codes.NOT_FOUND


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_assistant_update_participant(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        logging.info("response: %s", http_response.json())
        assistant_id = http_response.json()["id"]

        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id = conversation_response["id"]

        http_response = client.put(f"/conversations/{conversation_id}/participants/{assistant_id}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation_id}/participants")
        assert httpx.codes.is_success(http_response.status_code)
        logging.info("response: %s", http_response.json())
        participants_response = http_response.json()
        assert "participants" in participants_response
        participants = participants_response["participants"]
        assert len(participants) == 2

        expected_participant_ids = {test_user.id, assistant_id}
        participant_ids = {p["id"] for p in participants}
        assert participant_ids == expected_participant_ids

        http_response = client.get(f"/conversations/{conversation_id}/participants/{test_user.id}")
        assert httpx.codes.is_success(http_response.status_code)
        my_id_participant = http_response.json()

        http_response = client.get(f"/conversations/{conversation_id}/participants/me")
        assert httpx.codes.is_success(http_response.status_code)
        me_participant = http_response.json()

        assert my_id_participant == me_participant

        http_response = client.patch(f"/conversations/{conversation_id}/participants/me", json={"status": "testing"})
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation_id}/participants/me")
        assert httpx.codes.is_success(http_response.status_code)
        updated_me_participant = http_response.json()
        assert updated_me_participant["status"] == "testing"

        me_timestamp = datetime.datetime.fromisoformat(me_participant["status_updated_timestamp"])
        updated_timestamp = datetime.datetime.fromisoformat(updated_me_participant["status_updated_timestamp"])
        assert updated_timestamp > me_timestamp


@pytest.mark.parametrize("message_type", ["command", "log", "note", "notice"])
def test_create_conversation_send_nonchat_message(
    workbench_service: FastAPI,
    test_user: MockUser,
    message_type: str,
):
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id = conversation_response["id"]

        # create a chat message that should not be returned
        message_content = "message of type chat"
        payload = {"message_type": "chat", "content_type": "text/plain", "content": message_content}
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        assert httpx.codes.is_success(http_response.status_code)

        message_content = f"message of type {message_type}"
        payload = {"message_type": message_type, "content_type": "text/plain", "content": message_content}
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation_id}/messages", params={"message_type": message_type})
        assert httpx.codes.is_success(http_response.status_code)
        messages_response = http_response.json()
        assert "messages" in messages_response
        messages = messages_response["messages"]
        assert len(messages) == 1
        message = messages[0]
        assert message["content"] == message_content
        assert message["sender"]["participant_id"] == test_user.id
        assert message["message_type"] == message_type


def test_create_conversation_send_user_message(workbench_service: FastAPI, test_user: MockUser):
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation = workbench_model.Conversation.model_validate(http_response.json())
        conversation_id = conversation.id

        assert conversation.latest_message is None

        payload = {"content": "hello"}
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        message = workbench_model.ConversationMessage.model_validate(http_response.json())
        message_id = message.id
        assert message.has_debug_data is False

        http_response = client.get(f"/conversations/{conversation_id}/messages")
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 1
        message = messages.messages[0]
        assert message.content == "hello"
        assert message.sender.participant_id == test_user.id

        http_response = client.get(f"/conversations/{conversation_id}/messages/{message_id}")
        assert httpx.codes.is_success(http_response.status_code)
        message = workbench_model.ConversationMessage.model_validate(http_response.json())
        assert message.content == "hello"
        assert message.sender.participant_id == test_user.id

        # send another chat message, with debug
        payload = {
            "content": "hello again",
            "metadata": {"debug": {"key1": "value1"}},
            "debug_data": {"key2": "value2"},
        }
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        message = workbench_model.ConversationMessage.model_validate(http_response.json())
        message_two_id = message.id

        # debug should be stripped out
        assert message.metadata == {}
        assert message.has_debug_data is True

        http_response = client.get(f"/conversations/{conversation_id}/messages/{message_two_id}/debug_data")
        assert httpx.codes.is_success(http_response.status_code)
        message = workbench_model.ConversationMessageDebug.model_validate(http_response.json())

        assert message.debug_data == {"key1": "value1", "key2": "value2"}

        # send a log message
        payload = {"content": "hello again", "message_type": "log"}
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        message = workbench_model.ConversationMessage.model_validate(http_response.json())
        message_log_id = message.id

        # get all messages
        http_response = client.get(f"/conversations/{conversation_id}/messages")
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 3
        message = messages.messages[0]
        assert message.content == "hello"
        assert message.sender.participant_id == test_user.id
        assert message.id == message_id
        message = messages.messages[1]
        assert message.content == "hello again"
        assert message.sender.participant_id == test_user.id
        assert message.id == message_two_id
        message = messages.messages[2]
        assert message.content == "hello again"
        assert message.sender.participant_id == test_user.id
        assert message.id == message_log_id

        # limit messages
        http_response = client.get(f"/conversations/{conversation_id}/messages", params={"limit": 1})
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 1
        message = messages.messages[0]
        assert message.id == message_log_id

        # get messages before
        http_response = client.get(f"/conversations/{conversation_id}/messages", params={"before": str(message_two_id)})
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 1
        message = messages.messages[0]
        assert message.id == message_id

        # get messages after
        http_response = client.get(f"/conversations/{conversation_id}/messages", params={"after": str(message_id)})
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 2
        message = messages.messages[0]
        assert message.id == message_two_id
        message = messages.messages[1]
        assert message.id == message_log_id

        # get messages by type
        http_response = client.get(f"/conversations/{conversation_id}/messages", params={"message_type": "chat"})
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 2

        http_response = client.get(f"/conversations/{conversation_id}/messages", params={"message_type": "log"})
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 1

        http_response = client.get(
            f"/conversations/{conversation_id}/messages",
            params={"message_type": ["chat", "log"]},
        )
        assert httpx.codes.is_success(http_response.status_code)
        messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
        assert len(messages.messages) == 3

        # check latest chat message in conversation (chat is default)
        http_response = client.get(f"/conversations/{conversation_id}")
        assert httpx.codes.is_success(http_response.status_code)
        conversation = workbench_model.Conversation.model_validate(http_response.json())
        assert conversation.latest_message is not None
        assert conversation.latest_message.id == message_two_id

        # check latest log message in conversation
        http_response = client.get(f"/conversations/{conversation_id}", params={"latest_message_type": ["log"]})
        assert httpx.codes.is_success(http_response.status_code)
        conversation = workbench_model.Conversation.model_validate(http_response.json())
        assert conversation.latest_message is not None
        assert conversation.latest_message.id == message_log_id


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_assistant_send_assistant_message(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        logging.info("response: %s", http_response.json())
        assistant_response = http_response.json()
        assistant_id = assistant_response["id"]

        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id = conversation_response["id"]

        http_response = client.put(f"/conversations/{conversation_id}/participants/{assistant_id}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        payload = {"content": "hello", "metadata": {"assistant_id": assistant_id, "generated_by": "test"}}
        assistant_headers = {
            **workbench_service_client.AssistantServiceRequestHeaders(
                assistant_service_id=registration.assistant_service_id,
                api_key=registration.api_key or "",
            ).to_headers(),
            **workbench_service_client.AssistantRequestHeaders(
                assistant_id=assistant_id,
            ).to_headers(),
        }
        http_response = client.post(
            f"/conversations/{conversation_id}/messages",
            json=payload,
            headers=assistant_headers,
        )
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation_id}/messages")
        assert httpx.codes.is_success(http_response.status_code)
        messages_response = http_response.json()
        assert "messages" in messages_response
        messages = messages_response["messages"]
        assert len(messages) == 1
        message = messages[0]
        assert message["content"] == "hello"
        assert message["sender"]["participant_id"] == assistant_id
        assert message["metadata"] == {"assistant_id": assistant_id, "generated_by": "test"}


def test_create_conversation_write_read_delete_file(
    workbench_service: FastAPI,
    test_user: MockUser,
):
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id = conversation_response["id"]

        http_response = client.get(f"/conversations/{conversation_id}/files")
        assert httpx.codes.is_success(http_response.status_code)
        logging.info("response: %s", http_response.json())
        files = http_response.json()["files"]
        assert len(files) == 0

        # write 3 files
        payload = [
            ("files", ("test.txt", "hello world\n", "text/plain")),
            ("files", ("path1/path2/test.html", "<html><body></body></html>\n", "text/html")),
            ("files", ("path1/path2/test.bin", bytes(range(ord("a"), ord("f"))), "application/octet-stream")),
        ]
        http_response = client.put(
            f"/conversations/{conversation_id}/files",
            files=payload,
            # one of them has metadata
            data={"metadata": json.dumps({"path1/path2/test.bin": {"generated_by": "test"}})},
        )
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        files = http_response.json()["files"]
        assert [f["filename"] for f in files] == ["test.txt", "path1/path2/test.html", "path1/path2/test.bin"]
        assert files[2]["metadata"] == {"generated_by": "test"}

        # get the file listing
        http_response = client.get(f"/conversations/{conversation_id}/files")
        assert httpx.codes.is_success(http_response.status_code)
        logging.info("response: %s", http_response.json())
        files = http_response.json()["files"]
        assert [f["filename"] for f in files] == ["path1/path2/test.bin", "path1/path2/test.html", "test.txt"]

        # get files by prefix
        http_response = client.get(f"/conversations/{conversation_id}/files", params={"prefix": "path1/path2"})
        assert httpx.codes.is_success(http_response.status_code)
        logging.info("response: %s", http_response.json())
        files = http_response.json()["files"]
        assert [f["filename"] for f in files] == ["path1/path2/test.bin", "path1/path2/test.html"]

        # download a file
        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt")
        assert httpx.codes.is_success(http_response.status_code)
        assert http_response.text == "hello world\n"

        # download another file
        http_response = client.get(f"/conversations/{conversation_id}/files/path1/path2/test.html")
        assert httpx.codes.is_success(http_response.status_code)
        assert http_response.text == "<html><body></body></html>\n"

        # re-write test.txt
        payload = [
            ("files", ("test.txt", "hello again\n", "text/plain")),
        ]
        http_response = client.put(f"/conversations/{conversation_id}/files", files=payload)
        assert httpx.codes.is_success(http_response.status_code)

        # get all versions
        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt/versions")
        assert httpx.codes.is_success(http_response.status_code)
        file_versions = http_response.json()
        assert len(file_versions["versions"]) == 2
        assert file_versions["versions"][0]["version"] == 1
        assert file_versions["versions"][1]["version"] == 2

        # get a single version
        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt/versions", params={"version": 1})
        assert httpx.codes.is_success(http_response.status_code)
        file_versions = http_response.json()
        assert len(file_versions["versions"]) == 1
        assert file_versions["versions"][0]["version"] == 1

        # get the file content for the current version
        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt")
        assert httpx.codes.is_success(http_response.status_code)
        assert http_response.text == "hello again\n"

        # get the file content for the prior version
        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt", params={"version": 1})
        assert httpx.codes.is_success(http_response.status_code)
        assert http_response.text == "hello world\n"

        # delete a file
        http_response = client.delete(f"/conversations/{conversation_id}/files/test.txt")
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt")
        assert http_response.status_code == httpx.codes.NOT_FOUND

        http_response = client.get(f"/conversations/{conversation_id}/files/test.txt/versions")
        assert http_response.status_code == httpx.codes.NOT_FOUND


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_assistant_export_import_data(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/export-data"),
        method="GET",
        json={"data": "assistant test export data"},
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/export-data"),
        method="GET",
        json={"data": "conversation test export data"},
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = http_response.json()
        assistant_id = assistant_response["id"]

        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id = conversation_response["id"]

        http_response = client.put(f"/conversations/{conversation_id}/participants/{assistant_id}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        payload = {"content": "hello"}
        http_response = client.post(f"/conversations/{conversation_id}/messages", json=payload)
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get(f"/assistants/{assistant_id}/export")
        assert httpx.codes.is_success(http_response.status_code)

        assert http_response.headers["content-type"] == "application/zip"
        assert "content-length" in http_response.headers
        assert int(http_response.headers["content-length"]) > 0

        logging.info("response: %s", http_response.content)

        file_io = io.BytesIO(http_response.content)

        for import_number in range(1, 3):
            http_response = client.post("/conversations/import", files={"from_export": file_io})
            logging.info("response: %s", http_response.json())
            assert httpx.codes.is_success(http_response.status_code)

            http_response = client.get("/assistants")
            logging.info("response: %s", http_response.json())
            assert httpx.codes.is_success(http_response.status_code)
            assistants_response = http_response.json()
            assert "assistants" in assistants_response
            assistant_count = len(assistants_response["assistants"])
            assert assistant_count == 1

            for index, assistant in enumerate(assistants_response["assistants"]):
                assert assistant["name"] == "test-assistant"


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_assistant_conversations_export_import_conversations(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
) -> None:
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/export-data"),
        method="GET",
        json={"data": "assistant test export data"},
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/export-data"),
        method="GET",
        json={"data": "conversation test export data"},
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant-1",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = http_response.json()
        assistant_id_1 = assistant_response["id"]

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant-2",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = http_response.json()
        assistant_id_2 = assistant_response["id"]

        http_response = client.post("/conversations", json={"title": "test-conversation-1"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id_1 = conversation_response["id"]

        http_response = client.post("/conversations", json={"title": "test-conversation-2"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = http_response.json()
        conversation_id_2 = conversation_response["id"]

        # both assistants are in conversation-1
        http_response = client.put(f"/conversations/{conversation_id_1}/participants/{assistant_id_1}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.put(f"/conversations/{conversation_id_1}/participants/{assistant_id_2}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        # only assistant-1 is in conversation-2
        http_response = client.put(f"/conversations/{conversation_id_2}/participants/{assistant_id_1}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        payload = {"content": "hello", "debug_data": {"key": "value"}}
        http_response = client.post(f"/conversations/{conversation_id_1}/messages", json=payload)
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.post(f"/conversations/{conversation_id_2}/messages", json=payload)
        assert httpx.codes.is_success(http_response.status_code)

        # export both conversations
        http_response = client.get("/conversations/export", params={"id": [conversation_id_1, conversation_id_2]})
        assert httpx.codes.is_success(http_response.status_code)

        assert http_response.headers["content-type"] == "application/zip"
        assert "content-length" in http_response.headers
        assert int(http_response.headers["content-length"]) > 0

        logging.info("response: %s", http_response.content)

        file_io = io.BytesIO(http_response.content)

        for _ in range(1, 3):
            http_response = client.post("/conversations/import", files={"from_export": file_io})
            logging.info("response: %s", http_response.json())
            assert httpx.codes.is_success(http_response.status_code)

            http_response = client.get("/assistants")
            logging.info("response: %s", http_response.json())
            assert httpx.codes.is_success(http_response.status_code)
            assistants_response = http_response.json()
            assert "assistants" in assistants_response
            assistant_count = len(assistants_response["assistants"])
            assert assistant_count == 2

        http_response = client.get("/assistants")
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)

        assistants = workbench_model.AssistantList.model_validate(http_response.json())

        assistants.assistants = sorted(assistants.assistants, key=lambda a: a.name)
        assert len(assistants.assistants) == 2
        assert assistants.assistants[0].name == "test-assistant-1"
        assert assistants.assistants[1].name == "test-assistant-2"

        http_response = client.get("/conversations")
        logging.info("response: %s", http_response.json())
        assert httpx.codes.is_success(http_response.status_code)

        conversations = workbench_model.ConversationList.model_validate(http_response.json())
        conversations.conversations = sorted(conversations.conversations, key=lambda c: c.title)

        assert conversations.conversations[0].title == "test-conversation-1"
        assert conversations.conversations[1].title == "test-conversation-1 (1)"
        assert conversations.conversations[2].title == "test-conversation-1 (2)"
        assert conversations.conversations[3].title == "test-conversation-2"
        assert conversations.conversations[4].title == "test-conversation-2 (1)"
        assert conversations.conversations[5].title == "test-conversation-2 (2)"

        for conversation in conversations.conversations:
            http_response = client.get(f"/conversations/{conversation.id}/messages")
            assert httpx.codes.is_success(http_response.status_code)

            messages = workbench_model.ConversationMessageList.model_validate(http_response.json())
            assert len(messages.messages) == 1

            message = messages.messages[0]
            assert message.content == "hello"
            assert message.sender.participant_id == test_user.id
            assert message.has_debug_data is True

            http_response = client.get(f"/conversations/{conversation.id}/messages/{message.id}/debug_data")
            assert httpx.codes.is_success(http_response.status_code)
            message_debug = workbench_model.ConversationMessageDebug.model_validate(http_response.json())
            assert message_debug.debug_data == {"key": "value"}


def test_export_import_conversations_with_files(
    workbench_service: FastAPI,
    test_user: MockUser,
) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        http_response = client.post("/conversations", json={"title": "test-conversation-1"})
        assert httpx.codes.is_success(http_response.status_code)

        conversation_1 = workbench_model.Conversation.model_validate(http_response.json())

        http_response = client.post("/conversations", json={"title": "test-conversation-2"})
        assert httpx.codes.is_success(http_response.status_code)

        conversation_2 = workbench_model.Conversation.model_validate(http_response.json())

        for conversation in [conversation_1, conversation_2]:
            payload = [
                ("files", ("test.txt", "hello world\n", "text/plain")),
                ("files", ("path1/path2/test.html", "<html><body></body></html>\n", "text/html")),
                ("files", ("path1/path2/test.bin", bytes(range(ord("a"), ord("f"))), "application/octet-stream")),
            ]
            http_response = client.put(f"/conversations/{conversation.id}/files", files=payload)
            assert httpx.codes.is_success(http_response.status_code)

            file_list = workbench_model.FileList.model_validate(http_response.json())
            assert len(file_list.files) == 3

        http_response = client.get(
            "/conversations/export", params={"id": [str(conversation_1.id), str(conversation_2.id)]}
        )
        assert httpx.codes.is_success(http_response.status_code)

        exported_data = io.BytesIO(http_response.content)

        for _ in range(1, 2):
            http_response = client.post("/conversations/import", files={"from_export": exported_data})
            assert httpx.codes.is_success(http_response.status_code)

            import_result = workbench_model.ConversationImportResult.model_validate(http_response.json())
            assert len(import_result.conversation_ids) == 2

            for conversation_id in import_result.conversation_ids:
                http_response = client.get(f"/conversations/{conversation_id}/files")
                assert httpx.codes.is_success(http_response.status_code)

                file_list = workbench_model.FileList.model_validate(http_response.json())
                assert len(file_list.files) == 3

                for file in file_list.files:
                    http_response = client.get(f"/conversations/{conversation_id}/files/{file.filename}")
                    assert httpx.codes.is_success(http_response.status_code)

                    match file.filename:
                        case "test.txt":
                            assert http_response.text == "hello world\n"
                        case "path1/path2/test.html":
                            assert http_response.text == "<html><body></body></html>\n"
                        case "path1/path2/test.bin":
                            assert http_response.content == bytes(range(ord("a"), ord("f")))
                        case _:
                            pytest.fail(f"unexpected file: {file.filename}")


@pytest.mark.httpx_mock(can_send_already_matched_responses=True)
def test_create_conversations_get_participants(
    workbench_service: FastAPI,
    httpx_mock: HTTPXMock,
    test_user: MockUser,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )
    new_conversation_response = api_model.ConversationResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}"),
        method="PUT",
        json=new_conversation_response.model_dump(),
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}/conversations/{id_segment}/events"),
        method="POST",
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        http_response = client.post("/conversations", json={"title": "test-conversation-1"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        conversation_id_1 = conversation_response.id

        http_response = client.post("/conversations", json={"title": "test-conversation-2"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        conversation_id_2 = conversation_response.id

        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant-1",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = workbench_model.Assistant.model_validate(http_response.json())
        assistant_id_1 = assistant_response.id

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant-2",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = workbench_model.Assistant.model_validate(http_response.json())
        assistant_id_2 = assistant_response.id

        # both assistants are in conversation-1
        http_response = client.put(f"/conversations/{conversation_id_1}/participants/{assistant_id_1}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.put(f"/conversations/{conversation_id_1}/participants/{assistant_id_2}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        # only assistant-1 is in conversation-2
        http_response = client.put(f"/conversations/{conversation_id_2}/participants/{assistant_id_1}", json={})
        assert httpx.codes.is_success(http_response.status_code)

        for conversation_id, participant_ids in {
            conversation_id_1: {str(assistant_id_1), str(assistant_id_2), test_user.id},
            conversation_id_2: {str(assistant_id_1), test_user.id},
        }.items():
            http_response = client.get(f"/conversations/{conversation_id}/participants")
            assert httpx.codes.is_success(http_response.status_code)
            participants_response = workbench_model.ConversationParticipantList.model_validate(http_response.json())

            assert {p.id for p in participants_response.participants} == participant_ids

        for assistant_id, conversation_ids in {
            assistant_id_1: {conversation_id_1, conversation_id_2},
            assistant_id_2: {conversation_id_1},
        }.items():
            http_response = client.get(f"/assistants/{assistant_id}/conversations")
            assert httpx.codes.is_success(http_response.status_code)
            conversations_response = workbench_model.ConversationList.model_validate(http_response.json())

            assert {c.id for c in conversations_response.conversations} == conversation_ids


@pytest.mark.parametrize(
    "url_template",
    [
        "/conversations/{conversation_id}",
        "/conversations/{conversation_id}/messages",
        "/conversations/{conversation_id}/participants",
    ],
)
def test_conversation_not_visible_to_non_participants(
    workbench_service: FastAPI,
    test_user: MockUser,
    test_user_2: MockUser,
    httpx_mock: HTTPXMock,
    url_template: str,
):
    httpx_mock.add_response(
        url="http://testassistantservice/",
        method="GET",
        json=api_model.ServiceInfoModel(assistant_service_id="", name="", templates=[], metadata={}).model_dump(
            mode="json"
        ),
    )
    new_assistant_response = api_model.AssistantResponseModel(
        id="123",
    )
    httpx_mock.add_response(
        url=re.compile(f"http://testassistantservice/{id_segment}"),
        method="PUT",
        json=new_assistant_response.model_dump(),
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        http_response = client.post("/conversations", json={"title": "test-conversation"})
        assert httpx.codes.is_success(http_response.status_code)
        conversation_response = workbench_model.Conversation.model_validate(http_response.json())
        conversation_id = conversation_response.id

        registration = register_assistant_service(client)

        http_response = client.post(
            "/assistants",
            json=workbench_model.NewAssistant(
                name="test-assistant",
                assistant_service_id=registration.assistant_service_id,
            ).model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_response = workbench_model.Assistant.model_validate(http_response.json())

        # ensure user 2 cannot make get request
        http_response = client.get(
            url_template.format(conversation_id=conversation_id),
            headers=test_user_2.authorization_headers,
        )
        assert http_response.status_code == httpx.codes.NOT_FOUND

        # ensure assistant request always returns 404
        assistant_headers = {
            **workbench_service_client.AssistantServiceRequestHeaders(
                assistant_service_id=registration.assistant_service_id,
                api_key=registration.api_key or "",
            ).to_headers(),
            **workbench_service_client.AssistantRequestHeaders(
                assistant_id=assistant_response.id,
            ).to_headers(),
        }
        http_response = client.get(url_template.format(conversation_id=conversation_id), headers=assistant_headers)
        assert http_response.status_code == httpx.codes.NOT_FOUND


def test_create_assistant_service_registration(workbench_service: FastAPI, test_user: MockUser) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_assistant_service = workbench_model.NewAssistantServiceRegistration(
            assistant_service_id="test-assistant-service-id",
            name="test-assistant-service-name",
            description="test description",
        )

        http_response = client.post(
            "/assistant-service-registrations",
            json=new_assistant_service.model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        created_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert created_assistant_service.name == new_assistant_service.name
        assert created_assistant_service.description == new_assistant_service.description
        assert created_assistant_service.created_by_user_id == test_user.id
        assert created_assistant_service.api_key is not None


def test_create_get_assistant_service_registration(workbench_service: FastAPI, test_user: MockUser) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_assistant_service = workbench_model.NewAssistantServiceRegistration(
            assistant_service_id="test-assistant-service-id",
            name="test-assistant-service-name",
            description="test description",
        )

        http_response = client.post(
            "/assistant-service-registrations",
            json=new_assistant_service.model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        # get single registration
        http_response = client.get(f"/assistant-service-registrations/{new_assistant_service.assistant_service_id}")
        assert httpx.codes.is_success(http_response.status_code)

        assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert assistant_service.name == new_assistant_service.name
        assert assistant_service.description == new_assistant_service.description
        assert assistant_service.created_by_user_id == test_user.id
        # get on single registration returns a mask API key
        assert assistant_service.api_key is not None
        assert assistant_service.api_key.endswith("*" * 10)

        # get all registrations
        http_response = client.get("/assistant-service-registrations")
        assert httpx.codes.is_success(http_response.status_code)

        retrieved_assistant_services = workbench_model.AssistantServiceRegistrationList.model_validate(
            http_response.json(),
        )

        assert len(retrieved_assistant_services.assistant_service_registrations) == 1

        assistant_service = retrieved_assistant_services.assistant_service_registrations[0]
        assert assistant_service.name == new_assistant_service.name
        assert assistant_service.description == new_assistant_service.description
        assert assistant_service.created_by_user_id == test_user.id
        assert assistant_service.api_key is None

        # get registrations owned by user
        http_response = client.get("/assistant-service-registrations", params={"owner_id": "me"})
        assert httpx.codes.is_success(http_response.status_code)

        retrieved_assistant_services = workbench_model.AssistantServiceRegistrationList.model_validate(
            http_response.json(),
        )

        assert len(retrieved_assistant_services.assistant_service_registrations) == 1

        assistant_service = retrieved_assistant_services.assistant_service_registrations[0]
        assert assistant_service.name == new_assistant_service.name
        assert assistant_service.description == new_assistant_service.description
        assert assistant_service.created_by_user_id == test_user.id
        assert assistant_service.api_key is None


def test_create_update_assistant_service_registration(workbench_service: FastAPI, test_user: MockUser) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_assistant_service = workbench_model.NewAssistantServiceRegistration(
            assistant_service_id="test-assistant-service-id",
            name="test-assistant-service-nam",
            description="test description",
        )
        http_response = client.post(
            "/assistant-service-registrations",
            json=new_assistant_service.model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        update_assistant_service = workbench_model.UpdateAssistantServiceRegistration(
            name="updated-assistant-service",
            description="updated description",
        )
        http_response = client.patch(
            f"/assistant-service-registrations/{assistant_service.assistant_service_id}",
            json=update_assistant_service.model_dump(mode="json", exclude_unset=True),
        )
        assert httpx.codes.is_success(http_response.status_code)

        updated_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert updated_assistant_service.name == update_assistant_service.name
        assert updated_assistant_service.description == update_assistant_service.description
        assert updated_assistant_service.api_key is None


def test_create_assistant_service_registration_reset_api_key(workbench_service: FastAPI, test_user: MockUser) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_assistant_service = workbench_model.NewAssistantServiceRegistration(
            assistant_service_id="test-assistant-service-id",
            name="test-assistant-service-name",
            description="test description",
        )

        http_response = client.post(
            "/assistant-service-registrations",
            json=new_assistant_service.model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        created_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert created_assistant_service.api_key is not None

        http_response = client.post(
            f"/assistant-service-registrations/{created_assistant_service.assistant_service_id}/api-key",
        )
        assert httpx.codes.is_success(http_response.status_code)

        reset_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert reset_assistant_service.api_key is not None
        # NOTE: the api key will not change because the test ApiKeyStore is used


def test_create_delete_assistant_service_registration(workbench_service: FastAPI, test_user: MockUser) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_assistant_service = workbench_model.NewAssistantServiceRegistration(
            assistant_service_id="test-assistant-service-id",
            name="test-assistant-service-name",
            description="test description",
        )

        http_response = client.post(
            "/assistant-service-registrations",
            json=new_assistant_service.model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)

        created_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        http_response = client.delete(
            f"/assistant-service-registrations/{created_assistant_service.assistant_service_id}",
        )
        assert httpx.codes.is_success(http_response.status_code)

        http_response = client.get("/assistant-service-registrations")
        assert httpx.codes.is_success(http_response.status_code)

        retrieved_assistant_services = workbench_model.AssistantServiceRegistrationList.model_validate(
            http_response.json(),
        )

        assert len(retrieved_assistant_services.assistant_service_registrations) == 0


async def test_create_update_assistant_service_registration_url(
    workbench_service: FastAPI,
    test_user: MockUser,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    # force continuous checks for assistants going offline
    monkeypatch.setattr(
        semantic_workbench_service.settings.service,
        "assistant_service_online_check_interval_seconds",
        0.1,
    )

    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_assistant_service = workbench_model.NewAssistantServiceRegistration(
            assistant_service_id="test-assistant-service-id",
            name="test-assistant-service-nam",
            description="test description",
        )
        http_response = client.post(
            "/assistant-service-registrations",
            json=new_assistant_service.model_dump(mode="json"),
        )
        assert httpx.codes.is_success(http_response.status_code)
        assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        update_assistant_service = workbench_model.UpdateAssistantServiceRegistrationUrl(
            name="updated-assistant-service",
            description="updated description",
            url=HttpUrl("https://example.com"),
            online_expires_in_seconds=0,
        )
        http_response = client.put(
            f"/assistant-service-registrations/{assistant_service.assistant_service_id}",
            json=update_assistant_service.model_dump(mode="json", exclude_unset=True),
            headers=workbench_service_client.AssistantServiceRequestHeaders(
                assistant_service_id=assistant_service.assistant_service_id,
                api_key=assistant_service.api_key or "",
            ).to_headers(),
        )
        assert httpx.codes.is_success(http_response.status_code)

        updated_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert updated_assistant_service.api_key is None
        assert updated_assistant_service.assistant_service_url == str(update_assistant_service.url)
        assert updated_assistant_service.assistant_service_online is True

        # give time for the assistant service online check to run
        await asyncio.sleep(1.0)

        # verify that when the url expires, the assistant service is reported as offline
        http_response = client.get(f"/assistant-service-registrations/{assistant_service.assistant_service_id}")
        assert httpx.codes.is_success(http_response.status_code)
        retrieved_assistant_service = workbench_model.AssistantServiceRegistration.model_validate(http_response.json())

        assert retrieved_assistant_service.assistant_service_online is False


@pytest.mark.parametrize(
    ("permission"),
    [
        workbench_model.ConversationPermission.read,
        workbench_model.ConversationPermission.read_write,
    ],
)
async def test_create_redeem_delete_conversation_share(
    workbench_service: FastAPI,
    test_user: MockUser,
    test_user_2: MockUser,
    permission: workbench_model.ConversationPermission,
) -> None:
    with TestClient(app=workbench_service, headers=test_user.authorization_headers) as client:
        new_conversation = workbench_model.NewConversation(title="test-conversation")

        http_response = client.post("/conversations", json=new_conversation.model_dump(mode="json"))
        assert httpx.codes.is_success(http_response.status_code)

        created_conversation = workbench_model.Conversation.model_validate(http_response.json())

        new_conversation_share = workbench_model.NewConversationShare(
            conversation_id=created_conversation.id,
            label="share",
            conversation_permission=permission,
        )

        http_response = client.post("/conversation-shares", json=new_conversation_share.model_dump(mode="json"))
        assert httpx.codes.is_success(http_response.status_code)

        created_conversation_share = workbench_model.ConversationShare.model_validate(http_response.json())

        assert created_conversation_share.conversation_id == created_conversation.id
        assert created_conversation_share.conversation_permission == permission

        http_response = client.get(f"/conversation-shares/{created_conversation_share.id}")
        assert httpx.codes.is_success(http_response.status_code)

        retrieved_conversation_share = workbench_model.ConversationShare.model_validate(http_response.json())

        assert retrieved_conversation_share == created_conversation_share

        http_response = client.get("/conversation-shares")
        assert httpx.codes.is_success(http_response.status_code)

        retrieved_conversation_shares = workbench_model.ConversationShareList.model_validate(http_response.json())
        assert retrieved_conversation_shares.conversation_shares == [created_conversation_share]

        # redeem the conversation share with user-2
        http_response = client.post(
            f"/conversation-shares/{created_conversation_share.id}/redemptions",
            headers=test_user_2.authorization_headers,
        )
        assert httpx.codes.is_success(http_response.status_code)

        redemption = workbench_model.ConversationShareRedemption.model_validate(http_response.json())
        assert redemption.redeemed_by_user.id == test_user_2.id
        assert redemption.conversation_permission == permission

        # ensure user-2 can retrieve the conversation
        http_response = client.get(
            f"/conversations/{created_conversation.id}", headers=test_user_2.authorization_headers
        )
        assert httpx.codes.is_success(http_response.status_code)

        retrieved_conversation = workbench_model.Conversation.model_validate(http_response.json())
        assert retrieved_conversation.id == created_conversation.id

        # ensure user-2 can retrieve their participant
        http_response = client.get(
            f"/conversations/{created_conversation.id}/participants/me", headers=test_user_2.authorization_headers
        )
        participant = workbench_model.ConversationParticipant.model_validate(http_response.json())
        assert participant.role == workbench_model.ParticipantRole.user
        assert participant.conversation_id == created_conversation.id
        assert participant.active_participant is True
        assert participant.conversation_permission == permission

        # delete the conversation share
        http_response = client.delete(f"/conversation-shares/{created_conversation_share.id}")
        assert httpx.codes.is_success(http_response.status_code)

        # ensure user-2 can still retrieve the conversation
        http_response = client.get(
            f"/conversations/{created_conversation.id}", headers=test_user_2.authorization_headers
        )
        assert httpx.codes.is_success(http_response.status_code)

        # ensure user 2 can no longer redeem the conversation share
        http_response = client.post(
            "/conversation-shares/redeem",
            headers=test_user_2.authorization_headers,
        )
        assert httpx.codes.is_client_error(http_response.status_code)

```
Page 97/114FirstPrevNextLast