#
tokens: 47962/50000 3/1784 files (page 94/145)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 94 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/document-assistant/assistant/filesystem/_inspector.py:
--------------------------------------------------------------------------------

```python
  1 | # Copyright (c) Microsoft. All rights reserved.
  2 | 
  3 | import asyncio
  4 | import datetime
  5 | import io
  6 | import logging
  7 | import uuid
  8 | from contextlib import asynccontextmanager
  9 | from hashlib import md5
 10 | from typing import Annotated, Any, AsyncGenerator, Callable, Literal, Protocol
 11 | 
 12 | import deepmerge
 13 | from assistant_drive import Drive, IfDriveFileExistsBehavior
 14 | from pydantic import BaseModel, Field, ValidationError, create_model
 15 | from semantic_workbench_api_model import workbench_model
 16 | from semantic_workbench_assistant.assistant_app import (
 17 |     AssistantAppProtocol,
 18 |     AssistantConversationInspectorStateDataModel,
 19 |     BaseModelAssistantConfig,
 20 |     ConversationContext,
 21 | )
 22 | from semantic_workbench_assistant.config import UISchema, get_ui_schema
 23 | 
 24 | from assistant.config import AssistantConfigModel
 25 | from assistant.filesystem._tasks import task_compute_summary
 26 | 
 27 | from ._model import DocumentEditorConfigProvider
 28 | 
 29 | logger = logging.getLogger(__name__)
 30 | 
 31 | assistant_config = BaseModelAssistantConfig(AssistantConfigModel)
 32 | 
 33 | 
 34 | class DocumentFileStateModel(BaseModel):
 35 |     filename: Annotated[str, UISchema(readonly=True)]
 36 |     content: Annotated[str, UISchema(widget="textarea", rows=800, hide_label=True)]
 37 | 
 38 | 
 39 | def document_list_model(documents: list[DocumentFileStateModel]) -> type[BaseModel]:
 40 |     filenames = [document.filename for document in documents]
 41 | 
 42 |     return create_model(
 43 |         "DocumentListModel",
 44 |         active_document=Annotated[
 45 |             Literal[tuple(filenames)],
 46 |             Field(
 47 |                 title="Edit a document:",
 48 |                 description="Select a document and click Edit .",
 49 |             ),
 50 |         ],
 51 |     )
 52 | 
 53 | 
 54 | def _get_document_editor_ui_schema(readonly: bool, documents: list[DocumentFileStateModel]) -> dict[str, Any]:
 55 |     schema = get_ui_schema(DocumentFileStateModel)
 56 |     multiple_files_message = "To edit another document, switch to the Documents tab." if len(documents) > 1 else ""
 57 |     schema = deepmerge.always_merger.merge(
 58 |         schema.copy(),
 59 |         {
 60 |             "ui:options": {
 61 |                 "collapsible": False,
 62 |                 "title": "Document Editor",
 63 |                 "description": multiple_files_message,
 64 |                 "readonly": readonly,
 65 |             },
 66 |         },
 67 |     )
 68 |     return schema
 69 | 
 70 | 
 71 | def _get_document_list_ui_schema(model: type[BaseModel], filenames: list[str]) -> dict[str, Any]:
 72 |     return {
 73 |         "ui:options": {
 74 |             "collapsible": False,
 75 |             "hideTitle": True,
 76 |         },
 77 |         "ui:submitButtonOptions": {
 78 |             "submitText": "Edit",
 79 |             "norender": len(filenames) <= 1,
 80 |         },
 81 |         "active_document": {
 82 |             "ui:options": {
 83 |                 "widget": "radio" if len(filenames) > 1 else "hidden",
 84 |             },
 85 |         },
 86 |     }
 87 | 
 88 | 
 89 | def markdown_content_postprocessor(content: str) -> str:
 90 |     """
 91 |     Post-process the markdown content
 92 |     This is a workaround for the issue where <br /> tags are not rendered correctly in the UI.
 93 |     """
 94 |     return content.replace("<br />", "&#x20;")
 95 | 
 96 | 
 97 | class InspectorController(Protocol):
 98 |     async def is_enabled(self, context: ConversationContext) -> bool: ...
 99 |     async def is_read_only(self, context: ConversationContext) -> bool: ...
100 |     async def read_active_document(self, context: ConversationContext) -> DocumentFileStateModel | None: ...
101 |     async def write_active_document(self, context: ConversationContext, content: str) -> None: ...
102 |     async def set_active_filename(self, context: ConversationContext, filename: str) -> None: ...
103 |     async def list_documents(self, context: ConversationContext) -> list[DocumentFileStateModel]: ...
104 | 
105 | 
106 | class EditableDocumentFileStateInspector:
107 |     def __init__(
108 |         self,
109 |         controller: InspectorController,
110 |         display_name: str,
111 |         description: str = "",
112 |     ) -> None:
113 |         self._state_id = md5(
114 |             (type(self).__name__ + "_" + display_name).encode("utf-8"),
115 |             usedforsecurity=False,
116 |         ).hexdigest()
117 |         self._display_name = display_name
118 |         self._description = description
119 |         self._controller = controller
120 | 
121 |     @property
122 |     def state_id(self) -> str:
123 |         return self._state_id
124 | 
125 |     @property
126 |     def display_name(self) -> str:
127 |         return self._display_name
128 | 
129 |     @property
130 |     def description(self) -> str:
131 |         return self._description
132 | 
133 |     async def is_enabled(self, context: ConversationContext) -> bool:
134 |         return await self._controller.is_enabled(context)
135 | 
136 |     async def get(self, context: ConversationContext) -> AssistantConversationInspectorStateDataModel:
137 |         if not await self._controller.is_enabled(context):
138 |             return AssistantConversationInspectorStateDataModel(
139 |                 data={"content": "The Document Editor extension is not enabled."}
140 |             )
141 | 
142 |         document = await self._controller.read_active_document(context)
143 |         if not document:
144 |             return AssistantConversationInspectorStateDataModel(data={"content": "No current document."})
145 | 
146 |         is_readonly = await self._controller.is_read_only(context)
147 | 
148 |         markdown_content = markdown_content_postprocessor(document.content)
149 |         return AssistantConversationInspectorStateDataModel(
150 |             data={
151 |                 "markdown_content": markdown_content,
152 |                 "filename": document.filename,
153 |                 "readonly": is_readonly,
154 |             }
155 |         )
156 | 
157 |     async def set(
158 |         self,
159 |         context: ConversationContext,
160 |         data: dict[str, Any],
161 |     ) -> None:
162 |         if not await self._controller.is_enabled(context):
163 |             return
164 |         if await self._controller.is_read_only(context):
165 |             return
166 | 
167 |         # The data comes in with 'markdown_content' but our model expects 'content'
168 |         if "markdown_content" in data:
169 |             content = data["markdown_content"]
170 |             # If filename is present but we don't need to modify it, we can just get the content
171 |             await self._controller.write_active_document(context, content)
172 |         else:
173 |             try:
174 |                 model = DocumentFileStateModel.model_validate(data)
175 |                 await self._controller.write_active_document(context, model.content)
176 |             except ValidationError:
177 |                 logger.exception("invalid data for DocumentFileStateModel")
178 |                 return
179 | 
180 | 
181 | class ReadonlyDocumentFileStateInspector:
182 |     def __init__(
183 |         self,
184 |         controller: InspectorController,
185 |         display_name: str,
186 |         description: str = "",
187 |     ) -> None:
188 |         self._state_id = md5(
189 |             (type(self).__name__ + "_" + display_name).encode("utf-8"),
190 |             usedforsecurity=False,
191 |         ).hexdigest()
192 |         self._display_name = display_name
193 |         self._description = description
194 |         self._controller = controller
195 | 
196 |     @property
197 |     def state_id(self) -> str:
198 |         return self._state_id
199 | 
200 |     @property
201 |     def display_name(self) -> str:
202 |         return self._display_name
203 | 
204 |     @property
205 |     def description(self) -> str:
206 |         return self._description
207 | 
208 |     async def get(self, context: ConversationContext) -> AssistantConversationInspectorStateDataModel:
209 |         if not await self._controller.is_enabled(context):
210 |             return AssistantConversationInspectorStateDataModel(
211 |                 data={"content": "The Document Editor extension is not enabled."}
212 |             )
213 | 
214 |         document = await self._controller.read_active_document(context)
215 |         if not document:
216 |             return AssistantConversationInspectorStateDataModel(data={"content": "No current document."})
217 | 
218 |         markdown_content = markdown_content_postprocessor(document.content)
219 |         return AssistantConversationInspectorStateDataModel(
220 |             data={
221 |                 "markdown_content": markdown_content,
222 |                 "filename": document.filename,
223 |                 "readonly": True,  # Always read-only for this inspector
224 |             },
225 |         )
226 | 
227 | 
228 | class DocumentListInspector:
229 |     def __init__(
230 |         self,
231 |         controller: InspectorController,
232 |         display_name: str,
233 |         description: str = "",
234 |     ) -> None:
235 |         self._state_id = md5(
236 |             (type(self).__name__ + "_" + display_name).encode("utf-8"),
237 |             usedforsecurity=False,
238 |         ).hexdigest()
239 |         self._display_name = display_name
240 |         self._description = description
241 |         self._controller = controller
242 | 
243 |     @property
244 |     def state_id(self) -> str:
245 |         return self._state_id
246 | 
247 |     @property
248 |     def display_name(self) -> str:
249 |         return self._display_name
250 | 
251 |     @property
252 |     def description(self) -> str:
253 |         return self._description
254 | 
255 |     async def is_enabled(self, context: ConversationContext) -> bool:
256 |         return await self._controller.is_enabled(context)
257 | 
258 |     async def get(self, context: ConversationContext) -> AssistantConversationInspectorStateDataModel:
259 |         if not await self._controller.is_enabled(context):
260 |             return AssistantConversationInspectorStateDataModel(
261 |                 data={"content": "The Document Editor extension is not enabled."}
262 |             )
263 | 
264 |         documents = await self._controller.list_documents(context)
265 |         if not documents:
266 |             return AssistantConversationInspectorStateDataModel(data={"content": "No documents available."})
267 | 
268 |         filenames = [document.filename for document in documents]
269 |         model = document_list_model(documents)
270 | 
271 |         current_document = await self._controller.read_active_document(context)
272 |         selected_filename = current_document.filename if current_document else filenames[0]
273 | 
274 |         return AssistantConversationInspectorStateDataModel(
275 |             data={
276 |                 "attachments": [
277 |                     DocumentFileStateModel.model_validate(document).model_dump(mode="json") for document in documents
278 |                 ],
279 |                 "active_document": selected_filename,
280 |             },
281 |             json_schema=model.model_json_schema(),
282 |             ui_schema=_get_document_list_ui_schema(model, filenames),
283 |         )
284 | 
285 |     async def set(
286 |         self,
287 |         context: ConversationContext,
288 |         data: dict[str, Any],
289 |     ) -> None:
290 |         if not await self._controller.is_enabled(context):
291 |             return
292 | 
293 |         active_document = data.get("active_document")
294 |         if not active_document:
295 |             return
296 | 
297 |         await self._controller.set_active_filename(context, active_document)
298 | 
299 | 
300 | class DocumentInspectors:
301 |     def __init__(
302 |         self,
303 |         app: AssistantAppProtocol,
304 |         config_provider: DocumentEditorConfigProvider,
305 |         drive_provider: Callable[[ConversationContext], Drive],
306 |     ) -> None:
307 |         self._config_provider = config_provider
308 |         self._drive_provider = drive_provider
309 |         self._selected_file: dict[str, str] = {}
310 |         self._readonly: set[str] = set()
311 | 
312 |         self._file_list = DocumentListInspector(
313 |             controller=self,
314 |             display_name="Documents",
315 |             description="Download a document:",
316 |         )
317 |         app.add_inspector_state_provider(state_id=self._file_list.state_id, provider=self._file_list)
318 | 
319 |         self._viewer = ReadonlyDocumentFileStateInspector(
320 |             controller=self,
321 |             display_name="Document Viewer",
322 |         )
323 | 
324 |         self._editor = EditableDocumentFileStateInspector(
325 |             controller=self,
326 |             display_name="Document Editor",
327 |         )
328 |         app.add_inspector_state_provider(state_id=self._editor.state_id, provider=self._editor)
329 | 
330 |         @app.events.conversation.participant.on_updated_including_mine
331 |         async def on_participant_update(
332 |             ctx: ConversationContext,
333 |             event: workbench_model.ConversationEvent,
334 |             participant: workbench_model.ConversationParticipant,
335 |         ) -> None:
336 |             documents_locked = participant.metadata.get("document_lock", None)
337 |             if documents_locked is None:
338 |                 return
339 | 
340 |             match documents_locked:
341 |                 case True:
342 |                     if ctx.id in self._readonly:
343 |                         return
344 |                     self._readonly.add(ctx.id)
345 |                     await self._emit_state_change_event(ctx)
346 | 
347 |                 case False:
348 |                     if ctx.id not in self._readonly:
349 |                         return
350 |                     self._readonly.remove(ctx.id)
351 |                     await self._emit_state_change_event(ctx)
352 | 
353 |     async def _emit_state_change_event(self, ctx: ConversationContext) -> None:
354 |         for state_id in (
355 |             self._editor.state_id,
356 |             # self._viewer.state_id,
357 |             self._file_list.state_id,
358 |         ):
359 |             await ctx.send_conversation_state_event(
360 |                 workbench_model.AssistantStateEvent(
361 |                     state_id=state_id,
362 |                     event="updated",
363 |                     state=None,
364 |                 )
365 |             )
366 | 
367 |     async def on_external_write(self, context: ConversationContext, filename: str) -> None:
368 |         self._selected_file[context.id] = filename
369 | 
370 |         content = await self.get_file_content(context=context, filename=filename) or ""
371 |         config = await assistant_config.get(context.assistant)
372 |         asyncio.create_task(
373 |             task_compute_summary(context=context, config=config, file_content=content, filename=filename)
374 |         )
375 |         await context.send_conversation_state_event(
376 |             workbench_model.AssistantStateEvent(
377 |                 state_id=self._editor.state_id,
378 |                 event="focus",
379 |                 state=None,
380 |             )
381 |         )
382 | 
383 |     async def is_enabled(self, context: ConversationContext) -> bool:
384 |         config = await self._config_provider(context)
385 |         return config.enabled
386 | 
387 |     async def is_read_only(self, context: ConversationContext) -> bool:
388 |         return context.id in self._readonly
389 | 
390 |     async def read_active_document(self, context: ConversationContext) -> DocumentFileStateModel | None:
391 |         drive = self._drive_provider(context)
392 |         markdown_files = [filename for filename in drive.list() if filename.endswith(".md")]
393 |         if not markdown_files:
394 |             self._selected_file.pop(context.id, None)
395 |             return None
396 | 
397 |         if context.id not in self._selected_file:
398 |             self._selected_file[context.id] = markdown_files[0]
399 | 
400 |         selected_file_name = self._selected_file[context.id]
401 | 
402 |         buffer = io.BytesIO()
403 |         try:
404 |             with drive.open_file(selected_file_name) as file:
405 |                 buffer.write(file.read())
406 |         except FileNotFoundError:
407 |             return None
408 | 
409 |         file_content = buffer.getvalue().decode("utf-8")
410 | 
411 |         return DocumentFileStateModel(content=file_content, filename=selected_file_name)
412 | 
413 |     async def write_active_document(self, context: ConversationContext, content: str) -> None:
414 |         drive = self._drive_provider(context)
415 |         filename = self._selected_file.get(context.id)
416 |         if not filename:
417 |             raise ValueError("No file selected")
418 | 
419 |         drive.write(
420 |             content=io.BytesIO(content.encode("utf-8")),
421 |             filename=filename,
422 |             if_exists=IfDriveFileExistsBehavior.OVERWRITE,
423 |             content_type="text/markdown",
424 |         )
425 | 
426 |     async def list_documents(self, context: ConversationContext) -> list[DocumentFileStateModel]:
427 |         drive = self._drive_provider(context)
428 |         markdown_files = [filename for filename in drive.list() if filename.endswith(".md")]
429 | 
430 |         documents = []
431 |         for filename in markdown_files:
432 |             buffer = io.BytesIO()
433 |             try:
434 |                 with drive.open_file(filename) as file:
435 |                     buffer.write(file.read())
436 |             except FileNotFoundError:
437 |                 continue
438 | 
439 |             file_content = buffer.getvalue().decode("utf-8")
440 |             documents.append(DocumentFileStateModel(content=file_content, filename=filename))
441 | 
442 |         return sorted(documents, key=lambda x: x.filename)
443 | 
444 |     async def list_document_filenames(self, context: ConversationContext) -> list[str]:
445 |         """Returns a list of available markdown document filenames."""
446 |         drive = self._drive_provider(context)
447 |         markdown_files = [filename for filename in drive.list() if filename.endswith(".md")]
448 |         return sorted(markdown_files)
449 | 
450 |     async def set_active_filename(self, context: ConversationContext, filename: str) -> None:
451 |         self._selected_file[context.id] = filename
452 | 
453 |         await context.send_conversation_state_event(
454 |             workbench_model.AssistantStateEvent(
455 |                 state_id=self._editor.state_id,
456 |                 event="focus",
457 |                 state=None,
458 |             )
459 |         )
460 | 
461 |     async def get_file_content(self, context: ConversationContext, filename: str) -> str | None:
462 |         """
463 |         Get the content of a file by filename.
464 | 
465 |         Args:
466 |             context: The conversation context.
467 |             filename: The filename of the document to retrieve.
468 | 
469 |         Returns:
470 |             The file content as a string if found, None otherwise.
471 |         """
472 |         if not await self.is_enabled(context):
473 |             return None
474 | 
475 |         drive = self._drive_provider(context)
476 | 
477 |         # Check if the file exists in the drive
478 |         try:
479 |             buffer = io.BytesIO()
480 |             with drive.open_file(filename) as file:
481 |                 buffer.write(file.read())
482 | 
483 |             return buffer.getvalue().decode("utf-8")
484 |         except FileNotFoundError:
485 |             # File doesn't exist in the document store
486 |             return None
487 | 
488 | 
489 | @asynccontextmanager
490 | async def lock_document_edits(app: AssistantAppProtocol, context: ConversationContext) -> AsyncGenerator[None, None]:
491 |     """
492 |     A temporary work-around to call the event handlers directly to communicate the document lock
493 |     status to the document inspectors. This circumvents the serialization of event delivery by
494 |     calling the event handlers directly.
495 | 
496 |     It uses an arbitrary event type that the inspector is listening for. The key data is in the
497 |     Participant.metadata["document_lock"] field. The rest is unused.
498 |     """
499 | 
500 |     def participant(lock: bool) -> workbench_model.ConversationParticipant:
501 |         return workbench_model.ConversationParticipant(
502 |             conversation_id=uuid.UUID(context.id),
503 |             active_participant=True,
504 |             conversation_permission=workbench_model.ConversationPermission.read,
505 |             id="",
506 |             role=workbench_model.ParticipantRole.assistant,
507 |             name="",
508 |             status=None,
509 |             metadata={
510 |                 "document_lock": lock,
511 |             },
512 |             status_updated_timestamp=datetime.datetime.now(),
513 |             image=None,
514 |         )
515 | 
516 |     # lock document edits
517 |     await app.events.conversation.participant._on_updated_handlers(
518 |         False,
519 |         context,
520 |         None,
521 |         participant(True),
522 |     )
523 | 
524 |     try:
525 |         yield
526 | 
527 |     finally:
528 |         # unlock the documents
529 |         await app.events.conversation.participant._on_updated_handlers(
530 |             False,
531 |             context,
532 |             None,
533 |             participant(False),
534 |         )
535 | 
```

