#
tokens: 48708/50000 3/1784 files (page 98/145)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 98 of 145. Use http://codebase.md/microsoft/semanticworkbench?lines=true&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

--------------------------------------------------------------------------------
/assistants/knowledge-transfer-assistant/assistant/assistant.py:
--------------------------------------------------------------------------------

```python
  1 | # Copyright (c) Microsoft. All rights reserved.
  2 | 
  3 | # Project Assistant implementation
  4 | 
  5 | import asyncio
  6 | import pathlib
  7 | from typing import Any
  8 | 
  9 | from assistant_extensions import attachments, dashboard_card, navigator
 10 | from content_safety.evaluators import CombinedContentSafetyEvaluator
 11 | from semantic_workbench_api_model import workbench_model
 12 | from semantic_workbench_api_model.workbench_model import (
 13 |     AssistantStateEvent,
 14 |     ConversationEvent,
 15 |     ConversationMessage,
 16 |     MessageType,
 17 |     NewConversationMessage,
 18 |     ParticipantRole,
 19 |     UpdateParticipant,
 20 | )
 21 | from semantic_workbench_assistant.assistant_app import (
 22 |     AssistantApp,
 23 |     AssistantCapability,
 24 |     ContentSafety,
 25 |     ContentSafetyEvaluator,
 26 |     ConversationContext,
 27 | )
 28 | 
 29 | from .agentic.team_welcome import generate_team_welcome_message
 30 | from .common import detect_assistant_role, detect_conversation_type, get_shared_conversation_id, ConversationType
 31 | from .config import assistant_config
 32 | from .conversation_share_link import ConversationKnowledgePackageManager
 33 | from .data import InspectorTab, LogEntryType
 34 | from .files import ShareFilesManager
 35 | from .logging import logger
 36 | from .domain import KnowledgeTransferManager
 37 | from .notifications import Notifications
 38 | from .respond import respond_to_conversation
 39 | from .ui_tabs import BriefInspector, LearningInspector, SharingInspector, DebugInspector
 40 | from .storage import ShareStorage
 41 | from .storage_models import ConversationRole
 42 | from .utils import (
 43 |     DEFAULT_TEMPLATE_ID,
 44 |     load_text_include,
 45 | )
 46 | 
 47 | service_id = "knowledge-transfer-assistant.made-exploration"
 48 | service_name = "Knowledge Transfer Assistant"
 49 | service_description = "A mediator assistant that facilitates sharing knowledge between parties."
 50 | 
 51 | 
 52 | async def content_evaluator_factory(
 53 |     context: ConversationContext,
 54 | ) -> ContentSafetyEvaluator:
 55 |     config = await assistant_config.get(context.assistant)
 56 |     return CombinedContentSafetyEvaluator(config.content_safety_config)
 57 | 
 58 | 
 59 | content_safety = ContentSafety(content_evaluator_factory)
 60 | 
 61 | assistant = AssistantApp(
 62 |     assistant_service_id=service_id,
 63 |     assistant_service_name=service_name,
 64 |     assistant_service_description=service_description,
 65 |     config_provider=assistant_config.provider,
 66 |     content_interceptor=content_safety,
 67 |     capabilities={AssistantCapability.supports_conversation_files},
 68 |     inspector_state_providers={
 69 |         InspectorTab.BRIEF: BriefInspector(assistant_config),
 70 |         InspectorTab.LEARNING: LearningInspector(assistant_config),
 71 |         InspectorTab.SHARING: SharingInspector(assistant_config),
 72 |         InspectorTab.DEBUG: DebugInspector(assistant_config),
 73 |     },
 74 |     assistant_service_metadata={
 75 |         **dashboard_card.metadata(
 76 |             dashboard_card.TemplateConfig(
 77 |                 enabled=True,
 78 |                 template_id=DEFAULT_TEMPLATE_ID,
 79 |                 background_color="rgb(198, 177, 222)",
 80 |                 icon=dashboard_card.image_to_url(
 81 |                     pathlib.Path(__file__).parent / "assets" / "icon-knowledge-transfer.svg", "image/svg+xml"
 82 |                 ),
 83 |                 card_content=dashboard_card.CardContent(
 84 |                     content_type="text/markdown",
 85 |                     content=load_text_include("card_content.md"),
 86 |                 ),
 87 |             ),
 88 |         ),
 89 |         **navigator.metadata_for_assistant_navigator({
 90 |             "default": load_text_include("assistant_info.md"),
 91 |         }),
 92 |     },
 93 | )
 94 | 
 95 | attachments_extension = attachments.AttachmentsExtension(assistant)
 96 | 
 97 | app = assistant.fastapi_app()
 98 | 
 99 | 
100 | @assistant.events.conversation.on_created_including_mine
101 | async def on_conversation_created(context: ConversationContext) -> None:
102 |     """
103 |     The assistant manages three types of conversations:
104 |     1. Coordinator Conversation: The main conversation used by the knowledge coordinator
105 |     2. Shareable Team Conversation: A template conversation that has a share URL and is never directly used
106 |     3. Team Conversation(s): Individual conversations for team members created when they redeem the share URL
107 |     """
108 | 
109 |     conversation = await context.get_conversation()
110 |     conversation_metadata = conversation.metadata or {}
111 |     share_id = conversation_metadata.get("share_id")
112 | 
113 |     config = await assistant_config.get(context.assistant)
114 |     conversation_type = detect_conversation_type(conversation)
115 | 
116 |     match conversation_type:
117 |         case ConversationType.SHAREABLE_TEMPLATE:
118 |             # Associate the shareable template with a share ID
119 |             if not share_id:
120 |                 logger.error("No share ID found for shareable team conversation.")
121 |                 return
122 |             await ConversationKnowledgePackageManager.associate_conversation_with_share(context, share_id)
123 |             return
124 | 
125 |         case ConversationType.TEAM:
126 |             if not share_id:
127 |                 logger.error("No share ID found for team conversation.")
128 |                 return
129 | 
130 |             # I'd put status messages here, but the attachment's extension is causing race conditions.
131 |             await context.send_messages(
132 |                 NewConversationMessage(
133 |                     content="Hold on a second while I set up your space...",
134 |                     message_type=MessageType.chat,
135 |                 )
136 |             )
137 | 
138 |             await ConversationKnowledgePackageManager.associate_conversation_with_share(context, share_id)
139 |             await ConversationKnowledgePackageManager.set_conversation_role(context, share_id, ConversationRole.TEAM)
140 |             await ShareFilesManager.synchronize_files_to_team_conversation(context=context, share_id=share_id)
141 | 
142 |             welcome_message, debug = await generate_team_welcome_message(context)
143 |             await context.send_messages(
144 |                 NewConversationMessage(
145 |                     content=welcome_message,
146 |                     message_type=MessageType.chat,
147 |                     metadata={
148 |                         "generated_content": True,
149 |                         "debug": debug,
150 |                     },
151 |                 )
152 |             )
153 | 
154 |             # Pop open the inspector panel.
155 |             await context.send_conversation_state_event(
156 |                 AssistantStateEvent(
157 |                     state_id="brief",
158 |                     event="focus",
159 |                     state=None,
160 |                 )
161 |             )
162 | 
163 |             return
164 | 
165 |         case ConversationType.COORDINATOR:
166 |             try:
167 |                 # In the beginning, we created a share...
168 |                 share_id = await KnowledgeTransferManager.create_share(context)
169 | 
170 |                 # And it was good. So we then created a sharable conversation that we use as a template.
171 |                 share_url = await KnowledgeTransferManager.create_shareable_team_conversation(
172 |                     context=context, share_id=share_id
173 |                 )
174 | 
175 |                 welcome_message = config.coordinator_config.welcome_message.format(
176 |                     share_url=share_url or "<Share URL generation failed>"
177 |                 )
178 | 
179 |             except Exception as e:
180 |                 welcome_message = f"I'm having trouble setting up your knowledge transfer. Please try again or contact support if the issue persists. {str(e)}"
181 | 
182 |             await context.send_messages(
183 |                 NewConversationMessage(
184 |                     content=welcome_message,
185 |                     message_type=MessageType.chat,
186 |                 )
187 |             )
188 | 
189 |             # Pop open the inspector panel.
190 |             await context.send_conversation_state_event(
191 |                 AssistantStateEvent(
192 |                     state_id="brief",
193 |                     event="focus",
194 |                     state=None,
195 |                 )
196 |             )
197 | 
198 | 
199 | @assistant.events.conversation.on_updated
200 | async def on_conversation_updated(context: ConversationContext) -> None:
201 |     """
202 |     Handle conversation updates (including title changes) and sync with shareable template.
203 |     """
204 |     try:
205 |         conversation = await context.get_conversation()
206 |         conversation_type = detect_conversation_type(conversation)
207 |         if conversation_type != ConversationType.COORDINATOR:
208 |             return
209 | 
210 |         shared_conversation_id = await get_shared_conversation_id(context)
211 |         if not shared_conversation_id:
212 |             return
213 | 
214 |         # Update the shareable template conversation's title if needed.
215 |         try:
216 |             target_context = context.for_conversation(shared_conversation_id)
217 |             target_conversation = await target_context.get_conversation()
218 |             if target_conversation.title != conversation.title:
219 |                 await target_context.update_conversation_title(conversation.title)
220 |                 logger.debug(
221 |                     f"Updated conversation {shared_conversation_id} title from '{target_conversation.title}' to '{conversation.title}'"
222 |                 )
223 |             else:
224 |                 logger.debug(f"Conversation {shared_conversation_id} title already matches: '{conversation.title}'")
225 |         except Exception as title_update_error:
226 |             logger.error(f"Error updating conversation {shared_conversation_id} title: {title_update_error}")
227 | 
228 |     except Exception as e:
229 |         logger.error(f"Error syncing conversation title: {e}")
230 | 
231 | 
232 | @assistant.events.conversation.message.chat.on_created
233 | async def on_message_created(
234 |     context: ConversationContext, event: ConversationEvent, message: ConversationMessage
235 | ) -> None:
236 |     await context.update_participant_me(UpdateParticipant(status="thinking..."))
237 | 
238 |     metadata: dict[str, Any] = {
239 |         "debug": {
240 |             "content_safety": event.data.get(content_safety.metadata_key, {}),
241 |         }
242 |     }
243 | 
244 |     try:
245 |         share_id = await KnowledgeTransferManager.get_share_id(context)
246 |         metadata["debug"]["share_id"] = share_id
247 | 
248 |         # If this is a Coordinator conversation, store the message for Team access
249 |         async with context.set_status("jotting..."):
250 |             role = await detect_assistant_role(context)
251 |             if role == ConversationRole.COORDINATOR and message.message_type == MessageType.chat:
252 |                 try:
253 |                     if share_id:
254 |                         # Get the sender's name
255 |                         sender_name = "Coordinator"
256 |                         if message.sender:
257 |                             participants = await context.get_participants()
258 |                             for participant in participants.participants:
259 |                                 if participant.id == message.sender.participant_id:
260 |                                     sender_name = participant.name
261 |                                     break
262 | 
263 |                         # Store the message for Team access
264 |                         ShareStorage.append_coordinator_message(
265 |                             share_id=share_id,
266 |                             message_id=str(message.id),
267 |                             content=message.content,
268 |                             sender_name=sender_name,
269 |                             is_assistant=message.sender.participant_role == ParticipantRole.assistant,
270 |                             timestamp=message.timestamp,
271 |                         )
272 | 
273 |                         # If this is the coordinator's first message, pop the share canvas
274 |                         messages = await context.get_messages()
275 |                         if len(messages.messages) == 2:
276 |                             await context.send_conversation_state_event(
277 |                                 AssistantStateEvent(
278 |                                     state_id="brief",
279 |                                     event="focus",
280 |                                     state=None,
281 |                                 )
282 |                             )
283 |                 except Exception as e:
284 |                     # Don't fail message handling if storage fails
285 |                     logger.exception(f"Error storing Coordinator message for Team access: {e}")
286 | 
287 |         async with context.set_status("pondering..."):
288 |             await respond_to_conversation(
289 |                 context,
290 |                 new_message=message,
291 |                 attachments_extension=attachments_extension,
292 |                 metadata=metadata,
293 |             )
294 | 
295 |         # If the message is from a Coordinator, update the whiteboard in the background
296 |         if role == ConversationRole.COORDINATOR and message.message_type == MessageType.chat:
297 |             asyncio.create_task(KnowledgeTransferManager.auto_update_knowledge_digest(context))
298 | 
299 |     except Exception as e:
300 |         logger.exception(f"Error handling message: {e}")
301 |         await context.send_messages(
302 |             NewConversationMessage(
303 |                 content=f"Error: {str(e)}",
304 |                 message_type=MessageType.notice,
305 |                 metadata={"generated_content": False, **metadata},
306 |             )
307 |         )
308 |     finally:
309 |         await context.update_participant_me(UpdateParticipant(status=None))
310 | 
311 | 
312 | @assistant.events.conversation.message.command.on_created
313 | async def on_command_created(
314 |     context: ConversationContext, event: ConversationEvent, message: ConversationMessage
315 | ) -> None:
316 |     if message.message_type != MessageType.command:
317 |         return
318 | 
319 |     await context.update_participant_me(UpdateParticipant(status="processing command..."))
320 |     try:
321 |         metadata = {"debug": {"content_safety": event.data.get(content_safety.metadata_key, {})}}
322 | 
323 |         # Respond to the conversation
324 |         await respond_to_conversation(
325 |             context,
326 |             new_message=message,
327 |             attachments_extension=attachments_extension,
328 |             metadata=metadata,
329 |         )
330 |     finally:
331 |         # update the participant status to indicate the assistant is done thinking
332 |         await context.update_participant_me(UpdateParticipant(status=None))
333 | 
334 | 
335 | @assistant.events.conversation.file.on_created
336 | async def on_file_created(
337 |     context: ConversationContext,
338 |     event: workbench_model.ConversationEvent,
339 |     file: workbench_model.File,
340 | ) -> None:
341 |     """
342 |     Handle when a file is created in the conversation.
343 | 
344 |     For Coordinator files:
345 |     1. Store a copy in share storage
346 |     2. Synchronize to all Team conversations
347 | 
348 |     For Team files:
349 |     1. Use as-is without copying to share storage
350 |     """
351 |     try:
352 |         share_id = await KnowledgeTransferManager.get_share_id(context)
353 |         if not share_id or not file.filename:
354 |             logger.warning(f"No share ID found or missing filename: share_id={share_id}, filename={file.filename}")
355 |             return
356 | 
357 |         role = await detect_assistant_role(context)
358 | 
359 |         # Process based on role
360 |         if role == ConversationRole.COORDINATOR:
361 |             # For Coordinator files:
362 |             # 1. Store in share storage (marked as coordinator file)
363 | 
364 |             success = await ShareFilesManager.copy_file_to_share_storage(
365 |                 context=context,
366 |                 share_id=share_id,
367 |                 file=file,
368 |                 is_coordinator_file=True,
369 |             )
370 | 
371 |             if not success:
372 |                 logger.error(f"Failed to copy file to share storage: {file.filename}")
373 |                 return
374 | 
375 |             # 2. Synchronize to all Team conversations
376 |             # Get all Team conversations
377 |             team_conversations = await ShareFilesManager.get_team_conversations(context, share_id)
378 | 
379 |             if team_conversations:
380 |                 for team_conv_id in team_conversations:
381 |                     await ShareFilesManager.copy_file_to_conversation(
382 |                         context=context,
383 |                         share_id=share_id,
384 |                         filename=file.filename,
385 |                         target_conversation_id=team_conv_id,
386 |                     )
387 | 
388 |             # 3. Update all UIs but don't send notifications to reduce noise
389 |             await Notifications.notify_all_state_update(context, share_id, [InspectorTab.DEBUG])
390 |         # Team files don't need special handling as they're already in the conversation
391 | 
392 |         # Log file creation to knowledge transfer log for all files
393 |         await ShareStorage.log_share_event(
394 |             context=context,
395 |             share_id=share_id,
396 |             entry_type="file_shared",
397 |             message=f"File shared: {file.filename}",
398 |             metadata={
399 |                 "file_id": getattr(file, "id", ""),
400 |                 "filename": file.filename,
401 |                 "is_coordinator_file": role.value == "coordinator",
402 |             },
403 |         )
404 | 
405 |     except Exception as e:
406 |         logger.exception(f"Error handling file creation: {e}")
407 | 
408 | 
409 | @assistant.events.conversation.file.on_updated
410 | async def on_file_updated(
411 |     context: ConversationContext,
412 |     event: workbench_model.ConversationEvent,
413 |     file: workbench_model.File,
414 | ) -> None:
415 |     try:
416 |         # Get share ID
417 |         share_id = await KnowledgeTransferManager.get_share_id(context)
418 |         if not share_id or not file.filename:
419 |             return
420 | 
421 |         role = await detect_assistant_role(context)
422 |         if role == ConversationRole.COORDINATOR:
423 |             # For Coordinator files:
424 |             # 1. Update in share storage
425 |             success = await ShareFilesManager.copy_file_to_share_storage(
426 |                 context=context,
427 |                 share_id=share_id,
428 |                 file=file,
429 |                 is_coordinator_file=True,
430 |             )
431 | 
432 |             if not success:
433 |                 logger.error(f"Failed to update file in share storage: {file.filename}")
434 |                 return
435 | 
436 |             team_conversations = await ShareFilesManager.get_team_conversations(context, share_id)
437 |             for team_conv_id in team_conversations:
438 |                 await ShareFilesManager.copy_file_to_conversation(
439 |                     context=context,
440 |                     share_id=share_id,
441 |                     filename=file.filename,
442 |                     target_conversation_id=team_conv_id,
443 |                 )
444 | 
445 |             # 3. Update all UIs but don't send notifications to reduce noise
446 |             await Notifications.notify_all_state_update(context, share_id, [InspectorTab.DEBUG])
447 | 
448 |         await ShareStorage.log_share_event(
449 |             context=context,
450 |             share_id=share_id,
451 |             entry_type="file_shared",
452 |             message=f"File updated: {file.filename}",
453 |             metadata={
454 |                 "file_id": getattr(file, "id", ""),
455 |                 "filename": file.filename,
456 |                 "is_coordinator_file": role.value == "coordinator",
457 |             },
458 |         )
459 | 
460 |     except Exception as e:
461 |         logger.exception(f"Error handling file update: {e}")
462 | 
463 | 
464 | @assistant.events.conversation.file.on_deleted
465 | async def on_file_deleted(
466 |     context: ConversationContext,
467 |     event: workbench_model.ConversationEvent,
468 |     file: workbench_model.File,
469 | ) -> None:
470 |     try:
471 |         # Get share ID
472 |         share_id = await KnowledgeTransferManager.get_share_id(context)
473 |         if not share_id or not file.filename:
474 |             return
475 | 
476 |         role = await detect_assistant_role(context)
477 |         if role == ConversationRole.COORDINATOR:
478 |             # For Coordinator files:
479 |             # 1. Delete from share storage
480 |             success = await ShareFilesManager.delete_file_from_knowledge_share_storage(
481 |                 context=context, share_id=share_id, filename=file.filename
482 |             )
483 | 
484 |             if not success:
485 |                 logger.error(f"Failed to delete file from share storage: {file.filename}")
486 | 
487 |             # 2. Update all UIs about the deletion but don't send notifications to reduce noise
488 |             await Notifications.notify_all_state_update(context, share_id, [InspectorTab.DEBUG])
489 |         # Team files don't need special handling
490 | 
491 |         await ShareStorage.log_share_event(
492 |             context=context,
493 |             share_id=share_id,
494 |             entry_type="file_deleted",
495 |             message=f"File deleted: {file.filename}",
496 |             metadata={
497 |                 "file_id": getattr(file, "id", ""),
498 |                 "filename": file.filename,
499 |                 "is_coordinator_file": role.value == "coordinator",
500 |             },
501 |         )
502 | 
503 |     except Exception as e:
504 |         logger.exception(f"Error handling file deletion: {e}")
505 | 
506 | 
507 | @assistant.events.conversation.participant.on_created
508 | async def on_participant_joined(
509 |     context: ConversationContext,
510 |     event: ConversationEvent,
511 |     participant: workbench_model.ConversationParticipant,
512 | ) -> None:
513 |     try:
514 |         if participant.id == context.assistant.id:
515 |             return
516 | 
517 |         # Open the Brief tab (state inspector).
518 |         await context.send_conversation_state_event(
519 |             AssistantStateEvent(
520 |                 state_id="brief",
521 |                 event="focus",
522 |                 state=None,
523 |             )
524 |         )
525 | 
526 |         role = await detect_assistant_role(context)
527 |         if role != ConversationRole.TEAM:
528 |             return
529 | 
530 |         share_id = await ConversationKnowledgePackageManager.get_associated_share_id(context)
531 |         if not share_id:
532 |             return
533 | 
534 |         await ShareFilesManager.synchronize_files_to_team_conversation(context=context, share_id=share_id)
535 | 
536 |         await ShareStorage.log_share_event(
537 |             context=context,
538 |             share_id=share_id,
539 |             entry_type=LogEntryType.PARTICIPANT_JOINED,
540 |             message=f"Participant joined: {participant.name}",
541 |             metadata={
542 |                 "participant_id": participant.id,
543 |                 "participant_name": participant.name,
544 |                 "conversation_id": str(context.id),
545 |             },
546 |         )
547 | 
548 |     except Exception as e:
549 |         logger.exception(f"Error handling participant join event: {e}")
550 | 
```

