This is page 97 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
--------------------------------------------------------------------------------
/mcp-servers/mcp-server-filesystem-edit/mcp_server_filesystem_edit/tools/edit.py:
--------------------------------------------------------------------------------
```python
1 | # Copyright (c) Microsoft. All rights reserved.
2 |
3 | import json
4 | import logging
5 | import re
6 |
7 | from mcp.server.fastmcp import Context
8 | from mcp_extensions.llm.chat_completion import chat_completion
9 | from mcp_extensions.llm.helpers import compile_messages
10 | from mcp_extensions.llm.llm_types import ChatCompletionRequest, ChatCompletionResponse, MessageT, UserMessage
11 |
12 | from mcp_server_filesystem_edit import settings
13 | from mcp_server_filesystem_edit.prompts.latex_edit import LATEX_EDIT_REASONING_MESSAGES
14 | from mcp_server_filesystem_edit.prompts.markdown_draft import MD_DRAFT_REASONING_MESSAGES
15 | from mcp_server_filesystem_edit.prompts.markdown_edit import (
16 | MARKDOWN_EDIT_FORMAT_INSTRUCTIONS,
17 | MD_EDIT_CHANGES_MESSAGES,
18 | MD_EDIT_CONVERT_MESSAGES,
19 | MD_EDIT_REASONING_MESSAGES,
20 | MD_EDIT_TOOL_DEF,
21 | MD_EDIT_TOOL_NAME,
22 | SEND_MESSAGE_TOOL_DEF,
23 | SEND_MESSAGE_TOOL_NAME,
24 | WORD_EDIT_FORMAT_INSTRUCTIONS,
25 | )
26 | from mcp_server_filesystem_edit.prompts.powerpoint_edit import PPT_EDIT_REASONING_MESSAGES, PPT_EDIT_TOOL_DEF
27 | from mcp_server_filesystem_edit.tools.edit_adapters.common import execute_tools, format_blocks_for_llm
28 | from mcp_server_filesystem_edit.tools.edit_adapters.latex import blockify as latex_blockify
29 | from mcp_server_filesystem_edit.tools.edit_adapters.latex import unblockify as latex_unblockify
30 | from mcp_server_filesystem_edit.tools.edit_adapters.markdown import blockify as markdown_blockify
31 | from mcp_server_filesystem_edit.tools.edit_adapters.markdown import unblockify as markdown_unblockify
32 | from mcp_server_filesystem_edit.tools.helpers import TokenizerOpenAI, format_chat_history
33 | from mcp_server_filesystem_edit.types import Block, CustomContext, EditOutput, FileOpRequest, FileOpTelemetry
34 |
35 | logger = logging.getLogger(__name__)
36 |
37 | # region CommonEdit
38 |
39 |
40 | class CommonEdit:
41 | def __init__(self) -> None:
42 | self.telemetry = FileOpTelemetry()
43 | self.tokenizer = TokenizerOpenAI(model="gpt-4o")
44 |
45 | async def blockify(self, request: FileOpRequest) -> list[Block]:
46 | if request.file_type == "latex":
47 | blocks = latex_blockify(request.file_content)
48 | else:
49 | blocks = markdown_blockify(request.file_content)
50 | return blocks
51 |
52 | async def unblockify(self, request: FileOpRequest, blocks: list[Block]) -> str:
53 | if request.file_type == "latex":
54 | unblockified_doc = latex_unblockify(blocks)
55 | else:
56 | unblockified_doc = markdown_unblockify(blocks)
57 | return unblockified_doc
58 |
59 | async def take_draft_path(self, request: FileOpRequest) -> bool:
60 | """
61 | Decides if we should take a separate path that will simply use a single prompt
62 | that rewrites the document in a single step rather than using editing logic.
63 |
64 | Returns True if we should take the draft path, False otherwise.
65 | """
66 | if request.file_type == "latex":
67 | return False
68 | else:
69 | # If the document is over the rewrite threshold tokens, we do not take the draft path.
70 | num_tokens = self.tokenizer.num_tokens_in_str(request.file_content)
71 | if num_tokens > settings.rewrite_threshold:
72 | return False
73 | return True
74 |
75 | async def get_draft_response(self, request: FileOpRequest) -> str:
76 | """
77 | Get the rewritten document
78 | """
79 | chat_history = ""
80 | if request.request_type == "dev" and isinstance(request.context, CustomContext):
81 | chat_history = format_chat_history(request.context.chat_history)
82 | context = ""
83 | if request.request_type == "dev" and isinstance(request.context, CustomContext):
84 | context = request.context.additional_context
85 | messages = compile_messages(
86 | messages=MD_DRAFT_REASONING_MESSAGES,
87 | variables={
88 | "knowledge_cutoff": settings.knowledge_cutoff,
89 | "current_date": settings.current_date_func(),
90 | "task": request.task,
91 | "context": context,
92 | "document": request.file_content,
93 | "chat_history": chat_history,
94 | "format_instructions": (
95 | WORD_EDIT_FORMAT_INSTRUCTIONS if request.file_type == "word" else MARKDOWN_EDIT_FORMAT_INSTRUCTIONS
96 | ),
97 | },
98 | )
99 | mcp_messages = messages
100 | if request.request_type == "mcp" and isinstance(request.context, Context):
101 | mcp_messages = [messages[0]] # Developer message
102 | mcp_messages.append(UserMessage(content=json.dumps({"variable": "history_messages"})))
103 | mcp_messages.append(messages[3]) # Document message
104 |
105 | reasoning_response = await chat_completion(
106 | request=ChatCompletionRequest(
107 | messages=mcp_messages,
108 | model=settings.draft_path_model,
109 | max_completion_tokens=20000,
110 | temperature=1,
111 | ),
112 | provider=request.request_type,
113 | client=request.context if request.request_type == "mcp" else request.chat_completion_client, # type: ignore
114 | )
115 | self.telemetry.reasoning_latency = reasoning_response.response_duration
116 | draft = reasoning_response.choices[0].message.content
117 | # Look for content between <new_document> tags, otherwise return the entire response as the doc.
118 | pattern = r"<new_document>(.*?)</new_document>"
119 | match = re.search(pattern, draft, re.DOTALL)
120 | if match:
121 | draft = match.group(1).strip()
122 | return draft
123 |
124 | async def construct_reasoning_prompt(self, request: FileOpRequest, blockified_doc: list[Block]) -> list[MessageT]:
125 | doc_for_llm = await format_blocks_for_llm(blockified_doc)
126 |
127 | chat_history = ""
128 | if request.request_type == "dev" and isinstance(request.context, CustomContext):
129 | chat_history = format_chat_history(request.context.chat_history)
130 |
131 | context = ""
132 | if request.request_type == "dev" and isinstance(request.context, CustomContext):
133 | context = request.context.additional_context
134 |
135 | reasoning_messages = compile_messages(
136 | messages=LATEX_EDIT_REASONING_MESSAGES if request.file_type == "latex" else MD_EDIT_REASONING_MESSAGES,
137 | variables={
138 | "knowledge_cutoff": settings.knowledge_cutoff,
139 | "current_date": settings.current_date_func(),
140 | "task": request.task,
141 | "context": context,
142 | "document": doc_for_llm,
143 | "chat_history": chat_history,
144 | "format_instructions": (
145 | WORD_EDIT_FORMAT_INSTRUCTIONS if request.file_type == "word" else MARKDOWN_EDIT_FORMAT_INSTRUCTIONS
146 | ),
147 | },
148 | )
149 | return reasoning_messages
150 |
151 | async def get_reasoning_response(self, request: FileOpRequest, messages: list[MessageT]) -> str:
152 | mcp_messages = messages
153 | if request.request_type == "mcp" and isinstance(request.context, Context):
154 | mcp_messages = [messages[0]] # Developer message
155 | mcp_messages.append(UserMessage(content=json.dumps({"variable": "history_messages"})))
156 | mcp_messages.append(messages[3]) # Document message
157 | reasoning_response = await chat_completion(
158 | request=ChatCompletionRequest(
159 | messages=mcp_messages,
160 | model=settings.edit_model,
161 | max_completion_tokens=20000,
162 | reasoning_effort="high",
163 | ),
164 | provider=request.request_type,
165 | client=request.context if request.request_type == "mcp" else request.chat_completion_client, # type: ignore
166 | )
167 | self.telemetry.reasoning_latency = reasoning_response.response_duration
168 | reasoning = reasoning_response.choices[0].message.content
169 | return reasoning
170 |
171 | async def construct_convert_prompt(self, reasoning: str) -> list[MessageT]:
172 | convert_messages = compile_messages(
173 | messages=MD_EDIT_CONVERT_MESSAGES,
174 | variables={"reasoning": reasoning},
175 | )
176 | return convert_messages
177 |
178 | async def get_convert_response(self, request: FileOpRequest, messages: list[MessageT]) -> ChatCompletionResponse:
179 | chat_completion_request = ChatCompletionRequest(
180 | messages=messages,
181 | model=settings.convert_tool_calls_model,
182 | temperature=0,
183 | max_completion_tokens=8000,
184 | tools=[MD_EDIT_TOOL_DEF, SEND_MESSAGE_TOOL_DEF],
185 | tool_choice="required",
186 | parallel_tool_calls=False,
187 | )
188 | convert_response = await chat_completion(
189 | request=chat_completion_request,
190 | provider=request.request_type,
191 | client=request.context if request.request_type == "mcp" else request.chat_completion_client, # type: ignore
192 | )
193 | self.telemetry.convert_latency = convert_response.response_duration
194 | return convert_response
195 |
196 | async def execute_tool_calls(
197 | self, request: FileOpRequest, convert_response: ChatCompletionResponse
198 | ) -> tuple[str, str]:
199 | updated_doc_markdown = request.file_content
200 | output_message = ""
201 | if convert_response.choices[0].message.tool_calls:
202 | tool_call = convert_response.choices[0].message.tool_calls[0].function
203 | logger.info(f"Tool call:\n{tool_call}")
204 | # If the the model called the send_message, don't update the doc and return the message
205 | if tool_call.name == SEND_MESSAGE_TOOL_NAME:
206 | output_message = settings.doc_editor_prefix + convert_response.choices[0].message.content
207 | elif tool_call.name == MD_EDIT_TOOL_NAME:
208 | tool_args = tool_call.arguments
209 | blocks = await self.blockify(request)
210 | blocks = execute_tools(blocks=blocks, edit_tool_call={"name": tool_call.name, "arguments": tool_args})
211 | updated_doc_markdown = await self.unblockify(request, blocks)
212 | else:
213 | output_message = (
214 | settings.doc_editor_prefix + "Something went wrong when editing the document and no changes were made."
215 | )
216 | return updated_doc_markdown, output_message
217 |
218 | async def run_change_summary(self, before_doc: str, after_doc: str, edit_request: FileOpRequest) -> str:
219 | change_summary_messages = compile_messages(
220 | messages=MD_EDIT_CHANGES_MESSAGES,
221 | variables={
222 | "before_doc": before_doc,
223 | "after_doc": after_doc,
224 | },
225 | )
226 | change_summary_response = await chat_completion(
227 | request=ChatCompletionRequest(
228 | messages=change_summary_messages,
229 | model=settings.summarization_model,
230 | max_completion_tokens=1000,
231 | ),
232 | provider=edit_request.request_type,
233 | client=edit_request.context if edit_request.request_type == "mcp" else edit_request.chat_completion_client, # type: ignore
234 | )
235 | self.telemetry.change_summary_latency = change_summary_response.response_duration
236 |
237 | change_summary = change_summary_response.choices[0].message.content
238 | change_summary = settings.doc_editor_prefix + change_summary
239 | return change_summary
240 |
241 | async def run(self, request: FileOpRequest) -> EditOutput:
242 | """
243 | Run the edit request and return the result.
244 | """
245 | self.telemetry.reset()
246 |
247 | if await self.take_draft_path(request):
248 | logger.info("Taking draft path instead of editing.")
249 | updated_doc_markdown = await self.get_draft_response(request)
250 | output_message = ""
251 | reasoning = ""
252 | tool_calls = []
253 | else:
254 | blockified_doc = await self.blockify(request)
255 | reasoning_messages = await self.construct_reasoning_prompt(request, blockified_doc)
256 | reasoning = await self.get_reasoning_response(request, reasoning_messages)
257 | logger.info(f"Reasoning:\n{reasoning}")
258 | convert_messages = await self.construct_convert_prompt(reasoning)
259 | convert_response = await self.get_convert_response(request, convert_messages)
260 | tool_calls = convert_response.choices[0].message.tool_calls or []
261 | updated_doc_markdown, output_message = await self.execute_tool_calls(request, convert_response)
262 |
263 | output = EditOutput(
264 | change_summary="",
265 | output_message=output_message,
266 | new_content=updated_doc_markdown,
267 | reasoning=reasoning,
268 | tool_calls=tool_calls,
269 | llm_latency=self.telemetry.reasoning_latency
270 | + self.telemetry.convert_latency
271 | + self.telemetry.change_summary_latency,
272 | )
273 | return output
274 |
275 |
276 | # endregion
277 |
278 | # region PowerpointEdit
279 |
280 |
281 | class PowerpointEdit:
282 | def __init__(self) -> None:
283 | self.telemetry = FileOpTelemetry()
284 |
285 | async def blockify(self, request: FileOpRequest) -> list[Block]:
286 | blocks = []
287 |
288 | slide_pattern = r'<slide\s+index=(?:"?(\d+)"?).*?</slide>'
289 |
290 | slide_matches = re.finditer(slide_pattern, request.file_content, re.DOTALL)
291 | for match in slide_matches:
292 | slide_content = match.group(0)
293 | slide_index = match.group(1)
294 |
295 | try:
296 | slide_index = int(slide_index)
297 | except ValueError:
298 | logger.error(f"Invalid slide number: {slide_index}")
299 | continue
300 |
301 | block = Block(
302 | id=slide_index,
303 | content=slide_content,
304 | )
305 | blocks.append(block)
306 | return blocks
307 |
308 | async def unblockify(self, blocks: list[Block]) -> str:
309 | return "".join(block.content for block in blocks)
310 |
311 | async def construct_reasoning_prompt(self, request: FileOpRequest, blockified_doc: list[Block]) -> list[MessageT]:
312 | doc_for_llm = await format_blocks_for_llm(blockified_doc)
313 |
314 | chat_history = ""
315 | if request.request_type == "dev" and isinstance(request.context, CustomContext):
316 | chat_history = format_chat_history(request.context.chat_history)
317 |
318 | context = ""
319 | if request.request_type == "dev" and isinstance(request.context, CustomContext):
320 | context = request.context.additional_context
321 |
322 | reasoning_messages = compile_messages(
323 | messages=PPT_EDIT_REASONING_MESSAGES,
324 | variables={
325 | "knowledge_cutoff": settings.knowledge_cutoff,
326 | "current_date": settings.current_date_func(),
327 | "task": request.task,
328 | "context": context,
329 | "document": doc_for_llm,
330 | "chat_history": chat_history,
331 | },
332 | )
333 | return reasoning_messages
334 |
335 | async def get_reasoning_response(self, request: FileOpRequest, messages: list[MessageT]) -> str:
336 | mcp_messages = messages
337 | if request.request_type == "mcp" and isinstance(request.context, Context):
338 | mcp_messages = [messages[0]] # Developer message
339 | mcp_messages.append(UserMessage(content=json.dumps({"variable": "history_messages"})))
340 | mcp_messages.append(messages[3]) # Document message
341 | reasoning_response = await chat_completion(
342 | request=ChatCompletionRequest(
343 | messages=mcp_messages,
344 | model=settings.edit_model,
345 | max_completion_tokens=20000,
346 | reasoning_effort="high",
347 | ),
348 | provider=request.request_type,
349 | client=request.context if request.request_type == "mcp" else request.chat_completion_client, # type: ignore
350 | )
351 | self.telemetry.reasoning_latency = reasoning_response.response_duration
352 | reasoning = reasoning_response.choices[0].message.content
353 | return reasoning
354 |
355 | async def construct_convert_prompt(self, reasoning: str) -> list[MessageT]:
356 | convert_messages = compile_messages(
357 | messages=MD_EDIT_CONVERT_MESSAGES,
358 | variables={"reasoning": reasoning},
359 | )
360 | return convert_messages
361 |
362 | async def get_convert_response(self, request: FileOpRequest, messages: list[MessageT]) -> ChatCompletionResponse:
363 | chat_completion_request = ChatCompletionRequest(
364 | messages=messages,
365 | model=settings.convert_tool_calls_model,
366 | temperature=0,
367 | max_completion_tokens=8000,
368 | tools=[PPT_EDIT_TOOL_DEF, SEND_MESSAGE_TOOL_DEF],
369 | tool_choice="required",
370 | parallel_tool_calls=False,
371 | )
372 | convert_response = await chat_completion(
373 | request=chat_completion_request,
374 | provider=request.request_type,
375 | client=request.context if request.request_type == "mcp" else request.chat_completion_client, # type: ignore
376 | )
377 | self.telemetry.convert_latency = convert_response.response_duration
378 | return convert_response
379 |
380 | async def execute_tool_calls(
381 | self, request: FileOpRequest, convert_response: ChatCompletionResponse
382 | ) -> tuple[str, str]:
383 | updated_doc_markdown = request.file_content
384 | output_message = ""
385 | if convert_response.choices[0].message.tool_calls:
386 | tool_call = convert_response.choices[0].message.tool_calls[0].function
387 | logger.info(f"Tool call:\n{tool_call}")
388 | # If the the model called the send_message, don't update the doc and return the message
389 | if tool_call.name == SEND_MESSAGE_TOOL_NAME:
390 | output_message = settings.doc_editor_prefix + convert_response.choices[0].message.content
391 | elif tool_call.name == MD_EDIT_TOOL_NAME:
392 | tool_args = tool_call.arguments
393 | blocks = await self.blockify(request)
394 | blocks = execute_tools(blocks=blocks, edit_tool_call={"name": tool_call.name, "arguments": tool_args})
395 | updated_doc_markdown = await self.unblockify(blocks)
396 | else:
397 | output_message = (
398 | settings.doc_editor_prefix + "Something went wrong when editing the document and no changes were made."
399 | )
400 | return updated_doc_markdown, output_message
401 |
402 | async def run_change_summary(self, before_doc: str, after_doc: str, edit_request: FileOpRequest) -> str:
403 | change_summary_messages = compile_messages(
404 | messages=MD_EDIT_CHANGES_MESSAGES,
405 | variables={
406 | "before_doc": before_doc,
407 | "after_doc": after_doc,
408 | },
409 | )
410 | change_summary_response = await chat_completion(
411 | request=ChatCompletionRequest(
412 | messages=change_summary_messages,
413 | model=settings.summarization_model,
414 | max_completion_tokens=1000,
415 | ),
416 | provider=edit_request.request_type,
417 | client=edit_request.context if edit_request.request_type == "mcp" else edit_request.chat_completion_client, # type: ignore
418 | )
419 | self.telemetry.change_summary_latency = change_summary_response.response_duration
420 | change_summary = change_summary_response.choices[0].message.content
421 | change_summary = settings.doc_editor_prefix + change_summary
422 | return change_summary
423 |
424 | async def run(self, request: FileOpRequest) -> EditOutput:
425 | self.telemetry.reset()
426 | blockified_doc = await self.blockify(request)
427 | reasoning_messages = await self.construct_reasoning_prompt(request, blockified_doc)
428 | reasoning = await self.get_reasoning_response(request, reasoning_messages)
429 | logger.info(f"Reasoning:\n{reasoning}")
430 | convert_messages = await self.construct_convert_prompt(reasoning)
431 | convert_response = await self.get_convert_response(request, convert_messages)
432 | tool_calls = convert_response.choices[0].message.tool_calls or []
433 | updated_doc_markdown, output_message = await self.execute_tool_calls(request, convert_response)
434 | change_summary = await self.run_change_summary(
435 | before_doc=request.file_content,
436 | after_doc=updated_doc_markdown,
437 | edit_request=request,
438 | )
439 | output = EditOutput(
440 | change_summary=change_summary,
441 | output_message=output_message,
442 | new_content=updated_doc_markdown,
443 | reasoning=reasoning,
444 | tool_calls=tool_calls,
445 | llm_latency=self.telemetry.reasoning_latency
446 | + self.telemetry.convert_latency
447 | + self.telemetry.change_summary_latency,
448 | )
449 | return output
450 |
451 |
452 | # endregion
453 |
```
--------------------------------------------------------------------------------
/workbench-service/semantic_workbench_service/db.py:
--------------------------------------------------------------------------------
```python
1 | import datetime
2 | import logging
3 | import pathlib
4 | import uuid
5 | from contextlib import asynccontextmanager
6 | from typing import Annotated, Any, AsyncIterator
7 | from urllib.parse import urlparse
8 |
9 | import sqlalchemy
10 | import sqlalchemy.event
11 | import sqlalchemy.orm
12 | import sqlalchemy.orm.attributes
13 | from sqlalchemy.dialects import postgresql
14 | from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine
15 | from sqlmodel import Field, Relationship, Session, SQLModel, select
16 | from sqlmodel.ext.asyncio.session import AsyncSession
17 |
18 | from . import service_user_principals
19 | from .config import DBSettings
20 |
21 | # Download DB Browser for SQLite to view the database
22 | # https://sqlitebrowser.org/dl/
23 |
24 | logger = logging.getLogger(__name__)
25 |
26 |
27 | def _date_time_nullable() -> Any: # noqa: ANN401
28 | return Field(sa_column=sqlalchemy.Column(sqlalchemy.DateTime(timezone=True), nullable=True))
29 |
30 |
31 | def date_time_default_to_now(index: bool | None = None) -> Any: # noqa: ANN401
32 | return Field(
33 | sa_column=sqlalchemy.Column(
34 | sqlalchemy.DateTime(timezone=True),
35 | nullable=False,
36 | index=index,
37 | default=lambda: datetime.datetime.now(datetime.UTC),
38 | ),
39 | default_factory=lambda: datetime.datetime.now(datetime.UTC),
40 | )
41 |
42 |
43 | class User(SQLModel, table=True):
44 | user_id: str = Field(primary_key=True)
45 | created_datetime: datetime.datetime = date_time_default_to_now()
46 | name: str
47 | image: str | None = None
48 | service_user: bool = False
49 |
50 | def on_update(self, session: Session) -> None:
51 | # update UserParticipants for this user
52 | participants = session.exec(select(UserParticipant).where(UserParticipant.user_id == self.user_id))
53 | for participant in participants:
54 | participant.name = self.name
55 | participant.image = self.image
56 | participant.service_user = self.service_user
57 | session.add(participant)
58 |
59 |
60 | class AssistantServiceRegistration(SQLModel, table=True):
61 | assistant_service_id: str = Field(primary_key=True)
62 | created_by_user_id: str = Field(foreign_key="user.user_id")
63 | created_datetime: datetime.datetime = date_time_default_to_now()
64 | name: str
65 | description: str
66 | include_in_listing: bool = True
67 | api_key_name: str
68 |
69 | assistant_service_url: str = ""
70 | assistant_service_online_expiration_datetime: Annotated[datetime.datetime | None, _date_time_nullable()] = None
71 | assistant_service_online: bool = False
72 |
73 | related_created_by_user: User = Relationship(sa_relationship_kwargs={"lazy": "selectin"})
74 |
75 |
76 | class Assistant(SQLModel, table=True):
77 | assistant_id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
78 | owner_id: str = Field(foreign_key="user.user_id", index=True)
79 | assistant_service_id: str = Field(
80 | sa_column=sqlalchemy.Column(
81 | sqlalchemy.ForeignKey(
82 | "assistantserviceregistration.assistant_service_id",
83 | name="fk_assistant_assistant_service_id_assistantserviceregistration",
84 | ondelete="CASCADE",
85 | ),
86 | nullable=False,
87 | ),
88 | )
89 | template_id: str
90 | created_datetime: datetime.datetime = date_time_default_to_now()
91 | imported_from_assistant_id: uuid.UUID | None
92 | name: str
93 | image: str | None = None
94 | meta_data: dict[str, Any] = Field(sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON), default={})
95 |
96 | # this relationship is needed to enforce correct INSERT order by SQLModel
97 | related_owner: User = Relationship()
98 | related_assistant_service_registration: sqlalchemy.orm.Mapped[AssistantServiceRegistration] = Relationship(
99 | sa_relationship_kwargs={"lazy": "selectin"},
100 | )
101 |
102 | def on_update(self, session: Session) -> None:
103 | # update AssistantParticipants for this assistant
104 | participants = session.exec(
105 | select(AssistantParticipant).where(AssistantParticipant.assistant_id == self.assistant_id),
106 | )
107 | for participant in participants:
108 | participant.name = self.name
109 | participant.image = self.image
110 | session.add(participant)
111 |
112 |
113 | class Conversation(SQLModel, table=True):
114 | conversation_id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
115 | created_datetime: datetime.datetime = date_time_default_to_now()
116 | owner_id: str = Field(foreign_key="user.user_id")
117 | title: str
118 | meta_data: dict[str, Any] = Field(sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON), default={})
119 | imported_from_conversation_id: uuid.UUID | None
120 |
121 | # this relationship is needed to enforce correct INSERT order by SQLModel
122 | related_owner: sqlalchemy.orm.Mapped[User] = Relationship()
123 |
124 |
125 | class ConversationShare(SQLModel, table=True):
126 | conversation_share_id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
127 | conversation_id: uuid.UUID = Field(
128 | sa_column=sqlalchemy.Column(
129 | sqlalchemy.ForeignKey(
130 | "conversation.conversation_id",
131 | name="fk_file_conversation_id_conversation",
132 | ondelete="CASCADE",
133 | ),
134 | nullable=False,
135 | ),
136 | )
137 | created_datetime: datetime.datetime = date_time_default_to_now()
138 | owner_id: str = Field(foreign_key="user.user_id")
139 | label: str
140 | meta_data: dict[str, Any] = Field(sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON), default={})
141 |
142 | conversation_permission: str
143 |
144 | is_redeemable: bool = True
145 |
146 | # these relationships are needed to enforce correct INSERT order by SQLModel
147 | related_owner: sqlalchemy.orm.Mapped[User] = Relationship(
148 | sa_relationship_kwargs={"lazy": "selectin"},
149 | )
150 | related_conversation: sqlalchemy.orm.Mapped[Conversation] = Relationship(
151 | sa_relationship_kwargs={"lazy": "selectin"},
152 | )
153 |
154 |
155 | class ConversationShareRedemption(SQLModel, table=True):
156 | conversation_share_redemption_id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
157 | conversation_share_id: uuid.UUID = Field(
158 | sa_column=sqlalchemy.Column(
159 | sqlalchemy.ForeignKey(
160 | "conversationshare.conversation_share_id",
161 | name="fk_conversationshareredemption_conversation_share_id",
162 | ondelete="CASCADE",
163 | ),
164 | nullable=False,
165 | ),
166 | )
167 | conversation_id: uuid.UUID
168 | conversation_permission: str
169 | new_participant: bool
170 | redeemed_by_user_id: str = Field(
171 | sa_column=sqlalchemy.Column(
172 | sqlalchemy.ForeignKey(
173 | "user.user_id",
174 | name="fk_conversationshareredemption_user_id_user",
175 | ondelete="CASCADE",
176 | ),
177 | nullable=False,
178 | ),
179 | )
180 | created_datetime: datetime.datetime = date_time_default_to_now()
181 |
182 | # these relationships are needed to enforce correct INSERT order by SQLModel
183 | related_conversation_share: sqlalchemy.orm.Mapped[ConversationShare] = Relationship()
184 | related_redeemed_by_user: sqlalchemy.orm.Mapped[User] = Relationship(
185 | sa_relationship_kwargs={"lazy": "selectin"},
186 | )
187 |
188 |
189 | class AssistantParticipant(SQLModel, table=True):
190 | conversation_id: uuid.UUID = Field(
191 | sa_column=sqlalchemy.Column(
192 | sqlalchemy.ForeignKey(
193 | "conversation.conversation_id",
194 | name="fk_assistantparticipant_conversation_id_conversation",
195 | ondelete="CASCADE",
196 | ),
197 | primary_key=True,
198 | nullable=False,
199 | ),
200 | )
201 | assistant_id: uuid.UUID = Field(primary_key=True)
202 | name: str = ""
203 | image: str | None = None
204 | joined_datetime: datetime.datetime = date_time_default_to_now()
205 | status: str | None = None
206 | status_updated_datetime: datetime.datetime = date_time_default_to_now()
207 | active_participant: bool = True
208 | meta_data: dict[str, Any] = Field(
209 | sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON, server_default="{}", nullable=False), default={}
210 | )
211 |
212 | # this relationship is needed to enforce correct INSERT order by SQLModel
213 | related_conversation: Conversation = Relationship()
214 |
215 | def on_update(self, session: Session) -> None:
216 | # update this participant to match the related assistant, if one exists
217 | assistant = session.exec(select(Assistant).where(Assistant.assistant_id == self.assistant_id)).one_or_none()
218 | if assistant is None:
219 | return
220 |
221 | sqlalchemy.orm.attributes.set_attribute(self, "name", assistant.name)
222 | sqlalchemy.orm.attributes.set_attribute(self, "image", assistant.image)
223 |
224 | def on_insert(self, session: Session) -> None:
225 | # update this participant to match the related assistant, requiring one to exist
226 | assistant = session.exec(select(Assistant).where(Assistant.assistant_id == self.assistant_id)).one()
227 | sqlalchemy.orm.attributes.set_attribute(self, "name", assistant.name)
228 | sqlalchemy.orm.attributes.set_attribute(self, "image", assistant.image)
229 |
230 |
231 | class UserParticipant(SQLModel, table=True):
232 | conversation_id: uuid.UUID = Field(
233 | sa_column=sqlalchemy.Column(
234 | sqlalchemy.ForeignKey(
235 | "conversation.conversation_id",
236 | name="fk_userparticipant_conversation_id_conversation",
237 | ondelete="CASCADE",
238 | ),
239 | primary_key=True,
240 | nullable=False,
241 | ),
242 | )
243 | user_id: str = Field(primary_key=True)
244 | name: str = ""
245 | image: str | None = None
246 | service_user: bool = False
247 | joined_datetime: datetime.datetime = date_time_default_to_now()
248 | status: str | None = None
249 | status_updated_datetime: datetime.datetime = date_time_default_to_now()
250 | active_participant: bool = True
251 | meta_data: dict[str, Any] = Field(
252 | sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON, server_default="{}", nullable=False), default={}
253 | )
254 | conversation_permission: str
255 |
256 | # this relationship is needed to enforce correct INSERT order by SQLModel
257 | related_conversation: Conversation = Relationship()
258 |
259 | def on_update(self, session: Session) -> None:
260 | # update this participant to match the related user, if one exists
261 | user = session.exec(select(User).where(User.user_id == self.user_id)).one_or_none()
262 | if user is None:
263 | return
264 |
265 | sqlalchemy.orm.attributes.set_attribute(self, "name", user.name)
266 | sqlalchemy.orm.attributes.set_attribute(self, "image", user.image)
267 | sqlalchemy.orm.attributes.set_attribute(self, "service_user", user.service_user)
268 |
269 | def on_insert(self, session: Session) -> None:
270 | # update this participant to match the related user, requiring one to exist
271 | user = session.exec(select(User).where(User.user_id == self.user_id)).one()
272 |
273 | sqlalchemy.orm.attributes.set_attribute(self, "name", user.name)
274 | sqlalchemy.orm.attributes.set_attribute(self, "image", user.image)
275 | sqlalchemy.orm.attributes.set_attribute(self, "service_user", user.service_user)
276 |
277 |
278 | class ConversationMessage(SQLModel, table=True):
279 | sequence: int = Field(default=None, nullable=False, primary_key=True)
280 | message_id: uuid.UUID = Field(default_factory=uuid.uuid4, unique=True)
281 | conversation_id: uuid.UUID = Field(
282 | sa_column=sqlalchemy.Column(
283 | sqlalchemy.ForeignKey(
284 | "conversation.conversation_id",
285 | name="fk_conversationmessage_conversation_id_conversation",
286 | ondelete="CASCADE",
287 | ),
288 | nullable=False,
289 | ),
290 | )
291 | created_datetime: datetime.datetime = date_time_default_to_now()
292 | sender_participant_id: str
293 | sender_participant_role: str
294 | message_type: str = Field(index=True)
295 | content: str
296 | content_type: str
297 | meta_data: dict[str, Any] = Field(sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON), default={})
298 | filenames: list[str] = Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON), default=[])
299 |
300 | # this relationship is needed to enforce correct INSERT order by SQLModel
301 | related_conversation: Conversation = Relationship()
302 |
303 |
304 | class ConversationMessageDebug(SQLModel, table=True):
305 | message_id: uuid.UUID = Field(
306 | sa_column=sqlalchemy.Column(
307 | sqlalchemy.ForeignKey(
308 | "conversationmessage.message_id",
309 | name="fk_conversationmessagedebug_message_id_conversationmessage",
310 | ondelete="CASCADE",
311 | ),
312 | nullable=False,
313 | primary_key=True,
314 | ),
315 | )
316 | data: dict[str, Any] = Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON, nullable=False), default={})
317 |
318 | # this relationship is needed to enforce correct INSERT order by SQLModel
319 | related_messag: ConversationMessage = Relationship()
320 |
321 |
322 | class File(SQLModel, table=True):
323 | file_id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
324 | conversation_id: uuid.UUID = Field(
325 | sa_column=sqlalchemy.Column(
326 | sqlalchemy.ForeignKey(
327 | "conversation.conversation_id",
328 | name="fk_file_conversation_id_conversation",
329 | ondelete="CASCADE",
330 | ),
331 | nullable=False,
332 | ),
333 | )
334 |
335 | filename: str
336 | current_version: int
337 | created_datetime: datetime.datetime = date_time_default_to_now(index=True)
338 |
339 | # this relationship is needed to enforce correct INSERT order by SQLModel
340 | related_conversation: Conversation = Relationship()
341 |
342 | __table_args__ = (
343 | sqlalchemy.UniqueConstraint("conversation_id", "filename", name="uq_file_conversation_id_filename"),
344 | )
345 |
346 |
347 | class FileVersion(SQLModel, table=True):
348 | file_id: uuid.UUID = Field(
349 | sa_column=sqlalchemy.Column(
350 | sqlalchemy.ForeignKey(
351 | "file.file_id",
352 | name="fk_fileversion_file_id_file",
353 | ondelete="CASCADE",
354 | ),
355 | primary_key=True,
356 | nullable=False,
357 | ),
358 | )
359 | version: int = Field(primary_key=True)
360 | participant_id: str
361 | participant_role: str
362 | created_datetime: datetime.datetime = date_time_default_to_now(index=True)
363 | meta_data: dict[str, Any] = Field(sa_column=sqlalchemy.Column("metadata", sqlalchemy.JSON), default={})
364 | content_type: str
365 | file_size: int
366 | storage_filename: str
367 |
368 | # this relationship is needed to enforce correct INSERT order by SQLModel
369 | related_file: File = Relationship()
370 |
371 |
372 | NAMING_CONVENTION = {
373 | "ix": "ix_%(column_0_label)s",
374 | "uq": "uq_%(table_name)s_%(column_0_N_name)s",
375 | "ck": "ck_%(table_name)s_%(constraint_name)s",
376 | "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
377 | "pk": "pk_%(table_name)s",
378 | }
379 | SQLModel.metadata.naming_convention = NAMING_CONVENTION
380 |
381 |
382 | def ensure_async_driver_scheme(url: str) -> str:
383 | return url.replace("sqlite://", "sqlite+aiosqlite://").replace("postgresql://", "postgresql+asyncpg://")
384 |
385 |
386 | @sqlalchemy.event.listens_for(sqlalchemy.Pool, "connect")
387 | def set_sqlite_pragma(
388 | dbapi_connection: sqlalchemy.engine.interfaces.DBAPIConnection,
389 | _: sqlalchemy.pool.ConnectionPoolEntry,
390 | ) -> None:
391 | if hasattr(sqlalchemy.dialects, "sqlite"):
392 | cursor = dbapi_connection.cursor()
393 | cursor.execute("PRAGMA journal_mode=WAL")
394 | cursor.close()
395 |
396 |
397 | @asynccontextmanager
398 | async def create_engine(settings: DBSettings) -> AsyncIterator[AsyncEngine]:
399 | # ensure that the database url is using the async driver
400 | db_url = ensure_async_driver_scheme(settings.url)
401 | parsed_url = urlparse(db_url)
402 | is_sqlite = parsed_url.scheme.startswith("sqlite")
403 | is_postgres = parsed_url.scheme.startswith("postgresql")
404 |
405 | url_for_log = db_url
406 | if parsed_url.password:
407 | url_for_log = url_for_log.replace(parsed_url.password, "****")
408 | logger.info("creating database engine for %s", url_for_log)
409 |
410 | if is_sqlite and "/" in parsed_url.path:
411 | # create parent directory for sqlite db file as a convenience
412 | file_path = parsed_url.path[1:]
413 | pathlib.Path(file_path).parent.mkdir(parents=True, exist_ok=True)
414 |
415 | kw_args: dict = {"echo": settings.echosql, "future": True}
416 | if is_postgres:
417 | kw_args.update({
418 | "connect_args": {
419 | "ssl": settings.postgresql_ssl_mode,
420 | },
421 | "pool_pre_ping": True,
422 | "pool_size": settings.postgresql_pool_size,
423 | })
424 |
425 | engine = create_async_engine(db_url, **kw_args)
426 |
427 | try:
428 | yield engine
429 | finally:
430 | await engine.dispose()
431 |
432 |
433 | @sqlalchemy.event.listens_for(Session, "before_flush")
434 | def _session_before_flush(session: Session, flush_context, instances) -> None: # noqa: ANN001, ARG001
435 | for obj in session.dirty:
436 | if not hasattr(obj, "on_update"):
437 | continue
438 | obj.on_update(session)
439 |
440 | for obj in session.new:
441 | if not hasattr(obj, "on_insert"):
442 | continue
443 | obj.on_insert(session)
444 |
445 |
446 | async def bootstrap_db(engine: AsyncEngine, settings: DBSettings) -> None:
447 | logger.info("bootstrapping database")
448 | await _ensure_schema(engine=engine, settings=settings)
449 | await _create_default_data(engine=engine)
450 |
451 |
452 | async def _ensure_schema(engine: AsyncEngine, settings: DBSettings) -> None:
453 | def execute_ensure_version(connection: sqlalchemy.Connection) -> None:
454 | from alembic import command, config
455 |
456 | cfg = config.Config(settings.alembic_config_path)
457 | cfg.attributes["connection"] = connection
458 | command.ensure_version(cfg)
459 |
460 | async with engine.begin() as conn:
461 | await conn.run_sync(execute_ensure_version)
462 |
463 | alembic_version_exists = False
464 | async with engine.begin() as conn:
465 | row = (await conn.exec_driver_sql("SELECT count(version_num) FROM alembic_version")).one()
466 | alembic_version_exists = row[0] > 0
467 |
468 | if not alembic_version_exists:
469 | return await _create_schema(engine=engine, alembic_config_path=settings.alembic_config_path)
470 |
471 | return await _migrate_schema(engine=engine, alembic_config_path=settings.alembic_config_path)
472 |
473 |
474 | async def _migrate_schema(engine: AsyncEngine, alembic_config_path: str) -> None:
475 | from alembic import command, config
476 |
477 | logger.info("migrating database schema; alembic_config_path=%s", alembic_config_path)
478 |
479 | def execute_upgrade(connection: sqlalchemy.Connection) -> None:
480 | logger.info("running alembic upgrade to head")
481 | cfg = config.Config(alembic_config_path)
482 | cfg.attributes["connection"] = connection
483 | command.upgrade(cfg, "head")
484 |
485 | def execute_check(connection: sqlalchemy.Connection) -> None:
486 | logger.info("running alembic check")
487 | cfg = config.Config(alembic_config_path)
488 | cfg.attributes["connection"] = connection
489 | command.check(cfg)
490 |
491 | async with engine.begin() as conn:
492 | await conn.run_sync(execute_upgrade)
493 | await conn.run_sync(execute_check)
494 |
495 | return None
496 |
497 |
498 | async def _create_schema(engine: AsyncEngine, alembic_config_path: str) -> None:
499 | logger.info("creating database schema; alembic_config_path=%s", alembic_config_path)
500 |
501 | def execute_stamp_head(connection: sqlalchemy.Connection) -> None:
502 | from alembic import command, config
503 |
504 | cfg = config.Config(alembic_config_path)
505 | cfg.attributes["connection"] = connection
506 | command.stamp(cfg, "head")
507 |
508 | async with engine.begin() as conn:
509 | await conn.run_sync(SQLModel.metadata.create_all)
510 | await conn.run_sync(execute_stamp_head)
511 |
512 |
513 | async def _create_default_data(engine: AsyncEngine) -> None:
514 | async with create_session(engine) as session:
515 | workbench_user = User(
516 | user_id=service_user_principals.semantic_workbench.user_id,
517 | name=service_user_principals.semantic_workbench.name,
518 | service_user=True,
519 | )
520 | await insert_if_not_exists(session, workbench_user)
521 | await session.commit()
522 |
523 |
524 | @asynccontextmanager
525 | async def create_session(engine: AsyncEngine) -> AsyncIterator[AsyncSession]:
526 | session_maker = async_sessionmaker(
527 | bind=engine,
528 | class_=AsyncSession,
529 | expire_on_commit=False,
530 | autocommit=False,
531 | autoflush=False,
532 | )
533 | async with session_maker() as async_session:
534 | yield async_session
535 |
536 |
537 | async def insert_if_not_exists(session: AsyncSession, model: SQLModel) -> bool:
538 | """
539 | Inserts the provided record if a row with the same primary key(s) does already exist in the table.
540 | Returns True if the record was inserted, False if it already existed.
541 | """
542 |
543 | # the postgresql.insert function is used to generate an INSERT statement with an ON CONFLICT DO NOTHING clause.
544 | # note that sqlite also supports ON CONFLICT DO NOTHING, so this works with both database types.
545 | statement = (
546 | postgresql.insert(model.__class__).values(**model.model_dump(exclude_unset=True)).on_conflict_do_nothing()
547 | )
548 | conn = await session.connection()
549 | result = await conn.execute(statement)
550 | return result.rowcount > 0
551 |
```
--------------------------------------------------------------------------------
/libraries/python/openai-client/openai_client/tools.py:
--------------------------------------------------------------------------------
```python
1 | import ast
2 | import inspect
3 | import json
4 | from collections.abc import Callable, Iterable
5 | from dataclasses import dataclass
6 | from typing import Any
7 |
8 | from openai import (
9 | NOT_GIVEN,
10 | AsyncOpenAI,
11 | NotGiven,
12 | )
13 | from openai.types.chat import (
14 | ChatCompletionMessageParam,
15 | ChatCompletionToolParam,
16 | ParsedChatCompletion,
17 | ParsedFunctionToolCall,
18 | )
19 | from openai.types.shared_params.function_definition import FunctionDefinition
20 | from pydantic import BaseModel, create_model
21 | from pydantic.fields import FieldInfo
22 |
23 | from . import logger
24 | from .completion import assistant_message_from_completion
25 | from .errors import CompletionError, validate_completion
26 | from .logging import (
27 | add_serializable_data,
28 | make_completion_args_serializable,
29 | serializable,
30 | )
31 |
32 |
33 | def to_string(value: Any) -> str:
34 | """
35 | Convert a value to a string. This is a helper function to get the response
36 | of a tool function call into a message.
37 | """
38 | if value is None:
39 | return "Function executed successfully."
40 | elif isinstance(value, str):
41 | return value
42 | elif isinstance(value, int | float):
43 | return str(value)
44 | elif isinstance(value, dict):
45 | return json.dumps(value)
46 | elif isinstance(value, list):
47 | return json.dumps(value, indent=2)
48 | elif isinstance(value, tuple):
49 | return json.dumps(value)
50 | elif isinstance(value, BaseModel):
51 | return value.model_dump_json(indent=2)
52 | else:
53 | return str(value)
54 |
55 |
56 | def function_list_to_tool_choice(
57 | functions: list[str] | None,
58 | ) -> Iterable[ChatCompletionToolParam] | None:
59 | """
60 | Convert a list of function names to a list of ChatCompletionToolParam
61 | objects. This is used in the Chat Completions API if you want to tell the
62 | completion it MUST use a specific set of tool functions.
63 | """
64 | if not functions:
65 | return None
66 | return [
67 | ChatCompletionToolParam(type="function", function={"name": name})
68 | for name in functions
69 | ] or None
70 |
71 |
72 | @dataclass
73 | class Parameter:
74 | """
75 | Tool functions are described by their parameters. This dataclass
76 | describes a single parameter of a tool function.
77 | """
78 |
79 | name: str
80 | type: Any
81 | description: str | None
82 | default_value: Any | None = None
83 |
84 |
85 | class ToolFunction:
86 | """
87 | A tool function is a Python function that can be called as a tool from the
88 | chat completion API. This class wraps a function so you can generate it's
89 | JSON schema for the chat completion API, execute it with arguments, and
90 | generate a usage string (for help messages)
91 | """
92 |
93 | def __init__(
94 | self, fn: Callable, name: str | None = None, description: str | None = None
95 | ) -> None:
96 | self.fn = fn
97 | self.name = name or fn.__name__
98 | self.description = (
99 | description or inspect.getdoc(fn) or self.name.replace("_", " ").title()
100 | )
101 |
102 | def parameters(self, exclude: list[str] | None = None) -> list[Parameter]:
103 | """
104 | This function's parameters and their default values.
105 | """
106 | if exclude is None:
107 | exclude = []
108 | parameters = dict(inspect.signature(self.fn).parameters)
109 | for param_name in exclude:
110 | del parameters[param_name]
111 | return [
112 | Parameter(
113 | name=param_name,
114 | type=param.annotation,
115 | description=None, # param.annotation.description,
116 | default_value=param.default,
117 | )
118 | for param_name, param in parameters.items()
119 | ]
120 |
121 | def usage(self) -> str:
122 | """
123 | A usage string for this function. This can be used in help messages.
124 | """
125 | name = self.name
126 | param_usages = []
127 | for param in self.parameters():
128 | param_type = param.type
129 | try:
130 | param_type = param.type.__name__
131 | except AttributeError:
132 | param_type = param.type
133 | usage = f"{param.name}: {param_type}"
134 | if param.default_value is not inspect.Parameter.empty:
135 | if isinstance(param.default_value, str):
136 | usage += f' = "{param.default_value}"'
137 | else:
138 | usage += f" = {param.default_value}"
139 | param_usages.append(usage)
140 |
141 | description = self.description
142 | return f"{name}({', '.join(param_usages)}): {description}"
143 |
144 | def schema(self, strict: bool = True) -> dict[str, Any]:
145 | """
146 | Generate a JSON schema for this function that is suitable for the OpenAI
147 | completion API.
148 | """
149 |
150 | # Create the Pydantic model using create_model.
151 | model_name = self.fn.__name__.title().replace("_", "")
152 | fields = {}
153 | for parameter in self.parameters():
154 | field_info = FieldInfo(description=parameter.description)
155 | if parameter.default_value is not inspect.Parameter.empty:
156 | field_info.default = parameter.default_value
157 | fields[parameter.name] = (
158 | parameter.type,
159 | field_info,
160 | )
161 | pydantic_model = create_model(model_name, **fields)
162 |
163 | # Generate the JSON schema from the Pydantic model.
164 | parameters_schema = pydantic_model.model_json_schema(mode="serialization")
165 |
166 | # Remove title attribute from all properties (not allowed by the Chat
167 | # Completions API).
168 | properties = parameters_schema["properties"]
169 | for property_key in properties:
170 | if "title" in properties[property_key]:
171 | del properties[property_key]["title"]
172 |
173 | # And from the top-level object.
174 | if "title" in parameters_schema:
175 | del parameters_schema["title"]
176 |
177 | # Output a schema that matches OpenAI's "tool" format.
178 | # e.g., https://platform.openai.com/docs/guides/function-calling
179 | # We use this because they trained GPT on it.
180 | schema = {
181 | # "$schema": "http://json-schema.org/draft-07/schema#",
182 | # "$id": f"urn:jsonschema:{name}",
183 | "name": self.name,
184 | "description": self.description,
185 | "strict": strict,
186 | "parameters": {
187 | "type": "object",
188 | "properties": parameters_schema["properties"],
189 | },
190 | }
191 |
192 | # If this is a strict schema, OpenAI requires additionalProperties to be
193 | # False. "strict mode" is required for JSON or structured output from
194 | # the API.
195 | if strict:
196 | schema["parameters"]["additionalProperties"] = False
197 |
198 | # Add required fields (another Chat Completions API requirement).
199 | if "required" in parameters_schema:
200 | schema["parameters"]["required"] = parameters_schema["required"]
201 |
202 | # Add type definitions (another Chat Completions API requirement).
203 | if "$defs" in parameters_schema:
204 | schema["parameters"]["$defs"] = parameters_schema["$defs"]
205 | for key in schema["parameters"]["$defs"]:
206 | schema["parameters"]["$defs"][key]["additionalProperties"] = False
207 |
208 | return schema
209 |
210 | async def execute(self, *args, **kwargs) -> Any:
211 | """
212 | Run this function, and return its value. If the function is a coroutine,
213 | it will be awaited. If string_response is True, the response will be
214 | converted to a string.
215 | """
216 | result = self.fn(*args, **kwargs)
217 | if inspect.iscoroutine(result):
218 | result = await result
219 | return result
220 |
221 |
222 | class FunctionHandler:
223 | def __init__(self, tool_functions: "ToolFunctions") -> None:
224 | self.tool_functions = tool_functions
225 |
226 | def __getattr__(self, name: str) -> Callable:
227 | """Makes registered functions accessible as attributes of the functions object."""
228 | if name not in self.tool_functions.function_map:
229 | raise AttributeError(f"'FunctionHandler' object has no attribute '{name}'")
230 |
231 | async def wrapper(*args, **kwargs) -> Any:
232 | return await self.tool_functions.execute_function(name, args, kwargs)
233 |
234 | return wrapper
235 |
236 |
237 | class ToolFunctions:
238 | """
239 | A set of tool functions that can be called from the Chat Completions API.
240 | Pass this into the `complete_with_tool_calls` helper function to run a full
241 | tool-call completion against the API.
242 | """
243 |
244 | def __init__(
245 | self, functions: list[ToolFunction] | None = None, with_help: bool = False
246 | ) -> None:
247 | # Set up function map.
248 | self.function_map: dict[str, ToolFunction] = {}
249 | if functions:
250 | for function in functions:
251 | self.function_map[function.name] = function
252 |
253 | # A help message can be generated for the function map.
254 | if with_help:
255 | self.function_map["help"] = ToolFunction(self.help)
256 |
257 | # This allows actions to be called as attributes.
258 | self.functions = FunctionHandler(self)
259 |
260 | def help(self) -> str:
261 | """Return this help message."""
262 |
263 | usage = [f"{command.usage()}" for command in self.function_map.values()]
264 | usage.sort()
265 | return "```text\nCommands:\n" + "\n".join(usage) + "\n```"
266 |
267 | def add_function(
268 | self,
269 | function: Callable,
270 | name: str | None = None,
271 | description: str | None = None,
272 | ) -> None:
273 | """Register a function with the tool functions."""
274 | if not name:
275 | name = function.__name__
276 | self.function_map[name] = ToolFunction(function, name, description)
277 |
278 | def has_function(self, name: str) -> bool:
279 | return name in self.function_map
280 |
281 | def get_function(self, name: str) -> ToolFunction | None:
282 | return self.function_map.get(name)
283 |
284 | def get_functions(self) -> list[ToolFunction]:
285 | return list(self.function_map.values())
286 |
287 | async def execute_function(
288 | self,
289 | name: str,
290 | args: tuple = (),
291 | kwargs: dict[str, Any] | None = None,
292 | string_response: bool = False,
293 | ) -> Any:
294 | """
295 | Run a function from the ToolFunctions list by name. If string_response
296 | is True, the function return value will be converted to a string.
297 | """
298 | if kwargs is None:
299 | kwargs = {}
300 | function = self.get_function(name)
301 | if not function:
302 | raise ValueError(f"Function {name} not found in registry.")
303 | response = await function.execute(*args, **kwargs)
304 | if string_response:
305 | return to_string(response)
306 |
307 | async def execute_function_string(
308 | self, function_string: str, string_response: bool = False
309 | ) -> Any:
310 | """Parse a function string and execute the function."""
311 | try:
312 | function, args, kwargs = self.parse_function_string(function_string)
313 | except ValueError as e:
314 | raise ValueError(f"{e} Type: `/help` for more information.") from e
315 | if not function:
316 | raise ValueError(
317 | "Function not found in registry. Type: `/help` for more information."
318 | )
319 | result = await function.execute(*args, **kwargs)
320 | if string_response:
321 | return to_string(result)
322 |
323 | @staticmethod
324 | def parse_fn_string(
325 | function_string: str,
326 | ) -> tuple[str | None, list[Any], dict[str, Any]]:
327 | """
328 | Parse a string representing a function call into its name, positional
329 | arguments, and keyword arguments.
330 | """
331 |
332 | # As a convenience, remove any leading slashes.
333 | function_string = function_string.lstrip("/")
334 |
335 | # As a convenience, add parentheses if they are missing.
336 | if " " not in function_string and "(" not in function_string:
337 | function_string += "()"
338 |
339 | # Parse the string into an AST (Abstract Syntax Tree)
340 | try:
341 | tree = ast.parse(function_string)
342 | except SyntaxError as err:
343 | raise ValueError(
344 | "Invalid function call. Please check your syntax."
345 | ) from err
346 |
347 | # Ensure the tree contains exactly one expression (the function call)
348 | if not (
349 | isinstance(tree, ast.Module)
350 | and len(tree.body) == 1
351 | and isinstance(tree.body[0], ast.Expr)
352 | ):
353 | raise ValueError("Expected a single function call.")
354 |
355 | # The function call is stored as a `Call` node within the expression
356 | call_node = tree.body[0].value
357 | if not isinstance(call_node, ast.Call):
358 | raise ValueError("Invalid function call. Please check your syntax.")
359 |
360 | # Extract the function name
361 | if isinstance(call_node.func, ast.Name):
362 | function_name = call_node.func.id
363 | else:
364 | raise ValueError("Unsupported function format. Please check your syntax.")
365 |
366 | # Helper function to evaluate AST nodes to their Python equivalent
367 | def eval_node(node):
368 | if isinstance(node, ast.Constant):
369 | return node.value
370 | elif isinstance(node, ast.List):
371 | return [eval_node(elem) for elem in node.elts]
372 | elif isinstance(node, ast.Tuple):
373 | return tuple(eval_node(elem) for elem in node.elts)
374 | elif isinstance(node, ast.Dict):
375 | return {
376 | eval_node(key): eval_node(value)
377 | for key, value in zip(node.keys, node.values, strict=False)
378 | }
379 | elif isinstance(node, ast.Name):
380 | return (
381 | node.id
382 | ) # This can return variable names, but we assume they're constants
383 | elif isinstance(node, ast.BinOp): # Handling arithmetic expressions
384 | return eval(compile(ast.Expression(node), filename="", mode="eval"))
385 | elif isinstance(node, ast.Call):
386 | raise ValueError("Nested function calls are not supported.")
387 | else:
388 | raise ValueError(f"Unsupported AST node type: {type(node).__name__}")
389 |
390 | # Extract positional arguments
391 | args = [eval_node(arg) for arg in call_node.args]
392 |
393 | # Extract keyword arguments
394 | kwargs = {}
395 | for kw in call_node.keywords:
396 | kwargs[kw.arg] = eval_node(kw.value)
397 |
398 | return function_name, args, kwargs
399 |
400 | def parse_function_string(
401 | self, function_string: str
402 | ) -> tuple[ToolFunction | None, list[Any], dict[str, Any]]:
403 | """Parse a function call string into a function and its arguments."""
404 |
405 | function_name, args, kwargs = ToolFunctions.parse_fn_string(function_string)
406 | if not function_name:
407 | return None, [], {}
408 |
409 | function = self.get_function(function_name)
410 | if not function:
411 | return None, [], {}
412 |
413 | return function, args, kwargs
414 |
415 | def chat_completion_tools(self) -> list[ChatCompletionToolParam] | NotGiven:
416 | """
417 | Return a list of ChatCompletionToolParam objects that describe the tool
418 | functions in this ToolFunctions object. These can be passed to the Chat
419 | Completions API (in the "tools" parameter) to enable tool function
420 | calls.
421 | """
422 | tools = [
423 | ChatCompletionToolParam(
424 | type="function", function=FunctionDefinition(**func.schema())
425 | )
426 | for func in self.function_map.values()
427 | ]
428 | return tools or NOT_GIVEN
429 |
430 | async def execute_tool_call(
431 | self, tool_call: ParsedFunctionToolCall
432 | ) -> ChatCompletionMessageParam | None:
433 | """
434 | Execute a function as requested by a ParsedFunctionToolCall (generated
435 | by the Chat Completions API) and return the response as a
436 | ChatCompletionMessageParam message (as required by the Chat Completions
437 | API)
438 | """
439 | function = tool_call.function
440 | if self.has_function(function.name):
441 | logger.debug(
442 | "Function call.",
443 | extra=add_serializable_data(
444 | {"name": function.name, "arguments": function.arguments}
445 | ),
446 | )
447 | value: Any = None
448 | try:
449 | kwargs: dict[str, Any] = json.loads(function.arguments)
450 | value = await self.execute_function(
451 | function.name, (), kwargs, string_response=True
452 | )
453 | except Exception as e:
454 | logger.error("Error.", extra=add_serializable_data({"error": e}))
455 | value = f"Error: {e}"
456 | finally:
457 | logger.debug(
458 | "Function response.",
459 | extra=add_serializable_data(
460 | {"tool_call_id": tool_call.id, "content": value}
461 | ),
462 | )
463 | return {
464 | "role": "tool",
465 | "content": value,
466 | "tool_call_id": tool_call.id,
467 | }
468 | else:
469 | logger.error(f"Function not found: {function.name}")
470 | return None
471 |
472 |
473 | async def complete_with_tool_calls(
474 | async_client: AsyncOpenAI,
475 | completion_args: dict[str, Any],
476 | tool_functions: ToolFunctions,
477 | metadata: dict[str, Any] | None = None,
478 | max_tool_call_rounds: int = 5, # Adding a parameter to limit the maximum number of rounds
479 | ) -> tuple[ParsedChatCompletion | None, list[ChatCompletionMessageParam]]:
480 | """
481 | Complete a chat response with tool calls handled by the supplied tool
482 | functions. This function supports multiple rounds of tool calls, continuing
483 | until the model no longer requests tool calls or the maximum number of rounds is reached.
484 |
485 | Parameters:
486 |
487 | - async_client: The OpenAI client.
488 | - completion_args: The completion arguments passed onto the OpenAI `parse`
489 | call. See the OpenAI API docs for more information.
490 | - tool_functions: A ToolFunctions object that contains the tool functions to
491 | be available to be called.
492 | - metadata: Metadata to be added to the completion response.
493 | - max_tool_call_rounds: Maximum number of tool call rounds to prevent infinite loops (default: 5)
494 | """
495 | if metadata is None:
496 | metadata = {}
497 | messages: list[ChatCompletionMessageParam] = completion_args.get("messages", [])
498 | all_new_messages: list[ChatCompletionMessageParam] = []
499 | current_completion = None
500 | rounds = 0
501 |
502 | # Set up the tools if tool_functions exists.
503 | if tool_functions:
504 | # Note: this overwrites any existing tools.
505 | completion_args["tools"] = tool_functions.chat_completion_tools()
506 |
507 | # Keep making completions until no more tool calls are requested
508 | # or we hit the maximum number of rounds
509 | while rounds < max_tool_call_rounds:
510 | rounds += 1
511 | round_description = f"round {rounds}"
512 |
513 | current_args = {**completion_args, "messages": [*messages, *all_new_messages]}
514 | logger.debug(
515 | f"Completion call ({round_description}).",
516 | extra=add_serializable_data(
517 | make_completion_args_serializable(current_args)
518 | ),
519 | )
520 | metadata[f"completion_request ({round_description})"] = serializable(
521 | current_args
522 | )
523 |
524 | # Make the completion call
525 | try:
526 | current_completion = await async_client.beta.chat.completions.parse(
527 | **current_args,
528 | )
529 | validate_completion(current_completion)
530 | logger.debug(
531 | f"Completion response ({round_description}).",
532 | extra=add_serializable_data(
533 | {"completion": current_completion.model_dump()}
534 | ),
535 | )
536 | metadata[f"completion_response ({round_description})"] = (
537 | current_completion.model_dump()
538 | )
539 | except Exception as e:
540 | completion_error = CompletionError(e)
541 | metadata[f"completion_error ({round_description})"] = (
542 | completion_error.message
543 | )
544 | logger.error(
545 | completion_error.message,
546 | extra=add_serializable_data(
547 | {"completion_error": completion_error.body, "metadata": metadata}
548 | ),
549 | )
550 | raise completion_error from e
551 |
552 | # Extract assistant message from completion and add to new messages
553 | assistant_message = assistant_message_from_completion(current_completion)
554 | if assistant_message:
555 | all_new_messages.append(assistant_message)
556 |
557 | # Check for tool calls
558 | completion_message = current_completion.choices[0].message
559 | if not completion_message.tool_calls:
560 | # No more tool calls, we're done
561 | break
562 |
563 | # Call all tool functions and generate return messages
564 | round_tool_messages: list[ChatCompletionMessageParam] = []
565 | for tool_call in completion_message.tool_calls:
566 | function_call_result_message = await tool_functions.execute_tool_call(
567 | tool_call
568 | )
569 | if function_call_result_message:
570 | round_tool_messages.append(function_call_result_message)
571 | all_new_messages.append(function_call_result_message)
572 |
573 | return current_completion, all_new_messages
574 |
```