--------------------------------------------------------------------------------
/mcp-servers/mcp-server-filesystem-edit/mcp_server_filesystem_edit/app_handling/powerpoint.py:
--------------------------------------------------------------------------------

```python
  1 | # Copyright (c) Microsoft. All rights reserved.
  2 | 
  3 | import re
  4 | 
  5 | 
  6 | def get_markdown_representation(document) -> str:
  7 |     """
  8 |     Return the content of the PowerPoint presentation formatted as a combination of XML and markdown.
  9 |     """
 10 |     markdown_text = []
 11 | 
 12 |     # Get layout mappings (reverse of the write_markdown layout dictionary)
 13 |     layout_types = {
 14 |         1: "title",  # Title Slide
 15 |         2: "title_and_content",  # Title and Content
 16 |         3: "two_content",  # Section Header
 17 |         33: "section_header",  # Section Header
 18 |     }
 19 | 
 20 |     # Process each slide
 21 |     for slide_index in range(1, document.Slides.Count + 1):
 22 |         slide = document.Slides(slide_index)
 23 | 
 24 |         # Get layout type - slide.Layout is already an integer
 25 |         layout_type = slide.Layout
 26 |         layout_name = layout_types.get(layout_type, "title_and_content")  # Default if not recognized
 27 | 
 28 |         # Start slide tag
 29 |         markdown_text.append(f'<slide index="{slide_index}" layout="{layout_name}">')
 30 | 
 31 |         # Extract title if slide has one
 32 |         if slide.Shapes.HasTitle:
 33 |             title_text = slide.Shapes.Title.TextFrame.TextRange.Text
 34 |             markdown_text.append(f"<title>{title_text}</title>")
 35 |         else:
 36 |             markdown_text.append("<title></title>")
 37 | 
 38 |         # Process content based on layout type
 39 |         if layout_name == "title":
 40 |             # Title slides typically have a subtitle as the second shape
 41 |             if slide.Shapes.Count >= 2:
 42 |                 subtitle_shape = slide.Shapes(2)  # PowerPoint indexing is 1-based
 43 |                 if hasattr(subtitle_shape, "TextFrame"):
 44 |                     subtitle_text = subtitle_shape.TextFrame.TextRange.Text
 45 |                     markdown_text.append(f"<content>{subtitle_text}</content>")
 46 |                 else:
 47 |                     markdown_text.append("<content></content>")
 48 |             else:
 49 |                 markdown_text.append("<content></content>")
 50 | 
 51 |         elif layout_name == "section_header":
 52 |             # Section headers typically have description text as the second shape
 53 |             if slide.Shapes.Count >= 2:
 54 |                 description_shape = slide.Shapes(2)
 55 |                 if hasattr(description_shape, "TextFrame"):
 56 |                     description_text = description_shape.TextFrame.TextRange.Text
 57 |                     markdown_text.append(f"<content>{description_text}</content>")
 58 |                 else:
 59 |                     markdown_text.append("<content></content>")
 60 |             else:
 61 |                 markdown_text.append("<content></content>")
 62 | 
 63 |         elif layout_name == "title_and_content":
 64 |             # Title and content slides have content in the second shape
 65 |             if slide.Shapes.Count >= 2:
 66 |                 content_shape = slide.Shapes(2)
 67 |                 if hasattr(content_shape, "TextFrame"):
 68 |                     content_text = extract_formatted_content(content_shape.TextFrame)
 69 |                     markdown_text.append(f"<content>\n{content_text}\n</content>")
 70 |                 else:
 71 |                     markdown_text.append("<content></content>")
 72 |             else:
 73 |                 markdown_text.append("<content></content>")
 74 | 
 75 |         elif layout_name == "two_content":
 76 |             # Two content slides have content in second and third shapes
 77 |             # First content
 78 |             if slide.Shapes.Count >= 2:
 79 |                 left_content_shape = slide.Shapes(2)
 80 |                 if hasattr(left_content_shape, "TextFrame"):
 81 |                     left_content_text = extract_formatted_content(left_content_shape.TextFrame)
 82 |                     markdown_text.append(f"<content>\n{left_content_text}\n</content>")
 83 |                 else:
 84 |                     markdown_text.append("<content></content>")
 85 |             else:
 86 |                 markdown_text.append("<content></content>")
 87 | 
 88 |             # Second content
 89 |             if slide.Shapes.Count >= 3:
 90 |                 right_content_shape = slide.Shapes(3)
 91 |                 if hasattr(right_content_shape, "TextFrame"):
 92 |                     right_content_text = extract_formatted_content(right_content_shape.TextFrame)
 93 |                     markdown_text.append(f"<content>\n{right_content_text}\n</content>")
 94 |                 else:
 95 |                     markdown_text.append("<content></content>")
 96 |             else:
 97 |                 markdown_text.append("<content></content>")
 98 | 
 99 |         # Close slide tag
100 |         markdown_text.append("</slide>\n")
101 | 
102 |     return "\n".join(markdown_text)
103 | 
104 | 
105 | def extract_formatted_content(text_frame) -> str:
106 |     markdown_lines = []
107 |     for para_index in range(1, text_frame.TextRange.Paragraphs().Count + 1):
108 |         para = text_frame.TextRange.Paragraphs(para_index)
109 | 
110 |         # Skip empty paragraphs
111 |         if not para.Text.strip():
112 |             markdown_lines.append("")
113 |             continue
114 | 
115 |         # Check if paragraph is a heading based on font size
116 |         font_size = para.Font.Size
117 |         is_bold = para.Font.Bold
118 |         line = ""
119 | 
120 |         # Handle bulleted lists
121 |         if para.ParagraphFormat.Bullet.Type == 1:  # Bullet
122 |             line = "- "
123 |         # Handle numbered lists
124 |         elif para.ParagraphFormat.Bullet.Type == 2:  # Numbered
125 |             line = "1. "  # We'll use 1. for all items since PowerPoint maintains the actual numbering
126 | 
127 |         # Handle headers (larger font with bold)
128 |         elif font_size >= 20 and is_bold:
129 |             # Inverse of font size calculation in add_formatted_content
130 |             heading_level = int((30 - font_size) // 2)
131 |             line = "#" * heading_level + " "
132 | 
133 |         # Process text with formatting
134 |         line += extract_text_with_formatting(para)
135 |         markdown_lines.append(line)
136 | 
137 |     return "\n".join(markdown_lines)
138 | 
139 | 
140 | def extract_text_with_formatting(text_range) -> str:
141 |     # For simple cases where there's no mixed formatting
142 |     if text_range.Characters().Count <= 1:
143 |         return text_range.Text
144 | 
145 |     # For performance, first check if there's any formatting at all
146 |     has_formatting = False
147 |     for i in range(1, text_range.Characters().Count + 1):
148 |         char = text_range.Characters(i)
149 |         if char.Font.Bold or char.Font.Italic:
150 |             has_formatting = True
151 |             break
152 | 
153 |     # If no formatting, return the plain text
154 |     if not has_formatting:
155 |         return text_range.Text
156 | 
157 |     formatted_text = []
158 |     current_run = {"text": "", "bold": False, "italic": False}
159 |     for i in range(1, text_range.Characters().Count + 1):
160 |         char = text_range.Characters(i)
161 |         char_text = char.Text
162 | 
163 |         # Skip null characters or carriage returns
164 |         if not char_text or ord(char_text) == 13:
165 |             continue
166 | 
167 |         is_bold = char.Font.Bold
168 |         is_italic = char.Font.Italic
169 |         # If formatting changed, start a new run
170 |         if is_bold != current_run["bold"] or is_italic != current_run["italic"]:
171 |             # Finish the previous run if it exists
172 |             if current_run["text"]:
173 |                 if current_run["bold"] and current_run["italic"]:
174 |                     formatted_text.append(f"***{current_run['text']}***")
175 |                 elif current_run["bold"]:
176 |                     formatted_text.append(f"**{current_run['text']}**")
177 |                 elif current_run["italic"]:
178 |                     formatted_text.append(f"*{current_run['text']}*")
179 |                 else:
180 |                     formatted_text.append(current_run["text"])
181 | 
182 |             current_run = {"text": char_text, "bold": is_bold, "italic": is_italic}
183 |         else:
184 |             current_run["text"] += char_text
185 | 
186 |     # Process the final run
187 |     if current_run["text"]:
188 |         if current_run["bold"] and current_run["italic"]:
189 |             formatted_text.append(f"***{current_run['text']}***")
190 |         elif current_run["bold"]:
191 |             formatted_text.append(f"**{current_run['text']}**")
192 |         elif current_run["italic"]:
193 |             formatted_text.append(f"*{current_run['text']}*")
194 |         else:
195 |             formatted_text.append(current_run["text"])
196 | 
197 |     return "".join(formatted_text)
198 | 
199 | 
200 | def write_markdown(document, markdown_text: str) -> None:
201 |     """
202 |     Converts markdown text to PowerPoint slides with themes.
203 |     Uses a structured format with <slide> tags to define slides.
204 | 
205 |     Format:
206 |     <slide index=1 layout=<"title"|"section header"|"two content"|"title and content">
207 |     <title>Title text</title>
208 |     <content>Markdown content</content>
209 |     <content>Second Markdown content - use only if two content is chosen </content>
210 |     </slide>
211 |     <slide index=2 layout=<"title"|"section header"|"two content"|"title and content">
212 |     ...
213 |     </slide>
214 |     ...
215 | 
216 |     Other syntax and settings:
217 |     - Heading has no Markdown formatting applied
218 |     - Content is treated as Markdown, with paragraphs, H2, H3, bold, italics, numbered lists, and bullet points supported.
219 |     - Second content is only used if layout is "two content" and ignored otherwise.
220 |     """
221 |     # Clear all slides and sections
222 |     try:
223 |         document.Slides.Range().Delete()
224 |         if document.SectionProperties.Count > 0:
225 |             for i in range(document.SectionProperties.Count, 0, -1):
226 |                 document.SectionProperties.Delete(i, True)
227 |     except Exception as e:
228 |         print(f"Error deleting slides: {e}")
229 | 
230 |     # Create mappings to PowerPoint slide layouts types
231 |     layouts = {
232 |         "title": 1,  # Title Slide
233 |         "title_and_content": 2,  # Title and Content
234 |         "two_content": 3,  # Two Content
235 |         "section_header": 33,  # Section Header
236 |     }
237 | 
238 |     slide_pattern = re.compile(r'<slide\s+index=(?:"?(\d+)"?)\s+layout="([^"]+)">(.*?)</slide>', re.DOTALL)
239 |     title_pattern = re.compile(r"<title>(.*?)</title>", re.DOTALL)
240 |     content_pattern = re.compile(r"<content>(.*?)</content>", re.DOTALL)
241 | 
242 |     slides = slide_pattern.findall(markdown_text)
243 |     for _, layout_name, slide_content in slides:
244 |         layout_name_typed = layout_name.lower()
245 |         # Use default layout if not recognized
246 |         if layout_name_typed not in layouts:
247 |             layout_name_typed = "title_and_content"
248 | 
249 |         # Extract title and content
250 |         title_match = title_pattern.search(slide_content)
251 |         title_text = title_match.group(1).strip() if title_match else ""
252 | 
253 |         # Extract content sections
254 |         content_matches = content_pattern.findall(slide_content)
255 |         content_text = content_matches[0].strip() if content_matches else ""
256 |         second_content_text = content_matches[1].strip() if len(content_matches) > 1 else ""
257 | 
258 |         # Create a new slide with the specified layout
259 |         slide_layout_index = layouts[layout_name_typed]
260 |         slide = document.Slides.Add(document.Slides.Count + 1, slide_layout_index)
261 | 
262 |         # Add title if it exists
263 |         if title_text and slide.Shapes.HasTitle:
264 |             slide.Shapes.Title.TextFrame.TextRange.Text = title_text
265 | 
266 |         # Process content based on layout type
267 |         if layout_name_typed == "title":
268 |             if content_text and slide.Shapes.Count >= 2:
269 |                 subtitle_shape = slide.Shapes[1]
270 |                 if hasattr(subtitle_shape, "TextFrame"):
271 |                     add_formatted_content(subtitle_shape.TextFrame, content_text)
272 | 
273 |         elif layout_name_typed == "section_header":
274 |             if content_text and slide.Shapes.Count >= 2:
275 |                 description_shape = slide.Shapes[1]
276 |                 if hasattr(description_shape, "TextFrame"):
277 |                     add_formatted_content(description_shape.TextFrame, content_text)
278 | 
279 |         elif layout_name_typed == "title_and_content":
280 |             if content_text and slide.Shapes.Count >= 2:
281 |                 content_shape = slide.Shapes[1]
282 |                 if hasattr(content_shape, "TextFrame"):
283 |                     add_formatted_content(content_shape.TextFrame, content_text)
284 | 
285 |         elif layout_name_typed == "two_content":
286 |             if content_text and slide.Shapes.Count >= 2:
287 |                 left_content_shape = slide.Shapes[1]
288 |                 if hasattr(left_content_shape, "TextFrame"):
289 |                     add_formatted_content(left_content_shape.TextFrame, content_text)
290 | 
291 |             if second_content_text and slide.Shapes.Count >= 3:
292 |                 right_content_shape = slide.Shapes[2]
293 |                 if hasattr(right_content_shape, "TextFrame"):
294 |                     add_formatted_content(right_content_shape.TextFrame, second_content_text)
295 | 
296 | 
297 | def add_formatted_content(text_frame, content: str) -> None:
298 |     """
299 |     Adds formatted content to a PowerPoint text frame.
300 |     Supports Markdown formatting including headers, lists, bold, and italic.
301 | 
302 |     Args:
303 |         text_frame: PowerPoint TextFrame object
304 |         content: Markdown-formatted text to add
305 |     """
306 |     # Clear existing text
307 |     text_frame.TextRange.Text = ""
308 |     text_range = text_frame.TextRange
309 |     text_range.ParagraphFormat.Bullet.Type = 0
310 | 
311 |     # Split content into paragraphs
312 |     paragraphs = content.split("\n")
313 | 
314 |     # Track if we're in a list and what type
315 |     in_bulleted_list = False
316 |     in_numbered_list = False
317 |     for i, paragraph in enumerate(paragraphs):
318 |         # Skip empty paragraphs and clear formatting
319 |         if not paragraph.strip():
320 |             if i < len(paragraphs) - 1:
321 |                 text_range.InsertAfter("\r")
322 |             continue
323 | 
324 |         # Insert paragraph break for non-first paragraphs
325 |         if i > 0:
326 |             # Use newline for paragraph break in PowerPoint
327 |             text_range.InsertAfter("\r")
328 | 
329 |             # Access the end of what was just inserted
330 |             text_length = len(text_frame.TextRange.Text)
331 |             text_range = text_frame.TextRange.Characters(text_length + 1)
332 | 
333 |         # Check for headers (## Header)
334 |         if paragraph.strip().startswith("#"):
335 |             # Clear any previous formatting for headers
336 |             text_range.ParagraphFormat.Bullet.Type = 0
337 |             in_bulleted_list = False
338 |             in_numbered_list = False
339 | 
340 |             # Calculate header level
341 |             header_level = 0
342 |             for char in paragraph:
343 |                 if char == "#":
344 |                     header_level += 1
345 |                 else:
346 |                     break
347 | 
348 |             header_text = paragraph[header_level:].strip()
349 | 
350 |             # Set header formatting before inserting text
351 |             text_range.Font.Bold = True
352 |             text_range.Font.Size = 30 - (header_level * 2) if header_level > 1 else 30
353 | 
354 |             format_and_insert_text(text_range, header_text)
355 |             continue
356 | 
357 |         # Check for bullet points
358 |         if paragraph.strip().startswith(("- ", "* ")):
359 |             # Extract content after bullet
360 |             bullet_text = paragraph.strip()[2:].strip()
361 | 
362 |             # Apply bullet formatting to the paragraph
363 |             text_range.ParagraphFormat.Bullet.Type = 1
364 | 
365 |             # Reset text formatting for bullet content
366 |             text_range.Font.Size = 18
367 |             text_range.Font.Bold = False
368 |             text_range.Font.Italic = False
369 | 
370 |             in_bulleted_list = True
371 |             in_numbered_list = False
372 | 
373 |             format_and_insert_text(text_range, bullet_text)
374 |             continue
375 | 
376 |         # Check for numbered lists
377 |         numbered_match = re.match(r"^\s*(\d+)[\.\)]\s+(.*)", paragraph.strip())
378 |         if numbered_match:
379 |             # Extract content after number
380 |             number_text = numbered_match.group(2).strip()
381 | 
382 |             # Apply numbered list formatting to the paragraph
383 |             text_range.ParagraphFormat.Bullet.Type = 2
384 | 
385 |             # Reset text formatting for numbered content
386 |             text_range.Font.Size = 18
387 |             text_range.Font.Bold = False
388 |             text_range.Font.Italic = False
389 | 
390 |             in_numbered_list = True
391 |             in_bulleted_list = False
392 | 
393 |             format_and_insert_text(text_range, number_text)
394 |             continue
395 | 
396 |         # For regular paragraphs (not headers or list items)
397 |         if in_bulleted_list or in_numbered_list:
398 |             text_range.ParagraphFormat.Bullet.Type = 0
399 |             in_bulleted_list = False
400 |             in_numbered_list = False
401 | 
402 |         text_range.Font.Size = 18
403 |         text_range.Font.Bold = False
404 |         text_range.Font.Italic = False
405 | 
406 |         format_and_insert_text(text_range, paragraph)
407 | 
408 | 
409 | def format_and_insert_text(text_range, text: str) -> None:
410 |     """
411 |     Formats and inserts text with markdown-style formatting.
412 |     Supports bold, italic, and bold+italic.
413 |     """
414 |     # First parse the text into formatted segments
415 |     segments = []
416 |     i = 0
417 |     while i < len(text):
418 |         # Bold+Italic (***text***)
419 |         if i + 2 < len(text) and text[i : i + 3] == "***" and "***" in text[i + 3 :]:
420 |             end_pos = text.find("***", i + 3)
421 |             if end_pos != -1:
422 |                 segments.append(("bold_italic", text[i + 3 : end_pos]))
423 |                 i = end_pos + 3
424 |                 continue
425 | 
426 |         # Bold (**text**)
427 |         elif i + 1 < len(text) and text[i : i + 2] == "**" and "**" in text[i + 2 :]:
428 |             end_pos = text.find("**", i + 2)
429 |             if end_pos != -1:
430 |                 segments.append(("bold", text[i + 2 : end_pos]))
431 |                 i = end_pos + 2
432 |                 continue
433 | 
434 |         # Italic (*text*)
435 |         elif text[i] == "*" and i + 1 < len(text) and text[i + 1] != "*" and "*" in text[i + 1 :]:
436 |             end_pos = text.find("*", i + 1)
437 |             if end_pos != -1:
438 |                 segments.append(("italic", text[i + 1 : end_pos]))
439 |                 i = end_pos + 1
440 |                 continue
441 | 
442 |         # Find the next special marker or end of string
443 |         next_marker = len(text)
444 |         for marker in ["***", "**", "*"]:
445 |             pos = text.find(marker, i)
446 |             if pos != -1 and pos < next_marker:
447 |                 next_marker = pos
448 | 
449 |         # Add plain text segment
450 |         if next_marker == len(text):
451 |             segments.append(("plain", text[i:]))
452 |             break
453 |         else:
454 |             segments.append(("plain", text[i:next_marker]))
455 |             i = next_marker
456 | 
457 |     # If no formatting needed, just insert the whole text
458 |     if all(segment[0] == "plain" for segment in segments):
459 |         text_range.InsertAfter("".join(segment[1] for segment in segments))
460 |         return
461 | 
462 |     # Insert all text first as plain text
463 |     full_text = "".join(segment[1] for segment in segments)
464 |     start_pos = len(text_range.Text)
465 |     text_range.InsertAfter(full_text)
466 | 
467 |     # Now apply formatting to specific parts
468 |     current_pos = start_pos
469 |     for format_type, content in segments:
470 |         if not content or format_type == "plain":
471 |             current_pos += len(content)
472 |             continue
473 | 
474 |         # Select the range to format
475 |         format_range = text_range.Characters(
476 |             current_pos + 1,  # Start (1-indexed)
477 |             len(content),  # Length
478 |         )
479 | 
480 |         # Apply formatting
481 |         if format_type in ("bold", "bold_italic"):
482 |             format_range.Font.Bold = True
483 | 
484 |         if format_type in ("italic", "bold_italic"):
485 |             format_range.Font.Italic = True
486 | 
487 |         current_pos += len(content)
488 | 
489 |     # Reset the text_range to point to the end for future insertions
490 |     text_length = len(text_range.Text)
491 |     text_range = text_range.Characters(text_length)
492 | 
```