--------------------------------------------------------------------------------
/assistants/explorer-assistant/assistant/response/response.py:
--------------------------------------------------------------------------------

```python
  1 | # Copyright (c) Microsoft. All rights reserved.
  2 | 
  3 | # Prospector Assistant
  4 | #
  5 | # This assistant helps you mine ideas from artifacts.
  6 | #
  7 | 
  8 | import logging
  9 | import re
 10 | import time
 11 | from typing import Any, Awaitable, Callable, Sequence
 12 | 
 13 | import deepmerge
 14 | from assistant_extensions.artifacts import ArtifactsExtension
 15 | from assistant_extensions.attachments import AttachmentsExtension
 16 | from llm_client.model import CompletionMessage
 17 | from semantic_workbench_api_model.workbench_model import (
 18 |     ConversationMessage,
 19 |     ConversationParticipant,
 20 |     MessageType,
 21 |     NewConversationMessage,
 22 | )
 23 | from semantic_workbench_assistant.assistant_app import (
 24 |     ConversationContext,
 25 | )
 26 | 
 27 | from ..config import AssistantConfigModel
 28 | from .model import NumberTokensResult, ResponseProvider
 29 | from .response_anthropic import AnthropicResponseProvider
 30 | from .response_openai import OpenAIResponseProvider
 31 | 
 32 | logger = logging.getLogger(__name__)
 33 | 
 34 | 
 35 | #
 36 | # region Response
 37 | #
 38 | 
 39 | 
 40 | # demonstrates how to respond to a conversation message using the OpenAI API.
 41 | async def respond_to_conversation(
 42 |     artifacts_extension: ArtifactsExtension,
 43 |     attachments_extension: AttachmentsExtension,
 44 |     context: ConversationContext,
 45 |     config: AssistantConfigModel,
 46 |     metadata: dict[str, Any] = {},
 47 | ) -> None:
 48 |     """
 49 |     Respond to a conversation message.
 50 | 
 51 |     This method uses the OpenAI API to generate a response to the message.
 52 | 
 53 |     It includes any attachments as individual system messages before the chat history, along with references
 54 |     to the attachments in the point in the conversation where they were mentioned. This allows the model to
 55 |     consider the full contents of the attachments separate from the conversation, but with the context of
 56 |     where they were mentioned and any relevant surrounding context such as how to interpret the attachment
 57 |     or why it was shared or what to do with it.
 58 |     """
 59 | 
 60 |     response_provider = (
 61 |         AnthropicResponseProvider(assistant_config=config, anthropic_client_config=config.ai_client_config)
 62 |         if config.ai_client_config.ai_service_type == "anthropic"
 63 |         else OpenAIResponseProvider(
 64 |             artifacts_extension=artifacts_extension,
 65 |             conversation_context=context,
 66 |             assistant_config=config,
 67 |             openai_client_config=config.ai_client_config,
 68 |         )
 69 |     )
 70 | 
 71 |     request_config = config.ai_client_config.request_config
 72 | 
 73 |     # define the metadata key for any metadata created within this method
 74 |     method_metadata_key = "respond_to_conversation"
 75 | 
 76 |     # track the start time of the response generation
 77 |     response_start_time = time.time()
 78 | 
 79 |     # get the list of conversation participants
 80 |     participants_response = await context.get_participants(include_inactive=True)
 81 | 
 82 |     # establish a token to be used by the AI model to indicate no response
 83 |     silence_token = "{{SILENCE}}"
 84 | 
 85 |     system_message_content = f'{config.instruction_prompt}\n\nYour name is "{context.assistant.name}".'
 86 |     if len(participants_response.participants) > 2:
 87 |         system_message_content += (
 88 |             "\n\n"
 89 |             f"There are {len(participants_response.participants)} participants in the conversation,"
 90 |             " including you as the assistant and the following users:"
 91 |             + ",".join([
 92 |                 f' "{participant.name}"'
 93 |                 for participant in participants_response.participants
 94 |                 if participant.id != context.assistant.id
 95 |             ])
 96 |             + "\n\nYou do not need to respond to every message. Do not respond if the last thing said was a closing"
 97 |             " statement such as 'bye' or 'goodbye', or just a general acknowledgement like 'ok' or 'thanks'. Do not"
 98 |             f' respond as another user in the conversation, only as "{context.assistant.name}".'
 99 |             " Sometimes the other users need to talk amongst themselves and that is ok. If the conversation seems to"
100 |             f' be directed at you or the general audience, go ahead and respond.\n\nSay "{silence_token}" to skip'
101 |             " your turn."
102 |         )
103 | 
104 |     # add the artifact agent instruction prompt to the system message content
105 |     if config.extensions_config.artifacts.enabled:
106 |         system_message_content += f"\n\n{config.extensions_config.artifacts.instruction_prompt}"
107 | 
108 |     # add the guardrails prompt to the system message content
109 |     system_message_content += f"\n\n{config.guardrails_prompt}"
110 | 
111 |     # initialize the completion messages with the system message
112 |     completion_messages: list[CompletionMessage] = [
113 |         CompletionMessage(
114 |             role="system",
115 |             content=system_message_content,
116 |         )
117 |     ]
118 | 
119 |     token_count = 0
120 | 
121 |     # calculate the token count for the messages so far
122 |     result = await _num_tokens_from_messages(
123 |         context=context,
124 |         response_provider=response_provider,
125 |         messages=completion_messages,
126 |         model=request_config.model,
127 |         metadata=metadata,
128 |         metadata_key="system_message",
129 |     )
130 |     if result is not None:
131 |         token_count += result.count
132 |     else:
133 |         return
134 | 
135 |     # generate the attachment messages from the attachment agent
136 |     attachment_messages = await attachments_extension.get_completion_messages_for_attachments(
137 |         context,
138 |         config=config.extensions_config.attachments,
139 |     )
140 |     result = await _num_tokens_from_messages(
141 |         context=context,
142 |         response_provider=response_provider,
143 |         messages=attachment_messages,
144 |         model=request_config.model,
145 |         metadata=metadata,
146 |         metadata_key="attachment_messages",
147 |     )
148 |     if result is not None:
149 |         token_count += result.count
150 |     else:
151 |         return
152 | 
153 |     # calculate the total available tokens for the response generation
154 |     available_tokens = request_config.max_tokens - request_config.response_tokens
155 | 
156 |     history_messages = await _get_history_messages(
157 |         response_provider=response_provider,
158 |         context=context,
159 |         participants=participants_response.participants,
160 |         converter=_conversation_message_to_completion_messages,
161 |         model=request_config.model,
162 |         token_limit=available_tokens - token_count,
163 |     )
164 | 
165 |     # add the attachment messages to the completion messages, either inline or as separate messages
166 |     if config.use_inline_attachments:
167 |         # inject the attachment messages inline into the history messages
168 |         history_messages = _inject_attachments_inline(history_messages, attachment_messages)
169 |     else:
170 |         # add the attachment messages to the completion messages before the history messages
171 |         completion_messages.extend(attachment_messages)
172 | 
173 |     # add the history messages to the completion messages
174 |     completion_messages.extend(history_messages)
175 | 
176 |     result = await _num_tokens_from_messages(
177 |         context=context,
178 |         response_provider=response_provider,
179 |         messages=completion_messages,
180 |         model=request_config.model,
181 |         metadata=metadata,
182 |         metadata_key=method_metadata_key,
183 |     )
184 |     if result is not None:
185 |         estimated_token_count = result.count
186 |         if estimated_token_count > request_config.max_tokens:
187 |             await context.send_messages(
188 |                 NewConversationMessage(
189 |                     content=(
190 |                         f"You've exceeded the token limit of {request_config.max_tokens} in this conversation ({estimated_token_count})."
191 |                         " This assistant does not support recovery from this state."
192 |                         " Please start a new conversation and let us know you ran into this."
193 |                     ),
194 |                     message_type=MessageType.chat,
195 |                 )
196 |             )
197 |             return
198 |     else:
199 |         return
200 | 
201 |     # set default response message type
202 |     message_type = MessageType.chat
203 | 
204 |     # generate a response from the AI model
205 |     response_result = await response_provider.get_response(
206 |         messages=completion_messages,
207 |         metadata_key=method_metadata_key,
208 |     )
209 |     content = response_result.content
210 |     message_type = response_result.message_type
211 |     completion_total_tokens = response_result.completion_total_tokens
212 | 
213 |     deepmerge.always_merger.merge(metadata, response_result.metadata)
214 | 
215 |     # create the footer items for the response
216 |     footer_items = []
217 | 
218 |     # add the token usage message to the footer items
219 |     if completion_total_tokens > 0:
220 |         footer_items.append(_get_token_usage_message(request_config.max_tokens, completion_total_tokens))
221 | 
222 |     # track the end time of the response generation
223 |     response_end_time = time.time()
224 |     response_duration = response_end_time - response_start_time
225 | 
226 |     # add the response duration to the footer items
227 |     footer_items.append(_get_response_duration_message(response_duration))
228 | 
229 |     # update the metadata with the footer items
230 |     deepmerge.always_merger.merge(
231 |         metadata,
232 |         {
233 |             "footer_items": footer_items,
234 |         },
235 |     )
236 | 
237 |     if content:
238 |         # strip out the username from the response
239 |         if content.startswith("["):
240 |             content = re.sub(r"\[.*\]:\s", "", content)
241 | 
242 |         # model sometimes puts extra spaces in the response, so remove them
243 |         # when checking for the silence token
244 |         if content.replace(" ", "") == silence_token:
245 |             # if debug output is enabled, notify the conversation that the assistant chose to remain silent
246 |             if config.enable_debug_output:
247 |                 # add debug metadata to indicate the assistant chose to remain silent
248 |                 deepmerge.always_merger.merge(
249 |                     metadata,
250 |                     {
251 |                         "debug": {
252 |                             method_metadata_key: {
253 |                                 "silence_token": True,
254 |                             },
255 |                         },
256 |                         "attribution": "debug output",
257 |                         "generated_content": False,
258 |                     },
259 |                 )
260 |                 # send a notice message to the conversation
261 |                 await context.send_messages(
262 |                     NewConversationMessage(
263 |                         message_type=MessageType.notice,
264 |                         content="[assistant chose to remain silent]",
265 |                         metadata=metadata,
266 |                     )
267 |                 )
268 |             return
269 | 
270 |         # override message type if content starts with /
271 |         if content.startswith("/"):
272 |             message_type = MessageType.command_response
273 | 
274 |     # send the response to the conversation
275 |     await context.send_messages(
276 |         NewConversationMessage(
277 |             content=content or "[no response from openai]",
278 |             message_type=message_type,
279 |             metadata=metadata,
280 |         )
281 |     )
282 | 
283 |     # check the token usage and send a warning if it is high
284 |     if completion_total_tokens and config.high_token_usage_warning.enabled:
285 |         # calculate the token count for the warning threshold
286 |         token_count_for_warning = request_config.max_tokens * (config.high_token_usage_warning.threshold / 100)
287 | 
288 |         # check if the completion total tokens exceed the warning threshold
289 |         if completion_total_tokens > token_count_for_warning:
290 |             content = f"{config.high_token_usage_warning.message}\n\nTotal tokens used: {completion_total_tokens}"
291 | 
292 |             # send a notice message to the conversation that the token usage is high
293 |             await context.send_messages(
294 |                 NewConversationMessage(
295 |                     content=content,
296 |                     message_type=MessageType.notice,
297 |                     metadata={
298 |                         "debug": {
299 |                             "high_token_usage_warning": {
300 |                                 "completion_total_tokens": completion_total_tokens,
301 |                                 "threshold": config.high_token_usage_warning.threshold,
302 |                                 "token_count_for_warning": token_count_for_warning,
303 |                             }
304 |                         },
305 |                         "attribution": "system",
306 |                     },
307 |                 )
308 |             )
309 | 
310 | 
311 | # endregion
312 | 
313 | 
314 | #
315 | # region Helpers
316 | #
317 | 
318 | # TODO: move to a common module, such as either the openai_client or attachment module for easy re-use in other assistants
319 | 
320 | 
321 | async def _num_tokens_from_messages(
322 |     context: ConversationContext,
323 |     response_provider: ResponseProvider,
324 |     messages: Sequence[CompletionMessage],
325 |     model: str,
326 |     metadata: dict[str, Any],
327 |     metadata_key: str,
328 | ) -> NumberTokensResult | None:
329 |     """
330 |     Calculate the number of tokens required to generate the completion messages.
331 |     """
332 |     try:
333 |         return await response_provider.num_tokens_from_messages(
334 |             messages=messages, model=model, metadata_key=metadata_key
335 |         )
336 |     except Exception as e:
337 |         logger.exception(f"exception occurred calculating token count: {e}")
338 |         deepmerge.always_merger.merge(
339 |             metadata,
340 |             {
341 |                 "debug": {
342 |                     metadata_key: {
343 |                         "num_tokens_from_messages": {
344 |                             "request": {
345 |                                 "messages": messages,
346 |                                 "model": model,
347 |                             },
348 |                             "error": str(e),
349 |                         },
350 |                     },
351 |                 }
352 |             },
353 |         )
354 |         await context.send_messages(
355 |             NewConversationMessage(
356 |                 content="An error occurred while calculating the token count for the messages.",
357 |                 message_type=MessageType.notice,
358 |                 metadata=metadata,
359 |             )
360 |         )
361 | 
362 | 
363 | async def _conversation_message_to_completion_messages(
364 |     context: ConversationContext, message: ConversationMessage, participants: list[ConversationParticipant]
365 | ) -> list[CompletionMessage]:
366 |     """
367 |     Convert a conversation message to a list of completion messages.
368 |     """
369 | 
370 |     # some messages may have multiple parts, such as a text message with an attachment
371 |     completion_messages: list[CompletionMessage] = []
372 | 
373 |     # add the message to the completion messages, treating any message from a source other than the assistant
374 |     # as a user message
375 |     if message.sender.participant_id == context.assistant.id:
376 |         completion_messages.append(CompletionMessage(role="assistant", content=_format_message(message, participants)))
377 | 
378 |     else:
379 |         # add the user message to the completion messages
380 |         completion_messages.append(CompletionMessage(role="user", content=_format_message(message, participants)))
381 | 
382 |         if message.filenames and len(message.filenames) > 0:
383 |             # add a system message to indicate the attachments
384 |             completion_messages.append(
385 |                 CompletionMessage(role="system", content=f"Attachment(s): {', '.join(message.filenames)}")
386 |             )
387 | 
388 |     return completion_messages
389 | 
390 | 
391 | async def _get_history_messages(
392 |     response_provider: ResponseProvider,
393 |     context: ConversationContext,
394 |     participants: list[ConversationParticipant],
395 |     converter: Callable[
396 |         [ConversationContext, ConversationMessage, list[ConversationParticipant]],
397 |         Awaitable[list[CompletionMessage]],
398 |     ],
399 |     model: str,
400 |     token_limit: int | None = None,
401 | ) -> list[CompletionMessage]:
402 |     """
403 |     Get all messages in the conversation, formatted for use in a completion.
404 |     """
405 | 
406 |     # each call to get_messages will return a maximum of 100 messages
407 |     # so we need to loop until all messages are retrieved
408 |     # if token_limit is provided, we will stop when the token limit is reached
409 | 
410 |     history = []
411 |     token_count = 0
412 |     before_message_id = None
413 | 
414 |     while True:
415 |         # get the next batch of messages
416 |         messages_response = await context.get_messages(limit=100, before=before_message_id)
417 |         messages_list = messages_response.messages
418 | 
419 |         # if there are no more messages, break the loop
420 |         if not messages_list or messages_list.count == 0:
421 |             break
422 | 
423 |         # set the before_message_id for the next batch of messages
424 |         before_message_id = messages_list[0].id
425 | 
426 |         # messages are returned in reverse order, so we need to reverse them
427 |         for message in reversed(messages_list):
428 |             # format the message
429 |             formatted_message_list = await converter(context, message, participants)
430 |             try:
431 |                 results = await _num_tokens_from_messages(
432 |                     context=context,
433 |                     response_provider=response_provider,
434 |                     messages=formatted_message_list,
435 |                     model=model,
436 |                     metadata={},
437 |                     metadata_key="get_history_messages",
438 |                 )
439 |                 if results is not None:
440 |                     token_count += results.count
441 |             except Exception as e:
442 |                 logger.exception(f"exception occurred calculating token count: {e}")
443 | 
444 |             # if a token limit is provided and the token count exceeds the limit, break the loop
445 |             if token_limit and token_count > token_limit:
446 |                 break
447 | 
448 |             # insert the formatted messages into the beginning of the history list
449 |             history = formatted_message_list + history
450 | 
451 |     # return the formatted messages
452 |     return history
453 | 
454 | 
455 | def _inject_attachments_inline(
456 |     history_messages: list[CompletionMessage],
457 |     attachment_messages: Sequence[CompletionMessage],
458 | ) -> list[CompletionMessage]:
459 |     """
460 |     Inject the attachment messages inline into the history messages.
461 |     """
462 | 
463 |     # iterate over the history messages and for every message that contains an attachment,
464 |     # find the related attachment message and replace the attachment message with the inline attachment content
465 |     for index, history_message in enumerate(history_messages):
466 |         # if the history message does not contain content, as a string value, skip
467 |         content = history_message.content
468 |         if not content or not isinstance(content, str):
469 |             # TODO: handle list content, which may contain multiple parts including images
470 |             continue
471 | 
472 |         # get the attachment filenames string from the history message content
473 |         attachment_filenames_string = re.findall(r"Attachment\(s\): (.+)", content)
474 | 
475 |         # if the history message does not contain an attachment filenames string, skip
476 |         if not attachment_filenames_string:
477 |             continue
478 | 
479 |         # split the attachment filenames string into a list of attachment filenames
480 |         attachment_filenames = [filename.strip() for filename in attachment_filenames_string[0].split(",")]
481 | 
482 |         # initialize a list to store the replacement messages
483 |         replacement_messages = []
484 | 
485 |         # iterate over the attachment filenames and find the related attachment message
486 |         for attachment_filename in attachment_filenames:
487 |             # find the related attachment message
488 |             attachment_message = next(
489 |                 (
490 |                     attachment_message
491 |                     for attachment_message in attachment_messages
492 |                     if f"<ATTACHMENT><FILENAME>{attachment_filename}</FILENAME>" in str(attachment_message.content)
493 |                 ),
494 |                 None,
495 |             )
496 | 
497 |             if attachment_message:
498 |                 # replace the attachment message with the inline attachment content
499 |                 replacement_messages.append(attachment_message)
500 | 
501 |         # if there are replacement messages, replace the history message with the replacement messages
502 |         if len(replacement_messages) > 0:
503 |             history_messages[index : index + 1] = replacement_messages
504 | 
505 |     return history_messages
506 | 
507 | 
508 | def _get_response_duration_message(response_duration: float) -> str:
509 |     """
510 |     Generate a display friendly message for the response duration, to be added to the footer items.
511 |     """
512 | 
513 |     return f"Response time: {response_duration:.2f} seconds"
514 | 
515 | 
516 | def _get_token_usage_message(
517 |     max_tokens: int,
518 |     completion_total_tokens: int,
519 | ) -> str:
520 |     """
521 |     Generate a display friendly message for the token usage, to be added to the footer items.
522 |     """
523 | 
524 |     def get_display_count(tokens: int) -> str:
525 |         # if less than 1k, return the number of tokens
526 |         # if greater than or equal to 1k, return the number of tokens in k
527 |         # use 1 decimal place for k
528 |         # drop the decimal place if the number of tokens in k is a whole number
529 |         if tokens < 1000:
530 |             return str(tokens)
531 |         else:
532 |             tokens_in_k = tokens / 1000
533 |             if tokens_in_k.is_integer():
534 |                 return f"{int(tokens_in_k)}k"
535 |             else:
536 |                 return f"{tokens_in_k:.1f}k"
537 | 
538 |     return f"Tokens used: {get_display_count(completion_total_tokens)} of {get_display_count(max_tokens)} ({int(completion_total_tokens / max_tokens * 100)}%)"
539 | 
540 | 
541 | def _format_message(message: ConversationMessage, participants: list[ConversationParticipant]) -> str:
542 |     """
543 |     Format a conversation message for display.
544 |     """
545 |     conversation_participant = next(
546 |         (participant for participant in participants if participant.id == message.sender.participant_id),
547 |         None,
548 |     )
549 |     participant_name = conversation_participant.name if conversation_participant else "unknown"
550 |     message_datetime = message.timestamp.strftime("%Y-%m-%d %H:%M:%S")
551 |     return f"[{participant_name} - {message_datetime}]: {message.content}"
552 | 
553 | 
554 | # endregion
555 | 
```