--------------------------------------------------------------------------------
/libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/assistant_service.py:
--------------------------------------------------------------------------------

```python
  1 | import asyncio
  2 | import logging
  3 | import random
  4 | from abc import ABC, abstractmethod
  5 | from contextlib import AsyncExitStack, asynccontextmanager
  6 | from typing import (
  7 |     IO,
  8 |     Annotated,
  9 |     AsyncContextManager,
 10 |     AsyncGenerator,
 11 |     AsyncIterator,
 12 |     Callable,
 13 |     NoReturn,
 14 |     Optional,
 15 | )
 16 | 
 17 | import asgi_correlation_id
 18 | import backoff
 19 | import backoff.types
 20 | import httpx
 21 | import semantic_workbench_api_model.workbench_service_client
 22 | from fastapi import (
 23 |     FastAPI,
 24 |     File,
 25 |     Form,
 26 |     HTTPException,
 27 |     Request,
 28 |     Response,
 29 |     UploadFile,
 30 |     status,
 31 | )
 32 | from fastapi.encoders import jsonable_encoder
 33 | from fastapi.exception_handlers import http_exception_handler
 34 | from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
 35 | from pydantic import BaseModel, HttpUrl, ValidationError
 36 | from semantic_workbench_api_model import (
 37 |     assistant_model,
 38 |     workbench_model,
 39 |     workbench_service_client,
 40 | )
 41 | from starlette.exceptions import HTTPException as StarletteHTTPException
 42 | 
 43 | from . import auth, settings
 44 | from .logging_config import log_request_middleware
 45 | 
 46 | logger = logging.getLogger(__name__)
 47 | 
 48 | 
 49 | def _backoff_success_handler(details: backoff.types.Details) -> None:
 50 |     if details["tries"] == 1:
 51 |         return
 52 |     logger.info(
 53 |         "Success after backoff %s(...); tries: %d, elapsed: %.1fs",
 54 |         details["target"].__name__,
 55 |         details["tries"],
 56 |         details["elapsed"],
 57 |     )
 58 | 
 59 | 
 60 | class FastAPIAssistantService(ABC):
 61 |     """
 62 |     Base class for implementations of assistant services using FastAPI.
 63 |     """
 64 | 
 65 |     def __init__(
 66 |         self,
 67 |         service_id: str,
 68 |         service_name: str,
 69 |         service_description: str,
 70 |         register_lifespan_handler: Callable[[Callable[[], AsyncContextManager[None]]], None],
 71 |     ) -> None:
 72 |         self._service_id = service_id
 73 |         self._service_name = service_name
 74 |         self._service_description = service_description
 75 |         self._workbench_httpx_client = httpx.AsyncClient(
 76 |             transport=semantic_workbench_api_model.workbench_service_client.httpx_transport_factory(),
 77 |             timeout=httpx.Timeout(5.0, connect=10.0, read=60.0),
 78 |             base_url=str(settings.workbench_service_url),
 79 |         )
 80 | 
 81 |         @asynccontextmanager
 82 |         async def lifespan() -> AsyncIterator[None]:
 83 |             logger.info(
 84 |                 "connecting to semantic-workbench-service; workbench_service_url: %s, assistant_service_id: %s, callback_url: %s",
 85 |                 settings.workbench_service_url,
 86 |                 self.service_id,
 87 |                 settings.callback_url,
 88 |             )
 89 | 
 90 |             service_client = self.workbench_client.for_service()
 91 |             # start periodic pings to workbench
 92 |             ping_task = asyncio.create_task(
 93 |                 self._periodically_ping_semantic_workbench(service_client), name="ping-workbench"
 94 |             )
 95 | 
 96 |             try:
 97 |                 yield
 98 | 
 99 |             finally:
100 |                 ping_task.cancel()
101 |                 try:
102 |                     await ping_task
103 |                 except asyncio.CancelledError:
104 |                     pass
105 | 
106 |         register_lifespan_handler(lifespan)
107 | 
108 |     async def _periodically_ping_semantic_workbench(
109 |         self, client: workbench_service_client.AssistantServiceAPIClient
110 |     ) -> NoReturn:
111 |         while True:
112 |             try:
113 |                 try:
114 |                     await self._ping_semantic_workbench(client)
115 |                 except httpx.HTTPError:
116 |                     logger.exception("ping to workbench failed")
117 | 
118 |                 jitter = random.uniform(0, settings.workbench_service_ping_interval_seconds / 2.0)
119 |                 await asyncio.sleep(settings.workbench_service_ping_interval_seconds + jitter)
120 | 
121 |             except Exception:
122 |                 logger.exception("unexpected error in ping loop")
123 | 
124 |     @backoff.on_exception(
125 |         backoff.expo,
126 |         httpx.HTTPError,
127 |         max_time=30,
128 |         logger=logger,
129 |         on_success=_backoff_success_handler,
130 |     )
131 |     async def _ping_semantic_workbench(self, client: workbench_service_client.AssistantServiceAPIClient) -> None:
132 |         try:
133 |             await client.update_registration_url(
134 |                 assistant_service_id=self.service_id,
135 |                 update=workbench_model.UpdateAssistantServiceRegistrationUrl(
136 |                     name=self.service_name,
137 |                     description=self.service_description,
138 |                     url=HttpUrl(settings.callback_url),
139 |                     online_expires_in_seconds=settings.workbench_service_ping_interval_seconds * 3.5,
140 |                 ),
141 |             )
142 | 
143 |         except httpx.HTTPStatusError as e:
144 |             # log additional information for common error cases
145 |             match e.response.status_code:
146 |                 case 401:
147 |                     logger.warning(
148 |                         "authentication failed with semantic-workbench service, configured assistant_service_id and/or"
149 |                         " workbench_service_api_key are incorrect; workbench_service_url: %s,"
150 |                         " assistant_service_id: %s, callback_url: %s",
151 |                         settings.workbench_service_url,
152 |                         self.service_id,
153 |                         settings.callback_url,
154 |                     )
155 |                 case 404:
156 |                     logger.warning(
157 |                         "configured assistant_service_id does not exist in the semantic-workbench-service;"
158 |                         " workbench_service_url: %s, assistant_service_id: %s, callback_url: %s",
159 |                         settings.workbench_service_url,
160 |                         self.service_id,
161 |                         settings.callback_url,
162 |                     )
163 |             raise
164 | 
165 |     @property
166 |     def service_id(self) -> str:
167 |         return settings.assistant_service_id if settings.assistant_service_id is not None else self._service_id
168 | 
169 |     @property
170 |     def service_name(self) -> str:
171 |         return settings.assistant_service_name if settings.assistant_service_name is not None else self._service_name
172 | 
173 |     @property
174 |     def service_description(self) -> str:
175 |         return (
176 |             settings.assistant_service_description
177 |             if settings.assistant_service_description is not None
178 |             else self._service_description
179 |         )
180 | 
181 |     @property
182 |     def workbench_client(self) -> workbench_service_client.WorkbenchServiceClientBuilder:
183 |         return workbench_service_client.WorkbenchServiceClientBuilder(
184 |             assistant_service_id=self.service_id,
185 |             api_key=settings.workbench_service_api_key,
186 |             httpx_client=self._workbench_httpx_client,
187 |         )
188 | 
189 |     @abstractmethod
190 |     async def get_service_info(self) -> assistant_model.ServiceInfoModel:
191 |         pass
192 | 
193 |     @abstractmethod
194 |     async def put_assistant(
195 |         self,
196 |         assistant_id: str,
197 |         assistant: assistant_model.AssistantPutRequestModel,
198 |         from_export: Optional[IO[bytes]] = None,
199 |     ) -> assistant_model.AssistantResponseModel:
200 |         pass
201 | 
202 |     @abstractmethod
203 |     async def export_assistant_data(
204 |         self, assistant_id: str
205 |     ) -> StreamingResponse | FileResponse | JSONResponse | BaseModel:
206 |         pass
207 | 
208 |     @abstractmethod
209 |     async def get_assistant(self, assistant_id: str) -> assistant_model.AssistantResponseModel:
210 |         pass
211 | 
212 |     @abstractmethod
213 |     async def delete_assistant(self, assistant_id: str) -> None:
214 |         pass
215 | 
216 |     @abstractmethod
217 |     async def get_config(self, assistant_id: str) -> assistant_model.ConfigResponseModel:
218 |         pass
219 | 
220 |     @abstractmethod
221 |     async def put_config(
222 |         self, assistant_id: str, updated_config: assistant_model.ConfigPutRequestModel
223 |     ) -> assistant_model.ConfigResponseModel:
224 |         pass
225 | 
226 |     @abstractmethod
227 |     async def put_conversation(
228 |         self,
229 |         assistant_id: str,
230 |         conversation_id: str,
231 |         conversation: assistant_model.ConversationPutRequestModel,
232 |         from_export: Optional[IO[bytes]] = None,
233 |     ) -> assistant_model.ConversationResponseModel:
234 |         pass
235 | 
236 |     @abstractmethod
237 |     async def export_conversation_data(
238 |         self,
239 |         assistant_id: str,
240 |         conversation_id: str,
241 |     ) -> StreamingResponse | FileResponse | JSONResponse | BaseModel:
242 |         pass
243 | 
244 |     @abstractmethod
245 |     async def get_conversation(
246 |         self, assistant_id: str, conversation_id: str
247 |     ) -> assistant_model.ConversationResponseModel:
248 |         pass
249 | 
250 |     @abstractmethod
251 |     async def delete_conversation(self, assistant_id: str, conversation_id: str) -> None:
252 |         pass
253 | 
254 |     @abstractmethod
255 |     async def post_conversation_event(
256 |         self,
257 |         assistant_id: str,
258 |         conversation_id: str,
259 |         event: workbench_model.ConversationEvent,
260 |     ) -> None:
261 |         pass
262 | 
263 |     @abstractmethod
264 |     async def get_conversation_state_descriptions(
265 |         self, assistant_id: str, conversation_id: str
266 |     ) -> assistant_model.StateDescriptionListResponseModel:
267 |         pass
268 | 
269 |     @abstractmethod
270 |     async def get_conversation_state(
271 |         self, assistant_id: str, conversation_id: str, state_id: str
272 |     ) -> assistant_model.StateResponseModel:
273 |         pass
274 | 
275 |     @abstractmethod
276 |     async def put_conversation_state(
277 |         self,
278 |         assistant_id: str,
279 |         conversation_id: str,
280 |         state_id: str,
281 |         updated_state: assistant_model.StatePutRequestModel,
282 |     ) -> assistant_model.StateResponseModel:
283 |         pass
284 | 
285 | 
286 | def _assistant_service_api(
287 |     app: FastAPI,
288 |     service: FastAPIAssistantService,
289 |     enable_auth_middleware: bool = True,
290 | ):
291 |     """
292 |     Implements API for AssistantService, forwarding requests to AssistantService.
293 |     """
294 | 
295 |     if enable_auth_middleware:
296 |         app.add_middleware(
297 |             middleware_class=auth.AuthMiddleware,
298 |             exclude_methods={"OPTIONS"},
299 |             exclude_paths=set(settings.anonymous_paths),
300 |         )
301 |     app.add_middleware(asgi_correlation_id.CorrelationIdMiddleware)
302 |     app.middleware("http")(log_request_middleware())
303 | 
304 |     @app.exception_handler(StarletteHTTPException)
305 |     async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException) -> Response:
306 |         if 500 <= exc.status_code < 600:
307 |             logger.exception(
308 |                 "exception in request handler; method: %s, path: %s", request.method, request.url.path, exc_info=exc
309 |             )
310 |         return await http_exception_handler(request, exc)
311 | 
312 |     @app.get("/", description="Get the description of the assistant service")
313 |     async def get_service_description(response: Response) -> assistant_model.ServiceInfoModel:
314 |         response.headers["Cache-Control"] = "max-age=600"
315 |         return await service.get_service_info()
316 | 
317 |     @app.put(
318 |         "/{assistant_id}",
319 |         description=(
320 |             "Connect an assistant to the workbench, optionally providing exported-data to restore the assistant"
321 |         ),
322 |     )
323 |     async def put_assistant(
324 |         assistant_id: str,
325 |         assistant_json: Annotated[str, Form(alias="assistant")],
326 |         from_export: Annotated[Optional[UploadFile], File(alias="from_export")] = None,
327 |     ) -> assistant_model.AssistantResponseModel:
328 |         try:
329 |             assistant_request = assistant_model.AssistantPutRequestModel.model_validate_json(assistant_json)
330 |         except ValidationError as e:
331 |             raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=e.errors())
332 | 
333 |         if from_export:
334 |             return await service.put_assistant(assistant_id, assistant_request, from_export.file)
335 | 
336 |         return await service.put_assistant(assistant_id, assistant_request)
337 | 
338 |     @app.get(
339 |         "/{assistant_id}",
340 |         description="Get an assistant",
341 |     )
342 |     async def get_assistant(assistant_id: str) -> assistant_model.AssistantResponseModel:
343 |         return await service.get_assistant(assistant_id)
344 | 
345 |     @app.delete(
346 |         "/{assistant_id}",
347 |         description="Delete an assistant",
348 |     )
349 |     async def delete_assistant(assistant_id: str) -> None:
350 |         return await service.delete_assistant(assistant_id)
351 | 
352 |     @app.get(
353 |         "/{assistant_id}/export-data",
354 |         description="Export all data for this assistant",
355 |     )
356 |     async def export_assistant_data(assistant_id: str) -> Response:
357 |         response = await service.export_assistant_data(assistant_id)
358 |         match response:
359 |             case StreamingResponse() | FileResponse() | JSONResponse():
360 |                 return response
361 |             case BaseModel():
362 |                 return JSONResponse(jsonable_encoder(response))
363 |             case _:
364 |                 raise TypeError(f"Unexpected response type {type(response)}")
365 | 
366 |     @app.get(
367 |         "/{assistant_id}/config",
368 |         description="Get config for this assistant",
369 |     )
370 |     async def get_config(assistant_id: str) -> assistant_model.ConfigResponseModel:
371 |         return await service.get_config(assistant_id)
372 | 
373 |     @app.put(
374 |         "/{assistant_id}/config",
375 |         description="Set config for this assistant",
376 |     )
377 |     async def put_config(
378 |         assistant_id: str, updated_config: assistant_model.ConfigPutRequestModel
379 |     ) -> assistant_model.ConfigResponseModel:
380 |         return await service.put_config(assistant_id, updated_config=updated_config)
381 | 
382 |     @app.put(
383 |         "/{assistant_id}/conversations/{conversation_id}",
384 |         description=(
385 |             "Join an assistant to a workbench conversation, optionally"
386 |             " providing exported-data to restore the conversation"
387 |         ),
388 |     )
389 |     async def put_conversation(
390 |         assistant_id: str,
391 |         conversation_id: str,
392 |         conversation_json: Annotated[str, Form(alias="conversation")],
393 |         from_export: Annotated[Optional[UploadFile], File(alias="from_export")] = None,
394 |     ) -> assistant_model.ConversationResponseModel:
395 |         try:
396 |             conversation = assistant_model.ConversationPutRequestModel.model_validate_json(conversation_json)
397 |         except ValidationError as e:
398 |             raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=e.errors())
399 | 
400 |         if from_export:
401 |             return await service.put_conversation(assistant_id, conversation_id, conversation, from_export.file)
402 | 
403 |         return await service.put_conversation(assistant_id, conversation_id, conversation)
404 | 
405 |     @app.get(
406 |         "/{assistant_id}/conversations/{conversation_id}",
407 |         description="Get the status of a conversation",
408 |     )
409 |     async def get_conversation(assistant_id: str, conversation_id: str) -> assistant_model.ConversationResponseModel:
410 |         return await service.get_conversation(assistant_id, conversation_id)
411 | 
412 |     @app.delete(
413 |         "/{assistant_id}/conversations/{conversation_id}",
414 |         description="Delete a conversation",
415 |     )
416 |     async def delete_conversation(assistant_id: str, conversation_id: str) -> None:
417 |         return await service.delete_conversation(assistant_id, conversation_id)
418 | 
419 |     @app.get(
420 |         "/{assistant_id}/conversations/{conversation_id}/export-data",
421 |         description="Export all data for a conversation",
422 |     )
423 |     async def export_conversation_data(assistant_id: str, conversation_id: str) -> Response:
424 |         response = await service.export_conversation_data(assistant_id=assistant_id, conversation_id=conversation_id)
425 |         match response:
426 |             case StreamingResponse():
427 |                 return response
428 |             case FileResponse():
429 |                 return response
430 |             case JSONResponse():
431 |                 return response
432 |             case BaseModel():
433 |                 return JSONResponse(jsonable_encoder(response))
434 |             case _:
435 |                 raise TypeError(f"Unexpected response type {type(response)}")
436 | 
437 |     @app.post(
438 |         "/{assistant_id}/conversations/{conversation_id}/events",
439 |         description="Notify assistant of an event in the conversation",
440 |         status_code=status.HTTP_204_NO_CONTENT,
441 |     )
442 |     async def post_conversation_event(
443 |         assistant_id: str,
444 |         conversation_id: str,
445 |         event: workbench_model.ConversationEvent,
446 |     ) -> None:
447 |         return await service.post_conversation_event(assistant_id, conversation_id, event)
448 | 
449 |     @app.get(
450 |         "/{assistant_id}/conversations/{conversation_id}/states",
451 |         description="Get the descriptions of the states available for a conversation",
452 |     )
453 |     async def get_conversation_state_descriptions(
454 |         assistant_id: str, conversation_id: str
455 |     ) -> assistant_model.StateDescriptionListResponseModel:
456 |         return await service.get_conversation_state_descriptions(assistant_id, conversation_id)
457 | 
458 |     @app.get(
459 |         "/{assistant_id}/conversations/{conversation_id}/states/{state_id}",
460 |         description="Get a specific state by id for a conversation",
461 |     )
462 |     async def get_conversation_state(
463 |         assistant_id: str, conversation_id: str, state_id: str
464 |     ) -> assistant_model.StateResponseModel:
465 |         return await service.get_conversation_state(assistant_id, conversation_id, state_id)
466 | 
467 |     @app.put(
468 |         "/{assistant_id}/conversations/{conversation_id}/states/{state_id}",
469 |         description="Update a specific state by id for a conversation",
470 |     )
471 |     async def put_conversation_state(
472 |         assistant_id: str,
473 |         conversation_id: str,
474 |         state_id: str,
475 |         updated_state: assistant_model.StatePutRequestModel,
476 |     ) -> assistant_model.StateResponseModel:
477 |         return await service.put_conversation_state(assistant_id, conversation_id, state_id, updated_state)
478 | 
479 | 
480 | logger = logging.getLogger(__name__)
481 | 
482 | 
483 | class FastAPILifespan:
484 |     def __init__(self) -> None:
485 |         self._lifecycle_handlers: list[Callable[[], AsyncContextManager[None]]] = []
486 | 
487 |     def register_handler(self, handler: Callable[[], AsyncContextManager[None]]) -> None:
488 |         self._lifecycle_handlers.append(handler)
489 | 
490 |     @asynccontextmanager
491 |     async def lifespan(self, app: FastAPI) -> AsyncGenerator[None, None]:
492 |         async with AsyncExitStack() as stack:
493 |             logger.debug("app lifespan starting up; title: %s, version: %s", app.title, app.version)
494 | 
495 |             for handler in self._lifecycle_handlers:
496 |                 await stack.enter_async_context(handler())
497 | 
498 |             logger.info("app lifespan started; title: %s, version: %s", app.title, app.version)
499 | 
500 |             try:
501 |                 yield
502 |             finally:
503 |                 logger.debug("app lifespan shutting down; title: %s, version: %s", app.title, app.version)
504 | 
505 |         logger.info("app lifespan shut down; title: %s, version: %s", app.title, app.version)
506 | 
507 | 
508 | def create_app(
509 |     factory: Callable[[FastAPILifespan], FastAPIAssistantService],
510 |     enable_auth_middleware: bool = True,
511 | ) -> FastAPI:
512 |     """
513 |     Create a FastAPI app for an AssistantService.
514 |     """
515 |     lifespan = FastAPILifespan()
516 |     svc = factory(lifespan)
517 |     app = FastAPI(
518 |         lifespan=lifespan.lifespan,
519 |         title=svc.service_name,
520 |         description=svc.service_description,
521 |         # extra is used to store metadata about the service
522 |         assistant_service_id=svc.service_id,
523 |     )
524 |     _assistant_service_api(app, svc, enable_auth_middleware)
525 |     return app
526 | 
```
Page 94/145FirstPrevNextLast