--------------------------------------------------------------------------------
/workbench-service/tests/test_integration.py:
--------------------------------------------------------------------------------

```python
  1 | import asyncio
  2 | import io
  3 | import json
  4 | import logging
  5 | import re
  6 | import uuid
  7 | 
  8 | import httpx
  9 | import pytest
 10 | import semantic_workbench_assistant.canonical
 11 | from asgi_lifespan import LifespanManager
 12 | from fastapi import FastAPI
 13 | from semantic_workbench_api_model import assistant_model, workbench_model
 14 | 
 15 | from .types import MockUser
 16 | 
 17 | 
 18 | async def wait_for_assistant_service_registration(
 19 |     wb_client: httpx.AsyncClient,
 20 | ) -> workbench_model.AssistantServiceRegistration:
 21 |     for _ in range(10):
 22 |         http_response = await wb_client.get("/assistant-service-registrations")
 23 |         http_response.raise_for_status()
 24 |         assistant_services = workbench_model.AssistantServiceRegistrationList.model_validate(http_response.json())
 25 |         if assistant_services.assistant_service_registrations:
 26 |             return assistant_services.assistant_service_registrations[0]
 27 | 
 28 |         await asyncio.sleep(0.01)
 29 | 
 30 |     raise Exception("Timed out waiting for assistant service registration")
 31 | 
 32 | 
 33 | async def test_flow_create_assistant_update_config(
 34 |     workbench_service: FastAPI,
 35 |     canonical_assistant_service: FastAPI,
 36 |     test_user: MockUser,
 37 | ) -> None:
 38 |     async with (
 39 |         LifespanManager(workbench_service),
 40 |         httpx.AsyncClient(
 41 |             transport=httpx.ASGITransport(app=workbench_service),
 42 |             headers=test_user.authorization_headers,
 43 |             base_url="http://test",
 44 |         ) as wb_client,
 45 |         LifespanManager(canonical_assistant_service),
 46 |     ):
 47 |         assistant_service = await wait_for_assistant_service_registration(wb_client)
 48 | 
 49 |         resp = await wb_client.post(
 50 |             "/assistants",
 51 |             json=workbench_model.NewAssistant(
 52 |                 name="test-assistant",
 53 |                 assistant_service_id=assistant_service.assistant_service_id,
 54 |             ).model_dump(mode="json"),
 55 |         )
 56 |         logging.info("POST wb/assistants resp: %s", resp.json())
 57 |         resp.raise_for_status()
 58 | 
 59 |         assistant = workbench_model.Assistant(**resp.json())
 60 |         logging.info("POST wb/assistants resp loaded into model: %s", assistant)
 61 | 
 62 |         resp = await wb_client.get(f"/assistants/{assistant.id}")
 63 |         logging.info("GET wb/assistant/id resp: %s", resp.json())
 64 |         resp.raise_for_status()
 65 | 
 66 |         assert resp.json() == json.loads(assistant.model_dump_json())
 67 | 
 68 |         config = assistant_model.ConfigPutRequestModel(
 69 |             config=semantic_workbench_assistant.canonical.ConfigStateModel(
 70 |                 short_text="test short text",
 71 |                 long_text="test long text",
 72 |                 prompt=semantic_workbench_assistant.canonical.PromptConfigModel(
 73 |                     custom_prompt="test custom prompt",
 74 |                     temperature=0.999999,
 75 |                 ),
 76 |             ).model_dump(),
 77 |         )
 78 |         resp = await wb_client.put(f"/assistants/{assistant.id}/config", json=config.model_dump(mode="json"))
 79 |         resp.raise_for_status()
 80 | 
 81 | 
 82 | async def test_flow_create_assistant_update_conversation_state(
 83 |     workbench_service: FastAPI,
 84 |     canonical_assistant_service: FastAPI,
 85 |     test_user: MockUser,
 86 | ) -> None:
 87 |     async with (
 88 |         LifespanManager(workbench_service),
 89 |         httpx.AsyncClient(
 90 |             transport=httpx.ASGITransport(app=workbench_service),
 91 |             headers=test_user.authorization_headers,
 92 |             base_url="http://test",
 93 |         ) as wb_client,
 94 |         LifespanManager(canonical_assistant_service),
 95 |     ):
 96 |         assistant_service = await wait_for_assistant_service_registration(wb_client)
 97 | 
 98 |         resp = await wb_client.post(
 99 |             "/assistants",
100 |             json=workbench_model.NewAssistant(
101 |                 name="test-assistant",
102 |                 assistant_service_id=assistant_service.assistant_service_id,
103 |             ).model_dump(mode="json"),
104 |         )
105 |         resp.raise_for_status()
106 |         logging.info("POST wb/assistants resp: %s", resp.json())
107 | 
108 |         assistant = workbench_model.Assistant.model_validate(resp.json())
109 |         logging.info("POST wb/assistants resp loaded into model: %s", assistant)
110 | 
111 |         resp = await wb_client.get(f"/assistants/{assistant.id}")
112 |         resp.raise_for_status()
113 |         logging.info("GET wb/assistant/id resp: %s", resp.json())
114 | 
115 |         assert resp.json() == json.loads(assistant.model_dump_json())
116 | 
117 |         resp = await wb_client.post("/conversations", json={"title": "test-conversation"})
118 |         resp.raise_for_status()
119 |         conversation = workbench_model.Conversation.model_validate(resp.json())
120 | 
121 |         resp = await wb_client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
122 |         resp.raise_for_status()
123 |         participant = workbench_model.ConversationParticipant.model_validate(resp.json())
124 |         assert participant.online is True
125 | 
126 |         resp = await wb_client.get(f"/assistants/{assistant.id}/conversations/{conversation.id}/states")
127 |         resp.raise_for_status()
128 |         logging.info("GET asst/conversations/id/states resp: %s", resp.json())
129 | 
130 |         states = assistant_model.StateDescriptionListResponseModel(**resp.json())
131 |         logging.info("GET asst/conversations/id/states resp loaded into model: %s", states)
132 | 
133 |         assert len(states.states) == 1
134 |         assert states.states[0].id == "simple_state"
135 | 
136 |         resp = await wb_client.get(f"/assistants/{assistant.id}/conversations/{conversation.id}/states/simple_state")
137 |         resp.raise_for_status()
138 |         logging.info("GET asst/conversations/id/states/simple_state resp: %s", resp.json())
139 | 
140 |         state = assistant_model.StateResponseModel(**resp.json())
141 |         logging.info("GET asst/conversations/id/states/simple_state resp loaded into model: %s", state)
142 | 
143 |         assert "message" in state.data
144 | 
145 |         updated_message = f"updated message {uuid.uuid4()}"
146 |         state_update = assistant_model.StatePutRequestModel(
147 |             data={"message": updated_message},
148 |         )
149 |         resp = await wb_client.put(
150 |             f"/assistants/{assistant.id}/conversations/{conversation.id}/states/simple_state",
151 |             json=state_update.model_dump(mode="json"),
152 |         )
153 |         resp.raise_for_status()
154 | 
155 |         resp = await wb_client.get(f"/assistants/{assistant.id}/conversations/{conversation.id}/states/simple_state")
156 |         resp.raise_for_status()
157 |         logging.info("GET asst/conversations/id/states/simple_state resp: %s", resp.json())
158 | 
159 |         state = assistant_model.StateResponseModel(**resp.json())
160 |         logging.info("GET asst/conversations/id/states/simple_state resp loaded into model: %s", state)
161 | 
162 |         assert "message" in state.data
163 |         assert state.data["message"] == updated_message
164 | 
165 | 
166 | async def test_flow_create_assistant_send_message_receive_resp(
167 |     workbench_service: FastAPI,
168 |     canonical_assistant_service: FastAPI,
169 |     test_user: MockUser,
170 | ) -> None:
171 |     async with (
172 |         LifespanManager(workbench_service),
173 |         httpx.AsyncClient(
174 |             transport=httpx.ASGITransport(app=workbench_service),
175 |             headers=test_user.authorization_headers,
176 |             base_url="http://test",
177 |         ) as wb_client,
178 |         LifespanManager(canonical_assistant_service),
179 |     ):
180 |         assistant_service = await wait_for_assistant_service_registration(wb_client)
181 | 
182 |         resp = await wb_client.post(
183 |             "/assistants",
184 |             json=workbench_model.NewAssistant(
185 |                 name="test-assistant",
186 |                 assistant_service_id=assistant_service.assistant_service_id,
187 |             ).model_dump(mode="json"),
188 |         )
189 |         resp.raise_for_status()
190 |         logging.info("POST wb/assistants resp: %s", resp.json())
191 | 
192 |         assistant = workbench_model.Assistant(**resp.json())
193 | 
194 |         resp = await wb_client.post("/conversations", json={"title": "test-conversation"})
195 |         resp.raise_for_status()
196 |         conversation = workbench_model.Conversation.model_validate(resp.json())
197 | 
198 |         resp = await wb_client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
199 |         resp.raise_for_status()
200 | 
201 |         resp = await wb_client.post(
202 |             f"/conversations/{conversation.id}/messages",
203 |             json={"content": "hello"},
204 |         )
205 |         resp.raise_for_status()
206 |         logging.info("POST wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
207 | 
208 |         attempts = 1
209 |         messages = []
210 |         while attempts <= 10 and len(messages) < 2:
211 |             if attempts > 1:
212 |                 await asyncio.sleep(0.5)
213 |             attempts += 1
214 | 
215 |             resp = await wb_client.get(f"/conversations/{conversation.id}/messages")
216 |             resp.raise_for_status()
217 |             logging.info("GET wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
218 | 
219 |             messages_resp = resp.json()
220 | 
221 |             assert "messages" in messages_resp
222 |             messages = messages_resp["messages"]
223 | 
224 |         assert len(messages) > 1
225 | 
226 | 
227 | async def test_flow_create_assistant_send_message_receive_resp_export_import_assistant(
228 |     workbench_service: FastAPI,
229 |     canonical_assistant_service: FastAPI,
230 |     test_user: MockUser,
231 | ) -> None:
232 |     async with (
233 |         LifespanManager(workbench_service),
234 |         httpx.AsyncClient(
235 |             transport=httpx.ASGITransport(app=workbench_service),
236 |             headers=test_user.authorization_headers,
237 |             base_url="http://test",
238 |         ) as wb_client,
239 |         LifespanManager(canonical_assistant_service),
240 |     ):
241 |         assistant_service = await wait_for_assistant_service_registration(wb_client)
242 | 
243 |         resp = await wb_client.post(
244 |             "/assistants",
245 |             json=workbench_model.NewAssistant(
246 |                 name="test-assistant",
247 |                 assistant_service_id=assistant_service.assistant_service_id,
248 |             ).model_dump(mode="json"),
249 |         )
250 |         resp.raise_for_status()
251 |         logging.info("POST wb/assistants resp: %s", resp.json())
252 | 
253 |         assistant = workbench_model.Assistant(**resp.json())
254 | 
255 |         resp = await wb_client.post("/conversations", json={"title": "test-conversation"})
256 |         resp.raise_for_status()
257 |         conversation = workbench_model.Conversation.model_validate(resp.json())
258 | 
259 |         resp = await wb_client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
260 |         resp.raise_for_status()
261 | 
262 |         async def send_message_wait_for_response(conversation: workbench_model.Conversation) -> None:
263 |             resp = await wb_client.get(f"/conversations/{conversation.id}/messages")
264 |             resp.raise_for_status()
265 |             existing_messages = workbench_model.ConversationMessageList.model_validate(resp.json())
266 | 
267 |             logging.info("POST wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
268 |             resp = await wb_client.post(
269 |                 f"/conversations/{conversation.id}/messages",
270 |                 json={"content": "hello"},
271 |             )
272 |             resp.raise_for_status()
273 |             logging.info("POST wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
274 | 
275 |             url = f"/conversations/{conversation.id}/messages"
276 |             params = {}
277 |             if existing_messages.messages:
278 |                 params = {"after": str(existing_messages.messages[-1].id)}
279 |             attempts = 1
280 |             messages = []
281 |             while attempts <= 10 and len(messages) < 2:
282 |                 if attempts > 1:
283 |                     await asyncio.sleep(0.5)
284 | 
285 |                 attempts += 1
286 | 
287 |                 resp = await wb_client.get(url, params=params)
288 |                 resp.raise_for_status()
289 |                 logging.info("GET %s resp: %s", url, resp.json())
290 | 
291 |                 messages_response = workbench_model.ConversationMessageList.model_validate(resp.json())
292 |                 messages = messages_response.messages
293 | 
294 |             assert len(messages) == 2
295 |             assert messages[0].sender.participant_role == workbench_model.ParticipantRole.user
296 |             assert messages[1].sender.participant_role == workbench_model.ParticipantRole.assistant
297 | 
298 |         await send_message_wait_for_response(conversation)
299 | 
300 |         resp = await wb_client.get(f"/assistants/{assistant.id}/export")
301 |         resp.raise_for_status()
302 | 
303 |         assert resp.headers["content-type"] == "application/zip"
304 |         assert "content-length" in resp.headers
305 |         assert int(resp.headers["content-length"]) > 0
306 | 
307 |         logging.info("response: %s", resp.content)
308 | 
309 |         exported_file = io.BytesIO(resp.content)
310 | 
311 |         for import_number in range(1, 3):
312 |             resp = await wb_client.post("/conversations/import", files={"from_export": exported_file})
313 |             logging.info("import %s response: %s", import_number, resp.json())
314 |             resp.raise_for_status()
315 | 
316 |             import_result = workbench_model.ConversationImportResult.model_validate(resp.json())
317 |             new_assistant_id = import_result.assistant_ids[0]
318 | 
319 |             resp = await wb_client.get(f"/assistants/{new_assistant_id}/conversations")
320 |             conversations = workbench_model.ConversationList.model_validate(resp.json())
321 |             new_conversation = conversations.conversations[0]
322 | 
323 |             resp = await wb_client.get("/assistants")
324 |             logging.info("response: %s", resp.json())
325 |             resp.raise_for_status()
326 |             assistants_response = workbench_model.AssistantList.model_validate(resp.json())
327 |             assistant_count = len(assistants_response.assistants)
328 |             assert assistant_count == 1
329 |             assert assistants_response.assistants[0].name == "test-assistant"
330 | 
331 |             # ensure the new assistant can send and receive messages in the new conversation
332 |             await send_message_wait_for_response(new_conversation)
333 | 
334 | 
335 | async def test_flow_create_assistant_send_message_receive_resp_export_import_conversations(
336 |     workbench_service: FastAPI,
337 |     canonical_assistant_service: FastAPI,
338 |     test_user: MockUser,
339 | ) -> None:
340 |     async with (
341 |         LifespanManager(workbench_service),
342 |         httpx.AsyncClient(
343 |             transport=httpx.ASGITransport(app=workbench_service),
344 |             headers=test_user.authorization_headers,
345 |             base_url="http://test",
346 |         ) as wb_client,
347 |         LifespanManager(canonical_assistant_service),
348 |     ):
349 |         assistant_service = await wait_for_assistant_service_registration(wb_client)
350 | 
351 |         resp = await wb_client.post(
352 |             "/assistants",
353 |             json=workbench_model.NewAssistant(
354 |                 name="test-assistant",
355 |                 assistant_service_id=assistant_service.assistant_service_id,
356 |             ).model_dump(mode="json"),
357 |         )
358 |         resp.raise_for_status()
359 |         logging.info("POST wb/assistants resp: %s", resp.json())
360 | 
361 |         assistant = workbench_model.Assistant(**resp.json())
362 | 
363 |         resp = await wb_client.post("/conversations", json={"title": "test-conversation"})
364 |         resp.raise_for_status()
365 |         conversation = workbench_model.Conversation.model_validate(resp.json())
366 | 
367 |         resp = await wb_client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
368 |         resp.raise_for_status()
369 | 
370 |         async def send_message_wait_for_response(conversation: workbench_model.Conversation) -> None:
371 |             resp = await wb_client.get(f"/conversations/{conversation.id}/messages")
372 |             resp.raise_for_status()
373 |             existing_messages = workbench_model.ConversationMessageList.model_validate(resp.json())
374 | 
375 |             resp = await wb_client.post(
376 |                 f"/conversations/{conversation.id}/messages",
377 |                 json={"content": "hello"},
378 |             )
379 |             resp.raise_for_status()
380 |             logging.info("POST wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
381 | 
382 |             url = f"/conversations/{conversation.id}/messages"
383 |             params = {}
384 |             if existing_messages.messages:
385 |                 params = {"after": str(existing_messages.messages[-1].id)}
386 |             attempts = 1
387 |             messages = []
388 |             while attempts <= 10 and len(messages) < 2:
389 |                 if attempts > 1:
390 |                     await asyncio.sleep(0.5)
391 | 
392 |                 attempts += 1
393 | 
394 |                 resp = await wb_client.get(url, params=params)
395 |                 resp.raise_for_status()
396 |                 logging.info("GET wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
397 | 
398 |                 messages_response = workbench_model.ConversationMessageList.model_validate(resp.json())
399 |                 messages = messages_response.messages
400 | 
401 |             assert len(messages) == 2
402 |             assert messages[0].sender.participant_role == workbench_model.ParticipantRole.user
403 |             assert messages[1].sender.participant_role == workbench_model.ParticipantRole.assistant
404 | 
405 |         await send_message_wait_for_response(conversation)
406 | 
407 |         resp = await wb_client.get("/conversations/export", params={"id": str(conversation.id)})
408 |         resp.raise_for_status()
409 | 
410 |         assert resp.headers["content-type"] == "application/zip"
411 |         assert "content-length" in resp.headers
412 |         assert int(resp.headers["content-length"]) > 0
413 | 
414 |         logging.info("response: %s", resp.content)
415 | 
416 |         file_io = io.BytesIO(resp.content)
417 | 
418 |         for import_number in range(1, 3):
419 |             resp = await wb_client.post("/conversations/import", files={"from_export": file_io})
420 |             logging.info("import %s response: %s", import_number, resp.json())
421 |             resp.raise_for_status()
422 |             import_result = workbench_model.ConversationImportResult.model_validate(resp.json())
423 |             assert len(import_result.assistant_ids) == 1
424 |             new_assistant_id = import_result.assistant_ids[0]
425 | 
426 |             resp = await wb_client.get(f"/assistants/{new_assistant_id}/conversations")
427 |             conversations = workbench_model.ConversationList.model_validate(resp.json())
428 |             new_conversation = conversations.conversations[0]
429 | 
430 |             resp = await wb_client.get("/assistants")
431 |             logging.info("response: %s", resp.json())
432 |             resp.raise_for_status()
433 | 
434 |             assistants_response = workbench_model.AssistantList.model_validate(resp.json())
435 |             assistant_count = len(assistants_response.assistants)
436 |             assert assistant_count == 1
437 | 
438 |             assert assistants_response.assistants[0].name == "test-assistant"
439 | 
440 |             # ensure the new assistant can send and receive messages in the new conversation
441 |             await send_message_wait_for_response(new_conversation)
442 | 
443 | 
444 | @pytest.mark.parametrize(
445 |     # spell-checker:ignore dlrow olleh
446 |     ("command", "command_args", "expected_response_content_regex"),
447 |     [
448 |         ("/reverse", "hello world", re.compile("dlrow olleh")),
449 |         ("/reverse", "-h", re.compile("usage: /reverse.+", re.DOTALL)),
450 |         ("/reverse", "", re.compile("/reverse: error: .+", re.DOTALL)),
451 |     ],
452 | )
453 | async def test_flow_create_assistant_send_command_message_receive_resp(
454 |     workbench_service: FastAPI,
455 |     canonical_assistant_service: FastAPI,
456 |     test_user: MockUser,
457 |     command: str,
458 |     command_args: str,
459 |     expected_response_content_regex: re.Pattern,
460 | ) -> None:
461 |     async with (
462 |         LifespanManager(workbench_service),
463 |         httpx.AsyncClient(
464 |             transport=httpx.ASGITransport(app=workbench_service),
465 |             headers=test_user.authorization_headers,
466 |             base_url="http://test",
467 |         ) as wb_client,
468 |         LifespanManager(canonical_assistant_service),
469 |     ):
470 |         assistant_service = await wait_for_assistant_service_registration(wb_client)
471 | 
472 |         resp = await wb_client.post(
473 |             "/assistants",
474 |             json=workbench_model.NewAssistant(
475 |                 name="test-assistant",
476 |                 assistant_service_id=assistant_service.assistant_service_id,
477 |             ).model_dump(mode="json"),
478 |         )
479 |         resp.raise_for_status()
480 |         logging.info("POST wb/assistants resp: %s", resp.json())
481 |         assistant = workbench_model.Assistant.model_validate(resp.json())
482 |         logging.info("assistant: %s", assistant)
483 | 
484 |         resp = await wb_client.post(
485 |             "/conversations",
486 |             json={"title": "test-assistant"},
487 |         )
488 |         resp.raise_for_status()
489 |         logging.info("POST wb/conversations resp: %s", resp.json())
490 |         conversation = workbench_model.Conversation.model_validate(resp.json())
491 | 
492 |         resp = await wb_client.put(f"/conversations/{conversation.id}/participants/{assistant.id}", json={})
493 |         resp.raise_for_status()
494 | 
495 |         command_content = f"{command} {command_args}"
496 |         resp = await wb_client.post(
497 |             f"/conversations/{conversation.id}/messages",
498 |             json={
499 |                 "message_type": "command",
500 |                 "content_type": "application/json",
501 |                 "content": command_content,
502 |             },
503 |         )
504 |         resp.raise_for_status()
505 |         logging.info("POST wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
506 | 
507 |         attempts = 1
508 |         messages = []
509 |         while attempts <= 10 and len(messages) < 2:
510 |             if attempts > 1:
511 |                 await asyncio.sleep(0.5)
512 |             attempts += 1
513 | 
514 |             resp = await wb_client.get(f"/conversations/{conversation.id}/messages")
515 |             resp.raise_for_status()
516 |             logging.info("GET wb/conversations/%s/messages resp: %s", conversation.id, resp.json())
517 | 
518 |             messages_resp = resp.json()
519 | 
520 |             assert "messages" in messages_resp
521 |             messages = messages_resp["messages"]
522 | 
523 |         assert len(messages) > 1
524 |         response_message = messages[1]
525 | 
526 |         assert expected_response_content_regex.fullmatch(response_message["content"])
527 |         assert response_message["message_type"] == "command-response"
528 | 
```
Page 98/145FirstPrevNextLast